/* * TI Touch Screen driver * * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ * * 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 #include #include #include #include #include #include #include #include #include #include #include #define REG_IRQEOI 0x020 #define REG_RAWIRQSTATUS 0x024 #define REG_IRQSTATUS 0x028 #define REG_IRQENABLE 0x02C #define REG_IRQWAKEUP 0x034 #define REG_CTRL 0x040 #define REG_ADCFSM 0x044 #define REG_CLKDIV 0x04C #define REG_SE 0x054 #define REG_IDLECONFIG 0x058 #define REG_CHARGECONFIG 0x05C #define REG_CHARGEDELAY 0x060 #define REG_STEPCONFIG(n) (0x64 + ((n - 1) * 8)) #define REG_STEPDELAY(n) (0x68 + ((n - 1) * 8)) #define REG_STEPCONFIG13 0x0C4 #define REG_STEPDELAY13 0x0C8 #define REG_STEPCONFIG14 0x0CC #define REG_STEPDELAY14 0x0D0 #define REG_FIFO0CNT 0xE4 #define REG_FIFO1THR 0xF4 #define REG_FIFO0 0x100 #define REG_FIFO1 0x200 /* Register Bitfields */ #define IRQWKUP_ENB BIT(0) #define STPENB_STEPENB 0x7FFF #define IRQENB_FIFO1THRES BIT(5) #define IRQENB_PENUP BIT(9) #define STEPCONFIG_MODE_HWSYNC 0x2 #define STEPCONFIG_SAMPLES_AVG (1 << 4) #define STEPCONFIG_XPP (1 << 5) #define STEPCONFIG_XNN (1 << 6) #define STEPCONFIG_YPP (1 << 7) #define STEPCONFIG_YNN (1 << 8) #define STEPCONFIG_XNP (1 << 9) #define STEPCONFIG_YPN (1 << 10) #define STEPCONFIG_INM (1 << 18) #define STEPCONFIG_INP (1 << 20) #define STEPCONFIG_INP_5 (1 << 21) #define STEPCONFIG_FIFO1 (1 << 26) #define STEPCONFIG_OPENDLY 0xff #define STEPCONFIG_Z1 (3 << 19) #define STEPIDLE_INP (1 << 22) #define STEPCHARGE_RFP (1 << 12) #define STEPCHARGE_INM (1 << 15) #define STEPCHARGE_INP (1 << 19) #define STEPCHARGE_RFM (1 << 23) #define STEPCHARGE_DELAY 0x1 #define CNTRLREG_TSCSSENB (1 << 0) #define CNTRLREG_STEPID (1 << 1) #define CNTRLREG_STEPCONFIGWRT (1 << 2) #define CNTRLREG_4WIRE (1 << 5) #define CNTRLREG_5WIRE (1 << 6) #define CNTRLREG_8WIRE (3 << 5) #define CNTRLREG_TSCENB (1 << 7) #define ADCFSM_STEPID 0x10 #define SEQ_SETTLE 275 #define ADC_CLK 3000000 #define MAX_12BIT ((1 << 12) - 1) #define TSCADC_DELTA_X 15 #define TSCADC_DELTA_Y 15 struct tscadc { struct input_dev *input; struct clk *tsc_ick; void __iomem *tsc_base; unsigned int irq; unsigned int wires; unsigned int x_plate_resistance; bool pen_down; }; static unsigned int tscadc_readl(struct tscadc *ts, unsigned int reg) { return readl(ts->tsc_base + reg); } static void tscadc_writel(struct tscadc *tsc, unsigned int reg, unsigned int val) { writel(val, tsc->tsc_base + reg); } static void tscadc_step_config(struct tscadc *ts_dev) { unsigned int config; int i; /* Configure the Step registers */ config = STEPCONFIG_MODE_HWSYNC | STEPCONFIG_SAMPLES_AVG | STEPCONFIG_XPP; switch (ts_dev->wires) { case 4: config |= STEPCONFIG_INP | STEPCONFIG_XNN; break; case 5: config |= STEPCONFIG_YNN | STEPCONFIG_INP_5 | STEPCONFIG_XNN | STEPCONFIG_YPP; break; case 8: config |= STEPCONFIG_INP | STEPCONFIG_XNN; break; } for (i = 1; i < 7; i++) { tscadc_writel(ts_dev, REG_STEPCONFIG(i), config); tscadc_writel(ts_dev, REG_STEPDELAY(i), STEPCONFIG_OPENDLY); } config = 0; config = STEPCONFIG_MODE_HWSYNC | STEPCONFIG_SAMPLES_AVG | STEPCONFIG_YNN | STEPCONFIG_INM | STEPCONFIG_FIFO1; switch (ts_dev->wires) { case 4: config |= STEPCONFIG_YPP; break; case 5: config |= STEPCONFIG_XPP | STEPCONFIG_INP_5 | STEPCONFIG_XNP | STEPCONFIG_YPN; break; case 8: config |= STEPCONFIG_YPP; break; } for (i = 7; i < 13; i++) { tscadc_writel(ts_dev, REG_STEPCONFIG(i), config); tscadc_writel(ts_dev, REG_STEPDELAY(i), STEPCONFIG_OPENDLY); } config = 0; /* Charge step configuration */ config = STEPCONFIG_XPP | STEPCONFIG_YNN | STEPCHARGE_RFP | STEPCHARGE_RFM | STEPCHARGE_INM | STEPCHARGE_INP; tscadc_writel(ts_dev, REG_CHARGECONFIG, config); tscadc_writel(ts_dev, REG_CHARGEDELAY, STEPCHARGE_DELAY); config = 0; /* Configure to calculate pressure */ config = STEPCONFIG_MODE_HWSYNC | STEPCONFIG_SAMPLES_AVG | STEPCONFIG_YPP | STEPCONFIG_XNN | STEPCONFIG_INM; tscadc_writel(ts_dev, REG_STEPCONFIG13, config); tscadc_writel(ts_dev, REG_STEPDELAY13, STEPCONFIG_OPENDLY); config |= STEPCONFIG_Z1 | STEPCONFIG_FIFO1; tscadc_writel(ts_dev, REG_STEPCONFIG14, config); tscadc_writel(ts_dev, REG_STEPDELAY14, STEPCONFIG_OPENDLY); tscadc_writel(ts_dev, REG_SE, STPENB_STEPENB); } static void tscadc_idle_config(struct tscadc *ts_config) { unsigned int idleconfig; idleconfig = STEPCONFIG_YNN | STEPCONFIG_INM | STEPCONFIG_YPN | STEPIDLE_INP; tscadc_writel(ts_config, REG_IDLECONFIG, idleconfig); } static void tscadc_read_coordinates(struct tscadc *ts_dev, unsigned int *x, unsigned int *y) { unsigned int fifocount = tscadc_readl(ts_dev, REG_FIFO0CNT); unsigned int prev_val_x = ~0, prev_val_y = ~0; unsigned int prev_diff_x = ~0, prev_diff_y = ~0; unsigned int read, diff; unsigned int i; /* * Delta filter is used to remove large variations in sampled * values from ADC. The filter tries to predict where the next * coordinate could be. This is done by taking a previous * coordinate and subtracting it form current one. Further the * algorithm compares the difference with that of a present value, * if true the value is reported to the sub system. */ for (i = 0; i < fifocount - 1; i++) { read = tscadc_readl(ts_dev, REG_FIFO0) & 0xfff; diff = abs(read - prev_val_x); if (diff < prev_diff_x) { prev_diff_x = diff; *x = read; } prev_val_x = read; read = tscadc_readl(ts_dev, REG_FIFO1) & 0xfff; diff = abs(read - prev_val_y); if (diff < prev_diff_y) { prev_diff_y = diff; *y = read; } prev_val_y = read; } } static irqreturn_t tscadc_irq(int irq, void *dev) { struct tscadc *ts_dev = dev; struct input_dev *input_dev = ts_dev->input; unsigned int status, irqclr = 0; unsigned int x = 0, y = 0; unsigned int z1, z2, z; unsigned int fsm; status = tscadc_readl(ts_dev, REG_IRQSTATUS); if (status & IRQENB_FIFO1THRES) { tscadc_read_coordinates(ts_dev, &x, &y); z1 = tscadc_readl(ts_dev, REG_FIFO0) & 0xfff; z2 = tscadc_readl(ts_dev, REG_FIFO1) & 0xfff; if (ts_dev->pen_down && z1 != 0 && z2 != 0) { /* * Calculate pressure using formula * Resistance(touch) = x plate resistance * * x postion/4096 * ((z2 / z1) - 1) */ z = z2 - z1; z *= x; z *= ts_dev->x_plate_resistance; z /= z1; z = (z + 2047) >> 12; if (z <= MAX_12BIT) { input_report_abs(input_dev, ABS_X, x); input_report_abs(input_dev, ABS_Y, y); input_report_abs(input_dev, ABS_PRESSURE, z); input_report_key(input_dev, BTN_TOUCH, 1); input_sync(input_dev); } } irqclr |= IRQENB_FIFO1THRES; } /* * Time for sequencer to settle, to read * correct state of the sequencer. */ udelay(SEQ_SETTLE); status = tscadc_readl(ts_dev, REG_RAWIRQSTATUS); if (status & IRQENB_PENUP) { /* Pen up event */ fsm = tscadc_readl(ts_dev, REG_ADCFSM); if (fsm == ADCFSM_STEPID) { ts_dev->pen_down = false; input_report_key(input_dev, BTN_TOUCH, 0); input_report_abs(input_dev, ABS_PRESSURE, 0); input_sync(input_dev); } else { ts_dev->pen_down = true; } irqclr |= IRQENB_PENUP; } tscadc_writel(ts_dev, REG_IRQSTATUS, irqclr); /* check pending interrupts */ tscadc_writel(ts_dev, REG_IRQEOI, 0x0); tscadc_writel(ts_dev, REG_SE, STPENB_STEPENB); return IRQ_HANDLED; } /* * The functions for inserting/removing driver as a module. */ static int __devinit tscadc_probe(struct platform_device *pdev) { const struct tsc_data *pdata = pdev->dev.platform_data; struct resource *res; struct tscadc *ts_dev; struct input_dev *input_dev; struct clk *clk; int err; int clk_value, ctrl, irq; if (!pdata) { dev_err(&pdev->dev, "missing platform data.\n"); return -EINVAL; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no memory resource defined.\n"); return -EINVAL; } irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "no irq ID is specified.\n"); return -EINVAL; } /* Allocate memory for device */ ts_dev = kzalloc(sizeof(struct tscadc), GFP_KERNEL); input_dev = input_allocate_device(); if (!ts_dev || !input_dev) { dev_err(&pdev->dev, "failed to allocate memory.\n"); err = -ENOMEM; goto err_free_mem; } ts_dev->input = input_dev; ts_dev->irq = irq; ts_dev->wires = pdata->wires; ts_dev->x_plate_resistance = pdata->x_plate_resistance; res = request_mem_region(res->start, resource_size(res), pdev->name); if (!res) { dev_err(&pdev->dev, "failed to reserve registers.\n"); err = -EBUSY; goto err_free_mem; } ts_dev->tsc_base = ioremap(res->start, resource_size(res)); if (!ts_dev->tsc_base) { dev_err(&pdev->dev, "failed to map registers.\n"); err = -ENOMEM; goto err_release_mem_region; } err = request_irq(ts_dev->irq, tscadc_irq, 0, pdev->dev.driver->name, ts_dev); if (err) { dev_err(&pdev->dev, "failed to allocate irq.\n"); goto err_unmap_regs; } ts_dev->tsc_ick = clk_get(&pdev->dev, "adc_tsc_ick"); if (IS_ERR(ts_dev->tsc_ick)) { dev_err(&pdev->dev, "failed to get TSC ick\n"); goto err_free_irq; } clk_enable(ts_dev->tsc_ick); clk = clk_get(&pdev->dev, "adc_tsc_fck"); if (IS_ERR(clk)) { dev_err(&pdev->dev, "failed to get TSC fck\n"); err = PTR_ERR(clk); goto err_disable_clk; } clk_value = clk_get_rate(clk) / ADC_CLK; clk_put(clk); if (clk_value < 7) { dev_err(&pdev->dev, "clock input less than min clock requirement\n"); goto err_disable_clk; } /* CLKDIV needs to be configured to the value minus 1 */ tscadc_writel(ts_dev, REG_CLKDIV, clk_value - 1); /* Enable wake-up of the SoC using touchscreen */ tscadc_writel(ts_dev, REG_IRQWAKEUP, IRQWKUP_ENB); ctrl = CNTRLREG_STEPCONFIGWRT | CNTRLREG_TSCENB | CNTRLREG_STEPID; switch (ts_dev->wires) { case 4: ctrl |= CNTRLREG_4WIRE; break; case 5: ctrl |= CNTRLREG_5WIRE; break; case 8: ctrl |= CNTRLREG_8WIRE; break; } tscadc_writel(ts_dev, REG_CTRL, ctrl); tscadc_idle_config(ts_dev); tscadc_writel(ts_dev, REG_IRQENABLE, IRQENB_FIFO1THRES); tscadc_step_config(ts_dev); tscadc_writel(ts_dev, REG_FIFO1THR, 6); ctrl |= CNTRLREG_TSCSSENB; tscadc_writel(ts_dev, REG_CTRL, ctrl); input_dev->name = "ti-tsc-adc"; input_dev->dev.parent = &pdev->dev; input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0); input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0); input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0); /* register to the input system */ err = input_register_device(input_dev); if (err) goto err_disable_clk; platform_set_drvdata(pdev, ts_dev); return 0; err_disable_clk: clk_disable(ts_dev->tsc_ick); clk_put(ts_dev->tsc_ick); err_free_irq: free_irq(ts_dev->irq, ts_dev); err_unmap_regs: iounmap(ts_dev->tsc_base); err_release_mem_region: release_mem_region(res->start, resource_size(res)); err_free_mem: input_free_device(input_dev); kfree(ts_dev); return err; } static int __devexit tscadc_remove(struct platform_device *pdev) { struct tscadc *ts_dev = platform_get_drvdata(pdev); struct resource *res; free_irq(ts_dev->irq, ts_dev); input_unregister_device(ts_dev->input); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); iounmap(ts_dev->tsc_base); release_mem_region(res->start, resource_size(res)); clk_disable(ts_dev->tsc_ick); clk_put(ts_dev->tsc_ick); kfree(ts_dev); platform_set_drvdata(pdev, NULL); return 0; } static struct platform_driver ti_tsc_driver = { .probe = tscadc_probe, .remove = __devexit_p(tscadc_remove), .driver = { .name = "tsc", .owner = THIS_MODULE, }, }; module_platform_driver(ti_tsc_driver); MODULE_DESCRIPTION("TI touchscreen controller driver"); MODULE_AUTHOR("Rachna Patil "); MODULE_LICENSE("GPL");