| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/device.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/io.h> |
| #include <linux/mcb.h> |
| #include <linux/serial.h> |
| #include <linux/serial_core.h> |
| #include <linux/serial_8250.h> |
| |
| #define MEN_UART_ID_Z025 0x19 |
| #define MEN_UART_ID_Z057 0x39 |
| #define MEN_UART_ID_Z125 0x7d |
| |
| /* |
| * IP Cores Z025 and Z057 can have up to 4 UART |
| * The UARTs available are stored in a global |
| * register saved in physical address + 0x40 |
| * Is saved as follows: |
| * |
| * 7 0 |
| * +------+-------+-------+-------+-------+-------+-------+-------+ |
| * |UART4 | UART3 | UART2 | UART1 | U4irq | U3irq | U2irq | U1irq | |
| * +------+-------+-------+-------+-------+-------+-------+-------+ |
| */ |
| #define MEN_UART1_MASK 0x01 |
| #define MEN_UART2_MASK 0x02 |
| #define MEN_UART3_MASK 0x04 |
| #define MEN_UART4_MASK 0x08 |
| |
| #define MEN_Z125_UARTS_AVAILABLE 0x01 |
| |
| #define MEN_Z025_MAX_UARTS 4 |
| #define MEN_UART_MEM_SIZE 0x10 |
| #define MEM_UART_REGISTER_SIZE 0x01 |
| #define MEN_Z025_REGISTER_OFFSET 0x40 |
| |
| #define MEN_UART1_OFFSET 0 |
| #define MEN_UART2_OFFSET (MEN_UART1_OFFSET + MEN_UART_MEM_SIZE) |
| #define MEN_UART3_OFFSET (MEN_UART2_OFFSET + MEN_UART_MEM_SIZE) |
| #define MEN_UART4_OFFSET (MEN_UART3_OFFSET + MEN_UART_MEM_SIZE) |
| |
| #define MEN_READ_REGISTER(addr) readb(addr) |
| |
| #define MAX_PORTS 4 |
| |
| struct serial_8250_men_mcb_data { |
| int num_ports; |
| int line[MAX_PORTS]; |
| unsigned int offset[MAX_PORTS]; |
| }; |
| |
| /* |
| * The Z125 16550-compatible UART has no fixed base clock assigned |
| * So, depending on the board we're on, we need to adjust the |
| * parameter in order to really set the correct baudrate, and |
| * do so if possible without user interaction |
| */ |
| static u32 men_lookup_uartclk(struct mcb_device *mdev) |
| { |
| /* use default value if board is not available below */ |
| u32 clkval = 1041666; |
| |
| dev_info(&mdev->dev, "%s on board %s\n", |
| dev_name(&mdev->dev), |
| mdev->bus->name); |
| if (strncmp(mdev->bus->name, "F075", 4) == 0) |
| clkval = 1041666; |
| else if (strncmp(mdev->bus->name, "F216", 4) == 0) |
| clkval = 1843200; |
| else if (strncmp(mdev->bus->name, "F210", 4) == 0) |
| clkval = 115200; |
| else if (strstr(mdev->bus->name, "215")) |
| clkval = 1843200; |
| else |
| dev_info(&mdev->dev, |
| "board not detected, using default uartclk\n"); |
| |
| clkval = clkval << 4; |
| |
| return clkval; |
| } |
| |
| static int read_uarts_available_from_register(struct resource *mem_res, |
| u8 *uarts_available) |
| { |
| void __iomem *mem; |
| int reg_value; |
| |
| if (!request_mem_region(mem_res->start + MEN_Z025_REGISTER_OFFSET, |
| MEM_UART_REGISTER_SIZE, KBUILD_MODNAME)) { |
| return -EBUSY; |
| } |
| |
| mem = ioremap(mem_res->start + MEN_Z025_REGISTER_OFFSET, |
| MEM_UART_REGISTER_SIZE); |
| if (!mem) { |
| release_mem_region(mem_res->start + MEN_Z025_REGISTER_OFFSET, |
| MEM_UART_REGISTER_SIZE); |
| return -ENOMEM; |
| } |
| |
| reg_value = MEN_READ_REGISTER(mem); |
| |
| iounmap(mem); |
| |
| release_mem_region(mem_res->start + MEN_Z025_REGISTER_OFFSET, |
| MEM_UART_REGISTER_SIZE); |
| |
| *uarts_available = reg_value >> 4; |
| |
| return 0; |
| } |
| |
| static int read_serial_data(struct mcb_device *mdev, |
| struct resource *mem_res, |
| struct serial_8250_men_mcb_data *serial_data) |
| { |
| u8 uarts_available; |
| int count = 0; |
| int mask; |
| int res; |
| int i; |
| |
| res = read_uarts_available_from_register(mem_res, &uarts_available); |
| if (res < 0) |
| return res; |
| |
| for (i = 0; i < MAX_PORTS; i++) { |
| mask = 0x1 << i; |
| switch (uarts_available & mask) { |
| case MEN_UART1_MASK: |
| serial_data->offset[count] = MEN_UART1_OFFSET; |
| count++; |
| break; |
| case MEN_UART2_MASK: |
| serial_data->offset[count] = MEN_UART2_OFFSET; |
| count++; |
| break; |
| case MEN_UART3_MASK: |
| serial_data->offset[count] = MEN_UART3_OFFSET; |
| count++; |
| break; |
| case MEN_UART4_MASK: |
| serial_data->offset[count] = MEN_UART4_OFFSET; |
| count++; |
| break; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| if (count <= 0 || count > MAX_PORTS) { |
| dev_err(&mdev->dev, "unexpected number of ports: %u\n", |
| count); |
| return -ENODEV; |
| } |
| |
| serial_data->num_ports = count; |
| |
| return 0; |
| } |
| |
| static int init_serial_data(struct mcb_device *mdev, |
| struct resource *mem_res, |
| struct serial_8250_men_mcb_data *serial_data) |
| { |
| switch (mdev->id) { |
| case MEN_UART_ID_Z125: |
| serial_data->num_ports = 1; |
| serial_data->offset[0] = 0; |
| return 0; |
| case MEN_UART_ID_Z025: |
| case MEN_UART_ID_Z057: |
| return read_serial_data(mdev, mem_res, serial_data); |
| default: |
| dev_err(&mdev->dev, "no supported device!\n"); |
| return -ENODEV; |
| } |
| } |
| |
| static int serial_8250_men_mcb_probe(struct mcb_device *mdev, |
| const struct mcb_device_id *id) |
| { |
| struct uart_8250_port uart; |
| struct serial_8250_men_mcb_data *data; |
| struct resource *mem; |
| int i; |
| int res; |
| |
| mem = mcb_get_resource(mdev, IORESOURCE_MEM); |
| if (mem == NULL) |
| return -ENXIO; |
| |
| data = devm_kzalloc(&mdev->dev, |
| sizeof(struct serial_8250_men_mcb_data), |
| GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| res = init_serial_data(mdev, mem, data); |
| if (res < 0) |
| return res; |
| |
| dev_dbg(&mdev->dev, "found a 16z%03u with %u ports\n", |
| mdev->id, data->num_ports); |
| |
| mcb_set_drvdata(mdev, data); |
| |
| for (i = 0; i < data->num_ports; i++) { |
| memset(&uart, 0, sizeof(struct uart_8250_port)); |
| spin_lock_init(&uart.port.lock); |
| |
| uart.port.flags = UPF_SKIP_TEST | |
| UPF_SHARE_IRQ | |
| UPF_BOOT_AUTOCONF | |
| UPF_IOREMAP; |
| uart.port.iotype = UPIO_MEM; |
| uart.port.uartclk = men_lookup_uartclk(mdev); |
| uart.port.irq = mcb_get_irq(mdev); |
| uart.port.mapbase = (unsigned long) mem->start |
| + data->offset[i]; |
| |
| /* ok, register the port */ |
| res = serial8250_register_8250_port(&uart); |
| if (res < 0) { |
| dev_err(&mdev->dev, "unable to register UART port\n"); |
| return res; |
| } |
| |
| data->line[i] = res; |
| dev_info(&mdev->dev, "found MCB UART: ttyS%d\n", data->line[i]); |
| } |
| |
| return 0; |
| } |
| |
| static void serial_8250_men_mcb_remove(struct mcb_device *mdev) |
| { |
| int i; |
| struct serial_8250_men_mcb_data *data = mcb_get_drvdata(mdev); |
| |
| if (!data) |
| return; |
| |
| for (i = 0; i < data->num_ports; i++) |
| serial8250_unregister_port(data->line[i]); |
| } |
| |
| static const struct mcb_device_id serial_8250_men_mcb_ids[] = { |
| { .device = MEN_UART_ID_Z025 }, |
| { .device = MEN_UART_ID_Z057 }, |
| { .device = MEN_UART_ID_Z125 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(mcb, serial_8250_men_mcb_ids); |
| |
| static struct mcb_driver mcb_driver = { |
| .driver = { |
| .name = "8250_men_mcb", |
| }, |
| .probe = serial_8250_men_mcb_probe, |
| .remove = serial_8250_men_mcb_remove, |
| .id_table = serial_8250_men_mcb_ids, |
| }; |
| module_mcb_driver(mcb_driver); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("MEN 8250 UART driver"); |
| MODULE_AUTHOR("Michael Moese <michael.moese@men.de"); |
| MODULE_ALIAS("mcb:16z125"); |
| MODULE_ALIAS("mcb:16z025"); |
| MODULE_ALIAS("mcb:16z057"); |
| MODULE_IMPORT_NS(MCB); |