mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-22 20:53:00 +07:00
c7d28eca1d
Core: - Export add/remove for lookup tables so that modules can export GPIO descriptor tables. - Handle GPIO sleep states: it is now possible to flag that a GPIO line may loose its state during suspend/resume of the system to save power. This is used in the Wolfson Micro Arizona driver. - ACPI-based GPIO was tightened up a lot around the edges. - Use bitmap_fill() to speed up a loop. New drivers: - Exar XRA1403 SPI-based GPIO. - MVEBU driver now supports Armada 7K and 8K. - LP87565 PMIC GPIO. - Renesas R-CAR R8A7743 (RZ/G1M). - The new IOT2040 8250 serial/GPIO also comes in through this changeset. Substantial driver changes: - Seriously fix the Exar 8250 GPIO portions to work. - The MCP23S08 was moved out to a pin control driver. - Convert MEVEBU to use regmap for register access. - Drop Vulcan support from the Broadcom driver. - Serious cleanup and improvement of the mockup driver, giving us a better test coverage. Misc: - Lots of janitorial clean up. - A bunch of documentation fixes. -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJZX1MjAAoJEEEQszewGV1zEYUQALFsjJH7D2mRN4TSSEeVAcYr Uz52uupsou8tgW0IupRb/khO+V6zgd7j+kHDJLMxX+rCTw3pTq5+XGyi5+iNpxof TIIT1XBx4eq7Q/n4nWdGodHbHN9BXw7cGsNmTb1TS/G/6h1wOKxfzjvUNhDAC+2v idPy6B5G+WrDsYpBtTWlKHKQKVqbUlhLFyJYoglzqIeM5L9Ry/UoZ6sGleho3hKn Vlg/hMtkCexnVO9zopBe5CuEfseLrkcCgCvtQ713egzVXApryp4hqm3Xti20Ntgy OxnKhmVyloqd0kU0qLSpvDAf7B1invbHHbeZsag6wluTMrxgUYJONuonrqGeGiwB FBDtw9SGn2GlEXcs7sg8ANmAyr2XxxezKXD9XLBL5jadNB2KCY5yKMv1IK3VnYdq gEpFAiZ5cmlpZxIXqlyeZP6LKHNTci4amb33x1I/ghH2BTkGQ/3E3anXEbPNWF8G DDE6nrSgU0oQcNqRHyZaWNZpUIz4aFUgJtOEO4lYYP4+VzYSKTdrHseTiiJ91J7E WBz9p5JvSnB22+60RhyTAPjVjXgWa30nidf7WGCK0UHiIYffihCxGZRTlrhoEEUB fXgveJpqxLopYvxpUxi1OqlPYYo7zKRF5BzHsjKMpdVYXfdMdvs7eq2g/X889i1D WpbE9LyAH9FY5BM8YjFX =TpW1 -----END PGP SIGNATURE----- Merge tag 'gpio-v4.13-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio Pull GPIO updates from Linus Walleij: "This is the bulk of GPIO changes for the v4.13 series. Some administrativa: I have a slew of 8250 serial patches and the new IOT2040 serial+GPIO driver coming in through this tree, along with a whole bunch of Exar 8250 fixes. These are ACKed by Greg and also hit drivers/platform/* where they are ACKed by Andy Shevchenko. Speaking about drivers/platform/* there is also a bunch of ACPI stuff coming through that route, again ACKed by Andy. The MCP23S08 changes are coming in here as well. You already have the commits in your tree, so this is just a result of sharing an immutable branch between pin control and GPIO. Core: - Export add/remove for lookup tables so that modules can export GPIO descriptor tables. - Handle GPIO sleep states: it is now possible to flag that a GPIO line may loose its state during suspend/resume of the system to save power. This is used in the Wolfson Micro Arizona driver. - ACPI-based GPIO was tightened up a lot around the edges. - Use bitmap_fill() to speed up a loop. New drivers: - Exar XRA1403 SPI-based GPIO. - MVEBU driver now supports Armada 7K and 8K. - LP87565 PMIC GPIO. - Renesas R-CAR R8A7743 (RZ/G1M). - The new IOT2040 8250 serial/GPIO also comes in through this changeset. Substantial driver changes: - Seriously fix the Exar 8250 GPIO portions to work. - The MCP23S08 was moved out to a pin control driver. - Convert MEVEBU to use regmap for register access. - Drop Vulcan support from the Broadcom driver. - Serious cleanup and improvement of the mockup driver, giving us a better test coverage. Misc: - Lots of janitorial clean up. - A bunch of documentation fixes" * tag 'gpio-v4.13-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (70 commits) serial: exar: Add support for IOT2040 device gpio-exar/8250-exar: Make set of exported GPIOs configurable platform: Accept const properties serial: exar: Factor out platform hooks gpio-exar/8250-exar: Rearrange gpiochip parenthood gpio: exar: Fix iomap request gpio-exar/8250-exar: Do not even instantiate a GPIO device for Commtech cards serial: uapi: Add support for bus termination gpio: rcar: Add R8A7743 (RZ/G1M) support gpio: gpio-wcove: Fix GPIO control register offset calculation gpio: lp87565: Add support for GPIO gpio: dwapb: fix missing first irq for edgeboth irq type MAINTAINERS: Take maintainership for GPIO ACPI support gpio: exar: Fix reading of directions and values gpio: exar: Allocate resources on behalf of the platform device gpio-exar/8250-exar: Fix passing in of parent PCI device gpio: mockup: use devm_kcalloc() where applicable gpio: mockup: add myself as author gpio: mockup: improve the error message gpio: mockup: don't return magic numbers from probe() ...
1277 lines
33 KiB
C
1277 lines
33 KiB
C
/*
|
|
* GPIO driver for Marvell SoCs
|
|
*
|
|
* Copyright (C) 2012 Marvell
|
|
*
|
|
* Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
|
|
* Andrew Lunn <andrew@lunn.ch>
|
|
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*
|
|
* This driver is a fairly straightforward GPIO driver for the
|
|
* complete family of Marvell EBU SoC platforms (Orion, Dove,
|
|
* Kirkwood, Discovery, Armada 370/XP). The only complexity of this
|
|
* driver is the different register layout that exists between the
|
|
* non-SMP platforms (Orion, Dove, Kirkwood, Armada 370) and the SMP
|
|
* platforms (MV78200 from the Discovery family and the Armada
|
|
* XP). Therefore, this driver handles three variants of the GPIO
|
|
* block:
|
|
* - the basic variant, called "orion-gpio", with the simplest
|
|
* register set. Used on Orion, Dove, Kirkwoord, Armada 370 and
|
|
* non-SMP Discovery systems
|
|
* - the mv78200 variant for MV78200 Discovery systems. This variant
|
|
* turns the edge mask and level mask registers into CPU0 edge
|
|
* mask/level mask registers, and adds CPU1 edge mask/level mask
|
|
* registers.
|
|
* - the armadaxp variant for Armada XP systems. This variant keeps
|
|
* the normal cause/edge mask/level mask registers when the global
|
|
* interrupts are used, but adds per-CPU cause/edge mask/level mask
|
|
* registers n a separate memory area for the per-CPU GPIO
|
|
* interrupts.
|
|
*/
|
|
|
|
#include <linux/bitops.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqchip/chained_irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pwm.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "gpiolib.h"
|
|
|
|
/*
|
|
* GPIO unit register offsets.
|
|
*/
|
|
#define GPIO_OUT_OFF 0x0000
|
|
#define GPIO_IO_CONF_OFF 0x0004
|
|
#define GPIO_BLINK_EN_OFF 0x0008
|
|
#define GPIO_IN_POL_OFF 0x000c
|
|
#define GPIO_DATA_IN_OFF 0x0010
|
|
#define GPIO_EDGE_CAUSE_OFF 0x0014
|
|
#define GPIO_EDGE_MASK_OFF 0x0018
|
|
#define GPIO_LEVEL_MASK_OFF 0x001c
|
|
#define GPIO_BLINK_CNT_SELECT_OFF 0x0020
|
|
|
|
/*
|
|
* PWM register offsets.
|
|
*/
|
|
#define PWM_BLINK_ON_DURATION_OFF 0x0
|
|
#define PWM_BLINK_OFF_DURATION_OFF 0x4
|
|
|
|
|
|
/* The MV78200 has per-CPU registers for edge mask and level mask */
|
|
#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18)
|
|
#define GPIO_LEVEL_MASK_MV78200_OFF(cpu) ((cpu) ? 0x34 : 0x1C)
|
|
|
|
/*
|
|
* The Armada XP has per-CPU registers for interrupt cause, interrupt
|
|
* mask and interrupt level mask. Those are relative to the
|
|
* percpu_membase.
|
|
*/
|
|
#define GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu) ((cpu) * 0x4)
|
|
#define GPIO_EDGE_MASK_ARMADAXP_OFF(cpu) (0x10 + (cpu) * 0x4)
|
|
#define GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu) (0x20 + (cpu) * 0x4)
|
|
|
|
#define MVEBU_GPIO_SOC_VARIANT_ORION 0x1
|
|
#define MVEBU_GPIO_SOC_VARIANT_MV78200 0x2
|
|
#define MVEBU_GPIO_SOC_VARIANT_ARMADAXP 0x3
|
|
#define MVEBU_GPIO_SOC_VARIANT_A8K 0x4
|
|
|
|
#define MVEBU_MAX_GPIO_PER_BANK 32
|
|
|
|
struct mvebu_pwm {
|
|
void __iomem *membase;
|
|
unsigned long clk_rate;
|
|
struct gpio_desc *gpiod;
|
|
struct pwm_chip chip;
|
|
spinlock_t lock;
|
|
struct mvebu_gpio_chip *mvchip;
|
|
|
|
/* Used to preserve GPIO/PWM registers across suspend/resume */
|
|
u32 blink_select;
|
|
u32 blink_on_duration;
|
|
u32 blink_off_duration;
|
|
};
|
|
|
|
struct mvebu_gpio_chip {
|
|
struct gpio_chip chip;
|
|
struct regmap *regs;
|
|
u32 offset;
|
|
struct regmap *percpu_regs;
|
|
int irqbase;
|
|
struct irq_domain *domain;
|
|
int soc_variant;
|
|
|
|
/* Used for PWM support */
|
|
struct clk *clk;
|
|
struct mvebu_pwm *mvpwm;
|
|
|
|
/* Used to preserve GPIO registers across suspend/resume */
|
|
u32 out_reg;
|
|
u32 io_conf_reg;
|
|
u32 blink_en_reg;
|
|
u32 in_pol_reg;
|
|
u32 edge_mask_regs[4];
|
|
u32 level_mask_regs[4];
|
|
};
|
|
|
|
/*
|
|
* Functions returning addresses of individual registers for a given
|
|
* GPIO controller.
|
|
*/
|
|
|
|
static void mvebu_gpioreg_edge_cause(struct mvebu_gpio_chip *mvchip,
|
|
struct regmap **map, unsigned int *offset)
|
|
{
|
|
int cpu;
|
|
|
|
switch (mvchip->soc_variant) {
|
|
case MVEBU_GPIO_SOC_VARIANT_ORION:
|
|
case MVEBU_GPIO_SOC_VARIANT_MV78200:
|
|
case MVEBU_GPIO_SOC_VARIANT_A8K:
|
|
*map = mvchip->regs;
|
|
*offset = GPIO_EDGE_CAUSE_OFF + mvchip->offset;
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
|
|
cpu = smp_processor_id();
|
|
*map = mvchip->percpu_regs;
|
|
*offset = GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu);
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
static u32
|
|
mvebu_gpio_read_edge_cause(struct mvebu_gpio_chip *mvchip)
|
|
{
|
|
struct regmap *map;
|
|
unsigned int offset;
|
|
u32 val;
|
|
|
|
mvebu_gpioreg_edge_cause(mvchip, &map, &offset);
|
|
regmap_read(map, offset, &val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
mvebu_gpio_write_edge_cause(struct mvebu_gpio_chip *mvchip, u32 val)
|
|
{
|
|
struct regmap *map;
|
|
unsigned int offset;
|
|
|
|
mvebu_gpioreg_edge_cause(mvchip, &map, &offset);
|
|
regmap_write(map, offset, val);
|
|
}
|
|
|
|
static inline void
|
|
mvebu_gpioreg_edge_mask(struct mvebu_gpio_chip *mvchip,
|
|
struct regmap **map, unsigned int *offset)
|
|
{
|
|
int cpu;
|
|
|
|
switch (mvchip->soc_variant) {
|
|
case MVEBU_GPIO_SOC_VARIANT_ORION:
|
|
case MVEBU_GPIO_SOC_VARIANT_A8K:
|
|
*map = mvchip->regs;
|
|
*offset = GPIO_EDGE_MASK_OFF + mvchip->offset;
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_MV78200:
|
|
cpu = smp_processor_id();
|
|
*map = mvchip->regs;
|
|
*offset = GPIO_EDGE_MASK_MV78200_OFF(cpu);
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
|
|
cpu = smp_processor_id();
|
|
*map = mvchip->percpu_regs;
|
|
*offset = GPIO_EDGE_MASK_ARMADAXP_OFF(cpu);
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
static u32
|
|
mvebu_gpio_read_edge_mask(struct mvebu_gpio_chip *mvchip)
|
|
{
|
|
struct regmap *map;
|
|
unsigned int offset;
|
|
u32 val;
|
|
|
|
mvebu_gpioreg_edge_mask(mvchip, &map, &offset);
|
|
regmap_read(map, offset, &val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
mvebu_gpio_write_edge_mask(struct mvebu_gpio_chip *mvchip, u32 val)
|
|
{
|
|
struct regmap *map;
|
|
unsigned int offset;
|
|
|
|
mvebu_gpioreg_edge_mask(mvchip, &map, &offset);
|
|
regmap_write(map, offset, val);
|
|
}
|
|
|
|
static void
|
|
mvebu_gpioreg_level_mask(struct mvebu_gpio_chip *mvchip,
|
|
struct regmap **map, unsigned int *offset)
|
|
{
|
|
int cpu;
|
|
|
|
switch (mvchip->soc_variant) {
|
|
case MVEBU_GPIO_SOC_VARIANT_ORION:
|
|
case MVEBU_GPIO_SOC_VARIANT_A8K:
|
|
*map = mvchip->regs;
|
|
*offset = GPIO_LEVEL_MASK_OFF + mvchip->offset;
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_MV78200:
|
|
cpu = smp_processor_id();
|
|
*map = mvchip->regs;
|
|
*offset = GPIO_LEVEL_MASK_MV78200_OFF(cpu);
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
|
|
cpu = smp_processor_id();
|
|
*map = mvchip->percpu_regs;
|
|
*offset = GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu);
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
static u32
|
|
mvebu_gpio_read_level_mask(struct mvebu_gpio_chip *mvchip)
|
|
{
|
|
struct regmap *map;
|
|
unsigned int offset;
|
|
u32 val;
|
|
|
|
mvebu_gpioreg_level_mask(mvchip, &map, &offset);
|
|
regmap_read(map, offset, &val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
mvebu_gpio_write_level_mask(struct mvebu_gpio_chip *mvchip, u32 val)
|
|
{
|
|
struct regmap *map;
|
|
unsigned int offset;
|
|
|
|
mvebu_gpioreg_level_mask(mvchip, &map, &offset);
|
|
regmap_write(map, offset, val);
|
|
}
|
|
|
|
/*
|
|
* Functions returning addresses of individual registers for a given
|
|
* PWM controller.
|
|
*/
|
|
static void __iomem *mvebu_pwmreg_blink_on_duration(struct mvebu_pwm *mvpwm)
|
|
{
|
|
return mvpwm->membase + PWM_BLINK_ON_DURATION_OFF;
|
|
}
|
|
|
|
static void __iomem *mvebu_pwmreg_blink_off_duration(struct mvebu_pwm *mvpwm)
|
|
{
|
|
return mvpwm->membase + PWM_BLINK_OFF_DURATION_OFF;
|
|
}
|
|
|
|
/*
|
|
* Functions implementing the gpio_chip methods
|
|
*/
|
|
static void mvebu_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
|
|
|
|
regmap_update_bits(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
|
|
BIT(pin), value ? BIT(pin) : 0);
|
|
}
|
|
|
|
static int mvebu_gpio_get(struct gpio_chip *chip, unsigned int pin)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
|
|
u32 u;
|
|
|
|
regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset, &u);
|
|
|
|
if (u & BIT(pin)) {
|
|
u32 data_in, in_pol;
|
|
|
|
regmap_read(mvchip->regs, GPIO_DATA_IN_OFF + mvchip->offset,
|
|
&data_in);
|
|
regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
|
|
&in_pol);
|
|
u = data_in ^ in_pol;
|
|
} else {
|
|
regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset, &u);
|
|
}
|
|
|
|
return (u >> pin) & 1;
|
|
}
|
|
|
|
static void mvebu_gpio_blink(struct gpio_chip *chip, unsigned int pin,
|
|
int value)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
|
|
|
|
regmap_update_bits(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
|
|
BIT(pin), value ? BIT(pin) : 0);
|
|
}
|
|
|
|
static int mvebu_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
|
|
int ret;
|
|
|
|
/*
|
|
* Check with the pinctrl driver whether this pin is usable as
|
|
* an input GPIO
|
|
*/
|
|
ret = pinctrl_gpio_direction_input(chip->base + pin);
|
|
if (ret)
|
|
return ret;
|
|
|
|
regmap_update_bits(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
|
|
BIT(pin), BIT(pin));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mvebu_gpio_direction_output(struct gpio_chip *chip, unsigned int pin,
|
|
int value)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
|
|
int ret;
|
|
|
|
/*
|
|
* Check with the pinctrl driver whether this pin is usable as
|
|
* an output GPIO
|
|
*/
|
|
ret = pinctrl_gpio_direction_output(chip->base + pin);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mvebu_gpio_blink(chip, pin, 0);
|
|
mvebu_gpio_set(chip, pin, value);
|
|
|
|
regmap_update_bits(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
|
|
BIT(pin), 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mvebu_gpio_to_irq(struct gpio_chip *chip, unsigned int pin)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
|
|
|
|
return irq_create_mapping(mvchip->domain, pin);
|
|
}
|
|
|
|
/*
|
|
* Functions implementing the irq_chip methods
|
|
*/
|
|
static void mvebu_gpio_irq_ack(struct irq_data *d)
|
|
{
|
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
|
struct mvebu_gpio_chip *mvchip = gc->private;
|
|
u32 mask = d->mask;
|
|
|
|
irq_gc_lock(gc);
|
|
mvebu_gpio_write_edge_cause(mvchip, ~mask);
|
|
irq_gc_unlock(gc);
|
|
}
|
|
|
|
static void mvebu_gpio_edge_irq_mask(struct irq_data *d)
|
|
{
|
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
|
struct mvebu_gpio_chip *mvchip = gc->private;
|
|
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
|
u32 mask = d->mask;
|
|
|
|
irq_gc_lock(gc);
|
|
ct->mask_cache_priv &= ~mask;
|
|
mvebu_gpio_write_edge_mask(mvchip, ct->mask_cache_priv);
|
|
irq_gc_unlock(gc);
|
|
}
|
|
|
|
static void mvebu_gpio_edge_irq_unmask(struct irq_data *d)
|
|
{
|
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
|
struct mvebu_gpio_chip *mvchip = gc->private;
|
|
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
|
u32 mask = d->mask;
|
|
|
|
irq_gc_lock(gc);
|
|
ct->mask_cache_priv |= mask;
|
|
mvebu_gpio_write_edge_mask(mvchip, ct->mask_cache_priv);
|
|
irq_gc_unlock(gc);
|
|
}
|
|
|
|
static void mvebu_gpio_level_irq_mask(struct irq_data *d)
|
|
{
|
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
|
struct mvebu_gpio_chip *mvchip = gc->private;
|
|
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
|
u32 mask = d->mask;
|
|
|
|
irq_gc_lock(gc);
|
|
ct->mask_cache_priv &= ~mask;
|
|
mvebu_gpio_write_level_mask(mvchip, ct->mask_cache_priv);
|
|
irq_gc_unlock(gc);
|
|
}
|
|
|
|
static void mvebu_gpio_level_irq_unmask(struct irq_data *d)
|
|
{
|
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
|
struct mvebu_gpio_chip *mvchip = gc->private;
|
|
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
|
u32 mask = d->mask;
|
|
|
|
irq_gc_lock(gc);
|
|
ct->mask_cache_priv |= mask;
|
|
mvebu_gpio_write_level_mask(mvchip, ct->mask_cache_priv);
|
|
irq_gc_unlock(gc);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* MVEBU GPIO IRQ
|
|
*
|
|
* GPIO_IN_POL register controls whether GPIO_DATA_IN will hold the same
|
|
* value of the line or the opposite value.
|
|
*
|
|
* Level IRQ handlers: DATA_IN is used directly as cause register.
|
|
* Interrupt are masked by LEVEL_MASK registers.
|
|
* Edge IRQ handlers: Change in DATA_IN are latched in EDGE_CAUSE.
|
|
* Interrupt are masked by EDGE_MASK registers.
|
|
* Both-edge handlers: Similar to regular Edge handlers, but also swaps
|
|
* the polarity to catch the next line transaction.
|
|
* This is a race condition that might not perfectly
|
|
* work on some use cases.
|
|
*
|
|
* Every eight GPIO lines are grouped (OR'ed) before going up to main
|
|
* cause register.
|
|
*
|
|
* EDGE cause mask
|
|
* data-in /--------| |-----| |----\
|
|
* -----| |----- ---- to main cause reg
|
|
* X \----------------| |----/
|
|
* polarity LEVEL mask
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int mvebu_gpio_irq_set_type(struct irq_data *d, unsigned int type)
|
|
{
|
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
|
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
|
struct mvebu_gpio_chip *mvchip = gc->private;
|
|
int pin;
|
|
u32 u;
|
|
|
|
pin = d->hwirq;
|
|
|
|
regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset, &u);
|
|
if ((u & BIT(pin)) == 0)
|
|
return -EINVAL;
|
|
|
|
type &= IRQ_TYPE_SENSE_MASK;
|
|
if (type == IRQ_TYPE_NONE)
|
|
return -EINVAL;
|
|
|
|
/* Check if we need to change chip and handler */
|
|
if (!(ct->type & type))
|
|
if (irq_setup_alt_chip(d, type))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Configure interrupt polarity.
|
|
*/
|
|
switch (type) {
|
|
case IRQ_TYPE_EDGE_RISING:
|
|
case IRQ_TYPE_LEVEL_HIGH:
|
|
regmap_update_bits(mvchip->regs,
|
|
GPIO_IN_POL_OFF + mvchip->offset,
|
|
BIT(pin), 0);
|
|
break;
|
|
case IRQ_TYPE_EDGE_FALLING:
|
|
case IRQ_TYPE_LEVEL_LOW:
|
|
regmap_update_bits(mvchip->regs,
|
|
GPIO_IN_POL_OFF + mvchip->offset,
|
|
BIT(pin), BIT(pin));
|
|
break;
|
|
case IRQ_TYPE_EDGE_BOTH: {
|
|
u32 data_in, in_pol, val;
|
|
|
|
regmap_read(mvchip->regs,
|
|
GPIO_IN_POL_OFF + mvchip->offset, &in_pol);
|
|
regmap_read(mvchip->regs,
|
|
GPIO_DATA_IN_OFF + mvchip->offset, &data_in);
|
|
|
|
/*
|
|
* set initial polarity based on current input level
|
|
*/
|
|
if ((data_in ^ in_pol) & BIT(pin))
|
|
val = BIT(pin); /* falling */
|
|
else
|
|
val = 0; /* raising */
|
|
|
|
regmap_update_bits(mvchip->regs,
|
|
GPIO_IN_POL_OFF + mvchip->offset,
|
|
BIT(pin), val);
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mvebu_gpio_irq_handler(struct irq_desc *desc)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = irq_desc_get_handler_data(desc);
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
u32 cause, type, data_in, level_mask, edge_cause, edge_mask;
|
|
int i;
|
|
|
|
if (mvchip == NULL)
|
|
return;
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
regmap_read(mvchip->regs, GPIO_DATA_IN_OFF + mvchip->offset, &data_in);
|
|
level_mask = mvebu_gpio_read_level_mask(mvchip);
|
|
edge_cause = mvebu_gpio_read_edge_cause(mvchip);
|
|
edge_mask = mvebu_gpio_read_edge_mask(mvchip);
|
|
|
|
cause = (data_in ^ level_mask) | (edge_cause & edge_mask);
|
|
|
|
for (i = 0; i < mvchip->chip.ngpio; i++) {
|
|
int irq;
|
|
|
|
irq = irq_find_mapping(mvchip->domain, i);
|
|
|
|
if (!(cause & BIT(i)))
|
|
continue;
|
|
|
|
type = irq_get_trigger_type(irq);
|
|
if ((type & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) {
|
|
/* Swap polarity (race with GPIO line) */
|
|
u32 polarity;
|
|
|
|
regmap_read(mvchip->regs,
|
|
GPIO_IN_POL_OFF + mvchip->offset,
|
|
&polarity);
|
|
polarity ^= BIT(i);
|
|
regmap_write(mvchip->regs,
|
|
GPIO_IN_POL_OFF + mvchip->offset,
|
|
polarity);
|
|
}
|
|
|
|
generic_handle_irq(irq);
|
|
}
|
|
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
/*
|
|
* Functions implementing the pwm_chip methods
|
|
*/
|
|
static struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip)
|
|
{
|
|
return container_of(chip, struct mvebu_pwm, chip);
|
|
}
|
|
|
|
static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
{
|
|
struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
|
|
struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
|
|
struct gpio_desc *desc;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&mvpwm->lock, flags);
|
|
|
|
if (mvpwm->gpiod) {
|
|
ret = -EBUSY;
|
|
} else {
|
|
desc = gpio_to_desc(mvchip->chip.base + pwm->hwpwm);
|
|
if (!desc) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
ret = gpiod_request(desc, "mvebu-pwm");
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = gpiod_direction_output(desc, 0);
|
|
if (ret) {
|
|
gpiod_free(desc);
|
|
goto out;
|
|
}
|
|
|
|
mvpwm->gpiod = desc;
|
|
}
|
|
out:
|
|
spin_unlock_irqrestore(&mvpwm->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
{
|
|
struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mvpwm->lock, flags);
|
|
gpiod_free(mvpwm->gpiod);
|
|
mvpwm->gpiod = NULL;
|
|
spin_unlock_irqrestore(&mvpwm->lock, flags);
|
|
}
|
|
|
|
static void mvebu_pwm_get_state(struct pwm_chip *chip,
|
|
struct pwm_device *pwm,
|
|
struct pwm_state *state) {
|
|
|
|
struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
|
|
struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
|
|
unsigned long long val;
|
|
unsigned long flags;
|
|
u32 u;
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset, &u);
|
|
if (u)
|
|
state->enabled = true;
|
|
else
|
|
state->enabled = false;
|
|
|
|
spin_unlock_irqrestore(&mvpwm->lock, flags);
|
|
}
|
|
|
|
static int mvebu_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
struct pwm_state *state)
|
|
{
|
|
struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip);
|
|
struct mvebu_gpio_chip *mvchip = mvpwm->mvchip;
|
|
unsigned long long val;
|
|
unsigned long flags;
|
|
unsigned int on, off;
|
|
|
|
val = (unsigned long long) mvpwm->clk_rate * state->duty_cycle;
|
|
do_div(val, NSEC_PER_SEC);
|
|
if (val > UINT_MAX)
|
|
return -EINVAL;
|
|
if (val)
|
|
on = val;
|
|
else
|
|
on = 1;
|
|
|
|
val = (unsigned long long) mvpwm->clk_rate *
|
|
(state->period - state->duty_cycle);
|
|
do_div(val, NSEC_PER_SEC);
|
|
if (val > UINT_MAX)
|
|
return -EINVAL;
|
|
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));
|
|
if (state->enabled)
|
|
mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 1);
|
|
else
|
|
mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 0);
|
|
|
|
spin_unlock_irqrestore(&mvpwm->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pwm_ops mvebu_pwm_ops = {
|
|
.request = mvebu_pwm_request,
|
|
.free = mvebu_pwm_free,
|
|
.get_state = mvebu_pwm_get_state,
|
|
.apply = mvebu_pwm_apply,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static void __maybe_unused mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
|
|
{
|
|
struct mvebu_pwm *mvpwm = mvchip->mvpwm;
|
|
|
|
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));
|
|
}
|
|
|
|
static void __maybe_unused mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
|
|
{
|
|
struct mvebu_pwm *mvpwm = mvchip->mvpwm;
|
|
|
|
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));
|
|
}
|
|
|
|
static int mvebu_pwm_probe(struct platform_device *pdev,
|
|
struct mvebu_gpio_chip *mvchip,
|
|
int id)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct mvebu_pwm *mvpwm;
|
|
struct resource *res;
|
|
u32 set;
|
|
|
|
if (!of_device_is_compatible(mvchip->chip.of_node,
|
|
"marvell,armada-370-gpio"))
|
|
return 0;
|
|
|
|
if (IS_ERR(mvchip->clk))
|
|
return PTR_ERR(mvchip->clk);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
|
|
if (!res)
|
|
return 0;
|
|
|
|
/*
|
|
* 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->membase = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(mvpwm->membase))
|
|
return PTR_ERR(mvpwm->membase);
|
|
|
|
mvpwm->clk_rate = clk_get_rate(mvchip->clk);
|
|
if (!mvpwm->clk_rate) {
|
|
dev_err(dev, "failed to get clock rate\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mvpwm->chip.dev = dev;
|
|
mvpwm->chip.ops = &mvebu_pwm_ops;
|
|
mvpwm->chip.npwm = mvchip->chip.ngpio;
|
|
/*
|
|
* There may already be some PWM allocated, so we can't force
|
|
* mvpwm->chip.base to a fixed point like mvchip->chip.base.
|
|
* So, we let pwmchip_add() do the numbering and take the next free
|
|
* region.
|
|
*/
|
|
mvpwm->chip.base = -1;
|
|
|
|
spin_lock_init(&mvpwm->lock);
|
|
|
|
return pwmchip_add(&mvpwm->chip);
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#include <linux/seq_file.h>
|
|
|
|
static void mvebu_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = gpiochip_get_data(chip);
|
|
u32 out, io_conf, blink, in_pol, data_in, cause, edg_msk, lvl_msk;
|
|
int i;
|
|
|
|
regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset, &out);
|
|
regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset, &io_conf);
|
|
regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset, &blink);
|
|
regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset, &in_pol);
|
|
regmap_read(mvchip->regs, GPIO_DATA_IN_OFF + mvchip->offset, &data_in);
|
|
cause = mvebu_gpio_read_edge_cause(mvchip);
|
|
edg_msk = mvebu_gpio_read_edge_mask(mvchip);
|
|
lvl_msk = mvebu_gpio_read_level_mask(mvchip);
|
|
|
|
for (i = 0; i < chip->ngpio; i++) {
|
|
const char *label;
|
|
u32 msk;
|
|
bool is_out;
|
|
|
|
label = gpiochip_is_requested(chip, i);
|
|
if (!label)
|
|
continue;
|
|
|
|
msk = BIT(i);
|
|
is_out = !(io_conf & msk);
|
|
|
|
seq_printf(s, " gpio-%-3d (%-20.20s)", chip->base + i, label);
|
|
|
|
if (is_out) {
|
|
seq_printf(s, " out %s %s\n",
|
|
out & msk ? "hi" : "lo",
|
|
blink & msk ? "(blink )" : "");
|
|
continue;
|
|
}
|
|
|
|
seq_printf(s, " in %s (act %s) - IRQ",
|
|
(data_in ^ in_pol) & msk ? "hi" : "lo",
|
|
in_pol & msk ? "lo" : "hi");
|
|
if (!((edg_msk | lvl_msk) & msk)) {
|
|
seq_puts(s, " disabled\n");
|
|
continue;
|
|
}
|
|
if (edg_msk & msk)
|
|
seq_puts(s, " edge ");
|
|
if (lvl_msk & msk)
|
|
seq_puts(s, " level");
|
|
seq_printf(s, " (%s)\n", cause & msk ? "pending" : "clear ");
|
|
}
|
|
}
|
|
#else
|
|
#define mvebu_gpio_dbg_show NULL
|
|
#endif
|
|
|
|
static const struct of_device_id mvebu_gpio_of_match[] = {
|
|
{
|
|
.compatible = "marvell,orion-gpio",
|
|
.data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
|
|
},
|
|
{
|
|
.compatible = "marvell,mv78200-gpio",
|
|
.data = (void *) MVEBU_GPIO_SOC_VARIANT_MV78200,
|
|
},
|
|
{
|
|
.compatible = "marvell,armadaxp-gpio",
|
|
.data = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP,
|
|
},
|
|
{
|
|
.compatible = "marvell,armada-370-gpio",
|
|
.data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
|
|
},
|
|
{
|
|
.compatible = "marvell,armada-8k-gpio",
|
|
.data = (void *) MVEBU_GPIO_SOC_VARIANT_A8K,
|
|
},
|
|
{
|
|
/* sentinel */
|
|
},
|
|
};
|
|
|
|
static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
|
|
int i;
|
|
|
|
regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
|
|
&mvchip->out_reg);
|
|
regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
|
|
&mvchip->io_conf_reg);
|
|
regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
|
|
&mvchip->blink_en_reg);
|
|
regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
|
|
&mvchip->in_pol_reg);
|
|
|
|
switch (mvchip->soc_variant) {
|
|
case MVEBU_GPIO_SOC_VARIANT_ORION:
|
|
case MVEBU_GPIO_SOC_VARIANT_A8K:
|
|
regmap_read(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
|
|
&mvchip->edge_mask_regs[0]);
|
|
regmap_read(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
|
|
&mvchip->level_mask_regs[0]);
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_MV78200:
|
|
for (i = 0; i < 2; i++) {
|
|
regmap_read(mvchip->regs,
|
|
GPIO_EDGE_MASK_MV78200_OFF(i),
|
|
&mvchip->edge_mask_regs[i]);
|
|
regmap_read(mvchip->regs,
|
|
GPIO_LEVEL_MASK_MV78200_OFF(i),
|
|
&mvchip->level_mask_regs[i]);
|
|
}
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
|
|
for (i = 0; i < 4; i++) {
|
|
regmap_read(mvchip->regs,
|
|
GPIO_EDGE_MASK_ARMADAXP_OFF(i),
|
|
&mvchip->edge_mask_regs[i]);
|
|
regmap_read(mvchip->regs,
|
|
GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
|
|
&mvchip->level_mask_regs[i]);
|
|
}
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PWM))
|
|
mvebu_pwm_suspend(mvchip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mvebu_gpio_resume(struct platform_device *pdev)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
|
|
int i;
|
|
|
|
regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
|
|
mvchip->out_reg);
|
|
regmap_write(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
|
|
mvchip->io_conf_reg);
|
|
regmap_write(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
|
|
mvchip->blink_en_reg);
|
|
regmap_write(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
|
|
mvchip->in_pol_reg);
|
|
|
|
switch (mvchip->soc_variant) {
|
|
case MVEBU_GPIO_SOC_VARIANT_ORION:
|
|
case MVEBU_GPIO_SOC_VARIANT_A8K:
|
|
regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
|
|
mvchip->edge_mask_regs[0]);
|
|
regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
|
|
mvchip->level_mask_regs[0]);
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_MV78200:
|
|
for (i = 0; i < 2; i++) {
|
|
regmap_write(mvchip->regs,
|
|
GPIO_EDGE_MASK_MV78200_OFF(i),
|
|
mvchip->edge_mask_regs[i]);
|
|
regmap_write(mvchip->regs,
|
|
GPIO_LEVEL_MASK_MV78200_OFF(i),
|
|
mvchip->level_mask_regs[i]);
|
|
}
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
|
|
for (i = 0; i < 4; i++) {
|
|
regmap_write(mvchip->regs,
|
|
GPIO_EDGE_MASK_ARMADAXP_OFF(i),
|
|
mvchip->edge_mask_regs[i]);
|
|
regmap_write(mvchip->regs,
|
|
GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
|
|
mvchip->level_mask_regs[i]);
|
|
}
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PWM))
|
|
mvebu_pwm_resume(mvchip);
|
|
|
|
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)
|
|
{
|
|
struct resource *res;
|
|
void __iomem *base;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
mvchip->regs = devm_regmap_init_mmio(&pdev->dev, base,
|
|
&mvebu_gpio_regmap_config);
|
|
if (IS_ERR(mvchip->regs))
|
|
return PTR_ERR(mvchip->regs);
|
|
|
|
/*
|
|
* For the legacy SoCs, the regmap directly maps to the GPIO
|
|
* registers, so no offset is needed.
|
|
*/
|
|
mvchip->offset = 0;
|
|
|
|
/*
|
|
* The Armada XP has a second range of registers for the
|
|
* per-CPU registers
|
|
*/
|
|
if (mvchip->soc_variant == MVEBU_GPIO_SOC_VARIANT_ARMADAXP) {
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
mvchip->percpu_regs =
|
|
devm_regmap_init_mmio(&pdev->dev, base,
|
|
&mvebu_gpio_regmap_config);
|
|
if (IS_ERR(mvchip->percpu_regs))
|
|
return PTR_ERR(mvchip->percpu_regs);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mvebu_gpio_probe_syscon(struct platform_device *pdev,
|
|
struct mvebu_gpio_chip *mvchip)
|
|
{
|
|
mvchip->regs = syscon_node_to_regmap(pdev->dev.parent->of_node);
|
|
if (IS_ERR(mvchip->regs))
|
|
return PTR_ERR(mvchip->regs);
|
|
|
|
if (of_property_read_u32(pdev->dev.of_node, "offset", &mvchip->offset))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mvebu_gpio_probe(struct platform_device *pdev)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip;
|
|
const struct of_device_id *match;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct irq_chip_generic *gc;
|
|
struct irq_chip_type *ct;
|
|
unsigned int ngpios;
|
|
bool have_irqs;
|
|
int soc_variant;
|
|
int i, cpu, id;
|
|
int err;
|
|
|
|
match = of_match_device(mvebu_gpio_of_match, &pdev->dev);
|
|
if (match)
|
|
soc_variant = (unsigned long) match->data;
|
|
else
|
|
soc_variant = MVEBU_GPIO_SOC_VARIANT_ORION;
|
|
|
|
/* Some gpio controllers do not provide irq support */
|
|
have_irqs = of_irq_count(np) != 0;
|
|
|
|
mvchip = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_gpio_chip),
|
|
GFP_KERNEL);
|
|
if (!mvchip)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, mvchip);
|
|
|
|
if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
|
|
dev_err(&pdev->dev, "Missing ngpios OF property\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
id = of_alias_get_id(pdev->dev.of_node, "gpio");
|
|
if (id < 0) {
|
|
dev_err(&pdev->dev, "Couldn't get OF id\n");
|
|
return id;
|
|
}
|
|
|
|
mvchip->clk = devm_clk_get(&pdev->dev, NULL);
|
|
/* Not all SoCs require a clock.*/
|
|
if (!IS_ERR(mvchip->clk))
|
|
clk_prepare_enable(mvchip->clk);
|
|
|
|
mvchip->soc_variant = soc_variant;
|
|
mvchip->chip.label = dev_name(&pdev->dev);
|
|
mvchip->chip.parent = &pdev->dev;
|
|
mvchip->chip.request = gpiochip_generic_request;
|
|
mvchip->chip.free = gpiochip_generic_free;
|
|
mvchip->chip.direction_input = mvebu_gpio_direction_input;
|
|
mvchip->chip.get = mvebu_gpio_get;
|
|
mvchip->chip.direction_output = mvebu_gpio_direction_output;
|
|
mvchip->chip.set = mvebu_gpio_set;
|
|
if (have_irqs)
|
|
mvchip->chip.to_irq = mvebu_gpio_to_irq;
|
|
mvchip->chip.base = id * MVEBU_MAX_GPIO_PER_BANK;
|
|
mvchip->chip.ngpio = ngpios;
|
|
mvchip->chip.can_sleep = false;
|
|
mvchip->chip.of_node = np;
|
|
mvchip->chip.dbg_show = mvebu_gpio_dbg_show;
|
|
|
|
if (soc_variant == MVEBU_GPIO_SOC_VARIANT_A8K)
|
|
err = mvebu_gpio_probe_syscon(pdev, mvchip);
|
|
else
|
|
err = mvebu_gpio_probe_raw(pdev, mvchip);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
/*
|
|
* Mask and clear GPIO interrupts.
|
|
*/
|
|
switch (soc_variant) {
|
|
case MVEBU_GPIO_SOC_VARIANT_ORION:
|
|
case MVEBU_GPIO_SOC_VARIANT_A8K:
|
|
regmap_write(mvchip->regs,
|
|
GPIO_EDGE_CAUSE_OFF + mvchip->offset, 0);
|
|
regmap_write(mvchip->regs,
|
|
GPIO_EDGE_MASK_OFF + mvchip->offset, 0);
|
|
regmap_write(mvchip->regs,
|
|
GPIO_LEVEL_MASK_OFF + mvchip->offset, 0);
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_MV78200:
|
|
regmap_write(mvchip->regs, GPIO_EDGE_CAUSE_OFF, 0);
|
|
for (cpu = 0; cpu < 2; cpu++) {
|
|
regmap_write(mvchip->regs,
|
|
GPIO_EDGE_MASK_MV78200_OFF(cpu), 0);
|
|
regmap_write(mvchip->regs,
|
|
GPIO_LEVEL_MASK_MV78200_OFF(cpu), 0);
|
|
}
|
|
break;
|
|
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
|
|
regmap_write(mvchip->regs, GPIO_EDGE_CAUSE_OFF, 0);
|
|
regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF, 0);
|
|
regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF, 0);
|
|
for (cpu = 0; cpu < 4; cpu++) {
|
|
regmap_write(mvchip->percpu_regs,
|
|
GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu), 0);
|
|
regmap_write(mvchip->percpu_regs,
|
|
GPIO_EDGE_MASK_ARMADAXP_OFF(cpu), 0);
|
|
regmap_write(mvchip->percpu_regs,
|
|
GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu), 0);
|
|
}
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
devm_gpiochip_add_data(&pdev->dev, &mvchip->chip, mvchip);
|
|
|
|
/* Some gpio controllers do not provide irq support */
|
|
if (!have_irqs)
|
|
return 0;
|
|
|
|
mvchip->domain =
|
|
irq_domain_add_linear(np, ngpios, &irq_generic_chip_ops, NULL);
|
|
if (!mvchip->domain) {
|
|
dev_err(&pdev->dev, "couldn't allocate irq domain %s (DT).\n",
|
|
mvchip->chip.label);
|
|
return -ENODEV;
|
|
}
|
|
|
|
err = irq_alloc_domain_generic_chips(
|
|
mvchip->domain, ngpios, 2, np->name, handle_level_irq,
|
|
IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_LEVEL, 0, 0);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "couldn't allocate irq chips %s (DT).\n",
|
|
mvchip->chip.label);
|
|
goto err_domain;
|
|
}
|
|
|
|
/*
|
|
* NOTE: The common accessors cannot be used because of the percpu
|
|
* access to the mask registers
|
|
*/
|
|
gc = irq_get_domain_generic_chip(mvchip->domain, 0);
|
|
gc->private = mvchip;
|
|
ct = &gc->chip_types[0];
|
|
ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW;
|
|
ct->chip.irq_mask = mvebu_gpio_level_irq_mask;
|
|
ct->chip.irq_unmask = mvebu_gpio_level_irq_unmask;
|
|
ct->chip.irq_set_type = mvebu_gpio_irq_set_type;
|
|
ct->chip.name = mvchip->chip.label;
|
|
|
|
ct = &gc->chip_types[1];
|
|
ct->type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
|
ct->chip.irq_ack = mvebu_gpio_irq_ack;
|
|
ct->chip.irq_mask = mvebu_gpio_edge_irq_mask;
|
|
ct->chip.irq_unmask = mvebu_gpio_edge_irq_unmask;
|
|
ct->chip.irq_set_type = mvebu_gpio_irq_set_type;
|
|
ct->handler = handle_edge_irq;
|
|
ct->chip.name = mvchip->chip.label;
|
|
|
|
/*
|
|
* Setup the interrupt handlers. Each chip can have up to 4
|
|
* interrupt handlers, with each handler dealing with 8 GPIO
|
|
* pins.
|
|
*/
|
|
for (i = 0; i < 4; i++) {
|
|
int irq = platform_get_irq(pdev, i);
|
|
|
|
if (irq < 0)
|
|
continue;
|
|
irq_set_chained_handler_and_data(irq, mvebu_gpio_irq_handler,
|
|
mvchip);
|
|
}
|
|
|
|
/* Some MVEBU SoCs have simple PWM support for GPIO lines */
|
|
if (IS_ENABLED(CONFIG_PWM))
|
|
return mvebu_pwm_probe(pdev, mvchip, id);
|
|
|
|
return 0;
|
|
|
|
err_domain:
|
|
irq_domain_remove(mvchip->domain);
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct platform_driver mvebu_gpio_driver = {
|
|
.driver = {
|
|
.name = "mvebu-gpio",
|
|
.of_match_table = mvebu_gpio_of_match,
|
|
},
|
|
.probe = mvebu_gpio_probe,
|
|
.suspend = mvebu_gpio_suspend,
|
|
.resume = mvebu_gpio_resume,
|
|
};
|
|
builtin_platform_driver(mvebu_gpio_driver);
|