1
0
Files
Greg Kroah-Hartman 49f4e44fac Merge 4.9.241 into android-4.9-q
Changes in 4.9.241
	ibmveth: Identify ingress large send packets.
	tipc: fix the skb_unshare() in tipc_buf_append()
	net/ipv4: always honour route mtu during forwarding
	r8169: fix data corruption issue on RTL8402
	ALSA: bebob: potential info leak in hwdep_read()
	net: hdlc: In hdlc_rcv, check to make sure dev is an HDLC device
	net: hdlc_raw_eth: Clear the IFF_TX_SKB_SHARING flag after calling ether_setup
	nfc: Ensure presence of NFC_ATTR_FIRMWARE_NAME attribute in nfc_genl_fw_download()
	tcp: fix to update snd_wl1 in bulk receiver fast path
	icmp: randomize the global rate limiter
	cifs: remove bogus debug code
	KVM: x86/mmu: Commit zap of remaining invalid pages when recovering lpages
	ima: Don't ignore errors from crypto_shash_update()
	crypto: algif_aead - Do not set MAY_BACKLOG on the async path
	EDAC/i5100: Fix error handling order in i5100_init_one()
	crypto: ixp4xx - Fix the size used in a 'dma_free_coherent()' call
	media: Revert "media: exynos4-is: Add missed check for pinctrl_lookup_state()"
	media: m5mols: Check function pointer in m5mols_sensor_power
	media: omap3isp: Fix memleak in isp_probe
	crypto: omap-sham - fix digcnt register handling with export/import
	media: tc358743: initialize variable
	media: platform: fcp: Fix a reference count leak.
	media: ti-vpe: Fix a missing check and reference count leak
	regulator: resolve supply after creating regulator
	ath10k: provide survey info as accumulated data
	ath6kl: prevent potential array overflow in ath6kl_add_new_sta()
	ath9k: Fix potential out of bounds in ath9k_htc_txcompletion_cb()
	wcn36xx: Fix reported 802.11n rx_highest rate wcn3660/wcn3680
	ASoC: qcom: lpass-platform: fix memory leak
	mwifiex: Do not use GFP_KERNEL in atomic context
	drm/gma500: fix error check
	scsi: qla4xxx: Fix an error handling path in 'qla4xxx_get_host_stats()'
	scsi: csiostor: Fix wrong return value in csio_hw_prep_fw()
	backlight: sky81452-backlight: Fix refcount imbalance on error
	VMCI: check return value of get_user_pages_fast() for errors
	tty: serial: earlycon dependency
	tty: hvcs: Don't NULL tty->driver_data until hvcs_cleanup()
	pty: do tty_flip_buffer_push without port->lock in pty_write
	drivers/virt/fsl_hypervisor: Fix error handling path
	video: fbdev: vga16fb: fix setting of pixclock because a pass-by-value error
	video: fbdev: sis: fix null ptr dereference
	HID: roccat: add bounds checking in kone_sysfs_write_settings()
	ath6kl: wmi: prevent a shift wrapping bug in ath6kl_wmi_delete_pstream_cmd()
	misc: mic: scif: Fix error handling path
	ALSA: seq: oss: Avoid mutex lock for a long-time ioctl
	quota: clear padding in v2r1_mem2diskdqb()
	net: enic: Cure the enic api locking trainwreck
	mfd: sm501: Fix leaks in probe()
	iwlwifi: mvm: split a print to avoid a WARNING in ROC
	usb: gadget: f_ncm: fix ncm_bitrate for SuperSpeed and above.
	usb: gadget: u_ether: enable qmult on SuperSpeed Plus as well
	nl80211: fix non-split wiphy information
	scsi: be2iscsi: Fix a theoretical leak in beiscsi_create_eqs()
	mwifiex: fix double free
	net: korina: fix kfree of rx/tx descriptor array
	IB/mlx4: Fix starvation in paravirt mux/demux
	IB/mlx4: Adjust delayed work when a dup is observed
	powerpc/pseries: Fix missing of_node_put() in rng_init()
	powerpc/icp-hv: Fix missing of_node_put() in success path
	mtd: lpddr: fix excessive stack usage with clang
	mtd: mtdoops: Don't write panic data twice
	ARM: 9007/1: l2c: fix prefetch bits init in L2X0_AUX_CTRL using DT values
	RDMA/qedr: Fix use of uninitialized field
	powerpc/tau: Use appropriate temperature sample interval
	powerpc/tau: Remove duplicated set_thresholds() call
	powerpc/tau: Disable TAU between measurements
	perf intel-pt: Fix "context_switch event has no tid" error
	RDMA/hns: Set the unsupported wr opcode
	kdb: Fix pager search for multi-line strings
	overflow: Include header file with SIZE_MAX declaration
	powerpc/perf: Exclude pmc5/6 from the irrelevant PMU group constraints
	powerpc/perf/hv-gpci: Fix starting index value
	cpufreq: powernv: Fix frame-size-overflow in powernv_cpufreq_reboot_notifier
	IB/rdmavt: Fix sizeof mismatch
	lib/crc32.c: fix trivial typo in preprocessor condition
	rapidio: fix error handling path
	rapidio: fix the missed put_device() for rio_mport_add_riodev
	clk: at91: clk-main: update key before writing AT91_CKGR_MOR
	clk: bcm2835: add missing release if devm_clk_hw_register fails
	vfio/pci: Clear token on bypass registration failure
	Input: imx6ul_tsc - clean up some errors in imx6ul_tsc_resume()
	Input: ep93xx_keypad - fix handling of platform_get_irq() error
	Input: omap4-keypad - fix handling of platform_get_irq() error
	Input: twl4030_keypad - fix handling of platform_get_irq() error
	Input: sun4i-ps2 - fix handling of platform_get_irq() error
	KVM: x86: emulating RDPID failure shall return #UD rather than #GP
	memory: omap-gpmc: Fix a couple off by ones
	memory: fsl-corenet-cf: Fix handling of platform_get_irq() error
	arm64: dts: qcom: msm8916: Fix MDP/DSI interrupts
	arm64: dts: zynqmp: Remove additional compatible string for i2c IPs
	powerpc/powernv/dump: Fix race while processing OPAL dump
	nvmet: fix uninitialized work for zero kato
	NTB: hw: amd: fix an issue about leak system resources
	crypto: ccp - fix error handling
	media: firewire: fix memory leak
	media: ati_remote: sanity check for both endpoints
	media: exynos4-is: Fix several reference count leaks due to pm_runtime_get_sync
	media: exynos4-is: Fix a reference count leak due to pm_runtime_get_sync
	media: exynos4-is: Fix a reference count leak
	media: vsp1: Fix runtime PM imbalance on error
	media: platform: s3c-camif: Fix runtime PM imbalance on error
	media: platform: sti: hva: Fix runtime PM imbalance on error
	media: bdisp: Fix runtime PM imbalance on error
	media: media/pci: prevent memory leak in bttv_probe
	media: uvcvideo: Ensure all probed info is returned to v4l2
	mmc: sdio: Check for CISTPL_VERS_1 buffer size
	media: saa7134: avoid a shift overflow
	fs: dlm: fix configfs memory leak
	ntfs: add check for mft record size in superblock
	PM: hibernate: remove the bogus call to get_gendisk() in software_resume()
	scsi: mvumi: Fix error return in mvumi_io_attach()
	scsi: target: core: Add CONTROL field for trace events
	mic: vop: copy data to kernel space then write to io memory
	misc: vop: add round_up(x,4) for vring_size to avoid kernel panic
	usb: gadget: function: printer: fix use-after-free in __lock_acquire
	udf: Limit sparing table size
	udf: Avoid accessing uninitialized data on failed inode read
	USB: cdc-acm: handle broken union descriptors
	ath9k: hif_usb: fix race condition between usb_get_urb() and usb_kill_anchored_urbs()
	misc: rtsx: Fix memory leak in rtsx_pci_probe
	reiserfs: only call unlock_new_inode() if I_NEW
	xfs: make sure the rt allocator doesn't run off the end
	usb: ohci: Default to per-port over-current protection
	Bluetooth: Only mark socket zapped after unlocking
	scsi: ibmvfc: Fix error return in ibmvfc_probe()
	brcmsmac: fix memory leak in wlc_phy_attach_lcnphy
	rtl8xxxu: prevent potential memory leak
	Fix use after free in get_capset_info callback.
	tty: ipwireless: fix error handling
	ipvs: Fix uninit-value in do_ip_vs_set_ctl()
	reiserfs: Fix memory leak in reiserfs_parse_options()
	brcm80211: fix possible memleak in brcmf_proto_msgbuf_attach
	usb: core: Solve race condition in anchor cleanup functions
	ath10k: check idx validity in __ath10k_htt_rx_ring_fill_n()
	net: korina: cast KSEG0 address to pointer in kfree
	usb: cdc-acm: add quirk to blacklist ETAS ES58X devices
	USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
	eeprom: at25: set minimum read/write access stride to 1
	usb: gadget: f_ncm: allow using NCM in SuperSpeed Plus gadgets.
	Linux 4.9.241

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Change-Id: Ie59605b312e5d0314299cad46ab57df803070564
2020-11-10 11:35:46 +03:00

