mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2025-08-14 03:06:59 +02:00
This adds support for the ADP588 GPIO expander from Analog Devices. It is accessed over I2C and provides up to 18 pins. It is largely a port of the Linux driver developed by Michael Hennerich <michael.hennerich@analog.com> Signed-off-by: Ian Roberts <ian.roberts@timesys.com> Signed-off-by: Greg Malysa <malysagreg@gmail.com> Signed-off-by: Vasileios Bimpikas <vasileios.bimpikas@analog.com> Signed-off-by: Utsav Agarwal <utsav.agarwal@analog.com> Signed-off-by: Arturs Artamonovs <arturs.artamonovs@analog.com> Signed-off-by: Oliver Gaskell <Oliver.Gaskell@analog.com> Signed-off-by: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
209 lines
5.3 KiB
C
209 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* GPIO Chip driver for Analog Devices
|
|
* ADP5588/ADP5587 I/O Expander and QWERTY Keypad Controller
|
|
*
|
|
* (C) Copyright 2022 - Analog Devices, Inc.
|
|
*
|
|
* Written and/or maintained by Timesys Corporation
|
|
*
|
|
* Contact: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
|
|
* Contact: Greg Malysa <greg.malysa@timesys.com>
|
|
*
|
|
* Based on Michael Hennerich's Linux driver:
|
|
* Michael Hennerich <michael.hennerich@analog.com>
|
|
*
|
|
*/
|
|
|
|
#include <dm.h>
|
|
#include <i2c.h>
|
|
#include <asm-generic/gpio.h>
|
|
|
|
#define ADP5588_MAXGPIO 18
|
|
#define ADP5588_BANK(offs) ((offs) >> 3)
|
|
#define ADP5588_BIT(offs) (1u << ((offs) & 0x7))
|
|
|
|
#define DEV_ID 0x00 /* Device ID */
|
|
#define GPIO_DAT_STAT1 0x14 /* GPIO Data Status, Read twice to clear */
|
|
#define GPIO_DAT_STAT2 0x15 /* GPIO Data Status, Read twice to clear */
|
|
#define GPIO_DAT_STAT3 0x16 /* GPIO Data Status, Read twice to clear */
|
|
#define GPIO_DAT_OUT1 0x17 /* GPIO DATA OUT */
|
|
#define GPIO_DAT_OUT2 0x18 /* GPIO DATA OUT */
|
|
#define GPIO_DAT_OUT3 0x19 /* GPIO DATA OUT */
|
|
#define GPIO_INT_EN1 0x1A /* GPIO Interrupt Enable */
|
|
#define GPIO_INT_EN2 0x1B /* GPIO Interrupt Enable */
|
|
#define GPIO_INT_EN3 0x1C /* GPIO Interrupt Enable */
|
|
#define KP_GPIO1 0x1D /* Keypad or GPIO Selection */
|
|
#define KP_GPIO2 0x1E /* Keypad or GPIO Selection */
|
|
#define KP_GPIO3 0x1F /* Keypad or GPIO Selection */
|
|
#define GPIO_DIR1 0x23 /* GPIO Data Direction */
|
|
#define GPIO_DIR2 0x24 /* GPIO Data Direction */
|
|
#define GPIO_DIR3 0x25 /* GPIO Data Direction */
|
|
#define GPIO_PULL1 0x2C /* GPIO Pull Disable */
|
|
#define GPIO_PULL2 0x2D /* GPIO Pull Disable */
|
|
#define GPIO_PULL3 0x2E /* GPIO Pull Disable */
|
|
#define ID_MASK 0x0F
|
|
|
|
struct adp5588_gpio {
|
|
u8 dat_out[3];
|
|
u8 dir[3];
|
|
};
|
|
|
|
static int adp5588_gpio_read(struct udevice *dev, u8 reg)
|
|
{
|
|
int ret;
|
|
u8 val;
|
|
|
|
ret = dm_i2c_read(dev, reg, &val, 1);
|
|
|
|
if (ret < 0) {
|
|
pr_err("%s: read error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int adp5588_gpio_write(struct udevice *dev, u8 reg, u8 val)
|
|
{
|
|
int ret;
|
|
|
|
ret = dm_i2c_write(dev, reg, &val, 1);
|
|
if (ret < 0) {
|
|
pr_err("%s: write error\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adp5588_get_value(struct udevice *dev, u32 offset)
|
|
{
|
|
struct adp5588_gpio *plat = dev_get_plat(dev);
|
|
unsigned int bank = ADP5588_BANK(offset);
|
|
unsigned int bit = ADP5588_BIT(offset);
|
|
int val;
|
|
|
|
if (plat->dir[bank] & bit)
|
|
val = plat->dat_out[bank];
|
|
else
|
|
val = adp5588_gpio_read(dev, GPIO_DAT_STAT1 + bank);
|
|
|
|
return !!(val & bit);
|
|
}
|
|
|
|
static int adp5588_set_value(struct udevice *dev, u32 offset,
|
|
int32_t value)
|
|
{
|
|
unsigned int bank, bit;
|
|
int ret;
|
|
struct adp5588_gpio *plat = dev_get_plat(dev);
|
|
|
|
bank = ADP5588_BANK(offset);
|
|
bit = ADP5588_BIT(offset);
|
|
|
|
if (value)
|
|
plat->dat_out[bank] |= bit;
|
|
else
|
|
plat->dat_out[bank] &= ~bit;
|
|
|
|
ret = adp5588_gpio_write(dev, GPIO_DAT_OUT1 + bank,
|
|
plat->dat_out[bank]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int adp5588_direction_input(struct udevice *dev, u32 offset)
|
|
{
|
|
int ret;
|
|
unsigned int bank;
|
|
struct adp5588_gpio *plat = dev_get_plat(dev);
|
|
|
|
bank = ADP5588_BANK(offset);
|
|
|
|
plat->dir[bank] &= ~ADP5588_BIT(offset);
|
|
ret = adp5588_gpio_write(dev, GPIO_DIR1 + bank, plat->dir[bank]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int adp5588_direction_output(struct udevice *dev,
|
|
u32 offset, int value)
|
|
{
|
|
int ret;
|
|
unsigned int bank, bit;
|
|
struct adp5588_gpio *plat = dev_get_plat(dev);
|
|
|
|
bank = ADP5588_BANK(offset);
|
|
bit = ADP5588_BIT(offset);
|
|
|
|
plat->dir[bank] |= bit;
|
|
|
|
if (value)
|
|
plat->dat_out[bank] |= bit;
|
|
else
|
|
plat->dat_out[bank] &= ~bit;
|
|
|
|
ret = adp5588_gpio_write(dev, GPIO_DAT_OUT1 + bank,
|
|
plat->dat_out[bank]);
|
|
ret |= adp5588_gpio_write(dev, GPIO_DIR1 + bank,
|
|
plat->dir[bank]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int adp5588_ofdata_platdata(struct udevice *dev)
|
|
{
|
|
struct adp5588_gpio *plat = dev_get_plat(dev);
|
|
struct gpio_dev_priv *priv = dev_get_uclass_priv(dev);
|
|
int node = dev_of_offset(dev);
|
|
int ret, i, revid;
|
|
|
|
priv->gpio_count = ADP5588_MAXGPIO;
|
|
priv->bank_name = fdt_get_name(gd->fdt_blob, node, NULL);
|
|
|
|
ret = adp5588_gpio_read(dev, DEV_ID);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
revid = ret & ID_MASK;
|
|
|
|
printf("ADP5588 Detected: Rev %x, Rev ID %x\n", ret, revid);
|
|
|
|
for (i = 0, ret = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) {
|
|
plat->dat_out[i] = adp5588_gpio_read(dev, GPIO_DAT_OUT1 + i);
|
|
plat->dir[i] = adp5588_gpio_read(dev, GPIO_DIR1 + i);
|
|
ret |= adp5588_gpio_write(dev, KP_GPIO1 + i, 0);
|
|
ret |= adp5588_gpio_write(dev, GPIO_PULL1 + i, 0);
|
|
ret |= adp5588_gpio_write(dev, GPIO_INT_EN1 + i, 0);
|
|
if (ret) {
|
|
pr_err("%s: Initialization error\n", __func__);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dm_gpio_ops adp5588_ops = {
|
|
.direction_input = adp5588_direction_input,
|
|
.direction_output = adp5588_direction_output,
|
|
.get_value = adp5588_get_value,
|
|
.set_value = adp5588_set_value,
|
|
};
|
|
|
|
static const struct udevice_id adp5588_of_match_list[] = {
|
|
{ .compatible = "adi,adp5588"},
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(gpio_adp5588) = {
|
|
.name = "gpio_adp5588",
|
|
.id = UCLASS_GPIO,
|
|
.ops = &adp5588_ops,
|
|
.of_match = adp5588_of_match_list,
|
|
.of_to_plat = adp5588_ofdata_platdata,
|
|
.plat_auto = sizeof(struct adp5588_gpio),
|
|
.flags = DM_FLAG_PRE_RELOC,
|
|
};
|