tegra: Convert tegra GPIO driver to use driver model

This is an implementation of GPIOs for Tegra that uses driver model. It has
been tested on trimslice and also using the new iotrace feature.

The implementation uses a top-level GPIO device (which has no actual GPIOS).
Under this all the banks are created as separate GPIO devices.

The GPIOs are named as per the Tegra datasheet/header files: A0..A7, B0..B7,
..., Z0..Z7, AA0..AA7, etc.

Since driver model is not yet available before relocation, or in SPL, a
special function is provided for seaboard's SPL code.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2014-09-03 17:37:03 -06:00
parent a47411110c
commit 2fccd2d96b
4 changed files with 285 additions and 64 deletions

View file

@ -6,6 +6,8 @@
#ifndef _TEGRA_GPIO_H_ #ifndef _TEGRA_GPIO_H_
#define _TEGRA_GPIO_H_ #define _TEGRA_GPIO_H_
#define TEGRA_GPIOS_PER_PORT 8
#define TEGRA_PORTS_PER_BANK 4
#define MAX_NUM_GPIOS (TEGRA_GPIO_PORTS * TEGRA_GPIO_BANKS * 8) #define MAX_NUM_GPIOS (TEGRA_GPIO_PORTS * TEGRA_GPIO_BANKS * 8)
#define GPIO_NAME_SIZE 20 /* gpio_request max label len */ #define GPIO_NAME_SIZE 20 /* gpio_request max label len */
@ -25,9 +27,14 @@ struct tegra_gpio_config {
u32 init:2; u32 init:2;
}; };
/* /**
* Tegra-specific GPIO API * tegra_spl_gpio_direction_output() - set the output value of a GPIO
*
* This function is only used from SPL on seaboard, which needs to enable a
* GPIO to get the UART running. It could be done in U-Boot rather than SPL,
* but for now, this gets it working
*/ */
int tegra_spl_gpio_direction_output(int gpio, int value);
/** /**
* Configure a list of GPIOs * Configure a list of GPIOs
@ -37,8 +44,4 @@ struct tegra_gpio_config {
*/ */
void gpio_config_table(const struct tegra_gpio_config *config, int len); void gpio_config_table(const struct tegra_gpio_config *config, int len);
void gpio_info(void);
#define gpio_status() gpio_info()
#endif /* TEGRA_GPIO_H_ */ #endif /* TEGRA_GPIO_H_ */

View file

@ -22,7 +22,7 @@ void gpio_early_init_uart(void)
#ifndef CONFIG_SPL_BUILD #ifndef CONFIG_SPL_BUILD
gpio_request(GPIO_PI3, NULL); gpio_request(GPIO_PI3, NULL);
#endif #endif
gpio_direction_output(GPIO_PI3, 0); tegra_spl_gpio_direction_output(GPIO_PI3, 0);
} }
#endif #endif

View file

