blob: 3227a8d5b574eb074987870c6487b231fba0d980 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2016 Freescale Semiconductor, Inc.
*
* RGPIO2P driver for the Freescale i.MX7ULP.
*/
#include <common.h>
#include <dm.h>
#include <errno.h>
#include <fdtdec.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <dm/device-internal.h>
#include <malloc.h>
enum imx_rgpio2p_direction {
IMX_RGPIO2P_DIRECTION_IN,
IMX_RGPIO2P_DIRECTION_OUT,
};
#define GPIO_PER_BANK 32
struct imx_rgpio2p_soc_data {
bool have_dual_base;
};
#define IMX8ULP_GPIO_BASE_OFF 0x40
struct imx_rgpio2p_data {
struct gpio_regs *regs;
};
struct imx_rgpio2p_plat {
int bank_index;
struct gpio_regs *regs;
};
static int imx_rgpio2p_is_output(struct gpio_regs *regs, int offset)
{
u32 val;
val = readl(&regs->gpio_pddr);
return val & (1 << offset) ? 1 : 0;
}
static int imx_rgpio2p_bank_get_direction(struct gpio_regs *regs, int offset)
{
if ((readl(&regs->gpio_pddr) >> offset) & 0x01)
return IMX_RGPIO2P_DIRECTION_OUT;
return IMX_RGPIO2P_DIRECTION_IN;
}
static void imx_rgpio2p_bank_direction(struct gpio_regs *regs, int offset,
enum imx_rgpio2p_direction direction)
{
u32 l;
l = readl(&regs->gpio_pddr);
switch (direction) {
case IMX_RGPIO2P_DIRECTION_OUT:
l |= 1 << offset;
break;
case IMX_RGPIO2P_DIRECTION_IN:
l &= ~(1 << offset);
}
writel(l, &regs->gpio_pddr);
}
static void imx_rgpio2p_bank_set_value(struct gpio_regs *regs, int offset,
int value)
{
if (value)
writel((1 << offset), &regs->gpio_psor);
else
writel((1 << offset), &regs->gpio_pcor);
}
static int imx_rgpio2p_bank_get_value(struct gpio_regs *regs, int offset)
{
if (imx_rgpio2p_bank_get_direction(regs, offset) ==
IMX_RGPIO2P_DIRECTION_IN)
return (readl(&regs->gpio_pdir) >> offset) & 0x01;
return (readl(&regs->gpio_pdor) >> offset) & 0x01;
}
static int imx_rgpio2p_direction_input(struct udevice *dev, unsigned offset)
{
struct imx_rgpio2p_data *bank = dev_get_priv(dev);
/* Configure GPIO direction as input. */
imx_rgpio2p_bank_direction(bank->regs, offset, IMX_RGPIO2P_DIRECTION_IN);
return 0;
}
static int imx_rgpio2p_direction_output(struct udevice *dev, unsigned offset,
int value)
{
struct imx_rgpio2p_data *bank = dev_get_priv(dev);
/* Configure GPIO output value. */
imx_rgpio2p_bank_set_value(bank->regs, offset, value);
/* Configure GPIO direction as output. */
imx_rgpio2p_bank_direction(bank->regs, offset, IMX_RGPIO2P_DIRECTION_OUT);
return 0;
}
static int imx_rgpio2p_get_value(struct udevice *dev, unsigned offset)
{
struct imx_rgpio2p_data *bank = dev_get_priv(dev);
return imx_rgpio2p_bank_get_value(bank->regs, offset);
}
static int imx_rgpio2p_set_value(struct udevice *dev, unsigned offset,
int value)
{
struct imx_rgpio2p_data *bank = dev_get_priv(dev);
imx_rgpio2p_bank_set_value(bank->regs, offset, value);
return 0;
}
static int imx_rgpio2p_get_function(struct udevice *dev, unsigned offset)
{
struct imx_rgpio2p_data *bank = dev_get_priv(dev);
/* GPIOF_FUNC is not implemented yet */
if (imx_rgpio2p_is_output(bank->regs, offset))
return GPIOF_OUTPUT;
else
return GPIOF_INPUT;
}
static const struct dm_gpio_ops imx_rgpio2p_ops = {
.direction_input = imx_rgpio2p_direction_input,
.direction_output = imx_rgpio2p_direction_output,
.get_value = imx_rgpio2p_get_value,
.set_value = imx_rgpio2p_set_value,
.get_function = imx_rgpio2p_get_function,
};
static int imx_rgpio2p_probe(struct udevice *dev)
{
struct imx_rgpio2p_data *bank = dev_get_priv(dev);
struct imx_rgpio2p_plat *plat = dev_get_plat(dev);
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
int banknum;
char name[18], *str;
banknum = plat->bank_index;
sprintf(name, "GPIO%d_", banknum + 1);
str = strdup(name);
if (!str)
return -ENOMEM;
uc_priv->bank_name = str;
uc_priv->gpio_count = GPIO_PER_BANK;
bank->regs = plat->regs;
return 0;
}
static int imx_rgpio2p_bind(struct udevice *dev)
{
struct imx_rgpio2p_plat *plat = dev_get_plat(dev);
struct imx_rgpio2p_soc_data *data =
(struct imx_rgpio2p_soc_data *)dev_get_driver_data(dev);
bool dual_base = data->have_dual_base;
fdt_addr_t addr;
/*
* If plat already exsits, directly return.
* Actually only when DT is not supported, plat
* is statically initialized in U_BOOT_DRVINFOS.Here
* will return.
*/
if (plat)
return 0;
/*
* Handle legacy compatible combinations which used two reg values
* for the i.MX8ULP and i.MX93.
*/
if (device_is_compatible(dev, "fsl,imx7ulp-gpio") &&
(device_is_compatible(dev, "fsl,imx93-gpio") ||
(device_is_compatible(dev, "fsl,imx8ulp-gpio"))))
dual_base = true;
if (dual_base) {
addr = devfdt_get_addr_index(dev, 1);
if (addr == FDT_ADDR_T_NONE)
return -EINVAL;
} else {
addr = devfdt_get_addr_index(dev, 0);
if (addr == FDT_ADDR_T_NONE)
return -EINVAL;
addr += IMX8ULP_GPIO_BASE_OFF;
}
/*
* TODO:
* When every board is converted to driver model and DT is supported,
* this can be done by auto-alloc feature, but not using calloc
* to alloc memory for plat.
*
* For example imx_rgpio2p_plat uses platform data rather than device
* tree.
*
* NOTE: DO NOT COPY this code if you are using device tree.
*/
plat = calloc(1, sizeof(*plat));
if (!plat)
return -ENOMEM;
plat->regs = (struct gpio_regs *)addr;
plat->bank_index = dev_seq(dev);
dev_set_plat(dev, plat);
return 0;
}
static struct imx_rgpio2p_soc_data imx7ulp_data = {
.have_dual_base = true,
};
static struct imx_rgpio2p_soc_data imx8ulp_data = {
.have_dual_base = false,
};
static const struct udevice_id imx_rgpio2p_ids[] = {
{ .compatible = "fsl,imx7ulp-gpio", .data = (ulong)&imx7ulp_data },
{ .compatible = "fsl,imx8ulp-gpio", .data = (ulong)&imx8ulp_data },
{ }
};
U_BOOT_DRIVER(imx_rgpio2p) = {
.name = "imx_rgpio2p",
.id = UCLASS_GPIO,
.ops = &imx_rgpio2p_ops,
.probe = imx_rgpio2p_probe,
.priv_auto = sizeof(struct imx_rgpio2p_plat),
.of_match = imx_rgpio2p_ids,
.bind = imx_rgpio2p_bind,
};
#if !CONFIG_IS_ENABLED(OF_CONTROL)
static const struct imx_rgpio2p_plat imx_plat[] = {
{ 0, (struct gpio_regs *)RGPIO2P_GPIO1_BASE_ADDR },
{ 1, (struct gpio_regs *)RGPIO2P_GPIO2_BASE_ADDR },
{ 2, (struct gpio_regs *)RGPIO2P_GPIO3_BASE_ADDR },
{ 3, (struct gpio_regs *)RGPIO2P_GPIO4_BASE_ADDR },
{ 4, (struct gpio_regs *)RGPIO2P_GPIO5_BASE_ADDR },
{ 5, (struct gpio_regs *)RGPIO2P_GPIO6_BASE_ADDR },
};
U_BOOT_DRVINFOS(imx_rgpio2ps) = {
{ "imx_rgpio2p", &imx_plat[0] },
{ "imx_rgpio2p", &imx_plat[1] },
{ "imx_rgpio2p", &imx_plat[2] },
{ "imx_rgpio2p", &imx_plat[3] },
{ "imx_rgpio2p", &imx_plat[4] },
{ "imx_rgpio2p", &imx_plat[5] },
};
#endif