/* * Freescale i.MX6UL touchscreen controller driver * * Copyright (C) 2015 Freescale Semiconductor, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* ADC configuration registers field define */ #define ADC_AIEN (0x1 << 7) #define ADC_CONV_DISABLE 0x1F #define ADC_CAL (0x1 << 7) #define ADC_CALF 0x2 #define ADC_12BIT_MODE (0x2 << 2) #define ADC_IPG_CLK 0x00 #define ADC_CLK_DIV_8 (0x03 << 5) #define ADC_SHORT_SAMPLE_MODE (0x0 << 4) #define ADC_HARDWARE_TRIGGER (0x1 << 13) #define SELECT_CHANNEL_4 0x04 #define SELECT_CHANNEL_1 0x01 #define DISABLE_CONVERSION_INT (0x0 << 7) /* ADC registers */ #define REG_ADC_HC0 0x00 #define REG_ADC_HC1 0x04 #define REG_ADC_HC2 0x08 #define REG_ADC_HC3 0x0C #define REG_ADC_HC4 0x10 #define REG_ADC_HS 0x14 #define REG_ADC_R0 0x18 #define REG_ADC_CFG 0x2C #define REG_ADC_GC 0x30 #define REG_ADC_GS 0x34 #define ADC_TIMEOUT msecs_to_jiffies(100) /* TSC registers */ #define REG_TSC_BASIC_SETING 0x00 #define REG_TSC_PRE_CHARGE_TIME 0x10 #define REG_TSC_FLOW_CONTROL 0x20 #define REG_TSC_MEASURE_VALUE 0x30 #define REG_TSC_INT_EN 0x40 #define REG_TSC_INT_SIG_EN 0x50 #define REG_TSC_INT_STATUS 0x60 #define REG_TSC_DEBUG_MODE 0x70 #define REG_TSC_DEBUG_MODE2 0x80 /* TSC configuration registers field define */ #define DETECT_4_WIRE_MODE (0x0 << 4) #define AUTO_MEASURE 0x1 #define MEASURE_SIGNAL 0x1 #define DETECT_SIGNAL (0x1 << 4) #define VALID_SIGNAL (0x1 << 8) #define MEASURE_INT_EN 0x1 #define MEASURE_SIG_EN 0x1 #define VALID_SIG_EN (0x1 << 8) #define DE_GLITCH_2 (0x2 << 29) #define START_SENSE (0x1 << 12) #define TSC_DISABLE (0x1 << 16) #define DETECT_MODE 0x2 struct imx6ul_tsc { struct device *dev; struct input_dev *input; void __iomem *tsc_regs; void __iomem *adc_regs; struct clk *tsc_clk; struct clk *adc_clk; struct gpio_desc *xnur_gpio; int measure_delay_time; int pre_charge_time; struct completion completion; }; /* * TSC module need ADC to get the measure value. So * before config TSC, we should initialize ADC module. */ static int imx6ul_adc_init(struct imx6ul_tsc *tsc) { int adc_hc = 0; int adc_gc; int adc_gs; int adc_cfg; int timeout; reinit_completion(&tsc->completion); adc_cfg = readl(tsc->adc_regs + REG_ADC_CFG); adc_cfg |= ADC_12BIT_MODE | ADC_IPG_CLK; adc_cfg |= ADC_CLK_DIV_8 | ADC_SHORT_SAMPLE_MODE; adc_cfg &= ~ADC_HARDWARE_TRIGGER; writel(adc_cfg, tsc->adc_regs + REG_ADC_CFG); /* enable calibration interrupt */ adc_hc |= ADC_AIEN; adc_hc |= ADC_CONV_DISABLE; writel(adc_hc, tsc->adc_regs + REG_ADC_HC0); /* start ADC calibration */ adc_gc = readl(tsc->adc_regs + REG_ADC_GC); adc_gc |= ADC_CAL; writel(adc_gc, tsc->adc_regs + REG_ADC_GC); timeout = wait_for_completion_timeout (&tsc->completion, ADC_TIMEOUT); if (timeout == 0) { dev_err(tsc->dev, "Timeout for adc calibration\n"); return -ETIMEDOUT; } adc_gs = readl(tsc->adc_regs + REG_ADC_GS); if (adc_gs & ADC_CALF) { dev_err(tsc->dev, "ADC calibration failed\n"); return -EINVAL; } /* TSC need the ADC work in hardware trigger */ adc_cfg = readl(tsc->adc_regs + REG_ADC_CFG); adc_cfg |= ADC_HARDWARE_TRIGGER; writel(adc_cfg, tsc->adc_regs + REG_ADC_CFG); return 0; } /* * This is a TSC workaround. Currently TSC misconnect two * ADC channels, this function remap channel configure for * hardware trigger. */ static void imx6ul_tsc_channel_config(struct imx6ul_tsc *tsc) { int adc_hc0, adc_hc1, adc_hc2, adc_hc3, adc_hc4; adc_hc0 = DISABLE_CONVERSION_INT; writel(adc_hc0, tsc->adc_regs + REG_ADC_HC0); adc_hc1 = DISABLE_CONVERSION_INT | SELECT_CHANNEL_4; writel(adc_hc1, tsc->adc_regs + REG_ADC_HC1); adc_hc2 = DISABLE_CONVERSION_INT; writel(adc_hc2, tsc->adc_regs + REG_ADC_HC2); adc_hc3 = DISABLE_CONVERSION_INT | SELECT_CHANNEL_1; writel(adc_hc3, tsc->adc_regs + REG_ADC_HC3); adc_hc4 = DISABLE_CONVERSION_INT; writel(adc_hc4, tsc->adc_regs + REG_ADC_HC4); } /* * TSC setting, confige the pre-charge time and measure delay time. * different touch screen may need different pre-charge time and * measure delay time. */ static void imx6ul_tsc_set(struct imx6ul_tsc *tsc) { int basic_setting = 0; int start; basic_setting |= tsc->measure_delay_time << 8; basic_setting |= DETECT_4_WIRE_MODE | AUTO_MEASURE; writel(basic_setting, tsc->tsc_regs + REG_TSC_BASIC_SETING); writel(DE_GLITCH_2, tsc->tsc_regs + REG_TSC_DEBUG_MODE2); writel(tsc->pre_charge_time, tsc->tsc_regs + REG_TSC_PRE_CHARGE_TIME); writel(MEASURE_INT_EN, tsc->tsc_regs + REG_TSC_INT_EN); writel(MEASURE_SIG_EN | VALID_SIG_EN, tsc->tsc_regs + REG_TSC_INT_SIG_EN); /* start sense detection */ start = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL); start |= START_SENSE; start &= ~TSC_DISABLE; writel(start, tsc->tsc_regs + REG_TSC_FLOW_CONTROL); } static int imx6ul_tsc_init(struct imx6ul_tsc *tsc) { int err; err = imx6ul_adc_init(tsc); if (err) return err; imx6ul_tsc_channel_config(tsc); imx6ul_tsc_set(tsc); return 0; } static void imx6ul_tsc_disable(struct imx6ul_tsc *tsc) { int tsc_flow; int adc_cfg; /* TSC controller enters to idle status */ tsc_flow = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL); tsc_flow |= TSC_DISABLE; writel(tsc_flow, tsc->tsc_regs + REG_TSC_FLOW_CONTROL); /* ADC controller enters to stop mode */ adc_cfg = readl(tsc->adc_regs + REG_ADC_HC0); adc_cfg |= ADC_CONV_DISABLE; writel(adc_cfg, tsc->adc_regs + REG_ADC_HC0); } /* Delay some time (max 2ms), wait the pre-charge done. */ static bool tsc_wait_detect_mode(struct imx6ul_tsc *tsc) { unsigned long timeout = jiffies + msecs_to_jiffies(2); int state_machine; int debug_mode2; do { if (time_after(jiffies, timeout)) return false; usleep_range(200, 400); debug_mode2 = readl(tsc->tsc_regs + REG_TSC_DEBUG_MODE2); state_machine = (debug_mode2 >> 20) & 0x7; } while (state_machine != DETECT_MODE); usleep_range(200, 400); return true; } static irqreturn_t tsc_irq_fn(int irq, void *dev_id) { struct imx6ul_tsc *tsc = dev_id; int status; int value; int x, y; int start; status = readl(tsc->tsc_regs + REG_TSC_INT_STATUS); /* write 1 to clear the bit measure-signal */ writel(MEASURE_SIGNAL | DETECT_SIGNAL, tsc->tsc_regs + REG_TSC_INT_STATUS);