18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for Allwinner A10 PS2 host controller 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Author: Vishnu Patekar <vishnupatekar0510@gmail.com> 68c2ecf20Sopenharmony_ci * Aaron.maoye <leafy.myeh@newbietech.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/serio.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/errno.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/io.h> 158c2ecf20Sopenharmony_ci#include <linux/clk.h> 168c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define DRIVER_NAME "sun4i-ps2" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* register offset definitions */ 228c2ecf20Sopenharmony_ci#define PS2_REG_GCTL 0x00 /* PS2 Module Global Control Reg */ 238c2ecf20Sopenharmony_ci#define PS2_REG_DATA 0x04 /* PS2 Module Data Reg */ 248c2ecf20Sopenharmony_ci#define PS2_REG_LCTL 0x08 /* PS2 Module Line Control Reg */ 258c2ecf20Sopenharmony_ci#define PS2_REG_LSTS 0x0C /* PS2 Module Line Status Reg */ 268c2ecf20Sopenharmony_ci#define PS2_REG_FCTL 0x10 /* PS2 Module FIFO Control Reg */ 278c2ecf20Sopenharmony_ci#define PS2_REG_FSTS 0x14 /* PS2 Module FIFO Status Reg */ 288c2ecf20Sopenharmony_ci#define PS2_REG_CLKDR 0x18 /* PS2 Module Clock Divider Reg*/ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* PS2 GLOBAL CONTROL REGISTER PS2_GCTL */ 318c2ecf20Sopenharmony_ci#define PS2_GCTL_INTFLAG BIT(4) 328c2ecf20Sopenharmony_ci#define PS2_GCTL_INTEN BIT(3) 338c2ecf20Sopenharmony_ci#define PS2_GCTL_RESET BIT(2) 348c2ecf20Sopenharmony_ci#define PS2_GCTL_MASTER BIT(1) 358c2ecf20Sopenharmony_ci#define PS2_GCTL_BUSEN BIT(0) 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* PS2 LINE CONTROL REGISTER */ 388c2ecf20Sopenharmony_ci#define PS2_LCTL_NOACK BIT(18) 398c2ecf20Sopenharmony_ci#define PS2_LCTL_TXDTOEN BIT(8) 408c2ecf20Sopenharmony_ci#define PS2_LCTL_STOPERREN BIT(3) 418c2ecf20Sopenharmony_ci#define PS2_LCTL_ACKERREN BIT(2) 428c2ecf20Sopenharmony_ci#define PS2_LCTL_PARERREN BIT(1) 438c2ecf20Sopenharmony_ci#define PS2_LCTL_RXDTOEN BIT(0) 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* PS2 LINE STATUS REGISTER */ 468c2ecf20Sopenharmony_ci#define PS2_LSTS_TXTDO BIT(8) 478c2ecf20Sopenharmony_ci#define PS2_LSTS_STOPERR BIT(3) 488c2ecf20Sopenharmony_ci#define PS2_LSTS_ACKERR BIT(2) 498c2ecf20Sopenharmony_ci#define PS2_LSTS_PARERR BIT(1) 508c2ecf20Sopenharmony_ci#define PS2_LSTS_RXTDO BIT(0) 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#define PS2_LINE_ERROR_BIT \ 538c2ecf20Sopenharmony_ci (PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | \ 548c2ecf20Sopenharmony_ci PS2_LSTS_PARERR | PS2_LSTS_RXTDO) 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci/* PS2 FIFO CONTROL REGISTER */ 578c2ecf20Sopenharmony_ci#define PS2_FCTL_TXRST BIT(17) 588c2ecf20Sopenharmony_ci#define PS2_FCTL_RXRST BIT(16) 598c2ecf20Sopenharmony_ci#define PS2_FCTL_TXUFIEN BIT(10) 608c2ecf20Sopenharmony_ci#define PS2_FCTL_TXOFIEN BIT(9) 618c2ecf20Sopenharmony_ci#define PS2_FCTL_TXRDYIEN BIT(8) 628c2ecf20Sopenharmony_ci#define PS2_FCTL_RXUFIEN BIT(2) 638c2ecf20Sopenharmony_ci#define PS2_FCTL_RXOFIEN BIT(1) 648c2ecf20Sopenharmony_ci#define PS2_FCTL_RXRDYIEN BIT(0) 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci/* PS2 FIFO STATUS REGISTER */ 678c2ecf20Sopenharmony_ci#define PS2_FSTS_TXUF BIT(10) 688c2ecf20Sopenharmony_ci#define PS2_FSTS_TXOF BIT(9) 698c2ecf20Sopenharmony_ci#define PS2_FSTS_TXRDY BIT(8) 708c2ecf20Sopenharmony_ci#define PS2_FSTS_RXUF BIT(2) 718c2ecf20Sopenharmony_ci#define PS2_FSTS_RXOF BIT(1) 728c2ecf20Sopenharmony_ci#define PS2_FSTS_RXRDY BIT(0) 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci#define PS2_FIFO_ERROR_BIT \ 758c2ecf20Sopenharmony_ci (PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_RXUF | PS2_FSTS_RXOF) 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci#define PS2_SAMPLE_CLK 1000000 788c2ecf20Sopenharmony_ci#define PS2_SCLK 125000 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistruct sun4i_ps2data { 818c2ecf20Sopenharmony_ci struct serio *serio; 828c2ecf20Sopenharmony_ci struct device *dev; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci /* IO mapping base */ 858c2ecf20Sopenharmony_ci void __iomem *reg_base; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* clock management */ 888c2ecf20Sopenharmony_ci struct clk *clk; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci /* irq */ 918c2ecf20Sopenharmony_ci spinlock_t lock; 928c2ecf20Sopenharmony_ci int irq; 938c2ecf20Sopenharmony_ci}; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic irqreturn_t sun4i_ps2_interrupt(int irq, void *dev_id) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci struct sun4i_ps2data *drvdata = dev_id; 988c2ecf20Sopenharmony_ci u32 intr_status; 998c2ecf20Sopenharmony_ci u32 fifo_status; 1008c2ecf20Sopenharmony_ci unsigned char byte; 1018c2ecf20Sopenharmony_ci unsigned int rxflags = 0; 1028c2ecf20Sopenharmony_ci u32 rval; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci spin_lock(&drvdata->lock); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* Get the PS/2 interrupts and clear them */ 1078c2ecf20Sopenharmony_ci intr_status = readl(drvdata->reg_base + PS2_REG_LSTS); 1088c2ecf20Sopenharmony_ci fifo_status = readl(drvdata->reg_base + PS2_REG_FSTS); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* Check line status register */ 1118c2ecf20Sopenharmony_ci if (intr_status & PS2_LINE_ERROR_BIT) { 1128c2ecf20Sopenharmony_ci rxflags = (intr_status & PS2_LINE_ERROR_BIT) ? SERIO_FRAME : 0; 1138c2ecf20Sopenharmony_ci rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_PARITY : 0; 1148c2ecf20Sopenharmony_ci rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_TIMEOUT : 0; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci rval = PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | 1178c2ecf20Sopenharmony_ci PS2_LSTS_PARERR | PS2_LSTS_RXTDO; 1188c2ecf20Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_LSTS); 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci /* Check FIFO status register */ 1228c2ecf20Sopenharmony_ci if (fifo_status & PS2_FIFO_ERROR_BIT) { 1238c2ecf20Sopenharmony_ci rval = PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_TXRDY | 1248c2ecf20Sopenharmony_ci PS2_FSTS_RXUF | PS2_FSTS_RXOF | PS2_FSTS_RXRDY; 1258c2ecf20Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_FSTS); 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci rval = (fifo_status >> 16) & 0x3; 1298c2ecf20Sopenharmony_ci while (rval--) { 1308c2ecf20Sopenharmony_ci byte = readl(drvdata->reg_base + PS2_REG_DATA) & 0xff; 1318c2ecf20Sopenharmony_ci serio_interrupt(drvdata->serio, byte, rxflags); 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci writel(intr_status, drvdata->reg_base + PS2_REG_LSTS); 1358c2ecf20Sopenharmony_ci writel(fifo_status, drvdata->reg_base + PS2_REG_FSTS); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci spin_unlock(&drvdata->lock); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic int sun4i_ps2_open(struct serio *serio) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci struct sun4i_ps2data *drvdata = serio->port_data; 1458c2ecf20Sopenharmony_ci u32 src_clk = 0; 1468c2ecf20Sopenharmony_ci u32 clk_scdf; 1478c2ecf20Sopenharmony_ci u32 clk_pcdf; 1488c2ecf20Sopenharmony_ci u32 rval; 1498c2ecf20Sopenharmony_ci unsigned long flags; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* Set line control and enable interrupt */ 1528c2ecf20Sopenharmony_ci rval = PS2_LCTL_STOPERREN | PS2_LCTL_ACKERREN 1538c2ecf20Sopenharmony_ci | PS2_LCTL_PARERREN | PS2_LCTL_RXDTOEN; 1548c2ecf20Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_LCTL); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* Reset FIFO */ 1578c2ecf20Sopenharmony_ci rval = PS2_FCTL_TXRST | PS2_FCTL_RXRST | PS2_FCTL_TXUFIEN 1588c2ecf20Sopenharmony_ci | PS2_FCTL_TXOFIEN | PS2_FCTL_RXUFIEN 1598c2ecf20Sopenharmony_ci | PS2_FCTL_RXOFIEN | PS2_FCTL_RXRDYIEN; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_FCTL); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci src_clk = clk_get_rate(drvdata->clk); 1648c2ecf20Sopenharmony_ci /* Set clock divider register */ 1658c2ecf20Sopenharmony_ci clk_scdf = src_clk / PS2_SAMPLE_CLK - 1; 1668c2ecf20Sopenharmony_ci clk_pcdf = PS2_SAMPLE_CLK / PS2_SCLK - 1; 1678c2ecf20Sopenharmony_ci rval = (clk_scdf << 8) | clk_pcdf; 1688c2ecf20Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_CLKDR); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* Set global control register */ 1718c2ecf20Sopenharmony_ci rval = PS2_GCTL_RESET | PS2_GCTL_INTEN | PS2_GCTL_MASTER 1728c2ecf20Sopenharmony_ci | PS2_GCTL_BUSEN; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci spin_lock_irqsave(&drvdata->lock, flags); 1758c2ecf20Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_GCTL); 1768c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&drvdata->lock, flags); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci return 0; 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic void sun4i_ps2_close(struct serio *serio) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci struct sun4i_ps2data *drvdata = serio->port_data; 1848c2ecf20Sopenharmony_ci u32 rval; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* Shut off the interrupt */ 1878c2ecf20Sopenharmony_ci rval = readl(drvdata->reg_base + PS2_REG_GCTL); 1888c2ecf20Sopenharmony_ci writel(rval & ~(PS2_GCTL_INTEN), drvdata->reg_base + PS2_REG_GCTL); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci synchronize_irq(drvdata->irq); 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic int sun4i_ps2_write(struct serio *serio, unsigned char val) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci unsigned long expire = jiffies + msecs_to_jiffies(10000); 1968c2ecf20Sopenharmony_ci struct sun4i_ps2data *drvdata = serio->port_data; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci do { 1998c2ecf20Sopenharmony_ci if (readl(drvdata->reg_base + PS2_REG_FSTS) & PS2_FSTS_TXRDY) { 2008c2ecf20Sopenharmony_ci writel(val, drvdata->reg_base + PS2_REG_DATA); 2018c2ecf20Sopenharmony_ci return 0; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci } while (time_before(jiffies, expire)); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci return SERIO_TIMEOUT; 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic int sun4i_ps2_probe(struct platform_device *pdev) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci struct resource *res; /* IO mem resources */ 2118c2ecf20Sopenharmony_ci struct sun4i_ps2data *drvdata; 2128c2ecf20Sopenharmony_ci struct serio *serio; 2138c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2148c2ecf20Sopenharmony_ci int error; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci drvdata = kzalloc(sizeof(struct sun4i_ps2data), GFP_KERNEL); 2178c2ecf20Sopenharmony_ci serio = kzalloc(sizeof(struct serio), GFP_KERNEL); 2188c2ecf20Sopenharmony_ci if (!drvdata || !serio) { 2198c2ecf20Sopenharmony_ci error = -ENOMEM; 2208c2ecf20Sopenharmony_ci goto err_free_mem; 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci spin_lock_init(&drvdata->lock); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* IO */ 2268c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2278c2ecf20Sopenharmony_ci if (!res) { 2288c2ecf20Sopenharmony_ci dev_err(dev, "failed to locate registers\n"); 2298c2ecf20Sopenharmony_ci error = -ENXIO; 2308c2ecf20Sopenharmony_ci goto err_free_mem; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci drvdata->reg_base = ioremap(res->start, resource_size(res)); 2348c2ecf20Sopenharmony_ci if (!drvdata->reg_base) { 2358c2ecf20Sopenharmony_ci dev_err(dev, "failed to map registers\n"); 2368c2ecf20Sopenharmony_ci error = -ENOMEM; 2378c2ecf20Sopenharmony_ci goto err_free_mem; 2388c2ecf20Sopenharmony_ci } 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci drvdata->clk = clk_get(dev, NULL); 2418c2ecf20Sopenharmony_ci if (IS_ERR(drvdata->clk)) { 2428c2ecf20Sopenharmony_ci error = PTR_ERR(drvdata->clk); 2438c2ecf20Sopenharmony_ci dev_err(dev, "couldn't get clock %d\n", error); 2448c2ecf20Sopenharmony_ci goto err_ioremap; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci error = clk_prepare_enable(drvdata->clk); 2488c2ecf20Sopenharmony_ci if (error) { 2498c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable clock %d\n", error); 2508c2ecf20Sopenharmony_ci goto err_clk; 2518c2ecf20Sopenharmony_ci } 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci serio->id.type = SERIO_8042; 2548c2ecf20Sopenharmony_ci serio->write = sun4i_ps2_write; 2558c2ecf20Sopenharmony_ci serio->open = sun4i_ps2_open; 2568c2ecf20Sopenharmony_ci serio->close = sun4i_ps2_close; 2578c2ecf20Sopenharmony_ci serio->port_data = drvdata; 2588c2ecf20Sopenharmony_ci serio->dev.parent = dev; 2598c2ecf20Sopenharmony_ci strlcpy(serio->name, dev_name(dev), sizeof(serio->name)); 2608c2ecf20Sopenharmony_ci strlcpy(serio->phys, dev_name(dev), sizeof(serio->phys)); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci /* shutoff interrupt */ 2638c2ecf20Sopenharmony_ci writel(0, drvdata->reg_base + PS2_REG_GCTL); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci /* Get IRQ for the device */ 2668c2ecf20Sopenharmony_ci drvdata->irq = platform_get_irq(pdev, 0); 2678c2ecf20Sopenharmony_ci if (drvdata->irq < 0) { 2688c2ecf20Sopenharmony_ci error = drvdata->irq; 2698c2ecf20Sopenharmony_ci goto err_disable_clk; 2708c2ecf20Sopenharmony_ci } 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci drvdata->serio = serio; 2738c2ecf20Sopenharmony_ci drvdata->dev = dev; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci error = request_irq(drvdata->irq, sun4i_ps2_interrupt, 0, 2768c2ecf20Sopenharmony_ci DRIVER_NAME, drvdata); 2778c2ecf20Sopenharmony_ci if (error) { 2788c2ecf20Sopenharmony_ci dev_err(drvdata->dev, "failed to allocate interrupt %d: %d\n", 2798c2ecf20Sopenharmony_ci drvdata->irq, error); 2808c2ecf20Sopenharmony_ci goto err_disable_clk; 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci serio_register_port(serio); 2848c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, drvdata); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci return 0; /* success */ 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cierr_disable_clk: 2898c2ecf20Sopenharmony_ci clk_disable_unprepare(drvdata->clk); 2908c2ecf20Sopenharmony_cierr_clk: 2918c2ecf20Sopenharmony_ci clk_put(drvdata->clk); 2928c2ecf20Sopenharmony_cierr_ioremap: 2938c2ecf20Sopenharmony_ci iounmap(drvdata->reg_base); 2948c2ecf20Sopenharmony_cierr_free_mem: 2958c2ecf20Sopenharmony_ci kfree(serio); 2968c2ecf20Sopenharmony_ci kfree(drvdata); 2978c2ecf20Sopenharmony_ci return error; 2988c2ecf20Sopenharmony_ci} 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cistatic int sun4i_ps2_remove(struct platform_device *pdev) 3018c2ecf20Sopenharmony_ci{ 3028c2ecf20Sopenharmony_ci struct sun4i_ps2data *drvdata = platform_get_drvdata(pdev); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci serio_unregister_port(drvdata->serio); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci free_irq(drvdata->irq, drvdata); 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci clk_disable_unprepare(drvdata->clk); 3098c2ecf20Sopenharmony_ci clk_put(drvdata->clk); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci iounmap(drvdata->reg_base); 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci kfree(drvdata); 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci return 0; 3168c2ecf20Sopenharmony_ci} 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_cistatic const struct of_device_id sun4i_ps2_match[] = { 3198c2ecf20Sopenharmony_ci { .compatible = "allwinner,sun4i-a10-ps2", }, 3208c2ecf20Sopenharmony_ci { }, 3218c2ecf20Sopenharmony_ci}; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, sun4i_ps2_match); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_cistatic struct platform_driver sun4i_ps2_driver = { 3268c2ecf20Sopenharmony_ci .probe = sun4i_ps2_probe, 3278c2ecf20Sopenharmony_ci .remove = sun4i_ps2_remove, 3288c2ecf20Sopenharmony_ci .driver = { 3298c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 3308c2ecf20Sopenharmony_ci .of_match_table = sun4i_ps2_match, 3318c2ecf20Sopenharmony_ci }, 3328c2ecf20Sopenharmony_ci}; 3338c2ecf20Sopenharmony_cimodule_platform_driver(sun4i_ps2_driver); 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vishnu Patekar <vishnupatekar0510@gmail.com>"); 3368c2ecf20Sopenharmony_ciMODULE_AUTHOR("Aaron.maoye <leafy.myeh@newbietech.com>"); 3378c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Allwinner A10/Sun4i PS/2 driver"); 3388c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 339