| // SPDX-License-Identifier: GPL-2.0+ |
| // |
| // Synopsys CREG (Control REGisters) GPIO driver |
| // |
| // Copyright (C) 2018 Synopsys |
| // Author: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com> |
| |
| #include <linux/gpio/driver.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/of_platform.h> |
| |
| #define MAX_GPIO 32 |
| |
| struct creg_layout { |
| u8 ngpio; |
| u8 shift[MAX_GPIO]; |
| u8 on[MAX_GPIO]; |
| u8 off[MAX_GPIO]; |
| u8 bit_per_gpio[MAX_GPIO]; |
| }; |
| |
| struct creg_gpio { |
| struct gpio_chip gc; |
| void __iomem *regs; |
| spinlock_t lock; |
| const struct creg_layout *layout; |
| }; |
| |
| static void creg_gpio_set(struct gpio_chip *gc, unsigned int offset, int val) |
| { |
| struct creg_gpio *hcg = gpiochip_get_data(gc); |
| const struct creg_layout *layout = hcg->layout; |
| u32 reg, reg_shift, value; |
| unsigned long flags; |
| int i; |
| |
| value = val ? hcg->layout->on[offset] : hcg->layout->off[offset]; |
| |
| reg_shift = layout->shift[offset]; |
| for (i = 0; i < offset; i++) |
| reg_shift += layout->bit_per_gpio[i] + layout->shift[i]; |
| |
| spin_lock_irqsave(&hcg->lock, flags); |
| reg = readl(hcg->regs); |
| reg &= ~(GENMASK(layout->bit_per_gpio[i] - 1, 0) << reg_shift); |
| reg |= (value << reg_shift); |
| writel(reg, hcg->regs); |
| spin_unlock_irqrestore(&hcg->lock, flags); |
| } |
| |
| static int creg_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val) |
| { |
| creg_gpio_set(gc, offset, val); |
| |
| return 0; |
| } |
| |
| static int creg_gpio_validate_pg(struct device *dev, struct creg_gpio *hcg, |
| int i) |
| { |
| const struct creg_layout *layout = hcg->layout; |
| |
| if (layout->bit_per_gpio[i] < 1 || layout->bit_per_gpio[i] > 8) |
| return -EINVAL; |
| |
| /* Check that on valiue fits it's placeholder */ |
| if (GENMASK(31, layout->bit_per_gpio[i]) & layout->on[i]) |
| return -EINVAL; |
| |
| /* Check that off valiue fits it's placeholder */ |
| if (GENMASK(31, layout->bit_per_gpio[i]) & layout->off[i]) |
| return -EINVAL; |
| |
| if (layout->on[i] == layout->off[i]) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int creg_gpio_validate(struct device *dev, struct creg_gpio *hcg, |
| u32 ngpios) |
| { |
| u32 reg_len = 0; |
| int i; |
| |
| if (hcg->layout->ngpio < 1 || hcg->layout->ngpio > MAX_GPIO) |
| return -EINVAL; |
| |
| if (ngpios < 1 || ngpios > hcg->layout->ngpio) { |
| dev_err(dev, "ngpios must be in [1:%u]\n", hcg->layout->ngpio); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < hcg->layout->ngpio; i++) { |
| if (creg_gpio_validate_pg(dev, hcg, i)) |
| return -EINVAL; |
| |
| reg_len += hcg->layout->shift[i] + hcg->layout->bit_per_gpio[i]; |
| } |
| |
| /* Check that we fit in 32 bit register */ |
| if (reg_len > 32) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static const struct creg_layout hsdk_cs_ctl = { |
| .ngpio = 10, |
| .shift = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| .off = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, |
| .on = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, |
| .bit_per_gpio = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 } |
| }; |
| |
| static const struct creg_layout axs10x_flsh_cs_ctl = { |
| .ngpio = 1, |
| .shift = { 0 }, |
| .off = { 1 }, |
| .on = { 3 }, |
| .bit_per_gpio = { 2 } |
| }; |
| |
| static const struct of_device_id creg_gpio_ids[] = { |
| { |
| .compatible = "snps,creg-gpio-axs10x", |
| .data = &axs10x_flsh_cs_ctl |
| }, { |
| .compatible = "snps,creg-gpio-hsdk", |
| .data = &hsdk_cs_ctl |
| }, { /* sentinel */ } |
| }; |
| |
| static int creg_gpio_probe(struct platform_device *pdev) |
| { |
| const struct of_device_id *match; |
| struct device *dev = &pdev->dev; |
| struct creg_gpio *hcg; |
| struct resource *mem; |
| u32 ngpios; |
| int ret; |
| |
| hcg = devm_kzalloc(dev, sizeof(struct creg_gpio), GFP_KERNEL); |
| if (!hcg) |
| return -ENOMEM; |
| |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| hcg->regs = devm_ioremap_resource(dev, mem); |
| if (IS_ERR(hcg->regs)) |
| return PTR_ERR(hcg->regs); |
| |
| match = of_match_node(creg_gpio_ids, pdev->dev.of_node); |
| hcg->layout = match->data; |
| if (!hcg->layout) |
| return -EINVAL; |
| |
| ret = of_property_read_u32(dev->of_node, "ngpios", &ngpios); |
| if (ret) |
| return ret; |
| |
| ret = creg_gpio_validate(dev, hcg, ngpios); |
| if (ret) |
| return ret; |
| |
| spin_lock_init(&hcg->lock); |
| |
| hcg->gc.label = dev_name(dev); |
| hcg->gc.base = -1; |
| hcg->gc.ngpio = ngpios; |
| hcg->gc.set = creg_gpio_set; |
| hcg->gc.direction_output = creg_gpio_dir_out; |
| hcg->gc.of_node = dev->of_node; |
| |
| ret = devm_gpiochip_add_data(dev, &hcg->gc, hcg); |
| if (ret) |
| return ret; |
| |
| dev_info(dev, "GPIO controller with %d gpios probed\n", ngpios); |
| |
| return 0; |
| } |
| |
| static struct platform_driver creg_gpio_snps_driver = { |
| .driver = { |
| .name = "snps-creg-gpio", |
| .of_match_table = creg_gpio_ids, |
| }, |
| .probe = creg_gpio_probe, |
| }; |
| builtin_platform_driver(creg_gpio_snps_driver); |