]>
Commit | Line | Data |
---|---|---|
28e3fb6c RR |
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 | ||
92a5f11a RR |
42 | struct fintek_8250 { |
43 | u8 index; | |
44 | long line; | |
45 | }; | |
46 | ||
28e3fb6c RR |
47 | static int fintek_8250_enter_key(void){ |
48 | ||
49 | if (!request_muxed_region(ADDR_PORT, 2, DRIVER_NAME)) | |
50 | return -EBUSY; | |
51 | ||
52 | outb(ENTRY_KEY, ADDR_PORT); | |
53 | outb(ENTRY_KEY, ADDR_PORT); | |
54 | return 0; | |
55 | } | |
56 | ||
57 | static void fintek_8250_exit_key(void){ | |
58 | ||
59 | outb(EXIT_KEY, ADDR_PORT); | |
60 | release_region(ADDR_PORT, 2); | |
61 | } | |
62 | ||
63 | static int fintek_8250_get_index(resource_size_t base_addr) | |
64 | { | |
65 | resource_size_t base[] = {0x3f8, 0x2f8, 0x3e8, 0x2e8}; | |
66 | int i; | |
67 | ||
68 | for (i = 0; i < ARRAY_SIZE(base); i++) | |
69 | if (base_addr == base[i]) | |
70 | return i; | |
71 | ||
72 | return -ENODEV; | |
73 | } | |
74 | ||
75 | static int fintek_8250_check_id(void) | |
76 | { | |
77 | ||
78 | outb(CHIP_ID1, ADDR_PORT); | |
79 | if (inb(DATA_PORT) != CHIP_ID1_VAL) | |
80 | return -ENODEV; | |
81 | ||
82 | outb(CHIP_ID2, ADDR_PORT); | |
83 | if (inb(DATA_PORT) != CHIP_ID2_VAL) | |
84 | return -ENODEV; | |
85 | ||
86 | outb(VENDOR_ID1, ADDR_PORT); | |
87 | if (inb(DATA_PORT) != VENDOR_ID1_VAL) | |
88 | return -ENODEV; | |
89 | ||
90 | outb(VENDOR_ID2, ADDR_PORT); | |
91 | if (inb(DATA_PORT) != VENDOR_ID2_VAL) | |
92 | return -ENODEV; | |
93 | ||
94 | return 0; | |
95 | } | |
96 | ||
41e69093 | 97 | static int fintek_8250_rs485_config(struct uart_port *port, |
28e3fb6c RR |
98 | struct serial_rs485 *rs485) |
99 | { | |
100 | uint8_t config = 0; | |
92a5f11a | 101 | struct fintek_8250 *pdata = port->private_data; |
28e3fb6c | 102 | |
92a5f11a | 103 | if (!pdata) |
28e3fb6c RR |
104 | return -EINVAL; |
105 | ||
106 | if (rs485->flags & SER_RS485_ENABLED) | |
107 | memset(rs485->padding, 0, sizeof(rs485->padding)); | |
108 | else | |
109 | memset(rs485, 0, sizeof(*rs485)); | |
110 | ||
111 | rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | | |
112 | SER_RS485_RTS_AFTER_SEND; | |
113 | ||
114 | if (rs485->delay_rts_before_send) { | |
115 | rs485->delay_rts_before_send = 1; | |
116 | config |= TXW4C_IRA; | |
117 | } | |
118 | ||
119 | if (rs485->delay_rts_after_send) { | |
120 | rs485->delay_rts_after_send = 1; | |
121 | config |= RXW4C_IRA; | |
122 | } | |
123 | ||
124 | if ((!!(rs485->flags & SER_RS485_RTS_ON_SEND)) == | |
125 | (!!(rs485->flags & SER_RS485_RTS_AFTER_SEND))) | |
126 | rs485->flags &= SER_RS485_ENABLED; | |
127 | else | |
128 | config |= RS485_URA; | |
129 | ||
130 | if (rs485->flags & SER_RS485_RTS_ON_SEND) | |
131 | config |= RTS_INVERT; | |
132 | ||
133 | if (fintek_8250_enter_key()) | |
134 | return -EBUSY; | |
135 | ||
136 | outb(LDN, ADDR_PORT); | |
92a5f11a | 137 | outb(pdata->index, DATA_PORT); |
28e3fb6c RR |
138 | outb(RS485, ADDR_PORT); |
139 | outb(config, DATA_PORT); | |
140 | fintek_8250_exit_key(); | |
141 | ||
41e69093 RR |
142 | port->rs485 = *rs485; |
143 | ||
28e3fb6c RR |
144 | return 0; |
145 | } | |
146 | ||
147 | static int | |
148 | fintek_8250_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) | |
149 | { | |
28e3fb6c | 150 | struct uart_8250_port uart; |
92a5f11a | 151 | struct fintek_8250 *pdata; |
28e3fb6c | 152 | int ret; |
92a5f11a | 153 | int index; |
28e3fb6c RR |
154 | |
155 | if (!pnp_port_valid(dev, 0)) | |
156 | return -ENODEV; | |
157 | ||
92a5f11a RR |
158 | index = fintek_8250_get_index(pnp_port_start(dev, 0)); |
159 | if (index < 0) | |
28e3fb6c RR |
160 | return -ENODEV; |
161 | ||
162 | /* Enable configuration registers*/ | |
163 | if (fintek_8250_enter_key()) | |
164 | return -EBUSY; | |
165 | ||
166 | /*Check ID*/ | |
167 | ret = fintek_8250_check_id(); | |
168 | fintek_8250_exit_key(); | |
169 | if (ret) | |
170 | return ret; | |
171 | ||
172 | memset(&uart, 0, sizeof(uart)); | |
92a5f11a RR |
173 | |
174 | pdata = devm_kzalloc(&dev->dev, sizeof(*pdata), GFP_KERNEL); | |
175 | if (!pdata) | |
176 | return -ENOMEM; | |
177 | uart.port.private_data = pdata; | |
178 | ||
28e3fb6c RR |
179 | if (!pnp_irq_valid(dev, 0)) |
180 | return -ENODEV; | |
181 | uart.port.irq = pnp_irq(dev, 0); | |
182 | uart.port.iobase = pnp_port_start(dev, 0); | |
183 | uart.port.iotype = UPIO_PORT; | |
41e69093 | 184 | uart.port.rs485_config = fintek_8250_rs485_config; |
28e3fb6c RR |
185 | |
186 | uart.port.flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF; | |
187 | if (pnp_irq_flags(dev, 0) & IORESOURCE_IRQ_SHAREABLE) | |
188 | uart.port.flags |= UPF_SHARE_IRQ; | |
189 | uart.port.uartclk = 1843200; | |
190 | uart.port.dev = &dev->dev; | |
191 | ||
92a5f11a RR |
192 | pdata->index = index; |
193 | pdata->line = serial8250_register_8250_port(&uart); | |
194 | if (pdata->line < 0) | |
28e3fb6c RR |
195 | return -ENODEV; |
196 | ||
92a5f11a | 197 | pnp_set_drvdata(dev, pdata); |
28e3fb6c RR |
198 | return 0; |
199 | } | |
200 | ||
201 | static void fintek_8250_remove(struct pnp_dev *dev) | |
202 | { | |
92a5f11a | 203 | struct fintek_8250 *pdata = pnp_get_drvdata(dev); |
28e3fb6c | 204 | |
92a5f11a RR |
205 | if (pdata) |
206 | serial8250_unregister_port(pdata->line); | |
28e3fb6c RR |
207 | } |
208 | ||
209 | #ifdef CONFIG_PM | |
210 | static int fintek_8250_suspend(struct pnp_dev *dev, pm_message_t state) | |
211 | { | |
92a5f11a | 212 | struct fintek_8250 *pdata = pnp_get_drvdata(dev); |
28e3fb6c | 213 | |
92a5f11a | 214 | if (!pdata) |
28e3fb6c | 215 | return -ENODEV; |
92a5f11a | 216 | serial8250_suspend_port(pdata->line); |
28e3fb6c RR |
217 | return 0; |
218 | } | |
219 | ||
220 | static int fintek_8250_resume(struct pnp_dev *dev) | |
221 | { | |
92a5f11a | 222 | struct fintek_8250 *pdata = pnp_get_drvdata(dev); |
28e3fb6c | 223 | |
92a5f11a | 224 | if (!pdata) |
28e3fb6c | 225 | return -ENODEV; |
92a5f11a | 226 | serial8250_resume_port(pdata->line); |
28e3fb6c RR |
227 | return 0; |
228 | } | |
229 | #else | |
230 | #define fintek_8250_suspend NULL | |
231 | #define fintek_8250_resume NULL | |
232 | #endif /* CONFIG_PM */ | |
233 | ||
234 | static const struct pnp_device_id fintek_dev_table[] = { | |
235 | /* Qtechnology Panel PC / IO1000 */ | |
236 | { "PNP0501"}, | |
237 | {} | |
238 | }; | |
239 | ||
240 | MODULE_DEVICE_TABLE(pnp, fintek_dev_table); | |
241 | ||
242 | static struct pnp_driver fintek_8250_driver = { | |
243 | .name = DRIVER_NAME, | |
244 | .probe = fintek_8250_probe, | |
245 | .remove = fintek_8250_remove, | |
246 | .suspend = fintek_8250_suspend, | |
247 | .resume = fintek_8250_resume, | |
248 | .id_table = fintek_dev_table, | |
249 | }; | |
250 | ||
aee94467 | 251 | module_pnp_driver(fintek_8250_driver); |
28e3fb6c RR |
252 | MODULE_DESCRIPTION("Fintek F812164 module"); |
253 | MODULE_AUTHOR("Ricardo Ribalda <ricardo.ribalda@gmail.com>"); | |
254 | MODULE_LICENSE("GPL"); |