linux/drivers/irqchip/irq-mbigen.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2015 Hisilicon Limited, All Rights Reserved.
   3 * Author: Jun Ma <majun258@huawei.com>
   4 * Author: Yun Wu <wuyun.wu@huawei.com>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 *
  10 * This program is distributed in the hope that it will be useful,
  11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 * GNU General Public License for more details.
  14 *
  15 * You should have received a copy of the GNU General Public License
  16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17 */
  18
  19#include <linux/acpi.h>
  20#include <linux/interrupt.h>
  21#include <linux/irqchip.h>
  22#include <linux/module.h>
  23#include <linux/msi.h>
  24#include <linux/of_address.h>
  25#include <linux/of_irq.h>
  26#include <linux/of_platform.h>
  27#include <linux/platform_device.h>
  28#include <linux/slab.h>
  29
  30/* Interrupt numbers per mbigen node supported */
  31#define IRQS_PER_MBIGEN_NODE            128
  32
  33/* 64 irqs (Pin0-pin63) are reserved for each mbigen chip */
  34#define RESERVED_IRQ_PER_MBIGEN_CHIP    64
  35
  36/* The maximum IRQ pin number of mbigen chip(start from 0) */
  37#define MAXIMUM_IRQ_PIN_NUM             1407
  38
  39/**
  40 * In mbigen vector register
  41 * bit[21:12]:  event id value
  42 * bit[11:0]:   device id
  43 */
  44#define IRQ_EVENT_ID_SHIFT              12
  45#define IRQ_EVENT_ID_MASK               0x3ff
  46
  47/* register range of each mbigen node */
  48#define MBIGEN_NODE_OFFSET              0x1000
  49
  50/* offset of vector register in mbigen node */
  51#define REG_MBIGEN_VEC_OFFSET           0x200
  52
  53/**
  54 * offset of clear register in mbigen node
  55 * This register is used to clear the status
  56 * of interrupt
  57 */
  58#define REG_MBIGEN_CLEAR_OFFSET         0xa000
  59
  60/**
  61 * offset of interrupt type register
  62 * This register is used to configure interrupt
  63 * trigger type
  64 */
  65#define REG_MBIGEN_TYPE_OFFSET          0x0
  66
  67/**
  68 * struct mbigen_device - holds the information of mbigen device.
  69 *
  70 * @pdev:               pointer to the platform device structure of mbigen chip.
  71 * @base:               mapped address of this mbigen chip.
  72 */
  73struct mbigen_device {
  74        struct platform_device  *pdev;
  75        void __iomem            *base;
  76};
  77
  78static inline unsigned int get_mbigen_vec_reg(irq_hw_number_t hwirq)
  79{
  80        unsigned int nid, pin;
  81
  82        hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP;
  83        nid = hwirq / IRQS_PER_MBIGEN_NODE + 1;
  84        pin = hwirq % IRQS_PER_MBIGEN_NODE;
  85
  86        return pin * 4 + nid * MBIGEN_NODE_OFFSET
  87                        + REG_MBIGEN_VEC_OFFSET;
  88}
  89
  90static inline void get_mbigen_type_reg(irq_hw_number_t hwirq,
  91                                        u32 *mask, u32 *addr)
  92{
  93        unsigned int nid, irq_ofst, ofst;
  94
  95        hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP;
  96        nid = hwirq / IRQS_PER_MBIGEN_NODE + 1;
  97        irq_ofst = hwirq % IRQS_PER_MBIGEN_NODE;
  98
  99        *mask = 1 << (irq_ofst % 32);
 100        ofst = irq_ofst / 32 * 4;
 101
 102        *addr = ofst + nid * MBIGEN_NODE_OFFSET
 103                + REG_MBIGEN_TYPE_OFFSET;
 104}
 105
 106static inline void get_mbigen_clear_reg(irq_hw_number_t hwirq,
 107                                        u32 *mask, u32 *addr)
 108{
 109        unsigned int ofst = (hwirq / 32) * 4;
 110
 111        *mask = 1 << (hwirq % 32);
 112        *addr = ofst + REG_MBIGEN_CLEAR_OFFSET;
 113}
 114
 115static void mbigen_eoi_irq(struct irq_data *data)
 116{
 117        void __iomem *base = data->chip_data;
 118        u32 mask, addr;
 119
 120        get_mbigen_clear_reg(data->hwirq, &mask, &addr);
 121
 122        writel_relaxed(mask, base + addr);
 123
 124        irq_chip_eoi_parent(data);
 125}
 126
 127static int mbigen_set_type(struct irq_data *data, unsigned int type)
 128{
 129        void __iomem *base = data->chip_data;
 130        u32 mask, addr, val;
 131
 132        if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING)
 133                return -EINVAL;
 134
 135        get_mbigen_type_reg(data->hwirq, &mask, &addr);
 136
 137        val = readl_relaxed(base + addr);
 138
 139        if (type == IRQ_TYPE_LEVEL_HIGH)
 140                val |= mask;
 141        else
 142                val &= ~mask;
 143
 144        writel_relaxed(val, base + addr);
 145
 146        return 0;
 147}
 148
 149static struct irq_chip mbigen_irq_chip = {
 150        .name =                 "mbigen-v2",
 151        .irq_mask =             irq_chip_mask_parent,
 152        .irq_unmask =           irq_chip_unmask_parent,
 153        .irq_eoi =              mbigen_eoi_irq,
 154        .irq_set_type =         mbigen_set_type,
 155        .irq_set_affinity =     irq_chip_set_affinity_parent,
 156};
 157
 158static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg)
 159{
 160        struct irq_data *d = irq_get_irq_data(desc->irq);
 161        void __iomem *base = d->chip_data;
 162        u32 val;
 163
 164        base += get_mbigen_vec_reg(d->hwirq);
 165        val = readl_relaxed(base);
 166
 167        val &= ~(IRQ_EVENT_ID_MASK << IRQ_EVENT_ID_SHIFT);
 168        val |= (msg->data << IRQ_EVENT_ID_SHIFT);
 169
 170        /* The address of doorbell is encoded in mbigen register by default
 171         * So,we don't need to program the doorbell address at here
 172         */
 173        writel_relaxed(val, base);
 174}
 175
 176static int mbigen_domain_translate(struct irq_domain *d,
 177                                    struct irq_fwspec *fwspec,
 178                                    unsigned long *hwirq,
 179                                    unsigned int *type)
 180{
 181        if (is_of_node(fwspec->fwnode) || is_acpi_device_node(fwspec->fwnode)) {
 182                if (fwspec->param_count != 2)
 183                        return -EINVAL;
 184
 185                if ((fwspec->param[0] > MAXIMUM_IRQ_PIN_NUM) ||
 186                        (fwspec->param[0] < RESERVED_IRQ_PER_MBIGEN_CHIP))
 187                        return -EINVAL;
 188                else
 189                        *hwirq = fwspec->param[0];
 190
 191                /* If there is no valid irq type, just use the default type */
 192                if ((fwspec->param[1] == IRQ_TYPE_EDGE_RISING) ||
 193                        (fwspec->param[1] == IRQ_TYPE_LEVEL_HIGH))
 194                        *type = fwspec->param[1];
 195                else
 196                        return -EINVAL;
 197
 198                return 0;
 199        }
 200        return -EINVAL;
 201}
 202
 203static int mbigen_irq_domain_alloc(struct irq_domain *domain,
 204                                        unsigned int virq,
 205                                        unsigned int nr_irqs,
 206                                        void *args)
 207{
 208        struct irq_fwspec *fwspec = args;
 209        irq_hw_number_t hwirq;
 210        unsigned int type;
 211        struct mbigen_device *mgn_chip;
 212        int i, err;
 213
 214        err = mbigen_domain_translate(domain, fwspec, &hwirq, &type);
 215        if (err)
 216                return err;
 217
 218        err = platform_msi_domain_alloc(domain, virq, nr_irqs);
 219        if (err)
 220                return err;
 221
 222        mgn_chip = platform_msi_get_host_data(domain);
 223
 224        for (i = 0; i < nr_irqs; i++)
 225                irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
 226                                      &mbigen_irq_chip, mgn_chip->base);
 227
 228        return 0;
 229}
 230
 231static const struct irq_domain_ops mbigen_domain_ops = {
 232        .translate      = mbigen_domain_translate,
 233        .alloc          = mbigen_irq_domain_alloc,
 234        .free           = irq_domain_free_irqs_common,
 235};
 236
 237static int mbigen_of_create_domain(struct platform_device *pdev,
 238                                   struct mbigen_device *mgn_chip)
 239{
 240        struct device *parent;
 241        struct platform_device *child;
 242        struct irq_domain *domain;
 243        struct device_node *np;
 244        u32 num_pins;
 245
 246        for_each_child_of_node(pdev->dev.of_node, np) {
 247                if (!of_property_read_bool(np, "interrupt-controller"))
 248                        continue;
 249
 250                parent = platform_bus_type.dev_root;
 251                child = of_platform_device_create(np, NULL, parent);
 252                if (!child)
 253                        return -ENOMEM;
 254
 255                if (of_property_read_u32(child->dev.of_node, "num-pins",
 256                                         &num_pins) < 0) {
 257                        dev_err(&pdev->dev, "No num-pins property\n");
 258                        return -EINVAL;
 259                }
 260
 261                domain = platform_msi_create_device_domain(&child->dev, num_pins,
 262                                                           mbigen_write_msg,
 263                                                           &mbigen_domain_ops,
 264                                                           mgn_chip);
 265                if (!domain)
 266                        return -ENOMEM;
 267        }
 268
 269        return 0;
 270}
 271
 272#ifdef CONFIG_ACPI
 273static int mbigen_acpi_create_domain(struct platform_device *pdev,
 274                                     struct mbigen_device *mgn_chip)
 275{
 276        struct irq_domain *domain;
 277        u32 num_pins = 0;
 278        int ret;
 279
 280        /*
 281         * "num-pins" is the total number of interrupt pins implemented in
 282         * this mbigen instance, and mbigen is an interrupt controller
 283         * connected to ITS  converting wired interrupts into MSI, so we
 284         * use "num-pins" to alloc MSI vectors which are needed by client
 285         * devices connected to it.
 286         *
 287         * Here is the DSDT device node used for mbigen in firmware:
 288         *      Device(MBI0) {
 289         *              Name(_HID, "HISI0152")
 290         *              Name(_UID, Zero)
 291         *              Name(_CRS, ResourceTemplate() {
 292         *                      Memory32Fixed(ReadWrite, 0xa0080000, 0x10000)
 293         *              })
 294         *
 295         *              Name(_DSD, Package () {
 296         *                      ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
 297         *                      Package () {
 298         *                              Package () {"num-pins", 378}
 299         *                      }
 300         *              })
 301         *      }
 302         */
 303        ret = device_property_read_u32(&pdev->dev, "num-pins", &num_pins);
 304        if (ret || num_pins == 0)
 305                return -EINVAL;
 306
 307        domain = platform_msi_create_device_domain(&pdev->dev, num_pins,
 308                                                   mbigen_write_msg,
 309                                                   &mbigen_domain_ops,
 310                                                   mgn_chip);
 311        if (!domain)
 312                return -ENOMEM;
 313
 314        return 0;
 315}
 316#else
 317static inline int mbigen_acpi_create_domain(struct platform_device *pdev,
 318                                            struct mbigen_device *mgn_chip)
 319{
 320        return -ENODEV;
 321}
 322#endif
 323
 324static int mbigen_device_probe(struct platform_device *pdev)
 325{
 326        struct mbigen_device *mgn_chip;
 327        struct resource *res;
 328        int err;
 329
 330        mgn_chip = devm_kzalloc(&pdev->dev, sizeof(*mgn_chip), GFP_KERNEL);
 331        if (!mgn_chip)
 332                return -ENOMEM;
 333
 334        mgn_chip->pdev = pdev;
 335
 336        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 337        if (!res)
 338                return -EINVAL;
 339
 340        mgn_chip->base = devm_ioremap(&pdev->dev, res->start,
 341                                      resource_size(res));
 342        if (!mgn_chip->base) {
 343                dev_err(&pdev->dev, "failed to ioremap %pR\n", res);
 344                return -ENOMEM;
 345        }
 346
 347        if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node)
 348                err = mbigen_of_create_domain(pdev, mgn_chip);
 349        else if (ACPI_COMPANION(&pdev->dev))
 350                err = mbigen_acpi_create_domain(pdev, mgn_chip);
 351        else
 352                err = -EINVAL;
 353
 354        if (err) {
 355                dev_err(&pdev->dev, "Failed to create mbi-gen@%p irqdomain",
 356                        mgn_chip->base);
 357                return err;
 358        }
 359
 360        platform_set_drvdata(pdev, mgn_chip);
 361        return 0;
 362}
 363
 364static const struct of_device_id mbigen_of_match[] = {
 365        { .compatible = "hisilicon,mbigen-v2" },
 366        { /* END */ }
 367};
 368MODULE_DEVICE_TABLE(of, mbigen_of_match);
 369
 370static const struct acpi_device_id mbigen_acpi_match[] = {
 371        { "HISI0152", 0 },
 372        {}
 373};
 374MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match);
 375
 376static struct platform_driver mbigen_platform_driver = {
 377        .driver = {
 378                .name           = "Hisilicon MBIGEN-V2",
 379                .of_match_table = mbigen_of_match,
 380                .acpi_match_table = ACPI_PTR(mbigen_acpi_match),
 381        },
 382        .probe                  = mbigen_device_probe,
 383};
 384
 385module_platform_driver(mbigen_platform_driver);
 386
 387MODULE_AUTHOR("Jun Ma <majun258@huawei.com>");
 388MODULE_AUTHOR("Yun Wu <wuyun.wu@huawei.com>");
 389MODULE_LICENSE("GPL");
 390MODULE_DESCRIPTION("Hisilicon MBI Generator driver");
 391