can: mcp251xfd: add gpio functionality

The mcp251xfd devices allow two pins to be configured as gpio. Add this
functionality to driver.

Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Gregor Herburger <gregor.herburger@ew.tq-group.com>
Tested-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
Link: https://patch.msgid.link/20251001091006.4003841-6-viken.dadhaniya@oss.qualcomm.com
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
Gregor Herburger
2025-10-01 14:40:05 +05:30
committed by Marc Kleine-Budde
parent d35fa005f5
commit c6106336ec
3 changed files with 163 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ config CAN_MCP251XFD
select CAN_RX_OFFLOAD
select REGMAP
select WANT_DEV_COREDUMP
select GPIOLIB
help
Driver for the Microchip MCP251XFD SPI FD-CAN controller
family.

View File

@@ -1797,6 +1797,160 @@ static int mcp251xfd_register_check_rx_int(struct mcp251xfd_priv *priv)
return 0;
}
static const char * const mcp251xfd_gpio_names[] = { "GPIO0", "GPIO1" };
static int mcp251xfd_gpio_request(struct gpio_chip *chip, unsigned int offset)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 pin_mask = MCP251XFD_REG_IOCON_PM(offset);
int ret;
if (priv->rx_int && offset == 1) {
netdev_err(priv->ndev, "Can't use GPIO 1 with RX-INT!\n");
return -EINVAL;
}
ret = pm_runtime_resume_and_get(priv->ndev->dev.parent);
if (ret)
return ret;
return regmap_update_bits(priv->map_reg, MCP251XFD_REG_IOCON, pin_mask, pin_mask);
}
static void mcp251xfd_gpio_free(struct gpio_chip *chip, unsigned int offset)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
pm_runtime_put(priv->ndev->dev.parent);
}
static int mcp251xfd_gpio_get_direction(struct gpio_chip *chip,
unsigned int offset)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 mask = MCP251XFD_REG_IOCON_TRIS(offset);
u32 val;
int ret;
ret = regmap_read(priv->map_reg, MCP251XFD_REG_IOCON, &val);
if (ret)
return ret;
if (mask & val)
return GPIO_LINE_DIRECTION_IN;
return GPIO_LINE_DIRECTION_OUT;
}
static int mcp251xfd_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 mask = MCP251XFD_REG_IOCON_GPIO(offset);
u32 val;
int ret;
ret = regmap_read(priv->map_reg, MCP251XFD_REG_IOCON, &val);
if (ret)
return ret;
return !!(mask & val);
}
static int mcp251xfd_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bit)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 val;
int ret;
ret = regmap_read(priv->map_reg, MCP251XFD_REG_IOCON, &val);
if (ret)
return ret;
*bit = FIELD_GET(MCP251XFD_REG_IOCON_GPIO_MASK, val) & *mask;
return 0;
}
static int mcp251xfd_gpio_direction_output(struct gpio_chip *chip,
unsigned int offset, int value)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 dir_mask = MCP251XFD_REG_IOCON_TRIS(offset);
u32 val_mask = MCP251XFD_REG_IOCON_LAT(offset);
u32 val;
if (value)
val = val_mask;
else
val = 0;
return regmap_update_bits(priv->map_reg, MCP251XFD_REG_IOCON,
dir_mask | val_mask, val);
}
static int mcp251xfd_gpio_direction_input(struct gpio_chip *chip,
unsigned int offset)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 dir_mask = MCP251XFD_REG_IOCON_TRIS(offset);
return regmap_update_bits(priv->map_reg, MCP251XFD_REG_IOCON, dir_mask, dir_mask);
}
static int mcp251xfd_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 val_mask = MCP251XFD_REG_IOCON_LAT(offset);
u32 val;
if (value)
val = val_mask;
else
val = 0;
return regmap_update_bits(priv->map_reg, MCP251XFD_REG_IOCON, val_mask, val);
}
static int mcp251xfd_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits)
{
struct mcp251xfd_priv *priv = gpiochip_get_data(chip);
u32 val;
val = FIELD_PREP(MCP251XFD_REG_IOCON_LAT_MASK, *bits);
return regmap_update_bits(priv->map_reg, MCP251XFD_REG_IOCON,
MCP251XFD_REG_IOCON_LAT_MASK, val);
}
static int mcp251fdx_gpio_setup(struct mcp251xfd_priv *priv)
{
struct gpio_chip *gc = &priv->gc;
if (!device_property_present(&priv->spi->dev, "gpio-controller"))
return 0;
gc->label = dev_name(&priv->spi->dev);
gc->parent = &priv->spi->dev;
gc->owner = THIS_MODULE;
gc->request = mcp251xfd_gpio_request;
gc->free = mcp251xfd_gpio_free;
gc->get_direction = mcp251xfd_gpio_get_direction;
gc->direction_output = mcp251xfd_gpio_direction_output;
gc->direction_input = mcp251xfd_gpio_direction_input;
gc->get = mcp251xfd_gpio_get;
gc->get_multiple = mcp251xfd_gpio_get_multiple;
gc->set = mcp251xfd_gpio_set;
gc->set_multiple = mcp251xfd_gpio_set_multiple;
gc->base = -1;
gc->can_sleep = true;
gc->ngpio = ARRAY_SIZE(mcp251xfd_gpio_names);
gc->names = mcp251xfd_gpio_names;
return devm_gpiochip_add_data(&priv->spi->dev, gc, priv);
}
static int
mcp251xfd_register_get_dev_id(const struct mcp251xfd_priv *priv, u32 *dev_id,
u32 *effective_speed_hz_slow,
@@ -1930,6 +2084,12 @@ static int mcp251xfd_register(struct mcp251xfd_priv *priv)
mcp251xfd_ethtool_init(priv);
err = mcp251fdx_gpio_setup(priv);
if (err) {
dev_err_probe(&priv->spi->dev, err, "Failed to register gpio-controller.\n");
goto out_runtime_disable;
}
err = register_candev(ndev);
if (err)
goto out_runtime_disable;

View File

@@ -15,6 +15,7 @@
#include <linux/can/dev.h>
#include <linux/can/rx-offload.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/regmap.h>
@@ -676,6 +677,7 @@ struct mcp251xfd_priv {
struct mcp251xfd_devtype_data devtype_data;
struct can_berr_counter bec;
struct gpio_chip gc;
};
#define MCP251XFD_IS(_model) \