diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/gpio/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/gpio/gpio-mpfs.c | 122 | ||||
| -rw-r--r-- | drivers/soc/microchip/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/soc/microchip/Makefile | 1 | ||||
| -rw-r--r-- | drivers/soc/microchip/mpfs-irqmux.c | 181 | ||||
| -rw-r--r-- | drivers/soc/microchip/mpfs-sys-controller.c | 74 |
6 files changed, 367 insertions, 23 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b45fb799e36c..1d1239323f61 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -572,6 +572,7 @@ config GPIO_PL061 config GPIO_POLARFIRE_SOC bool "Microchip FPGA GPIO support" select REGMAP_MMIO + select GPIOLIB_IRQCHIP help Say yes here to support the GPIO controllers on Microchip FPGAs. diff --git a/drivers/gpio/gpio-mpfs.c b/drivers/gpio/gpio-mpfs.c index 9468795b9634..1a4cf213c723 100644 --- a/drivers/gpio/gpio-mpfs.c +++ b/drivers/gpio/gpio-mpfs.c @@ -9,8 +9,9 @@ #include <linux/device.h> #include <linux/errno.h> #include <linux/gpio/driver.h> -#include <linux/init.h> +#include <linux/interrupt.h> #include <linux/mod_devicetable.h> +#include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/regmap.h> @@ -18,7 +19,7 @@ #define MPFS_GPIO_CTRL(i) (0x4 * (i)) #define MPFS_MAX_NUM_GPIO 32 -#define MPFS_GPIO_EN_INT 3 +#define MPFS_GPIO_EN_INT BIT(3) #define MPFS_GPIO_EN_OUT_BUF BIT(2) #define MPFS_GPIO_EN_IN BIT(1) #define MPFS_GPIO_EN_OUT BIT(0) @@ -52,6 +53,7 @@ static const struct regmap_config mpfs_gpio_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, + .use_raw_spinlock = true, }; static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index) @@ -114,13 +116,98 @@ static int mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int valu return ret; } +static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + int gpio_index = irqd_to_hwirq(data) % 32; + u32 interrupt_type; + + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH; + break; + case IRQ_TYPE_EDGE_FALLING: + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG; + break; + case IRQ_TYPE_EDGE_RISING: + interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS; + break; + case IRQ_TYPE_LEVEL_HIGH: + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW; + break; + } + + regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index), + MPFS_GPIO_TYPE_INT_MASK, interrupt_type); + + return 0; +} + +static void mpfs_gpio_irq_unmask(struct irq_data *data) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + int gpio_index = irqd_to_hwirq(data) % 32; + + gpiochip_enable_irq(gc, gpio_index); + mpfs_gpio_direction_input(gc, gpio_index); + regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index), + MPFS_GPIO_EN_INT, MPFS_GPIO_EN_INT); +} + +static void mpfs_gpio_irq_mask(struct irq_data *data) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(data); + struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc); + int gpio_index = irqd_to_hwirq(data) % 32; + + regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index), + MPFS_GPIO_EN_INT, 0); + gpiochip_disable_irq(gc, gpio_index); +} + +static const struct irq_chip mpfs_gpio_irqchip = { + .name = "MPFS GPIO", + .irq_set_type = mpfs_gpio_irq_set_type, + .irq_mask = mpfs_gpio_irq_mask, + .irq_unmask = mpfs_gpio_irq_unmask, + .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static void mpfs_gpio_irq_handler(struct irq_desc *desc) +{ + struct irq_chip *irqchip = irq_desc_get_chip(desc); + struct mpfs_gpio_chip *mpfs_gpio = irq_desc_get_handler_data(desc); + unsigned long status; + u32 val; + int i; + + chained_irq_enter(irqchip, desc); + + regmap_read(mpfs_gpio->regs, MPFS_IRQ_REG, &val); + status = val; + for_each_set_bit(i, &status, MPFS_MAX_NUM_GPIO) { + regmap_write(mpfs_gpio->regs, MPFS_IRQ_REG, BIT(i)); + generic_handle_domain_irq(mpfs_gpio->gc.irq.domain, i); + } + + chained_irq_exit(irqchip, desc); +} + static int mpfs_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; struct mpfs_gpio_chip *mpfs_gpio; + struct gpio_irq_chip *girq; struct clk *clk; void __iomem *base; - int ngpios; + int ngpios, nirqs, ret; mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL); if (!mpfs_gpio) @@ -157,6 +244,35 @@ static int mpfs_gpio_probe(struct platform_device *pdev) mpfs_gpio->gc.parent = dev; mpfs_gpio->gc.owner = THIS_MODULE; + nirqs = of_irq_count(node); + if (nirqs > MPFS_MAX_NUM_GPIO) + return -ENXIO; + + if (nirqs) { + girq = &mpfs_gpio->gc.irq; + + gpio_irq_chip_set_chip(girq, &mpfs_gpio_irqchip); + + girq->num_parents = nirqs; + girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents, + sizeof(*girq->parents), GFP_KERNEL); + if (!girq->parents) + return -ENOMEM; + + for (int i = 0; i < nirqs; i++) { + ret = platform_get_irq(pdev, i); + if (ret < 0) + return ret; + + girq->parents[i] = ret; + girq->parent_handler_data = mpfs_gpio; + girq->parent_handler = mpfs_gpio_irq_handler; + } + + girq->handler = handle_level_irq; + girq->default_type = IRQ_TYPE_NONE; + } + return devm_gpiochip_add_data(dev, &mpfs_gpio->gc, mpfs_gpio); } diff --git a/drivers/soc/microchip/Kconfig b/drivers/soc/microchip/Kconfig index bcf554602561..af7946741bce 100644 --- a/drivers/soc/microchip/Kconfig +++ b/drivers/soc/microchip/Kconfig @@ -1,3 +1,14 @@ +config POLARFIRE_SOC_IRQ_MUX + bool "Microchip PolarFire SoC's GPIO IRQ Mux" + depends on ARCH_MICROCHIP + select REGMAP + select REGMAP_MMIO + default y + help + Support for the interrupt mux on Polarfire SoC. It sits between + the GPIO controllers and the PLIC, as only 41 interrupts are shared + between 3 GPIO controllers with a total of 70 interrupts. + config POLARFIRE_SOC_SYS_CTRL tristate "Microchip PolarFire SoC (MPFS) system controller support" depends on POLARFIRE_SOC_MAILBOX diff --git a/drivers/soc/microchip/Makefile b/drivers/soc/microchip/Makefile index 1a3a1594b089..55775db45ee7 100644 --- a/drivers/soc/microchip/Makefile +++ b/drivers/soc/microchip/Makefile @@ -1,2 +1,3 @@ +obj-$(CONFIG_POLARFIRE_SOC_IRQ_MUX) += mpfs-irqmux.o obj-$(CONFIG_POLARFIRE_SOC_SYS_CTRL) += mpfs-sys-controller.o obj-$(CONFIG_POLARFIRE_SOC_SYSCONS) += mpfs-control-scb.o mpfs-mss-top-sysreg.o diff --git a/drivers/soc/microchip/mpfs-irqmux.c b/drivers/soc/microchip/mpfs-irqmux.c new file mode 100644 index 000000000000..ae15e913e780 --- /dev/null +++ b/drivers/soc/microchip/mpfs-irqmux.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Largely copied from rzn1_irqmux.c + */ + +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define MPFS_IRQMUX_CR 0x54 +#define MPFS_IRQMUX_NUM_CHILDREN 96 +#define MPFS_IRQMUX_NUM_DIRECT 38 +#define MPFS_IRQMUX_DIRECT_START 13 +#define MPFS_IRQMUX_DIRECT_END 50 +#define MPFS_IRQMUX_NONDIRECT_END 53 + +static int mpfs_irqmux_is_direct_mode(struct device *dev, + const struct of_phandle_args *parent_args) +{ + if (parent_args->args_count != 1) { + dev_err(dev, "Invalid interrupt-map item\n"); + return -EINVAL; + } + + if (parent_args->args[0] < MPFS_IRQMUX_DIRECT_START || + parent_args->args[0] > MPFS_IRQMUX_NONDIRECT_END) { + dev_err(dev, "Invalid interrupt %u\n", parent_args->args[0]); + return -EINVAL; + } + + if (parent_args->args[0] > MPFS_IRQMUX_DIRECT_END) + return 0; + + return 1; +} + +static int mpfs_irqmux_probe(struct platform_device *pdev) +{ + DECLARE_BITMAP(child_done, MPFS_IRQMUX_NUM_CHILDREN) = {}; + DECLARE_BITMAP(parent_done, MPFS_IRQMUX_NUM_DIRECT) = {}; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct of_imap_parser imap_parser; + struct of_imap_item imap_item; + struct regmap *regmap; + int ret, direct_mode, line, controller, gpio, parent_line; + u32 tmp, val = 0, old; + + regmap = device_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to find syscon regmap\n"); + + /* We support only #interrupt-cells = <1> and #address-cells = <0> */ + ret = of_property_read_u32(np, "#interrupt-cells", &tmp); + if (ret) + return ret; + if (tmp != 1) + return -EINVAL; + + ret = of_property_read_u32(np, "#address-cells", &tmp); + if (ret) + return ret; + if (tmp != 0) + return -EINVAL; + + ret = of_imap_parser_init(&imap_parser, np, &imap_item); + if (ret) + return ret; + + for_each_of_imap_item(&imap_parser, &imap_item) { + + direct_mode = mpfs_irqmux_is_direct_mode(dev, &imap_item.parent_args); + if (direct_mode < 0) { + of_node_put(imap_item.parent_args.np); + return direct_mode; + } + + line = imap_item.child_imap[0]; + gpio = line % 32; + controller = line / 32; + + if (controller > 2) { + of_node_put(imap_item.parent_args.np); + dev_err(dev, "child interrupt number too large: %d\n", line); + return -EINVAL; + } + + if (test_and_set_bit(line, child_done)) { + of_node_put(imap_item.parent_args.np); + dev_err(dev, "mux child line %d already defined in interrupt-map\n", + line); + return -EINVAL; + } + + parent_line = imap_item.parent_args.args[0] - MPFS_IRQMUX_DIRECT_START; + if (direct_mode && test_and_set_bit(parent_line, parent_done)) { + of_node_put(imap_item.parent_args.np); + dev_err(dev, "mux parent line %d already defined in interrupt-map\n", + line); + return -EINVAL; + } + + /* + * There are 41 interrupts assigned to GPIOs, of which 38 are "direct". Since the + * mux has 32 bits only, 6 of these exclusive/"direct" interrupts remain. These + * are used by GPIO controller 1's lines 18 to 23. Nothing needs to be done + * for these interrupts. + */ + if (controller == 1 && gpio >= 18) + continue; + + /* + * The mux has a single register, where bits 0 to 13 mux between GPIO controller + * 1's 14 GPIOs and GPIO controller 2's first 14 GPIOs. The remaining bits mux + * between the first 18 GPIOs of controller 1 and the last 18 GPIOS of + * controller 2. If a bit in the mux's control register is set, the + * corresponding interrupt line for GPIO controller 0 or 1 will be put in + * "non-direct" mode. If cleared, the "fabric" controller's will. + * + * Register layout: + * GPIO 1 interrupt line 17 | mux bit 31 | GPIO 2 interrupt line 31 + * ... | ... | ... + * ... | ... | ... + * GPIO 1 interrupt line 0 | mux bit 14 | GPIO 2 interrupt line 14 + * GPIO 0 interrupt line 13 | mux bit 13 | GPIO 2 interrupt line 13 + * ... | ... | ... + * ... | ... | ... + * GPIO 0 interrupt line 0 | mux bit 0 | GPIO 2 interrupt line 0 + * + * As the binding mandates 70 items, one for each GPIO line, there's no need to + * handle anything for GPIO controller 2, since the bit will be set for the + * corresponding line in GPIO controller 0 or 1. + */ + if (controller == 2) + continue; + + /* + * If in direct mode, the bit is cleared, nothing needs to be done as val is zero + * initialised and that's the direct mode setting for GPIO controller 0 and 1. + */ + if (direct_mode) + continue; + + if (controller == 0) + val |= 1U << gpio; + else + val |= 1U << (gpio + 14); + } + + regmap_read(regmap, MPFS_IRQMUX_CR, &old); + regmap_write(regmap, MPFS_IRQMUX_CR, val); + + if (val != old) + dev_info(dev, "firmware mux setting of 0x%x overwritten to 0x%x\n", old, val); + + return 0; +} + +static const struct of_device_id mpfs_irqmux_of_match[] = { + { .compatible = "microchip,mpfs-irqmux", }, + { } +}; +MODULE_DEVICE_TABLE(of, mpfs_irqmux_of_match); + +static struct platform_driver mpfs_irqmux_driver = { + .probe = mpfs_irqmux_probe, + .driver = { + .name = "mpfs_irqmux", + .of_match_table = mpfs_irqmux_of_match, + }, +}; +module_platform_driver(mpfs_irqmux_driver); + +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_DESCRIPTION("Polarfire SoC interrupt mux driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/microchip/mpfs-sys-controller.c b/drivers/soc/microchip/mpfs-sys-controller.c index 8e7ae3cb92ff..0be7b109df05 100644 --- a/drivers/soc/microchip/mpfs-sys-controller.c +++ b/drivers/soc/microchip/mpfs-sys-controller.c @@ -36,6 +36,11 @@ struct mpfs_sys_controller { struct kref consumers; }; +struct mpfs_syscon_config { + unsigned int nb_subdevs; + struct platform_device *subdevs; +}; + int mpfs_blocking_transaction(struct mpfs_sys_controller *sys_controller, struct mpfs_mss_msg *msg) { unsigned long timeout = msecs_to_jiffies(MPFS_SYS_CTRL_TIMEOUT_MS); @@ -110,25 +115,11 @@ struct mtd_info *mpfs_sys_controller_get_flash(struct mpfs_sys_controller *mpfs_ } EXPORT_SYMBOL(mpfs_sys_controller_get_flash); -static struct platform_device subdevs[] = { - { - .name = "mpfs-rng", - .id = -1, - }, - { - .name = "mpfs-generic-service", - .id = -1, - }, - { - .name = "mpfs-auto-update", - .id = -1, - }, -}; - static int mpfs_sys_controller_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mpfs_sys_controller *sys_controller; + struct mpfs_syscon_config *of_data; struct device_node *np; int i, ret; @@ -164,11 +155,17 @@ no_flash: platform_set_drvdata(pdev, sys_controller); + of_data = (struct mpfs_syscon_config *) device_get_match_data(dev); + if (!of_data) { + dev_err(dev, "Error getting match data\n"); + return -EINVAL; + } - for (i = 0; i < ARRAY_SIZE(subdevs); i++) { - subdevs[i].dev.parent = dev; - if (platform_device_register(&subdevs[i])) - dev_warn(dev, "Error registering sub device %s\n", subdevs[i].name); + for (i = 0; i < of_data->nb_subdevs; i++) { + of_data->subdevs[i].dev.parent = dev; + if (platform_device_register(&of_data->subdevs[i])) + dev_warn(dev, "Error registering sub device %s\n", + of_data->subdevs[i].name); } dev_info(&pdev->dev, "Registered MPFS system controller\n"); @@ -183,8 +180,45 @@ static void mpfs_sys_controller_remove(struct platform_device *pdev) mpfs_sys_controller_put(sys_controller); } +static struct platform_device mpfs_subdevs[] = { + { + .name = "mpfs-rng", + .id = -1, + }, + { + .name = "mpfs-generic-service", + .id = -1, + }, + { + .name = "mpfs-auto-update", + .id = -1, + }, +}; + +static struct platform_device pic64gx_subdevs[] = { + { + .name = "mpfs-rng", + .id = -1, + }, + { + .name = "mpfs-generic-service", + .id = -1, + }, +}; + +static const struct mpfs_syscon_config mpfs_config = { + .nb_subdevs = ARRAY_SIZE(mpfs_subdevs), + .subdevs = mpfs_subdevs, +}; + +static const struct mpfs_syscon_config pic64gx_config = { + .nb_subdevs = ARRAY_SIZE(pic64gx_subdevs), + .subdevs = pic64gx_subdevs, +}; + static const struct of_device_id mpfs_sys_controller_of_match[] = { - {.compatible = "microchip,mpfs-sys-controller", }, + {.compatible = "microchip,mpfs-sys-controller", .data = &mpfs_config}, + {.compatible = "microchip,pic64gx-sys-controller", .data = &pic64gx_config}, {}, }; MODULE_DEVICE_TABLE(of, mpfs_sys_controller_of_match); |
