162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * SYSCON GPIO driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/err.h> 962306a36Sopenharmony_ci#include <linux/gpio/driver.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/of.h> 1262306a36Sopenharmony_ci#include <linux/platform_device.h> 1362306a36Sopenharmony_ci#include <linux/regmap.h> 1462306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define GPIO_SYSCON_FEAT_IN BIT(0) 1762306a36Sopenharmony_ci#define GPIO_SYSCON_FEAT_OUT BIT(1) 1862306a36Sopenharmony_ci#define GPIO_SYSCON_FEAT_DIR BIT(2) 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* SYSCON driver is designed to use 32-bit wide registers */ 2162306a36Sopenharmony_ci#define SYSCON_REG_SIZE (4) 2262306a36Sopenharmony_ci#define SYSCON_REG_BITS (SYSCON_REG_SIZE * 8) 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci/** 2562306a36Sopenharmony_ci * struct syscon_gpio_data - Configuration for the device. 2662306a36Sopenharmony_ci * @compatible: SYSCON driver compatible string. 2762306a36Sopenharmony_ci * @flags: Set of GPIO_SYSCON_FEAT_ flags: 2862306a36Sopenharmony_ci * GPIO_SYSCON_FEAT_IN: GPIOs supports input, 2962306a36Sopenharmony_ci * GPIO_SYSCON_FEAT_OUT: GPIOs supports output, 3062306a36Sopenharmony_ci * GPIO_SYSCON_FEAT_DIR: GPIOs supports switch direction. 3162306a36Sopenharmony_ci * @bit_count: Number of bits used as GPIOs. 3262306a36Sopenharmony_ci * @dat_bit_offset: Offset (in bits) to the first GPIO bit. 3362306a36Sopenharmony_ci * @dir_bit_offset: Optional offset (in bits) to the first bit to switch 3462306a36Sopenharmony_ci * GPIO direction (Used with GPIO_SYSCON_FEAT_DIR flag). 3562306a36Sopenharmony_ci * @set: HW specific callback to assigns output value 3662306a36Sopenharmony_ci * for signal "offset" 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct syscon_gpio_data { 4062306a36Sopenharmony_ci unsigned int flags; 4162306a36Sopenharmony_ci unsigned int bit_count; 4262306a36Sopenharmony_ci unsigned int dat_bit_offset; 4362306a36Sopenharmony_ci unsigned int dir_bit_offset; 4462306a36Sopenharmony_ci void (*set)(struct gpio_chip *chip, 4562306a36Sopenharmony_ci unsigned offset, int value); 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct syscon_gpio_priv { 4962306a36Sopenharmony_ci struct gpio_chip chip; 5062306a36Sopenharmony_ci struct regmap *syscon; 5162306a36Sopenharmony_ci const struct syscon_gpio_data *data; 5262306a36Sopenharmony_ci u32 dreg_offset; 5362306a36Sopenharmony_ci u32 dir_reg_offset; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int syscon_gpio_get(struct gpio_chip *chip, unsigned offset) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 5962306a36Sopenharmony_ci unsigned int val, offs; 6062306a36Sopenharmony_ci int ret; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ret = regmap_read(priv->syscon, 6562306a36Sopenharmony_ci (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val); 6662306a36Sopenharmony_ci if (ret) 6762306a36Sopenharmony_ci return ret; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return !!(val & BIT(offs % SYSCON_REG_BITS)); 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 7562306a36Sopenharmony_ci unsigned int offs; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci regmap_update_bits(priv->syscon, 8062306a36Sopenharmony_ci (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 8162306a36Sopenharmony_ci BIT(offs % SYSCON_REG_BITS), 8262306a36Sopenharmony_ci val ? BIT(offs % SYSCON_REG_BITS) : 0); 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { 9062306a36Sopenharmony_ci unsigned int offs; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci offs = priv->dir_reg_offset + 9362306a36Sopenharmony_ci priv->data->dir_bit_offset + offset; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci regmap_update_bits(priv->syscon, 9662306a36Sopenharmony_ci (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 9762306a36Sopenharmony_ci BIT(offs % SYSCON_REG_BITS), 0); 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci return 0; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { 10862306a36Sopenharmony_ci unsigned int offs; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci offs = priv->dir_reg_offset + 11162306a36Sopenharmony_ci priv->data->dir_bit_offset + offset; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci regmap_update_bits(priv->syscon, 11462306a36Sopenharmony_ci (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 11562306a36Sopenharmony_ci BIT(offs % SYSCON_REG_BITS), 11662306a36Sopenharmony_ci BIT(offs % SYSCON_REG_BITS)); 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci chip->set(chip, offset, val); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return 0; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic const struct syscon_gpio_data clps711x_mctrl_gpio = { 12562306a36Sopenharmony_ci /* ARM CLPS711X SYSFLG1 Bits 8-10 */ 12662306a36Sopenharmony_ci .flags = GPIO_SYSCON_FEAT_IN, 12762306a36Sopenharmony_ci .bit_count = 3, 12862306a36Sopenharmony_ci .dat_bit_offset = 0x40 * 8 + 8, 12962306a36Sopenharmony_ci}; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic void rockchip_gpio_set(struct gpio_chip *chip, unsigned int offset, 13262306a36Sopenharmony_ci int val) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 13562306a36Sopenharmony_ci unsigned int offs; 13662306a36Sopenharmony_ci u8 bit; 13762306a36Sopenharmony_ci u32 data; 13862306a36Sopenharmony_ci int ret; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 14162306a36Sopenharmony_ci bit = offs % SYSCON_REG_BITS; 14262306a36Sopenharmony_ci data = (val ? BIT(bit) : 0) | BIT(bit + 16); 14362306a36Sopenharmony_ci ret = regmap_write(priv->syscon, 14462306a36Sopenharmony_ci (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 14562306a36Sopenharmony_ci data); 14662306a36Sopenharmony_ci if (ret < 0) 14762306a36Sopenharmony_ci dev_err(chip->parent, "gpio write failed ret(%d)\n", ret); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic const struct syscon_gpio_data rockchip_rk3328_gpio_mute = { 15162306a36Sopenharmony_ci /* RK3328 GPIO_MUTE is an output only pin at GRF_SOC_CON10[1] */ 15262306a36Sopenharmony_ci .flags = GPIO_SYSCON_FEAT_OUT, 15362306a36Sopenharmony_ci .bit_count = 1, 15462306a36Sopenharmony_ci .dat_bit_offset = 0x0428 * 8 + 1, 15562306a36Sopenharmony_ci .set = rockchip_gpio_set, 15662306a36Sopenharmony_ci}; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci#define KEYSTONE_LOCK_BIT BIT(0) 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic void keystone_gpio_set(struct gpio_chip *chip, unsigned offset, int val) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 16362306a36Sopenharmony_ci unsigned int offs; 16462306a36Sopenharmony_ci int ret; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (!val) 16962306a36Sopenharmony_ci return; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci ret = regmap_update_bits( 17262306a36Sopenharmony_ci priv->syscon, 17362306a36Sopenharmony_ci (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 17462306a36Sopenharmony_ci BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT, 17562306a36Sopenharmony_ci BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT); 17662306a36Sopenharmony_ci if (ret < 0) 17762306a36Sopenharmony_ci dev_err(chip->parent, "gpio write failed ret(%d)\n", ret); 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic const struct syscon_gpio_data keystone_dsp_gpio = { 18162306a36Sopenharmony_ci /* ARM Keystone 2 */ 18262306a36Sopenharmony_ci .flags = GPIO_SYSCON_FEAT_OUT, 18362306a36Sopenharmony_ci .bit_count = 28, 18462306a36Sopenharmony_ci .dat_bit_offset = 4, 18562306a36Sopenharmony_ci .set = keystone_gpio_set, 18662306a36Sopenharmony_ci}; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic const struct of_device_id syscon_gpio_ids[] = { 18962306a36Sopenharmony_ci { 19062306a36Sopenharmony_ci .compatible = "cirrus,ep7209-mctrl-gpio", 19162306a36Sopenharmony_ci .data = &clps711x_mctrl_gpio, 19262306a36Sopenharmony_ci }, 19362306a36Sopenharmony_ci { 19462306a36Sopenharmony_ci .compatible = "ti,keystone-dsp-gpio", 19562306a36Sopenharmony_ci .data = &keystone_dsp_gpio, 19662306a36Sopenharmony_ci }, 19762306a36Sopenharmony_ci { 19862306a36Sopenharmony_ci .compatible = "rockchip,rk3328-grf-gpio", 19962306a36Sopenharmony_ci .data = &rockchip_rk3328_gpio_mute, 20062306a36Sopenharmony_ci }, 20162306a36Sopenharmony_ci { } 20262306a36Sopenharmony_ci}; 20362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, syscon_gpio_ids); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic int syscon_gpio_probe(struct platform_device *pdev) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct device *dev = &pdev->dev; 20862306a36Sopenharmony_ci struct syscon_gpio_priv *priv; 20962306a36Sopenharmony_ci struct device_node *np = dev->of_node; 21062306a36Sopenharmony_ci int ret; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 21362306a36Sopenharmony_ci if (!priv) 21462306a36Sopenharmony_ci return -ENOMEM; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci priv->data = of_device_get_match_data(dev); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci priv->syscon = syscon_regmap_lookup_by_phandle(np, "gpio,syscon-dev"); 21962306a36Sopenharmony_ci if (IS_ERR(priv->syscon) && np->parent) 22062306a36Sopenharmony_ci priv->syscon = syscon_node_to_regmap(np->parent); 22162306a36Sopenharmony_ci if (IS_ERR(priv->syscon)) 22262306a36Sopenharmony_ci return PTR_ERR(priv->syscon); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1, 22562306a36Sopenharmony_ci &priv->dreg_offset); 22662306a36Sopenharmony_ci if (ret) 22762306a36Sopenharmony_ci dev_err(dev, "can't read the data register offset!\n"); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci priv->dreg_offset <<= 3; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2, 23262306a36Sopenharmony_ci &priv->dir_reg_offset); 23362306a36Sopenharmony_ci if (ret) 23462306a36Sopenharmony_ci dev_dbg(dev, "can't read the dir register offset!\n"); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci priv->dir_reg_offset <<= 3; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci priv->chip.parent = dev; 23962306a36Sopenharmony_ci priv->chip.owner = THIS_MODULE; 24062306a36Sopenharmony_ci priv->chip.label = dev_name(dev); 24162306a36Sopenharmony_ci priv->chip.base = -1; 24262306a36Sopenharmony_ci priv->chip.ngpio = priv->data->bit_count; 24362306a36Sopenharmony_ci priv->chip.get = syscon_gpio_get; 24462306a36Sopenharmony_ci if (priv->data->flags & GPIO_SYSCON_FEAT_IN) 24562306a36Sopenharmony_ci priv->chip.direction_input = syscon_gpio_dir_in; 24662306a36Sopenharmony_ci if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) { 24762306a36Sopenharmony_ci priv->chip.set = priv->data->set ? : syscon_gpio_set; 24862306a36Sopenharmony_ci priv->chip.direction_output = syscon_gpio_dir_out; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci return devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv); 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic struct platform_driver syscon_gpio_driver = { 25562306a36Sopenharmony_ci .driver = { 25662306a36Sopenharmony_ci .name = "gpio-syscon", 25762306a36Sopenharmony_ci .of_match_table = syscon_gpio_ids, 25862306a36Sopenharmony_ci }, 25962306a36Sopenharmony_ci .probe = syscon_gpio_probe, 26062306a36Sopenharmony_ci}; 26162306a36Sopenharmony_cimodule_platform_driver(syscon_gpio_driver); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ciMODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 26462306a36Sopenharmony_ciMODULE_DESCRIPTION("SYSCON GPIO driver"); 26562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 266