Merge patch series "can: mcp251xfd: add gpio functionality"

Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com> says:

The mcp251xfd allows two pins to be configured as GPIOs. This series adds
support for this feature.

The GPIO functionality is controlled with the IOCON register which has an
erratum.

Patch 1 from https://lore.kernel.org/linux-can/20240429-mcp251xfd-runtime_pm-v1-3-c26a93a66544@pengutronix.de/
Patch 2 refactor of no-crc functions to prepare workaround for non-crc writes
Patch 3 is the fix/workaround for the aforementioned erratum
Patch 4 only configure pin1 for rx-int
Patch 5 adds the gpio support
Patch 6 updates dt-binding

https://lore.kernel.org/all/20240806-industrious-augmented-crane-44239a-mkl@pengutronix.de/

Link: https://patch.msgid.link/20251001091006.4003841-1-viken.dadhaniya@oss.qualcomm.com
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
Marc Kleine-Budde 2025-11-12 19:30:55 +01:00
commit 2d0938702c
5 changed files with 339 additions and 70 deletions

View File

@ -49,6 +49,11 @@ properties:
Must be half or less of "clocks" frequency.
maximum: 20000000
gpio-controller: true
"#gpio-cells":
const: 2
required:
- compatible
- reg

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

@ -608,23 +608,21 @@ static int mcp251xfd_set_bittiming(const struct mcp251xfd_priv *priv)
static int mcp251xfd_chip_rx_int_enable(const struct mcp251xfd_priv *priv)
{
u32 val;
u32 val, mask;
if (!priv->rx_int)
return 0;
/* Configure GPIOs:
* - PIN0: GPIO Input
* - PIN1: GPIO Input/RX Interrupt
/* Configure PIN1 as RX Interrupt:
*
* PIN1 must be Input, otherwise there is a glitch on the
* rx-INT line. It happens between setting the PIN as output
* (in the first byte of the SPI transfer) and configuring the
* PIN as interrupt (in the last byte of the SPI transfer).
*/
val = MCP251XFD_REG_IOCON_PM0 | MCP251XFD_REG_IOCON_TRIS1 |
MCP251XFD_REG_IOCON_TRIS0;
return regmap_write(priv->map_reg, MCP251XFD_REG_IOCON, val);
val = MCP251XFD_REG_IOCON_TRIS(1);
mask = MCP251XFD_REG_IOCON_TRIS(1) | MCP251XFD_REG_IOCON_PM(1);
return regmap_update_bits(priv->map_reg, MCP251XFD_REG_IOCON, mask, val);
}
static int mcp251xfd_chip_rx_int_disable(const struct mcp251xfd_priv *priv)
@ -634,13 +632,9 @@ static int mcp251xfd_chip_rx_int_disable(const struct mcp251xfd_priv *priv)
if (!priv->rx_int)
return 0;
/* Configure GPIOs:
* - PIN0: GPIO Input
* - PIN1: GPIO Input
*/
val = MCP251XFD_REG_IOCON_PM1 | MCP251XFD_REG_IOCON_PM0 |
MCP251XFD_REG_IOCON_TRIS1 | MCP251XFD_REG_IOCON_TRIS0;
return regmap_write(priv->map_reg, MCP251XFD_REG_IOCON, val);
/* Configure PIN1 as GPIO Input */
val = MCP251XFD_REG_IOCON_PM(1) | MCP251XFD_REG_IOCON_TRIS(1);
return regmap_update_bits(priv->map_reg, MCP251XFD_REG_IOCON, val, val);
}
static int mcp251xfd_chip_ecc_init(struct mcp251xfd_priv *priv)
@ -767,21 +761,13 @@ static void mcp251xfd_chip_stop(struct mcp251xfd_priv *priv,
mcp251xfd_chip_interrupts_disable(priv);
mcp251xfd_chip_rx_int_disable(priv);
mcp251xfd_timestamp_stop(priv);
mcp251xfd_chip_sleep(priv);
mcp251xfd_chip_set_mode(priv, MCP251XFD_REG_CON_MODE_CONFIG);
}
static int mcp251xfd_chip_start(struct mcp251xfd_priv *priv)
{
int err;
err = mcp251xfd_chip_softreset(priv);
if (err)
goto out_chip_stop;
err = mcp251xfd_chip_clock_init(priv);
if (err)
goto out_chip_stop;
err = mcp251xfd_chip_timestamp_init(priv);
if (err)
goto out_chip_stop;
@ -1625,8 +1611,11 @@ static int mcp251xfd_open(struct net_device *ndev)
return err;
err = pm_runtime_resume_and_get(ndev->dev.parent);
if (err)
if (err) {
if (err == -ETIMEDOUT || err == -ENODEV)
pm_runtime_set_suspended(ndev->dev.parent);
goto out_close_candev;
}
err = mcp251xfd_ring_alloc(priv);
if (err)
@ -1808,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,
@ -1907,53 +2050,59 @@ static int mcp251xfd_register(struct mcp251xfd_priv *priv)
struct net_device *ndev = priv->ndev;
int err;
mcp251xfd_register_quirks(priv);
err = mcp251xfd_clks_and_vdd_enable(priv);
if (err)
return err;
err = mcp251xfd_chip_softreset(priv);
if (err == -ENODEV)
goto out_clks_and_vdd_disable;
if (err)
goto out_chip_sleep;
err = mcp251xfd_chip_clock_init(priv);
if (err == -ENODEV)
goto out_clks_and_vdd_disable;
if (err)
goto out_chip_sleep;
pm_runtime_get_noresume(ndev->dev.parent);
err = pm_runtime_set_active(ndev->dev.parent);
if (err)
goto out_runtime_put_noidle;
pm_runtime_enable(ndev->dev.parent);
mcp251xfd_register_quirks(priv);
err = mcp251xfd_chip_softreset(priv);
if (err == -ENODEV)
goto out_runtime_disable;
if (err)
goto out_chip_sleep;
err = mcp251xfd_chip_clock_init(priv);
if (err == -ENODEV)
goto out_runtime_disable;
if (err)
goto out_chip_sleep;
err = mcp251xfd_register_chip_detect(priv);
if (err)
goto out_chip_sleep;
goto out_runtime_disable;
err = mcp251xfd_register_check_rx_int(priv);
if (err)
goto out_chip_sleep;
goto out_runtime_disable;
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_chip_sleep;
goto out_runtime_disable;
err = mcp251xfd_register_done(priv);
if (err)
goto out_unregister_candev;
/* Put controller into sleep mode and let pm_runtime_put()
* disable the clocks and vdd. If CONFIG_PM is not enabled,
* the clocks and vdd will stay powered.
/* Put controller into Config mode and let pm_runtime_put()
* put in sleep mode, disable the clocks and vdd. If CONFIG_PM
* is not enabled, the clocks and vdd will stay powered.
*/
err = mcp251xfd_chip_sleep(priv);
err = mcp251xfd_chip_set_mode(priv, MCP251XFD_REG_CON_MODE_CONFIG);
if (err)
goto out_unregister_candev;
@ -1963,12 +2112,13 @@ static int mcp251xfd_register(struct mcp251xfd_priv *priv)
out_unregister_candev:
unregister_candev(ndev);
out_chip_sleep:
mcp251xfd_chip_sleep(priv);
out_runtime_disable:
pm_runtime_disable(ndev->dev.parent);
out_runtime_put_noidle:
pm_runtime_put_noidle(ndev->dev.parent);
out_chip_sleep:
mcp251xfd_chip_sleep(priv);
out_clks_and_vdd_disable:
mcp251xfd_clks_and_vdd_disable(priv);
return err;
@ -1980,10 +2130,12 @@ static inline void mcp251xfd_unregister(struct mcp251xfd_priv *priv)
unregister_candev(ndev);
if (pm_runtime_enabled(ndev->dev.parent))
if (pm_runtime_enabled(ndev->dev.parent)) {
pm_runtime_disable(ndev->dev.parent);
else
} else {
mcp251xfd_chip_sleep(priv);
mcp251xfd_clks_and_vdd_disable(priv);
}
}
static const struct of_device_id mcp251xfd_of_match[] = {
@ -2206,16 +2358,41 @@ static void mcp251xfd_remove(struct spi_device *spi)
static int __maybe_unused mcp251xfd_runtime_suspend(struct device *device)
{
const struct mcp251xfd_priv *priv = dev_get_drvdata(device);
struct mcp251xfd_priv *priv = dev_get_drvdata(device);
mcp251xfd_chip_sleep(priv);
return mcp251xfd_clks_and_vdd_disable(priv);
}
static int __maybe_unused mcp251xfd_runtime_resume(struct device *device)
{
const struct mcp251xfd_priv *priv = dev_get_drvdata(device);
struct mcp251xfd_priv *priv = dev_get_drvdata(device);
int err;
return mcp251xfd_clks_and_vdd_enable(priv);
err = mcp251xfd_clks_and_vdd_enable(priv);
if (err)
return err;
err = mcp251xfd_chip_softreset(priv);
if (err == -ENODEV)
goto out_clks_and_vdd_disable;
if (err)
goto out_chip_sleep;
err = mcp251xfd_chip_clock_init(priv);
if (err == -ENODEV)
goto out_clks_and_vdd_disable;
if (err)
goto out_chip_sleep;
return 0;
out_chip_sleep:
mcp251xfd_chip_sleep(priv);
out_clks_and_vdd_disable:
mcp251xfd_clks_and_vdd_disable(priv);
return err;
}
static const struct dev_pm_ops mcp251xfd_pm_ops = {

View File

@ -13,17 +13,9 @@
static const struct regmap_config mcp251xfd_regmap_crc;
static int
mcp251xfd_regmap_nocrc_write(void *context, const void *data, size_t count)
{
struct spi_device *spi = context;
return spi_write(spi, data, count);
}
static int
mcp251xfd_regmap_nocrc_gather_write(void *context,
const void *reg, size_t reg_len,
const void *val, size_t val_len)
_mcp251xfd_regmap_nocrc_gather_write(void *context,
const void *reg, size_t reg_len,
const void *val, size_t val_len)
{
struct spi_device *spi = context;
struct mcp251xfd_priv *priv = spi_get_drvdata(spi);
@ -47,6 +39,54 @@ mcp251xfd_regmap_nocrc_gather_write(void *context,
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
}
static int
mcp251xfd_regmap_nocrc_gather_write(void *context,
const void *reg_p, size_t reg_len,
const void *val, size_t val_len)
{
const u16 byte_exclude = MCP251XFD_REG_IOCON +
mcp251xfd_first_byte_set(MCP251XFD_REG_IOCON_GPIO_MASK);
u16 reg = be16_to_cpu(*(__be16 *)reg_p) & MCP251XFD_SPI_ADDRESS_MASK;
int ret;
/* Never write to bits 16..23 of IOCON register to avoid clearing of LAT0/LAT1
*
* According to MCP2518FD Errata DS80000789E 5 writing IOCON register using one
* SPI write command clears LAT0/LAT1.
*
* Errata Fix/Work Around suggests to write registers with single byte
* write instructions. However, it seems that the byte at 0xe06(IOCON[23:16])
* is for read-only access and writing to it causes the clearing of LAT0/LAT1.
*/
if (reg <= byte_exclude && reg + val_len > byte_exclude) {
size_t len = byte_exclude - reg;
/* Write up to 0xe05 */
ret = _mcp251xfd_regmap_nocrc_gather_write(context, reg_p, reg_len, val, len);
if (ret)
return ret;
/* Write from 0xe07 on */
reg += len + 1;
reg = (__force unsigned short)cpu_to_be16(MCP251XFD_SPI_INSTRUCTION_WRITE | reg);
return _mcp251xfd_regmap_nocrc_gather_write(context, &reg, reg_len,
val + len + 1,
val_len - len - 1);
}
return _mcp251xfd_regmap_nocrc_gather_write(context, reg_p, reg_len,
val, val_len);
}
static int
mcp251xfd_regmap_nocrc_write(void *context, const void *data, size_t count)
{
const size_t data_offset = sizeof(__be16);
return mcp251xfd_regmap_nocrc_gather_write(context, data, data_offset,
data + data_offset, count - data_offset);
}
static inline bool
mcp251xfd_update_bits_read_reg(const struct mcp251xfd_priv *priv,
unsigned int reg)
@ -64,6 +104,7 @@ mcp251xfd_update_bits_read_reg(const struct mcp251xfd_priv *priv,
case MCP251XFD_REG_CON:
case MCP251XFD_REG_OSC:
case MCP251XFD_REG_ECCCON:
case MCP251XFD_REG_IOCON:
return true;
default:
mcp251xfd_for_each_rx_ring(priv, ring, n) {
@ -139,10 +180,9 @@ mcp251xfd_regmap_nocrc_update_bits(void *context, unsigned int reg,
tmp_le32 = orig_le32 & ~mask_le32;
tmp_le32 |= val_le32 & mask_le32;
mcp251xfd_spi_cmd_write_nocrc(&buf_tx->cmd, reg + first_byte);
memcpy(buf_tx->data, &tmp_le32, len);
return spi_write(spi, buf_tx, sizeof(buf_tx->cmd) + len);
reg += first_byte;
mcp251xfd_spi_cmd_write_nocrc(&buf_tx->cmd, reg);
return mcp251xfd_regmap_nocrc_gather_write(context, &buf_tx->cmd, 2, &tmp_le32, len);
}
static int
@ -196,9 +236,9 @@ mcp251xfd_regmap_nocrc_read(void *context,
}
static int
mcp251xfd_regmap_crc_gather_write(void *context,
const void *reg_p, size_t reg_len,
const void *val, size_t val_len)
_mcp251xfd_regmap_crc_gather_write(void *context,
const void *reg_p, size_t reg_len,
const void *val, size_t val_len)
{
struct spi_device *spi = context;
struct mcp251xfd_priv *priv = spi_get_drvdata(spi);
@ -229,6 +269,44 @@ mcp251xfd_regmap_crc_gather_write(void *context,
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
}
static int
mcp251xfd_regmap_crc_gather_write(void *context,
const void *reg_p, size_t reg_len,
const void *val, size_t val_len)
{
const u16 byte_exclude = MCP251XFD_REG_IOCON +
mcp251xfd_first_byte_set(MCP251XFD_REG_IOCON_GPIO_MASK);
u16 reg = *(u16 *)reg_p;
int ret;
/* Never write to bits 16..23 of IOCON register to avoid clearing of LAT0/LAT1
*
* According to MCP2518FD Errata DS80000789E 5 writing IOCON register using one
* SPI write command clears LAT0/LAT1.
*
* Errata Fix/Work Around suggests to write registers with single byte
* write instructions. However, it seems that the byte at 0xe06(IOCON[23:16])
* is for read-only access and writing to it causes the clearing of LAT0/LAT1.
*/
if (reg <= byte_exclude && reg + val_len > byte_exclude) {
size_t len = byte_exclude - reg;
/* Write up to 0xe05 */
ret = _mcp251xfd_regmap_crc_gather_write(context, &reg, reg_len, val, len);
if (ret)
return ret;
/* Write from 0xe07 on */
reg += len + 1;
return _mcp251xfd_regmap_crc_gather_write(context, &reg, reg_len,
val + len + 1,
val_len - len - 1);
}
return _mcp251xfd_regmap_crc_gather_write(context, reg_p, reg_len,
val, val_len);
}
static int
mcp251xfd_regmap_crc_write(void *context,
const void *data, size_t count)

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>
@ -335,13 +336,19 @@
#define MCP251XFD_REG_IOCON_TXCANOD BIT(28)
#define MCP251XFD_REG_IOCON_PM1 BIT(25)
#define MCP251XFD_REG_IOCON_PM0 BIT(24)
#define MCP251XFD_REG_IOCON_PM(n) (MCP251XFD_REG_IOCON_PM0 << (n))
#define MCP251XFD_REG_IOCON_GPIO1 BIT(17)
#define MCP251XFD_REG_IOCON_GPIO0 BIT(16)
#define MCP251XFD_REG_IOCON_GPIO(n) (MCP251XFD_REG_IOCON_GPIO0 << (n))
#define MCP251XFD_REG_IOCON_GPIO_MASK GENMASK(17, 16)
#define MCP251XFD_REG_IOCON_LAT1 BIT(9)
#define MCP251XFD_REG_IOCON_LAT0 BIT(8)
#define MCP251XFD_REG_IOCON_LAT(n) (MCP251XFD_REG_IOCON_LAT0 << (n))
#define MCP251XFD_REG_IOCON_LAT_MASK GENMASK(9, 8)
#define MCP251XFD_REG_IOCON_XSTBYEN BIT(6)
#define MCP251XFD_REG_IOCON_TRIS1 BIT(1)
#define MCP251XFD_REG_IOCON_TRIS0 BIT(0)
#define MCP251XFD_REG_IOCON_TRIS(n) (MCP251XFD_REG_IOCON_TRIS0 << (n))
#define MCP251XFD_REG_CRC 0xe08
#define MCP251XFD_REG_CRC_FERRIE BIT(25)
@ -670,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) \