Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright (C) 2019 SiFive |
| 4 | */ |
| 5 | |
| 6 | #include <linux/bitops.h> |
| 7 | #include <linux/device.h> |
| 8 | #include <linux/errno.h> |
| 9 | #include <linux/of_irq.h> |
| 10 | #include <linux/gpio/driver.h> |
| 11 | #include <linux/init.h> |
| 12 | #include <linux/platform_device.h> |
| 13 | #include <linux/slab.h> |
| 14 | #include <linux/spinlock.h> |
| 15 | #include <linux/regmap.h> |
| 16 | |
| 17 | #define SIFIVE_GPIO_INPUT_VAL 0x00 |
| 18 | #define SIFIVE_GPIO_INPUT_EN 0x04 |
| 19 | #define SIFIVE_GPIO_OUTPUT_EN 0x08 |
| 20 | #define SIFIVE_GPIO_OUTPUT_VAL 0x0C |
| 21 | #define SIFIVE_GPIO_RISE_IE 0x18 |
| 22 | #define SIFIVE_GPIO_RISE_IP 0x1C |
| 23 | #define SIFIVE_GPIO_FALL_IE 0x20 |
| 24 | #define SIFIVE_GPIO_FALL_IP 0x24 |
| 25 | #define SIFIVE_GPIO_HIGH_IE 0x28 |
| 26 | #define SIFIVE_GPIO_HIGH_IP 0x2C |
| 27 | #define SIFIVE_GPIO_LOW_IE 0x30 |
| 28 | #define SIFIVE_GPIO_LOW_IP 0x34 |
| 29 | #define SIFIVE_GPIO_OUTPUT_XOR 0x40 |
| 30 | |
| 31 | #define SIFIVE_GPIO_MAX 32 |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 32 | |
| 33 | struct sifive_gpio { |
| 34 | void __iomem *base; |
| 35 | struct gpio_chip gc; |
| 36 | struct regmap *regs; |
Yash Shah | a924eae | 2020-01-28 10:54:21 +0530 | [diff] [blame] | 37 | unsigned long irq_state; |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 38 | unsigned int trigger[SIFIVE_GPIO_MAX]; |
Greentime Hu | f52d6d8 | 2020-11-13 10:33:55 +0800 | [diff] [blame] | 39 | unsigned int irq_number[SIFIVE_GPIO_MAX]; |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 40 | }; |
| 41 | |
| 42 | static void sifive_gpio_set_ie(struct sifive_gpio *chip, unsigned int offset) |
| 43 | { |
| 44 | unsigned long flags; |
| 45 | unsigned int trigger; |
| 46 | |
| 47 | spin_lock_irqsave(&chip->gc.bgpio_lock, flags); |
| 48 | trigger = (chip->irq_state & BIT(offset)) ? chip->trigger[offset] : 0; |
| 49 | regmap_update_bits(chip->regs, SIFIVE_GPIO_RISE_IE, BIT(offset), |
| 50 | (trigger & IRQ_TYPE_EDGE_RISING) ? BIT(offset) : 0); |
| 51 | regmap_update_bits(chip->regs, SIFIVE_GPIO_FALL_IE, BIT(offset), |
| 52 | (trigger & IRQ_TYPE_EDGE_FALLING) ? BIT(offset) : 0); |
| 53 | regmap_update_bits(chip->regs, SIFIVE_GPIO_HIGH_IE, BIT(offset), |
| 54 | (trigger & IRQ_TYPE_LEVEL_HIGH) ? BIT(offset) : 0); |
| 55 | regmap_update_bits(chip->regs, SIFIVE_GPIO_LOW_IE, BIT(offset), |
| 56 | (trigger & IRQ_TYPE_LEVEL_LOW) ? BIT(offset) : 0); |
| 57 | spin_unlock_irqrestore(&chip->gc.bgpio_lock, flags); |
| 58 | } |
| 59 | |
| 60 | static int sifive_gpio_irq_set_type(struct irq_data *d, unsigned int trigger) |
| 61 | { |
| 62 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| 63 | struct sifive_gpio *chip = gpiochip_get_data(gc); |
| 64 | int offset = irqd_to_hwirq(d); |
| 65 | |
| 66 | if (offset < 0 || offset >= gc->ngpio) |
| 67 | return -EINVAL; |
| 68 | |
| 69 | chip->trigger[offset] = trigger; |
| 70 | sifive_gpio_set_ie(chip, offset); |
| 71 | return 0; |
| 72 | } |
| 73 | |
| 74 | static void sifive_gpio_irq_enable(struct irq_data *d) |
| 75 | { |
| 76 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| 77 | struct sifive_gpio *chip = gpiochip_get_data(gc); |
| 78 | int offset = irqd_to_hwirq(d) % SIFIVE_GPIO_MAX; |
| 79 | u32 bit = BIT(offset); |
| 80 | unsigned long flags; |
| 81 | |
| 82 | irq_chip_enable_parent(d); |
| 83 | |
| 84 | /* Switch to input */ |
| 85 | gc->direction_input(gc, offset); |
| 86 | |
| 87 | spin_lock_irqsave(&gc->bgpio_lock, flags); |
| 88 | /* Clear any sticky pending interrupts */ |
| 89 | regmap_write(chip->regs, SIFIVE_GPIO_RISE_IP, bit); |
| 90 | regmap_write(chip->regs, SIFIVE_GPIO_FALL_IP, bit); |
| 91 | regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IP, bit); |
| 92 | regmap_write(chip->regs, SIFIVE_GPIO_LOW_IP, bit); |
| 93 | spin_unlock_irqrestore(&gc->bgpio_lock, flags); |
| 94 | |
| 95 | /* Enable interrupts */ |
Yash Shah | a924eae | 2020-01-28 10:54:21 +0530 | [diff] [blame] | 96 | assign_bit(offset, &chip->irq_state, 1); |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 97 | sifive_gpio_set_ie(chip, offset); |
| 98 | } |
| 99 | |
| 100 | static void sifive_gpio_irq_disable(struct irq_data *d) |
| 101 | { |
| 102 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| 103 | struct sifive_gpio *chip = gpiochip_get_data(gc); |
| 104 | int offset = irqd_to_hwirq(d) % SIFIVE_GPIO_MAX; |
| 105 | |
Yash Shah | a924eae | 2020-01-28 10:54:21 +0530 | [diff] [blame] | 106 | assign_bit(offset, &chip->irq_state, 0); |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 107 | sifive_gpio_set_ie(chip, offset); |
| 108 | irq_chip_disable_parent(d); |
| 109 | } |
| 110 | |
| 111 | static void sifive_gpio_irq_eoi(struct irq_data *d) |
| 112 | { |
| 113 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| 114 | struct sifive_gpio *chip = gpiochip_get_data(gc); |
| 115 | int offset = irqd_to_hwirq(d) % SIFIVE_GPIO_MAX; |
| 116 | u32 bit = BIT(offset); |
| 117 | unsigned long flags; |
| 118 | |
| 119 | spin_lock_irqsave(&gc->bgpio_lock, flags); |
| 120 | /* Clear all pending interrupts */ |
| 121 | regmap_write(chip->regs, SIFIVE_GPIO_RISE_IP, bit); |
| 122 | regmap_write(chip->regs, SIFIVE_GPIO_FALL_IP, bit); |
| 123 | regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IP, bit); |
| 124 | regmap_write(chip->regs, SIFIVE_GPIO_LOW_IP, bit); |
| 125 | spin_unlock_irqrestore(&gc->bgpio_lock, flags); |
| 126 | |
| 127 | irq_chip_eoi_parent(d); |
| 128 | } |
| 129 | |
Linus Walleij | 011a78c | 2020-11-17 22:33:50 +0100 | [diff] [blame] | 130 | static int sifive_gpio_irq_set_affinity(struct irq_data *data, |
| 131 | const struct cpumask *dest, |
| 132 | bool force) |
| 133 | { |
| 134 | if (data->parent_data) |
| 135 | return irq_chip_set_affinity_parent(data, dest, force); |
| 136 | |
| 137 | return -EINVAL; |
| 138 | } |
| 139 | |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 140 | static struct irq_chip sifive_gpio_irqchip = { |
| 141 | .name = "sifive-gpio", |
| 142 | .irq_set_type = sifive_gpio_irq_set_type, |
| 143 | .irq_mask = irq_chip_mask_parent, |
| 144 | .irq_unmask = irq_chip_unmask_parent, |
| 145 | .irq_enable = sifive_gpio_irq_enable, |
| 146 | .irq_disable = sifive_gpio_irq_disable, |
| 147 | .irq_eoi = sifive_gpio_irq_eoi, |
Linus Walleij | 011a78c | 2020-11-17 22:33:50 +0100 | [diff] [blame] | 148 | .irq_set_affinity = sifive_gpio_irq_set_affinity, |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 149 | }; |
| 150 | |
| 151 | static int sifive_gpio_child_to_parent_hwirq(struct gpio_chip *gc, |
| 152 | unsigned int child, |
| 153 | unsigned int child_type, |
| 154 | unsigned int *parent, |
| 155 | unsigned int *parent_type) |
| 156 | { |
Greentime Hu | f52d6d8 | 2020-11-13 10:33:55 +0800 | [diff] [blame] | 157 | struct sifive_gpio *chip = gpiochip_get_data(gc); |
| 158 | struct irq_data *d = irq_get_irq_data(chip->irq_number[child]); |
| 159 | |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 160 | *parent_type = IRQ_TYPE_NONE; |
Greentime Hu | f52d6d8 | 2020-11-13 10:33:55 +0800 | [diff] [blame] | 161 | *parent = irqd_to_hwirq(d); |
| 162 | |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 163 | return 0; |
| 164 | } |
| 165 | |
| 166 | static const struct regmap_config sifive_gpio_regmap_config = { |
| 167 | .reg_bits = 32, |
| 168 | .reg_stride = 4, |
| 169 | .val_bits = 32, |
| 170 | .fast_io = true, |
| 171 | .disable_locking = true, |
| 172 | }; |
| 173 | |
| 174 | static int sifive_gpio_probe(struct platform_device *pdev) |
| 175 | { |
| 176 | struct device *dev = &pdev->dev; |
| 177 | struct device_node *node = pdev->dev.of_node; |
| 178 | struct device_node *irq_parent; |
| 179 | struct irq_domain *parent; |
| 180 | struct gpio_irq_chip *girq; |
| 181 | struct sifive_gpio *chip; |
Greentime Hu | f52d6d8 | 2020-11-13 10:33:55 +0800 | [diff] [blame] | 182 | int ret, ngpio, i; |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 183 | |
| 184 | chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); |
| 185 | if (!chip) |
| 186 | return -ENOMEM; |
| 187 | |
| 188 | chip->base = devm_platform_ioremap_resource(pdev, 0); |
| 189 | if (IS_ERR(chip->base)) { |
| 190 | dev_err(dev, "failed to allocate device memory\n"); |
| 191 | return PTR_ERR(chip->base); |
| 192 | } |
| 193 | |
| 194 | chip->regs = devm_regmap_init_mmio(dev, chip->base, |
| 195 | &sifive_gpio_regmap_config); |
| 196 | if (IS_ERR(chip->regs)) |
| 197 | return PTR_ERR(chip->regs); |
| 198 | |
| 199 | ngpio = of_irq_count(node); |
Damien Le Moal | b72de3f | 2020-11-07 17:13:57 +0900 | [diff] [blame] | 200 | if (ngpio > SIFIVE_GPIO_MAX) { |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 201 | dev_err(dev, "Too many GPIO interrupts (max=%d)\n", |
| 202 | SIFIVE_GPIO_MAX); |
| 203 | return -ENXIO; |
| 204 | } |
| 205 | |
| 206 | irq_parent = of_irq_find_parent(node); |
| 207 | if (!irq_parent) { |
| 208 | dev_err(dev, "no IRQ parent node\n"); |
| 209 | return -ENODEV; |
| 210 | } |
| 211 | parent = irq_find_host(irq_parent); |
| 212 | if (!parent) { |
| 213 | dev_err(dev, "no IRQ parent domain\n"); |
| 214 | return -ENODEV; |
| 215 | } |
| 216 | |
Greentime Hu | f52d6d8 | 2020-11-13 10:33:55 +0800 | [diff] [blame] | 217 | for (i = 0; i < ngpio; i++) |
| 218 | chip->irq_number[i] = platform_get_irq(pdev, i); |
| 219 | |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 220 | ret = bgpio_init(&chip->gc, dev, 4, |
| 221 | chip->base + SIFIVE_GPIO_INPUT_VAL, |
| 222 | chip->base + SIFIVE_GPIO_OUTPUT_VAL, |
| 223 | NULL, |
| 224 | chip->base + SIFIVE_GPIO_OUTPUT_EN, |
| 225 | chip->base + SIFIVE_GPIO_INPUT_EN, |
Niklas Cassel | cc38ef9 | 2022-02-04 13:02:25 +0000 | [diff] [blame] | 226 | BGPIOF_READ_OUTPUT_REG_SET); |
Yash Shah | 96868dc | 2019-12-10 16:41:13 +0530 | [diff] [blame] | 227 | if (ret) { |
| 228 | dev_err(dev, "unable to init generic GPIO\n"); |
| 229 | return ret; |
| 230 | } |
| 231 | |
| 232 | /* Disable all GPIO interrupts before enabling parent interrupts */ |
| 233 | regmap_write(chip->regs, SIFIVE_GPIO_RISE_IE, 0); |
| 234 | regmap_write(chip->regs, SIFIVE_GPIO_FALL_IE, 0); |
| 235 | regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IE, 0); |
| 236 | regmap_write(chip->regs, SIFIVE_GPIO_LOW_IE, 0); |
| 237 | chip->irq_state = 0; |
| 238 | |
| 239 | chip->gc.base = -1; |
| 240 | chip->gc.ngpio = ngpio; |
| 241 | chip->gc.label = dev_name(dev); |
| 242 | chip->gc.parent = dev; |
| 243 | chip->gc.owner = THIS_MODULE; |
| 244 | girq = &chip->gc.irq; |
| 245 | girq->chip = &sifive_gpio_irqchip; |
| 246 | girq->fwnode = of_node_to_fwnode(node); |
| 247 | girq->parent_domain = parent; |
| 248 | girq->child_to_parent_hwirq = sifive_gpio_child_to_parent_hwirq; |
| 249 | girq->handler = handle_bad_irq; |
| 250 | girq->default_type = IRQ_TYPE_NONE; |
| 251 | |
| 252 | platform_set_drvdata(pdev, chip); |
| 253 | return gpiochip_add_data(&chip->gc, chip); |
| 254 | } |
| 255 | |
| 256 | static const struct of_device_id sifive_gpio_match[] = { |
| 257 | { .compatible = "sifive,gpio0" }, |
| 258 | { .compatible = "sifive,fu540-c000-gpio" }, |
| 259 | { }, |
| 260 | }; |
| 261 | |
| 262 | static struct platform_driver sifive_gpio_driver = { |
| 263 | .probe = sifive_gpio_probe, |
| 264 | .driver = { |
| 265 | .name = "sifive_gpio", |
| 266 | .of_match_table = of_match_ptr(sifive_gpio_match), |
| 267 | }, |
| 268 | }; |
| 269 | builtin_platform_driver(sifive_gpio_driver) |