[RFC PATCH RESEND v1 1/3] regmap-irq: Add support for peripheral offsets

From: Guru Das Srinagesh
Date: Tue Oct 20 2020 - 18:33:58 EST


Some MFD chips do not have the register space for their peripherals
mapped out with a fixed stride. Add peripheral address offsets to the
framework to support such address spaces.

In this new scheme, the regmap-irq client registering with the framework
shall have to define the *_base registers (e.g. status_base, mask_base,
type_base, etc.) as those of the very first peripheral in the chip, and
then specify address offsets of each subsequent peripheral so that their
corresponding *_base registers may be calculated by the framework. The
first element of the periph_offs array must be zero so that the first
peripherals' addresses may be accessed.

Some MFD chips define two registers in addition to the IRQ type
registers: POLARITY_HI and POLARITY_LO, so add support to manage their
data as well as write to them.

Signed-off-by: Guru Das Srinagesh <gurus@xxxxxxxxxxxxxx>
---
drivers/base/regmap/regmap-irq.c | 191 ++++++++++++++++++++++++++++++++-------
include/linux/regmap.h | 6 ++
2 files changed, 163 insertions(+), 34 deletions(-)

diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c
index ad5c2de..dbf2c86 100644
--- a/drivers/base/regmap/regmap-irq.c
+++ b/drivers/base/regmap/regmap-irq.c
@@ -38,6 +38,8 @@ struct regmap_irq_chip_data {
unsigned int *wake_buf;
unsigned int *type_buf;
unsigned int *type_buf_def;
+ unsigned int *polarity_hi_buf;
+ unsigned int *polarity_lo_buf;

unsigned int irq_reg_stride;
unsigned int type_reg_stride;
@@ -87,8 +89,13 @@ static void regmap_irq_sync_unlock(struct irq_data *data)

if (d->clear_status) {
for (i = 0; i < d->chip->num_regs; i++) {
- reg = d->chip->status_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (d->chip->periph_offs)
+ reg = d->chip->status_base +
+ d->chip->periph_offs[i];
+ else
+ reg = d->chip->status_base +
+ (i * map->reg_stride *
+ d->irq_reg_stride);

ret = regmap_read(map, reg, &val);
if (ret)
@@ -108,8 +115,13 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
if (!d->chip->mask_base)
continue;

- reg = d->chip->mask_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (d->chip->periph_offs)
+ reg = d->chip->mask_base +
+ d->chip->periph_offs[i];
+ else
+ reg = d->chip->mask_base +
+ (i * map->reg_stride * d->irq_reg_stride);
+
if (d->chip->mask_invert) {
ret = regmap_irq_update_bits(d, reg,
d->mask_buf_def[i], ~d->mask_buf[i]);
@@ -136,8 +148,13 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
dev_err(d->map->dev, "Failed to sync masks in %x\n",
reg);

- reg = d->chip->wake_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (d->chip->periph_offs)
+ reg = d->chip->wake_base +
+ d->chip->periph_offs[i];
+ else
+ reg = d->chip->wake_base +
+ (i * map->reg_stride * d->irq_reg_stride);
+
if (d->wake_buf) {
if (d->chip->wake_invert)
ret = regmap_irq_update_bits(d, reg,
@@ -161,8 +178,14 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
* it'll be ignored in irq handler, then may introduce irq storm
*/
if (d->mask_buf[i] && (d->chip->ack_base || d->chip->use_ack)) {
- reg = d->chip->ack_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (d->chip->periph_offs)
+ reg = d->chip->ack_base +
+ d->chip->periph_offs[i];
+ else
+ reg = d->chip->ack_base +
+ (i * map->reg_stride *
+ d->irq_reg_stride);
+
/* some chips ack by write 0 */
if (d->chip->ack_invert)
ret = regmap_write(map, reg, ~d->mask_buf[i]);
@@ -187,8 +210,14 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
for (i = 0; i < d->chip->num_type_reg; i++) {
if (!d->type_buf_def[i])
continue;
- reg = d->chip->type_base +
- (i * map->reg_stride * d->type_reg_stride);
+ if (d->chip->periph_offs)
+ reg = d->chip->type_base +
+ d->chip->periph_offs[i];
+ else
+ reg = d->chip->type_base +
+ (i * map->reg_stride *
+ d->type_reg_stride);
+
if (d->chip->type_invert)
ret = regmap_irq_update_bits(d, reg,
d->type_buf_def[i], ~d->type_buf[i]);
@@ -198,6 +227,25 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
if (ret != 0)
dev_err(d->map->dev, "Failed to sync type in %x\n",
reg);
+
+ if (!d->chip->periph_offs ||
+ !d->chip->polarity_hi_base ||
+ !d->chip->polarity_lo_base)
+ continue;
+
+ reg = d->chip->polarity_hi_base +
+ d->chip->periph_offs[i];
+ ret = regmap_write(map, reg, d->polarity_hi_buf[i]);
+ if (ret != 0)
+ dev_err(d->map->dev, "Failed to sync polarity hi in %x\n",
+ reg);
+
+ reg = d->chip->polarity_lo_base +
+ d->chip->periph_offs[i];
+ ret = regmap_write(map, reg, d->polarity_lo_buf[i]);
+ if (ret != 0)
+ dev_err(d->map->dev, "Failed to sync polarity lo in %x\n",
+ reg);
}
}

@@ -280,23 +328,49 @@ static int regmap_irq_set_type(struct irq_data *data, unsigned int type)
switch (type) {
case IRQ_TYPE_EDGE_FALLING:
d->type_buf[reg] |= t->type_falling_val;
+ if (d->chip->periph_offs) {
+ d->polarity_hi_buf[reg] &= ~t->type_falling_val;
+ d->polarity_lo_buf[reg] |= t->type_falling_val;
+ }
break;

case IRQ_TYPE_EDGE_RISING:
d->type_buf[reg] |= t->type_rising_val;
+ if (d->chip->periph_offs) {
+ d->polarity_hi_buf[reg] |= t->type_rising_val;
+ d->polarity_lo_buf[reg] &= ~t->type_rising_val;
+ }
break;

case IRQ_TYPE_EDGE_BOTH:
d->type_buf[reg] |= (t->type_falling_val |
t->type_rising_val);
+ if (d->chip->periph_offs) {
+ d->polarity_hi_buf[reg] |= (t->type_falling_val |
+ t->type_rising_val);
+ d->polarity_lo_buf[reg] |= (t->type_falling_val |
+ t->type_rising_val);
+ }
break;

case IRQ_TYPE_LEVEL_HIGH:
- d->type_buf[reg] |= t->type_level_high_val;
+ if (!d->chip->periph_offs) {
+ d->type_buf[reg] |= t->type_level_high_val;
+ } else {
+ d->type_buf[reg] &= ~t->type_level_high_val;
+ d->polarity_hi_buf[reg] |= t->type_level_high_val;
+ d->polarity_lo_buf[reg] &= ~t->type_level_high_val;
+ }
break;

case IRQ_TYPE_LEVEL_LOW:
- d->type_buf[reg] |= t->type_level_low_val;
+ if (!d->chip->periph_offs) {
+ d->type_buf[reg] |= t->type_level_low_val;
+ } else {
+ d->type_buf[reg] &= ~t->type_level_low_val;
+ d->polarity_hi_buf[reg] &= ~t->type_level_low_val;
+ d->polarity_lo_buf[reg] |= t->type_level_low_val;
+ }
break;
default:
return -EINVAL;
@@ -342,12 +416,10 @@ static inline int read_sub_irq_data(struct regmap_irq_chip_data *data,
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 {
+ if (chip->periph_offs) {
+ ret = regmap_read(map, chip->status_base + chip->periph_offs[b],
+ &data->status_buf[b]);
+ } else if (chip->sub_reg_offsets) {
subreg = &chip->sub_reg_offsets[b];
for (i = 0; i < subreg->num_regs; i++) {
unsigned int offset = subreg->offset[i];
@@ -357,6 +429,11 @@ static inline int read_sub_irq_data(struct regmap_irq_chip_data *data,
if (ret)
break;
}
+ } else {
+ /* Assume linear mapping */
+ ret = regmap_read(map, chip->status_base +
+ (b * map->reg_stride * data->irq_reg_stride),
+ &data->status_buf[b]);
}
return ret;
}
@@ -474,10 +551,14 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)

} else {
for (i = 0; i < data->chip->num_regs; i++) {
- ret = regmap_read(map, chip->status_base +
- (i * map->reg_stride
- * data->irq_reg_stride),
- &data->status_buf[i]);
+ if (chip->periph_offs)
+ reg = chip->status_base + chip->periph_offs[i];
+ else
+ reg = chip->status_base +
+ (i * map->reg_stride *
+ data->irq_reg_stride);
+
+ ret = regmap_read(map, reg, &data->status_buf[i]);

if (ret != 0) {
dev_err(map->dev,
@@ -499,8 +580,13 @@ static irqreturn_t regmap_irq_thread(int irq, void *d)
data->status_buf[i] &= ~data->mask_buf[i];

if (data->status_buf[i] && (chip->ack_base || chip->use_ack)) {
- reg = chip->ack_base +
- (i * map->reg_stride * data->irq_reg_stride);
+ if (chip->periph_offs)
+ reg = chip->ack_base + chip->periph_offs[i];
+ else
+ reg = chip->ack_base +
+ (i * map->reg_stride *
+ data->irq_reg_stride);
+
if (chip->ack_invert)
ret = regmap_write(map, reg,
~data->status_buf[i]);
@@ -662,6 +748,18 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
goto err_alloc;
}

+ if (chip->periph_offs) {
+ d->polarity_hi_buf = kcalloc(chip->num_regs,
+ sizeof(unsigned int), GFP_KERNEL);
+ if (!d->polarity_hi_buf)
+ goto err_alloc;
+
+ d->polarity_lo_buf = kcalloc(chip->num_regs,
+ sizeof(unsigned int), GFP_KERNEL);
+ if (!d->polarity_lo_buf)
+ goto err_alloc;
+ }
+
d->irq_chip = regmap_irq_chip;
d->irq_chip.name = chip->name;
d->irq = irq;
@@ -700,8 +798,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
if (!chip->mask_base)
continue;

- reg = chip->mask_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (chip->periph_offs)
+ reg = chip->mask_base + chip->periph_offs[i];
+ else
+ reg = chip->mask_base +
+ (i * map->reg_stride * d->irq_reg_stride);
+
if (chip->mask_invert)
ret = regmap_irq_update_bits(d, reg,
d->mask_buf[i], ~d->mask_buf[i]);
@@ -725,8 +827,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
continue;

/* Ack masked but set interrupts */
- reg = chip->status_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (chip->periph_offs)
+ reg = chip->status_base + chip->periph_offs[i];
+ else
+ reg = chip->status_base +
+ (i * map->reg_stride * d->irq_reg_stride);
+
ret = regmap_read(map, reg, &d->status_buf[i]);
if (ret != 0) {
dev_err(map->dev, "Failed to read IRQ status: %d\n",
@@ -735,8 +841,13 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
}

if (d->status_buf[i] && (chip->ack_base || chip->use_ack)) {
- reg = chip->ack_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (chip->periph_offs)
+ reg = chip->ack_base + chip->periph_offs[i];
+ else
+ reg = chip->ack_base +
+ (i * map->reg_stride *
+ d->irq_reg_stride);
+
if (chip->ack_invert)
ret = regmap_write(map, reg,
~(d->status_buf[i] & d->mask_buf[i]));
@@ -765,8 +876,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
if (d->wake_buf) {
for (i = 0; i < chip->num_regs; i++) {
d->wake_buf[i] = d->mask_buf_def[i];
- reg = chip->wake_base +
- (i * map->reg_stride * d->irq_reg_stride);
+ if (chip->periph_offs)
+ reg = chip->wake_base + chip->periph_offs[i];
+ else
+ reg = chip->wake_base +
+ (i * map->reg_stride *
+ d->irq_reg_stride);

if (chip->wake_invert)
ret = regmap_irq_update_bits(d, reg,
@@ -786,8 +901,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,

if (chip->num_type_reg && !chip->type_in_mask) {
for (i = 0; i < chip->num_type_reg; ++i) {
- reg = chip->type_base +
- (i * map->reg_stride * d->type_reg_stride);
+ if (chip->periph_offs)
+ reg = chip->type_base + chip->periph_offs[i];
+ else
+ reg = chip->type_base +
+ (i * map->reg_stride *
+ d->type_reg_stride);

ret = regmap_read(map, reg, &d->type_buf_def[i]);

@@ -833,6 +952,8 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
/* Should really dispose of the domain but... */
err_alloc:
kfree(d->type_buf);
+ kfree(d->polarity_hi_buf);
+ kfree(d->polarity_lo_buf);
kfree(d->type_buf_def);
kfree(d->wake_buf);
kfree(d->mask_buf_def);
@@ -903,6 +1024,8 @@ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d)

irq_domain_remove(d->domain);
kfree(d->type_buf);
+ kfree(d->polarity_hi_buf);
+ kfree(d->polarity_lo_buf);
kfree(d->type_buf_def);
kfree(d->wake_buf);
kfree(d->mask_buf_def);
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index e7834d9..6fb1090 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -1338,6 +1338,7 @@ struct regmap_irq_sub_irq_map {
* 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, ...
+ * @periph_offs: Array of addresses of peripherals, should be num_reg long.
* @num_main_regs: Number of 'main status' irq registers for chips which have
* main_status set.
*
@@ -1350,6 +1351,8 @@ struct regmap_irq_sub_irq_map {
* Using zero value is possible with @use_ack bit.
* @wake_base: Base address for wake enables. If zero unsupported.
* @type_base: Base address for irq type. If zero unsupported.
+ * @polarity_hi_base: Base address for polarity high when periph_offs is used.
+ * @polarity_lo_base: Base address for polarity low when periph_offs is used.
* @irq_reg_stride: Stride to use for chips where registers are not contiguous.
* @init_ack_masked: Ack all masked interrupts once during initalization.
* @mask_invert: Inverted mask register: cleared bits are masked out.
@@ -1390,6 +1393,7 @@ struct regmap_irq_chip {
unsigned int main_status;
unsigned int num_main_status_bits;
struct regmap_irq_sub_irq_map *sub_reg_offsets;
+ unsigned int *periph_offs;
int num_main_regs;

unsigned int status_base;
@@ -1398,6 +1402,8 @@ struct regmap_irq_chip {
unsigned int ack_base;
unsigned int wake_base;
unsigned int type_base;
+ unsigned int polarity_hi_base;
+ unsigned int polarity_lo_base;
unsigned int irq_reg_stride;
bool mask_writeonly:1;
bool init_ack_masked:1;
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project