Skip to content
Merged
3 changes: 3 additions & 0 deletions arch/arm64/boot/dts/marvell/armada-ap80x.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&ap_pinctrl 0 0 20>;
marvell,pwm-offset = <0x10c0>;
#pwm-cells = <2>;
clocks = <&ap_clk 3>;
};
};

Expand Down
10 changes: 10 additions & 0 deletions arch/arm64/boot/dts/marvell/armada-cp11x.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,17 @@
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&CP11X_LABEL(pinctrl) 0 0 32>;
marvell,pwm-offset = <0x1f0>;
#pwm-cells = <2>;
interrupt-controller;
interrupts = <86 IRQ_TYPE_LEVEL_HIGH>,
<85 IRQ_TYPE_LEVEL_HIGH>,
<84 IRQ_TYPE_LEVEL_HIGH>,
<83 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <2>;
clock-names = "core", "axi";
clocks = <&CP11X_LABEL(clk) 1 21>,
<&CP11X_LABEL(clk) 1 17>;
status = "disabled";
};

Expand All @@ -257,12 +262,17 @@
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&CP11X_LABEL(pinctrl) 0 32 31>;
marvell,pwm-offset = <0x1f0>;
#pwm-cells = <2>;
interrupt-controller;
interrupts = <82 IRQ_TYPE_LEVEL_HIGH>,
<81 IRQ_TYPE_LEVEL_HIGH>,
<80 IRQ_TYPE_LEVEL_HIGH>,
<79 IRQ_TYPE_LEVEL_HIGH>;
#interrupt-cells = <2>;
clock-names = "core", "axi";
clocks = <&CP11X_LABEL(clk) 1 21>,
<&CP11X_LABEL(clk) 1 17>;
status = "disabled";
};
};
Expand Down
201 changes: 120 additions & 81 deletions drivers/gpio/gpio-mvebu.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@
*/
#define PWM_BLINK_ON_DURATION_OFF 0x0
#define PWM_BLINK_OFF_DURATION_OFF 0x4
#define PWM_BLINK_COUNTER_B_OFF 0x8

/* Armada 8k variant gpios register offsets */
#define AP80X_GPIO0_OFF_A8K 0x1040
#define CP11X_GPIO0_OFF_A8K 0x100
#define CP11X_GPIO1_OFF_A8K 0x140

/* The MV78200 has per-CPU registers for edge mask and level mask */
#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18)
Expand All @@ -93,7 +98,8 @@
#define MVEBU_MAX_GPIO_PER_BANK 32

struct mvebu_pwm {
void __iomem *membase;
struct regmap *regs;
u32 offset;
unsigned long clk_rate;
struct gpio_desc *gpiod;
struct pwm_chip chip;
Expand Down Expand Up @@ -279,17 +285,17 @@ mvebu_gpio_write_level_mask(struct mvebu_gpio_chip *mvchip, u32 val)
}

/*
* Functions returning addresses of individual registers for a given
* Functions returning offsets of individual registers for a given
* PWM controller.
*/
static void __iomem *mvebu_pwmreg_blink_on_duration(struct mvebu_pwm *mvpwm)
static unsigned int mvebu_pwmreg_blink_on_duration(struct mvebu_pwm *mvpwm)
{
return mvpwm->membase + PWM_BLINK_ON_DURATION_OFF;
return mvpwm->offset + PWM_BLINK_ON_DURATION_OFF;
}

static void __iomem *mvebu_pwmreg_blink_off_duration(struct mvebu_pwm *mvpwm)
static unsigned int mvebu_pwmreg_blink_off_duration(struct mvebu_pwm *mvpwm)
{
return mvpwm->membase + PWM_BLINK_OFF_DURATION_OFF;
return mvpwm->offset + PWM_BLINK_OFF_DURATION_OFF;
}

