1 /*
2  * Copyright (C) 2012-2014 Panasonic Corporation
3  *   Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
4  *
5  * SPDX-License-Identifier:	GPL-2.0+
6  */
7 
8 #include <linux/serial_reg.h>
9 #include <asm/io.h>
10 #include <asm/errno.h>
11 #include <dm/device.h>
12 #include <dm/platform_data/serial-uniphier.h>
13 #include <serial.h>
14 #include <fdtdec.h>
15 
16 #define UART_REG(x)					\
17 	u8 x;						\
18 	u8 postpad_##x[3];
19 
20 /*
21  * Note: Register map is slightly different from that of 16550.
22  */
23 struct uniphier_serial {
24 	UART_REG(rbr);		/* 0x00 */
25 	UART_REG(ier);		/* 0x04 */
26 	UART_REG(iir);		/* 0x08 */
27 	UART_REG(fcr);		/* 0x0c */
28 	u8 mcr;			/* 0x10 */
29 	u8 lcr;
30 	u16 __postpad;
31 	UART_REG(lsr);		/* 0x14 */
32 	UART_REG(msr);		/* 0x18 */
33 	u32 __none1;
34 	u32 __none2;
35 	u16 dlr;
36 	u16 __postpad2;
37 };
38 
39 #define thr rbr
40 
41 struct uniphier_serial_private_data {
42 	struct uniphier_serial __iomem *membase;
43 };
44 
45 #define uniphier_serial_port(dev)	\
46 	((struct uniphier_serial_private_data *)dev_get_priv(dev))->membase
47 
48 static int uniphier_serial_setbrg(struct udevice *dev, int baudrate)
49 {
50 	struct uniphier_serial_platform_data *plat = dev_get_platdata(dev);
51 	struct uniphier_serial __iomem *port = uniphier_serial_port(dev);
52 	const unsigned int mode_x_div = 16;
53 	unsigned int divisor;
54 
55 	writeb(UART_LCR_WLEN8, &port->lcr);
56 
57 	divisor = DIV_ROUND_CLOSEST(plat->uartclk, mode_x_div * baudrate);
58 
59 	writew(divisor, &port->dlr);
60 
61 	return 0;
62 }
63 
64 static int uniphier_serial_getc(struct udevice *dev)
65 {
66 	struct uniphier_serial __iomem *port = uniphier_serial_port(dev);
67 
68 	if (!(readb(&port->lsr) & UART_LSR_DR))
69 		return -EAGAIN;
70 
71 	return readb(&port->rbr);
72 }
73 
74 static int uniphier_serial_putc(struct udevice *dev, const char c)
75 {
76 	struct uniphier_serial __iomem *port = uniphier_serial_port(dev);
77 
78 	if (!(readb(&port->lsr) & UART_LSR_THRE))
79 		return -EAGAIN;
80 
81 	writeb(c, &port->thr);
82 
83 	return 0;
84 }
85 
86 static int uniphier_serial_pending(struct udevice *dev, bool input)
87 {
88 	struct uniphier_serial __iomem *port = uniphier_serial_port(dev);
89 
90 	if (input)
91 		return readb(&port->lsr) & UART_LSR_DR;
92 	else
93 		return !(readb(&port->lsr) & UART_LSR_THRE);
94 }
95 
96 static int uniphier_serial_probe(struct udevice *dev)
97 {
98 	struct uniphier_serial_private_data *priv = dev_get_priv(dev);
99 	struct uniphier_serial_platform_data *plat = dev_get_platdata(dev);
100 
101 	priv->membase = map_sysmem(plat->base, sizeof(struct uniphier_serial));
102 
103 	if (!priv->membase)
104 		return -ENOMEM;
105 
106 	return 0;
107 }
108 
109 static int uniphier_serial_remove(struct udevice *dev)
110 {
111 	unmap_sysmem(uniphier_serial_port(dev));
112 
113 	return 0;
114 }
115 
116 #ifdef CONFIG_OF_CONTROL
117 static const struct udevice_id uniphier_uart_of_match[] = {
118 	{ .compatible = "panasonic,uniphier-uart" },
119 	{},
120 };
121 
122 static int uniphier_serial_ofdata_to_platdata(struct udevice *dev)
123 {
124 	struct uniphier_serial_platform_data *plat = dev_get_platdata(dev);
125 	DECLARE_GLOBAL_DATA_PTR;
126 
127 	plat->base = fdtdec_get_addr(gd->fdt_blob, dev->of_offset, "reg");
128 	plat->uartclk = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
129 				       "clock-frequency", 0);
130 
131 	return 0;
132 }
133 #endif
134 
135 static const struct dm_serial_ops uniphier_serial_ops = {
136 	.setbrg = uniphier_serial_setbrg,
137 	.getc = uniphier_serial_getc,
138 	.putc = uniphier_serial_putc,
139 	.pending = uniphier_serial_pending,
140 };
141 
142 U_BOOT_DRIVER(uniphier_serial) = {
143 	.name = DRIVER_NAME,
144 	.id = UCLASS_SERIAL,
145 	.of_match = of_match_ptr(uniphier_uart_of_match),
146 	.ofdata_to_platdata = of_match_ptr(uniphier_serial_ofdata_to_platdata),
147 	.probe = uniphier_serial_probe,
148 	.remove = uniphier_serial_remove,
149 	.priv_auto_alloc_size = sizeof(struct uniphier_serial_private_data),
150 	.platdata_auto_alloc_size =
151 				sizeof(struct uniphier_serial_platform_data),
152 	.ops = &uniphier_serial_ops,
153 	.flags = DM_FLAG_PRE_RELOC,
154 };
155