1/* 2 * Probe for F81216A LPC to 4 UART 3 * 4 * Based on drivers/tty/serial/8250_pnp.c, by Russell King, et al 5 * 6 * Copyright (C) 2014 Ricardo Ribalda, Qtechnology A/S 7 * 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License. 12 */ 13#include <linux/module.h> 14#include <linux/pci.h> 15#include <linux/pnp.h> 16#include <linux/kernel.h> 17#include <linux/serial_core.h> 18#include "8250.h" 19 20#define ADDR_PORT 0x4E 21#define DATA_PORT 0x4F 22#define ENTRY_KEY 0x77 23#define EXIT_KEY 0xAA 24#define CHIP_ID1 0x20 25#define CHIP_ID1_VAL 0x02 26#define CHIP_ID2 0x21 27#define CHIP_ID2_VAL 0x16 28#define VENDOR_ID1 0x23 29#define VENDOR_ID1_VAL 0x19 30#define VENDOR_ID2 0x24 31#define VENDOR_ID2_VAL 0x34 32#define LDN 0x7 33 34#define RS485 0xF0 35#define RTS_INVERT BIT(5) 36#define RS485_URA BIT(4) 37#define RXW4C_IRA BIT(3) 38#define TXW4C_IRA BIT(2) 39 40#define DRIVER_NAME "8250_fintek" 41 42static int fintek_8250_enter_key(void){ 43 44 if (!request_muxed_region(ADDR_PORT, 2, DRIVER_NAME)) 45 return -EBUSY; 46 47 outb(ENTRY_KEY, ADDR_PORT); 48 outb(ENTRY_KEY, ADDR_PORT); 49 return 0; 50} 51 52static void fintek_8250_exit_key(void){ 53 54 outb(EXIT_KEY, ADDR_PORT); 55 release_region(ADDR_PORT, 2); 56} 57 58static int fintek_8250_get_index(resource_size_t base_addr) 59{ 60 resource_size_t base[] = {0x3f8, 0x2f8, 0x3e8, 0x2e8}; 61 int i; 62 63 for (i = 0; i < ARRAY_SIZE(base); i++) 64 if (base_addr == base[i]) 65 return i; 66 67 return -ENODEV; 68} 69 70static int fintek_8250_check_id(void) 71{ 72 73 outb(CHIP_ID1, ADDR_PORT); 74 if (inb(DATA_PORT) != CHIP_ID1_VAL) 75 return -ENODEV; 76 77 outb(CHIP_ID2, ADDR_PORT); 78 if (inb(DATA_PORT) != CHIP_ID2_VAL) 79 return -ENODEV; 80 81 outb(VENDOR_ID1, ADDR_PORT); 82 if (inb(DATA_PORT) != VENDOR_ID1_VAL) 83 return -ENODEV; 84 85 outb(VENDOR_ID2, ADDR_PORT); 86 if (inb(DATA_PORT) != VENDOR_ID2_VAL) 87 return -ENODEV; 88 89 return 0; 90} 91 92static int fintek_8250_rs4850_config(struct uart_8250_port *uart, 93 struct serial_rs485 *rs485) 94{ 95 uint8_t config = 0; 96 int index = fintek_8250_get_index(uart->port.iobase); 97 98 if (index < 0) 99 return -EINVAL; 100 101 if (rs485->flags & SER_RS485_ENABLED) 102 memset(rs485->padding, 0, sizeof(rs485->padding)); 103 else 104 memset(rs485, 0, sizeof(*rs485)); 105 106 rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | 107 SER_RS485_RTS_AFTER_SEND; 108 109 if (rs485->delay_rts_before_send) { 110 rs485->delay_rts_before_send = 1; 111 config |= TXW4C_IRA; 112 } 113 114 if (rs485->delay_rts_after_send) { 115 rs485->delay_rts_after_send = 1; 116 config |= RXW4C_IRA; 117 } 118 119 if ((!!(rs485->flags & SER_RS485_RTS_ON_SEND)) == 120 (!!(rs485->flags & SER_RS485_RTS_AFTER_SEND))) 121 rs485->flags &= SER_RS485_ENABLED; 122 else 123 config |= RS485_URA; 124 125 if (rs485->flags & SER_RS485_RTS_ON_SEND) 126 config |= RTS_INVERT; 127 128 if (fintek_8250_enter_key()) 129 return -EBUSY; 130 131 outb(LDN, ADDR_PORT); 132 outb(index, DATA_PORT); 133 outb(RS485, ADDR_PORT); 134 outb(config, DATA_PORT); 135 fintek_8250_exit_key(); 136 137 return 0; 138} 139 140static int 141fintek_8250_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) 142{ 143 int line; 144 struct uart_8250_port uart; 145 int ret; 146 147 if (!pnp_port_valid(dev, 0)) 148 return -ENODEV; 149 150 if (fintek_8250_get_index(pnp_port_start(dev, 0)) < 0) 151 return -ENODEV; 152 153 /* Enable configuration registers*/ 154 if (fintek_8250_enter_key()) 155 return -EBUSY; 156 157 /*Check ID*/ 158 ret = fintek_8250_check_id(); 159 fintek_8250_exit_key(); 160 if (ret) 161 return ret; 162 163 memset(&uart, 0, sizeof(uart)); 164 if (!pnp_irq_valid(dev, 0)) 165 return -ENODEV; 166 uart.port.irq = pnp_irq(dev, 0); 167 uart.port.iobase = pnp_port_start(dev, 0); 168 uart.port.iotype = UPIO_PORT; 169 uart.rs485_config = fintek_8250_rs4850_config; 170 171 uart.port.flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF; 172 if (pnp_irq_flags(dev, 0) & IORESOURCE_IRQ_SHAREABLE) 173 uart.port.flags |= UPF_SHARE_IRQ; 174 uart.port.uartclk = 1843200; 175 uart.port.dev = &dev->dev; 176 177 line = serial8250_register_8250_port(&uart); 178 if (line < 0) 179 return -ENODEV; 180 181 pnp_set_drvdata(dev, (void *)((long)line + 1)); 182 return 0; 183} 184 185static void fintek_8250_remove(struct pnp_dev *dev) 186{ 187 long line = (long)pnp_get_drvdata(dev); 188 189 if (line) 190 serial8250_unregister_port(line - 1); 191} 192 193#ifdef CONFIG_PM 194static int fintek_8250_suspend(struct pnp_dev *dev, pm_message_t state) 195{ 196 long line = (long)pnp_get_drvdata(dev); 197 198 if (!line) 199 return -ENODEV; 200 serial8250_suspend_port(line - 1); 201 return 0; 202} 203 204static int fintek_8250_resume(struct pnp_dev *dev) 205{ 206 long line = (long)pnp_get_drvdata(dev); 207 208 if (!line) 209 return -ENODEV; 210 serial8250_resume_port(line - 1); 211 return 0; 212} 213#else 214#define fintek_8250_suspend NULL 215#define fintek_8250_resume NULL 216#endif /* CONFIG_PM */ 217 218static const struct pnp_device_id fintek_dev_table[] = { 219 /* Qtechnology Panel PC / IO1000 */ 220 { "PNP0501"}, 221 {} 222}; 223 224MODULE_DEVICE_TABLE(pnp, fintek_dev_table); 225 226static struct pnp_driver fintek_8250_driver = { 227 .name = DRIVER_NAME, 228 .probe = fintek_8250_probe, 229 .remove = fintek_8250_remove, 230 .suspend = fintek_8250_suspend, 231 .resume = fintek_8250_resume, 232 .id_table = fintek_dev_table, 233}; 234 235static int fintek_8250_init(void) 236{ 237 return pnp_register_driver(&fintek_8250_driver); 238} 239module_init(fintek_8250_init); 240 241static void fintek_8250_exit(void) 242{ 243 pnp_unregister_driver(&fintek_8250_driver); 244} 245module_exit(fintek_8250_exit); 246 247MODULE_DESCRIPTION("Fintek F812164 module"); 248MODULE_AUTHOR("Ricardo Ribalda <ricardo.ribalda@gmail.com>"); 249MODULE_LICENSE("GPL"); 250