18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * mainstone-wm97xx.c -- Mainstone Continuous Touch screen driver for 48c2ecf20Sopenharmony_ci * Wolfson WM97xx AC97 Codecs. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright 2004, 2007 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 * 118c2ecf20Sopenharmony_ci * Notes: 128c2ecf20Sopenharmony_ci * This is a wm97xx extended touch driver to capture touch 138c2ecf20Sopenharmony_ci * data in a continuous manner on the Intel XScale architecture 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * Features: 168c2ecf20Sopenharmony_ci * - codecs supported:- WM9705, WM9712, WM9713 178c2ecf20Sopenharmony_ci * - processors supported:- Intel XScale PXA25x, PXA26x, PXA27x 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 228c2ecf20Sopenharmony_ci#include <linux/kernel.h> 238c2ecf20Sopenharmony_ci#include <linux/delay.h> 248c2ecf20Sopenharmony_ci#include <linux/irq.h> 258c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 268c2ecf20Sopenharmony_ci#include <linux/wm97xx.h> 278c2ecf20Sopenharmony_ci#include <linux/io.h> 288c2ecf20Sopenharmony_ci#include <linux/gpio.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <mach/regs-ac97.h> 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#include <asm/mach-types.h> 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct continuous { 358c2ecf20Sopenharmony_ci u16 id; /* codec id */ 368c2ecf20Sopenharmony_ci u8 code; /* continuous code */ 378c2ecf20Sopenharmony_ci u8 reads; /* number of coord reads per read cycle */ 388c2ecf20Sopenharmony_ci u32 speed; /* number of coords per second */ 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#define WM_READS(sp) ((sp / HZ) + 1) 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic const struct continuous cinfo[] = { 448c2ecf20Sopenharmony_ci {WM9705_ID2, 0, WM_READS(94), 94}, 458c2ecf20Sopenharmony_ci {WM9705_ID2, 1, WM_READS(188), 188}, 468c2ecf20Sopenharmony_ci {WM9705_ID2, 2, WM_READS(375), 375}, 478c2ecf20Sopenharmony_ci {WM9705_ID2, 3, WM_READS(750), 750}, 488c2ecf20Sopenharmony_ci {WM9712_ID2, 0, WM_READS(94), 94}, 498c2ecf20Sopenharmony_ci {WM9712_ID2, 1, WM_READS(188), 188}, 508c2ecf20Sopenharmony_ci {WM9712_ID2, 2, WM_READS(375), 375}, 518c2ecf20Sopenharmony_ci {WM9712_ID2, 3, WM_READS(750), 750}, 528c2ecf20Sopenharmony_ci {WM9713_ID2, 0, WM_READS(94), 94}, 538c2ecf20Sopenharmony_ci {WM9713_ID2, 1, WM_READS(120), 120}, 548c2ecf20Sopenharmony_ci {WM9713_ID2, 2, WM_READS(154), 154}, 558c2ecf20Sopenharmony_ci {WM9713_ID2, 3, WM_READS(188), 188}, 568c2ecf20Sopenharmony_ci}; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci/* continuous speed index */ 598c2ecf20Sopenharmony_cistatic int sp_idx; 608c2ecf20Sopenharmony_cistatic u16 last, tries; 618c2ecf20Sopenharmony_cistatic int irq; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci/* 648c2ecf20Sopenharmony_ci * Pen sampling frequency (Hz) in continuous mode. 658c2ecf20Sopenharmony_ci */ 668c2ecf20Sopenharmony_cistatic int cont_rate = 200; 678c2ecf20Sopenharmony_cimodule_param(cont_rate, int, 0); 688c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)"); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci/* 718c2ecf20Sopenharmony_ci * Pen down detection. 728c2ecf20Sopenharmony_ci * 738c2ecf20Sopenharmony_ci * This driver can either poll or use an interrupt to indicate a pen down 748c2ecf20Sopenharmony_ci * event. If the irq request fails then it will fall back to polling mode. 758c2ecf20Sopenharmony_ci */ 768c2ecf20Sopenharmony_cistatic int pen_int; 778c2ecf20Sopenharmony_cimodule_param(pen_int, int, 0); 788c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pen_int, "Pen down detection (1 = interrupt, 0 = polling)"); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci/* 818c2ecf20Sopenharmony_ci * Pressure readback. 828c2ecf20Sopenharmony_ci * 838c2ecf20Sopenharmony_ci * Set to 1 to read back pen down pressure 848c2ecf20Sopenharmony_ci */ 858c2ecf20Sopenharmony_cistatic int pressure; 868c2ecf20Sopenharmony_cimodule_param(pressure, int, 0); 878c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)"); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci/* 908c2ecf20Sopenharmony_ci * AC97 touch data slot. 918c2ecf20Sopenharmony_ci * 928c2ecf20Sopenharmony_ci * Touch screen readback data ac97 slot 938c2ecf20Sopenharmony_ci */ 948c2ecf20Sopenharmony_cistatic int ac97_touch_slot = 5; 958c2ecf20Sopenharmony_cimodule_param(ac97_touch_slot, int, 0); 968c2ecf20Sopenharmony_ciMODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number"); 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci/* flush AC97 slot 5 FIFO on pxa machines */ 1008c2ecf20Sopenharmony_ci#ifdef CONFIG_PXA27x 1018c2ecf20Sopenharmony_cistatic void wm97xx_acc_pen_up(struct wm97xx *wm) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci schedule_timeout_uninterruptible(1); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci while (MISR & (1 << 2)) 1068c2ecf20Sopenharmony_ci MODR; 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci#else 1098c2ecf20Sopenharmony_cistatic void wm97xx_acc_pen_up(struct wm97xx *wm) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci unsigned int count; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci schedule_timeout_uninterruptible(1); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci for (count = 0; count < 16; count++) 1168c2ecf20Sopenharmony_ci MODR; 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci#endif 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic int wm97xx_acc_pen_down(struct wm97xx *wm) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES; 1238c2ecf20Sopenharmony_ci int reads = 0; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* When the AC97 queue has been drained we need to allow time 1268c2ecf20Sopenharmony_ci * to buffer up samples otherwise we end up spinning polling 1278c2ecf20Sopenharmony_ci * for samples. The controller can't have a suitably low 1288c2ecf20Sopenharmony_ci * threshold set to use the notifications it gives. 1298c2ecf20Sopenharmony_ci */ 1308c2ecf20Sopenharmony_ci schedule_timeout_uninterruptible(1); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (tries > 5) { 1338c2ecf20Sopenharmony_ci tries = 0; 1348c2ecf20Sopenharmony_ci return RC_PENUP; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci x = MODR; 1388c2ecf20Sopenharmony_ci if (x == last) { 1398c2ecf20Sopenharmony_ci tries++; 1408c2ecf20Sopenharmony_ci return RC_AGAIN; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci last = x; 1438c2ecf20Sopenharmony_ci do { 1448c2ecf20Sopenharmony_ci if (reads) 1458c2ecf20Sopenharmony_ci x = MODR; 1468c2ecf20Sopenharmony_ci y = MODR; 1478c2ecf20Sopenharmony_ci if (pressure) 1488c2ecf20Sopenharmony_ci p = MODR; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci dev_dbg(wm->dev, "Raw coordinates: x=%x, y=%x, p=%x\n", 1518c2ecf20Sopenharmony_ci x, y, p); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci /* are samples valid */ 1548c2ecf20Sopenharmony_ci if ((x & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_X || 1558c2ecf20Sopenharmony_ci (y & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_Y || 1568c2ecf20Sopenharmony_ci (p & WM97XX_ADCSEL_MASK) != WM97XX_ADCSEL_PRES) 1578c2ecf20Sopenharmony_ci goto up; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* coordinate is good */ 1608c2ecf20Sopenharmony_ci tries = 0; 1618c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_X, x & 0xfff); 1628c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_Y, y & 0xfff); 1638c2ecf20Sopenharmony_ci input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff); 1648c2ecf20Sopenharmony_ci input_report_key(wm->input_dev, BTN_TOUCH, (p != 0)); 1658c2ecf20Sopenharmony_ci input_sync(wm->input_dev); 1668c2ecf20Sopenharmony_ci reads++; 1678c2ecf20Sopenharmony_ci } while (reads < cinfo[sp_idx].reads); 1688c2ecf20Sopenharmony_ciup: 1698c2ecf20Sopenharmony_ci return RC_PENDOWN | RC_AGAIN; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int wm97xx_acc_startup(struct wm97xx *wm) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci int idx = 0, ret = 0; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* check we have a codec */ 1778c2ecf20Sopenharmony_ci if (wm->ac97 == NULL) 1788c2ecf20Sopenharmony_ci return -ENODEV; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci /* Go you big red fire engine */ 1818c2ecf20Sopenharmony_ci for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) { 1828c2ecf20Sopenharmony_ci if (wm->id != cinfo[idx].id) 1838c2ecf20Sopenharmony_ci continue; 1848c2ecf20Sopenharmony_ci sp_idx = idx; 1858c2ecf20Sopenharmony_ci if (cont_rate <= cinfo[idx].speed) 1868c2ecf20Sopenharmony_ci break; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci wm->acc_rate = cinfo[sp_idx].code; 1898c2ecf20Sopenharmony_ci wm->acc_slot = ac97_touch_slot; 1908c2ecf20Sopenharmony_ci dev_info(wm->dev, 1918c2ecf20Sopenharmony_ci "mainstone accelerated touchscreen driver, %d samples/sec\n", 1928c2ecf20Sopenharmony_ci cinfo[sp_idx].speed); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* IRQ driven touchscreen is used on Palm hardware */ 1958c2ecf20Sopenharmony_ci if (machine_is_palmt5() || machine_is_palmtx() || machine_is_palmld()) { 1968c2ecf20Sopenharmony_ci pen_int = 1; 1978c2ecf20Sopenharmony_ci irq = 27; 1988c2ecf20Sopenharmony_ci /* There is some obscure mutant of WM9712 interbred with WM9713 1998c2ecf20Sopenharmony_ci * used on Palm HW */ 2008c2ecf20Sopenharmony_ci wm->variant = WM97xx_WM1613; 2018c2ecf20Sopenharmony_ci } else if (machine_is_mainstone() && pen_int) 2028c2ecf20Sopenharmony_ci irq = 4; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (irq) { 2058c2ecf20Sopenharmony_ci ret = gpio_request(irq, "Touchscreen IRQ"); 2068c2ecf20Sopenharmony_ci if (ret) 2078c2ecf20Sopenharmony_ci goto out; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci ret = gpio_direction_input(irq); 2108c2ecf20Sopenharmony_ci if (ret) { 2118c2ecf20Sopenharmony_ci gpio_free(irq); 2128c2ecf20Sopenharmony_ci goto out; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci wm->pen_irq = gpio_to_irq(irq); 2168c2ecf20Sopenharmony_ci irq_set_irq_type(wm->pen_irq, IRQ_TYPE_EDGE_BOTH); 2178c2ecf20Sopenharmony_ci } else /* pen irq not supported */ 2188c2ecf20Sopenharmony_ci pen_int = 0; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci /* codec specific irq config */ 2218c2ecf20Sopenharmony_ci if (pen_int) { 2228c2ecf20Sopenharmony_ci switch (wm->id) { 2238c2ecf20Sopenharmony_ci case WM9705_ID2: 2248c2ecf20Sopenharmony_ci break; 2258c2ecf20Sopenharmony_ci case WM9712_ID2: 2268c2ecf20Sopenharmony_ci case WM9713_ID2: 2278c2ecf20Sopenharmony_ci /* use PEN_DOWN GPIO 13 to assert IRQ on GPIO line 2 */ 2288c2ecf20Sopenharmony_ci wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN, 2298c2ecf20Sopenharmony_ci WM97XX_GPIO_POL_HIGH, 2308c2ecf20Sopenharmony_ci WM97XX_GPIO_STICKY, 2318c2ecf20Sopenharmony_ci WM97XX_GPIO_WAKE); 2328c2ecf20Sopenharmony_ci wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT, 2338c2ecf20Sopenharmony_ci WM97XX_GPIO_POL_HIGH, 2348c2ecf20Sopenharmony_ci WM97XX_GPIO_NOTSTICKY, 2358c2ecf20Sopenharmony_ci WM97XX_GPIO_NOWAKE); 2368c2ecf20Sopenharmony_ci break; 2378c2ecf20Sopenharmony_ci default: 2388c2ecf20Sopenharmony_ci dev_err(wm->dev, 2398c2ecf20Sopenharmony_ci "pen down irq not supported on this device\n"); 2408c2ecf20Sopenharmony_ci pen_int = 0; 2418c2ecf20Sopenharmony_ci break; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ciout: 2468c2ecf20Sopenharmony_ci return ret; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic void wm97xx_acc_shutdown(struct wm97xx *wm) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci /* codec specific deconfig */ 2528c2ecf20Sopenharmony_ci if (pen_int) { 2538c2ecf20Sopenharmony_ci if (irq) 2548c2ecf20Sopenharmony_ci gpio_free(irq); 2558c2ecf20Sopenharmony_ci wm->pen_irq = 0; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic void wm97xx_irq_enable(struct wm97xx *wm, int enable) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci if (enable) 2628c2ecf20Sopenharmony_ci enable_irq(wm->pen_irq); 2638c2ecf20Sopenharmony_ci else 2648c2ecf20Sopenharmony_ci disable_irq_nosync(wm->pen_irq); 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cistatic struct wm97xx_mach_ops mainstone_mach_ops = { 2688c2ecf20Sopenharmony_ci .acc_enabled = 1, 2698c2ecf20Sopenharmony_ci .acc_pen_up = wm97xx_acc_pen_up, 2708c2ecf20Sopenharmony_ci .acc_pen_down = wm97xx_acc_pen_down, 2718c2ecf20Sopenharmony_ci .acc_startup = wm97xx_acc_startup, 2728c2ecf20Sopenharmony_ci .acc_shutdown = wm97xx_acc_shutdown, 2738c2ecf20Sopenharmony_ci .irq_enable = wm97xx_irq_enable, 2748c2ecf20Sopenharmony_ci .irq_gpio = WM97XX_GPIO_2, 2758c2ecf20Sopenharmony_ci}; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_cistatic int mainstone_wm97xx_probe(struct platform_device *pdev) 2788c2ecf20Sopenharmony_ci{ 2798c2ecf20Sopenharmony_ci struct wm97xx *wm = platform_get_drvdata(pdev); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci return wm97xx_register_mach_ops(wm, &mainstone_mach_ops); 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic int mainstone_wm97xx_remove(struct platform_device *pdev) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci struct wm97xx *wm = platform_get_drvdata(pdev); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci wm97xx_unregister_mach_ops(wm); 2898c2ecf20Sopenharmony_ci return 0; 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_cistatic struct platform_driver mainstone_wm97xx_driver = { 2938c2ecf20Sopenharmony_ci .probe = mainstone_wm97xx_probe, 2948c2ecf20Sopenharmony_ci .remove = mainstone_wm97xx_remove, 2958c2ecf20Sopenharmony_ci .driver = { 2968c2ecf20Sopenharmony_ci .name = "wm97xx-touch", 2978c2ecf20Sopenharmony_ci }, 2988c2ecf20Sopenharmony_ci}; 2998c2ecf20Sopenharmony_cimodule_platform_driver(mainstone_wm97xx_driver); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci/* Module information */ 3028c2ecf20Sopenharmony_ciMODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); 3038c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("wm97xx continuous touch driver for mainstone"); 3048c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 305