diff options
Diffstat (limited to 'drivers/tty/serial/8250/8250_fintek.c')
-rw-r--r-- | drivers/tty/serial/8250/8250_fintek.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/drivers/tty/serial/8250/8250_fintek.c b/drivers/tty/serial/8250/8250_fintek.c new file mode 100644 index 000000000000..1bb28cb69493 --- /dev/null +++ b/drivers/tty/serial/8250/8250_fintek.c @@ -0,0 +1,249 @@ +/* + * 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 + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + */ +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pnp.h> +#include <linux/kernel.h> +#include <linux/serial_core.h> +#include "8250.h" + +#define ADDR_PORT 0x4E +#define DATA_PORT 0x4F +#define ENTRY_KEY 0x77 +#define EXIT_KEY 0xAA +#define CHIP_ID1 0x20 +#define CHIP_ID1_VAL 0x02 +#define CHIP_ID2 0x21 +#define CHIP_ID2_VAL 0x16 +#define VENDOR_ID1 0x23 +#define VENDOR_ID1_VAL 0x19 +#define VENDOR_ID2 0x24 +#define VENDOR_ID2_VAL 0x34 +#define LDN 0x7 + +#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){ + + if (!request_muxed_region(ADDR_PORT, 2, DRIVER_NAME)) + return -EBUSY; + + outb(ENTRY_KEY, ADDR_PORT); + outb(ENTRY_KEY, 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) +{ + 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; +} + +static int fintek_8250_check_id(void) +{ + + outb(CHIP_ID1, ADDR_PORT); + if (inb(DATA_PORT) != CHIP_ID1_VAL) + return -ENODEV; + + outb(CHIP_ID2, ADDR_PORT); + if (inb(DATA_PORT) != CHIP_ID2_VAL) + return -ENODEV; + + outb(VENDOR_ID1, ADDR_PORT); + if (inb(DATA_PORT) != VENDOR_ID1_VAL) + return -ENODEV; + + outb(VENDOR_ID2, ADDR_PORT); + if (inb(DATA_PORT) != VENDOR_ID2_VAL) + return -ENODEV; + + return 0; +} + +static int fintek_8250_rs4850_config(struct uart_8250_port *uart, + struct serial_rs485 *rs485) +{ + uint8_t config = 0; + int index = fintek_8250_get_index(uart->port.iobase); + + if (index < 0) + return -EINVAL; + + if (rs485->flags & SER_RS485_ENABLED) + memset(rs485->padding, 0, sizeof(rs485->padding)); + else + memset(rs485, 0, sizeof(*rs485)); + + rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND; + + if (rs485->delay_rts_before_send) { + rs485->delay_rts_before_send = 1; + config |= TXW4C_IRA; + } + + if (rs485->delay_rts_after_send) { + rs485->delay_rts_after_send = 1; + config |= RXW4C_IRA; + } + + if ((!!(rs485->flags & SER_RS485_RTS_ON_SEND)) == + (!!(rs485->flags & SER_RS485_RTS_AFTER_SEND))) + rs485->flags &= SER_RS485_ENABLED; + else + config |= RS485_URA; + + if (rs485->flags & SER_RS485_RTS_ON_SEND) + config |= RTS_INVERT; + + if (fintek_8250_enter_key()) + return -EBUSY; + + outb(LDN, ADDR_PORT); + outb(index, DATA_PORT); + outb(RS485, ADDR_PORT); + outb(config, DATA_PORT); + fintek_8250_exit_key(); + + return 0; +} + +static int +fintek_8250_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) +{ + 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.rs485_config = fintek_8250_rs4850_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; + + pnp_set_drvdata(dev, (void *)((long)line + 1)); + return 0; +} + +static void fintek_8250_remove(struct pnp_dev *dev) +{ + long line = (long)pnp_get_drvdata(dev); + + if (line) + serial8250_unregister_port(line - 1); +} + +#ifdef CONFIG_PM +static int fintek_8250_suspend(struct pnp_dev *dev, pm_message_t state) +{ + long line = (long)pnp_get_drvdata(dev); + + if (!line) + return -ENODEV; + serial8250_suspend_port(line - 1); + return 0; +} + +static int fintek_8250_resume(struct pnp_dev *dev) +{ + long line = (long)pnp_get_drvdata(dev); + + if (!line) + 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); + +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, +}; + +static int fintek_8250_init(void) +{ + return pnp_register_driver(&fintek_8250_driver); +} +module_init(fintek_8250_init); + +static void fintek_8250_exit(void) +{ + pnp_unregister_driver(&fintek_8250_driver); +} +module_exit(fintek_8250_exit); + +MODULE_DESCRIPTION("Fintek F812164 module"); +MODULE_AUTHOR("Ricardo Ribalda <ricardo.ribalda@gmail.com>"); +MODULE_LICENSE("GPL"); |