539 lines
13 KiB
C

/*
* 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 <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
/* 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);
/* It's a HW self-clean bit. Set this bit and start sense detection */
start = readl(tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
start |= START_SENSE;
writel(start, tsc->tsc_regs + REG_TSC_FLOW_CONTROL);
if (status & MEASURE_SIGNAL) {
value = readl(tsc->tsc_regs + REG_TSC_MEASURE_VALUE);
x = (value >> 16) & 0x0fff;
y = value & 0x0fff;
/*
* In detect mode, we can get the xnur gpio value,
* otherwise assume contact is stiull active.
*/
if (!tsc_wait_detect_mode(tsc) ||
gpiod_get_value_cansleep(tsc->xnur_gpio)) {
input_report_key(tsc->input, BTN_TOUCH, 1);
input_report_abs(tsc->input, ABS_X, x);
input_report_abs(tsc->input, ABS_Y, y);
} else {
input_report_key(tsc->input, BTN_TOUCH, 0);
}
input_sync(tsc->input);
}
return IRQ_HANDLED;
}
static irqreturn_t adc_irq_fn(int irq, void *dev_id)
{
struct imx6ul_tsc *tsc = dev_id;
int coco;
int value;
coco = readl(tsc->adc_regs + REG_ADC_HS);
if (coco & 0x01) {
value = readl(tsc->adc_regs + REG_ADC_R0);
complete(&tsc->completion);
}
return IRQ_HANDLED;
}
static int imx6ul_tsc_open(struct input_dev *input_dev)
{
struct imx6ul_tsc *tsc = input_get_drvdata(input_dev);
int err;
err = clk_prepare_enable(tsc->adc_clk);
if (err) {
dev_err(tsc->dev,
"Could not prepare or enable the adc clock: %d\n",
err);
return err;
}
err = clk_prepare_enable(tsc->tsc_clk);
if (err) {
dev_err(tsc->dev,
"Could not prepare or enable the tsc clock: %d\n",
err);
clk_disable_unprepare(tsc->adc_clk);
return err;
}
return imx6ul_tsc_init(tsc);
}
static void imx6ul_tsc_close(struct input_dev *input_dev)
{
struct imx6ul_tsc *tsc = input_get_drvdata(input_dev);
imx6ul_tsc_disable(tsc);
clk_disable_unprepare(tsc->tsc_clk);
clk_disable_unprepare(tsc->adc_clk);
}
static int imx6ul_tsc_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct imx6ul_tsc *tsc;
struct input_dev *input_dev;
struct resource *tsc_mem;
struct resource *adc_mem;
int err;
int tsc_irq;
int adc_irq;
tsc = devm_kzalloc(&pdev->dev, sizeof(*tsc), GFP_KERNEL);
if (!tsc)
return -ENOMEM;
input_dev = devm_input_allocate_device(&pdev->dev);
if (!input_dev)
return -ENOMEM;
input_dev->name = "iMX6UL Touchscreen Controller";
input_dev->id.bustype = BUS_HOST;
input_dev->open = imx6ul_tsc_open;
input_dev->close = imx6ul_tsc_close;
input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, 0, 0xFFF, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 0xFFF, 0, 0);
input_set_drvdata(input_dev, tsc);
tsc->dev = &pdev->dev;
tsc->input = input_dev;
init_completion(&tsc->completion);
tsc->xnur_gpio = devm_gpiod_get(&pdev->dev, "xnur", GPIOD_IN);
if (IS_ERR(tsc->xnur_gpio)) {
err = PTR_ERR(tsc->xnur_gpio);
dev_err(&pdev->dev,
"failed to request GPIO tsc_X- (xnur): %d\n", err);
return err;
}
tsc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tsc->tsc_regs = devm_ioremap_resource(&pdev->dev, tsc_mem);
if (IS_ERR(tsc->tsc_regs)) {
err = PTR_ERR(tsc->tsc_regs);
dev_err(&pdev->dev, "failed to remap tsc memory: %d\n", err);
return err;
}
adc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
tsc->adc_regs = devm_ioremap_resource(&pdev->dev, adc_mem);
if (IS_ERR(tsc->adc_regs)) {
err = PTR_ERR(tsc->adc_regs);
dev_err(&pdev->dev, "failed to remap adc memory: %d\n", err);
return err;
}
tsc->tsc_clk = devm_clk_get(&pdev->dev, "tsc");
if (IS_ERR(tsc->tsc_clk)) {
err = PTR_ERR(tsc->tsc_clk);
dev_err(&pdev->dev, "failed getting tsc clock: %d\n", err);
return err;
}
tsc->adc_clk = devm_clk_get(&pdev->dev, "adc");
if (IS_ERR(tsc->adc_clk)) {
err = PTR_ERR(tsc->adc_clk);
dev_err(&pdev->dev, "failed getting adc clock: %d\n", err);
return err;
}
tsc_irq = platform_get_irq(pdev, 0);
if (tsc_irq < 0) {
dev_err(&pdev->dev, "no tsc irq resource?\n");
return tsc_irq;
}
adc_irq = platform_get_irq(pdev, 1);
if (adc_irq < 0) {
dev_err(&pdev->dev, "no adc irq resource?\n");
return adc_irq;
}
err = devm_request_threaded_irq(tsc->dev, tsc_irq,
NULL, tsc_irq_fn, IRQF_ONESHOT,
dev_name(&pdev->dev), tsc);
if (err) {
dev_err(&pdev->dev,
"failed requesting tsc irq %d: %d\n",
tsc_irq, err);
return err;
}
err = devm_request_irq(tsc->dev, adc_irq, adc_irq_fn, 0,
dev_name(&pdev->dev), tsc);
if (err) {
dev_err(&pdev->dev,
"failed requesting adc irq %d: %d\n",
adc_irq, err);
return err;
}
err = of_property_read_u32(np, "measure-delay-time",
&tsc->measure_delay_time);
if (err)
tsc->measure_delay_time = 0xffff;
err = of_property_read_u32(np, "pre-charge-time",
&tsc->pre_charge_time);
if (err)
tsc->pre_charge_time = 0xfff;
err = input_register_device(tsc->input);
if (err) {
dev_err(&pdev->dev,
"failed to register input device: %d\n", err);
return err;
}
platform_set_drvdata(pdev, tsc);
return 0;
}
static int __maybe_unused imx6ul_tsc_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct imx6ul_tsc *tsc = platform_get_drvdata(pdev);
struct input_dev *input_dev = tsc->input;
mutex_lock(&input_dev->mutex);
if (input_dev->users) {
imx6ul_tsc_disable(tsc);
clk_disable_unprepare(tsc->tsc_clk);
clk_disable_unprepare(tsc->adc_clk);
}
mutex_unlock(&input_dev->mutex);
return 0;
}
static int __maybe_unused imx6ul_tsc_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct imx6ul_tsc *tsc = platform_get_drvdata(pdev);
struct input_dev *input_dev = tsc->input;
int retval = 0;
mutex_lock(&input_dev->mutex);
if (!input_dev->users)
goto out;
retval = clk_prepare_enable(tsc->adc_clk);
if (retval)
goto out;
retval = clk_prepare_enable(tsc->tsc_clk);
if (retval) {
clk_disable_unprepare(tsc->adc_clk);
goto out;
}
retval = imx6ul_tsc_init(tsc);
if (retval) {
clk_disable_unprepare(tsc->tsc_clk);
clk_disable_unprepare(tsc->adc_clk);
goto out;
}
out:
mutex_unlock(&input_dev->mutex);
return retval;
}
static SIMPLE_DEV_PM_OPS(imx6ul_tsc_pm_ops,
imx6ul_tsc_suspend, imx6ul_tsc_resume);
static const struct of_device_id imx6ul_tsc_match[] = {
{ .compatible = "fsl,imx6ul-tsc", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx6ul_tsc_match);
static struct platform_driver imx6ul_tsc_driver = {
.driver = {
.name = "imx6ul-tsc",
.of_match_table = imx6ul_tsc_match,
.pm = &imx6ul_tsc_pm_ops,
},
.probe = imx6ul_tsc_probe,
};
module_platform_driver(imx6ul_tsc_driver);
MODULE_AUTHOR("Haibo Chen <haibo.chen@freescale.com>");
MODULE_DESCRIPTION("Freescale i.MX6UL Touchscreen controller driver");
MODULE_LICENSE("GPL v2");