xref: /openbmc/linux/drivers/tty/serial/8250/8250_fintek.c (revision 87a713c8ffca33d8e497a8b6c02034332bd80394)
128e3fb6cSRicardo Ribalda Delgado /*
228e3fb6cSRicardo Ribalda Delgado  *  Probe for F81216A LPC to 4 UART
328e3fb6cSRicardo Ribalda Delgado  *
4fa01e2caSRicardo Ribalda Delgado  *  Copyright (C) 2014-2016 Ricardo Ribalda, Qtechnology A/S
528e3fb6cSRicardo Ribalda Delgado  *
628e3fb6cSRicardo Ribalda Delgado  *
728e3fb6cSRicardo Ribalda Delgado  * This program is free software; you can redistribute it and/or modify
828e3fb6cSRicardo Ribalda Delgado  * it under the terms of the GNU General Public License as published by
928e3fb6cSRicardo Ribalda Delgado  * the Free Software Foundation; either version 2 of the License.
1028e3fb6cSRicardo Ribalda Delgado  */
1128e3fb6cSRicardo Ribalda Delgado #include <linux/module.h>
1228e3fb6cSRicardo Ribalda Delgado #include <linux/pci.h>
1328e3fb6cSRicardo Ribalda Delgado #include <linux/pnp.h>
1428e3fb6cSRicardo Ribalda Delgado #include <linux/kernel.h>
1528e3fb6cSRicardo Ribalda Delgado #include <linux/serial_core.h>
164da22f14SJi-Ze Hong (Peter Hong) #include <linux/irq.h>
1728e3fb6cSRicardo Ribalda Delgado #include  "8250.h"
1828e3fb6cSRicardo Ribalda Delgado 
19017bec38SRicardo Ribalda Delgado #define ADDR_PORT 0
20017bec38SRicardo Ribalda Delgado #define DATA_PORT 1
2128e3fb6cSRicardo Ribalda Delgado #define EXIT_KEY 0xAA
2228e3fb6cSRicardo Ribalda Delgado #define CHIP_ID1  0x20
2328e3fb6cSRicardo Ribalda Delgado #define CHIP_ID2  0x21
24dae77f75SRicardo Ribalda Delgado #define CHIP_ID_0 0x1602
25dae77f75SRicardo Ribalda Delgado #define CHIP_ID_1 0x0501
2628e3fb6cSRicardo Ribalda Delgado #define VENDOR_ID1 0x23
2728e3fb6cSRicardo Ribalda Delgado #define VENDOR_ID1_VAL 0x19
2828e3fb6cSRicardo Ribalda Delgado #define VENDOR_ID2 0x24
2928e3fb6cSRicardo Ribalda Delgado #define VENDOR_ID2_VAL 0x34
3029d58642SRicardo Ribalda Delgado #define IO_ADDR1 0x61
3129d58642SRicardo Ribalda Delgado #define IO_ADDR2 0x60
3228e3fb6cSRicardo Ribalda Delgado #define LDN 0x7
3328e3fb6cSRicardo Ribalda Delgado 
34*87a713c8SArnd Bergmann #define FINTEK_IRQ_MODE	0x70
354da22f14SJi-Ze Hong (Peter Hong) #define IRQ_SHARE	BIT(4)
364da22f14SJi-Ze Hong (Peter Hong) #define IRQ_MODE_MASK	(BIT(6) | BIT(5))
374da22f14SJi-Ze Hong (Peter Hong) #define IRQ_LEVEL_LOW	0
384da22f14SJi-Ze Hong (Peter Hong) #define IRQ_EDGE_HIGH	BIT(5)
394da22f14SJi-Ze Hong (Peter Hong) 
4028e3fb6cSRicardo Ribalda Delgado #define RS485  0xF0
4128e3fb6cSRicardo Ribalda Delgado #define RTS_INVERT BIT(5)
4228e3fb6cSRicardo Ribalda Delgado #define RS485_URA BIT(4)
4328e3fb6cSRicardo Ribalda Delgado #define RXW4C_IRA BIT(3)
4428e3fb6cSRicardo Ribalda Delgado #define TXW4C_IRA BIT(2)
4528e3fb6cSRicardo Ribalda Delgado 
4692a5f11aSRicardo Ribalda Delgado struct fintek_8250 {
47017bec38SRicardo Ribalda Delgado 	u16 base_port;
4892a5f11aSRicardo Ribalda Delgado 	u8 index;
49ce8c267eSRicardo Ribalda Delgado 	u8 key;
5092a5f11aSRicardo Ribalda Delgado };
5192a5f11aSRicardo Ribalda Delgado 
52ce8c267eSRicardo Ribalda Delgado static int fintek_8250_enter_key(u16 base_port, u8 key)
53017bec38SRicardo Ribalda Delgado {
54fa01e2caSRicardo Ribalda Delgado 	if (!request_muxed_region(base_port, 2, "8250_fintek"))
5528e3fb6cSRicardo Ribalda Delgado 		return -EBUSY;
5628e3fb6cSRicardo Ribalda Delgado 
57ce8c267eSRicardo Ribalda Delgado 	outb(key, base_port + ADDR_PORT);
58ce8c267eSRicardo Ribalda Delgado 	outb(key, base_port + ADDR_PORT);
5928e3fb6cSRicardo Ribalda Delgado 	return 0;
6028e3fb6cSRicardo Ribalda Delgado }
6128e3fb6cSRicardo Ribalda Delgado 
62017bec38SRicardo Ribalda Delgado static void fintek_8250_exit_key(u16 base_port)
63017bec38SRicardo Ribalda Delgado {
6428e3fb6cSRicardo Ribalda Delgado 
65017bec38SRicardo Ribalda Delgado 	outb(EXIT_KEY, base_port + ADDR_PORT);
66017bec38SRicardo Ribalda Delgado 	release_region(base_port + ADDR_PORT, 2);
6728e3fb6cSRicardo Ribalda Delgado }
6828e3fb6cSRicardo Ribalda Delgado 
69017bec38SRicardo Ribalda Delgado static int fintek_8250_check_id(u16 base_port)
7028e3fb6cSRicardo Ribalda Delgado {
71dae77f75SRicardo Ribalda Delgado 	u16 chip;
7228e3fb6cSRicardo Ribalda Delgado 
73017bec38SRicardo Ribalda Delgado 	outb(VENDOR_ID1, base_port + ADDR_PORT);
74017bec38SRicardo Ribalda Delgado 	if (inb(base_port + DATA_PORT) != VENDOR_ID1_VAL)
7528e3fb6cSRicardo Ribalda Delgado 		return -ENODEV;
7628e3fb6cSRicardo Ribalda Delgado 
77017bec38SRicardo Ribalda Delgado 	outb(VENDOR_ID2, base_port + ADDR_PORT);
78017bec38SRicardo Ribalda Delgado 	if (inb(base_port + DATA_PORT) != VENDOR_ID2_VAL)
7928e3fb6cSRicardo Ribalda Delgado 		return -ENODEV;
8028e3fb6cSRicardo Ribalda Delgado 
81dae77f75SRicardo Ribalda Delgado 	outb(CHIP_ID1, base_port + ADDR_PORT);
82dae77f75SRicardo Ribalda Delgado 	chip = inb(base_port + DATA_PORT);
83dae77f75SRicardo Ribalda Delgado 	outb(CHIP_ID2, base_port + ADDR_PORT);
84dae77f75SRicardo Ribalda Delgado 	chip |= inb(base_port + DATA_PORT) << 8;
85dae77f75SRicardo Ribalda Delgado 
86dae77f75SRicardo Ribalda Delgado 	if (chip != CHIP_ID_0 && chip != CHIP_ID_1)
87dae77f75SRicardo Ribalda Delgado 		return -ENODEV;
88dae77f75SRicardo Ribalda Delgado 
8928e3fb6cSRicardo Ribalda Delgado 	return 0;
9028e3fb6cSRicardo Ribalda Delgado }
9128e3fb6cSRicardo Ribalda Delgado 
9241e69093SRicardo Ribalda Delgado static int fintek_8250_rs485_config(struct uart_port *port,
9328e3fb6cSRicardo Ribalda Delgado 			      struct serial_rs485 *rs485)
9428e3fb6cSRicardo Ribalda Delgado {
9528e3fb6cSRicardo Ribalda Delgado 	uint8_t config = 0;
9692a5f11aSRicardo Ribalda Delgado 	struct fintek_8250 *pdata = port->private_data;
9728e3fb6cSRicardo Ribalda Delgado 
9892a5f11aSRicardo Ribalda Delgado 	if (!pdata)
9928e3fb6cSRicardo Ribalda Delgado 		return -EINVAL;
10028e3fb6cSRicardo Ribalda Delgado 
10128e3fb6cSRicardo Ribalda Delgado 	if (rs485->flags & SER_RS485_ENABLED)
10228e3fb6cSRicardo Ribalda Delgado 		memset(rs485->padding, 0, sizeof(rs485->padding));
10328e3fb6cSRicardo Ribalda Delgado 	else
10428e3fb6cSRicardo Ribalda Delgado 		memset(rs485, 0, sizeof(*rs485));
10528e3fb6cSRicardo Ribalda Delgado 
10628e3fb6cSRicardo Ribalda Delgado 	rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND |
10728e3fb6cSRicardo Ribalda Delgado 			SER_RS485_RTS_AFTER_SEND;
10828e3fb6cSRicardo Ribalda Delgado 
10928e3fb6cSRicardo Ribalda Delgado 	if (rs485->delay_rts_before_send) {
11028e3fb6cSRicardo Ribalda Delgado 		rs485->delay_rts_before_send = 1;
11128e3fb6cSRicardo Ribalda Delgado 		config |= TXW4C_IRA;
11228e3fb6cSRicardo Ribalda Delgado 	}
11328e3fb6cSRicardo Ribalda Delgado 
11428e3fb6cSRicardo Ribalda Delgado 	if (rs485->delay_rts_after_send) {
11528e3fb6cSRicardo Ribalda Delgado 		rs485->delay_rts_after_send = 1;
11628e3fb6cSRicardo Ribalda Delgado 		config |= RXW4C_IRA;
11728e3fb6cSRicardo Ribalda Delgado 	}
11828e3fb6cSRicardo Ribalda Delgado 
11928e3fb6cSRicardo Ribalda Delgado 	if ((!!(rs485->flags & SER_RS485_RTS_ON_SEND)) ==
12028e3fb6cSRicardo Ribalda Delgado 			(!!(rs485->flags & SER_RS485_RTS_AFTER_SEND)))
12128e3fb6cSRicardo Ribalda Delgado 		rs485->flags &= SER_RS485_ENABLED;
12228e3fb6cSRicardo Ribalda Delgado 	else
12328e3fb6cSRicardo Ribalda Delgado 		config |= RS485_URA;
12428e3fb6cSRicardo Ribalda Delgado 
12528e3fb6cSRicardo Ribalda Delgado 	if (rs485->flags & SER_RS485_RTS_ON_SEND)
12628e3fb6cSRicardo Ribalda Delgado 		config |= RTS_INVERT;
12728e3fb6cSRicardo Ribalda Delgado 
128ce8c267eSRicardo Ribalda Delgado 	if (fintek_8250_enter_key(pdata->base_port, pdata->key))
12928e3fb6cSRicardo Ribalda Delgado 		return -EBUSY;
13028e3fb6cSRicardo Ribalda Delgado 
131017bec38SRicardo Ribalda Delgado 	outb(LDN, pdata->base_port + ADDR_PORT);
132017bec38SRicardo Ribalda Delgado 	outb(pdata->index, pdata->base_port + DATA_PORT);
133017bec38SRicardo Ribalda Delgado 	outb(RS485, pdata->base_port + ADDR_PORT);
134017bec38SRicardo Ribalda Delgado 	outb(config, pdata->base_port + DATA_PORT);
135017bec38SRicardo Ribalda Delgado 	fintek_8250_exit_key(pdata->base_port);
13628e3fb6cSRicardo Ribalda Delgado 
13741e69093SRicardo Ribalda Delgado 	port->rs485 = *rs485;
13841e69093SRicardo Ribalda Delgado 
13928e3fb6cSRicardo Ribalda Delgado 	return 0;
14028e3fb6cSRicardo Ribalda Delgado }
14128e3fb6cSRicardo Ribalda Delgado 
142fa01e2caSRicardo Ribalda Delgado static int find_base_port(struct fintek_8250 *pdata, u16 io_address)
143017bec38SRicardo Ribalda Delgado {
144017bec38SRicardo Ribalda Delgado 	static const u16 addr[] = {0x4e, 0x2e};
145ce8c267eSRicardo Ribalda Delgado 	static const u8 keys[] = {0x77, 0xa0, 0x87, 0x67};
14629d58642SRicardo Ribalda Delgado 	int i, j, k;
147017bec38SRicardo Ribalda Delgado 
148017bec38SRicardo Ribalda Delgado 	for (i = 0; i < ARRAY_SIZE(addr); i++) {
149ce8c267eSRicardo Ribalda Delgado 		for (j = 0; j < ARRAY_SIZE(keys); j++) {
150017bec38SRicardo Ribalda Delgado 
151ce8c267eSRicardo Ribalda Delgado 			if (fintek_8250_enter_key(addr[i], keys[j]))
152017bec38SRicardo Ribalda Delgado 				continue;
15329d58642SRicardo Ribalda Delgado 			if (fintek_8250_check_id(addr[i])) {
154017bec38SRicardo Ribalda Delgado 				fintek_8250_exit_key(addr[i]);
15529d58642SRicardo Ribalda Delgado 				continue;
15629d58642SRicardo Ribalda Delgado 			}
15729d58642SRicardo Ribalda Delgado 
15829d58642SRicardo Ribalda Delgado 			for (k = 0; k < 4; k++) {
15929d58642SRicardo Ribalda Delgado 				u16 aux;
16029d58642SRicardo Ribalda Delgado 
16129d58642SRicardo Ribalda Delgado 				outb(LDN, addr[i] + ADDR_PORT);
16229d58642SRicardo Ribalda Delgado 				outb(k, addr[i] + DATA_PORT);
16329d58642SRicardo Ribalda Delgado 
16429d58642SRicardo Ribalda Delgado 				outb(IO_ADDR1, addr[i] + ADDR_PORT);
16529d58642SRicardo Ribalda Delgado 				aux = inb(addr[i] + DATA_PORT);
16629d58642SRicardo Ribalda Delgado 				outb(IO_ADDR2, addr[i] + ADDR_PORT);
16729d58642SRicardo Ribalda Delgado 				aux |= inb(addr[i] + DATA_PORT) << 8;
16829d58642SRicardo Ribalda Delgado 				if (aux != io_address)
16929d58642SRicardo Ribalda Delgado 					continue;
17029d58642SRicardo Ribalda Delgado 
17129d58642SRicardo Ribalda Delgado 				fintek_8250_exit_key(addr[i]);
172fa01e2caSRicardo Ribalda Delgado 				pdata->key = keys[j];
173fa01e2caSRicardo Ribalda Delgado 				pdata->base_port = addr[i];
174fa01e2caSRicardo Ribalda Delgado 				pdata->index = k;
175fa01e2caSRicardo Ribalda Delgado 
176fa01e2caSRicardo Ribalda Delgado 				return 0;
177017bec38SRicardo Ribalda Delgado 			}
178fa01e2caSRicardo Ribalda Delgado 
17929d58642SRicardo Ribalda Delgado 			fintek_8250_exit_key(addr[i]);
180ce8c267eSRicardo Ribalda Delgado 		}
181ce8c267eSRicardo Ribalda Delgado 	}
182017bec38SRicardo Ribalda Delgado 
183017bec38SRicardo Ribalda Delgado 	return -ENODEV;
184017bec38SRicardo Ribalda Delgado }
185017bec38SRicardo Ribalda Delgado 
1864da22f14SJi-Ze Hong (Peter Hong) static int fintek_8250_set_irq_mode(struct fintek_8250 *pdata, bool level_mode)
1874da22f14SJi-Ze Hong (Peter Hong) {
1884da22f14SJi-Ze Hong (Peter Hong) 	int status;
1894da22f14SJi-Ze Hong (Peter Hong) 	u8 tmp;
1904da22f14SJi-Ze Hong (Peter Hong) 
1914da22f14SJi-Ze Hong (Peter Hong) 	status = fintek_8250_enter_key(pdata->base_port, pdata->key);
1924da22f14SJi-Ze Hong (Peter Hong) 	if (status)
1934da22f14SJi-Ze Hong (Peter Hong) 		return status;
1944da22f14SJi-Ze Hong (Peter Hong) 
1954da22f14SJi-Ze Hong (Peter Hong) 	outb(LDN, pdata->base_port + ADDR_PORT);
1964da22f14SJi-Ze Hong (Peter Hong) 	outb(pdata->index, pdata->base_port + DATA_PORT);
1974da22f14SJi-Ze Hong (Peter Hong) 
198*87a713c8SArnd Bergmann 	outb(FINTEK_IRQ_MODE, pdata->base_port + ADDR_PORT);
1994da22f14SJi-Ze Hong (Peter Hong) 	tmp = inb(pdata->base_port + DATA_PORT);
2004da22f14SJi-Ze Hong (Peter Hong) 
2014da22f14SJi-Ze Hong (Peter Hong) 	tmp &= ~IRQ_MODE_MASK;
2024da22f14SJi-Ze Hong (Peter Hong) 	tmp |= IRQ_SHARE;
2034da22f14SJi-Ze Hong (Peter Hong) 	if (!level_mode)
2044da22f14SJi-Ze Hong (Peter Hong) 		tmp |= IRQ_EDGE_HIGH;
2054da22f14SJi-Ze Hong (Peter Hong) 
2064da22f14SJi-Ze Hong (Peter Hong) 	outb(tmp, pdata->base_port + DATA_PORT);
2074da22f14SJi-Ze Hong (Peter Hong) 	fintek_8250_exit_key(pdata->base_port);
2084da22f14SJi-Ze Hong (Peter Hong) 	return 0;
2094da22f14SJi-Ze Hong (Peter Hong) }
2104da22f14SJi-Ze Hong (Peter Hong) 
211fa01e2caSRicardo Ribalda Delgado int fintek_8250_probe(struct uart_8250_port *uart)
21228e3fb6cSRicardo Ribalda Delgado {
21392a5f11aSRicardo Ribalda Delgado 	struct fintek_8250 *pdata;
214fa01e2caSRicardo Ribalda Delgado 	struct fintek_8250 probe_data;
2154da22f14SJi-Ze Hong (Peter Hong) 	struct irq_data *irq_data = irq_get_irq_data(uart->port.irq);
2164da22f14SJi-Ze Hong (Peter Hong) 	bool level_mode = irqd_is_level_type(irq_data);
21728e3fb6cSRicardo Ribalda Delgado 
218fa01e2caSRicardo Ribalda Delgado 	if (find_base_port(&probe_data, uart->port.iobase))
21928e3fb6cSRicardo Ribalda Delgado 		return -ENODEV;
22028e3fb6cSRicardo Ribalda Delgado 
221fa01e2caSRicardo Ribalda Delgado 	pdata = devm_kzalloc(uart->port.dev, sizeof(*pdata), GFP_KERNEL);
22292a5f11aSRicardo Ribalda Delgado 	if (!pdata)
22392a5f11aSRicardo Ribalda Delgado 		return -ENOMEM;
22492a5f11aSRicardo Ribalda Delgado 
225fa01e2caSRicardo Ribalda Delgado 	memcpy(pdata, &probe_data, sizeof(probe_data));
226fa01e2caSRicardo Ribalda Delgado 	uart->port.rs485_config = fintek_8250_rs485_config;
227fa01e2caSRicardo Ribalda Delgado 	uart->port.private_data = pdata;
22828e3fb6cSRicardo Ribalda Delgado 
2294da22f14SJi-Ze Hong (Peter Hong) 	return fintek_8250_set_irq_mode(pdata, level_mode);
23028e3fb6cSRicardo Ribalda Delgado }
231