SmartAudio/lichee/linux-4.9/drivers/input/sensor/sunxi_tpadc.c

502 lines
12 KiB
C
Raw Normal View History

2018-07-13 01:31:50 +00:00
/*
* drivers/input/sensor/sunxi_tpadc.c
*
* Copyright (C) 2017 Allwinner.
* fuzhaoke <yangshounan@allwinnertech.com>
*
* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/timer.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/of_platform.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/pm.h>
#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");