Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

drm/imx: Add i.MX8qxp Display Controller interrupt controller

i.MX8qxp Display Controller has a built-in interrupt controller to support
Enable/Status/Preset/Clear interrupt bit. Add driver for it.

Reviewed-by: Maxime Ripard <mripard@kernel.org>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Signed-off-by: Liu Ying <victor.liu@nxp.com>
Link: https://lore.kernel.org/r/20250414035028.1561475-12-victor.liu@nxp.com

+286 -1
+1
drivers/gpu/drm/imx/dc/Kconfig
··· 1 1 config DRM_IMX8_DC 2 2 tristate "Freescale i.MX8 Display Controller Graphics" 3 3 depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST) 4 + select GENERIC_IRQ_CHIP 4 5 select REGMAP 5 6 select REGMAP_MMIO 6 7 help
+1 -1
drivers/gpu/drm/imx/dc/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 3 3 imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \ 4 - dc-fw.o dc-lb.o dc-pe.o dc-tc.o 4 + dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o 5 5 6 6 obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
+1
drivers/gpu/drm/imx/dc/dc-drv.c
··· 15 15 &dc_fg_driver, 16 16 &dc_fl_driver, 17 17 &dc_fw_driver, 18 + &dc_ic_driver, 18 19 &dc_lb_driver, 19 20 &dc_pe_driver, 20 21 &dc_tc_driver,
+1
drivers/gpu/drm/imx/dc/dc-drv.h
··· 54 54 extern struct platform_driver dc_fg_driver; 55 55 extern struct platform_driver dc_fl_driver; 56 56 extern struct platform_driver dc_fw_driver; 57 + extern struct platform_driver dc_ic_driver; 57 58 extern struct platform_driver dc_lb_driver; 58 59 extern struct platform_driver dc_pe_driver; 59 60 extern struct platform_driver dc_tc_driver;
+282
drivers/gpu/drm/imx/dc/dc-ic.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * Copyright 2024 NXP 4 + */ 5 + 6 + #include <linux/clk.h> 7 + #include <linux/interrupt.h> 8 + #include <linux/irq.h> 9 + #include <linux/irqchip/chained_irq.h> 10 + #include <linux/irqdomain.h> 11 + #include <linux/of.h> 12 + #include <linux/of_irq.h> 13 + #include <linux/platform_device.h> 14 + #include <linux/pm_runtime.h> 15 + #include <linux/regmap.h> 16 + 17 + #define USERINTERRUPTMASK(n) (0x8 + 4 * (n)) 18 + #define INTERRUPTENABLE(n) (0x10 + 4 * (n)) 19 + #define INTERRUPTPRESET(n) (0x18 + 4 * (n)) 20 + #define INTERRUPTCLEAR(n) (0x20 + 4 * (n)) 21 + #define INTERRUPTSTATUS(n) (0x28 + 4 * (n)) 22 + #define USERINTERRUPTENABLE(n) (0x40 + 4 * (n)) 23 + #define USERINTERRUPTPRESET(n) (0x48 + 4 * (n)) 24 + #define USERINTERRUPTCLEAR(n) (0x50 + 4 * (n)) 25 + #define USERINTERRUPTSTATUS(n) (0x58 + 4 * (n)) 26 + 27 + #define IRQ_COUNT 49 28 + #define IRQ_RESERVED 35 29 + #define REG_NUM 2 30 + 31 + struct dc_ic_data { 32 + struct regmap *regs; 33 + struct clk *clk_axi; 34 + int irq[IRQ_COUNT]; 35 + struct irq_domain *domain; 36 + }; 37 + 38 + struct dc_ic_entry { 39 + struct dc_ic_data *data; 40 + int irq; 41 + }; 42 + 43 + static const struct regmap_range dc_ic_regmap_write_ranges[] = { 44 + regmap_reg_range(USERINTERRUPTMASK(0), INTERRUPTCLEAR(1)), 45 + regmap_reg_range(USERINTERRUPTENABLE(0), USERINTERRUPTCLEAR(1)), 46 + }; 47 + 48 + static const struct regmap_access_table dc_ic_regmap_write_table = { 49 + .yes_ranges = dc_ic_regmap_write_ranges, 50 + .n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_write_ranges), 51 + }; 52 + 53 + static const struct regmap_range dc_ic_regmap_read_ranges[] = { 54 + regmap_reg_range(USERINTERRUPTMASK(0), INTERRUPTENABLE(1)), 55 + regmap_reg_range(INTERRUPTSTATUS(0), INTERRUPTSTATUS(1)), 56 + regmap_reg_range(USERINTERRUPTENABLE(0), USERINTERRUPTENABLE(1)), 57 + regmap_reg_range(USERINTERRUPTSTATUS(0), USERINTERRUPTSTATUS(1)), 58 + }; 59 + 60 + static const struct regmap_access_table dc_ic_regmap_read_table = { 61 + .yes_ranges = dc_ic_regmap_read_ranges, 62 + .n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_read_ranges), 63 + }; 64 + 65 + static const struct regmap_range dc_ic_regmap_volatile_ranges[] = { 66 + regmap_reg_range(INTERRUPTPRESET(0), INTERRUPTCLEAR(1)), 67 + regmap_reg_range(USERINTERRUPTPRESET(0), USERINTERRUPTCLEAR(1)), 68 + }; 69 + 70 + static const struct regmap_access_table dc_ic_regmap_volatile_table = { 71 + .yes_ranges = dc_ic_regmap_volatile_ranges, 72 + .n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_volatile_ranges), 73 + }; 74 + 75 + static const struct regmap_config dc_ic_regmap_config = { 76 + .reg_bits = 32, 77 + .reg_stride = 4, 78 + .val_bits = 32, 79 + .fast_io = true, 80 + .wr_table = &dc_ic_regmap_write_table, 81 + .rd_table = &dc_ic_regmap_read_table, 82 + .volatile_table = &dc_ic_regmap_volatile_table, 83 + .max_register = USERINTERRUPTSTATUS(1), 84 + }; 85 + 86 + static void dc_ic_irq_handler(struct irq_desc *desc) 87 + { 88 + struct dc_ic_entry *entry = irq_desc_get_handler_data(desc); 89 + struct dc_ic_data *data = entry->data; 90 + unsigned int status, enable; 91 + unsigned int virq; 92 + 93 + chained_irq_enter(irq_desc_get_chip(desc), desc); 94 + 95 + regmap_read(data->regs, USERINTERRUPTSTATUS(entry->irq / 32), &status); 96 + regmap_read(data->regs, USERINTERRUPTENABLE(entry->irq / 32), &enable); 97 + 98 + status &= enable; 99 + 100 + if (status & BIT(entry->irq % 32)) { 101 + virq = irq_find_mapping(data->domain, entry->irq); 102 + if (virq) 103 + generic_handle_irq(virq); 104 + } 105 + 106 + chained_irq_exit(irq_desc_get_chip(desc), desc); 107 + } 108 + 109 + static const unsigned long unused_irq[REG_NUM] = {0x00000000, 0xfffe0008}; 110 + 111 + static int dc_ic_probe(struct platform_device *pdev) 112 + { 113 + struct device *dev = &pdev->dev; 114 + struct irq_chip_generic *gc; 115 + struct dc_ic_entry *entry; 116 + struct irq_chip_type *ct; 117 + struct dc_ic_data *data; 118 + void __iomem *base; 119 + int i, ret; 120 + 121 + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 122 + if (!data) 123 + return -ENOMEM; 124 + 125 + entry = devm_kcalloc(dev, IRQ_COUNT, sizeof(*entry), GFP_KERNEL); 126 + if (!entry) 127 + return -ENOMEM; 128 + 129 + base = devm_platform_ioremap_resource(pdev, 0); 130 + if (IS_ERR(base)) { 131 + dev_err(dev, "failed to initialize reg\n"); 132 + return PTR_ERR(base); 133 + } 134 + 135 + data->regs = devm_regmap_init_mmio(dev, base, &dc_ic_regmap_config); 136 + if (IS_ERR(data->regs)) 137 + return PTR_ERR(data->regs); 138 + 139 + data->clk_axi = devm_clk_get(dev, NULL); 140 + if (IS_ERR(data->clk_axi)) 141 + return dev_err_probe(dev, PTR_ERR(data->clk_axi), 142 + "failed to get AXI clock\n"); 143 + 144 + for (i = 0; i < IRQ_COUNT; i++) { 145 + /* skip the reserved IRQ */ 146 + if (i == IRQ_RESERVED) 147 + continue; 148 + 149 + ret = platform_get_irq(pdev, i); 150 + if (ret < 0) 151 + return ret; 152 + } 153 + 154 + dev_set_drvdata(dev, data); 155 + 156 + ret = devm_pm_runtime_enable(dev); 157 + if (ret) 158 + return ret; 159 + 160 + ret = pm_runtime_resume_and_get(dev); 161 + if (ret < 0) { 162 + dev_err(dev, "failed to get runtime PM sync: %d\n", ret); 163 + return ret; 164 + } 165 + 166 + for (i = 0; i < REG_NUM; i++) { 167 + /* mask and clear all interrupts */ 168 + regmap_write(data->regs, USERINTERRUPTENABLE(i), 0x0); 169 + regmap_write(data->regs, INTERRUPTENABLE(i), 0x0); 170 + regmap_write(data->regs, USERINTERRUPTCLEAR(i), 0xffffffff); 171 + regmap_write(data->regs, INTERRUPTCLEAR(i), 0xffffffff); 172 + 173 + /* set all interrupts to user mode */ 174 + regmap_write(data->regs, USERINTERRUPTMASK(i), 0xffffffff); 175 + } 176 + 177 + data->domain = irq_domain_add_linear(dev->of_node, IRQ_COUNT, 178 + &irq_generic_chip_ops, data); 179 + if (!data->domain) { 180 + dev_err(dev, "failed to create IRQ domain\n"); 181 + pm_runtime_put(dev); 182 + return -ENOMEM; 183 + } 184 + irq_domain_set_pm_device(data->domain, dev); 185 + 186 + ret = irq_alloc_domain_generic_chips(data->domain, 32, 1, "DC", 187 + handle_level_irq, 0, 0, 0); 188 + if (ret) { 189 + dev_err(dev, "failed to alloc generic IRQ chips: %d\n", ret); 190 + irq_domain_remove(data->domain); 191 + pm_runtime_put(dev); 192 + return ret; 193 + } 194 + 195 + for (i = 0; i < IRQ_COUNT; i += 32) { 196 + gc = irq_get_domain_generic_chip(data->domain, i); 197 + gc->reg_base = base; 198 + gc->unused = unused_irq[i / 32]; 199 + ct = gc->chip_types; 200 + ct->chip.irq_ack = irq_gc_ack_set_bit; 201 + ct->chip.irq_mask = irq_gc_mask_clr_bit; 202 + ct->chip.irq_unmask = irq_gc_mask_set_bit; 203 + ct->regs.ack = USERINTERRUPTCLEAR(i / 32); 204 + ct->regs.mask = USERINTERRUPTENABLE(i / 32); 205 + } 206 + 207 + for (i = 0; i < IRQ_COUNT; i++) { 208 + /* skip the reserved IRQ */ 209 + if (i == IRQ_RESERVED) 210 + continue; 211 + 212 + data->irq[i] = irq_of_parse_and_map(dev->of_node, i); 213 + 214 + entry[i].data = data; 215 + entry[i].irq = i; 216 + 217 + irq_set_chained_handler_and_data(data->irq[i], 218 + dc_ic_irq_handler, &entry[i]); 219 + } 220 + 221 + return 0; 222 + } 223 + 224 + static void dc_ic_remove(struct platform_device *pdev) 225 + { 226 + struct dc_ic_data *data = dev_get_drvdata(&pdev->dev); 227 + int i; 228 + 229 + for (i = 0; i < IRQ_COUNT; i++) { 230 + if (i == IRQ_RESERVED) 231 + continue; 232 + 233 + irq_set_chained_handler_and_data(data->irq[i], NULL, NULL); 234 + } 235 + 236 + irq_domain_remove(data->domain); 237 + 238 + pm_runtime_put_sync(&pdev->dev); 239 + } 240 + 241 + static int dc_ic_runtime_suspend(struct device *dev) 242 + { 243 + struct dc_ic_data *data = dev_get_drvdata(dev); 244 + 245 + clk_disable_unprepare(data->clk_axi); 246 + 247 + return 0; 248 + } 249 + 250 + static int dc_ic_runtime_resume(struct device *dev) 251 + { 252 + struct dc_ic_data *data = dev_get_drvdata(dev); 253 + int ret; 254 + 255 + ret = clk_prepare_enable(data->clk_axi); 256 + if (ret) 257 + dev_err(dev, "failed to enable AXI clock: %d\n", ret); 258 + 259 + return ret; 260 + } 261 + 262 + static const struct dev_pm_ops dc_ic_pm_ops = { 263 + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 264 + pm_runtime_force_resume) 265 + RUNTIME_PM_OPS(dc_ic_runtime_suspend, dc_ic_runtime_resume, NULL) 266 + }; 267 + 268 + static const struct of_device_id dc_ic_dt_ids[] = { 269 + { .compatible = "fsl,imx8qxp-dc-intc", }, 270 + { /* sentinel */ } 271 + }; 272 + 273 + struct platform_driver dc_ic_driver = { 274 + .probe = dc_ic_probe, 275 + .remove = dc_ic_remove, 276 + .driver = { 277 + .name = "imx8-dc-intc", 278 + .suppress_bind_attrs = true, 279 + .of_match_table = dc_ic_dt_ids, 280 + .pm = pm_sleep_ptr(&dc_ic_pm_ops), 281 + }, 282 + };