diff options
Diffstat (limited to 'drivers/tty/serial/8250/8250_fintek.c')
-rw-r--r-- | drivers/tty/serial/8250/8250_fintek.c | 246 |
1 files changed, 118 insertions, 128 deletions
diff --git a/drivers/tty/serial/8250/8250_fintek.c b/drivers/tty/serial/8250/8250_fintek.c index 5815e81b5..0facc789f 100644 --- a/drivers/tty/serial/8250/8250_fintek.c +++ b/drivers/tty/serial/8250/8250_fintek.c @@ -1,9 +1,7 @@ /* * Probe for F81216A LPC to 4 UART * - * Based on drivers/tty/serial/8250_pnp.c, by Russell King, et al - * - * Copyright (C) 2014 Ricardo Ribalda, Qtechnology A/S + * Copyright (C) 2014-2016 Ricardo Ribalda, Qtechnology A/S * * * This program is free software; you can redistribute it and/or modify @@ -15,75 +13,77 @@ #include <linux/pnp.h> #include <linux/kernel.h> #include <linux/serial_core.h> +#include <linux/irq.h> #include "8250.h" -#define ADDR_PORT 0x4E -#define DATA_PORT 0x4F -#define ENTRY_KEY 0x77 +#define ADDR_PORT 0 +#define DATA_PORT 1 #define EXIT_KEY 0xAA #define CHIP_ID1 0x20 -#define CHIP_ID1_VAL 0x02 #define CHIP_ID2 0x21 -#define CHIP_ID2_VAL 0x16 +#define CHIP_ID_0 0x1602 +#define CHIP_ID_1 0x0501 #define VENDOR_ID1 0x23 #define VENDOR_ID1_VAL 0x19 #define VENDOR_ID2 0x24 #define VENDOR_ID2_VAL 0x34 +#define IO_ADDR1 0x61 +#define IO_ADDR2 0x60 #define LDN 0x7 +#define FINTEK_IRQ_MODE 0x70 +#define IRQ_SHARE BIT(4) +#define IRQ_MODE_MASK (BIT(6) | BIT(5)) +#define IRQ_LEVEL_LOW 0 +#define IRQ_EDGE_HIGH BIT(5) + #define RS485 0xF0 #define RTS_INVERT BIT(5) #define RS485_URA BIT(4) #define RXW4C_IRA BIT(3) #define TXW4C_IRA BIT(2) -#define DRIVER_NAME "8250_fintek" - -static int fintek_8250_enter_key(void){ +struct fintek_8250 { + u16 base_port; + u8 index; + u8 key; +}; - if (!request_muxed_region(ADDR_PORT, 2, DRIVER_NAME)) +static int fintek_8250_enter_key(u16 base_port, u8 key) +{ + if (!request_muxed_region(base_port, 2, "8250_fintek")) return -EBUSY; - outb(ENTRY_KEY, ADDR_PORT); - outb(ENTRY_KEY, ADDR_PORT); + outb(key, base_port + ADDR_PORT); + outb(key, base_port + ADDR_PORT); return 0; } -static void fintek_8250_exit_key(void){ - - outb(EXIT_KEY, ADDR_PORT); - release_region(ADDR_PORT, 2); -} - -static int fintek_8250_get_index(resource_size_t base_addr) +static void fintek_8250_exit_key(u16 base_port) { - resource_size_t base[] = {0x3f8, 0x2f8, 0x3e8, 0x2e8}; - int i; - - for (i = 0; i < ARRAY_SIZE(base); i++) - if (base_addr == base[i]) - return i; - return -ENODEV; + outb(EXIT_KEY, base_port + ADDR_PORT); + release_region(base_port + ADDR_PORT, 2); } -static int fintek_8250_check_id(void) +static int fintek_8250_check_id(u16 base_port) { + u16 chip; - outb(CHIP_ID1, ADDR_PORT); - if (inb(DATA_PORT) != CHIP_ID1_VAL) + outb(VENDOR_ID1, base_port + ADDR_PORT); + if (inb(base_port + DATA_PORT) != VENDOR_ID1_VAL) return -ENODEV; - outb(CHIP_ID2, ADDR_PORT); - if (inb(DATA_PORT) != CHIP_ID2_VAL) + outb(VENDOR_ID2, base_port + ADDR_PORT); + if (inb(base_port + DATA_PORT) != VENDOR_ID2_VAL) return -ENODEV; - outb(VENDOR_ID1, ADDR_PORT); - if (inb(DATA_PORT) != VENDOR_ID1_VAL) - return -ENODEV; + outb(CHIP_ID1, base_port + ADDR_PORT); + chip = inb(base_port + DATA_PORT); + outb(CHIP_ID2, base_port + ADDR_PORT); + chip |= inb(base_port + DATA_PORT) << 8; - outb(VENDOR_ID2, ADDR_PORT); - if (inb(DATA_PORT) != VENDOR_ID2_VAL) + if (chip != CHIP_ID_0 && chip != CHIP_ID_1) return -ENODEV; return 0; @@ -93,9 +93,9 @@ static int fintek_8250_rs485_config(struct uart_port *port, struct serial_rs485 *rs485) { uint8_t config = 0; - int index = fintek_8250_get_index(port->iobase); + struct fintek_8250 *pdata = port->private_data; - if (index < 0) + if (!pdata) return -EINVAL; if (rs485->flags & SER_RS485_ENABLED) @@ -125,116 +125,106 @@ static int fintek_8250_rs485_config(struct uart_port *port, if (rs485->flags & SER_RS485_RTS_ON_SEND) config |= RTS_INVERT; - if (fintek_8250_enter_key()) + if (fintek_8250_enter_key(pdata->base_port, pdata->key)) return -EBUSY; - outb(LDN, ADDR_PORT); - outb(index, DATA_PORT); - outb(RS485, ADDR_PORT); - outb(config, DATA_PORT); - fintek_8250_exit_key(); + outb(LDN, pdata->base_port + ADDR_PORT); + outb(pdata->index, pdata->base_port + DATA_PORT); + outb(RS485, pdata->base_port + ADDR_PORT); + outb(config, pdata->base_port + DATA_PORT); + fintek_8250_exit_key(pdata->base_port); port->rs485 = *rs485; return 0; } -static int -fintek_8250_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) +static int find_base_port(struct fintek_8250 *pdata, u16 io_address) { - int line; - struct uart_8250_port uart; - int ret; - - if (!pnp_port_valid(dev, 0)) - return -ENODEV; - - if (fintek_8250_get_index(pnp_port_start(dev, 0)) < 0) - return -ENODEV; - - /* Enable configuration registers*/ - if (fintek_8250_enter_key()) - return -EBUSY; - - /*Check ID*/ - ret = fintek_8250_check_id(); - fintek_8250_exit_key(); - if (ret) - return ret; - - memset(&uart, 0, sizeof(uart)); - if (!pnp_irq_valid(dev, 0)) - return -ENODEV; - uart.port.irq = pnp_irq(dev, 0); - uart.port.iobase = pnp_port_start(dev, 0); - uart.port.iotype = UPIO_PORT; - uart.port.rs485_config = fintek_8250_rs485_config; - - uart.port.flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF; - if (pnp_irq_flags(dev, 0) & IORESOURCE_IRQ_SHAREABLE) - uart.port.flags |= UPF_SHARE_IRQ; - uart.port.uartclk = 1843200; - uart.port.dev = &dev->dev; - - line = serial8250_register_8250_port(&uart); - if (line < 0) - return -ENODEV; + static const u16 addr[] = {0x4e, 0x2e}; + static const u8 keys[] = {0x77, 0xa0, 0x87, 0x67}; + int i, j, k; + + for (i = 0; i < ARRAY_SIZE(addr); i++) { + for (j = 0; j < ARRAY_SIZE(keys); j++) { + + if (fintek_8250_enter_key(addr[i], keys[j])) + continue; + if (fintek_8250_check_id(addr[i])) { + fintek_8250_exit_key(addr[i]); + continue; + } + + for (k = 0; k < 4; k++) { + u16 aux; + + outb(LDN, addr[i] + ADDR_PORT); + outb(k, addr[i] + DATA_PORT); + + outb(IO_ADDR1, addr[i] + ADDR_PORT); + aux = inb(addr[i] + DATA_PORT); + outb(IO_ADDR2, addr[i] + ADDR_PORT); + aux |= inb(addr[i] + DATA_PORT) << 8; + if (aux != io_address) + continue; + + fintek_8250_exit_key(addr[i]); + pdata->key = keys[j]; + pdata->base_port = addr[i]; + pdata->index = k; + + return 0; + } + + fintek_8250_exit_key(addr[i]); + } + } - pnp_set_drvdata(dev, (void *)((long)line + 1)); - return 0; + return -ENODEV; } -static void fintek_8250_remove(struct pnp_dev *dev) +static int fintek_8250_set_irq_mode(struct fintek_8250 *pdata, bool level_mode) { - long line = (long)pnp_get_drvdata(dev); + int status; + u8 tmp; - if (line) - serial8250_unregister_port(line - 1); -} + status = fintek_8250_enter_key(pdata->base_port, pdata->key); + if (status) + return status; -#ifdef CONFIG_PM -static int fintek_8250_suspend(struct pnp_dev *dev, pm_message_t state) -{ - long line = (long)pnp_get_drvdata(dev); + outb(LDN, pdata->base_port + ADDR_PORT); + outb(pdata->index, pdata->base_port + DATA_PORT); - if (!line) - return -ENODEV; - serial8250_suspend_port(line - 1); + outb(FINTEK_IRQ_MODE, pdata->base_port + ADDR_PORT); + tmp = inb(pdata->base_port + DATA_PORT); + + tmp &= ~IRQ_MODE_MASK; + tmp |= IRQ_SHARE; + if (!level_mode) + tmp |= IRQ_EDGE_HIGH; + + outb(tmp, pdata->base_port + DATA_PORT); + fintek_8250_exit_key(pdata->base_port); return 0; } -static int fintek_8250_resume(struct pnp_dev *dev) +int fintek_8250_probe(struct uart_8250_port *uart) { - long line = (long)pnp_get_drvdata(dev); + struct fintek_8250 *pdata; + struct fintek_8250 probe_data; + struct irq_data *irq_data = irq_get_irq_data(uart->port.irq); + bool level_mode = irqd_is_level_type(irq_data); - if (!line) + if (find_base_port(&probe_data, uart->port.iobase)) return -ENODEV; - serial8250_resume_port(line - 1); - return 0; -} -#else -#define fintek_8250_suspend NULL -#define fintek_8250_resume NULL -#endif /* CONFIG_PM */ - -static const struct pnp_device_id fintek_dev_table[] = { - /* Qtechnology Panel PC / IO1000 */ - { "PNP0501"}, - {} -}; -MODULE_DEVICE_TABLE(pnp, fintek_dev_table); + pdata = devm_kzalloc(uart->port.dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; -static struct pnp_driver fintek_8250_driver = { - .name = DRIVER_NAME, - .probe = fintek_8250_probe, - .remove = fintek_8250_remove, - .suspend = fintek_8250_suspend, - .resume = fintek_8250_resume, - .id_table = fintek_dev_table, -}; + memcpy(pdata, &probe_data, sizeof(probe_data)); + uart->port.rs485_config = fintek_8250_rs485_config; + uart->port.private_data = pdata; -module_pnp_driver(fintek_8250_driver); -MODULE_DESCRIPTION("Fintek F812164 module"); -MODULE_AUTHOR("Ricardo Ribalda <ricardo.ribalda@gmail.com>"); -MODULE_LICENSE("GPL"); + return fintek_8250_set_irq_mode(pdata, level_mode); +} |