18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * wm97xx-core.c -- Touch screen driver core for Wolfson WM9705, WM9712 48c2ecf20Sopenharmony_ci * and WM9713 AC97 Codecs. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC. 78c2ecf20Sopenharmony_ci * Author: Liam Girdwood <lrg@slimlogic.co.uk> 88c2ecf20Sopenharmony_ci * Parts Copyright : Ian Molton <spyro@f2s.com> 98c2ecf20Sopenharmony_ci * Andrew Zabolotny <zap@homelink.ru> 108c2ecf20Sopenharmony_ci * Russell King <rmk@arm.linux.org.uk> 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * Notes: 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * Features: 158c2ecf20Sopenharmony_ci * - supports WM9705, WM9712, WM9713 168c2ecf20Sopenharmony_ci * - polling mode 178c2ecf20Sopenharmony_ci * - continuous mode (arch-dependent) 188c2ecf20Sopenharmony_ci * - adjustable rpu/dpp settings 198c2ecf20Sopenharmony_ci * - adjustable pressure current 208c2ecf20Sopenharmony_ci * - adjustable sample settle delay 218c2ecf20Sopenharmony_ci * - 4 and 5 wire touchscreens (5 wire is WM9712 only) 228c2ecf20Sopenharmony_ci * - pen down detection 238c2ecf20Sopenharmony_ci * - battery monitor 248c2ecf20Sopenharmony_ci * - sample AUX adcs 258c2ecf20Sopenharmony_ci * - power management 268c2ecf20Sopenharmony_ci * - codec GPIO 278c2ecf20Sopenharmony_ci * - codec event notification 288c2ecf20Sopenharmony_ci * Todo 298c2ecf20Sopenharmony_ci * - Support for async sampling control for noisy LCDs. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#include <linux/module.h> 338c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 348c2ecf20Sopenharmony_ci#include <linux/kernel.h> 358c2ecf20Sopenharmony_ci#include <linux/init.h> 368c2ecf20Sopenharmony_ci#include <linux/delay.h> 378c2ecf20Sopenharmony_ci#include <linux/string.h> 388c2ecf20Sopenharmony_ci#include <linux/proc_fs.h> 398c2ecf20Sopenharmony_ci#include <linux/pm.h> 408c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 418c2ecf20Sopenharmony_ci#include <linux/bitops.h> 428c2ecf20Sopenharmony_ci#include <linux/mfd/wm97xx.h> 438c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 448c2ecf20Sopenharmony_ci#include <linux/wm97xx.h> 458c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 468c2ecf20Sopenharmony_ci#include <linux/io.h> 478c2ecf20Sopenharmony_ci#include <linux/slab.h> 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define TS_NAME "wm97xx" 508c2ecf20Sopenharmony_ci#define WM_CORE_VERSION "1.00" 518c2ecf20Sopenharmony_ci#define DEFAULT_PRESSURE 0xb0c0 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* 558c2ecf20Sopenharmony_ci * Touchscreen absolute values 568c2ecf20Sopenharmony_ci * 578c2ecf20Sopenharmony_ci * These parameters are used to help the input layer discard out of 588c2ecf20Sopenharmony_ci * range readings and reduce jitter etc. 598c2ecf20Sopenharmony_ci * 608c2ecf20Sopenharmony_ci * o min, max:- indicate the min and max values your touch screen returns 618c2ecf20Sopenharmony_ci * o fuzz:- use a higher number to reduce jitter 628c2ecf20Sopenharmony_ci * 638c2ecf20Sopenharmony_ci * The default values correspond to Mainstone II in QVGA mode 648c2ecf20Sopenharmony_ci * 658c2ecf20Sopenharmony_ci * Please read 668c2ecf20Sopenharmony_ci * Documentation/input/input-programming.rst for more details. 678c2ecf20Sopenharmony_ci */ 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int abs_x[3] = {150, 4000, 5}; 708c2ecf20Sopenharmony_cimodule_param_array(abs_x, int, NULL, 0); 718c2ecf20Sopenharmony_ciMODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz"); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int abs_y[3] = {200, 4000, 40}; 748c2ecf20Sopenharmony_cimodule_param_array(abs_y, int, NULL, 0); 758c2ecf20Sopenharmony_ciMODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz"); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int abs_p[3] = {0, 150, 4}; 788c2ecf20Sopenharmony_cimodule_param_array(abs_p, int, NULL, 0); 798c2ecf20Sopenharmony_ciMODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max, fuzz"); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci/* 828c2ecf20Sopenharmony_ci * wm97xx IO access, all IO locking done by AC97 layer 838c2ecf20Sopenharmony_ci */ 848c2ecf20Sopenharmony_ciint wm97xx_reg_read(struct wm97xx *wm, u16 reg) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci if (wm->ac97) 878c2ecf20Sopenharmony_ci return wm->ac97->bus->ops->read(wm->ac97, reg); 888c2ecf20Sopenharmony_ci else 898c2ecf20Sopenharmony_ci return -1; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_reg_read); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_civoid wm97xx_reg_write(struct wm97xx *wm, u16 reg, u16 val) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci /* cache digitiser registers */ 968c2ecf20Sopenharmony_ci if (reg >= AC97_WM9713_DIG1 && reg <= AC97_WM9713_DIG3) 978c2ecf20Sopenharmony_ci wm->dig[(reg - AC97_WM9713_DIG1) >> 1] = val; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci /* cache gpio regs */ 1008c2ecf20Sopenharmony_ci if (reg >= AC97_GPIO_CFG && reg <= AC97_MISC_AFE) 1018c2ecf20Sopenharmony_ci wm->gpio[(reg - AC97_GPIO_CFG) >> 1] = val; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci /* wm9713 irq reg */ 1048c2ecf20Sopenharmony_ci if (reg == 0x5a) 1058c2ecf20Sopenharmony_ci wm->misc = val; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (wm->ac97) 1088c2ecf20Sopenharmony_ci wm->ac97->bus->ops->write(wm->ac97, reg, val); 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_reg_write); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci/** 1138c2ecf20Sopenharmony_ci * wm97xx_read_aux_adc - Read the aux adc. 1148c2ecf20Sopenharmony_ci * @wm: wm97xx device. 1158c2ecf20Sopenharmony_ci * @adcsel: codec ADC to be read 1168c2ecf20Sopenharmony_ci * 1178c2ecf20Sopenharmony_ci * Reads the selected AUX ADC. 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ciint wm97xx_read_aux_adc(struct wm97xx *wm, u16 adcsel) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci int power_adc = 0, auxval; 1238c2ecf20Sopenharmony_ci u16 power = 0; 1248c2ecf20Sopenharmony_ci int rc = 0; 1258c2ecf20Sopenharmony_ci int timeout = 0; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* get codec */ 1288c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* When the touchscreen is not in use, we may have to power up 1318c2ecf20Sopenharmony_ci * the AUX ADC before we can use sample the AUX inputs-> 1328c2ecf20Sopenharmony_ci */ 1338c2ecf20Sopenharmony_ci if (wm->id == WM9713_ID2 && 1348c2ecf20Sopenharmony_ci (power = wm97xx_reg_read(wm, AC97_EXTENDED_MID)) & 0x8000) { 1358c2ecf20Sopenharmony_ci power_adc = 1; 1368c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_EXTENDED_MID, power & 0x7fff); 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* Prepare the codec for AUX reading */ 1408c2ecf20Sopenharmony_ci wm->codec->aux_prepare(wm); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* Turn polling mode on to read AUX ADC */ 1438c2ecf20Sopenharmony_ci wm->pen_probably_down = 1; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci while (rc != RC_VALID && timeout++ < 5) 1468c2ecf20Sopenharmony_ci rc = wm->codec->poll_sample(wm, adcsel, &auxval); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci if (power_adc) 1498c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_EXTENDED_MID, power | 0x8000); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci wm->codec->dig_restore(wm); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci wm->pen_probably_down = 0; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (timeout >= 5) { 1568c2ecf20Sopenharmony_ci dev_err(wm->dev, 1578c2ecf20Sopenharmony_ci "timeout reading auxadc %d, disabling digitiser\n", 1588c2ecf20Sopenharmony_ci adcsel); 1598c2ecf20Sopenharmony_ci wm->codec->dig_enable(wm, false); 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 1638c2ecf20Sopenharmony_ci return (rc == RC_VALID ? auxval & 0xfff : -EBUSY); 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_read_aux_adc); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci/** 1688c2ecf20Sopenharmony_ci * wm97xx_get_gpio - Get the status of a codec GPIO. 1698c2ecf20Sopenharmony_ci * @wm: wm97xx device. 1708c2ecf20Sopenharmony_ci * @gpio: gpio 1718c2ecf20Sopenharmony_ci * 1728c2ecf20Sopenharmony_ci * Get the status of a codec GPIO pin 1738c2ecf20Sopenharmony_ci */ 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cienum wm97xx_gpio_status wm97xx_get_gpio(struct wm97xx *wm, u32 gpio) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci u16 status; 1788c2ecf20Sopenharmony_ci enum wm97xx_gpio_status ret; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 1818c2ecf20Sopenharmony_ci status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci if (status & gpio) 1848c2ecf20Sopenharmony_ci ret = WM97XX_GPIO_HIGH; 1858c2ecf20Sopenharmony_ci else 1868c2ecf20Sopenharmony_ci ret = WM97XX_GPIO_LOW; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 1898c2ecf20Sopenharmony_ci return ret; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_get_gpio); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci/** 1948c2ecf20Sopenharmony_ci * wm97xx_set_gpio - Set the status of a codec GPIO. 1958c2ecf20Sopenharmony_ci * @wm: wm97xx device. 1968c2ecf20Sopenharmony_ci * @gpio: gpio 1978c2ecf20Sopenharmony_ci * 1988c2ecf20Sopenharmony_ci * 1998c2ecf20Sopenharmony_ci * Set the status of a codec GPIO pin 2008c2ecf20Sopenharmony_ci */ 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_civoid wm97xx_set_gpio(struct wm97xx *wm, u32 gpio, 2038c2ecf20Sopenharmony_ci enum wm97xx_gpio_status status) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci u16 reg; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 2088c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_GPIO_STATUS); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci if (status == WM97XX_GPIO_HIGH) 2118c2ecf20Sopenharmony_ci reg |= gpio; 2128c2ecf20Sopenharmony_ci else 2138c2ecf20Sopenharmony_ci reg &= ~gpio; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci if (wm->id == WM9712_ID2 && wm->variant != WM97xx_WM1613) 2168c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg << 1); 2178c2ecf20Sopenharmony_ci else 2188c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg); 2198c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_set_gpio); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci/* 2248c2ecf20Sopenharmony_ci * Codec GPIO pin configuration, this sets pin direction, polarity, 2258c2ecf20Sopenharmony_ci * stickyness and wake up. 2268c2ecf20Sopenharmony_ci */ 2278c2ecf20Sopenharmony_civoid wm97xx_config_gpio(struct wm97xx *wm, u32 gpio, enum wm97xx_gpio_dir dir, 2288c2ecf20Sopenharmony_ci enum wm97xx_gpio_pol pol, enum wm97xx_gpio_sticky sticky, 2298c2ecf20Sopenharmony_ci enum wm97xx_gpio_wake wake) 2308c2ecf20Sopenharmony_ci{ 2318c2ecf20Sopenharmony_ci u16 reg; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 2348c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci if (pol == WM97XX_GPIO_POL_HIGH) 2378c2ecf20Sopenharmony_ci reg |= gpio; 2388c2ecf20Sopenharmony_ci else 2398c2ecf20Sopenharmony_ci reg &= ~gpio; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_POLARITY, reg); 2428c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_GPIO_STICKY); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci if (sticky == WM97XX_GPIO_STICKY) 2458c2ecf20Sopenharmony_ci reg |= gpio; 2468c2ecf20Sopenharmony_ci else 2478c2ecf20Sopenharmony_ci reg &= ~gpio; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_STICKY, reg); 2508c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (wake == WM97XX_GPIO_WAKE) 2538c2ecf20Sopenharmony_ci reg |= gpio; 2548c2ecf20Sopenharmony_ci else 2558c2ecf20Sopenharmony_ci reg &= ~gpio; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, reg); 2588c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_GPIO_CFG); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci if (dir == WM97XX_GPIO_IN) 2618c2ecf20Sopenharmony_ci reg |= gpio; 2628c2ecf20Sopenharmony_ci else 2638c2ecf20Sopenharmony_ci reg &= ~gpio; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_CFG, reg); 2668c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_config_gpio); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci/* 2718c2ecf20Sopenharmony_ci * Configure the WM97XX_PRP value to use while system is suspended. 2728c2ecf20Sopenharmony_ci * If a value other than 0 is set then WM97xx pen detection will be 2738c2ecf20Sopenharmony_ci * left enabled in the configured mode while the system is in suspend, 2748c2ecf20Sopenharmony_ci * the device has users and suspend has not been disabled via the 2758c2ecf20Sopenharmony_ci * wakeup sysfs entries. 2768c2ecf20Sopenharmony_ci * 2778c2ecf20Sopenharmony_ci * @wm: WM97xx device to configure 2788c2ecf20Sopenharmony_ci * @mode: WM97XX_PRP value to configure while suspended 2798c2ecf20Sopenharmony_ci */ 2808c2ecf20Sopenharmony_civoid wm97xx_set_suspend_mode(struct wm97xx *wm, u16 mode) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci wm->suspend_mode = mode; 2838c2ecf20Sopenharmony_ci device_init_wakeup(&wm->input_dev->dev, mode != 0); 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_set_suspend_mode); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci/* 2888c2ecf20Sopenharmony_ci * Handle a pen down interrupt. 2898c2ecf20Sopenharmony_ci */ 2908c2ecf20Sopenharmony_cistatic void wm97xx_pen_irq_worker(struct work_struct *work) 2918c2ecf20Sopenharmony_ci{ 2928c2ecf20Sopenharmony_ci struct wm97xx *wm = container_of(work, struct wm97xx, pen_event_work); 2938c2ecf20Sopenharmony_ci int pen_was_down = wm->pen_is_down; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci /* do we need to enable the touch panel reader */ 2968c2ecf20Sopenharmony_ci if (wm->id == WM9705_ID2) { 2978c2ecf20Sopenharmony_ci if (wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD) & 2988c2ecf20Sopenharmony_ci WM97XX_PEN_DOWN) 2998c2ecf20Sopenharmony_ci wm->pen_is_down = 1; 3008c2ecf20Sopenharmony_ci else 3018c2ecf20Sopenharmony_ci wm->pen_is_down = 0; 3028c2ecf20Sopenharmony_ci } else { 3038c2ecf20Sopenharmony_ci u16 status, pol; 3048c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 3058c2ecf20Sopenharmony_ci status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); 3068c2ecf20Sopenharmony_ci pol = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci if (WM97XX_GPIO_13 & pol & status) { 3098c2ecf20Sopenharmony_ci wm->pen_is_down = 1; 3108c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol & 3118c2ecf20Sopenharmony_ci ~WM97XX_GPIO_13); 3128c2ecf20Sopenharmony_ci } else { 3138c2ecf20Sopenharmony_ci wm->pen_is_down = 0; 3148c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol | 3158c2ecf20Sopenharmony_ci WM97XX_GPIO_13); 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci if (wm->id == WM9712_ID2 && wm->variant != WM97xx_WM1613) 3198c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_STATUS, (status & 3208c2ecf20Sopenharmony_ci ~WM97XX_GPIO_13) << 1); 3218c2ecf20Sopenharmony_ci else 3228c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_STATUS, status & 3238c2ecf20Sopenharmony_ci ~WM97XX_GPIO_13); 3248c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 3258c2ecf20Sopenharmony_ci } 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci /* If the system is not using continuous mode or it provides a 3288c2ecf20Sopenharmony_ci * pen down operation then we need to schedule polls while the 3298c2ecf20Sopenharmony_ci * pen is down. Otherwise the machine driver is responsible 3308c2ecf20Sopenharmony_ci * for scheduling reads. 3318c2ecf20Sopenharmony_ci */ 3328c2ecf20Sopenharmony_ci if (!wm->mach_ops->acc_enabled || wm->mach_ops->acc_pen_down) { 3338c2ecf20Sopenharmony_ci if (wm->pen_is_down && !pen_was_down) { 3348c2ecf20Sopenharmony_ci /* Data is not available immediately on pen down */ 3358c2ecf20Sopenharmony_ci queue_delayed_work(wm->ts_workq, &wm->ts_reader, 1); 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci /* Let ts_reader report the pen up for debounce. */ 3398c2ecf20Sopenharmony_ci if (!wm->pen_is_down && pen_was_down) 3408c2ecf20Sopenharmony_ci wm->pen_is_down = 1; 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci if (!wm->pen_is_down && wm->mach_ops->acc_enabled) 3448c2ecf20Sopenharmony_ci wm->mach_ops->acc_pen_up(wm); 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci wm->mach_ops->irq_enable(wm, 1); 3478c2ecf20Sopenharmony_ci} 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci/* 3508c2ecf20Sopenharmony_ci * Codec PENDOWN irq handler 3518c2ecf20Sopenharmony_ci * 3528c2ecf20Sopenharmony_ci * We have to disable the codec interrupt in the handler because it 3538c2ecf20Sopenharmony_ci * can take up to 1ms to clear the interrupt source. We schedule a task 3548c2ecf20Sopenharmony_ci * in a work queue to do the actual interaction with the chip. The 3558c2ecf20Sopenharmony_ci * interrupt is then enabled again in the slow handler when the source 3568c2ecf20Sopenharmony_ci * has been cleared. 3578c2ecf20Sopenharmony_ci */ 3588c2ecf20Sopenharmony_cistatic irqreturn_t wm97xx_pen_interrupt(int irq, void *dev_id) 3598c2ecf20Sopenharmony_ci{ 3608c2ecf20Sopenharmony_ci struct wm97xx *wm = dev_id; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci if (!work_pending(&wm->pen_event_work)) { 3638c2ecf20Sopenharmony_ci wm->mach_ops->irq_enable(wm, 0); 3648c2ecf20Sopenharmony_ci queue_work(wm->ts_workq, &wm->pen_event_work); 3658c2ecf20Sopenharmony_ci } 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3688c2ecf20Sopenharmony_ci} 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci/* 3718c2ecf20Sopenharmony_ci * initialise pen IRQ handler and workqueue 3728c2ecf20Sopenharmony_ci */ 3738c2ecf20Sopenharmony_cistatic int wm97xx_init_pen_irq(struct wm97xx *wm) 3748c2ecf20Sopenharmony_ci{ 3758c2ecf20Sopenharmony_ci u16 reg; 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci /* If an interrupt is supplied an IRQ enable operation must also be 3788c2ecf20Sopenharmony_ci * provided. */ 3798c2ecf20Sopenharmony_ci BUG_ON(!wm->mach_ops->irq_enable); 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci if (request_irq(wm->pen_irq, wm97xx_pen_interrupt, IRQF_SHARED, 3828c2ecf20Sopenharmony_ci "wm97xx-pen", wm)) { 3838c2ecf20Sopenharmony_ci dev_err(wm->dev, 3848c2ecf20Sopenharmony_ci "Failed to register pen down interrupt, polling"); 3858c2ecf20Sopenharmony_ci wm->pen_irq = 0; 3868c2ecf20Sopenharmony_ci return -EINVAL; 3878c2ecf20Sopenharmony_ci } 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci /* Configure GPIO as interrupt source on WM971x */ 3908c2ecf20Sopenharmony_ci if (wm->id != WM9705_ID2) { 3918c2ecf20Sopenharmony_ci BUG_ON(!wm->mach_ops->irq_gpio); 3928c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_MISC_AFE); 3938c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_MISC_AFE, 3948c2ecf20Sopenharmony_ci reg & ~(wm->mach_ops->irq_gpio)); 3958c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, 0x5a); 3968c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, 0x5a, reg & ~0x0001); 3978c2ecf20Sopenharmony_ci } 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci return 0; 4008c2ecf20Sopenharmony_ci} 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_cistatic int wm97xx_read_samples(struct wm97xx *wm) 4038c2ecf20Sopenharmony_ci{ 4048c2ecf20Sopenharmony_ci struct wm97xx_data data; 4058c2ecf20Sopenharmony_ci int rc; 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci if (wm->mach_ops && wm->mach_ops->acc_enabled) 4108c2ecf20Sopenharmony_ci rc = wm->mach_ops->acc_pen_down(wm); 4118c2ecf20Sopenharmony_ci else 4128c2ecf20Sopenharmony_ci rc = wm->codec->poll_touch(wm, &data); 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci if (rc & RC_PENUP) { 4158c2ecf20Sopenharmony_ci if (wm->pen_is_down) { 4168c2ecf20Sopenharmony_ci wm->pen_is_down = 0; 4178c2ecf20Sopenharmony_ci dev_dbg(wm->dev, "pen up\n"); 4188c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_PRESSURE, 0); 4198c2ecf20Sopenharmony_ci input_report_key(wm->input_dev, BTN_TOUCH, 0); 4208c2ecf20Sopenharmony_ci input_sync(wm->input_dev); 4218c2ecf20Sopenharmony_ci } else if (!(rc & RC_AGAIN)) { 4228c2ecf20Sopenharmony_ci /* We need high frequency updates only while 4238c2ecf20Sopenharmony_ci * pen is down, the user never will be able to 4248c2ecf20Sopenharmony_ci * touch screen faster than a few times per 4258c2ecf20Sopenharmony_ci * second... On the other hand, when the user 4268c2ecf20Sopenharmony_ci * is actively working with the touchscreen we 4278c2ecf20Sopenharmony_ci * don't want to lose the quick response. So we 4288c2ecf20Sopenharmony_ci * will slowly increase sleep time after the 4298c2ecf20Sopenharmony_ci * pen is up and quicky restore it to ~one task 4308c2ecf20Sopenharmony_ci * switch when pen is down again. 4318c2ecf20Sopenharmony_ci */ 4328c2ecf20Sopenharmony_ci if (wm->ts_reader_interval < HZ / 10) 4338c2ecf20Sopenharmony_ci wm->ts_reader_interval++; 4348c2ecf20Sopenharmony_ci } 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci } else if (rc & RC_VALID) { 4378c2ecf20Sopenharmony_ci dev_dbg(wm->dev, 4388c2ecf20Sopenharmony_ci "pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n", 4398c2ecf20Sopenharmony_ci data.x >> 12, data.x & 0xfff, data.y >> 12, 4408c2ecf20Sopenharmony_ci data.y & 0xfff, data.p >> 12, data.p & 0xfff); 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci if (abs_x[0] > (data.x & 0xfff) || 4438c2ecf20Sopenharmony_ci abs_x[1] < (data.x & 0xfff) || 4448c2ecf20Sopenharmony_ci abs_y[0] > (data.y & 0xfff) || 4458c2ecf20Sopenharmony_ci abs_y[1] < (data.y & 0xfff)) { 4468c2ecf20Sopenharmony_ci dev_dbg(wm->dev, "Measurement out of range, dropping it\n"); 4478c2ecf20Sopenharmony_ci rc = RC_AGAIN; 4488c2ecf20Sopenharmony_ci goto out; 4498c2ecf20Sopenharmony_ci } 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff); 4528c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff); 4538c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff); 4548c2ecf20Sopenharmony_ci input_report_key(wm->input_dev, BTN_TOUCH, 1); 4558c2ecf20Sopenharmony_ci input_sync(wm->input_dev); 4568c2ecf20Sopenharmony_ci wm->pen_is_down = 1; 4578c2ecf20Sopenharmony_ci wm->ts_reader_interval = wm->ts_reader_min_interval; 4588c2ecf20Sopenharmony_ci } else if (rc & RC_PENDOWN) { 4598c2ecf20Sopenharmony_ci dev_dbg(wm->dev, "pen down\n"); 4608c2ecf20Sopenharmony_ci wm->pen_is_down = 1; 4618c2ecf20Sopenharmony_ci wm->ts_reader_interval = wm->ts_reader_min_interval; 4628c2ecf20Sopenharmony_ci } 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ciout: 4658c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 4668c2ecf20Sopenharmony_ci return rc; 4678c2ecf20Sopenharmony_ci} 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci/* 4708c2ecf20Sopenharmony_ci* The touchscreen sample reader. 4718c2ecf20Sopenharmony_ci*/ 4728c2ecf20Sopenharmony_cistatic void wm97xx_ts_reader(struct work_struct *work) 4738c2ecf20Sopenharmony_ci{ 4748c2ecf20Sopenharmony_ci int rc; 4758c2ecf20Sopenharmony_ci struct wm97xx *wm = container_of(work, struct wm97xx, ts_reader.work); 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci BUG_ON(!wm->codec); 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci do { 4808c2ecf20Sopenharmony_ci rc = wm97xx_read_samples(wm); 4818c2ecf20Sopenharmony_ci } while (rc & RC_AGAIN); 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci if (wm->pen_is_down || !wm->pen_irq) 4848c2ecf20Sopenharmony_ci queue_delayed_work(wm->ts_workq, &wm->ts_reader, 4858c2ecf20Sopenharmony_ci wm->ts_reader_interval); 4868c2ecf20Sopenharmony_ci} 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci/** 4898c2ecf20Sopenharmony_ci * wm97xx_ts_input_open - Open the touch screen input device. 4908c2ecf20Sopenharmony_ci * @idev: Input device to be opened. 4918c2ecf20Sopenharmony_ci * 4928c2ecf20Sopenharmony_ci * Called by the input sub system to open a wm97xx touchscreen device. 4938c2ecf20Sopenharmony_ci * Starts the touchscreen thread and touch digitiser. 4948c2ecf20Sopenharmony_ci */ 4958c2ecf20Sopenharmony_cistatic int wm97xx_ts_input_open(struct input_dev *idev) 4968c2ecf20Sopenharmony_ci{ 4978c2ecf20Sopenharmony_ci struct wm97xx *wm = input_get_drvdata(idev); 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci wm->ts_workq = alloc_ordered_workqueue("kwm97xx", 0); 5008c2ecf20Sopenharmony_ci if (wm->ts_workq == NULL) { 5018c2ecf20Sopenharmony_ci dev_err(wm->dev, 5028c2ecf20Sopenharmony_ci "Failed to create workqueue\n"); 5038c2ecf20Sopenharmony_ci return -EINVAL; 5048c2ecf20Sopenharmony_ci } 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci /* start digitiser */ 5078c2ecf20Sopenharmony_ci if (wm->mach_ops && wm->mach_ops->acc_enabled) 5088c2ecf20Sopenharmony_ci wm->codec->acc_enable(wm, 1); 5098c2ecf20Sopenharmony_ci wm->codec->dig_enable(wm, 1); 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&wm->ts_reader, wm97xx_ts_reader); 5128c2ecf20Sopenharmony_ci INIT_WORK(&wm->pen_event_work, wm97xx_pen_irq_worker); 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci wm->ts_reader_min_interval = HZ >= 100 ? HZ / 100 : 1; 5158c2ecf20Sopenharmony_ci if (wm->ts_reader_min_interval < 1) 5168c2ecf20Sopenharmony_ci wm->ts_reader_min_interval = 1; 5178c2ecf20Sopenharmony_ci wm->ts_reader_interval = wm->ts_reader_min_interval; 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci wm->pen_is_down = 0; 5208c2ecf20Sopenharmony_ci if (wm->pen_irq) 5218c2ecf20Sopenharmony_ci wm97xx_init_pen_irq(wm); 5228c2ecf20Sopenharmony_ci else 5238c2ecf20Sopenharmony_ci dev_err(wm->dev, "No IRQ specified\n"); 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci /* If we either don't have an interrupt for pen down events or 5268c2ecf20Sopenharmony_ci * failed to acquire it then we need to poll. 5278c2ecf20Sopenharmony_ci */ 5288c2ecf20Sopenharmony_ci if (wm->pen_irq == 0) 5298c2ecf20Sopenharmony_ci queue_delayed_work(wm->ts_workq, &wm->ts_reader, 5308c2ecf20Sopenharmony_ci wm->ts_reader_interval); 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci return 0; 5338c2ecf20Sopenharmony_ci} 5348c2ecf20Sopenharmony_ci 5358c2ecf20Sopenharmony_ci/** 5368c2ecf20Sopenharmony_ci * wm97xx_ts_input_close - Close the touch screen input device. 5378c2ecf20Sopenharmony_ci * @idev: Input device to be closed. 5388c2ecf20Sopenharmony_ci * 5398c2ecf20Sopenharmony_ci * Called by the input sub system to close a wm97xx touchscreen 5408c2ecf20Sopenharmony_ci * device. Kills the touchscreen thread and stops the touch 5418c2ecf20Sopenharmony_ci * digitiser. 5428c2ecf20Sopenharmony_ci */ 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_cistatic void wm97xx_ts_input_close(struct input_dev *idev) 5458c2ecf20Sopenharmony_ci{ 5468c2ecf20Sopenharmony_ci struct wm97xx *wm = input_get_drvdata(idev); 5478c2ecf20Sopenharmony_ci u16 reg; 5488c2ecf20Sopenharmony_ci 5498c2ecf20Sopenharmony_ci if (wm->pen_irq) { 5508c2ecf20Sopenharmony_ci /* Return the interrupt to GPIO usage (disabling it) */ 5518c2ecf20Sopenharmony_ci if (wm->id != WM9705_ID2) { 5528c2ecf20Sopenharmony_ci BUG_ON(!wm->mach_ops->irq_gpio); 5538c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_MISC_AFE); 5548c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_MISC_AFE, 5558c2ecf20Sopenharmony_ci reg | wm->mach_ops->irq_gpio); 5568c2ecf20Sopenharmony_ci } 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci free_irq(wm->pen_irq, wm); 5598c2ecf20Sopenharmony_ci } 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci wm->pen_is_down = 0; 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci /* Balance out interrupt disables/enables */ 5648c2ecf20Sopenharmony_ci if (cancel_work_sync(&wm->pen_event_work)) 5658c2ecf20Sopenharmony_ci wm->mach_ops->irq_enable(wm, 1); 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci /* ts_reader rearms itself so we need to explicitly stop it 5688c2ecf20Sopenharmony_ci * before we destroy the workqueue. 5698c2ecf20Sopenharmony_ci */ 5708c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&wm->ts_reader); 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci destroy_workqueue(wm->ts_workq); 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci /* stop digitiser */ 5758c2ecf20Sopenharmony_ci wm->codec->dig_enable(wm, 0); 5768c2ecf20Sopenharmony_ci if (wm->mach_ops && wm->mach_ops->acc_enabled) 5778c2ecf20Sopenharmony_ci wm->codec->acc_enable(wm, 0); 5788c2ecf20Sopenharmony_ci} 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_cistatic int wm97xx_register_touch(struct wm97xx *wm) 5818c2ecf20Sopenharmony_ci{ 5828c2ecf20Sopenharmony_ci struct wm97xx_pdata *pdata = dev_get_platdata(wm->dev); 5838c2ecf20Sopenharmony_ci int ret; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci wm->input_dev = devm_input_allocate_device(wm->dev); 5868c2ecf20Sopenharmony_ci if (wm->input_dev == NULL) 5878c2ecf20Sopenharmony_ci return -ENOMEM; 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ci /* set up touch configuration */ 5908c2ecf20Sopenharmony_ci wm->input_dev->name = "wm97xx touchscreen"; 5918c2ecf20Sopenharmony_ci wm->input_dev->phys = "wm97xx"; 5928c2ecf20Sopenharmony_ci wm->input_dev->open = wm97xx_ts_input_open; 5938c2ecf20Sopenharmony_ci wm->input_dev->close = wm97xx_ts_input_close; 5948c2ecf20Sopenharmony_ci 5958c2ecf20Sopenharmony_ci __set_bit(EV_ABS, wm->input_dev->evbit); 5968c2ecf20Sopenharmony_ci __set_bit(EV_KEY, wm->input_dev->evbit); 5978c2ecf20Sopenharmony_ci __set_bit(BTN_TOUCH, wm->input_dev->keybit); 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_ci input_set_abs_params(wm->input_dev, ABS_X, abs_x[0], abs_x[1], 6008c2ecf20Sopenharmony_ci abs_x[2], 0); 6018c2ecf20Sopenharmony_ci input_set_abs_params(wm->input_dev, ABS_Y, abs_y[0], abs_y[1], 6028c2ecf20Sopenharmony_ci abs_y[2], 0); 6038c2ecf20Sopenharmony_ci input_set_abs_params(wm->input_dev, ABS_PRESSURE, abs_p[0], abs_p[1], 6048c2ecf20Sopenharmony_ci abs_p[2], 0); 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci input_set_drvdata(wm->input_dev, wm); 6078c2ecf20Sopenharmony_ci wm->input_dev->dev.parent = wm->dev; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci ret = input_register_device(wm->input_dev); 6108c2ecf20Sopenharmony_ci if (ret) 6118c2ecf20Sopenharmony_ci return ret; 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci /* 6148c2ecf20Sopenharmony_ci * register our extended touch device (for machine specific 6158c2ecf20Sopenharmony_ci * extensions) 6168c2ecf20Sopenharmony_ci */ 6178c2ecf20Sopenharmony_ci wm->touch_dev = platform_device_alloc("wm97xx-touch", -1); 6188c2ecf20Sopenharmony_ci if (!wm->touch_dev) { 6198c2ecf20Sopenharmony_ci ret = -ENOMEM; 6208c2ecf20Sopenharmony_ci goto touch_err; 6218c2ecf20Sopenharmony_ci } 6228c2ecf20Sopenharmony_ci platform_set_drvdata(wm->touch_dev, wm); 6238c2ecf20Sopenharmony_ci wm->touch_dev->dev.parent = wm->dev; 6248c2ecf20Sopenharmony_ci wm->touch_dev->dev.platform_data = pdata; 6258c2ecf20Sopenharmony_ci ret = platform_device_add(wm->touch_dev); 6268c2ecf20Sopenharmony_ci if (ret < 0) 6278c2ecf20Sopenharmony_ci goto touch_reg_err; 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci return 0; 6308c2ecf20Sopenharmony_citouch_reg_err: 6318c2ecf20Sopenharmony_ci platform_device_put(wm->touch_dev); 6328c2ecf20Sopenharmony_citouch_err: 6338c2ecf20Sopenharmony_ci input_unregister_device(wm->input_dev); 6348c2ecf20Sopenharmony_ci wm->input_dev = NULL; 6358c2ecf20Sopenharmony_ci 6368c2ecf20Sopenharmony_ci return ret; 6378c2ecf20Sopenharmony_ci} 6388c2ecf20Sopenharmony_ci 6398c2ecf20Sopenharmony_cistatic void wm97xx_unregister_touch(struct wm97xx *wm) 6408c2ecf20Sopenharmony_ci{ 6418c2ecf20Sopenharmony_ci platform_device_unregister(wm->touch_dev); 6428c2ecf20Sopenharmony_ci input_unregister_device(wm->input_dev); 6438c2ecf20Sopenharmony_ci wm->input_dev = NULL; 6448c2ecf20Sopenharmony_ci} 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_cistatic int _wm97xx_probe(struct wm97xx *wm) 6478c2ecf20Sopenharmony_ci{ 6488c2ecf20Sopenharmony_ci int id = 0; 6498c2ecf20Sopenharmony_ci 6508c2ecf20Sopenharmony_ci mutex_init(&wm->codec_mutex); 6518c2ecf20Sopenharmony_ci dev_set_drvdata(wm->dev, wm); 6528c2ecf20Sopenharmony_ci 6538c2ecf20Sopenharmony_ci /* check that we have a supported codec */ 6548c2ecf20Sopenharmony_ci id = wm97xx_reg_read(wm, AC97_VENDOR_ID1); 6558c2ecf20Sopenharmony_ci if (id != WM97XX_ID1) { 6568c2ecf20Sopenharmony_ci dev_err(wm->dev, 6578c2ecf20Sopenharmony_ci "Device with vendor %04x is not a wm97xx\n", id); 6588c2ecf20Sopenharmony_ci return -ENODEV; 6598c2ecf20Sopenharmony_ci } 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci wm->id = wm97xx_reg_read(wm, AC97_VENDOR_ID2); 6628c2ecf20Sopenharmony_ci 6638c2ecf20Sopenharmony_ci wm->variant = WM97xx_GENERIC; 6648c2ecf20Sopenharmony_ci 6658c2ecf20Sopenharmony_ci dev_info(wm->dev, "detected a wm97%02x codec\n", wm->id & 0xff); 6668c2ecf20Sopenharmony_ci 6678c2ecf20Sopenharmony_ci switch (wm->id & 0xff) { 6688c2ecf20Sopenharmony_ci#ifdef CONFIG_TOUCHSCREEN_WM9705 6698c2ecf20Sopenharmony_ci case 0x05: 6708c2ecf20Sopenharmony_ci wm->codec = &wm9705_codec; 6718c2ecf20Sopenharmony_ci break; 6728c2ecf20Sopenharmony_ci#endif 6738c2ecf20Sopenharmony_ci#ifdef CONFIG_TOUCHSCREEN_WM9712 6748c2ecf20Sopenharmony_ci case 0x12: 6758c2ecf20Sopenharmony_ci wm->codec = &wm9712_codec; 6768c2ecf20Sopenharmony_ci break; 6778c2ecf20Sopenharmony_ci#endif 6788c2ecf20Sopenharmony_ci#ifdef CONFIG_TOUCHSCREEN_WM9713 6798c2ecf20Sopenharmony_ci case 0x13: 6808c2ecf20Sopenharmony_ci wm->codec = &wm9713_codec; 6818c2ecf20Sopenharmony_ci break; 6828c2ecf20Sopenharmony_ci#endif 6838c2ecf20Sopenharmony_ci default: 6848c2ecf20Sopenharmony_ci dev_err(wm->dev, "Support for wm97%02x not compiled in.\n", 6858c2ecf20Sopenharmony_ci wm->id & 0xff); 6868c2ecf20Sopenharmony_ci return -ENODEV; 6878c2ecf20Sopenharmony_ci } 6888c2ecf20Sopenharmony_ci 6898c2ecf20Sopenharmony_ci /* set up physical characteristics */ 6908c2ecf20Sopenharmony_ci wm->codec->phy_init(wm); 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci /* load gpio cache */ 6938c2ecf20Sopenharmony_ci wm->gpio[0] = wm97xx_reg_read(wm, AC97_GPIO_CFG); 6948c2ecf20Sopenharmony_ci wm->gpio[1] = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); 6958c2ecf20Sopenharmony_ci wm->gpio[2] = wm97xx_reg_read(wm, AC97_GPIO_STICKY); 6968c2ecf20Sopenharmony_ci wm->gpio[3] = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); 6978c2ecf20Sopenharmony_ci wm->gpio[4] = wm97xx_reg_read(wm, AC97_GPIO_STATUS); 6988c2ecf20Sopenharmony_ci wm->gpio[5] = wm97xx_reg_read(wm, AC97_MISC_AFE); 6998c2ecf20Sopenharmony_ci 7008c2ecf20Sopenharmony_ci return wm97xx_register_touch(wm); 7018c2ecf20Sopenharmony_ci} 7028c2ecf20Sopenharmony_ci 7038c2ecf20Sopenharmony_cistatic void wm97xx_remove_battery(struct wm97xx *wm) 7048c2ecf20Sopenharmony_ci{ 7058c2ecf20Sopenharmony_ci platform_device_unregister(wm->battery_dev); 7068c2ecf20Sopenharmony_ci} 7078c2ecf20Sopenharmony_ci 7088c2ecf20Sopenharmony_cistatic int wm97xx_add_battery(struct wm97xx *wm, 7098c2ecf20Sopenharmony_ci struct wm97xx_batt_pdata *pdata) 7108c2ecf20Sopenharmony_ci{ 7118c2ecf20Sopenharmony_ci int ret; 7128c2ecf20Sopenharmony_ci 7138c2ecf20Sopenharmony_ci wm->battery_dev = platform_device_alloc("wm97xx-battery", -1); 7148c2ecf20Sopenharmony_ci if (!wm->battery_dev) 7158c2ecf20Sopenharmony_ci return -ENOMEM; 7168c2ecf20Sopenharmony_ci 7178c2ecf20Sopenharmony_ci platform_set_drvdata(wm->battery_dev, wm); 7188c2ecf20Sopenharmony_ci wm->battery_dev->dev.parent = wm->dev; 7198c2ecf20Sopenharmony_ci wm->battery_dev->dev.platform_data = pdata; 7208c2ecf20Sopenharmony_ci ret = platform_device_add(wm->battery_dev); 7218c2ecf20Sopenharmony_ci if (ret) 7228c2ecf20Sopenharmony_ci platform_device_put(wm->battery_dev); 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci return ret; 7258c2ecf20Sopenharmony_ci} 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_cistatic int wm97xx_probe(struct device *dev) 7288c2ecf20Sopenharmony_ci{ 7298c2ecf20Sopenharmony_ci struct wm97xx *wm; 7308c2ecf20Sopenharmony_ci int ret; 7318c2ecf20Sopenharmony_ci struct wm97xx_pdata *pdata = dev_get_platdata(dev); 7328c2ecf20Sopenharmony_ci 7338c2ecf20Sopenharmony_ci wm = devm_kzalloc(dev, sizeof(struct wm97xx), GFP_KERNEL); 7348c2ecf20Sopenharmony_ci if (!wm) 7358c2ecf20Sopenharmony_ci return -ENOMEM; 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_ci wm->dev = dev; 7388c2ecf20Sopenharmony_ci wm->ac97 = to_ac97_t(dev); 7398c2ecf20Sopenharmony_ci 7408c2ecf20Sopenharmony_ci ret = _wm97xx_probe(wm); 7418c2ecf20Sopenharmony_ci if (ret) 7428c2ecf20Sopenharmony_ci return ret; 7438c2ecf20Sopenharmony_ci 7448c2ecf20Sopenharmony_ci ret = wm97xx_add_battery(wm, pdata ? pdata->batt_pdata : NULL); 7458c2ecf20Sopenharmony_ci if (ret < 0) 7468c2ecf20Sopenharmony_ci goto batt_err; 7478c2ecf20Sopenharmony_ci 7488c2ecf20Sopenharmony_ci return ret; 7498c2ecf20Sopenharmony_ci 7508c2ecf20Sopenharmony_cibatt_err: 7518c2ecf20Sopenharmony_ci wm97xx_unregister_touch(wm); 7528c2ecf20Sopenharmony_ci return ret; 7538c2ecf20Sopenharmony_ci} 7548c2ecf20Sopenharmony_ci 7558c2ecf20Sopenharmony_cistatic int wm97xx_remove(struct device *dev) 7568c2ecf20Sopenharmony_ci{ 7578c2ecf20Sopenharmony_ci struct wm97xx *wm = dev_get_drvdata(dev); 7588c2ecf20Sopenharmony_ci 7598c2ecf20Sopenharmony_ci wm97xx_remove_battery(wm); 7608c2ecf20Sopenharmony_ci wm97xx_unregister_touch(wm); 7618c2ecf20Sopenharmony_ci 7628c2ecf20Sopenharmony_ci return 0; 7638c2ecf20Sopenharmony_ci} 7648c2ecf20Sopenharmony_ci 7658c2ecf20Sopenharmony_cistatic int wm97xx_mfd_probe(struct platform_device *pdev) 7668c2ecf20Sopenharmony_ci{ 7678c2ecf20Sopenharmony_ci struct wm97xx *wm; 7688c2ecf20Sopenharmony_ci struct wm97xx_platform_data *mfd_pdata = dev_get_platdata(&pdev->dev); 7698c2ecf20Sopenharmony_ci int ret; 7708c2ecf20Sopenharmony_ci 7718c2ecf20Sopenharmony_ci wm = devm_kzalloc(&pdev->dev, sizeof(struct wm97xx), GFP_KERNEL); 7728c2ecf20Sopenharmony_ci if (!wm) 7738c2ecf20Sopenharmony_ci return -ENOMEM; 7748c2ecf20Sopenharmony_ci 7758c2ecf20Sopenharmony_ci wm->dev = &pdev->dev; 7768c2ecf20Sopenharmony_ci wm->ac97 = mfd_pdata->ac97; 7778c2ecf20Sopenharmony_ci 7788c2ecf20Sopenharmony_ci ret = _wm97xx_probe(wm); 7798c2ecf20Sopenharmony_ci if (ret) 7808c2ecf20Sopenharmony_ci return ret; 7818c2ecf20Sopenharmony_ci 7828c2ecf20Sopenharmony_ci ret = wm97xx_add_battery(wm, mfd_pdata->batt_pdata); 7838c2ecf20Sopenharmony_ci if (ret < 0) 7848c2ecf20Sopenharmony_ci goto batt_err; 7858c2ecf20Sopenharmony_ci 7868c2ecf20Sopenharmony_ci return ret; 7878c2ecf20Sopenharmony_ci 7888c2ecf20Sopenharmony_cibatt_err: 7898c2ecf20Sopenharmony_ci wm97xx_unregister_touch(wm); 7908c2ecf20Sopenharmony_ci return ret; 7918c2ecf20Sopenharmony_ci} 7928c2ecf20Sopenharmony_ci 7938c2ecf20Sopenharmony_cistatic int wm97xx_mfd_remove(struct platform_device *pdev) 7948c2ecf20Sopenharmony_ci{ 7958c2ecf20Sopenharmony_ci return wm97xx_remove(&pdev->dev); 7968c2ecf20Sopenharmony_ci} 7978c2ecf20Sopenharmony_ci 7988c2ecf20Sopenharmony_cistatic int __maybe_unused wm97xx_suspend(struct device *dev) 7998c2ecf20Sopenharmony_ci{ 8008c2ecf20Sopenharmony_ci struct wm97xx *wm = dev_get_drvdata(dev); 8018c2ecf20Sopenharmony_ci u16 reg; 8028c2ecf20Sopenharmony_ci int suspend_mode; 8038c2ecf20Sopenharmony_ci 8048c2ecf20Sopenharmony_ci if (device_may_wakeup(&wm->input_dev->dev)) 8058c2ecf20Sopenharmony_ci suspend_mode = wm->suspend_mode; 8068c2ecf20Sopenharmony_ci else 8078c2ecf20Sopenharmony_ci suspend_mode = 0; 8088c2ecf20Sopenharmony_ci 8098c2ecf20Sopenharmony_ci if (wm->input_dev->users) 8108c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&wm->ts_reader); 8118c2ecf20Sopenharmony_ci 8128c2ecf20Sopenharmony_ci /* Power down the digitiser (bypassing the cache for resume) */ 8138c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER2); 8148c2ecf20Sopenharmony_ci reg &= ~WM97XX_PRP_DET_DIG; 8158c2ecf20Sopenharmony_ci if (wm->input_dev->users) 8168c2ecf20Sopenharmony_ci reg |= suspend_mode; 8178c2ecf20Sopenharmony_ci wm->ac97->bus->ops->write(wm->ac97, AC97_WM97XX_DIGITISER2, reg); 8188c2ecf20Sopenharmony_ci 8198c2ecf20Sopenharmony_ci /* WM9713 has an additional power bit - turn it off if there 8208c2ecf20Sopenharmony_ci * are no users or if suspend mode is zero. */ 8218c2ecf20Sopenharmony_ci if (wm->id == WM9713_ID2 && 8228c2ecf20Sopenharmony_ci (!wm->input_dev->users || !suspend_mode)) { 8238c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) | 0x8000; 8248c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); 8258c2ecf20Sopenharmony_ci } 8268c2ecf20Sopenharmony_ci 8278c2ecf20Sopenharmony_ci return 0; 8288c2ecf20Sopenharmony_ci} 8298c2ecf20Sopenharmony_ci 8308c2ecf20Sopenharmony_cistatic int __maybe_unused wm97xx_resume(struct device *dev) 8318c2ecf20Sopenharmony_ci{ 8328c2ecf20Sopenharmony_ci struct wm97xx *wm = dev_get_drvdata(dev); 8338c2ecf20Sopenharmony_ci 8348c2ecf20Sopenharmony_ci /* restore digitiser and gpios */ 8358c2ecf20Sopenharmony_ci if (wm->id == WM9713_ID2) { 8368c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig[0]); 8378c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, 0x5a, wm->misc); 8388c2ecf20Sopenharmony_ci if (wm->input_dev->users) { 8398c2ecf20Sopenharmony_ci u16 reg; 8408c2ecf20Sopenharmony_ci reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) & 0x7fff; 8418c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); 8428c2ecf20Sopenharmony_ci } 8438c2ecf20Sopenharmony_ci } 8448c2ecf20Sopenharmony_ci 8458c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig[1]); 8468c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2]); 8478c2ecf20Sopenharmony_ci 8488c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_CFG, wm->gpio[0]); 8498c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_POLARITY, wm->gpio[1]); 8508c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_STICKY, wm->gpio[2]); 8518c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, wm->gpio[3]); 8528c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_GPIO_STATUS, wm->gpio[4]); 8538c2ecf20Sopenharmony_ci wm97xx_reg_write(wm, AC97_MISC_AFE, wm->gpio[5]); 8548c2ecf20Sopenharmony_ci 8558c2ecf20Sopenharmony_ci if (wm->input_dev->users && !wm->pen_irq) { 8568c2ecf20Sopenharmony_ci wm->ts_reader_interval = wm->ts_reader_min_interval; 8578c2ecf20Sopenharmony_ci queue_delayed_work(wm->ts_workq, &wm->ts_reader, 8588c2ecf20Sopenharmony_ci wm->ts_reader_interval); 8598c2ecf20Sopenharmony_ci } 8608c2ecf20Sopenharmony_ci 8618c2ecf20Sopenharmony_ci return 0; 8628c2ecf20Sopenharmony_ci} 8638c2ecf20Sopenharmony_ci 8648c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(wm97xx_pm_ops, wm97xx_suspend, wm97xx_resume); 8658c2ecf20Sopenharmony_ci 8668c2ecf20Sopenharmony_ci/* 8678c2ecf20Sopenharmony_ci * Machine specific operations 8688c2ecf20Sopenharmony_ci */ 8698c2ecf20Sopenharmony_ciint wm97xx_register_mach_ops(struct wm97xx *wm, 8708c2ecf20Sopenharmony_ci struct wm97xx_mach_ops *mach_ops) 8718c2ecf20Sopenharmony_ci{ 8728c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 8738c2ecf20Sopenharmony_ci if (wm->mach_ops) { 8748c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 8758c2ecf20Sopenharmony_ci return -EINVAL; 8768c2ecf20Sopenharmony_ci } 8778c2ecf20Sopenharmony_ci wm->mach_ops = mach_ops; 8788c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 8798c2ecf20Sopenharmony_ci 8808c2ecf20Sopenharmony_ci return 0; 8818c2ecf20Sopenharmony_ci} 8828c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_register_mach_ops); 8838c2ecf20Sopenharmony_ci 8848c2ecf20Sopenharmony_civoid wm97xx_unregister_mach_ops(struct wm97xx *wm) 8858c2ecf20Sopenharmony_ci{ 8868c2ecf20Sopenharmony_ci mutex_lock(&wm->codec_mutex); 8878c2ecf20Sopenharmony_ci wm->mach_ops = NULL; 8888c2ecf20Sopenharmony_ci mutex_unlock(&wm->codec_mutex); 8898c2ecf20Sopenharmony_ci} 8908c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm97xx_unregister_mach_ops); 8918c2ecf20Sopenharmony_ci 8928c2ecf20Sopenharmony_cistatic struct device_driver wm97xx_driver = { 8938c2ecf20Sopenharmony_ci .name = "wm97xx-ts", 8948c2ecf20Sopenharmony_ci#ifdef CONFIG_AC97_BUS 8958c2ecf20Sopenharmony_ci .bus = &ac97_bus_type, 8968c2ecf20Sopenharmony_ci#endif 8978c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 8988c2ecf20Sopenharmony_ci .probe = wm97xx_probe, 8998c2ecf20Sopenharmony_ci .remove = wm97xx_remove, 9008c2ecf20Sopenharmony_ci .pm = &wm97xx_pm_ops, 9018c2ecf20Sopenharmony_ci}; 9028c2ecf20Sopenharmony_ci 9038c2ecf20Sopenharmony_cistatic struct platform_driver wm97xx_mfd_driver = { 9048c2ecf20Sopenharmony_ci .driver = { 9058c2ecf20Sopenharmony_ci .name = "wm97xx-ts", 9068c2ecf20Sopenharmony_ci .pm = &wm97xx_pm_ops, 9078c2ecf20Sopenharmony_ci }, 9088c2ecf20Sopenharmony_ci .probe = wm97xx_mfd_probe, 9098c2ecf20Sopenharmony_ci .remove = wm97xx_mfd_remove, 9108c2ecf20Sopenharmony_ci}; 9118c2ecf20Sopenharmony_ci 9128c2ecf20Sopenharmony_cistatic int __init wm97xx_init(void) 9138c2ecf20Sopenharmony_ci{ 9148c2ecf20Sopenharmony_ci int ret; 9158c2ecf20Sopenharmony_ci 9168c2ecf20Sopenharmony_ci ret = platform_driver_register(&wm97xx_mfd_driver); 9178c2ecf20Sopenharmony_ci if (ret) 9188c2ecf20Sopenharmony_ci return ret; 9198c2ecf20Sopenharmony_ci 9208c2ecf20Sopenharmony_ci if (IS_BUILTIN(CONFIG_AC97_BUS)) 9218c2ecf20Sopenharmony_ci ret = driver_register(&wm97xx_driver); 9228c2ecf20Sopenharmony_ci return ret; 9238c2ecf20Sopenharmony_ci} 9248c2ecf20Sopenharmony_ci 9258c2ecf20Sopenharmony_cistatic void __exit wm97xx_exit(void) 9268c2ecf20Sopenharmony_ci{ 9278c2ecf20Sopenharmony_ci if (IS_BUILTIN(CONFIG_AC97_BUS)) 9288c2ecf20Sopenharmony_ci driver_unregister(&wm97xx_driver); 9298c2ecf20Sopenharmony_ci platform_driver_unregister(&wm97xx_mfd_driver); 9308c2ecf20Sopenharmony_ci} 9318c2ecf20Sopenharmony_ci 9328c2ecf20Sopenharmony_cimodule_init(wm97xx_init); 9338c2ecf20Sopenharmony_cimodule_exit(wm97xx_exit); 9348c2ecf20Sopenharmony_ci 9358c2ecf20Sopenharmony_ci/* Module information */ 9368c2ecf20Sopenharmony_ciMODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); 9378c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("WM97xx Core - Touch Screen / AUX ADC / GPIO Driver"); 9388c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 939