/*
Expand Down Expand Up @@ -600,6 +606,13 @@ static void mvebu_gpio_irq_handler(struct irq_desc *desc)
chained_irq_exit(chip, desc);
}

static const struct regmap_config mvebu_gpio_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
};

/*
* Functions implementing the pwm_chip methods
*/
Expand Down Expand Up @@ -660,32 +673,22 @@ static void mvebu_pwm_get_state(struct pwm_chip *chip,

spin_lock_irqsave(&mvpwm->lock, flags);

val = (unsigned long long)
readl_relaxed(mvebu_pwmreg_blink_on_duration(mvpwm));
val *= NSEC_PER_SEC;
do_div(val, mvpwm->clk_rate);
if (val > UINT_MAX)
state->duty_cycle = UINT_MAX;
else if (val)
state->duty_cycle = val;
regmap_read(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm), &u);
/* Hardware treats zero as 2^32. See mvebu_pwm_apply(). */
if (u > 0)
val = u;
else
state->duty_cycle = 1;

val = (unsigned long long)
readl_relaxed(mvebu_pwmreg_blink_off_duration(mvpwm));
val *= NSEC_PER_SEC;
do_div(val, mvpwm->clk_rate);
if (val < state->duty_cycle) {
state->period = 1;
} else {
val -= state->duty_cycle;
if (val > UINT_MAX)
state->period = UINT_MAX;
else if (val)
state->period = val;
else
state->period = 1;
}
val = UINT_MAX + 1ULL;
state->duty_cycle = DIV_ROUND_UP_ULL(val * NSEC_PER_SEC,
mvpwm->clk_rate);

regmap_read(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm), &u);
/* period = on + off duration */
if (u > 0)
val += u;
else
val += UINT_MAX + 1ULL;
state->period = DIV_ROUND_UP_ULL(val * NSEC_PER_SEC, mvpwm->clk_rate);

regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset, &u);
if (u)
Expand All @@ -707,27 +710,35 @@ static int mvebu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,

val = (unsigned long long) mvpwm->clk_rate * state->duty_cycle;
do_div(val, NSEC_PER_SEC);
if (val > UINT_MAX)
if (val > UINT_MAX + 1ULL)
return -EINVAL;
if (val)
/*
* Zero on/off values don't work as expected. Experimentation shows
* that zero value is treated as 2^32. This behavior is not documented.
*/
if (val == UINT_MAX + 1ULL)
on = 0;
else if (val)
on = val;
else
on = 1;

val = (unsigned long long) mvpwm->clk_rate *
(state->period - state->duty_cycle);
val = (unsigned long long) mvpwm->clk_rate * state->period;
do_div(val, NSEC_PER_SEC);
if (val > UINT_MAX)
val -= on;
if (val > UINT_MAX + 1ULL)
return -EINVAL;
if (val)
if (val == UINT_MAX + 1ULL)
off = 0;
else if (val)
off = val;
else
off = 1;

spin_lock_irqsave(&mvpwm->lock, flags);

writel_relaxed(on, mvebu_pwmreg_blink_on_duration(mvpwm));
writel_relaxed(off, mvebu_pwmreg_blink_off_duration(mvpwm));
regmap_write(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm), on);
regmap_write(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm), off);
if (state->enabled)
mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 1);
else
Expand All @@ -752,10 +763,10 @@ static void __maybe_unused mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)

regmap_read(mvchip->regs, GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset,
&mvpwm->blink_select);
mvpwm->blink_on_duration =
readl_relaxed(mvebu_pwmreg_blink_on_duration(mvpwm));
mvpwm->blink_off_duration =
readl_relaxed(mvebu_pwmreg_blink_off_duration(mvpwm));
regmap_read(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm),
&mvpwm->blink_on_duration);
regmap_read(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm),
&mvpwm->blink_off_duration);
}

static void __maybe_unused mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
Expand All @@ -764,10 +775,10 @@ static void __maybe_unused mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)

regmap_write(mvchip->regs, GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset,
mvpwm->blink_select);
writel_relaxed(mvpwm->blink_on_duration,
mvebu_pwmreg_blink_on_duration(mvpwm));
writel_relaxed(mvpwm->blink_off_duration,
mvebu_pwmreg_blink_off_duration(mvpwm));
regmap_write(mvpwm->regs, mvebu_pwmreg_blink_on_duration(mvpwm),
mvpwm->blink_on_duration);
regmap_write(mvpwm->regs, mvebu_pwmreg_blink_off_duration(mvpwm),
mvpwm->blink_off_duration);
}

