/* * drivers/input/sensor/sunxi_tpadc.c * * Copyright (C) 2017 Allwinner. * fuzhaoke * * SUNXI TPADC Controller Driver * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunxi_tpadc.h" static void sunxi_tpadc_clk_division(void __iomem *reg_base, unsigned char divider) { u32 reg; reg = readl(reg_base + TP_CTRL0_REG); reg |= (divider & 0x3) << ADC_CLK_DIVIDER; writel(reg, reg_base + TP_CTRL0_REG); } static void sunxi_tpadc_fs_set(void __iomem *reg_base, unsigned char divider) { u32 reg; reg = readl(reg_base + TP_CTRL0_REG); reg |= (divider & 0xf) << FS_DIVIDER; writel(reg, reg_base + TP_CTRL0_REG); } static void sunxi_tpadc_ta_set(void __iomem *reg_base, int val) { int reg; reg = readl(reg_base + TP_CTRL0_REG); reg |= val & 0xfff; writel(reg, reg_base + TP_CTRL0_REG); } static void sunxi_tpadc_clk_select(void __iomem *reg_base, bool on) { u32 reg; reg = readl(reg_base + TP_CTRL0_REG); if (on) reg &= ~TP_CLOCK; else reg |= TP_CLOCK; writel(reg, reg_base + TP_CTRL0_REG); } static void sunxi_tpadc_select_adc(void __iomem *reg_base) { int reg; reg = readl(reg_base + TP_CTRL1_REG); reg |= ADC_ENABLE; writel(reg, reg_base + TP_CTRL1_REG); } static void sunxi_tpadc_rtp_enable(void __iomem *reg_base) { int reg; reg = readl(reg_base + TP_CTRL1_REG); reg |= RTP_MODE_ENABLE; writel(reg, reg_base + TP_CTRL1_REG); } static void sunxi_tpadc_filter_enable(void __iomem *reg_base) { int reg; reg = readl(reg_base + TP_CTRL3_REG); reg |= RTP_FILTER_ENABLE; writel(reg, reg_base + TP_CTRL3_REG); } static void sunxi_tpadc_filter_type(void __iomem *reg_base) { int reg; reg = readl(reg_base + TP_CTRL3_REG); reg |= RTP_FILTER_TYPE; writel(reg, reg_base + TP_CTRL3_REG); } static void sunxi_tpadc_trig_level(void __iomem *reg_base, int val) { int reg; reg = readl(reg_base + TP_INT_FIFOC_REG); reg |= (val & 0xf)<<8; writel(reg, reg_base + TP_INT_FIFOC_REG); } static void sunxi_tpadc_downup_irq(void __iomem *reg_base) { int reg; reg = readl(reg_base + TP_INT_FIFOC_REG); reg |= 0x03; writel(reg, reg_base + TP_INT_FIFOC_REG); } static u32 sunxi_tpadc_ch_select(void __iomem *reg_base, enum tp_channel_id id) { u32 reg_val; reg_val = readl(reg_base + TP_CTRL1_REG); switch (id) { case TP_CH_0: reg_val |= TP_CH0_SELECT; break; case TP_CH_1: reg_val |= TP_CH1_SELECT; break; case TP_CH_2: reg_val |= TP_CH2_SELECT; break; case TP_CH_3: reg_val |= TP_CH3_SELECT; break; default: pr_err("%s, invalid channel id!", __func__); return -EINVAL; } writel(reg_val, reg_base + TP_CTRL1_REG); return 0; } static int sunxi_tpadc_ch_deselect(void __iomem *reg_base, enum tp_channel_id id) { u32 reg_val; reg_val = readl(reg_base + TP_CTRL1_REG); switch (id) { case TP_CH_0: reg_val &= ~TP_CH0_SELECT; break; case TP_CH_1: reg_val &= ~TP_CH1_SELECT; break; case TP_CH_2: reg_val &= ~TP_CH2_SELECT; break; case TP_CH_3: reg_val &= ~TP_CH3_SELECT; break; default: pr_err("%s, invalid channel id!", __func__); return -EINVAL; } writel(reg_val, reg_base + TP_CTRL1_REG); return 0; } static void sunxi_tpadc_enable_irq(void __iomem *reg_base) { u32 reg_val; reg_val = readl(reg_base + TP_INT_FIFOC_REG); reg_val |= TP_DATA_IRQ_EN; writel(reg_val, reg_base + TP_INT_FIFOC_REG); } static void sunxi_tpadc_disable_irq(void __iomem *reg_base) { u32 reg_val; reg_val = readl(reg_base + TP_INT_FIFOC_REG); reg_val &= ~TP_DATA_IRQ_EN; writel(reg_val, reg_base + TP_INT_FIFOC_REG); } static u32 sunxi_tpadc_irq_status(void __iomem *reg_base) { return readl(reg_base + TP_INT_FIFOS_REG); } static void sunxi_tpadc_clear_pending(void __iomem *reg_base, int reg_val) { writel(reg_val, reg_base + TP_INT_FIFOS_REG); } static u32 sunxi_tpadc_data_read(void __iomem *reg_base) { return readl(reg_base + TP_DATA_REG); } static u32 sunxi_tpadc_clk_config(void __iomem *reg_base, u32 clk_in, u32 sclk_freq) { u32 freq, divider = sclk_freq / clk_in; u32 val; if (divider > 3 && divider < 6) divider = 3; if (divider > 6) divider = 6; switch (divider) { case 1: val = 0x03; break; case 2: val = 0x00; break; case 3: val = 0x01; break; case 6: val = 0x02; break; default: break; } sunxi_tpadc_clk_division(reg_base, val); freq = sclk_freq / divider; return freq; } static u32 sunxi_tpadc_fs_config(void __iomem *reg_base, u32 clk_in, u32 fs_div) { u32 sample_freq; sunxi_tpadc_fs_set(reg_base, fs_div); sample_freq = clk_in / (1 << (20 - fs_div)); return sample_freq; } static int sunxi_tpadc_hw_init(struct sunxi_tpadc *sunxi_tpadc) { u32 clk_in; u32 sample_freq; sunxi_tpadc_clk_select(sunxi_tpadc->reg_base, HOSC); clk_in = sunxi_tpadc_clk_config(sunxi_tpadc->reg_base, CLK_IN, 24000000); sunxi_tpadc_fs_config(sunxi_tpadc->reg_base, clk_in, 0x06); sunxi_tpadc_ta_set(sunxi_tpadc->reg_base, 0xff); sunxi_tpadc_select_adc(sunxi_tpadc->reg_base); sunxi_tpadc_ch_select(sunxi_tpadc->reg_base, 0); sunxi_tpadc_ch_select(sunxi_tpadc->reg_base, 2); sunxi_tpadc_enable_irq(sunxi_tpadc->reg_base); sunxi_tpadc_filter_enable(sunxi_tpadc->reg_base); sunxi_tpadc_filter_type(sunxi_tpadc->reg_base); sunxi_tpadc_trig_level(sunxi_tpadc->reg_base, 0x03); sunxi_tpadc_downup_irq(sunxi_tpadc->reg_base); sunxi_tpadc_rtp_enable(sunxi_tpadc->reg_base); return 0; } static irqreturn_t sunxi_tpadc_interrupt(int irqno, void *dev_id) { struct sunxi_tpadc *sunxi_tpadc = (struct sunxi_tpadc *)dev_id; u32 reg_val; u32 data; reg_val = sunxi_tpadc_irq_status(sunxi_tpadc->reg_base); sunxi_tpadc_clear_pending(sunxi_tpadc->reg_base, reg_val); if (reg_val & SUNXI_FIFO_DATA) { data = sunxi_tpadc_data_read(sunxi_tpadc->reg_base); input_event(sunxi_tpadc->input_channel0, EV_MSC, MSC_SCAN, data); input_sync(sunxi_tpadc->input_channel0); data = sunxi_tpadc_data_read(sunxi_tpadc->reg_base); input_event(sunxi_tpadc->input_channel2, EV_MSC, MSC_SCAN, data); input_sync(sunxi_tpadc->input_channel2); } return IRQ_HANDLED; } static int sunxi_input_register_channel0(struct sunxi_tpadc *sunxi_tpadc) { static struct input_dev *input_dev; int ret; input_dev = input_allocate_device(); if (!input_dev) { pr_err("input_dev: not enough memory for input device\n"); return -ENOMEM; } input_dev->name = "sunxitpadc_channel0"; input_dev->phys = "sunxitpadc/input0"; input_dev->id.bustype = BUS_HOST; input_dev->id.vendor = 0x0001; input_dev->id.product = 0x0001; input_dev->id.version = 0x0100; input_dev->evbit[0] = BIT_MASK(EV_MSC); set_bit(EV_MSC, input_dev->evbit); set_bit(MSC_SCAN, input_dev->mscbit); sunxi_tpadc->input_channel0 = input_dev; ret = input_register_device(sunxi_tpadc->input_channel0); if (ret) { pr_err("input register fail\n"); goto fail; } return 0; fail: input_unregister_device(sunxi_tpadc->input_channel0); input_free_device(sunxi_tpadc->input_channel0); return ret; } static int sunxi_input_register_channel2(struct sunxi_tpadc *sunxi_tpadc) { static struct input_dev *input_dev; int ret; input_dev = input_allocate_device(); if (!input_dev) { pr_err("input_dev: not enough memory for input device\n"); return -ENOMEM; } input_dev->name = "sunxitpadc_channel2"; input_dev->phys = "sunxitpadc/input0"; input_dev->id.bustype = BUS_HOST; input_dev->id.vendor = 0x0001; input_dev->id.product = 0x0001; input_dev->id.version = 0x0100; input_dev->evbit[0] = BIT_MASK(EV_MSC); set_bit(EV_MSC, input_dev->evbit); set_bit(MSC_SCAN, input_dev->mscbit); sunxi_tpadc->input_channel2 = input_dev; ret = input_register_device(sunxi_tpadc->input_channel2); if (ret) { pr_err("input register fail\n"); goto fail; } return 0; fail: input_unregister_device(sunxi_tpadc->input_channel2); input_free_device(sunxi_tpadc->input_channel2); return ret; } int sunxi_tpadc_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct sunxi_tpadc *sunxi_tpadc; int ret = 0; if (!of_device_is_available(np)) { pr_err("%s: sunxi tpadc is disable\n", __func__); return -EPERM; } sunxi_tpadc = kzalloc(sizeof(struct sunxi_tpadc), GFP_KERNEL); if (IS_ERR_OR_NULL(sunxi_tpadc)) { pr_err("not enough memory for sunxi_tpadc\n"); return -ENOMEM; } sunxi_tpadc->reg_base = of_iomap(np, 0); if (NULL == sunxi_tpadc->reg_base) { pr_err("sunxi_tpadc iomap fail\n"); ret = -EBUSY; goto fail1; } sunxi_tpadc->irq_num = irq_of_parse_and_map(np, 0); if (0 == sunxi_tpadc->irq_num) { pr_err("sunxi_tpadc fail to map irq\n"); ret = -EBUSY; goto fail2; } sunxi_tpadc_hw_init(sunxi_tpadc); sunxi_input_register_channel0(sunxi_tpadc); sunxi_input_register_channel2(sunxi_tpadc); if (request_irq(sunxi_tpadc->irq_num, sunxi_tpadc_interrupt, IRQF_TRIGGER_NONE, "sunxi-tpadc", sunxi_tpadc)) { pr_err("sunxi_tpadc request irq failure\n"); return -1; } platform_set_drvdata(pdev, sunxi_tpadc); return 0; fail2: iounmap(sunxi_tpadc->reg_base); fail1: kfree(sunxi_tpadc); return ret; } int sunxi_tpadc_remove(struct platform_device *pdev) { struct sunxi_tpadc *sunxi_tpadc = platform_get_drvdata(pdev); sunxi_tpadc_disable_irq(sunxi_tpadc->reg_base); sunxi_tpadc_ch_deselect(sunxi_tpadc->reg_base, 0); sunxi_tpadc_ch_deselect(sunxi_tpadc->reg_base, 2); free_irq(sunxi_tpadc->irq_num, sunxi_tpadc); iounmap(sunxi_tpadc->reg_base); input_unregister_device(sunxi_tpadc->input_channel0); input_free_device(sunxi_tpadc->input_channel0); input_unregister_device(sunxi_tpadc->input_channel2); input_free_device(sunxi_tpadc->input_channel2); kfree(sunxi_tpadc); return 0; } #ifdef CONFIG_PM static int sunxi_tpadc_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct sunxi_tpadc *sunxi_tpadc = platform_get_drvdata(pdev); sunxi_tpadc_disable_irq(sunxi_tpadc->reg_base); sunxi_tpadc_ch_deselect(sunxi_tpadc->reg_base, 0); sunxi_tpadc_ch_deselect(sunxi_tpadc->reg_base, 2); disable_irq_nosync(sunxi_tpadc->irq_num); return 0; } static int sunxi_tpadc_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct sunxi_tpadc *sunxi_tpadc = platform_get_drvdata(pdev); sunxi_tpadc_enable_irq(sunxi_tpadc->reg_base); sunxi_tpadc_ch_select(sunxi_tpadc->reg_base, 0); sunxi_tpadc_ch_select(sunxi_tpadc->reg_base, 2); enable_irq(sunxi_tpadc->irq_num); return 0; } static const struct dev_pm_ops sunxi_tpadc_dev_pm_ops = { .suspend = sunxi_tpadc_suspend, .resume = sunxi_tpadc_resume, }; #define SUNXI_TPADC_DEV_PM_OPS (&sunxi_tpadc_dev_pm_ops) #else #define SUNXI_TPADC_DEV_PM_OPS NULL #endif static struct of_device_id sunxi_tpadc_of_match[] = { { .compatible = "allwinner,sunxi-tpadc"}, {}, }; MODULE_DEVICE_TABLE(of, sunxi_tpadc_of_match); static struct platform_driver sunxi_tpadc_driver = { .probe = sunxi_tpadc_probe, .remove = sunxi_tpadc_remove, .driver = { .name = "sunxi-tpadc", .owner = THIS_MODULE, .pm = SUNXI_TPADC_DEV_PM_OPS, .of_match_table = of_match_ptr(sunxi_tpadc_of_match), }, }; module_platform_driver(sunxi_tpadc_driver); MODULE_AUTHOR("yangshounan"); MODULE_DESCRIPTION("sunxi-tpadc driver"); MODULE_LICENSE("GPL");