162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Support for viafb GPIO ports. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2009 Jonathan Corbet <corbet@lwn.net> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/spinlock.h> 962306a36Sopenharmony_ci#include <linux/gpio/driver.h> 1062306a36Sopenharmony_ci#include <linux/gpio/machine.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <linux/via-core.h> 1362306a36Sopenharmony_ci#include <linux/export.h> 1462306a36Sopenharmony_ci#include "via-gpio.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci/* 1762306a36Sopenharmony_ci * The ports we know about. Note that the port-25 gpios are not 1862306a36Sopenharmony_ci * mentioned in the datasheet. 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistruct viafb_gpio { 2262306a36Sopenharmony_ci char *vg_name; /* Data sheet name */ 2362306a36Sopenharmony_ci u16 vg_io_port; 2462306a36Sopenharmony_ci u8 vg_port_index; 2562306a36Sopenharmony_ci int vg_mask_shift; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic struct viafb_gpio viafb_all_gpios[] = { 2962306a36Sopenharmony_ci { 3062306a36Sopenharmony_ci .vg_name = "VGPIO0", /* Guess - not in datasheet */ 3162306a36Sopenharmony_ci .vg_io_port = VIASR, 3262306a36Sopenharmony_ci .vg_port_index = 0x25, 3362306a36Sopenharmony_ci .vg_mask_shift = 1 3462306a36Sopenharmony_ci }, 3562306a36Sopenharmony_ci { 3662306a36Sopenharmony_ci .vg_name = "VGPIO1", 3762306a36Sopenharmony_ci .vg_io_port = VIASR, 3862306a36Sopenharmony_ci .vg_port_index = 0x25, 3962306a36Sopenharmony_ci .vg_mask_shift = 0 4062306a36Sopenharmony_ci }, 4162306a36Sopenharmony_ci { 4262306a36Sopenharmony_ci .vg_name = "VGPIO2", /* aka DISPCLKI0 */ 4362306a36Sopenharmony_ci .vg_io_port = VIASR, 4462306a36Sopenharmony_ci .vg_port_index = 0x2c, 4562306a36Sopenharmony_ci .vg_mask_shift = 1 4662306a36Sopenharmony_ci }, 4762306a36Sopenharmony_ci { 4862306a36Sopenharmony_ci .vg_name = "VGPIO3", /* aka DISPCLKO0 */ 4962306a36Sopenharmony_ci .vg_io_port = VIASR, 5062306a36Sopenharmony_ci .vg_port_index = 0x2c, 5162306a36Sopenharmony_ci .vg_mask_shift = 0 5262306a36Sopenharmony_ci }, 5362306a36Sopenharmony_ci { 5462306a36Sopenharmony_ci .vg_name = "VGPIO4", /* DISPCLKI1 */ 5562306a36Sopenharmony_ci .vg_io_port = VIASR, 5662306a36Sopenharmony_ci .vg_port_index = 0x3d, 5762306a36Sopenharmony_ci .vg_mask_shift = 1 5862306a36Sopenharmony_ci }, 5962306a36Sopenharmony_ci { 6062306a36Sopenharmony_ci .vg_name = "VGPIO5", /* DISPCLKO1 */ 6162306a36Sopenharmony_ci .vg_io_port = VIASR, 6262306a36Sopenharmony_ci .vg_port_index = 0x3d, 6362306a36Sopenharmony_ci .vg_mask_shift = 0 6462306a36Sopenharmony_ci }, 6562306a36Sopenharmony_ci}; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define VIAFB_NUM_GPIOS ARRAY_SIZE(viafb_all_gpios) 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/* 7062306a36Sopenharmony_ci * This structure controls the active GPIOs, which may be a subset 7162306a36Sopenharmony_ci * of those which are known. 7262306a36Sopenharmony_ci */ 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistruct viafb_gpio_cfg { 7562306a36Sopenharmony_ci struct gpio_chip gpio_chip; 7662306a36Sopenharmony_ci struct viafb_dev *vdev; 7762306a36Sopenharmony_ci struct viafb_gpio *active_gpios[VIAFB_NUM_GPIOS]; 7862306a36Sopenharmony_ci const char *gpio_names[VIAFB_NUM_GPIOS]; 7962306a36Sopenharmony_ci}; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci/* 8262306a36Sopenharmony_ci * GPIO access functions 8362306a36Sopenharmony_ci */ 8462306a36Sopenharmony_cistatic void via_gpio_set(struct gpio_chip *chip, unsigned int nr, 8562306a36Sopenharmony_ci int value) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct viafb_gpio_cfg *cfg = gpiochip_get_data(chip); 8862306a36Sopenharmony_ci u8 reg; 8962306a36Sopenharmony_ci struct viafb_gpio *gpio; 9062306a36Sopenharmony_ci unsigned long flags; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci spin_lock_irqsave(&cfg->vdev->reg_lock, flags); 9362306a36Sopenharmony_ci gpio = cfg->active_gpios[nr]; 9462306a36Sopenharmony_ci reg = via_read_reg(VIASR, gpio->vg_port_index); 9562306a36Sopenharmony_ci reg |= 0x40 << gpio->vg_mask_shift; /* output enable */ 9662306a36Sopenharmony_ci if (value) 9762306a36Sopenharmony_ci reg |= 0x10 << gpio->vg_mask_shift; 9862306a36Sopenharmony_ci else 9962306a36Sopenharmony_ci reg &= ~(0x10 << gpio->vg_mask_shift); 10062306a36Sopenharmony_ci via_write_reg(VIASR, gpio->vg_port_index, reg); 10162306a36Sopenharmony_ci spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic int via_gpio_dir_out(struct gpio_chip *chip, unsigned int nr, 10562306a36Sopenharmony_ci int value) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci via_gpio_set(chip, nr, value); 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/* 11262306a36Sopenharmony_ci * Set the input direction. I'm not sure this is right; we should 11362306a36Sopenharmony_ci * be able to do input without disabling output. 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_cistatic int via_gpio_dir_input(struct gpio_chip *chip, unsigned int nr) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci struct viafb_gpio_cfg *cfg = gpiochip_get_data(chip); 11862306a36Sopenharmony_ci struct viafb_gpio *gpio; 11962306a36Sopenharmony_ci unsigned long flags; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci spin_lock_irqsave(&cfg->vdev->reg_lock, flags); 12262306a36Sopenharmony_ci gpio = cfg->active_gpios[nr]; 12362306a36Sopenharmony_ci via_write_reg_mask(VIASR, gpio->vg_port_index, 0, 12462306a36Sopenharmony_ci 0x40 << gpio->vg_mask_shift); 12562306a36Sopenharmony_ci spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); 12662306a36Sopenharmony_ci return 0; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic int via_gpio_get(struct gpio_chip *chip, unsigned int nr) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct viafb_gpio_cfg *cfg = gpiochip_get_data(chip); 13262306a36Sopenharmony_ci u8 reg; 13362306a36Sopenharmony_ci struct viafb_gpio *gpio; 13462306a36Sopenharmony_ci unsigned long flags; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci spin_lock_irqsave(&cfg->vdev->reg_lock, flags); 13762306a36Sopenharmony_ci gpio = cfg->active_gpios[nr]; 13862306a36Sopenharmony_ci reg = via_read_reg(VIASR, gpio->vg_port_index); 13962306a36Sopenharmony_ci spin_unlock_irqrestore(&cfg->vdev->reg_lock, flags); 14062306a36Sopenharmony_ci return !!(reg & (0x04 << gpio->vg_mask_shift)); 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic struct viafb_gpio_cfg viafb_gpio_config = { 14562306a36Sopenharmony_ci .gpio_chip = { 14662306a36Sopenharmony_ci .label = "VIAFB onboard GPIO", 14762306a36Sopenharmony_ci .owner = THIS_MODULE, 14862306a36Sopenharmony_ci .direction_output = via_gpio_dir_out, 14962306a36Sopenharmony_ci .set = via_gpio_set, 15062306a36Sopenharmony_ci .direction_input = via_gpio_dir_input, 15162306a36Sopenharmony_ci .get = via_gpio_get, 15262306a36Sopenharmony_ci .base = -1, 15362306a36Sopenharmony_ci .ngpio = 0, 15462306a36Sopenharmony_ci .can_sleep = 0 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci}; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci/* 15962306a36Sopenharmony_ci * Manage the software enable bit. 16062306a36Sopenharmony_ci */ 16162306a36Sopenharmony_cistatic void viafb_gpio_enable(struct viafb_gpio *gpio) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci via_write_reg_mask(VIASR, gpio->vg_port_index, 0x02, 0x02); 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic void viafb_gpio_disable(struct viafb_gpio *gpio) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci via_write_reg_mask(VIASR, gpio->vg_port_index, 0, 0x02); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci#ifdef CONFIG_PM 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic int viafb_gpio_suspend(void *private) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci return 0; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic int viafb_gpio_resume(void *private) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci int i; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) 18362306a36Sopenharmony_ci viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); 18462306a36Sopenharmony_ci return 0; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic struct viafb_pm_hooks viafb_gpio_pm_hooks = { 18862306a36Sopenharmony_ci .suspend = viafb_gpio_suspend, 18962306a36Sopenharmony_ci .resume = viafb_gpio_resume 19062306a36Sopenharmony_ci}; 19162306a36Sopenharmony_ci#endif /* CONFIG_PM */ 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic struct gpiod_lookup_table viafb_gpio_table = { 19462306a36Sopenharmony_ci .dev_id = "viafb-camera", 19562306a36Sopenharmony_ci .table = { 19662306a36Sopenharmony_ci GPIO_LOOKUP("via-gpio", 2, "VGPIO2", GPIO_ACTIVE_LOW), 19762306a36Sopenharmony_ci GPIO_LOOKUP("via-gpio", 3, "VGPIO3", GPIO_ACTIVE_HIGH), 19862306a36Sopenharmony_ci { } 19962306a36Sopenharmony_ci }, 20062306a36Sopenharmony_ci}; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci/* 20362306a36Sopenharmony_ci * Platform device stuff. 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_cistatic int viafb_gpio_probe(struct platform_device *platdev) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct viafb_dev *vdev = platdev->dev.platform_data; 20862306a36Sopenharmony_ci struct via_port_cfg *port_cfg = vdev->port_cfg; 20962306a36Sopenharmony_ci int i, ngpio = 0, ret; 21062306a36Sopenharmony_ci struct viafb_gpio *gpio; 21162306a36Sopenharmony_ci unsigned long flags; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* 21462306a36Sopenharmony_ci * Set up entries for all GPIOs which have been configured to 21562306a36Sopenharmony_ci * operate as such (as opposed to as i2c ports). 21662306a36Sopenharmony_ci */ 21762306a36Sopenharmony_ci for (i = 0; i < VIAFB_NUM_PORTS; i++) { 21862306a36Sopenharmony_ci if (port_cfg[i].mode != VIA_MODE_GPIO) 21962306a36Sopenharmony_ci continue; 22062306a36Sopenharmony_ci for (gpio = viafb_all_gpios; 22162306a36Sopenharmony_ci gpio < viafb_all_gpios + VIAFB_NUM_GPIOS; gpio++) 22262306a36Sopenharmony_ci if (gpio->vg_port_index == port_cfg[i].ioport_index) { 22362306a36Sopenharmony_ci viafb_gpio_config.active_gpios[ngpio] = gpio; 22462306a36Sopenharmony_ci viafb_gpio_config.gpio_names[ngpio] = 22562306a36Sopenharmony_ci gpio->vg_name; 22662306a36Sopenharmony_ci ngpio++; 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci viafb_gpio_config.gpio_chip.ngpio = ngpio; 23062306a36Sopenharmony_ci viafb_gpio_config.gpio_chip.names = viafb_gpio_config.gpio_names; 23162306a36Sopenharmony_ci viafb_gpio_config.vdev = vdev; 23262306a36Sopenharmony_ci if (ngpio == 0) { 23362306a36Sopenharmony_ci printk(KERN_INFO "viafb: no GPIOs configured\n"); 23462306a36Sopenharmony_ci return 0; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci /* 23762306a36Sopenharmony_ci * Enable the ports. They come in pairs, with a single 23862306a36Sopenharmony_ci * enable bit for both. 23962306a36Sopenharmony_ci */ 24062306a36Sopenharmony_ci spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); 24162306a36Sopenharmony_ci for (i = 0; i < ngpio; i += 2) 24262306a36Sopenharmony_ci viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); 24362306a36Sopenharmony_ci spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); 24462306a36Sopenharmony_ci /* 24562306a36Sopenharmony_ci * Get registered. 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_ci viafb_gpio_config.gpio_chip.base = -1; /* Dynamic */ 24862306a36Sopenharmony_ci viafb_gpio_config.gpio_chip.label = "via-gpio"; 24962306a36Sopenharmony_ci ret = gpiochip_add_data(&viafb_gpio_config.gpio_chip, 25062306a36Sopenharmony_ci &viafb_gpio_config); 25162306a36Sopenharmony_ci if (ret) { 25262306a36Sopenharmony_ci printk(KERN_ERR "viafb: failed to add gpios (%d)\n", ret); 25362306a36Sopenharmony_ci viafb_gpio_config.gpio_chip.ngpio = 0; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci gpiod_add_lookup_table(&viafb_gpio_table); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci#ifdef CONFIG_PM 25962306a36Sopenharmony_ci viafb_pm_register(&viafb_gpio_pm_hooks); 26062306a36Sopenharmony_ci#endif 26162306a36Sopenharmony_ci return ret; 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic void viafb_gpio_remove(struct platform_device *platdev) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci unsigned long flags; 26862306a36Sopenharmony_ci int i; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci#ifdef CONFIG_PM 27162306a36Sopenharmony_ci viafb_pm_unregister(&viafb_gpio_pm_hooks); 27262306a36Sopenharmony_ci#endif 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci /* 27562306a36Sopenharmony_ci * Get unregistered. 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_ci if (viafb_gpio_config.gpio_chip.ngpio > 0) { 27862306a36Sopenharmony_ci gpiochip_remove(&viafb_gpio_config.gpio_chip); 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci /* 28162306a36Sopenharmony_ci * Disable the ports. 28262306a36Sopenharmony_ci */ 28362306a36Sopenharmony_ci spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); 28462306a36Sopenharmony_ci for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) 28562306a36Sopenharmony_ci viafb_gpio_disable(viafb_gpio_config.active_gpios[i]); 28662306a36Sopenharmony_ci viafb_gpio_config.gpio_chip.ngpio = 0; 28762306a36Sopenharmony_ci spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic struct platform_driver via_gpio_driver = { 29162306a36Sopenharmony_ci .driver = { 29262306a36Sopenharmony_ci .name = "viafb-gpio", 29362306a36Sopenharmony_ci }, 29462306a36Sopenharmony_ci .probe = viafb_gpio_probe, 29562306a36Sopenharmony_ci .remove_new = viafb_gpio_remove, 29662306a36Sopenharmony_ci}; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ciint viafb_gpio_init(void) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci return platform_driver_register(&via_gpio_driver); 30162306a36Sopenharmony_ci} 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_civoid viafb_gpio_exit(void) 30462306a36Sopenharmony_ci{ 30562306a36Sopenharmony_ci platform_driver_unregister(&via_gpio_driver); 30662306a36Sopenharmony_ci} 307