18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Support for viafb GPIO ports. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2009 Jonathan Corbet <corbet@lwn.net> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 98c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/via-core.h> 128c2ecf20Sopenharmony_ci#include <linux/via-gpio.h> 138c2ecf20Sopenharmony_ci#include <linux/export.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci/* 168c2ecf20Sopenharmony_ci * The ports we know about. Note that the port-25 gpios are not 178c2ecf20Sopenharmony_ci * mentioned in the datasheet. 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct viafb_gpio { 218c2ecf20Sopenharmony_ci char *vg_name; /* Data sheet name */ 228c2ecf20Sopenharmony_ci u16 vg_io_port; 238c2ecf20Sopenharmony_ci u8 vg_port_index; 248c2ecf20Sopenharmony_ci int vg_mask_shift; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic struct viafb_gpio viafb_all_gpios[] = { 288c2ecf20Sopenharmony_ci { 298c2ecf20Sopenharmony_ci .vg_name = "VGPIO0", /* Guess - not in datasheet */ 308c2ecf20Sopenharmony_ci .vg_io_port = VIASR, 318c2ecf20Sopenharmony_ci .vg_port_index = 0x25, 328c2ecf20Sopenharmony_ci .vg_mask_shift = 1 338c2ecf20Sopenharmony_ci }, 348c2ecf20Sopenharmony_ci { 358c2ecf20Sopenharmony_ci .vg_name = "VGPIO1", 368c2ecf20Sopenharmony_ci .vg_io_port = VIASR, 378c2ecf20Sopenharmony_ci .vg_port_index = 0x25, 388c2ecf20Sopenharmony_ci .vg_mask_shift = 0 398c2ecf20Sopenharmony_ci }, 408c2ecf20Sopenharmony_ci { 418c2ecf20Sopenharmony_ci .vg_name = "VGPIO2", /* aka DISPCLKI0 */ 428c2ecf20Sopenharmony_ci .vg_io_port = VIASR, 438c2ecf20Sopenharmony_ci .vg_port_index = 0x2c, 448c2ecf20Sopenharmony_ci .vg_mask_shift = 1 458c2ecf20Sopenharmony_ci }, 468c2ecf20Sopenharmony_ci { 478c2ecf20Sopenharmony_ci .vg_name = "VGPIO3", /* aka DISPCLKO0 */ 488c2ecf20Sopenharmony_ci .vg_io_port = VIASR, 498c2ecf20Sopenharmony_ci .vg_port_index = 0x2c, 508c2ecf20Sopenharmony_ci .vg_mask_shift = 0 518c2ecf20Sopenharmony_ci }, 528c2ecf20Sopenharmony_ci { 538c2ecf20Sopenharmony_ci .vg_name = "VGPIO4", /* DISPCLKI1 */ 548c2ecf20Sopenharmony_ci .vg_io_port = VIASR, 558c2ecf20Sopenharmony_ci .vg_port_index = 0x3d, 568c2ecf20Sopenharmony_ci .vg_mask_shift = 1 578c2ecf20Sopenharmony_ci }, 588c2ecf20Sopenharmony_ci { 598c2ecf20Sopenharmony_ci .vg_name = "VGPIO5", /* DISPCLKO1 */ 608c2ecf20Sopenharmony_ci .vg_io_port = VIASR, 618c2ecf20Sopenharmony_ci .vg_port_index = 0x3d, 628c2ecf20Sopenharmony_ci .vg_mask_shift = 0 638c2ecf20Sopenharmony_ci }, 648c2ecf20Sopenharmony_ci}; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci#define VIAFB_NUM_GPIOS ARRAY_SIZE(viafb_all_gpios) 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/* 698c2ecf20Sopenharmony_ci * This structure controls the active GPIOs, which may be a subset 708c2ecf20Sopenharmony_ci * of those which are known. 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistruct viafb_gpio_cfg { 748c2ecf20Sopenharmony_ci struct gpio_chip gpio_chip; 758c2ecf20Sopenharmony_ci struct viafb_dev *vdev; 768c2ecf20Sopenharmony_ci struct viafb_gpio *active_gpios[VIAFB_NUM_GPIOS]; 778c2ecf20Sopenharmony_ci const char *gpio_names[VIAFB_NUM_GPIOS]; 788c2ecf20Sopenharmony_ci}; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci/* 818c2ecf20Sopenharmony_ci * GPIO access functions 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_cistatic void via_gpio_set(struct gpio_chip *chip, unsigned int nr, 848c2ecf20Sopenharmony_ci int value) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct viafb_gpio_cfg *cfg = gpiochip_get_data(chip); 878c2ecf20Sopenharmony_ci u8 reg; 888c2ecf20Sopenharmony_ci struct viafb_gpio *gpio; 898c2ecf20Sopenharmony_ci unsigned long flags; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci spin_lock_irqsave(&cfg->vdev->reg_lock, flags); 928c2ecf20Sopenharmony_ci gpio = cfg->active_gpios[nr]; 938c2ecf20Sopenharmony_ci reg = via_read_reg(VIASR, gpio->vg_port_index); 948c2ecf20Sopenharmony_ci reg |= 0x40 << gpio->vg_mask_shift; /* output enable */ 958c2ecf20Sopenharmony_ci if (value) 968c2ecf20Sopenharmony_ci reg |= 0x10 << gpio->vg_mask_shift; 978c2ecf20Sopenharmony_ci else 988c2ecf20Sopenharmony_ci reg &= ~(0x10 << gpio->vg_mask_shift); 998c2ecf20Sopenharmony_ci via_write_reg(VIASR, gpio->vg_port_index, reg); 1008c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic int via_gpio_dir_out(struct gpio_chip *chip, unsigned int nr, 1048c2ecf20Sopenharmony_ci int value) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci via_gpio_set(chip, nr, value); 1078c2ecf20Sopenharmony_ci return 0; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci/* 1118c2ecf20Sopenharmony_ci * Set the input direction. I'm not sure this is right; we should 1128c2ecf20Sopenharmony_ci * be able to do input without disabling output. 1138c2ecf20Sopenharmony_ci */ 1148c2ecf20Sopenharmony_cistatic int via_gpio_dir_input(struct gpio_chip *chip, unsigned int nr) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci struct viafb_gpio_cfg *cfg = gpiochip_get_data(chip); 1178c2ecf20Sopenharmony_ci struct viafb_gpio *gpio; 1188c2ecf20Sopenharmony_ci unsigned long flags; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci spin_lock_irqsave(&cfg->vdev->reg_lock, flags); 1218c2ecf20Sopenharmony_ci gpio = cfg->active_gpios[nr]; 1228c2ecf20Sopenharmony_ci via_write_reg_mask(VIASR, gpio->vg_port_index, 0, 1238c2ecf20Sopenharmony_ci 0x40 << gpio->vg_mask_shift); 1248c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); 1258c2ecf20Sopenharmony_ci return 0; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int via_gpio_get(struct gpio_chip *chip, unsigned int nr) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct viafb_gpio_cfg *cfg = gpiochip_get_data(chip); 1318c2ecf20Sopenharmony_ci u8 reg; 1328c2ecf20Sopenharmony_ci struct viafb_gpio *gpio; 1338c2ecf20Sopenharmony_ci unsigned long flags; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci spin_lock_irqsave(&cfg->vdev->reg_lock, flags); 1368c2ecf20Sopenharmony_ci gpio = cfg->active_gpios[nr]; 1378c2ecf20Sopenharmony_ci reg = via_read_reg(VIASR, gpio->vg_port_index); 1388c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); 1398c2ecf20Sopenharmony_ci return !!(reg & (0x04 << gpio->vg_mask_shift)); 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic struct viafb_gpio_cfg viafb_gpio_config = { 1448c2ecf20Sopenharmony_ci .gpio_chip = { 1458c2ecf20Sopenharmony_ci .label = "VIAFB onboard GPIO", 1468c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1478c2ecf20Sopenharmony_ci .direction_output = via_gpio_dir_out, 1488c2ecf20Sopenharmony_ci .set = via_gpio_set, 1498c2ecf20Sopenharmony_ci .direction_input = via_gpio_dir_input, 1508c2ecf20Sopenharmony_ci .get = via_gpio_get, 1518c2ecf20Sopenharmony_ci .base = -1, 1528c2ecf20Sopenharmony_ci .ngpio = 0, 1538c2ecf20Sopenharmony_ci .can_sleep = 0 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci}; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci/* 1588c2ecf20Sopenharmony_ci * Manage the software enable bit. 1598c2ecf20Sopenharmony_ci */ 1608c2ecf20Sopenharmony_cistatic void viafb_gpio_enable(struct viafb_gpio *gpio) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci via_write_reg_mask(VIASR, gpio->vg_port_index, 0x02, 0x02); 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic void viafb_gpio_disable(struct viafb_gpio *gpio) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci via_write_reg_mask(VIASR, gpio->vg_port_index, 0, 0x02); 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int viafb_gpio_suspend(void *private) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci return 0; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic int viafb_gpio_resume(void *private) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci int i; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) 1828c2ecf20Sopenharmony_ci viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); 1838c2ecf20Sopenharmony_ci return 0; 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic struct viafb_pm_hooks viafb_gpio_pm_hooks = { 1878c2ecf20Sopenharmony_ci .suspend = viafb_gpio_suspend, 1888c2ecf20Sopenharmony_ci .resume = viafb_gpio_resume 1898c2ecf20Sopenharmony_ci}; 1908c2ecf20Sopenharmony_ci#endif /* CONFIG_PM */ 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci/* 1938c2ecf20Sopenharmony_ci * Look up a specific gpio and return the number it was assigned. 1948c2ecf20Sopenharmony_ci */ 1958c2ecf20Sopenharmony_ciint viafb_gpio_lookup(const char *name) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci int i; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i++) 2008c2ecf20Sopenharmony_ci if (!strcmp(name, viafb_gpio_config.active_gpios[i]->vg_name)) 2018c2ecf20Sopenharmony_ci return viafb_gpio_config.gpio_chip.base + i; 2028c2ecf20Sopenharmony_ci return -1; 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(viafb_gpio_lookup); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci/* 2078c2ecf20Sopenharmony_ci * Platform device stuff. 2088c2ecf20Sopenharmony_ci */ 2098c2ecf20Sopenharmony_cistatic int viafb_gpio_probe(struct platform_device *platdev) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci struct viafb_dev *vdev = platdev->dev.platform_data; 2128c2ecf20Sopenharmony_ci struct via_port_cfg *port_cfg = vdev->port_cfg; 2138c2ecf20Sopenharmony_ci int i, ngpio = 0, ret; 2148c2ecf20Sopenharmony_ci struct viafb_gpio *gpio; 2158c2ecf20Sopenharmony_ci unsigned long flags; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci /* 2188c2ecf20Sopenharmony_ci * Set up entries for all GPIOs which have been configured to 2198c2ecf20Sopenharmony_ci * operate as such (as opposed to as i2c ports). 2208c2ecf20Sopenharmony_ci */ 2218c2ecf20Sopenharmony_ci for (i = 0; i < VIAFB_NUM_PORTS; i++) { 2228c2ecf20Sopenharmony_ci if (port_cfg[i].mode != VIA_MODE_GPIO) 2238c2ecf20Sopenharmony_ci continue; 2248c2ecf20Sopenharmony_ci for (gpio = viafb_all_gpios; 2258c2ecf20Sopenharmony_ci gpio < viafb_all_gpios + VIAFB_NUM_GPIOS; gpio++) 2268c2ecf20Sopenharmony_ci if (gpio->vg_port_index == port_cfg[i].ioport_index) { 2278c2ecf20Sopenharmony_ci viafb_gpio_config.active_gpios[ngpio] = gpio; 2288c2ecf20Sopenharmony_ci viafb_gpio_config.gpio_names[ngpio] = 2298c2ecf20Sopenharmony_ci gpio->vg_name; 2308c2ecf20Sopenharmony_ci ngpio++; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci viafb_gpio_config.gpio_chip.ngpio = ngpio; 2348c2ecf20Sopenharmony_ci viafb_gpio_config.gpio_chip.names = viafb_gpio_config.gpio_names; 2358c2ecf20Sopenharmony_ci viafb_gpio_config.vdev = vdev; 2368c2ecf20Sopenharmony_ci if (ngpio == 0) { 2378c2ecf20Sopenharmony_ci printk(KERN_INFO "viafb: no GPIOs configured\n"); 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci /* 2418c2ecf20Sopenharmony_ci * Enable the ports. They come in pairs, with a single 2428c2ecf20Sopenharmony_ci * enable bit for both. 2438c2ecf20Sopenharmony_ci */ 2448c2ecf20Sopenharmony_ci spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); 2458c2ecf20Sopenharmony_ci for (i = 0; i < ngpio; i += 2) 2468c2ecf20Sopenharmony_ci viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); 2478c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); 2488c2ecf20Sopenharmony_ci /* 2498c2ecf20Sopenharmony_ci * Get registered. 2508c2ecf20Sopenharmony_ci */ 2518c2ecf20Sopenharmony_ci viafb_gpio_config.gpio_chip.base = -1; /* Dynamic */ 2528c2ecf20Sopenharmony_ci ret = gpiochip_add_data(&viafb_gpio_config.gpio_chip, 2538c2ecf20Sopenharmony_ci &viafb_gpio_config); 2548c2ecf20Sopenharmony_ci if (ret) { 2558c2ecf20Sopenharmony_ci printk(KERN_ERR "viafb: failed to add gpios (%d)\n", ret); 2568c2ecf20Sopenharmony_ci viafb_gpio_config.gpio_chip.ngpio = 0; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2598c2ecf20Sopenharmony_ci viafb_pm_register(&viafb_gpio_pm_hooks); 2608c2ecf20Sopenharmony_ci#endif 2618c2ecf20Sopenharmony_ci return ret; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic int viafb_gpio_remove(struct platform_device *platdev) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci unsigned long flags; 2688c2ecf20Sopenharmony_ci int i; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2718c2ecf20Sopenharmony_ci viafb_pm_unregister(&viafb_gpio_pm_hooks); 2728c2ecf20Sopenharmony_ci#endif 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci /* 2758c2ecf20Sopenharmony_ci * Get unregistered. 2768c2ecf20Sopenharmony_ci */ 2778c2ecf20Sopenharmony_ci if (viafb_gpio_config.gpio_chip.ngpio > 0) { 2788c2ecf20Sopenharmony_ci gpiochip_remove(&viafb_gpio_config.gpio_chip); 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci /* 2818c2ecf20Sopenharmony_ci * Disable the ports. 2828c2ecf20Sopenharmony_ci */ 2838c2ecf20Sopenharmony_ci spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); 2848c2ecf20Sopenharmony_ci for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) 2858c2ecf20Sopenharmony_ci viafb_gpio_disable(viafb_gpio_config.active_gpios[i]); 2868c2ecf20Sopenharmony_ci viafb_gpio_config.gpio_chip.ngpio = 0; 2878c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); 2888c2ecf20Sopenharmony_ci return 0; 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic struct platform_driver via_gpio_driver = { 2928c2ecf20Sopenharmony_ci .driver = { 2938c2ecf20Sopenharmony_ci .name = "viafb-gpio", 2948c2ecf20Sopenharmony_ci }, 2958c2ecf20Sopenharmony_ci .probe = viafb_gpio_probe, 2968c2ecf20Sopenharmony_ci .remove = viafb_gpio_remove, 2978c2ecf20Sopenharmony_ci}; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ciint viafb_gpio_init(void) 3008c2ecf20Sopenharmony_ci{ 3018c2ecf20Sopenharmony_ci return platform_driver_register(&via_gpio_driver); 3028c2ecf20Sopenharmony_ci} 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_civoid viafb_gpio_exit(void) 3058c2ecf20Sopenharmony_ci{ 3068c2ecf20Sopenharmony_ci platform_driver_unregister(&via_gpio_driver); 3078c2ecf20Sopenharmony_ci} 308