diff options
-rw-r--r-- | drivers/base/regmap/regmap-irq.c | 99 | ||||
-rw-r--r-- | include/linux/regmap.h | 31 |
2 files changed, 126 insertions, 4 deletions
diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 330c1f7e9665..5059748afd4c 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -35,6 +35,7 @@ struct regmap_irq_chip_data { int wake_count; void *status_reg_buf; + unsigned int *main_status_buf; unsigned int *status_buf; unsigned int *mask_buf; unsigned int *mask_buf_def; @@ -329,6 +330,33 @@ static const struct irq_chip regmap_irq_chip = { .irq_set_wake = regmap_irq_set_wake, }; +static inline int read_sub_irq_data(struct regmap_irq_chip_data *data, + unsigned int b) +{ + const struct regmap_irq_chip *chip = data->chip; + struct regmap *map = data->map; + struct regmap_irq_sub_irq_map *subreg; + int i, ret = 0; + + if (!chip->sub_reg_offsets) { + /* Assume linear mapping */ + ret = regmap_read(map, chip->status_base + + (b * map->reg_stride * data->irq_reg_stride), + &data->status_buf[b]); + } else { + subreg = &chip->sub_reg_offsets[b]; + for (i = 0; i < subreg->num_regs; i++) { + unsigned int offset = subreg->offset[i]; + + ret = regmap_read(map, chip->status_base + offset, + &data->status_buf[offset]); + if (ret) + break; + } + } + return ret; +} + static irqreturn_t regmap_irq_thread(int irq, void *d) { struct regmap_irq_chip_data *data = d; @@ -352,11 +380,65 @@ static irqreturn_t regmap_irq_thread(int irq, void *d) } /* - * Read in the statuses, using a single bulk read if possible - * in order to reduce the I/O overheads. + * Read only registers with active IRQs if the chip has 'main status + * register'. Else read in the statuses, using a single bulk read if + * possible in order to reduce the I/O overheads. */ - if (!map->use_single_read && map->reg_stride == 1 && - data->irq_reg_stride == 1) { + + if (chip->num_main_regs) { + unsigned int max_main_bits; + unsigned long size; + + size = chip->num_regs * sizeof(unsigned int); + + max_main_bits = (chip->num_main_status_bits) ? + chip->num_main_status_bits : chip->num_regs; + /* Clear the status buf as we don't read all status regs */ + memset(data->status_buf, 0, size); + + /* We could support bulk read for main status registers + * but I don't expect to see devices with really many main + * status registers so let's only support single reads for the + * sake of simplicity. and add bulk reads only if needed + */ + for (i = 0; i < chip->num_main_regs; i++) { + ret = regmap_read(map, chip->main_status + + (i * map->reg_stride + * data->irq_reg_stride), + &data->main_status_buf[i]); + if (ret) { + dev_err(map->dev, + "Failed to read IRQ status %d\n", + ret); + goto exit; + } + } + + /* Read sub registers with active IRQs */ + for (i = 0; i < chip->num_main_regs; i++) { + unsigned int b; + const unsigned long mreg = data->main_status_buf[i]; + + for_each_set_bit(b, &mreg, map->format.val_bytes * 8) { + if (i * map->format.val_bytes * 8 + b > + max_main_bits) + break; + ret = read_sub_irq_data(data, b); + + if (ret != 0) { + dev_err(map->dev, + "Failed to read IRQ status %d\n", + ret); + if (chip->runtime_pm) + pm_runtime_put(map->dev); + goto exit; + } + } + + } + } else if (!map->use_single_read && map->reg_stride == 1 && + data->irq_reg_stride == 1) { + u8 *buf8 = data->status_reg_buf; u16 *buf16 = data->status_reg_buf; u32 *buf32 = data->status_reg_buf; @@ -521,6 +603,15 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, if (!d) return -ENOMEM; + if (chip->num_main_regs) { + d->main_status_buf = kcalloc(chip->num_main_regs, + sizeof(unsigned int), + GFP_KERNEL); + + if (!d->main_status_buf) + goto err_alloc; + } + d->status_buf = kcalloc(chip->num_regs, sizeof(unsigned int), GFP_KERNEL); if (!d->status_buf) diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 1781b6cb793c..daeec7dbd65c 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -1131,11 +1131,37 @@ struct regmap_irq { .reg_offset = (_id) / (_reg_bits), \ } +#define REGMAP_IRQ_MAIN_REG_OFFSET(arr) \ + { .num_regs = ARRAY_SIZE((arr)), .offset = &(arr)[0] } + +struct regmap_irq_sub_irq_map { + unsigned int num_regs; + unsigned int *offset; +}; + /** * struct regmap_irq_chip - Description of a generic regmap irq_chip. * * @name: Descriptive name for IRQ controller. * + * @main_status: Base main status register address. For chips which have + * interrupts arranged in separate sub-irq blocks with own IRQ + * registers and which have a main IRQ registers indicating + * sub-irq blocks with unhandled interrupts. For such chips fill + * sub-irq register information in status_base, mask_base and + * ack_base. + * @num_main_status_bits: Should be given to chips where number of meaningfull + * main status bits differs from num_regs. + * @sub_reg_offsets: arrays of mappings from main register bits to sub irq + * registers. First item in array describes the registers + * for first main status bit. Second array for second bit etc. + * Offset is given as sub register status offset to + * status_base. Should contain num_regs arrays. + * Can be provided for chips with more complex mapping than + * 1.st bit to 1.st sub-reg, 2.nd bit to 2.nd sub-reg, ... + * @num_main_regs: Number of 'main status' irq registers for chips which have + * main_status set. + * * @status_base: Base status register address. * @mask_base: Base mask register address. * @mask_writeonly: Base mask register is write only. @@ -1181,6 +1207,11 @@ struct regmap_irq { struct regmap_irq_chip { const char *name; + unsigned int main_status; + unsigned int num_main_status_bits; + struct regmap_irq_sub_irq_map *sub_reg_offsets; + int num_main_regs; + unsigned int status_base; unsigned int mask_base; unsigned int unmask_base; |