gpio-syscon.c (7193B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * SYSCON GPIO driver 4 * 5 * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> 6 */ 7 8#include <linux/err.h> 9#include <linux/gpio/driver.h> 10#include <linux/module.h> 11#include <linux/of.h> 12#include <linux/of_device.h> 13#include <linux/platform_device.h> 14#include <linux/regmap.h> 15#include <linux/mfd/syscon.h> 16 17#define GPIO_SYSCON_FEAT_IN BIT(0) 18#define GPIO_SYSCON_FEAT_OUT BIT(1) 19#define GPIO_SYSCON_FEAT_DIR BIT(2) 20 21/* SYSCON driver is designed to use 32-bit wide registers */ 22#define SYSCON_REG_SIZE (4) 23#define SYSCON_REG_BITS (SYSCON_REG_SIZE * 8) 24 25/** 26 * struct syscon_gpio_data - Configuration for the device. 27 * @compatible: SYSCON driver compatible string. 28 * @flags: Set of GPIO_SYSCON_FEAT_ flags: 29 * GPIO_SYSCON_FEAT_IN: GPIOs supports input, 30 * GPIO_SYSCON_FEAT_OUT: GPIOs supports output, 31 * GPIO_SYSCON_FEAT_DIR: GPIOs supports switch direction. 32 * @bit_count: Number of bits used as GPIOs. 33 * @dat_bit_offset: Offset (in bits) to the first GPIO bit. 34 * @dir_bit_offset: Optional offset (in bits) to the first bit to switch 35 * GPIO direction (Used with GPIO_SYSCON_FEAT_DIR flag). 36 * @set: HW specific callback to assigns output value 37 * for signal "offset" 38 */ 39 40struct syscon_gpio_data { 41 unsigned int flags; 42 unsigned int bit_count; 43 unsigned int dat_bit_offset; 44 unsigned int dir_bit_offset; 45 void (*set)(struct gpio_chip *chip, 46 unsigned offset, int value); 47}; 48 49struct syscon_gpio_priv { 50 struct gpio_chip chip; 51 struct regmap *syscon; 52 const struct syscon_gpio_data *data; 53 u32 dreg_offset; 54 u32 dir_reg_offset; 55}; 56 57static int syscon_gpio_get(struct gpio_chip *chip, unsigned offset) 58{ 59 struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 60 unsigned int val, offs; 61 int ret; 62 63 offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 64 65 ret = regmap_read(priv->syscon, 66 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val); 67 if (ret) 68 return ret; 69 70 return !!(val & BIT(offs % SYSCON_REG_BITS)); 71} 72 73static void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val) 74{ 75 struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 76 unsigned int offs; 77 78 offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 79 80 regmap_update_bits(priv->syscon, 81 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 82 BIT(offs % SYSCON_REG_BITS), 83 val ? BIT(offs % SYSCON_REG_BITS) : 0); 84} 85 86static int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset) 87{ 88 struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 89 90 if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { 91 unsigned int offs; 92 93 offs = priv->dir_reg_offset + 94 priv->data->dir_bit_offset + offset; 95 96 regmap_update_bits(priv->syscon, 97 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 98 BIT(offs % SYSCON_REG_BITS), 0); 99 } 100 101 return 0; 102} 103 104static int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val) 105{ 106 struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 107 108 if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { 109 unsigned int offs; 110 111 offs = priv->dir_reg_offset + 112 priv->data->dir_bit_offset + offset; 113 114 regmap_update_bits(priv->syscon, 115 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 116 BIT(offs % SYSCON_REG_BITS), 117 BIT(offs % SYSCON_REG_BITS)); 118 } 119 120 chip->set(chip, offset, val); 121 122 return 0; 123} 124 125static const struct syscon_gpio_data clps711x_mctrl_gpio = { 126 /* ARM CLPS711X SYSFLG1 Bits 8-10 */ 127 .flags = GPIO_SYSCON_FEAT_IN, 128 .bit_count = 3, 129 .dat_bit_offset = 0x40 * 8 + 8, 130}; 131 132static void rockchip_gpio_set(struct gpio_chip *chip, unsigned int offset, 133 int val) 134{ 135 struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 136 unsigned int offs; 137 u8 bit; 138 u32 data; 139 int ret; 140 141 offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 142 bit = offs % SYSCON_REG_BITS; 143 data = (val ? BIT(bit) : 0) | BIT(bit + 16); 144 ret = regmap_write(priv->syscon, 145 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 146 data); 147 if (ret < 0) 148 dev_err(chip->parent, "gpio write failed ret(%d)\n", ret); 149} 150 151static const struct syscon_gpio_data rockchip_rk3328_gpio_mute = { 152 /* RK3328 GPIO_MUTE is an output only pin at GRF_SOC_CON10[1] */ 153 .flags = GPIO_SYSCON_FEAT_OUT, 154 .bit_count = 1, 155 .dat_bit_offset = 0x0428 * 8 + 1, 156 .set = rockchip_gpio_set, 157}; 158 159#define KEYSTONE_LOCK_BIT BIT(0) 160 161static void keystone_gpio_set(struct gpio_chip *chip, unsigned offset, int val) 162{ 163 struct syscon_gpio_priv *priv = gpiochip_get_data(chip); 164 unsigned int offs; 165 int ret; 166 167 offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; 168 169 if (!val) 170 return; 171 172 ret = regmap_update_bits( 173 priv->syscon, 174 (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, 175 BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT, 176 BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT); 177 if (ret < 0) 178 dev_err(chip->parent, "gpio write failed ret(%d)\n", ret); 179} 180 181static const struct syscon_gpio_data keystone_dsp_gpio = { 182 /* ARM Keystone 2 */ 183 .flags = GPIO_SYSCON_FEAT_OUT, 184 .bit_count = 28, 185 .dat_bit_offset = 4, 186 .set = keystone_gpio_set, 187}; 188 189static const struct of_device_id syscon_gpio_ids[] = { 190 { 191 .compatible = "cirrus,ep7209-mctrl-gpio", 192 .data = &clps711x_mctrl_gpio, 193 }, 194 { 195 .compatible = "ti,keystone-dsp-gpio", 196 .data = &keystone_dsp_gpio, 197 }, 198 { 199 .compatible = "rockchip,rk3328-grf-gpio", 200 .data = &rockchip_rk3328_gpio_mute, 201 }, 202 { } 203}; 204MODULE_DEVICE_TABLE(of, syscon_gpio_ids); 205 206static int syscon_gpio_probe(struct platform_device *pdev) 207{ 208 struct device *dev = &pdev->dev; 209 struct syscon_gpio_priv *priv; 210 struct device_node *np = dev->of_node; 211 int ret; 212 213 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 214 if (!priv) 215 return -ENOMEM; 216 217 priv->data = of_device_get_match_data(dev); 218 219 priv->syscon = syscon_regmap_lookup_by_phandle(np, "gpio,syscon-dev"); 220 if (IS_ERR(priv->syscon) && np->parent) 221 priv->syscon = syscon_node_to_regmap(np->parent); 222 if (IS_ERR(priv->syscon)) 223 return PTR_ERR(priv->syscon); 224 225 ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1, 226 &priv->dreg_offset); 227 if (ret) 228 dev_err(dev, "can't read the data register offset!\n"); 229 230 priv->dreg_offset <<= 3; 231 232 ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2, 233 &priv->dir_reg_offset); 234 if (ret) 235 dev_dbg(dev, "can't read the dir register offset!\n"); 236 237 priv->dir_reg_offset <<= 3; 238 239 priv->chip.parent = dev; 240 priv->chip.owner = THIS_MODULE; 241 priv->chip.label = dev_name(dev); 242 priv->chip.base = -1; 243 priv->chip.ngpio = priv->data->bit_count; 244 priv->chip.get = syscon_gpio_get; 245 if (priv->data->flags & GPIO_SYSCON_FEAT_IN) 246 priv->chip.direction_input = syscon_gpio_dir_in; 247 if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) { 248 priv->chip.set = priv->data->set ? : syscon_gpio_set; 249 priv->chip.direction_output = syscon_gpio_dir_out; 250 } 251 252 platform_set_drvdata(pdev, priv); 253 254 return devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv); 255} 256 257static struct platform_driver syscon_gpio_driver = { 258 .driver = { 259 .name = "gpio-syscon", 260 .of_match_table = syscon_gpio_ids, 261 }, 262 .probe = syscon_gpio_probe, 263}; 264module_platform_driver(syscon_gpio_driver); 265 266MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 267MODULE_DESCRIPTION("SYSCON GPIO driver"); 268MODULE_LICENSE("GPL");