blob: 9cd794461af74b175bb906f5728e49d1e24a6b34 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ASR built-in touchscreen driver
*
* Copyright (C) 2023 ASR
*/
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/timer.h>
/*
* Touchscreen controller register offsets
*/
#define ASR_TSCR 0x0
#define ASR_TSSR 0x4
#define ASR_XYDR(x) (0x8 + (x) * 0x4)
#define ASR_TSSR_DOWN 0
#define ASR_TSSR_UP 1
#define ASR_TSC_MIN_XY_VAL 0
#define ASR_TSC_MAX_XY_VAL 4095
#define ASR_TSC_DOT_CNT_MAX 8
#define MOD_NAME "ts-asr"
#define tsc_readl(dev, reg) \
__raw_readl((dev)->tsc_base + (reg))
#define tsc_writel(dev, reg, val) \
__raw_writel((val), (dev)->tsc_base + (reg))
struct asr_tp_info {
int x;
int y;
int last_x;
int last_y;
};
struct asr_tsc {
struct timer_list move_timer;
struct input_dev *dev;
void __iomem *tsc_base;
int rdy_irq;
int up_irq;
int down_irq;
struct clk *clk;
struct asr_tp_info tp;
int dummpy_points;
};
#define ASR_TSC_MOVE_TIMEOUT 10 /* ms */
#define ASR_TSC_DUMMY_POINTS 10
#define ASR_TSC_TP_MARGIN 10
enum {
TSC_MODE_IDLE = 0,
TSC_MODE_WAIT_PRESSDOWN,
TSC_MODE_WAIT_RISEUP,
TSC_MODE_WAIT_AUTOMEASURE,
};
static void asr_ts_set_mode(struct asr_tsc *tsc, int mode);
static void asr_ts_movement_detect(struct timer_list *t)
{
struct asr_tsc *tsc = from_timer(tsc, t, move_timer);
u32 val;
val = tsc_readl(tsc, ASR_TSSR);
if (val & ASR_TSSR_UP) {
asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
return;
}
asr_ts_set_mode(tsc, TSC_MODE_WAIT_AUTOMEASURE);
}
static void asr_ts_adc_init(void)
{
void __iomem* apb_spare;
u32 val;
apb_spare = ioremap(0xD4090000, SZ_4K);
if (!apb_spare) {
pr_err("%s: error to ioremap apb_spare base\n", __func__);
return;
}
val = readl(apb_spare + 0x12c);
val |= (1 << 8) | (1 << 20) | (1 << 22) | (0x4 << 24);
writel(val, apb_spare + 0x12c);
iounmap(apb_spare);
}
static void asr_ts_set_mode(struct asr_tsc *tsc, int mode)
{
u32 val;
val = tsc_readl(tsc, ASR_TSCR);
val &= ~(0x3 << 1);
switch (mode) {
case TSC_MODE_WAIT_PRESSDOWN:
val &= ~((1 << 8) | (1 << 7));
val |= (1 << 6) | (1 << 9);
break;
case TSC_MODE_WAIT_RISEUP:
val &= ~((1 << 8) | (1 << 6));
val |= (1 << 7) | (1 << 9);
break;
case TSC_MODE_WAIT_AUTOMEASURE:
val &= ~((1 << 6) | (1 << 7) | (1 << 9));
val |= (1 << 8);
break;
}
val |= (mode << 1);
tsc_writel(tsc, ASR_TSCR, val);
}
static void asr_ts_measure(struct asr_tsc *tsc)
{
int i;
u32 val, meas_x, meas_y, sum_x = 0, sum_y = 0;
u16 min_x, max_x, min_y, max_y;
u16 x[ASR_TSC_DOT_CNT_MAX] = {0};
u16 y[ASR_TSC_DOT_CNT_MAX] = {0};
struct asr_tp_info *tp = (struct asr_tp_info *)&tsc->tp;
for (i = 0; i < ASR_TSC_DOT_CNT_MAX; i++) {
val = tsc_readl(tsc, ASR_XYDR(i));
x[i] = val & 0xffff;
sum_x += x[i];
if (i == 0 ) {
min_x = x[0];
max_x = x[0];
}
if (min_x > x[i])
min_x = x[i];
if (max_x < x[i])
max_x = x[i];
y[i] = (val >> 16) & 0xffff;
sum_y += y[i];
if (i == 0 ) {
min_y = y[0];
max_y = y[0];
}
if (min_y > y[i])
min_y = y[i];
if(max_y < y[i])
max_y = y[i];
pr_debug(" %s: [%d] (%d, %d)\n", __func__, i, x[i], y[i]);
}
meas_x = (sum_x - max_x - min_x) / (ASR_TSC_DOT_CNT_MAX - 2);
meas_y = (sum_y - max_y - min_y) / (ASR_TSC_DOT_CNT_MAX - 2);
pr_debug("meas_x: %d, meas_y: %d\n", meas_x, meas_y);
tp->x = meas_x;
tp->y = meas_y;
}
static irqreturn_t asr_ts_down_isr(int irq, void *dev_id)
{
u32 val;
struct asr_tsc *tsc = (struct asr_tsc *)dev_id;
val = tsc_readl(tsc, ASR_TSSR);
if (val & (1 << 6)) {
val |= (1 << 6);
tsc_writel(tsc, ASR_TSSR, val);
if (val & ASR_TSSR_UP) {
asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
return IRQ_HANDLED;
}
tsc->dummpy_points = 1;
asr_ts_set_mode(tsc, TSC_MODE_WAIT_AUTOMEASURE);
pr_debug("%s: got down irq.\n", __func__);
} else {
pr_warn("%s: mismatch irq?\n", __func__);
}
return IRQ_HANDLED;
}
static irqreturn_t asr_ts_rdy_isr(int irq, void *dev_id)
{
u32 val;
int x, y;
struct asr_tsc *tsc = (struct asr_tsc *)dev_id;
struct input_dev *input = tsc->dev;
val = tsc_readl(tsc, ASR_TSSR);
if (val & (1 << 8)) {
val |= (1 << 8);
tsc_writel(tsc, ASR_TSSR, val);
#if 0
if (val & ASR_TSSR_UP) {
asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
return IRQ_HANDLED;
}
#endif
asr_ts_measure(tsc);
if (tsc->dummpy_points) {
tsc->dummpy_points++;
if (tsc->dummpy_points > ASR_TSC_DUMMY_POINTS) {
tsc->dummpy_points = 0;
x = tsc->tp.last_x = tsc->tp.x;
y = tsc->tp.last_y = tsc->tp.y;
} else
goto skip;
} else {
if (abs(tsc->tp.x - tsc->tp.last_x) < ASR_TSC_TP_MARGIN &&
abs(tsc->tp.y - tsc->tp.last_y) < ASR_TSC_TP_MARGIN)
goto skip;
}
x = tsc->tp.last_x = tsc->tp.x;
y = tsc->tp.last_y = tsc->tp.y;
input_report_abs(input, ABS_X, x);
input_report_abs(input, ABS_Y, y);
input_report_key(input, BTN_TOUCH, 1);
input_sync(input);
skip:
asr_ts_set_mode(tsc, TSC_MODE_WAIT_RISEUP);
mod_timer(&tsc->move_timer, jiffies + msecs_to_jiffies(ASR_TSC_MOVE_TIMEOUT));
pr_debug("%s: got rdy irq. 0x%x, (%d, %d)\n", __func__, val, tsc->tp.x, tsc->tp.y);
} else {
pr_warn("%s: mismatch irq?\n", __func__);
}
return IRQ_HANDLED;
}
static irqreturn_t asr_ts_up_isr(int irq, void *dev_id)
{
u32 val;
struct asr_tsc *tsc = (struct asr_tsc *)dev_id;
struct input_dev *input = tsc->dev;
val = tsc_readl(tsc, ASR_TSSR);
if (val & (1 << 7)) {
val |= (1 << 7);
tsc_writel(tsc, ASR_TSSR, val);
if ((val & 1) == ASR_TSSR_DOWN)
return IRQ_HANDLED;
pr_debug("%s: got up irq. (%d, %d)\n", __func__, tsc->tp.x, tsc->tp.y);
input_report_key(input, BTN_TOUCH, 0);
input_sync(input);
asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
} else {
pr_warn("%s: mismatch irq?\n", __func__);
}
return IRQ_HANDLED;
}
static void asr_stop_tsc(struct asr_tsc *tsc)
{
tsc_writel(tsc, ASR_TSCR, 0);
clk_disable_unprepare(tsc->clk);
}
static int asr_setup_tsc(struct asr_tsc *tsc)
{
u32 tmp;
int err;
asr_ts_adc_init();
err = clk_prepare_enable(tsc->clk);
if (err)
return err;
tmp = tsc_readl(tsc, ASR_TSCR);
tmp |= (1 << 0); /* enabled tsc */
tmp |= 0x7 << 16; /* YD ADC channel slection */
tmp |= 0x6 << 11; /* XL ADC channel slection */
tmp |= (1 << 8); /* INT_RDY_EN */
tmp |= (0x20 << 24);
tmp |= (0 << 21); /* XL_PU_RES_SEL */
tmp |= ((ASR_TSC_DOT_CNT_MAX - 1) << 3); /* DOT_CNT */
tsc_writel(tsc, ASR_TSCR, tmp);
asr_ts_set_mode(tsc, TSC_MODE_WAIT_PRESSDOWN);
return 0;
}
static int asr_ts_open(struct input_dev *dev)
{
struct asr_tsc *tsc = input_get_drvdata(dev);
return asr_setup_tsc(tsc);
}
static void asr_ts_close(struct input_dev *dev)
{
struct asr_tsc *tsc = input_get_drvdata(dev);
asr_stop_tsc(tsc);
}
static int asr_ts_probe(struct platform_device *pdev)
{
struct asr_tsc *tsc;
struct input_dev *input;
struct resource *res;
resource_size_t size;
int irq0, irq1, irq2;
int error;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Can't get memory resource\n");
return -ENOENT;
}
irq0 = platform_get_irq(pdev, 0);
irq1 = platform_get_irq(pdev, 1);
irq2 = platform_get_irq(pdev, 2);
if (irq0 < 0 || irq1 < 0 || irq2 < 0) {
dev_err(&pdev->dev, "err irq:%d, %d, %d\n", irq0, irq1, irq2);
return -ENOENT;
}
tsc = kzalloc(sizeof(*tsc), GFP_KERNEL);
input = input_allocate_device();
if (!tsc || !input) {
dev_err(&pdev->dev, "failed allocating memory\n");
error = -ENOMEM;
goto err_free_mem;
}
tsc->dev = input;
tsc->down_irq = irq0;
tsc->rdy_irq = irq1;
tsc->up_irq = irq2;
size = resource_size(res);
if (!request_mem_region(res->start, size, pdev->name)) {
dev_err(&pdev->dev, "TSC registers are not free\n");
error = -EBUSY;
goto err_free_mem;
}
tsc->tsc_base = ioremap(res->start, size);
if (!tsc->tsc_base) {
dev_err(&pdev->dev, "Can't map memory\n");
error = -ENOMEM;
goto err_release_mem;
}
tsc->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(tsc->clk)) {
dev_err(&pdev->dev, "failed getting clock\n");
error = PTR_ERR(tsc->clk);
goto err_unmap;
}
input->name = MOD_NAME;
input->phys = "asr/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0002;
input->id.version = 0x0100;
input->dev.parent = &pdev->dev;
input->open = asr_ts_open;
input->close = asr_ts_close;
__set_bit(EV_ABS, input->evbit);
__set_bit(ABS_X, input->absbit);
__set_bit(ABS_Y, input->absbit);
__set_bit(EV_SYN, input->evbit);
__set_bit(EV_KEY, input->evbit);
__set_bit(BTN_TOUCH, input->keybit);
input_set_abs_params(input, ABS_X, ASR_TSC_MIN_XY_VAL,
ASR_TSC_MAX_XY_VAL, 0, 0);
input_set_abs_params(input, ABS_Y, ASR_TSC_MIN_XY_VAL,
ASR_TSC_MAX_XY_VAL, 0, 0);
input_set_drvdata(input, tsc);
error = request_irq(tsc->down_irq, asr_ts_down_isr,
IRQF_SHARED, pdev->name, tsc);
if (error) {
dev_err(&pdev->dev, "failed requesting down irq\n");
goto err_put_clock;
}
error = request_irq(tsc->rdy_irq, asr_ts_rdy_isr,
IRQF_SHARED, pdev->name, tsc);
if (error) {
dev_err(&pdev->dev, "failed requesting rdy irq\n");
goto err_free_irq0;
}
error = request_irq(tsc->up_irq, asr_ts_up_isr,
IRQF_SHARED, pdev->name, tsc);
if (error) {
dev_err(&pdev->dev, "failed requesting up irq\n");
goto err_free_irq1;
}
error = input_register_device(input);
if (error) {
dev_err(&pdev->dev, "failed registering input device\n");
goto err_free_irq2;
}
platform_set_drvdata(pdev, tsc);
device_init_wakeup(&pdev->dev, 1);
timer_setup(&tsc->move_timer, asr_ts_movement_detect, 0);
dev_info(&pdev->dev, "bringup ok.\n");
return 0;
err_free_irq2:
free_irq(tsc->up_irq, tsc);
err_free_irq1:
free_irq(tsc->rdy_irq, tsc);
err_free_irq0:
free_irq(tsc->down_irq, tsc);
err_put_clock:
clk_put(tsc->clk);
err_unmap:
iounmap(tsc->tsc_base);
err_release_mem:
release_mem_region(res->start, size);
err_free_mem:
input_free_device(input);
kfree(tsc);
return error;
}
static int asr_ts_remove(struct platform_device *pdev)
{
struct asr_tsc *tsc = platform_get_drvdata(pdev);
struct resource *res;
free_irq(tsc->down_irq, tsc);
free_irq(tsc->rdy_irq, tsc);
free_irq(tsc->up_irq, tsc);
input_unregister_device(tsc->dev);
clk_put(tsc->clk);
iounmap(tsc->tsc_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(res->start, resource_size(res));
kfree(tsc);
return 0;
}
#ifdef CONFIG_PM
static int asr_ts_suspend(struct device *dev)
{
struct asr_tsc *tsc = dev_get_drvdata(dev);
struct input_dev *input = tsc->dev;
/*
* Suspend and resume can be called when the device hasn't been
* enabled. If there are no users that have the device open, then
* avoid calling the TSC stop and start functions as the TSC
* isn't yet clocked.
*/
mutex_lock(&input->mutex);
if (input->users) {
if (device_may_wakeup(dev)) {
enable_irq_wake(tsc->down_irq);
enable_irq_wake(tsc->rdy_irq);
enable_irq_wake(tsc->up_irq);
} else
asr_stop_tsc(tsc);
}
mutex_unlock(&input->mutex);
return 0;
}
static int asr_ts_resume(struct device *dev)
{
struct asr_tsc *tsc = dev_get_drvdata(dev);
struct input_dev *input = tsc->dev;
mutex_lock(&input->mutex);
if (input->users) {
if (device_may_wakeup(dev)) {
disable_irq_wake(tsc->down_irq);
disable_irq_wake(tsc->rdy_irq);
disable_irq_wake(tsc->up_irq);
} else
asr_setup_tsc(tsc);
}
mutex_unlock(&input->mutex);
return 0;
}
static const struct dev_pm_ops asr_ts_pm_ops = {
.suspend = asr_ts_suspend,
.resume = asr_ts_resume,
};
#define ASR_TS_PM_OPS (&asr_ts_pm_ops)
#else
#define ASR_TS_PM_OPS NULL
#endif
#ifdef CONFIG_OF
static const struct of_device_id asr_tsc_of_match[] = {
{ .compatible = "asr,tsc", },
{ },
};
MODULE_DEVICE_TABLE(of, asr_tsc_of_match);
#endif
static struct platform_driver asr_ts_driver = {
.probe = asr_ts_probe,
.remove = asr_ts_remove,
.driver = {
.name = MOD_NAME,
.pm = ASR_TS_PM_OPS,
.of_match_table = of_match_ptr(asr_tsc_of_match),
},
};
module_platform_driver(asr_ts_driver);
MODULE_AUTHOR("Mark Zhang <markzhang@asrmicro.com>");
MODULE_DESCRIPTION("ASR TSC Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:asr_ts");