]>
Commit | Line | Data |
---|---|---|
28e3fb6c RRD |
1 | /* |
2 | * Probe for F81216A LPC to 4 UART | |
3 | * | |
fa01e2ca | 4 | * Copyright (C) 2014-2016 Ricardo Ribalda, Qtechnology A/S |
28e3fb6c RRD |
5 | * |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License. | |
10 | */ | |
11 | #include <linux/module.h> | |
12 | #include <linux/pci.h> | |
13 | #include <linux/pnp.h> | |
14 | #include <linux/kernel.h> | |
15 | #include <linux/serial_core.h> | |
4da22f14 | 16 | #include <linux/irq.h> |
28e3fb6c RRD |
17 | #include "8250.h" |
18 | ||
017bec38 RRD |
19 | #define ADDR_PORT 0 |
20 | #define DATA_PORT 1 | |
28e3fb6c RRD |
21 | #define EXIT_KEY 0xAA |
22 | #define CHIP_ID1 0x20 | |
28e3fb6c | 23 | #define CHIP_ID2 0x21 |
dae77f75 RRD |
24 | #define CHIP_ID_0 0x1602 |
25 | #define CHIP_ID_1 0x0501 | |
28e3fb6c RRD |
26 | #define VENDOR_ID1 0x23 |
27 | #define VENDOR_ID1_VAL 0x19 | |
28 | #define VENDOR_ID2 0x24 | |
29 | #define VENDOR_ID2_VAL 0x34 | |
29d58642 RRD |
30 | #define IO_ADDR1 0x61 |
31 | #define IO_ADDR2 0x60 | |
28e3fb6c RRD |
32 | #define LDN 0x7 |
33 | ||
87a713c8 | 34 | #define FINTEK_IRQ_MODE 0x70 |
4da22f14 JZHPH |
35 | #define IRQ_SHARE BIT(4) |
36 | #define IRQ_MODE_MASK (BIT(6) | BIT(5)) | |
37 | #define IRQ_LEVEL_LOW 0 | |
38 | #define IRQ_EDGE_HIGH BIT(5) | |
39 | ||
28e3fb6c RRD |
40 | #define RS485 0xF0 |
41 | #define RTS_INVERT BIT(5) | |
42 | #define RS485_URA BIT(4) | |
43 | #define RXW4C_IRA BIT(3) | |
44 | #define TXW4C_IRA BIT(2) | |
45 | ||
92a5f11a | 46 | struct fintek_8250 { |
017bec38 | 47 | u16 base_port; |
92a5f11a | 48 | u8 index; |
ce8c267e | 49 | u8 key; |
92a5f11a RRD |
50 | }; |
51 | ||
ce8c267e | 52 | static int fintek_8250_enter_key(u16 base_port, u8 key) |
017bec38 | 53 | { |
fa01e2ca | 54 | if (!request_muxed_region(base_port, 2, "8250_fintek")) |
28e3fb6c RRD |
55 | return -EBUSY; |
56 | ||
ce8c267e RRD |
57 | outb(key, base_port + ADDR_PORT); |
58 | outb(key, base_port + ADDR_PORT); | |
28e3fb6c RRD |
59 | return 0; |
60 | } | |
61 | ||
017bec38 RRD |
62 | static void fintek_8250_exit_key(u16 base_port) |
63 | { | |
28e3fb6c | 64 | |
017bec38 RRD |
65 | outb(EXIT_KEY, base_port + ADDR_PORT); |
66 | release_region(base_port + ADDR_PORT, 2); | |
28e3fb6c RRD |
67 | } |
68 | ||
017bec38 | 69 | static int fintek_8250_check_id(u16 base_port) |
28e3fb6c | 70 | { |
dae77f75 | 71 | u16 chip; |
28e3fb6c | 72 | |
017bec38 RRD |
73 | outb(VENDOR_ID1, base_port + ADDR_PORT); |
74 | if (inb(base_port + DATA_PORT) != VENDOR_ID1_VAL) | |
28e3fb6c RRD |
75 | return -ENODEV; |
76 | ||
017bec38 RRD |
77 | outb(VENDOR_ID2, base_port + ADDR_PORT); |
78 | if (inb(base_port + DATA_PORT) != VENDOR_ID2_VAL) | |
28e3fb6c RRD |
79 | return -ENODEV; |
80 | ||
dae77f75 RRD |
81 | outb(CHIP_ID1, base_port + ADDR_PORT); |
82 | chip = inb(base_port + DATA_PORT); | |
83 | outb(CHIP_ID2, base_port + ADDR_PORT); | |
84 | chip |= inb(base_port + DATA_PORT) << 8; | |
85 | ||
86 | if (chip != CHIP_ID_0 && chip != CHIP_ID_1) | |
87 | return -ENODEV; | |
88 | ||
28e3fb6c RRD |
89 | return 0; |
90 | } | |
91 | ||
41e69093 | 92 | static int fintek_8250_rs485_config(struct uart_port *port, |
28e3fb6c RRD |
93 | struct serial_rs485 *rs485) |
94 | { | |
95 | uint8_t config = 0; | |
92a5f11a | 96 | struct fintek_8250 *pdata = port->private_data; |
28e3fb6c | 97 | |
92a5f11a | 98 | if (!pdata) |
28e3fb6c RRD |
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 | ||
ce8c267e | 128 | if (fintek_8250_enter_key(pdata->base_port, pdata->key)) |
28e3fb6c RRD |
129 | return -EBUSY; |
130 | ||
017bec38 RRD |
131 | outb(LDN, pdata->base_port + ADDR_PORT); |
132 | outb(pdata->index, pdata->base_port + DATA_PORT); | |
133 | outb(RS485, pdata->base_port + ADDR_PORT); | |
134 | outb(config, pdata->base_port + DATA_PORT); | |
135 | fintek_8250_exit_key(pdata->base_port); | |
28e3fb6c | 136 | |
41e69093 RRD |
137 | port->rs485 = *rs485; |
138 | ||
28e3fb6c RRD |
139 | return 0; |
140 | } | |
141 | ||
fa01e2ca | 142 | static int find_base_port(struct fintek_8250 *pdata, u16 io_address) |
017bec38 RRD |
143 | { |
144 | static const u16 addr[] = {0x4e, 0x2e}; | |
ce8c267e | 145 | static const u8 keys[] = {0x77, 0xa0, 0x87, 0x67}; |
29d58642 | 146 | int i, j, k; |
017bec38 RRD |
147 | |
148 | for (i = 0; i < ARRAY_SIZE(addr); i++) { | |
ce8c267e | 149 | for (j = 0; j < ARRAY_SIZE(keys); j++) { |
ce8c267e RRD |
150 | |
151 | if (fintek_8250_enter_key(addr[i], keys[j])) | |
152 | continue; | |
29d58642 RRD |
153 | if (fintek_8250_check_id(addr[i])) { |
154 | fintek_8250_exit_key(addr[i]); | |
155 | continue; | |
156 | } | |
157 | ||
158 | for (k = 0; k < 4; k++) { | |
159 | u16 aux; | |
160 | ||
161 | outb(LDN, addr[i] + ADDR_PORT); | |
162 | outb(k, addr[i] + DATA_PORT); | |
163 | ||
164 | outb(IO_ADDR1, addr[i] + ADDR_PORT); | |
165 | aux = inb(addr[i] + DATA_PORT); | |
166 | outb(IO_ADDR2, addr[i] + ADDR_PORT); | |
167 | aux |= inb(addr[i] + DATA_PORT) << 8; | |
168 | if (aux != io_address) | |
169 | continue; | |
170 | ||
171 | fintek_8250_exit_key(addr[i]); | |
fa01e2ca RRD |
172 | pdata->key = keys[j]; |
173 | pdata->base_port = addr[i]; | |
174 | pdata->index = k; | |
175 | ||
176 | return 0; | |
ce8c267e | 177 | } |
fa01e2ca | 178 | |
29d58642 | 179 | fintek_8250_exit_key(addr[i]); |
ce8c267e | 180 | } |
017bec38 RRD |
181 | } |
182 | ||
183 | return -ENODEV; | |
184 | } | |
185 | ||
4da22f14 JZHPH |
186 | static int fintek_8250_set_irq_mode(struct fintek_8250 *pdata, bool level_mode) |
187 | { | |
188 | int status; | |
189 | u8 tmp; | |
190 | ||
191 | status = fintek_8250_enter_key(pdata->base_port, pdata->key); | |
192 | if (status) | |
193 | return status; | |
194 | ||
195 | outb(LDN, pdata->base_port + ADDR_PORT); | |
196 | outb(pdata->index, pdata->base_port + DATA_PORT); | |
197 | ||
87a713c8 | 198 | outb(FINTEK_IRQ_MODE, pdata->base_port + ADDR_PORT); |
4da22f14 JZHPH |
199 | tmp = inb(pdata->base_port + DATA_PORT); |
200 | ||
201 | tmp &= ~IRQ_MODE_MASK; | |
202 | tmp |= IRQ_SHARE; | |
203 | if (!level_mode) | |
204 | tmp |= IRQ_EDGE_HIGH; | |
205 | ||
206 | outb(tmp, pdata->base_port + DATA_PORT); | |
207 | fintek_8250_exit_key(pdata->base_port); | |
208 | return 0; | |
209 | } | |
210 | ||
fa01e2ca | 211 | int fintek_8250_probe(struct uart_8250_port *uart) |
28e3fb6c | 212 | { |
92a5f11a | 213 | struct fintek_8250 *pdata; |
fa01e2ca | 214 | struct fintek_8250 probe_data; |
4da22f14 JZHPH |
215 | struct irq_data *irq_data = irq_get_irq_data(uart->port.irq); |
216 | bool level_mode = irqd_is_level_type(irq_data); | |
28e3fb6c | 217 | |
fa01e2ca | 218 | if (find_base_port(&probe_data, uart->port.iobase)) |
28e3fb6c RRD |
219 | return -ENODEV; |
220 | ||
fa01e2ca | 221 | pdata = devm_kzalloc(uart->port.dev, sizeof(*pdata), GFP_KERNEL); |
92a5f11a RRD |
222 | if (!pdata) |
223 | return -ENOMEM; | |
92a5f11a | 224 | |
fa01e2ca RRD |
225 | memcpy(pdata, &probe_data, sizeof(probe_data)); |
226 | uart->port.rs485_config = fintek_8250_rs485_config; | |
227 | uart->port.private_data = pdata; | |
28e3fb6c | 228 | |
4da22f14 | 229 | return fintek_8250_set_irq_mode(pdata, level_mode); |
28e3fb6c | 230 | } |