diff options
author | Jonathan Richardson <jonathar@broadcom.com> | 2015-03-21 21:04:59 -0700 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2015-03-23 11:04:48 -0700 |
commit | d5ae685f15307c85c6267c5a2be9ba3d70eb3297 (patch) | |
tree | 416649cd967241045d14bab40121ad9d7e51a5c8 /drivers/input/touchscreen/bcm_iproc_tsc.c | |
parent | 188933ac139a6f8ab06cad369bd0200af947b00d (diff) | |
download | lwn-d5ae685f15307c85c6267c5a2be9ba3d70eb3297.tar.gz lwn-d5ae685f15307c85c6267c5a2be9ba3d70eb3297.zip |
Input: add Broadcom iProc touchscreen driver
Add initial version of the Broadcom touchscreen driver.
Reviewed-by: Ray Jui <rjui@broadcom.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
Tested-by: Scott Branden <sbranden@broadcom.com>
Signed-off-by: Jonathan Richardson <jonathar@broadcom.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Diffstat (limited to 'drivers/input/touchscreen/bcm_iproc_tsc.c')
-rw-r--r-- | drivers/input/touchscreen/bcm_iproc_tsc.c | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/bcm_iproc_tsc.c b/drivers/input/touchscreen/bcm_iproc_tsc.c new file mode 100644 index 000000000000..ae460a5c93d5 --- /dev/null +++ b/drivers/input/touchscreen/bcm_iproc_tsc.c @@ -0,0 +1,522 @@ +/* +* Copyright (C) 2015 Broadcom Corporation +* +* 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 version 2. +* +* This program is distributed "as is" WITHOUT ANY WARRANTY of any +* kind, whether express or implied; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/keyboard.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <asm/irq.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/serio.h> + +#define IPROC_TS_NAME "iproc-ts" + +#define PEN_DOWN_STATUS 1 +#define PEN_UP_STATUS 0 + +#define X_MIN 0 +#define Y_MIN 0 +#define X_MAX 0xFFF +#define Y_MAX 0xFFF + +/* Value given by controller for invalid coordinate. */ +#define INVALID_COORD 0xFFFFFFFF + +/* Register offsets */ +#define REGCTL1 0x00 +#define REGCTL2 0x04 +#define INTERRUPT_THRES 0x08 +#define INTERRUPT_MASK 0x0c + +#define INTERRUPT_STATUS 0x10 +#define CONTROLLER_STATUS 0x14 +#define FIFO_DATA 0x18 +#define FIFO_DATA_X_Y_MASK 0xFFFF +#define ANALOG_CONTROL 0x1c + +#define AUX_DATA 0x20 +#define DEBOUNCE_CNTR_STAT 0x24 +#define SCAN_CNTR_STAT 0x28 +#define REM_CNTR_STAT 0x2c + +#define SETTLING_TIMER_STAT 0x30 +#define SPARE_REG 0x34 +#define SOFT_BYPASS_CONTROL 0x38 +#define SOFT_BYPASS_DATA 0x3c + + +/* Bit values for INTERRUPT_MASK and INTERRUPT_STATUS regs */ +#define TS_PEN_INTR_MASK BIT(0) +#define TS_FIFO_INTR_MASK BIT(2) + +/* Bit values for CONTROLLER_STATUS reg1 */ +#define TS_PEN_DOWN BIT(0) + +/* Shift values for control reg1 */ +#define SCANNING_PERIOD_SHIFT 24 +#define DEBOUNCE_TIMEOUT_SHIFT 16 +#define SETTLING_TIMEOUT_SHIFT 8 +#define TOUCH_TIMEOUT_SHIFT 0 + +/* Shift values for coordinates from fifo */ +#define X_COORD_SHIFT 0 +#define Y_COORD_SHIFT 16 + +/* Bit values for REGCTL2 */ +#define TS_CONTROLLER_EN_BIT BIT(16) +#define TS_CONTROLLER_AVGDATA_SHIFT 8 +#define TS_CONTROLLER_AVGDATA_MASK (0x7 << TS_CONTROLLER_AVGDATA_SHIFT) +#define TS_CONTROLLER_PWR_LDO BIT(5) +#define TS_CONTROLLER_PWR_ADC BIT(4) +#define TS_CONTROLLER_PWR_BGP BIT(3) +#define TS_CONTROLLER_PWR_TS BIT(2) +#define TS_WIRE_MODE_BIT BIT(1) + +#define dbg_reg(dev, priv, reg) \ + dev_dbg(dev, "%20s= 0x%08x\n", #reg, readl((priv)->regs + reg)) + +struct tsc_param { + /* Each step is 1024 us. Valid 1-256 */ + u32 scanning_period; + + /* Each step is 512 us. Valid 0-255 */ + u32 debounce_timeout; + + /* + * The settling duration (in ms) is the amount of time the tsc + * waits to allow the voltage to settle after turning on the + * drivers in detection mode. Valid values: 0-11 + * 0 = 0.008 ms + * 1 = 0.01 ms + * 2 = 0.02 ms + * 3 = 0.04 ms + * 4 = 0.08 ms + * 5 = 0.16 ms + * 6 = 0.32 ms + * 7 = 0.64 ms + * 8 = 1.28 ms + * 9 = 2.56 ms + * 10 = 5.12 ms + * 11 = 10.24 ms + */ + u32 settling_timeout; + + /* touch timeout in sample counts */ + u32 touch_timeout; + + /* + * Number of data samples which are averaged before a final data point + * is placed into the FIFO + */ + u32 average_data; + + /* FIFO threshold */ + u32 fifo_threshold; + + /* Optional standard touchscreen properties. */ + u32 max_x; + u32 max_y; + u32 fuzz_x; + u32 fuzz_y; + bool invert_x; + bool invert_y; +}; + +struct iproc_ts_priv { + struct platform_device *pdev; + struct input_dev *idev; + + void __iomem *regs; + struct clk *tsc_clk; + + int pen_status; + struct tsc_param cfg_params; +}; + +/* + * Set default values the same as hardware reset values + * except for fifo_threshold with is set to 1. + */ +static const struct tsc_param iproc_default_config = { + .scanning_period = 0x5, /* 1 to 256 */ + .debounce_timeout = 0x28, /* 0 to 255 */ + .settling_timeout = 0x7, /* 0 to 11 */ + .touch_timeout = 0xa, /* 0 to 255 */ + .average_data = 5, /* entry 5 = 32 pts */ + .fifo_threshold = 1, /* 0 to 31 */ + .max_x = X_MAX, + .max_y = Y_MAX, +}; + +static void ts_reg_dump(struct iproc_ts_priv *priv) +{ + struct device *dev = &priv->pdev->dev; + + dbg_reg(dev, priv, REGCTL1); + dbg_reg(dev, priv, REGCTL2); + dbg_reg(dev, priv, INTERRUPT_THRES); + dbg_reg(dev, priv, INTERRUPT_MASK); + dbg_reg(dev, priv, INTERRUPT_STATUS); + dbg_reg(dev, priv, CONTROLLER_STATUS); + dbg_reg(dev, priv, FIFO_DATA); + dbg_reg(dev, priv, ANALOG_CONTROL); + dbg_reg(dev, priv, AUX_DATA); + dbg_reg(dev, priv, DEBOUNCE_CNTR_STAT); + dbg_reg(dev, priv, SCAN_CNTR_STAT); + dbg_reg(dev, priv, REM_CNTR_STAT); + dbg_reg(dev, priv, SETTLING_TIMER_STAT); + dbg_reg(dev, priv, SPARE_REG); + dbg_reg(dev, priv, SOFT_BYPASS_CONTROL); + dbg_reg(dev, priv, SOFT_BYPASS_DATA); +} + +static irqreturn_t iproc_touchscreen_interrupt(int irq, void *data) +{ + struct platform_device *pdev = data; + struct iproc_ts_priv *priv = platform_get_drvdata(pdev); + u32 intr_status; + u32 raw_coordinate; + u16 x; + u16 y; + int i; + bool needs_sync = false; + + intr_status = readl(priv->regs + INTERRUPT_STATUS); + intr_status &= TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; + if (intr_status == 0) + return IRQ_NONE; + + /* Clear all interrupt status bits, write-1-clear */ + writel(intr_status, priv->regs + INTERRUPT_STATUS); + + /* Pen up/down */ + if (intr_status & TS_PEN_INTR_MASK) { + if (readl(priv->regs + CONTROLLER_STATUS) & TS_PEN_DOWN) + priv->pen_status = PEN_DOWN_STATUS; + else + priv->pen_status = PEN_UP_STATUS; + + input_report_key(priv->idev, BTN_TOUCH, priv->pen_status); + needs_sync = true; + + dev_dbg(&priv->pdev->dev, + "pen up-down (%d)\n", priv->pen_status); + } + + /* coordinates in FIFO exceed the theshold */ + if (intr_status & TS_FIFO_INTR_MASK) { + for (i = 0; i < priv->cfg_params.fifo_threshold; i++) { + raw_coordinate = readl(priv->regs + FIFO_DATA); + if (raw_coordinate == INVALID_COORD) + continue; + + /* + * The x and y coordinate are 16 bits each + * with the x in the lower 16 bits and y in the + * upper 16 bits. + */ + x = (raw_coordinate >> X_COORD_SHIFT) & + FIFO_DATA_X_Y_MASK; + y = (raw_coordinate >> Y_COORD_SHIFT) & + FIFO_DATA_X_Y_MASK; + + /* We only want to retain the 12 msb of the 16 */ + x = (x >> 4) & 0x0FFF; + y = (y >> 4) & 0x0FFF; + + /* adjust x y according to lcd tsc mount angle */ + if (priv->cfg_params.invert_x) + x = priv->cfg_params.max_x - x; + + if (priv->cfg_params.invert_y) + y = priv->cfg_params.max_y - y; + + input_report_abs(priv->idev, ABS_X, x); + input_report_abs(priv->idev, ABS_Y, y); + needs_sync = true; + + dev_dbg(&priv->pdev->dev, "xy (0x%x 0x%x)\n", x, y); + } + } + + if (needs_sync) + input_sync(priv->idev); + + return IRQ_HANDLED; +} + +static int iproc_ts_start(struct input_dev *idev) +{ + struct iproc_ts_priv *priv = input_get_drvdata(idev); + u32 val; + int error; + + /* Enable clock */ + error = clk_prepare_enable(priv->tsc_clk); + if (error) { + dev_err(&priv->pdev->dev, "%s clk_prepare_enable failed %d\n", + __func__, error); + return error; + } + + /* + * Interrupt is generated when: + * FIFO reaches the int_th value, and pen event(up/down) + */ + val = TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; + writel(val, priv->regs + INTERRUPT_MASK); + + writel(priv->cfg_params.fifo_threshold, priv->regs + INTERRUPT_THRES); + + /* Initialize control reg1 */ + val = 0; + val |= priv->cfg_params.scanning_period << SCANNING_PERIOD_SHIFT; + val |= priv->cfg_params.debounce_timeout << DEBOUNCE_TIMEOUT_SHIFT; + val |= priv->cfg_params.settling_timeout << SETTLING_TIMEOUT_SHIFT; + val |= priv->cfg_params.touch_timeout << TOUCH_TIMEOUT_SHIFT; + writel(val, priv->regs + REGCTL1); + + /* Try to clear all interrupt status */ + val = readl(priv->regs + INTERRUPT_STATUS); + val |= TS_FIFO_INTR_MASK | TS_PEN_INTR_MASK; + writel(val, priv->regs + INTERRUPT_STATUS); + + /* Initialize control reg2 */ + val = readl(priv->regs + REGCTL2); + val |= TS_CONTROLLER_EN_BIT | TS_WIRE_MODE_BIT; + + val &= ~TS_CONTROLLER_AVGDATA_MASK; + val |= priv->cfg_params.average_data << TS_CONTROLLER_AVGDATA_SHIFT; + + val &= ~(TS_CONTROLLER_PWR_LDO | /* PWR up LDO */ + TS_CONTROLLER_PWR_ADC | /* PWR up ADC */ + TS_CONTROLLER_PWR_BGP | /* PWR up BGP */ + TS_CONTROLLER_PWR_TS); /* PWR up TS */ + + writel(val, priv->regs + REGCTL2); + + ts_reg_dump(priv); + + return 0; +} + +static void iproc_ts_stop(struct input_dev *dev) +{ + u32 val; + struct iproc_ts_priv *priv = input_get_drvdata(dev); + + writel(0, priv->regs + INTERRUPT_MASK); /* Disable all interrupts */ + + /* Only power down touch screen controller */ + val = readl(priv->regs + REGCTL2); + val |= TS_CONTROLLER_PWR_TS; + writel(val, priv->regs + REGCTL2); + + clk_disable(priv->tsc_clk); +} + +static int iproc_get_tsc_config(struct device *dev, struct iproc_ts_priv *priv) +{ + struct device_node *np = dev->of_node; + u32 val; + + priv->cfg_params = iproc_default_config; + + if (!np) + return 0; + + if (of_property_read_u32(np, "scanning_period", &val) >= 0) { + if (val < 1 || val > 256) { + dev_err(dev, "scanning_period (%u) must be [1-256]\n", + val); + return -EINVAL; + } + priv->cfg_params.scanning_period = val; + } + + if (of_property_read_u32(np, "debounce_timeout", &val) >= 0) { + if (val > 255) { + dev_err(dev, "debounce_timeout (%u) must be [0-255]\n", + val); + return -EINVAL; + } + priv->cfg_params.debounce_timeout = val; + } + + if (of_property_read_u32(np, "settling_timeout", &val) >= 0) { + if (val > 11) { + dev_err(dev, "settling_timeout (%u) must be [0-11]\n", + val); + return -EINVAL; + } + priv->cfg_params.settling_timeout = val; + } + + if (of_property_read_u32(np, "touch_timeout", &val) >= 0) { + if (val > 255) { + dev_err(dev, "touch_timeout (%u) must be [0-255]\n", + val); + return -EINVAL; + } + priv->cfg_params.touch_timeout = val; + } + + if (of_property_read_u32(np, "average_data", &val) >= 0) { + if (val > 8) { + dev_err(dev, "average_data (%u) must be [0-8]\n", val); + return -EINVAL; + } + priv->cfg_params.average_data = val; + } + + if (of_property_read_u32(np, "fifo_threshold", &val) >= 0) { + if (val > 31) { + dev_err(dev, "fifo_threshold (%u)) must be [0-31]\n", + val); + return -EINVAL; + } + priv->cfg_params.fifo_threshold = val; + } + + /* Parse optional properties. */ + of_property_read_u32(np, "touchscreen-size-x", &priv->cfg_params.max_x); + of_property_read_u32(np, "touchscreen-size-y", &priv->cfg_params.max_y); + + of_property_read_u32(np, "touchscreen-fuzz-x", + &priv->cfg_params.fuzz_x); + of_property_read_u32(np, "touchscreen-fuzz-y", + &priv->cfg_params.fuzz_y); + + priv->cfg_params.invert_x = + of_property_read_bool(np, "touchscreen-inverted-x"); + priv->cfg_params.invert_y = + of_property_read_bool(np, "touchscreen-inverted-y"); + + return 0; +} + +static int iproc_ts_probe(struct platform_device *pdev) +{ + struct iproc_ts_priv *priv; + struct input_dev *idev; + struct resource *res; + int irq; + int error; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* touchscreen controller memory mapped regs */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->regs)) { + error = PTR_ERR(priv->regs); + dev_err(&pdev->dev, "unable to map I/O memory: %d\n", error); + return error; + } + + priv->tsc_clk = devm_clk_get(&pdev->dev, "tsc_clk"); + if (IS_ERR(priv->tsc_clk)) { + error = PTR_ERR(priv->tsc_clk); + dev_err(&pdev->dev, + "failed getting clock tsc_clk: %d\n", error); + return error; + } + + priv->pdev = pdev; + error = iproc_get_tsc_config(&pdev->dev, priv); + if (error) { + dev_err(&pdev->dev, "get_tsc_config failed: %d\n", error); + return error; + } + + idev = devm_input_allocate_device(&pdev->dev); + if (!idev) { + dev_err(&pdev->dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + priv->idev = idev; + priv->pen_status = PEN_UP_STATUS; + + /* Set input device info */ + idev->name = IPROC_TS_NAME; + idev->dev.parent = &pdev->dev; + + idev->id.bustype = BUS_HOST; + idev->id.vendor = SERIO_UNKNOWN; + idev->id.product = 0; + idev->id.version = 0; + + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + __set_bit(BTN_TOUCH, idev->keybit); + + input_set_abs_params(idev, ABS_X, X_MIN, priv->cfg_params.max_x, + priv->cfg_params.fuzz_x, 0); + input_set_abs_params(idev, ABS_Y, Y_MIN, priv->cfg_params.max_y, + priv->cfg_params.fuzz_y, 0); + + idev->open = iproc_ts_start; + idev->close = iproc_ts_stop; + + input_set_drvdata(idev, priv); + platform_set_drvdata(pdev, priv); + + /* get interrupt */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "platform_get_irq failed: %d\n", irq); + return irq; + } + + error = devm_request_irq(&pdev->dev, irq, + iproc_touchscreen_interrupt, + IRQF_SHARED, IPROC_TS_NAME, pdev); + if (error) + return error; + + error = input_register_device(priv->idev); + if (error) { + dev_err(&pdev->dev, + "failed to register input device: %d\n", error); + return error; + } + + return 0; +} + +static const struct of_device_id iproc_ts_of_match[] = { + {.compatible = "brcm,iproc-touchscreen", }, + { }, +}; +MODULE_DEVICE_TABLE(of, iproc_ts_of_match); + +static struct platform_driver iproc_ts_driver = { + .probe = iproc_ts_probe, + .driver = { + .name = IPROC_TS_NAME, + .of_match_table = of_match_ptr(iproc_ts_of_match), + }, +}; + +module_platform_driver(iproc_ts_driver); + +MODULE_DESCRIPTION("IPROC Touchscreen driver"); +MODULE_AUTHOR("Broadcom"); +MODULE_LICENSE("GPL v2"); |