Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Input: serio - add support for Alwinner A10/A20 PS/2 controller

This driver implements support for PS2 controller found on Allwinner A10,
A20 SOCs. It has been tested on A20 Olimex-Lime2 board and also on A10.

Signed-off-by: Vishnu Patekar <vishnupatekar0510@gmail.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

authored by

Vishnu Patekar and committed by
Dmitry Torokhov
e443631d 8d212820

+374
+23
Documentation/devicetree/bindings/serio/allwinner,sun4i-ps2.txt
··· 1 + * Device tree bindings for Allwinner A10, A20 PS2 host controller 2 + 3 + A20 PS2 is dual role controller (PS2 host and PS2 device). These bindings are 4 + for PS2 A10/A20 host controller. IBM compliant IBM PS2 and AT-compatible keyboard 5 + and mouse can be connected. 6 + 7 + Required properties: 8 + 9 + - reg : Offset and length of the register set for the device. 10 + - compatible : Should be as of the following: 11 + - "allwinner,sun4i-a10-ps2" 12 + - interrupts : The interrupt line connected to the PS2. 13 + - clocks : The gate clk connected to the PS2. 14 + 15 + 16 + Example: 17 + ps20: ps2@0x01c2a000 { 18 + compatible = "allwinner,sun4i-a10-ps2"; 19 + reg = <0x01c2a000 0x400>; 20 + interrupts = <0 62 4>; 21 + clocks = <&apb1_gates 6>; 22 + status = "disabled"; 23 + };
+10
drivers/input/serio/Kconfig
··· 281 281 To compile this driver as a module, choose M here: the module will 282 282 be called hyperv_keyboard. 283 283 284 + config SERIO_SUN4I_PS2 285 + tristate "Allwinner A10 PS/2 controller support" 286 + depends on ARCH_SUNXI || COMPILE_TEST 287 + help 288 + This selects support for the PS/2 Host Controller on 289 + Allwinner A10. 290 + 291 + To compile this driver as a module, choose M here: the 292 + module will be called sun4i-ps2. 293 + 284 294 endif
+1
drivers/input/serio/Makefile
··· 29 29 obj-$(CONFIG_SERIO_APBPS2) += apbps2.o 30 30 obj-$(CONFIG_SERIO_OLPC_APSP) += olpc_apsp.o 31 31 obj-$(CONFIG_HYPERV_KEYBOARD) += hyperv-keyboard.o 32 + obj-$(CONFIG_SERIO_SUN4I_PS2) += sun4i-ps2.o
+340
drivers/input/serio/sun4i-ps2.c
··· 1 + /* 2 + * Driver for Allwinner A10 PS2 host controller 3 + * 4 + * Author: Vishnu Patekar <vishnupatekar0510@gmail.com> 5 + * Aaron.maoye <leafy.myeh@newbietech.com> 6 + */ 7 + 8 + #include <linux/module.h> 9 + #include <linux/serio.h> 10 + #include <linux/interrupt.h> 11 + #include <linux/errno.h> 12 + #include <linux/slab.h> 13 + #include <linux/io.h> 14 + #include <linux/clk.h> 15 + #include <linux/mod_devicetable.h> 16 + #include <linux/platform_device.h> 17 + 18 + #define DRIVER_NAME "sun4i-ps2" 19 + 20 + /* register offset definitions */ 21 + #define PS2_REG_GCTL 0x00 /* PS2 Module Global Control Reg */ 22 + #define PS2_REG_DATA 0x04 /* PS2 Module Data Reg */ 23 + #define PS2_REG_LCTL 0x08 /* PS2 Module Line Control Reg */ 24 + #define PS2_REG_LSTS 0x0C /* PS2 Module Line Status Reg */ 25 + #define PS2_REG_FCTL 0x10 /* PS2 Module FIFO Control Reg */ 26 + #define PS2_REG_FSTS 0x14 /* PS2 Module FIFO Status Reg */ 27 + #define PS2_REG_CLKDR 0x18 /* PS2 Module Clock Divider Reg*/ 28 + 29 + /* PS2 GLOBAL CONTROL REGISTER PS2_GCTL */ 30 + #define PS2_GCTL_INTFLAG BIT(4) 31 + #define PS2_GCTL_INTEN BIT(3) 32 + #define PS2_GCTL_RESET BIT(2) 33 + #define PS2_GCTL_MASTER BIT(1) 34 + #define PS2_GCTL_BUSEN BIT(0) 35 + 36 + /* PS2 LINE CONTROL REGISTER */ 37 + #define PS2_LCTL_NOACK BIT(18) 38 + #define PS2_LCTL_TXDTOEN BIT(8) 39 + #define PS2_LCTL_STOPERREN BIT(3) 40 + #define PS2_LCTL_ACKERREN BIT(2) 41 + #define PS2_LCTL_PARERREN BIT(1) 42 + #define PS2_LCTL_RXDTOEN BIT(0) 43 + 44 + /* PS2 LINE STATUS REGISTER */ 45 + #define PS2_LSTS_TXTDO BIT(8) 46 + #define PS2_LSTS_STOPERR BIT(3) 47 + #define PS2_LSTS_ACKERR BIT(2) 48 + #define PS2_LSTS_PARERR BIT(1) 49 + #define PS2_LSTS_RXTDO BIT(0) 50 + 51 + #define PS2_LINE_ERROR_BIT \ 52 + (PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | \ 53 + PS2_LSTS_PARERR | PS2_LSTS_RXTDO) 54 + 55 + /* PS2 FIFO CONTROL REGISTER */ 56 + #define PS2_FCTL_TXRST BIT(17) 57 + #define PS2_FCTL_RXRST BIT(16) 58 + #define PS2_FCTL_TXUFIEN BIT(10) 59 + #define PS2_FCTL_TXOFIEN BIT(9) 60 + #define PS2_FCTL_TXRDYIEN BIT(8) 61 + #define PS2_FCTL_RXUFIEN BIT(2) 62 + #define PS2_FCTL_RXOFIEN BIT(1) 63 + #define PS2_FCTL_RXRDYIEN BIT(0) 64 + 65 + /* PS2 FIFO STATUS REGISTER */ 66 + #define PS2_FSTS_TXUF BIT(10) 67 + #define PS2_FSTS_TXOF BIT(9) 68 + #define PS2_FSTS_TXRDY BIT(8) 69 + #define PS2_FSTS_RXUF BIT(2) 70 + #define PS2_FSTS_RXOF BIT(1) 71 + #define PS2_FSTS_RXRDY BIT(0) 72 + 73 + #define PS2_FIFO_ERROR_BIT \ 74 + (PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_RXUF | PS2_FSTS_RXOF) 75 + 76 + #define PS2_SAMPLE_CLK 1000000 77 + #define PS2_SCLK 125000 78 + 79 + struct sun4i_ps2data { 80 + struct serio *serio; 81 + struct device *dev; 82 + 83 + /* IO mapping base */ 84 + void __iomem *reg_base; 85 + 86 + /* clock management */ 87 + struct clk *clk; 88 + 89 + /* irq */ 90 + spinlock_t lock; 91 + int irq; 92 + }; 93 + 94 + static irqreturn_t sun4i_ps2_interrupt(int irq, void *dev_id) 95 + { 96 + struct sun4i_ps2data *drvdata = dev_id; 97 + u32 intr_status; 98 + u32 fifo_status; 99 + unsigned char byte; 100 + unsigned int rxflags = 0; 101 + u32 rval; 102 + 103 + spin_lock(&drvdata->lock); 104 + 105 + /* Get the PS/2 interrupts and clear them */ 106 + intr_status = readl(drvdata->reg_base + PS2_REG_LSTS); 107 + fifo_status = readl(drvdata->reg_base + PS2_REG_FSTS); 108 + 109 + /* Check line status register */ 110 + if (intr_status & PS2_LINE_ERROR_BIT) { 111 + rxflags = (intr_status & PS2_LINE_ERROR_BIT) ? SERIO_FRAME : 0; 112 + rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_PARITY : 0; 113 + rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_TIMEOUT : 0; 114 + 115 + rval = PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | 116 + PS2_LSTS_PARERR | PS2_LSTS_RXTDO; 117 + writel(rval, drvdata->reg_base + PS2_REG_LSTS); 118 + } 119 + 120 + /* Check FIFO status register */ 121 + if (fifo_status & PS2_FIFO_ERROR_BIT) { 122 + rval = PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_TXRDY | 123 + PS2_FSTS_RXUF | PS2_FSTS_RXOF | PS2_FSTS_RXRDY; 124 + writel(rval, drvdata->reg_base + PS2_REG_FSTS); 125 + } 126 + 127 + rval = (fifo_status >> 16) & 0x3; 128 + while (rval--) { 129 + byte = readl(drvdata->reg_base + PS2_REG_DATA) & 0xff; 130 + serio_interrupt(drvdata->serio, byte, rxflags); 131 + } 132 + 133 + writel(intr_status, drvdata->reg_base + PS2_REG_LSTS); 134 + writel(fifo_status, drvdata->reg_base + PS2_REG_FSTS); 135 + 136 + spin_unlock(&drvdata->lock); 137 + 138 + return IRQ_HANDLED; 139 + } 140 + 141 + static int sun4i_ps2_open(struct serio *serio) 142 + { 143 + struct sun4i_ps2data *drvdata = serio->port_data; 144 + u32 src_clk = 0; 145 + u32 clk_scdf; 146 + u32 clk_pcdf; 147 + u32 rval; 148 + unsigned long flags; 149 + 150 + /* Set line control and enable interrupt */ 151 + rval = PS2_LCTL_STOPERREN | PS2_LCTL_ACKERREN 152 + | PS2_LCTL_PARERREN | PS2_LCTL_RXDTOEN; 153 + writel(rval, drvdata->reg_base + PS2_REG_LCTL); 154 + 155 + /* Reset FIFO */ 156 + rval = PS2_FCTL_TXRST | PS2_FCTL_RXRST | PS2_FCTL_TXUFIEN 157 + | PS2_FCTL_TXOFIEN | PS2_FCTL_RXUFIEN 158 + | PS2_FCTL_RXOFIEN | PS2_FCTL_RXRDYIEN; 159 + 160 + writel(rval, drvdata->reg_base + PS2_REG_FCTL); 161 + 162 + src_clk = clk_get_rate(drvdata->clk); 163 + /* Set clock divider register */ 164 + clk_scdf = src_clk / PS2_SAMPLE_CLK - 1; 165 + clk_pcdf = PS2_SAMPLE_CLK / PS2_SCLK - 1; 166 + rval = (clk_scdf << 8) | clk_pcdf; 167 + writel(rval, drvdata->reg_base + PS2_REG_CLKDR); 168 + 169 + /* Set global control register */ 170 + rval = PS2_GCTL_RESET | PS2_GCTL_INTEN | PS2_GCTL_MASTER 171 + | PS2_GCTL_BUSEN; 172 + 173 + spin_lock_irqsave(&drvdata->lock, flags); 174 + writel(rval, drvdata->reg_base + PS2_REG_GCTL); 175 + spin_unlock_irqrestore(&drvdata->lock, flags); 176 + 177 + return 0; 178 + } 179 + 180 + static void sun4i_ps2_close(struct serio *serio) 181 + { 182 + struct sun4i_ps2data *drvdata = serio->port_data; 183 + u32 rval; 184 + 185 + /* Shut off the interrupt */ 186 + rval = readl(drvdata->reg_base + PS2_REG_GCTL); 187 + writel(rval & ~(PS2_GCTL_INTEN), drvdata->reg_base + PS2_REG_GCTL); 188 + 189 + synchronize_irq(drvdata->irq); 190 + } 191 + 192 + static int sun4i_ps2_write(struct serio *serio, unsigned char val) 193 + { 194 + unsigned long expire = jiffies + msecs_to_jiffies(10000); 195 + struct sun4i_ps2data *drvdata = serio->port_data; 196 + 197 + do { 198 + if (readl(drvdata->reg_base + PS2_REG_FSTS) & PS2_FSTS_TXRDY) { 199 + writel(val, drvdata->reg_base + PS2_REG_DATA); 200 + return 0; 201 + } 202 + } while (time_before(jiffies, expire)); 203 + 204 + return SERIO_TIMEOUT; 205 + } 206 + 207 + static int sun4i_ps2_probe(struct platform_device *pdev) 208 + { 209 + struct resource *res; /* IO mem resources */ 210 + struct sun4i_ps2data *drvdata; 211 + struct serio *serio; 212 + struct device *dev = &pdev->dev; 213 + unsigned int irq; 214 + int error; 215 + 216 + drvdata = kzalloc(sizeof(struct sun4i_ps2data), GFP_KERNEL); 217 + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); 218 + if (!drvdata || !serio) { 219 + error = -ENOMEM; 220 + goto err_free_mem; 221 + } 222 + 223 + spin_lock_init(&drvdata->lock); 224 + 225 + /* IO */ 226 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 227 + if (!res) { 228 + dev_err(dev, "failed to locate registers\n"); 229 + error = -ENXIO; 230 + goto err_free_mem; 231 + } 232 + 233 + drvdata->reg_base = ioremap(res->start, resource_size(res)); 234 + if (!drvdata->reg_base) { 235 + dev_err(dev, "failed to map registers\n"); 236 + error = -ENOMEM; 237 + goto err_free_mem; 238 + } 239 + 240 + drvdata->clk = clk_get(dev, NULL); 241 + if (IS_ERR(drvdata->clk)) { 242 + error = PTR_ERR(drvdata->clk); 243 + dev_err(dev, "couldn't get clock %d\n", error); 244 + goto err_ioremap; 245 + } 246 + 247 + error = clk_prepare_enable(drvdata->clk); 248 + if (error) { 249 + dev_err(dev, "failed to enable clock %d\n", error); 250 + goto err_clk; 251 + } 252 + 253 + serio->id.type = SERIO_8042; 254 + serio->write = sun4i_ps2_write; 255 + serio->open = sun4i_ps2_open; 256 + serio->close = sun4i_ps2_close; 257 + serio->port_data = drvdata; 258 + serio->dev.parent = dev; 259 + strlcpy(serio->name, dev_name(dev), sizeof(serio->name)); 260 + strlcpy(serio->phys, dev_name(dev), sizeof(serio->phys)); 261 + 262 + /* shutoff interrupt */ 263 + writel(0, drvdata->reg_base + PS2_REG_GCTL); 264 + 265 + /* Get IRQ for the device */ 266 + irq = platform_get_irq(pdev, 0); 267 + if (!irq) { 268 + dev_err(dev, "no IRQ found\n"); 269 + error = -ENXIO; 270 + goto err_disable_clk; 271 + } 272 + 273 + drvdata->irq = irq; 274 + drvdata->serio = serio; 275 + drvdata->dev = dev; 276 + 277 + error = request_irq(drvdata->irq, sun4i_ps2_interrupt, 0, 278 + DRIVER_NAME, drvdata); 279 + if (error) { 280 + dev_err(drvdata->dev, "failed to allocate interrupt %d: %d\n", 281 + drvdata->irq, error); 282 + goto err_disable_clk; 283 + } 284 + 285 + serio_register_port(serio); 286 + platform_set_drvdata(pdev, drvdata); 287 + 288 + return 0; /* success */ 289 + 290 + err_disable_clk: 291 + clk_disable_unprepare(drvdata->clk); 292 + err_clk: 293 + clk_put(drvdata->clk); 294 + err_ioremap: 295 + iounmap(drvdata->reg_base); 296 + err_free_mem: 297 + kfree(serio); 298 + kfree(drvdata); 299 + return error; 300 + } 301 + 302 + static int sun4i_ps2_remove(struct platform_device *pdev) 303 + { 304 + struct sun4i_ps2data *drvdata = platform_get_drvdata(pdev); 305 + 306 + serio_unregister_port(drvdata->serio); 307 + 308 + free_irq(drvdata->irq, drvdata); 309 + 310 + clk_disable_unprepare(drvdata->clk); 311 + clk_put(drvdata->clk); 312 + 313 + iounmap(drvdata->reg_base); 314 + 315 + kfree(drvdata); 316 + 317 + return 0; 318 + } 319 + 320 + static const struct of_device_id sun4i_ps2_match[] = { 321 + { .compatible = "allwinner,sun4i-a10-ps2", }, 322 + { }, 323 + }; 324 + 325 + MODULE_DEVICE_TABLE(of, sun4i_ps2_match); 326 + 327 + static struct platform_driver sun4i_ps2_driver = { 328 + .probe = sun4i_ps2_probe, 329 + .remove = sun4i_ps2_remove, 330 + .driver = { 331 + .name = DRIVER_NAME, 332 + .of_match_table = sun4i_ps2_match, 333 + }, 334 + }; 335 + module_platform_driver(sun4i_ps2_driver); 336 + 337 + MODULE_AUTHOR("Vishnu Patekar <vishnupatekar0510@gmail.com>"); 338 + MODULE_AUTHOR("Aaron.maoye <leafy.myeh@newbietech.com>"); 339 + MODULE_DESCRIPTION("Allwinner A10/Sun4i PS/2 driver"); 340 + MODULE_LICENSE("GPL v2");