Commit 919eb475 authored by Mika Westerberg's avatar Mika Westerberg Committed by Linus Walleij

pinctrl: intel: Add support for variable size pad groups

The Intel GPIO hardware has a concept of pad groups, which means 1 to 32
pads occupying their own GPI_IS, GPI_IE, PAD_OWN and so on registers. The
existing hardware has the same amount of pads in each pad group (except the
last one) so it is possible to use community->gpp_size to calculate start
offset of each register.

With the next generation SoCs the pad group size is not always the same
anymore which means we cannot use community->gpp_size for register offset
calculations directly.

To support variable size pad groups we introduce struct intel_padgroup that
can be filled in by the client drivers according the hardware pad group
layout. The core driver will always use these when it performs calculations
for pad register offsets. The core driver will automatically populate pad
groups based on community->gpp_size if the driver does not provide any.
This makes sure the existing drivers still work as expected.
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: default avatarChuah, Kim Tatt <kim.tatt.chuah@intel.com>
Signed-off-by: default avatarTan Jui Nee <jui.nee.tan@intel.com>
Reviewed-by: default avatarAndy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: default avatarLinus Walleij <linus.walleij@linaro.org>
parent 41633edf
......@@ -117,6 +117,7 @@ struct intel_pinctrl {
};
#define pin_to_padno(c, p) ((p) - (c)->pin_base)
#define padgroup_offset(g, p) ((p) - (g)->base)
static struct intel_community *intel_get_community(struct intel_pinctrl *pctrl,
unsigned pin)
......@@ -135,6 +136,22 @@ static struct intel_community *intel_get_community(struct intel_pinctrl *pctrl,
return NULL;
}
static const struct intel_padgroup *
intel_community_get_padgroup(const struct intel_community *community,
unsigned pin)
{
int i;
for (i = 0; i < community->ngpps; i++) {
const struct intel_padgroup *padgrp = &community->gpps[i];
if (pin >= padgrp->base && pin < padgrp->base + padgrp->size)
return padgrp;
}
return NULL;
}
static void __iomem *intel_get_padcfg(struct intel_pinctrl *pctrl, unsigned pin,
unsigned reg)
{
......@@ -158,7 +175,8 @@ static void __iomem *intel_get_padcfg(struct intel_pinctrl *pctrl, unsigned pin,
static bool intel_pad_owned_by_host(struct intel_pinctrl *pctrl, unsigned pin)
{
const struct intel_community *community;
unsigned padno, gpp, offset, group;
const struct intel_padgroup *padgrp;
unsigned gpp, offset, gpp_offset;
void __iomem *padown;
community = intel_get_community(pctrl, pin);
......@@ -167,19 +185,23 @@ static bool intel_pad_owned_by_host(struct intel_pinctrl *pctrl, unsigned pin)
if (!community->padown_offset)
return true;
padno = pin_to_padno(community, pin);
group = padno / community->gpp_size;
gpp = PADOWN_GPP(padno % community->gpp_size);
offset = community->padown_offset + 0x10 * group + gpp * 4;
padgrp = intel_community_get_padgroup(community, pin);
if (!padgrp)
return false;
gpp_offset = padgroup_offset(padgrp, pin);
gpp = PADOWN_GPP(gpp_offset);
offset = community->padown_offset + padgrp->padown_num * 4 + gpp * 4;
padown = community->regs + offset;
return !(readl(padown) & PADOWN_MASK(padno));
return !(readl(padown) & PADOWN_MASK(gpp_offset));
}
static bool intel_pad_acpi_mode(struct intel_pinctrl *pctrl, unsigned pin)
{
const struct intel_community *community;
unsigned padno, gpp, offset;
const struct intel_padgroup *padgrp;
unsigned offset, gpp_offset;
void __iomem *hostown;
community = intel_get_community(pctrl, pin);
......@@ -188,18 +210,22 @@ static bool intel_pad_acpi_mode(struct intel_pinctrl *pctrl, unsigned pin)
if (!community->hostown_offset)
return false;
padno = pin_to_padno(community, pin);
gpp = padno / community->gpp_size;
offset = community->hostown_offset + gpp * 4;
padgrp = intel_community_get_padgroup(community, pin);
if (!padgrp)
return true;
gpp_offset = padgroup_offset(padgrp, pin);
offset = community->hostown_offset + padgrp->reg_num * 4;
hostown = community->regs + offset;
return !(readl(hostown) & BIT(padno % community->gpp_size));
return !(readl(hostown) & BIT(gpp_offset));
}
static bool intel_pad_locked(struct intel_pinctrl *pctrl, unsigned pin)
{
struct intel_community *community;
unsigned padno, gpp, offset;
const struct intel_padgroup *padgrp;
unsigned offset, gpp_offset;
u32 value;
community = intel_get_community(pctrl, pin);
......@@ -208,22 +234,25 @@ static bool intel_pad_locked(struct intel_pinctrl *pctrl, unsigned pin)
if (!community->padcfglock_offset)
return false;
padno = pin_to_padno(community, pin);
gpp = padno / community->gpp_size;
padgrp = intel_community_get_padgroup(community, pin);
if (!padgrp)
return true;
gpp_offset = padgroup_offset(padgrp, pin);
/*
* If PADCFGLOCK and PADCFGLOCKTX bits are both clear for this pad,
* the pad is considered unlocked. Any other case means that it is
* either fully or partially locked and we don't touch it.
*/
offset = community->padcfglock_offset + gpp * 8;
offset = community->padcfglock_offset + padgrp->reg_num * 8;
value = readl(community->regs + offset);
if (value & BIT(pin % community->gpp_size))
if (value & BIT(gpp_offset))
return true;
offset = community->padcfglock_offset + 4 + gpp * 8;
offset = community->padcfglock_offset + 4 + padgrp->reg_num * 8;
value = readl(community->regs + offset);
if (value & BIT(pin % community->gpp_size))
if (value & BIT(gpp_offset))
return true;
return false;
......@@ -777,18 +806,22 @@ static void intel_gpio_irq_ack(struct irq_data *d)
const struct intel_community *community;
unsigned pin = irqd_to_hwirq(d);
raw_spin_lock(&pctrl->lock);
community = intel_get_community(pctrl, pin);
if (community) {
unsigned padno = pin_to_padno(community, pin);
unsigned gpp_offset = padno % community->gpp_size;
unsigned gpp = padno / community->gpp_size;
const struct intel_padgroup *padgrp;
unsigned gpp, gpp_offset;
writel(BIT(gpp_offset), community->regs + GPI_IS + gpp * 4);
}
padgrp = intel_community_get_padgroup(community, pin);
if (!padgrp)
return;
gpp = padgrp->reg_num;
gpp_offset = padgroup_offset(padgrp, pin);
raw_spin_lock(&pctrl->lock);
writel(BIT(gpp_offset), community->regs + GPI_IS + gpp * 4);
raw_spin_unlock(&pctrl->lock);
}
}
static void intel_gpio_irq_enable(struct irq_data *d)
......@@ -797,27 +830,30 @@ static void intel_gpio_irq_enable(struct irq_data *d)
struct intel_pinctrl *pctrl = gpiochip_get_data(gc);
const struct intel_community *community;
unsigned pin = irqd_to_hwirq(d);
unsigned long flags;
raw_spin_lock_irqsave(&pctrl->lock, flags);
community = intel_get_community(pctrl, pin);
if (community) {
unsigned padno = pin_to_padno(community, pin);
unsigned gpp_size = community->gpp_size;
unsigned gpp_offset = padno % gpp_size;
unsigned gpp = padno / gpp_size;
const struct intel_padgroup *padgrp;
unsigned gpp, gpp_offset;
unsigned long flags;
u32 value;
padgrp = intel_community_get_padgroup(community, pin);
if (!padgrp)
return;
gpp = padgrp->reg_num;
gpp_offset = padgroup_offset(padgrp, pin);
raw_spin_lock_irqsave(&pctrl->lock, flags);
/* Clear interrupt status first to avoid unexpected interrupt */
writel(BIT(gpp_offset), community->regs + GPI_IS + gpp * 4);
value = readl(community->regs + community->ie_offset + gpp * 4);
value |= BIT(gpp_offset);
writel(value, community->regs + community->ie_offset + gpp * 4);
}
raw_spin_unlock_irqrestore(&pctrl->lock, flags);
}
}
static void intel_gpio_irq_mask_unmask(struct irq_data *d, bool mask)
......@@ -826,28 +862,33 @@ static void intel_gpio_irq_mask_unmask(struct irq_data *d, bool mask)
struct intel_pinctrl *pctrl = gpiochip_get_data(gc);
const struct intel_community *community;
unsigned pin = irqd_to_hwirq(d);
unsigned long flags;
raw_spin_lock_irqsave(&pctrl->lock, flags);
community = intel_get_community(pctrl, pin);
if (community) {
unsigned padno = pin_to_padno(community, pin);
unsigned gpp_offset = padno % community->gpp_size;
unsigned gpp = padno / community->gpp_size;
const struct intel_padgroup *padgrp;
unsigned gpp, gpp_offset;
unsigned long flags;
void __iomem *reg;
u32 value;
padgrp = intel_community_get_padgroup(community, pin);
if (!padgrp)
return;
gpp = padgrp->reg_num;
gpp_offset = padgroup_offset(padgrp, pin);
reg = community->regs + community->ie_offset + gpp * 4;
raw_spin_lock_irqsave(&pctrl->lock, flags);
value = readl(reg);
if (mask)
value &= ~BIT(gpp_offset);
else
value |= BIT(gpp_offset);
writel(value, reg);
}
raw_spin_unlock_irqrestore(&pctrl->lock, flags);
}
}
static void intel_gpio_irq_mask(struct irq_data *d)
......@@ -938,23 +979,20 @@ static irqreturn_t intel_gpio_community_irq_handler(struct intel_pinctrl *pctrl,
int gpp;
for (gpp = 0; gpp < community->ngpps; gpp++) {
const struct intel_padgroup *padgrp = &community->gpps[gpp];
unsigned long pending, enabled, gpp_offset;
pending = readl(community->regs + GPI_IS + gpp * 4);
pending = readl(community->regs + GPI_IS + padgrp->reg_num * 4);
enabled = readl(community->regs + community->ie_offset +
gpp * 4);
padgrp->reg_num * 4);
/* Only interrupts that are enabled */
pending &= enabled;
for_each_set_bit(gpp_offset, &pending, community->gpp_size) {
for_each_set_bit(gpp_offset, &pending, padgrp->size) {
unsigned padno, irq;
/*
* The last group in community can have less pins
* than NPADS_IN_GPP.
*/
padno = gpp_offset + gpp * community->gpp_size;
padno = padgrp->base - community->pin_base + gpp_offset;
if (padno >= community->npins)
break;
......@@ -1045,6 +1083,56 @@ static int intel_gpio_probe(struct intel_pinctrl *pctrl, int irq)
return 0;
}
static int intel_pinctrl_add_padgroups(struct intel_pinctrl *pctrl,
struct intel_community *community)
{
struct intel_padgroup *gpps;
unsigned npins = community->npins;
unsigned padown_num = 0;
size_t ngpps, i;
if (community->gpps)
ngpps = community->ngpps;
else
ngpps = DIV_ROUND_UP(community->npins, community->gpp_size);
gpps = devm_kcalloc(pctrl->dev, ngpps, sizeof(*gpps), GFP_KERNEL);
if (!gpps)
return -ENOMEM;
for (i = 0; i < ngpps; i++) {
if (community->gpps) {
gpps[i] = community->gpps[i];
} else {
unsigned gpp_size = community->gpp_size;
gpps[i].reg_num = i;
gpps[i].base = community->pin_base + i * gpp_size;
gpps[i].size = min(gpp_size, npins);
npins -= gpps[i].size;
}
if (gpps[i].size > 32)
return -EINVAL;
gpps[i].padown_num = padown_num;
/*
* In older hardware the number of padown registers per
* group is fixed regardless of the group size.
*/
if (community->gpp_num_padown_regs)
padown_num += community->gpp_num_padown_regs;
else
padown_num += DIV_ROUND_UP(gpps[i].size * 4, 32);
}
community->ngpps = ngpps;
community->gpps = gpps;
return 0;
}
static int intel_pinctrl_pm_init(struct intel_pinctrl *pctrl)
{
#ifdef CONFIG_PM_SLEEP
......@@ -1142,8 +1230,10 @@ int intel_pinctrl_probe(struct platform_device *pdev,
community->regs = regs;
community->pad_regs = regs + padbar;
community->ngpps = DIV_ROUND_UP(community->npins,
community->gpp_size);
ret = intel_pinctrl_add_padgroups(pctrl, community);
if (ret)
return ret;
}
irq = platform_get_irq(pdev, 0);
......
......@@ -43,6 +43,23 @@ struct intel_function {
size_t ngroups;
};
/**
* struct intel_padgroup - Hardware pad group information
* @reg_num: GPI_IS register number
* @base: Starting pin of this group
* @size: Size of this group (maximum is 32).
* @padown_num: PAD_OWN register number (assigned by the core driver)
*
* If pad groups of a community are not the same size, use this structure
* to specify them.
*/
struct intel_padgroup {
unsigned reg_num;
unsigned base;
unsigned size;
unsigned padown_num;
};
/**
* struct intel_community - Intel pin community description
* @barno: MMIO BAR number where registers for this community reside
......@@ -56,13 +73,22 @@ struct intel_function {
* @ie_offset: Register offset of GPI_IE from @regs.
* @pin_base: Starting pin of pins in this community
* @gpp_size: Maximum number of pads in each group, such as PADCFGLOCK,
* HOSTSW_OWN, GPI_IS, GPI_IE, etc.
* HOSTSW_OWN, GPI_IS, GPI_IE, etc. Used when @gpps is %NULL.
* @gpp_num_padown_regs: Number of pad registers each pad group consumes at
* minimum. Use %0 if the number of registers can be
* determined by the size of the group.
* @npins: Number of pins in this community
* @features: Additional features supported by the hardware
* @gpps: Pad groups if the controller has variable size pad groups
* @ngpps: Number of pad groups in this community
* @regs: Community specific common registers (reserved for core driver)
* @pad_regs: Community specific pad registers (reserved for core driver)
* @ngpps: Number of groups (hw groups) in this community (reserved for
* core driver)
*
* Most Intel GPIO host controllers this driver supports each pad group is
* of equal size (except the last one). In that case the driver can just
* fill in @gpp_size field and let the core driver to handle the rest. If
* the controller has pad groups of variable size the client driver can
* pass custom @gpps and @ngpps instead.
*/
struct intel_community {
unsigned barno;
......@@ -72,11 +98,14 @@ struct intel_community {
unsigned ie_offset;
unsigned pin_base;
unsigned gpp_size;
unsigned gpp_num_padown_regs;
size_t npins;
unsigned features;
const struct intel_padgroup *gpps;
size_t ngpps;
/* Reserved for the core driver */
void __iomem *regs;
void __iomem *pad_regs;
size_t ngpps;
};
/* Additional features supported by the hardware */
......
......@@ -31,6 +31,7 @@
.hostown_offset = SPT_HOSTSW_OWN, \
.ie_offset = SPT_GPI_IE, \
.gpp_size = 24, \
.gpp_num_padown_regs = 4, \
.pin_base = (s), \
.npins = ((e) - (s) + 1), \
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment