/* * 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");