162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for Allwinner A10 PS2 host controller 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Vishnu Patekar <vishnupatekar0510@gmail.com> 662306a36Sopenharmony_ci * Aaron.maoye <leafy.myeh@newbietech.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/serio.h> 1162306a36Sopenharmony_ci#include <linux/interrupt.h> 1262306a36Sopenharmony_ci#include <linux/errno.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/io.h> 1562306a36Sopenharmony_ci#include <linux/clk.h> 1662306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define DRIVER_NAME "sun4i-ps2" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* register offset definitions */ 2262306a36Sopenharmony_ci#define PS2_REG_GCTL 0x00 /* PS2 Module Global Control Reg */ 2362306a36Sopenharmony_ci#define PS2_REG_DATA 0x04 /* PS2 Module Data Reg */ 2462306a36Sopenharmony_ci#define PS2_REG_LCTL 0x08 /* PS2 Module Line Control Reg */ 2562306a36Sopenharmony_ci#define PS2_REG_LSTS 0x0C /* PS2 Module Line Status Reg */ 2662306a36Sopenharmony_ci#define PS2_REG_FCTL 0x10 /* PS2 Module FIFO Control Reg */ 2762306a36Sopenharmony_ci#define PS2_REG_FSTS 0x14 /* PS2 Module FIFO Status Reg */ 2862306a36Sopenharmony_ci#define PS2_REG_CLKDR 0x18 /* PS2 Module Clock Divider Reg*/ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* PS2 GLOBAL CONTROL REGISTER PS2_GCTL */ 3162306a36Sopenharmony_ci#define PS2_GCTL_INTFLAG BIT(4) 3262306a36Sopenharmony_ci#define PS2_GCTL_INTEN BIT(3) 3362306a36Sopenharmony_ci#define PS2_GCTL_RESET BIT(2) 3462306a36Sopenharmony_ci#define PS2_GCTL_MASTER BIT(1) 3562306a36Sopenharmony_ci#define PS2_GCTL_BUSEN BIT(0) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/* PS2 LINE CONTROL REGISTER */ 3862306a36Sopenharmony_ci#define PS2_LCTL_NOACK BIT(18) 3962306a36Sopenharmony_ci#define PS2_LCTL_TXDTOEN BIT(8) 4062306a36Sopenharmony_ci#define PS2_LCTL_STOPERREN BIT(3) 4162306a36Sopenharmony_ci#define PS2_LCTL_ACKERREN BIT(2) 4262306a36Sopenharmony_ci#define PS2_LCTL_PARERREN BIT(1) 4362306a36Sopenharmony_ci#define PS2_LCTL_RXDTOEN BIT(0) 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* PS2 LINE STATUS REGISTER */ 4662306a36Sopenharmony_ci#define PS2_LSTS_TXTDO BIT(8) 4762306a36Sopenharmony_ci#define PS2_LSTS_STOPERR BIT(3) 4862306a36Sopenharmony_ci#define PS2_LSTS_ACKERR BIT(2) 4962306a36Sopenharmony_ci#define PS2_LSTS_PARERR BIT(1) 5062306a36Sopenharmony_ci#define PS2_LSTS_RXTDO BIT(0) 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define PS2_LINE_ERROR_BIT \ 5362306a36Sopenharmony_ci (PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | \ 5462306a36Sopenharmony_ci PS2_LSTS_PARERR | PS2_LSTS_RXTDO) 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* PS2 FIFO CONTROL REGISTER */ 5762306a36Sopenharmony_ci#define PS2_FCTL_TXRST BIT(17) 5862306a36Sopenharmony_ci#define PS2_FCTL_RXRST BIT(16) 5962306a36Sopenharmony_ci#define PS2_FCTL_TXUFIEN BIT(10) 6062306a36Sopenharmony_ci#define PS2_FCTL_TXOFIEN BIT(9) 6162306a36Sopenharmony_ci#define PS2_FCTL_TXRDYIEN BIT(8) 6262306a36Sopenharmony_ci#define PS2_FCTL_RXUFIEN BIT(2) 6362306a36Sopenharmony_ci#define PS2_FCTL_RXOFIEN BIT(1) 6462306a36Sopenharmony_ci#define PS2_FCTL_RXRDYIEN BIT(0) 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci/* PS2 FIFO STATUS REGISTER */ 6762306a36Sopenharmony_ci#define PS2_FSTS_TXUF BIT(10) 6862306a36Sopenharmony_ci#define PS2_FSTS_TXOF BIT(9) 6962306a36Sopenharmony_ci#define PS2_FSTS_TXRDY BIT(8) 7062306a36Sopenharmony_ci#define PS2_FSTS_RXUF BIT(2) 7162306a36Sopenharmony_ci#define PS2_FSTS_RXOF BIT(1) 7262306a36Sopenharmony_ci#define PS2_FSTS_RXRDY BIT(0) 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci#define PS2_FIFO_ERROR_BIT \ 7562306a36Sopenharmony_ci (PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_RXUF | PS2_FSTS_RXOF) 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci#define PS2_SAMPLE_CLK 1000000 7862306a36Sopenharmony_ci#define PS2_SCLK 125000 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistruct sun4i_ps2data { 8162306a36Sopenharmony_ci struct serio *serio; 8262306a36Sopenharmony_ci struct device *dev; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci /* IO mapping base */ 8562306a36Sopenharmony_ci void __iomem *reg_base; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* clock management */ 8862306a36Sopenharmony_ci struct clk *clk; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* irq */ 9162306a36Sopenharmony_ci spinlock_t lock; 9262306a36Sopenharmony_ci int irq; 9362306a36Sopenharmony_ci}; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic irqreturn_t sun4i_ps2_interrupt(int irq, void *dev_id) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct sun4i_ps2data *drvdata = dev_id; 9862306a36Sopenharmony_ci u32 intr_status; 9962306a36Sopenharmony_ci u32 fifo_status; 10062306a36Sopenharmony_ci unsigned char byte; 10162306a36Sopenharmony_ci unsigned int rxflags = 0; 10262306a36Sopenharmony_ci u32 rval; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci spin_lock(&drvdata->lock); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* Get the PS/2 interrupts and clear them */ 10762306a36Sopenharmony_ci intr_status = readl(drvdata->reg_base + PS2_REG_LSTS); 10862306a36Sopenharmony_ci fifo_status = readl(drvdata->reg_base + PS2_REG_FSTS); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* Check line status register */ 11162306a36Sopenharmony_ci if (intr_status & PS2_LINE_ERROR_BIT) { 11262306a36Sopenharmony_ci rxflags = (intr_status & PS2_LINE_ERROR_BIT) ? SERIO_FRAME : 0; 11362306a36Sopenharmony_ci rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_PARITY : 0; 11462306a36Sopenharmony_ci rxflags |= (intr_status & PS2_LSTS_PARERR) ? SERIO_TIMEOUT : 0; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci rval = PS2_LSTS_TXTDO | PS2_LSTS_STOPERR | PS2_LSTS_ACKERR | 11762306a36Sopenharmony_ci PS2_LSTS_PARERR | PS2_LSTS_RXTDO; 11862306a36Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_LSTS); 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* Check FIFO status register */ 12262306a36Sopenharmony_ci if (fifo_status & PS2_FIFO_ERROR_BIT) { 12362306a36Sopenharmony_ci rval = PS2_FSTS_TXUF | PS2_FSTS_TXOF | PS2_FSTS_TXRDY | 12462306a36Sopenharmony_ci PS2_FSTS_RXUF | PS2_FSTS_RXOF | PS2_FSTS_RXRDY; 12562306a36Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_FSTS); 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci rval = (fifo_status >> 16) & 0x3; 12962306a36Sopenharmony_ci while (rval--) { 13062306a36Sopenharmony_ci byte = readl(drvdata->reg_base + PS2_REG_DATA) & 0xff; 13162306a36Sopenharmony_ci serio_interrupt(drvdata->serio, byte, rxflags); 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci writel(intr_status, drvdata->reg_base + PS2_REG_LSTS); 13562306a36Sopenharmony_ci writel(fifo_status, drvdata->reg_base + PS2_REG_FSTS); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci spin_unlock(&drvdata->lock); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return IRQ_HANDLED; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic int sun4i_ps2_open(struct serio *serio) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct sun4i_ps2data *drvdata = serio->port_data; 14562306a36Sopenharmony_ci u32 src_clk = 0; 14662306a36Sopenharmony_ci u32 clk_scdf; 14762306a36Sopenharmony_ci u32 clk_pcdf; 14862306a36Sopenharmony_ci u32 rval; 14962306a36Sopenharmony_ci unsigned long flags; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* Set line control and enable interrupt */ 15262306a36Sopenharmony_ci rval = PS2_LCTL_STOPERREN | PS2_LCTL_ACKERREN 15362306a36Sopenharmony_ci | PS2_LCTL_PARERREN | PS2_LCTL_RXDTOEN; 15462306a36Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_LCTL); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* Reset FIFO */ 15762306a36Sopenharmony_ci rval = PS2_FCTL_TXRST | PS2_FCTL_RXRST | PS2_FCTL_TXUFIEN 15862306a36Sopenharmony_ci | PS2_FCTL_TXOFIEN | PS2_FCTL_RXUFIEN 15962306a36Sopenharmony_ci | PS2_FCTL_RXOFIEN | PS2_FCTL_RXRDYIEN; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_FCTL); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci src_clk = clk_get_rate(drvdata->clk); 16462306a36Sopenharmony_ci /* Set clock divider register */ 16562306a36Sopenharmony_ci clk_scdf = src_clk / PS2_SAMPLE_CLK - 1; 16662306a36Sopenharmony_ci clk_pcdf = PS2_SAMPLE_CLK / PS2_SCLK - 1; 16762306a36Sopenharmony_ci rval = (clk_scdf << 8) | clk_pcdf; 16862306a36Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_CLKDR); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* Set global control register */ 17162306a36Sopenharmony_ci rval = PS2_GCTL_RESET | PS2_GCTL_INTEN | PS2_GCTL_MASTER 17262306a36Sopenharmony_ci | PS2_GCTL_BUSEN; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci spin_lock_irqsave(&drvdata->lock, flags); 17562306a36Sopenharmony_ci writel(rval, drvdata->reg_base + PS2_REG_GCTL); 17662306a36Sopenharmony_ci spin_unlock_irqrestore(&drvdata->lock, flags); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic void sun4i_ps2_close(struct serio *serio) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci struct sun4i_ps2data *drvdata = serio->port_data; 18462306a36Sopenharmony_ci u32 rval; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* Shut off the interrupt */ 18762306a36Sopenharmony_ci rval = readl(drvdata->reg_base + PS2_REG_GCTL); 18862306a36Sopenharmony_ci writel(rval & ~(PS2_GCTL_INTEN), drvdata->reg_base + PS2_REG_GCTL); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci synchronize_irq(drvdata->irq); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic int sun4i_ps2_write(struct serio *serio, unsigned char val) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci unsigned long expire = jiffies + msecs_to_jiffies(10000); 19662306a36Sopenharmony_ci struct sun4i_ps2data *drvdata = serio->port_data; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci do { 19962306a36Sopenharmony_ci if (readl(drvdata->reg_base + PS2_REG_FSTS) & PS2_FSTS_TXRDY) { 20062306a36Sopenharmony_ci writel(val, drvdata->reg_base + PS2_REG_DATA); 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci } while (time_before(jiffies, expire)); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci return SERIO_TIMEOUT; 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic int sun4i_ps2_probe(struct platform_device *pdev) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct resource *res; /* IO mem resources */ 21162306a36Sopenharmony_ci struct sun4i_ps2data *drvdata; 21262306a36Sopenharmony_ci struct serio *serio; 21362306a36Sopenharmony_ci struct device *dev = &pdev->dev; 21462306a36Sopenharmony_ci int error; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci drvdata = kzalloc(sizeof(struct sun4i_ps2data), GFP_KERNEL); 21762306a36Sopenharmony_ci serio = kzalloc(sizeof(struct serio), GFP_KERNEL); 21862306a36Sopenharmony_ci if (!drvdata || !serio) { 21962306a36Sopenharmony_ci error = -ENOMEM; 22062306a36Sopenharmony_ci goto err_free_mem; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci spin_lock_init(&drvdata->lock); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* IO */ 22662306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 22762306a36Sopenharmony_ci if (!res) { 22862306a36Sopenharmony_ci dev_err(dev, "failed to locate registers\n"); 22962306a36Sopenharmony_ci error = -ENXIO; 23062306a36Sopenharmony_ci goto err_free_mem; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci drvdata->reg_base = ioremap(res->start, resource_size(res)); 23462306a36Sopenharmony_ci if (!drvdata->reg_base) { 23562306a36Sopenharmony_ci dev_err(dev, "failed to map registers\n"); 23662306a36Sopenharmony_ci error = -ENOMEM; 23762306a36Sopenharmony_ci goto err_free_mem; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci drvdata->clk = clk_get(dev, NULL); 24162306a36Sopenharmony_ci if (IS_ERR(drvdata->clk)) { 24262306a36Sopenharmony_ci error = PTR_ERR(drvdata->clk); 24362306a36Sopenharmony_ci dev_err(dev, "couldn't get clock %d\n", error); 24462306a36Sopenharmony_ci goto err_ioremap; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci error = clk_prepare_enable(drvdata->clk); 24862306a36Sopenharmony_ci if (error) { 24962306a36Sopenharmony_ci dev_err(dev, "failed to enable clock %d\n", error); 25062306a36Sopenharmony_ci goto err_clk; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci serio->id.type = SERIO_8042; 25462306a36Sopenharmony_ci serio->write = sun4i_ps2_write; 25562306a36Sopenharmony_ci serio->open = sun4i_ps2_open; 25662306a36Sopenharmony_ci serio->close = sun4i_ps2_close; 25762306a36Sopenharmony_ci serio->port_data = drvdata; 25862306a36Sopenharmony_ci serio->dev.parent = dev; 25962306a36Sopenharmony_ci strscpy(serio->name, dev_name(dev), sizeof(serio->name)); 26062306a36Sopenharmony_ci strscpy(serio->phys, dev_name(dev), sizeof(serio->phys)); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci /* shutoff interrupt */ 26362306a36Sopenharmony_ci writel(0, drvdata->reg_base + PS2_REG_GCTL); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* Get IRQ for the device */ 26662306a36Sopenharmony_ci drvdata->irq = platform_get_irq(pdev, 0); 26762306a36Sopenharmony_ci if (drvdata->irq < 0) { 26862306a36Sopenharmony_ci error = drvdata->irq; 26962306a36Sopenharmony_ci goto err_disable_clk; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci drvdata->serio = serio; 27362306a36Sopenharmony_ci drvdata->dev = dev; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci error = request_irq(drvdata->irq, sun4i_ps2_interrupt, 0, 27662306a36Sopenharmony_ci DRIVER_NAME, drvdata); 27762306a36Sopenharmony_ci if (error) { 27862306a36Sopenharmony_ci dev_err(drvdata->dev, "failed to allocate interrupt %d: %d\n", 27962306a36Sopenharmony_ci drvdata->irq, error); 28062306a36Sopenharmony_ci goto err_disable_clk; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci serio_register_port(serio); 28462306a36Sopenharmony_ci platform_set_drvdata(pdev, drvdata); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci return 0; /* success */ 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cierr_disable_clk: 28962306a36Sopenharmony_ci clk_disable_unprepare(drvdata->clk); 29062306a36Sopenharmony_cierr_clk: 29162306a36Sopenharmony_ci clk_put(drvdata->clk); 29262306a36Sopenharmony_cierr_ioremap: 29362306a36Sopenharmony_ci iounmap(drvdata->reg_base); 29462306a36Sopenharmony_cierr_free_mem: 29562306a36Sopenharmony_ci kfree(serio); 29662306a36Sopenharmony_ci kfree(drvdata); 29762306a36Sopenharmony_ci return error; 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic int sun4i_ps2_remove(struct platform_device *pdev) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci struct sun4i_ps2data *drvdata = platform_get_drvdata(pdev); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci serio_unregister_port(drvdata->serio); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci free_irq(drvdata->irq, drvdata); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci clk_disable_unprepare(drvdata->clk); 30962306a36Sopenharmony_ci clk_put(drvdata->clk); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci iounmap(drvdata->reg_base); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci kfree(drvdata); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci return 0; 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic const struct of_device_id sun4i_ps2_match[] = { 31962306a36Sopenharmony_ci { .compatible = "allwinner,sun4i-a10-ps2", }, 32062306a36Sopenharmony_ci { }, 32162306a36Sopenharmony_ci}; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sun4i_ps2_match); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_cistatic struct platform_driver sun4i_ps2_driver = { 32662306a36Sopenharmony_ci .probe = sun4i_ps2_probe, 32762306a36Sopenharmony_ci .remove = sun4i_ps2_remove, 32862306a36Sopenharmony_ci .driver = { 32962306a36Sopenharmony_ci .name = DRIVER_NAME, 33062306a36Sopenharmony_ci .of_match_table = sun4i_ps2_match, 33162306a36Sopenharmony_ci }, 33262306a36Sopenharmony_ci}; 33362306a36Sopenharmony_cimodule_platform_driver(sun4i_ps2_driver); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ciMODULE_AUTHOR("Vishnu Patekar <vishnupatekar0510@gmail.com>"); 33662306a36Sopenharmony_ciMODULE_AUTHOR("Aaron.maoye <leafy.myeh@newbietech.com>"); 33762306a36Sopenharmony_ciMODULE_DESCRIPTION("Allwinner A10/Sun4i PS/2 driver"); 33862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 339