18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * zylonite-wm97xx.c -- Zylonite Continuous Touch screen driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2004, 2007, 2008 Wolfson Microelectronics PLC. 68c2ecf20Sopenharmony_ci * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 78c2ecf20Sopenharmony_ci * Parts Copyright : Ian Molton <spyro@f2s.com> 88c2ecf20Sopenharmony_ci * Andrew Zabolotny <zap@homelink.ru> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Notes: 118c2ecf20Sopenharmony_ci * This is a wm97xx extended touch driver supporting interrupt driven 128c2ecf20Sopenharmony_ci * and continuous operation on Marvell Zylonite development systems 138c2ecf20Sopenharmony_ci * (which have a WM9713 on board). 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 188c2ecf20Sopenharmony_ci#include <linux/kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/delay.h> 208c2ecf20Sopenharmony_ci#include <linux/gpio.h> 218c2ecf20Sopenharmony_ci#include <linux/irq.h> 228c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 238c2ecf20Sopenharmony_ci#include <linux/io.h> 248c2ecf20Sopenharmony_ci#include <linux/wm97xx.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#include <mach/hardware.h> 278c2ecf20Sopenharmony_ci#include <mach/mfp.h> 288c2ecf20Sopenharmony_ci#include <mach/regs-ac97.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct continuous { 318c2ecf20Sopenharmony_ci u16 id; /* codec id */ 328c2ecf20Sopenharmony_ci u8 code; /* continuous code */ 338c2ecf20Sopenharmony_ci u8 reads; /* number of coord reads per read cycle */ 348c2ecf20Sopenharmony_ci u32 speed; /* number of coords per second */ 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define WM_READS(sp) ((sp / HZ) + 1) 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic const struct continuous cinfo[] = { 408c2ecf20Sopenharmony_ci { WM9713_ID2, 0, WM_READS(94), 94 }, 418c2ecf20Sopenharmony_ci { WM9713_ID2, 1, WM_READS(120), 120 }, 428c2ecf20Sopenharmony_ci { WM9713_ID2, 2, WM_READS(154), 154 }, 438c2ecf20Sopenharmony_ci { WM9713_ID2, 3, WM_READS(188), 188 }, 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* continuous speed index */ 478c2ecf20Sopenharmony_cistatic int sp_idx; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* 508c2ecf20Sopenharmony_ci * Pen sampling frequency (Hz) in continuous mode. 518c2ecf20Sopenharmony_ci */ 528c2ecf20Sopenharmony_cistatic int cont_rate = 200; 538c2ecf20Sopenharmony_cimodule_param(cont_rate, int, 0); 548c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)"); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci/* 578c2ecf20Sopenharmony_ci * Pressure readback. 588c2ecf20Sopenharmony_ci * 598c2ecf20Sopenharmony_ci * Set to 1 to read back pen down pressure 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_cistatic int pressure; 628c2ecf20Sopenharmony_cimodule_param(pressure, int, 0); 638c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)"); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci/* 668c2ecf20Sopenharmony_ci * AC97 touch data slot. 678c2ecf20Sopenharmony_ci * 688c2ecf20Sopenharmony_ci * Touch screen readback data ac97 slot 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_cistatic int ac97_touch_slot = 5; 718c2ecf20Sopenharmony_cimodule_param(ac97_touch_slot, int, 0); 728c2ecf20Sopenharmony_ciMODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number"); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci/* flush AC97 slot 5 FIFO machines */ 768c2ecf20Sopenharmony_cistatic void wm97xx_acc_pen_up(struct wm97xx *wm) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci int i; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci msleep(1); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci for (i = 0; i < 16; i++) 838c2ecf20Sopenharmony_ci MODR; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int wm97xx_acc_pen_down(struct wm97xx *wm) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES; 898c2ecf20Sopenharmony_ci int reads = 0; 908c2ecf20Sopenharmony_ci static u16 last, tries; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci /* When the AC97 queue has been drained we need to allow time 938c2ecf20Sopenharmony_ci * to buffer up samples otherwise we end up spinning polling 948c2ecf20Sopenharmony_ci * for samples. The controller can't have a suitably low 958c2ecf20Sopenharmony_ci * threshold set to use the notifications it gives. 968c2ecf20Sopenharmony_ci */ 978c2ecf20Sopenharmony_ci msleep(1); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci if (tries > 5) { 1008c2ecf20Sopenharmony_ci tries = 0; 1018c2ecf20Sopenharmony_ci return RC_PENUP; 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci x = MODR; 1058c2ecf20Sopenharmony_ci if (x == last) { 1068c2ecf20Sopenharmony_ci tries++; 1078c2ecf20Sopenharmony_ci return RC_AGAIN; 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci last = x; 1108c2ecf20Sopenharmony_ci do { 1118c2ecf20Sopenharmony_ci if (reads) 1128c2ecf20Sopenharmony_ci x = MODR; 1138c2ecf20Sopenharmony_ci y = MODR; 1148c2ecf20Sopenharmony_ci if (pressure) 1158c2ecf20Sopenharmony_ci p = MODR; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci dev_dbg(wm->dev, "Raw coordinates: x=%x, y=%x, p=%x\n", 1188c2ecf20Sopenharmony_ci x, y, p); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* are samples valid */ 1218c2ecf20Sopenharmony_ci if ((x & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_X || 1228c2ecf20Sopenharmony_ci (y & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_Y || 1238c2ecf20Sopenharmony_ci (p & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_PRES) 1248c2ecf20Sopenharmony_ci goto up; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci /* coordinate is good */ 1278c2ecf20Sopenharmony_ci tries = 0; 1288c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_X, x & 0xfff); 1298c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_Y, y & 0xfff); 1308c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff); 1318c2ecf20Sopenharmony_ci input_report_key(wm->input_dev, BTN_TOUCH, (p != 0)); 1328c2ecf20Sopenharmony_ci input_sync(wm->input_dev); 1338c2ecf20Sopenharmony_ci reads++; 1348c2ecf20Sopenharmony_ci } while (reads < cinfo[sp_idx].reads); 1358c2ecf20Sopenharmony_ciup: 1368c2ecf20Sopenharmony_ci return RC_PENDOWN | RC_AGAIN; 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic int wm97xx_acc_startup(struct wm97xx *wm) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci int idx; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* check we have a codec */ 1448c2ecf20Sopenharmony_ci if (wm->ac97 == NULL) 1458c2ecf20Sopenharmony_ci return -ENODEV; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* Go you big red fire engine */ 1488c2ecf20Sopenharmony_ci for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) { 1498c2ecf20Sopenharmony_ci if (wm->id != cinfo[idx].id) 1508c2ecf20Sopenharmony_ci continue; 1518c2ecf20Sopenharmony_ci sp_idx = idx; 1528c2ecf20Sopenharmony_ci if (cont_rate <= cinfo[idx].speed) 1538c2ecf20Sopenharmony_ci break; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci wm->acc_rate = cinfo[sp_idx].code; 1568c2ecf20Sopenharmony_ci wm->acc_slot = ac97_touch_slot; 1578c2ecf20Sopenharmony_ci dev_info(wm->dev, 1588c2ecf20Sopenharmony_ci "zylonite accelerated touchscreen driver, %d samples/sec\n", 1598c2ecf20Sopenharmony_ci cinfo[sp_idx].speed); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return 0; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic void wm97xx_irq_enable(struct wm97xx *wm, int enable) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci if (enable) 1678c2ecf20Sopenharmony_ci enable_irq(wm->pen_irq); 1688c2ecf20Sopenharmony_ci else 1698c2ecf20Sopenharmony_ci disable_irq_nosync(wm->pen_irq); 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic struct wm97xx_mach_ops zylonite_mach_ops = { 1738c2ecf20Sopenharmony_ci .acc_enabled = 1, 1748c2ecf20Sopenharmony_ci .acc_pen_up = wm97xx_acc_pen_up, 1758c2ecf20Sopenharmony_ci .acc_pen_down = wm97xx_acc_pen_down, 1768c2ecf20Sopenharmony_ci .acc_startup = wm97xx_acc_startup, 1778c2ecf20Sopenharmony_ci .irq_enable = wm97xx_irq_enable, 1788c2ecf20Sopenharmony_ci .irq_gpio = WM97XX_GPIO_2, 1798c2ecf20Sopenharmony_ci}; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic int zylonite_wm97xx_probe(struct platform_device *pdev) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci struct wm97xx *wm = platform_get_drvdata(pdev); 1848c2ecf20Sopenharmony_ci int gpio_touch_irq; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (cpu_is_pxa320()) 1878c2ecf20Sopenharmony_ci gpio_touch_irq = mfp_to_gpio(MFP_PIN_GPIO15); 1888c2ecf20Sopenharmony_ci else 1898c2ecf20Sopenharmony_ci gpio_touch_irq = mfp_to_gpio(MFP_PIN_GPIO26); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci wm->pen_irq = gpio_to_irq(gpio_touch_irq); 1928c2ecf20Sopenharmony_ci irq_set_irq_type(wm->pen_irq, IRQ_TYPE_EDGE_BOTH); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN, 1958c2ecf20Sopenharmony_ci WM97XX_GPIO_POL_HIGH, 1968c2ecf20Sopenharmony_ci WM97XX_GPIO_STICKY, 1978c2ecf20Sopenharmony_ci WM97XX_GPIO_WAKE); 1988c2ecf20Sopenharmony_ci wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT, 1998c2ecf20Sopenharmony_ci WM97XX_GPIO_POL_HIGH, 2008c2ecf20Sopenharmony_ci WM97XX_GPIO_NOTSTICKY, 2018c2ecf20Sopenharmony_ci WM97XX_GPIO_NOWAKE); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci return wm97xx_register_mach_ops(wm, &zylonite_mach_ops); 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic int zylonite_wm97xx_remove(struct platform_device *pdev) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci struct wm97xx *wm = platform_get_drvdata(pdev); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci wm97xx_unregister_mach_ops(wm); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci return 0; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic struct platform_driver zylonite_wm97xx_driver = { 2168c2ecf20Sopenharmony_ci .probe = zylonite_wm97xx_probe, 2178c2ecf20Sopenharmony_ci .remove = zylonite_wm97xx_remove, 2188c2ecf20Sopenharmony_ci .driver = { 2198c2ecf20Sopenharmony_ci .name = "wm97xx-touch", 2208c2ecf20Sopenharmony_ci }, 2218c2ecf20Sopenharmony_ci}; 2228c2ecf20Sopenharmony_cimodule_platform_driver(zylonite_wm97xx_driver); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci/* Module information */ 2258c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 2268c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("wm97xx continuous touch driver for Zylonite"); 2278c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 228