| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2015 Broadcom Corporation | |
| * | |
| */ | |
| #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> | |
| #include <linux/mfd/syscon.h> | |
| #include <linux/regmap.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) \ | |
| do { \ | |
| u32 val; \ | |
| regmap_read(priv->regmap, reg, &val); \ | |
| dev_dbg(dev, "%20s= 0x%08x\n", #reg, val); \ | |
| } while (0) | |
| 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; | |
| struct regmap *regmap; | |
| 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; | |
| regmap_read(priv->regmap, INTERRUPT_STATUS, &intr_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 */ | |
| regmap_write(priv->regmap, INTERRUPT_STATUS, intr_status); | |
| /* Pen up/down */ | |
| if (intr_status & TS_PEN_INTR_MASK) { | |
| regmap_read(priv->regmap, CONTROLLER_STATUS, &priv->pen_status); | |
| if (priv->pen_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 threshold */ | |
| if (intr_status & TS_FIFO_INTR_MASK) { | |
| for (i = 0; i < priv->cfg_params.fifo_threshold; i++) { | |
| regmap_read(priv->regmap, FIFO_DATA, &raw_coordinate); | |
| 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) | |
| { | |
| u32 val; | |
| u32 mask; | |
| int error; | |
| struct iproc_ts_priv *priv = input_get_drvdata(idev); | |
| /* 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; | |
| regmap_update_bits(priv->regmap, INTERRUPT_MASK, val, val); | |
| val = priv->cfg_params.fifo_threshold; | |
| regmap_write(priv->regmap, INTERRUPT_THRES, val); | |
| /* 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; | |
| regmap_write(priv->regmap, REGCTL1, val); | |
| /* Try to clear all interrupt status */ | |
| val = TS_FIFO_INTR_MASK | TS_PEN_INTR_MASK; | |
| regmap_update_bits(priv->regmap, INTERRUPT_STATUS, val, val); | |
| /* Initialize control reg2 */ | |
| val = TS_CONTROLLER_EN_BIT | TS_WIRE_MODE_BIT; | |
| val |= priv->cfg_params.average_data << TS_CONTROLLER_AVGDATA_SHIFT; | |
| mask = (TS_CONTROLLER_AVGDATA_MASK); | |
| mask |= (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 */ | |
| mask |= val; | |
| regmap_update_bits(priv->regmap, REGCTL2, mask, val); | |
| 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); | |
| /* | |
| * Disable FIFO int_th and pen event(up/down)Interrupts only | |
| * as the interrupt mask register is shared between ADC, TS and | |
| * flextimer. | |
| */ | |
| val = TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; | |
| regmap_update_bits(priv->regmap, INTERRUPT_MASK, val, 0); | |
| /* Only power down touch screen controller */ | |
| val = TS_CONTROLLER_PWR_TS; | |
| regmap_update_bits(priv->regmap, REGCTL2, val, val); | |
| 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; | |
| int irq; | |
| int error; | |
| priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
| if (!priv) | |
| return -ENOMEM; | |
| /* touchscreen controller memory mapped regs via syscon*/ | |
| priv->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | |
| "ts_syscon"); | |
| if (IS_ERR(priv->regmap)) { | |
| error = PTR_ERR(priv->regmap); | |
| 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) | |
| 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 = iproc_ts_of_match, | |
| }, | |
| }; | |
| module_platform_driver(iproc_ts_driver); | |
| MODULE_DESCRIPTION("IPROC Touchscreen driver"); | |
| MODULE_AUTHOR("Broadcom"); | |
| MODULE_LICENSE("GPL v2"); |