@ -12,10 +12,17 @@
*/ */
#include <common.h> #include <common.h>
#include <dm.h>
#include <malloc.h>
#include <errno.h>
#include <fdtdec.h>
#include <asm/io.h> #include <asm/io.h>
#include <asm/bitops.h> #include <asm/bitops.h>
#include <asm/arch/tegra.h> #include <asm/arch/tegra.h>
#include <asm/gpio.h> #include <asm/gpio.h>
#include <dm/device-internal.h>
DECLARE_GLOBAL_DATA_PTR;
enum { enum {
TEGRA_CMD_INFO, TEGRA_CMD_INFO,
@ -24,14 +31,18 @@ enum {
TEGRA_CMD_INPUT, TEGRA_CMD_INPUT,
}; };
static struct gpio_names { struct tegra_gpio_platdata {
char name[GPIO_NAME_SIZE]; struct gpio_ctlr_bank *bank;
} gpio_names[MAX_NUM_GPIOS]; const char *port_name; /* Name of port, e.g. "B" */
int base_gpio; /* Port number for this port (0, 1,.., n-1) */
};
static char *get_name(int i) /* Information about each port at run-time */
{ struct tegra_port_info {
return *gpio_names[i].name ? gpio_names[i].name : "UNKNOWN"; char label[TEGRA_GPIOS_PER_PORT][GPIO_NAME_SIZE];
} struct gpio_ctlr_bank *bank;
int base_gpio; /* Port number for this port (0, 1,.., n-1) */
};
/* Return config of pin 'gpio' as GPIO (1) or SFPIO (0) */ /* Return config of pin 'gpio' as GPIO (1) or SFPIO (0) */
static int get_config(unsigned gpio) static int get_config(unsigned gpio)
@ -121,38 +132,72 @@ static void set_level(unsigned gpio, int high)
writel(u, &bank->gpio_out[GPIO_PORT(gpio)]); writel(u, &bank->gpio_out[GPIO_PORT(gpio)]);
} }
static int check_reserved(struct udevice *dev, unsigned offset,
const char *func)
{
struct tegra_port_info *state = dev_get_priv(dev);
struct gpio_dev_priv *uc_priv = dev->uclass_priv;
if (!*state->label[offset]) {
printf("tegra_gpio: %s: error: gpio %s%d not reserved\n",
func, uc_priv->bank_name, offset);
return -EBUSY;
}
return 0;
}
/* set GPIO pin 'gpio' as an output, with polarity 'value' */
int tegra_spl_gpio_direction_output(int gpio, int value)
{
/* Configure as a GPIO */
set_config(gpio, 1);
/* Configure GPIO output value. */
set_level(gpio, value);
/* Configure GPIO direction as output. */
set_direction(gpio, 1);
return 0;
}
/* /*
* Generic_GPIO primitives. * Generic_GPIO primitives.
*/ */
int gpio_request(unsigned gpio, const char *label) static int tegra_gpio_request(struct udevice *dev, unsigned offset,
const char *label)
{ {
if (gpio >= MAX_NUM_GPIOS) struct tegra_port_info *state = dev_get_priv(dev);
return -1;
if (label != NULL) { if (*state->label[offset])
strncpy(gpio_names[gpio].name, label, GPIO_NAME_SIZE); return -EBUSY;
gpio_names[gpio].name[GPIO_NAME_SIZE - 1] = '\0';
} strncpy(state->label[offset], label, GPIO_NAME_SIZE);
state->label[offset][GPIO_NAME_SIZE - 1] = '\0';
/* Configure as a GPIO */ /* Configure as a GPIO */
set_config(gpio, 1); set_config(state->base_gpio + offset, 1);
return 0; return 0;
} }
int gpio_free(unsigned gpio) static int tegra_gpio_free(struct udevice *dev, unsigned offset)
{ {
if (gpio >= MAX_NUM_GPIOS) struct tegra_port_info *state = dev_get_priv(dev);
return -1; int ret;
ret = check_reserved(dev, offset, __func__);
if (ret)
return ret;
state->label[offset][0] = '\0';
gpio_names[gpio].name[0] = '\0';
/* Do not configure as input or change pin mux here */
return 0; return 0;
} }
/* read GPIO OUT value of pin 'gpio' */ /* read GPIO OUT value of pin 'gpio' */
static int gpio_get_output_value(unsigned gpio) static int tegra_gpio_get_output_value(unsigned gpio)
{ {
struct gpio_ctlr *ctlr = (struct gpio_ctlr *)NV_PA_GPIO_BASE; struct gpio_ctlr *ctlr = (struct gpio_ctlr *)NV_PA_GPIO_BASE;
struct gpio_ctlr_bank *bank = &ctlr->gpio_bank[GPIO_BANK(gpio)]; struct gpio_ctlr_bank *bank = &ctlr->gpio_bank[GPIO_BANK(gpio)];
@ -166,24 +211,34 @@ static int gpio_get_output_value(unsigned gpio)
return (val >> GPIO_BIT(gpio)) & 1; return (val >> GPIO_BIT(gpio)) & 1;
} }
/* set GPIO pin 'gpio' as an input */ /* set GPIO pin 'gpio' as an input */
int gpio_direction_input(unsigned gpio) static int tegra_gpio_direction_input(struct udevice *dev, unsigned offset)
{ {
debug("gpio_direction_input: pin = %d (port %d:bit %d)\n", struct tegra_port_info *state = dev_get_priv(dev);
gpio, GPIO_FULLPORT(gpio), GPIO_BIT(gpio)); int ret;
ret = check_reserved(dev, offset, __func__);
if (ret)
return ret;
/* Configure GPIO direction as input. */ /* Configure GPIO direction as input. */
set_direction(gpio, 0); set_direction(state->base_gpio + offset, 0);
return 0; return 0;
} }
/* set GPIO pin 'gpio' as an output, with polarity 'value' */ /* set GPIO pin 'gpio' as an output, with polarity 'value' */
int gpio_direction_output(unsigned gpio, int value) static int tegra_gpio_direction_output(struct udevice *dev, unsigned offset,
int value)
{ {
debug("gpio_direction_output: pin = %d (port %d:bit %d) = %s\n", struct tegra_port_info *state = dev_get_priv(dev);
gpio, GPIO_FULLPORT(gpio), GPIO_BIT(gpio), int gpio = state->base_gpio + offset;
value ? "HIGH" : "LOW"); int ret;
ret = check_reserved(dev, offset, __func__);
if (ret)
return ret;
/* Configure GPIO output value. */ /* Configure GPIO output value. */
set_level(gpio, value); set_level(gpio, value);
@ -195,25 +250,38 @@ int gpio_direction_output(unsigned gpio, int value)
} }
/* read GPIO IN value of pin 'gpio' */ /* read GPIO IN value of pin 'gpio' */
int gpio_get_value(unsigned gpio) static int tegra_gpio_get_value(struct udevice *dev, unsigned offset)
{ {
struct gpio_ctlr *ctlr = (struct gpio_ctlr *)NV_PA_GPIO_BASE; struct tegra_port_info *state = dev_get_priv(dev);
struct gpio_ctlr_bank *bank = &ctlr->gpio_bank[GPIO_BANK(gpio)]; int gpio = state->base_gpio + offset;
int ret;
int val; int val;
debug("gpio_get_value: pin = %d (port %d:bit %d)\n", ret = check_reserved(dev, offset, __func__);
gpio, GPIO_FULLPORT(gpio), GPIO_BIT(gpio)); if (ret)
return ret;
val = readl(&bank->gpio_in[GPIO_PORT(gpio)]); debug("%s: pin = %d (port %d:bit %d)\n", __func__,
gpio, GPIO_FULLPORT(gpio), GPIO_BIT(gpio));
val = readl(&state->bank->gpio_in[GPIO_PORT(gpio)]);
return (val >> GPIO_BIT(gpio)) & 1; return (val >> GPIO_BIT(gpio)) & 1;
} }
/* write GPIO OUT value to pin 'gpio' */ /* write GPIO OUT value to pin 'gpio' */
int gpio_set_value(unsigned gpio, int value) static int tegra_gpio_set_value(struct udevice *dev, unsigned offset, int value)
{ {
struct tegra_port_info *state = dev_get_priv(dev);
int gpio = state->base_gpio + offset;
int ret;
ret = check_reserved(dev, offset, __func__);
if (ret)
return ret;
debug("gpio_set_value: pin = %d (port %d:bit %d), value = %d\n", debug("gpio_set_value: pin = %d (port %d:bit %d), value = %d\n",
gpio, GPIO_FULLPORT(gpio), GPIO_BIT(gpio), value); gpio, GPIO_FULLPORT(gpio), GPIO_BIT(gpio), value);
/* Configure GPIO output value. */ /* Configure GPIO output value. */
set_level(gpio, value); set_level(gpio, value);
@ -241,26 +309,175 @@ void gpio_config_table(const struct tegra_gpio_config *config, int len)
} }
} }
/* static int tegra_gpio_get_function(struct udevice *dev, unsigned offset)
* Display Tegra GPIO information
*/
void gpio_info(void)
{ {
unsigned c; struct tegra_port_info *state = dev_get_priv(dev);
int type; int gpio = state->base_gpio + offset;
for (c = 0; c < MAX_NUM_GPIOS; c++) { if (!*state->label[offset])
type = get_config(c); /* GPIO, not SFPIO */ return GPIOF_UNUSED;
if (type) { if (!get_config(gpio))
printf("GPIO_%d:\t%s is an %s, ", c, return GPIOF_FUNC;
get_name(c), else if (get_direction(gpio))
get_direction(c) ? "OUTPUT" : "INPUT"); return GPIOF_OUTPUT;
if (get_direction(c)) else
printf("value = %d", gpio_get_output_value(c)); return GPIOF_INPUT;
else
printf("value = %d", gpio_get_value(c));
printf("\n");
} else
continue;
}
} }
static int tegra_gpio_get_state(struct udevice *dev, unsigned int offset,
char *buf, int bufsize)
{
struct gpio_dev_priv *uc_priv = dev->uclass_priv;
struct tegra_port_info *state = dev_get_priv(dev);
int gpio = state->base_gpio + offset;
const char *label;
int is_output;
int is_gpio;
int size;
label = state->label[offset];
is_gpio = get_config(gpio); /* GPIO, not SFPIO */
size = snprintf(buf, bufsize, "%s%d: ",
uc_priv->bank_name ? uc_priv->bank_name : "", offset);
buf += size;
bufsize -= size;
if (is_gpio) {
is_output = get_direction(gpio);
snprintf(buf, bufsize, "%s: %d [%c]%s%s",
is_output ? "out" : " in",
is_output ?
tegra_gpio_get_output_value(gpio) :
tegra_gpio_get_value(dev, offset),
*label ? 'x' : ' ',
*label ? " " : "",
label);
} else {
snprintf(buf, bufsize, "sfpio");
}
return 0;
}
static const struct dm_gpio_ops gpio_tegra_ops = {
.request = tegra_gpio_request,
.free = tegra_gpio_free,
.direction_input = tegra_gpio_direction_input,
.direction_output = tegra_gpio_direction_output,
.get_value = tegra_gpio_get_value,
.set_value = tegra_gpio_set_value,
.get_function = tegra_gpio_get_function,
.get_state = tegra_gpio_get_state,
};
/**
* Returns the name of a GPIO port
*
* GPIOs are named A, B, C, ..., Z, AA, BB, CC, ...
*
* @base_port: Base port number (0, 1..n-1)
* @return allocated string containing the name
*/
static char *gpio_port_name(int base_port)
{
char *name, *s;
name = malloc(3);
if (name) {
s = name;
*s++ = 'A' + (base_port % 26);
if (base_port >= 26)
*s++ = *name;
*s = '\0';
}
return name;
}
static const struct udevice_id tegra_gpio_ids[] = {
{ .compatible = "nvidia,tegra30-gpio" },
{ .compatible = "nvidia,tegra20-gpio" },
{ }
};
static int gpio_tegra_probe(struct udevice *dev)
{
struct gpio_dev_priv *uc_priv = dev->uclass_priv;
struct tegra_port_info *priv = dev->priv;
struct tegra_gpio_platdata *plat = dev->platdata;
/* Only child devices have ports */
if (!plat)
return 0;
priv->bank = plat->bank;
priv->base_gpio = plat->base_gpio;
uc_priv->gpio_count = TEGRA_GPIOS_PER_PORT;
uc_priv->bank_name = plat->port_name;
return 0;
}
/**
* We have a top-level GPIO device with no actual GPIOs. It has a child
* device for each Tegra port.
*/
static int gpio_tegra_bind(struct udevice *parent)
{
struct tegra_gpio_platdata *plat = parent->platdata;
struct gpio_ctlr *ctlr;
int bank_count;
int bank;
int ret;
int len;
/* If this is a child device, there is nothing to do here */
if (plat)
return 0;
/*
* This driver does not make use of interrupts, other than to figure
* out the number of GPIO banks
*/
if (!fdt_getprop(gd->fdt_blob, parent->of_offset, "interrupts", &len))
return -EINVAL;
bank_count = len / 3 / sizeof(u32);
ctlr = (struct gpio_ctlr *)fdtdec_get_addr(gd->fdt_blob,
parent->of_offset, "reg");
for (bank = 0; bank < bank_count; bank++) {
int port;
for (port = 0; port < TEGRA_PORTS_PER_BANK; port++) {
struct tegra_gpio_platdata *plat;
struct udevice *dev;
int base_port;
plat = calloc(1, sizeof(*plat));
if (!plat)
return -ENOMEM;
plat->bank = &ctlr->gpio_bank[bank];
base_port = bank * TEGRA_PORTS_PER_BANK + port;
plat->base_gpio = TEGRA_GPIOS_PER_PORT * base_port;
plat->port_name = gpio_port_name(base_port);
ret = device_bind(parent, parent->driver,
plat->port_name, plat, -1, &dev);
if (ret)
return ret;
dev->of_offset = parent->of_offset;
}
}
return 0;
}
U_BOOT_DRIVER(gpio_tegra) = {
.name = "gpio_tegra",
.id = UCLASS_GPIO,
.of_match = tegra_gpio_ids,
.bind = gpio_tegra_bind,
.probe = gpio_tegra_probe,
.priv_auto_alloc_size = sizeof(struct tegra_port_info),
.ops = &gpio_tegra_ops,
};

View file

@ -20,6 +20,7 @@
#define CONFIG_DM #define CONFIG_DM
#define CONFIG_CMD_DM #define CONFIG_CMD_DM
#define CONFIG_DM_GPIO
#define CONFIG_SYS_TIMER_RATE 1000000 #define CONFIG_SYS_TIMER_RATE 1000000
#define CONFIG_SYS_TIMER_COUNTER NV_PA_TMRUS_BASE #define CONFIG_SYS_TIMER_COUNTER NV_PA_TMRUS_BASE