// SPDX-License-Identifier: GPL-2.0-or-later /* * SYSCON GPIO driver * * Copyright (C) 2014 Alexander Shiyan */ #include #include #include #include #include #include #include #define GPIO_SYSCON_FEAT_IN BIT(0) #define GPIO_SYSCON_FEAT_OUT BIT(1) #define GPIO_SYSCON_FEAT_DIR BIT(2) /* SYSCON driver is designed to use 32-bit wide registers */ #define SYSCON_REG_SIZE (4) #define SYSCON_REG_BITS (SYSCON_REG_SIZE * 8) /** * struct syscon_gpio_data - Configuration for the device. * @compatible: SYSCON driver compatible string. * @flags: Set of GPIO_SYSCON_FEAT_ flags: * GPIO_SYSCON_FEAT_IN: GPIOs supports input, * GPIO_SYSCON_FEAT_OUT: GPIOs supports output, * GPIO_SYSCON_FEAT_DIR: GPIOs supports switch direction. * @bit_count: Number of bits used as GPIOs. * @dat_bit_offset: Offset (in bits) to the first GPIO bit. * @dir_bit_offset: Optional offset (in bits) to the first bit to switch * GPIO direction (Used with GPIO_SYSCON_FEAT_DIR flag). * @set: HW specific callback to assigns output value * for signal "offset" */ struct syscon_gpio_data { unsigned int flags; unsigned int bit_count; unsigned int dat_bit_offset; unsigned int dir_bit_offset; void (*set)(struct gpio_chip *chip, unsigned offset, int value); }; struct syscon_gpio_priv { struct gpio_chip chip; struct regmap *syscon; const struct syscon_gpio_data *data; u32 dreg_offset; u32 dir_reg_offset; }; static int syscon_gpio_get(struct gpio_chip *chip, unsigned offset) { struct syscon_gpio_priv *priv = gpiochip_get_data(chip); unsigned int val, offs; int ret; offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; ret = regmap_read(priv->syscon, (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val); if (ret) return ret; return !!(val & BIT(offs % SYSCON_REG_BITS)); } static void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val) { struct syscon_gpio_priv *priv = gpiochip_get_data(chip); unsigned int offs; offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; regmap_update_bits(priv->syscon, (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, BIT(offs % SYSCON_REG_BITS), val ? BIT(offs % SYSCON_REG_BITS) : 0); } static int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset) { struct syscon_gpio_priv *priv = gpiochip_get_data(chip); if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { unsigned int offs; offs = priv->dir_reg_offset + priv->data->dir_bit_offset + offset; regmap_update_bits(priv->syscon, (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, BIT(offs % SYSCON_REG_BITS), 0); } return 0; } static int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val) { struct syscon_gpio_priv *priv = gpiochip_get_data(chip); if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { unsigned int offs; offs = priv->dir_reg_offset + priv->data->dir_bit_offset + offset; regmap_update_bits(priv->syscon, (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, BIT(offs % SYSCON_REG_BITS), BIT(offs % SYSCON_REG_BITS)); } chip->set(chip, offset, val); return 0; } static const struct syscon_gpio_data clps711x_mctrl_gpio = { /* ARM CLPS711X SYSFLG1 Bits 8-10 */ .flags = GPIO_SYSCON_FEAT_IN, .bit_count = 3, .dat_bit_offset = 0x40 * 8 + 8, }; static void rockchip_gpio_set(struct gpio_chip *chip, unsigned int offset, int val) { struct syscon_gpio_priv *priv = gpiochip_get_data(chip); unsigned int offs; u8 bit; u32 data; int ret; offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; bit = offs % SYSCON_REG_BITS; data = (val ? BIT(bit) : 0) | BIT(bit + 16); ret = regmap_write(priv->syscon, (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, data); if (ret < 0) dev_err(chip->parent, "gpio write failed ret(%d)\n", ret); } static const struct syscon_gpio_data rockchip_rk3328_gpio_mute = { /* RK3328 GPIO_MUTE is an output only pin at GRF_SOC_CON10[1] */ .flags = GPIO_SYSCON_FEAT_OUT, .bit_count = 1, .dat_bit_offset = 0x0428 * 8 + 1, .set = rockchip_gpio_set, }; #define KEYSTONE_LOCK_BIT BIT(0) static void keystone_gpio_set(struct gpio_chip *chip, unsigned offset, int val) { struct syscon_gpio_priv *priv = gpiochip_get_data(chip); unsigned int offs; int ret; offs = priv->dreg_offset + priv->data->dat_bit_offset + offset; if (!val) return; ret = regmap_update_bits( priv->syscon, (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT, BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT); if (ret < 0) dev_err(chip->parent, "gpio write failed ret(%d)\n", ret); } static const struct syscon_gpio_data keystone_dsp_gpio = { /* ARM Keystone 2 */ .flags = GPIO_SYSCON_FEAT_OUT, .bit_count = 28, .dat_bit_offset = 4, .set = keystone_gpio_set, }; static const struct of_device_id syscon_gpio_ids[] = { { .compatible = "cirrus,ep7209-mctrl-gpio", .data = &clps711x_mctrl_gpio, }, { .compatible = "ti,keystone-dsp-gpio", .data = &keystone_dsp_gpio, }, { .compatible = "rockchip,rk3328-grf-gpio", .data = &rockchip_rk3328_gpio_mute, }, { } }; MODULE_DEVICE_TABLE(of, syscon_gpio_ids); static int syscon_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct syscon_gpio_priv *priv; struct device_node *np = dev->of_node; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->data = of_device_get_match_data(dev); priv->syscon = syscon_regmap_lookup_by_phandle(np, "gpio,syscon-dev"); if (IS_ERR(priv->syscon) && np->parent) priv->syscon = syscon_node_to_regmap(np->parent); if (IS_ERR(priv->syscon)) return PTR_ERR(priv->syscon); ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1, &priv->dreg_offset); if (ret) dev_err(dev, "can't read the data register offset!\n"); priv->dreg_offset <<= 3; ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2, &priv->dir_reg_offset); if (ret) dev_dbg(dev, "can't read the dir register offset!\n"); priv->dir_reg_offset <<= 3; priv->chip.parent = dev; priv->chip.owner = THIS_MODULE; priv->chip.label = dev_name(dev); priv->chip.base = -1; priv->chip.ngpio = priv->data->bit_count; priv->chip.get = syscon_gpio_get; if (priv->data->flags & GPIO_SYSCON_FEAT_IN) priv->chip.direction_input = syscon_gpio_dir_in; if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) { priv->chip.set = priv->data->set ? : syscon_gpio_set; priv->chip.direction_output = syscon_gpio_dir_out; } platform_set_drvdata(pdev, priv); return devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv); } static struct platform_driver syscon_gpio_driver = { .driver = { .name = "gpio-syscon", .of_match_table = syscon_gpio_ids, }, .probe = syscon_gpio_probe, }; module_platform_driver(syscon_gpio_driver); MODULE_AUTHOR("Alexander Shiyan "); MODULE_DESCRIPTION("SYSCON GPIO driver"); MODULE_LICENSE("GPL");