1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Renesas Emma Mobile 8250 driver 4 * 5 * Copyright (C) 2012 Magnus Damm 6 */ 7 8 #include <linux/device.h> 9 #include <linux/io.h> 10 #include <linux/module.h> 11 #include <linux/mod_devicetable.h> 12 #include <linux/serial_8250.h> 13 #include <linux/serial_reg.h> 14 #include <linux/platform_device.h> 15 #include <linux/clk.h> 16 17 #include "8250.h" 18 19 #define UART_DLL_EM 9 20 #define UART_DLM_EM 10 21 #define UART_HCR0_EM 11 22 23 /* 24 * A high value for UART_FCR_EM avoids overlapping with existing UART_* 25 * register defines. UART_FCR_EM_HW is the real HW register offset. 26 */ 27 #define UART_FCR_EM 0x10003 28 #define UART_FCR_EM_HW 3 29 30 #define UART_HCR0_EM_SW_RESET BIT(7) /* SW Reset */ 31 32 struct serial8250_em_priv { 33 int line; 34 }; 35 36 static void serial8250_em_serial_out_helper(struct uart_port *p, int offset, 37 int value) 38 { 39 switch (offset) { 40 case UART_TX: /* TX @ 0x00 */ 41 writeb(value, p->membase); 42 break; 43 case UART_LCR: /* LCR @ 0x10 (+1) */ 44 case UART_MCR: /* MCR @ 0x14 (+1) */ 45 case UART_SCR: /* SCR @ 0x20 (+1) */ 46 writel(value, p->membase + ((offset + 1) << 2)); 47 break; 48 case UART_FCR_EM: 49 writel(value, p->membase + (UART_FCR_EM_HW << 2)); 50 break; 51 case UART_IER: /* IER @ 0x04 */ 52 value &= 0x0f; /* only 4 valid bits - not Xscale */ 53 fallthrough; 54 case UART_DLL_EM: /* DLL @ 0x24 (+9) */ 55 case UART_DLM_EM: /* DLM @ 0x28 (+9) */ 56 case UART_HCR0_EM: /* HCR0 @ 0x2c */ 57 writel(value, p->membase + (offset << 2)); 58 break; 59 } 60 } 61 62 static unsigned int serial8250_em_serial_in(struct uart_port *p, int offset) 63 { 64 switch (offset) { 65 case UART_RX: /* RX @ 0x00 */ 66 return readb(p->membase); 67 case UART_LCR: /* LCR @ 0x10 (+1) */ 68 case UART_MCR: /* MCR @ 0x14 (+1) */ 69 case UART_LSR: /* LSR @ 0x18 (+1) */ 70 case UART_MSR: /* MSR @ 0x1c (+1) */ 71 case UART_SCR: /* SCR @ 0x20 (+1) */ 72 return readl(p->membase + ((offset + 1) << 2)); 73 case UART_FCR_EM: 74 return readl(p->membase + (UART_FCR_EM_HW << 2)); 75 case UART_IER: /* IER @ 0x04 */ 76 case UART_IIR: /* IIR @ 0x08 */ 77 case UART_DLL_EM: /* DLL @ 0x24 (+9) */ 78 case UART_DLM_EM: /* DLM @ 0x28 (+9) */ 79 case UART_HCR0_EM: /* HCR0 @ 0x2c */ 80 return readl(p->membase + (offset << 2)); 81 } 82 return 0; 83 } 84 85 static void serial8250_em_reg_update(struct uart_port *p, int off, int value) 86 { 87 unsigned int ier, fcr, lcr, mcr, hcr0; 88 89 ier = serial8250_em_serial_in(p, UART_IER); 90 fcr = serial8250_em_serial_in(p, UART_FCR_EM); 91 lcr = serial8250_em_serial_in(p, UART_LCR); 92 mcr = serial8250_em_serial_in(p, UART_MCR); 93 hcr0 = serial8250_em_serial_in(p, UART_HCR0_EM); 94 95 serial8250_em_serial_out_helper(p, UART_FCR_EM, fcr | 96 UART_FCR_CLEAR_RCVR | 97 UART_FCR_CLEAR_XMIT); 98 serial8250_em_serial_out_helper(p, UART_HCR0_EM, hcr0 | 99 UART_HCR0_EM_SW_RESET); 100 serial8250_em_serial_out_helper(p, UART_HCR0_EM, hcr0 & 101 ~UART_HCR0_EM_SW_RESET); 102 103 switch (off) { 104 case UART_FCR_EM: 105 fcr = value; 106 break; 107 case UART_LCR: 108 lcr = value; 109 break; 110 case UART_MCR: 111 mcr = value; 112 break; 113 } 114 115 serial8250_em_serial_out_helper(p, UART_IER, ier); 116 serial8250_em_serial_out_helper(p, UART_FCR_EM, fcr); 117 serial8250_em_serial_out_helper(p, UART_MCR, mcr); 118 serial8250_em_serial_out_helper(p, UART_LCR, lcr); 119 serial8250_em_serial_out_helper(p, UART_HCR0_EM, hcr0); 120 } 121 122 static void serial8250_em_serial_out(struct uart_port *p, int offset, int value) 123 { 124 switch (offset) { 125 case UART_TX: 126 case UART_SCR: 127 case UART_IER: 128 case UART_DLL_EM: 129 case UART_DLM_EM: 130 serial8250_em_serial_out_helper(p, offset, value); 131 break; 132 case UART_FCR: 133 serial8250_em_reg_update(p, UART_FCR_EM, value); 134 break; 135 case UART_LCR: 136 case UART_MCR: 137 serial8250_em_reg_update(p, offset, value); 138 break; 139 } 140 } 141 142 static int serial8250_em_serial_dl_read(struct uart_8250_port *up) 143 { 144 return serial_in(up, UART_DLL_EM) | serial_in(up, UART_DLM_EM) << 8; 145 } 146 147 static void serial8250_em_serial_dl_write(struct uart_8250_port *up, int value) 148 { 149 serial_out(up, UART_DLL_EM, value & 0xff); 150 serial_out(up, UART_DLM_EM, value >> 8 & 0xff); 151 } 152 153 static int serial8250_em_probe(struct platform_device *pdev) 154 { 155 struct serial8250_em_priv *priv; 156 struct device *dev = &pdev->dev; 157 struct uart_8250_port up; 158 struct resource *regs; 159 struct clk *sclk; 160 int irq, ret; 161 162 irq = platform_get_irq(pdev, 0); 163 if (irq < 0) 164 return irq; 165 166 regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); 167 if (!regs) 168 return dev_err_probe(dev, -EINVAL, "missing registers\n"); 169 170 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 171 if (!priv) 172 return -ENOMEM; 173 174 sclk = devm_clk_get_enabled(dev, "sclk"); 175 if (IS_ERR(sclk)) 176 return dev_err_probe(dev, PTR_ERR(sclk), "unable to get clock\n"); 177 178 memset(&up, 0, sizeof(up)); 179 up.port.mapbase = regs->start; 180 up.port.irq = irq; 181 up.port.type = PORT_16750; 182 up.port.flags = UPF_FIXED_PORT | UPF_IOREMAP | UPF_FIXED_TYPE; 183 up.port.dev = dev; 184 up.port.private_data = priv; 185 186 up.port.uartclk = clk_get_rate(sclk); 187 188 up.port.iotype = UPIO_MEM32; 189 up.port.serial_in = serial8250_em_serial_in; 190 up.port.serial_out = serial8250_em_serial_out; 191 up.dl_read = serial8250_em_serial_dl_read; 192 up.dl_write = serial8250_em_serial_dl_write; 193 194 ret = serial8250_register_8250_port(&up); 195 if (ret < 0) 196 return dev_err_probe(dev, ret, "unable to register 8250 port\n"); 197 198 priv->line = ret; 199 platform_set_drvdata(pdev, priv); 200 return 0; 201 } 202 203 static int serial8250_em_remove(struct platform_device *pdev) 204 { 205 struct serial8250_em_priv *priv = platform_get_drvdata(pdev); 206 207 serial8250_unregister_port(priv->line); 208 return 0; 209 } 210 211 static const struct of_device_id serial8250_em_dt_ids[] = { 212 { .compatible = "renesas,em-uart", }, 213 {}, 214 }; 215 MODULE_DEVICE_TABLE(of, serial8250_em_dt_ids); 216 217 static struct platform_driver serial8250_em_platform_driver = { 218 .driver = { 219 .name = "serial8250-em", 220 .of_match_table = serial8250_em_dt_ids, 221 }, 222 .probe = serial8250_em_probe, 223 .remove = serial8250_em_remove, 224 }; 225 226 module_platform_driver(serial8250_em_platform_driver); 227 228 MODULE_AUTHOR("Magnus Damm"); 229 MODULE_DESCRIPTION("Renesas Emma Mobile 8250 Driver"); 230 MODULE_LICENSE("GPL v2"); 231