diff options
Diffstat (limited to 'drivers/gpio/gpio-rcar.c')
| -rw-r--r-- | drivers/gpio/gpio-rcar.c | 169 | 
1 files changed, 169 insertions, 0 deletions
| diff --git a/drivers/gpio/gpio-rcar.c b/drivers/gpio/gpio-rcar.c new file mode 100644 index 00000000000..8504dceb849 --- /dev/null +++ b/drivers/gpio/gpio-rcar.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017 Marek Vasut <marek.vasut@gmail.com> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <errno.h> +#include <asm/gpio.h> +#include <asm/io.h> + +#define GPIO_IOINTSEL	0x00	/* General IO/Interrupt Switching Register */ +#define GPIO_INOUTSEL	0x04	/* General Input/Output Switching Register */ +#define GPIO_OUTDT	0x08	/* General Output Register */ +#define GPIO_INDT	0x0c	/* General Input Register */ +#define GPIO_INTDT	0x10	/* Interrupt Display Register */ +#define GPIO_INTCLR	0x14	/* Interrupt Clear Register */ +#define GPIO_INTMSK	0x18	/* Interrupt Mask Register */ +#define GPIO_MSKCLR	0x1c	/* Interrupt Mask Clear Register */ +#define GPIO_POSNEG	0x20	/* Positive/Negative Logic Select Register */ +#define GPIO_EDGLEVEL	0x24	/* Edge/level Select Register */ +#define GPIO_FILONOFF	0x28	/* Chattering Prevention On/Off Register */ +#define GPIO_BOTHEDGE	0x4c	/* One Edge/Both Edge Select Register */ + +#define RCAR_MAX_GPIO_PER_BANK		32 + +DECLARE_GLOBAL_DATA_PTR; + +struct rcar_gpio_priv { +	void __iomem *regs; +}; + +static int rcar_gpio_get_value(struct udevice *dev, unsigned offset) +{ +	struct rcar_gpio_priv *priv = dev_get_priv(dev); +	const u32 bit = BIT(offset); + +	/* +	 * Testing on r8a7790 shows that INDT does not show correct pin state +	 * when configured as output, so use OUTDT in case of output pins. +	 */ +	if (readl(priv->regs + GPIO_INOUTSEL) & bit) +		return !!(readl(priv->regs + GPIO_OUTDT) & bit); +	else +		return !!(readl(priv->regs + GPIO_INDT) & bit); +} + +static int rcar_gpio_set_value(struct udevice *dev, unsigned offset, +			       int value) +{ +	struct rcar_gpio_priv *priv = dev_get_priv(dev); + +	if (value) +		setbits_le32(priv->regs + GPIO_OUTDT, BIT(offset)); +	else +		clrbits_le32(priv->regs + GPIO_OUTDT, BIT(offset)); + +	return 0; +} + +static void rcar_gpio_set_direction(void __iomem *regs, unsigned offset, +				    bool output) +{ +	/* +	 * follow steps in the GPIO documentation for +	 * "Setting General Output Mode" and +	 * "Setting General Input Mode" +	 */ + +	/* Configure postive logic in POSNEG */ +	clrbits_le32(regs + GPIO_POSNEG, BIT(offset)); + +	/* Select "General Input/Output Mode" in IOINTSEL */ +	clrbits_le32(regs + GPIO_IOINTSEL, BIT(offset)); + +	/* Select Input Mode or Output Mode in INOUTSEL */ +	if (output) +		setbits_le32(regs + GPIO_INOUTSEL, BIT(offset)); +	else +		clrbits_le32(regs + GPIO_INOUTSEL, BIT(offset)); +} + +static int rcar_gpio_direction_input(struct udevice *dev, unsigned offset) +{ +	struct rcar_gpio_priv *priv = dev_get_priv(dev); + +	rcar_gpio_set_direction(priv->regs, offset, false); + +	return 0; +} + +static int rcar_gpio_direction_output(struct udevice *dev, unsigned offset, +				      int value) +{ +	struct rcar_gpio_priv *priv = dev_get_priv(dev); + +	/* write GPIO value to output before selecting output mode of pin */ +	rcar_gpio_set_value(dev, offset, value); +	rcar_gpio_set_direction(priv->regs, offset, true); + +	return 0; +} + +static int rcar_gpio_get_function(struct udevice *dev, unsigned offset) +{ +	struct rcar_gpio_priv *priv = dev_get_priv(dev); + +	if (readl(priv->regs + GPIO_INOUTSEL) & BIT(offset)) +		return GPIOF_OUTPUT; +	else +		return GPIOF_INPUT; +} + +static const struct dm_gpio_ops rcar_gpio_ops = { +	.direction_input	= rcar_gpio_direction_input, +	.direction_output	= rcar_gpio_direction_output, +	.get_value		= rcar_gpio_get_value, +	.set_value		= rcar_gpio_set_value, +	.get_function		= rcar_gpio_get_function, +}; + +static int rcar_gpio_probe(struct udevice *dev) +{ +	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); +	struct rcar_gpio_priv *priv = dev_get_priv(dev); +	struct fdtdec_phandle_args args; +	struct clk clk; +	int node = dev_of_offset(dev); +	int ret; + +	priv->regs = (void __iomem *)devfdt_get_addr(dev); +	uc_priv->bank_name = dev->name; + +	ret = fdtdec_parse_phandle_with_args(gd->fdt_blob, node, "gpio-ranges", +					     NULL, 3, 0, &args); +	uc_priv->gpio_count = ret == 0 ? args.args[2] : RCAR_MAX_GPIO_PER_BANK; + +	ret = clk_get_by_index(dev, 0, &clk); +	if (ret < 0) { +		dev_err(dev, "Failed to get GPIO bank clock\n"); +		return ret; +	} + +	ret = clk_enable(&clk); +	clk_free(&clk); +	if (ret) { +		dev_err(dev, "Failed to enable GPIO bank clock\n"); +		return ret; +	} + +	return 0; +} + +static const struct udevice_id rcar_gpio_ids[] = { +	{ .compatible = "renesas,gpio-r8a7795" }, +	{ .compatible = "renesas,gpio-r8a7796" }, +	{ /* sentinel */ } +}; + +U_BOOT_DRIVER(rcar_gpio) = { +	.name	= "rcar-gpio", +	.id	= UCLASS_GPIO, +	.of_match = rcar_gpio_ids, +	.ops	= &rcar_gpio_ops, +	.priv_auto_alloc_size = sizeof(struct rcar_gpio_priv), +	.probe	= rcar_gpio_probe, +}; | 