static int mvebu_pwm_probe(struct platform_device *pdev,
Expand All @@ -776,46 +787,81 @@ static int mvebu_pwm_probe(struct platform_device *pdev,
{
struct device *dev = &pdev->dev;
struct mvebu_pwm *mvpwm;
void __iomem *base;
u32 offset;
u32 set;

if (!of_device_is_compatible(mvchip->chip.of_node,
"marvell,armada-370-gpio"))
return 0;

/*
* There are only two sets of PWM configuration registers for
* all the GPIO lines on those SoCs which this driver reserves
* for the first two GPIO chips. So if the resource is missing
* we can't treat it as an error.
*/
if (!platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm"))
if (of_device_is_compatible(mvchip->chip.of_node,
"marvell,armada-370-gpio")) {
/*
* There are only two sets of PWM configuration registers for
* all the GPIO lines on those SoCs which this driver reserves
* for the first two GPIO chips. So if the resource is missing
* we can't treat it as an error.
*/
if (!platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm"))
return 0;
offset = 0;
} else if (mvchip->soc_variant == MVEBU_GPIO_SOC_VARIANT_A8K) {
int ret = of_property_read_u32(dev->of_node,
"marvell,pwm-offset", &offset);
if (ret < 0)
return 0;
} else {
return 0;
}

if (IS_ERR(mvchip->clk))
return PTR_ERR(mvchip->clk);

/*
* Use set A for lines of GPIO chip with id 0, B for GPIO chip
* with id 1. Don't allow further GPIO chips to be used for PWM.
*/
if (id == 0)
set = 0;
else if (id == 1)
set = U32_MAX;
else
return -EINVAL;
regmap_write(mvchip->regs,
GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset, set);

mvpwm = devm_kzalloc(dev, sizeof(struct mvebu_pwm), GFP_KERNEL);
if (!mvpwm)
return -ENOMEM;
mvchip->mvpwm = mvpwm;
mvpwm->mvchip = mvchip;
mvpwm->offset = offset;

if (mvchip->soc_variant == MVEBU_GPIO_SOC_VARIANT_A8K) {
mvpwm->regs = mvchip->regs;

switch (mvchip->offset) {
case AP80X_GPIO0_OFF_A8K:
case CP11X_GPIO0_OFF_A8K:
/* Blink counter A */
set = 0;
break;
case CP11X_GPIO1_OFF_A8K:
/* Blink counter B */
set = U32_MAX;
mvpwm->offset += PWM_BLINK_COUNTER_B_OFF;
break;
default:
return -EINVAL;
}
} else {
base = devm_platform_ioremap_resource_byname(pdev, "pwm");
if (IS_ERR(base))
return PTR_ERR(base);

mvpwm->membase = devm_platform_ioremap_resource_byname(pdev, "pwm");
if (IS_ERR(mvpwm->membase))
return PTR_ERR(mvpwm->membase);
mvpwm->regs = devm_regmap_init_mmio(&pdev->dev, base,
&mvebu_gpio_regmap_config);
if (IS_ERR(mvpwm->regs))
return PTR_ERR(mvpwm->regs);

/*
* Use set A for lines of GPIO chip with id 0, B for GPIO chip
* with id 1. Don't allow further GPIO chips to be used for PWM.
*/
if (id == 0)
set = 0;
else if (id == 1)
set = U32_MAX;
else
return -EINVAL;
}

regmap_write(mvchip->regs,
GPIO_BLINK_CNT_SELECT_OFF + mvchip->offset, set);

mvpwm->clk_rate = clk_get_rate(mvchip->clk);
if (!mvpwm->clk_rate) {
Expand Down Expand Up @@ -1026,13 +1072,6 @@ static int mvebu_gpio_resume(struct platform_device *pdev)
return 0;
}

static const struct regmap_config mvebu_gpio_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
};

static int mvebu_gpio_probe_raw(struct platform_device *pdev,
struct mvebu_gpio_chip *mvchip)
{
Expand Down