diff options
Diffstat (limited to 'drivers/net/can/spi/mcp251xfd/mcp251xfd-regmap.c')
-rw-r--r-- | drivers/net/can/spi/mcp251xfd/mcp251xfd-regmap.c | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/drivers/net/can/spi/mcp251xfd/mcp251xfd-regmap.c b/drivers/net/can/spi/mcp251xfd/mcp251xfd-regmap.c new file mode 100644 index 000000000000..314f868b3465 --- /dev/null +++ b/drivers/net/can/spi/mcp251xfd/mcp251xfd-regmap.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mcp251xfd - Microchip MCP251xFD Family CAN controller driver +// +// Copyright (c) 2019, 2020 Pengutronix, +// Marc Kleine-Budde <kernel@pengutronix.de> +// + +#include "mcp251xfd.h" + +#include <asm/unaligned.h> + +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) +{ + struct spi_device *spi = context; + struct mcp251xfd_priv *priv = spi_get_drvdata(spi); + struct mcp251xfd_map_buf_nocrc *buf_tx = priv->map_buf_nocrc_tx; + struct spi_transfer xfer[] = { + { + .tx_buf = buf_tx, + .len = sizeof(buf_tx->cmd) + val_len, + }, + }; + + BUILD_BUG_ON(sizeof(buf_tx->cmd) != sizeof(__be16)); + + if (IS_ENABLED(CONFIG_CAN_MCP251XFD_SANITY) && + reg_len != sizeof(buf_tx->cmd.cmd)) + return -EINVAL; + + memcpy(&buf_tx->cmd, reg, sizeof(buf_tx->cmd)); + memcpy(buf_tx->data, val, val_len); + + return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer)); +} + +static inline bool mcp251xfd_update_bits_read_reg(unsigned int reg) +{ + switch (reg) { + case MCP251XFD_REG_INT: + case MCP251XFD_REG_TEFCON: + case MCP251XFD_REG_FIFOCON(MCP251XFD_RX_FIFO(0)): + case MCP251XFD_REG_FLTCON(0): + case MCP251XFD_REG_ECCSTAT: + case MCP251XFD_REG_CRC: + return false; + case MCP251XFD_REG_CON: + case MCP251XFD_REG_FIFOSTA(MCP251XFD_RX_FIFO(0)): + case MCP251XFD_REG_OSC: + case MCP251XFD_REG_ECCCON: + return true; + default: + WARN(1, "Status of reg 0x%04x unknown.\n", reg); + } + + return true; +} + +static int +mcp251xfd_regmap_nocrc_update_bits(void *context, unsigned int reg, + unsigned int mask, unsigned int val) +{ + struct spi_device *spi = context; + struct mcp251xfd_priv *priv = spi_get_drvdata(spi); + struct mcp251xfd_map_buf_nocrc *buf_rx = priv->map_buf_nocrc_rx; + struct mcp251xfd_map_buf_nocrc *buf_tx = priv->map_buf_nocrc_tx; + __le32 orig_le32 = 0, mask_le32, val_le32, tmp_le32; + u8 first_byte, last_byte, len; + int err; + + BUILD_BUG_ON(sizeof(buf_rx->cmd) != sizeof(__be16)); + BUILD_BUG_ON(sizeof(buf_tx->cmd) != sizeof(__be16)); + + if (IS_ENABLED(CONFIG_CAN_MCP251XFD_SANITY) && + mask == 0) + return -EINVAL; + + first_byte = mcp251xfd_first_byte_set(mask); + last_byte = mcp251xfd_last_byte_set(mask); + len = last_byte - first_byte + 1; + + if (mcp251xfd_update_bits_read_reg(reg)) { + struct spi_transfer xfer[2] = { }; + struct spi_message msg; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + + if (priv->devtype_data.quirks & MCP251XFD_QUIRK_HALF_DUPLEX) { + xfer[0].tx_buf = buf_tx; + xfer[0].len = sizeof(buf_tx->cmd); + + xfer[1].rx_buf = buf_rx->data; + xfer[1].len = len; + spi_message_add_tail(&xfer[1], &msg); + } else { + xfer[0].tx_buf = buf_tx; + xfer[0].rx_buf = buf_rx; + xfer[0].len = sizeof(buf_tx->cmd) + len; + + if (MCP251XFD_SANITIZE_SPI) + memset(buf_tx->data, 0x0, len); + } + + mcp251xfd_spi_cmd_read_nocrc(&buf_tx->cmd, reg + first_byte); + err = spi_sync(spi, &msg); + if (err) + return err; + + memcpy(&orig_le32, buf_rx->data, len); + } + + mask_le32 = cpu_to_le32(mask >> BITS_PER_BYTE * first_byte); + val_le32 = cpu_to_le32(val >> BITS_PER_BYTE * first_byte); + + 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); +} + +static int +mcp251xfd_regmap_nocrc_read(void *context, + const void *reg, size_t reg_len, + void *val_buf, size_t val_len) +{ + struct spi_device *spi = context; + struct mcp251xfd_priv *priv = spi_get_drvdata(spi); + struct mcp251xfd_map_buf_nocrc *buf_rx = priv->map_buf_nocrc_rx; + struct mcp251xfd_map_buf_nocrc *buf_tx = priv->map_buf_nocrc_tx; + struct spi_transfer xfer[2] = { }; + struct spi_message msg; + int err; + + BUILD_BUG_ON(sizeof(buf_rx->cmd) != sizeof(__be16)); + BUILD_BUG_ON(sizeof(buf_tx->cmd) != sizeof(__be16)); + + if (IS_ENABLED(CONFIG_CAN_MCP251XFD_SANITY) && + reg_len != sizeof(buf_tx->cmd.cmd)) + return -EINVAL; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + + if (priv->devtype_data.quirks & MCP251XFD_QUIRK_HALF_DUPLEX) { + xfer[0].tx_buf = reg; + xfer[0].len = sizeof(buf_tx->cmd); + + xfer[1].rx_buf = val_buf; + xfer[1].len = val_len; + spi_message_add_tail(&xfer[1], &msg); + } else { + xfer[0].tx_buf = buf_tx; + xfer[0].rx_buf = buf_rx; + xfer[0].len = sizeof(buf_tx->cmd) + val_len; + + memcpy(&buf_tx->cmd, reg, sizeof(buf_tx->cmd)); + if (MCP251XFD_SANITIZE_SPI) + memset(buf_tx->data, 0x0, val_len); + } + + err = spi_sync(spi, &msg); + if (err) + return err; + + if (!(priv->devtype_data.quirks & MCP251XFD_QUIRK_HALF_DUPLEX)) + memcpy(val_buf, buf_rx->data, val_len); + + return 0; +} + +static int +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); + struct mcp251xfd_map_buf_crc *buf_tx = priv->map_buf_crc_tx; + struct spi_transfer xfer[] = { + { + .tx_buf = buf_tx, + .len = sizeof(buf_tx->cmd) + val_len + + sizeof(buf_tx->crc), + }, + }; + u16 reg = *(u16 *)reg_p; + u16 crc; + + BUILD_BUG_ON(sizeof(buf_tx->cmd) != sizeof(__be16) + sizeof(u8)); + + if (IS_ENABLED(CONFIG_CAN_MCP251XFD_SANITY) && + reg_len != sizeof(buf_tx->cmd.cmd) + + mcp251xfd_regmap_crc.pad_bits / BITS_PER_BYTE) + return -EINVAL; + + mcp251xfd_spi_cmd_write_crc(&buf_tx->cmd, reg, val_len); + memcpy(buf_tx->data, val, val_len); + + crc = mcp251xfd_crc16_compute(buf_tx, sizeof(buf_tx->cmd) + val_len); + put_unaligned_be16(crc, buf_tx->data + val_len); + + return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer)); +} + +static int +mcp251xfd_regmap_crc_write(void *context, + const void *data, size_t count) +{ + const size_t data_offset = sizeof(__be16) + + mcp251xfd_regmap_crc.pad_bits / BITS_PER_BYTE; + + return mcp251xfd_regmap_crc_gather_write(context, + data, data_offset, + data + data_offset, + count - data_offset); +} + +static int +mcp251xfd_regmap_crc_read_one(struct mcp251xfd_priv *priv, + struct spi_message *msg, unsigned int data_len) +{ + const struct mcp251xfd_map_buf_crc *buf_rx = priv->map_buf_crc_rx; + const struct mcp251xfd_map_buf_crc *buf_tx = priv->map_buf_crc_tx; + u16 crc_received, crc_calculated; + int err; + + BUILD_BUG_ON(sizeof(buf_rx->cmd) != sizeof(__be16) + sizeof(u8)); + BUILD_BUG_ON(sizeof(buf_tx->cmd) != sizeof(__be16) + sizeof(u8)); + + err = spi_sync(priv->spi, msg); + if (err) + return err; + + crc_received = get_unaligned_be16(buf_rx->data + data_len); + crc_calculated = mcp251xfd_crc16_compute2(&buf_tx->cmd, + sizeof(buf_tx->cmd), + buf_rx->data, + data_len); + if (crc_received != crc_calculated) + return -EBADMSG; + + return 0; +} + +static int +mcp251xfd_regmap_crc_read(void *context, + const void *reg_p, size_t reg_len, + void *val_buf, size_t val_len) +{ + struct spi_device *spi = context; + struct mcp251xfd_priv *priv = spi_get_drvdata(spi); + struct mcp251xfd_map_buf_crc *buf_rx = priv->map_buf_crc_rx; + struct mcp251xfd_map_buf_crc *buf_tx = priv->map_buf_crc_tx; + struct spi_transfer xfer[2] = { }; + struct spi_message msg; + u16 reg = *(u16 *)reg_p; + int i, err; + + BUILD_BUG_ON(sizeof(buf_rx->cmd) != sizeof(__be16) + sizeof(u8)); + BUILD_BUG_ON(sizeof(buf_tx->cmd) != sizeof(__be16) + sizeof(u8)); + + if (IS_ENABLED(CONFIG_CAN_MCP251XFD_SANITY) && + reg_len != sizeof(buf_tx->cmd.cmd) + + mcp251xfd_regmap_crc.pad_bits / BITS_PER_BYTE) + return -EINVAL; + + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + + if (priv->devtype_data.quirks & MCP251XFD_QUIRK_HALF_DUPLEX) { + xfer[0].tx_buf = buf_tx; + xfer[0].len = sizeof(buf_tx->cmd); + + xfer[1].rx_buf = buf_rx->data; + xfer[1].len = val_len + sizeof(buf_tx->crc); + spi_message_add_tail(&xfer[1], &msg); + } else { + xfer[0].tx_buf = buf_tx; + xfer[0].rx_buf = buf_rx; + xfer[0].len = sizeof(buf_tx->cmd) + val_len + + sizeof(buf_tx->crc); + + if (MCP251XFD_SANITIZE_SPI) + memset(buf_tx->data, 0x0, val_len + + sizeof(buf_tx->crc)); + } + + mcp251xfd_spi_cmd_read_crc(&buf_tx->cmd, reg, val_len); + + for (i = 0; i < MCP251XFD_READ_CRC_RETRIES_MAX; i++) { + err = mcp251xfd_regmap_crc_read_one(priv, &msg, val_len); + if (!err) + goto out; + if (err != -EBADMSG) + return err; + + /* MCP251XFD_REG_OSC is the first ever reg we read from. + * + * The chip may be in deep sleep and this SPI transfer + * (i.e. the assertion of the CS) will wake the chip + * up. This takes about 3ms. The CRC of this transfer + * is wrong. + * + * Or there isn't a chip at all, in this case the CRC + * will be wrong, too. + * + * In both cases ignore the CRC and copy the read data + * to the caller. It will take care of both cases. + * + */ + if (reg == MCP251XFD_REG_OSC) { + err = 0; + goto out; + } + + netdev_info(priv->ndev, + "CRC read error at address 0x%04x (length=%zd, data=%*ph, CRC=0x%04x) retrying.\n", + reg, val_len, (int)val_len, buf_rx->data, + get_unaligned_be16(buf_rx->data + val_len)); + } + + if (err) { + netdev_err(priv->ndev, + "CRC read error at address 0x%04x (length=%zd, data=%*ph, CRC=0x%04x).\n", + reg, val_len, (int)val_len, buf_rx->data, + get_unaligned_be16(buf_rx->data + val_len)); + + return err; + } + out: + memcpy(val_buf, buf_rx->data, val_len); + + return 0; +} + +static const struct regmap_range mcp251xfd_reg_table_yes_range[] = { + regmap_reg_range(0x000, 0x2ec), /* CAN FD Controller Module SFR */ + regmap_reg_range(0x400, 0xbfc), /* RAM */ + regmap_reg_range(0xe00, 0xe14), /* MCP2517/18FD SFR */ +}; + +static const struct regmap_access_table mcp251xfd_reg_table = { + .yes_ranges = mcp251xfd_reg_table_yes_range, + .n_yes_ranges = ARRAY_SIZE(mcp251xfd_reg_table_yes_range), +}; + +static const struct regmap_config mcp251xfd_regmap_nocrc = { + .name = "nocrc", + .reg_bits = 16, + .reg_stride = 4, + .pad_bits = 0, + .val_bits = 32, + .max_register = 0xffc, + .wr_table = &mcp251xfd_reg_table, + .rd_table = &mcp251xfd_reg_table, + .cache_type = REGCACHE_NONE, + .read_flag_mask = (__force unsigned long) + cpu_to_be16(MCP251XFD_SPI_INSTRUCTION_READ), + .write_flag_mask = (__force unsigned long) + cpu_to_be16(MCP251XFD_SPI_INSTRUCTION_WRITE), +}; + +static const struct regmap_bus mcp251xfd_bus_nocrc = { + .write = mcp251xfd_regmap_nocrc_write, + .gather_write = mcp251xfd_regmap_nocrc_gather_write, + .reg_update_bits = mcp251xfd_regmap_nocrc_update_bits, + .read = mcp251xfd_regmap_nocrc_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, + .max_raw_read = sizeof_field(struct mcp251xfd_map_buf_nocrc, data), + .max_raw_write = sizeof_field(struct mcp251xfd_map_buf_nocrc, data), +}; + +static const struct regmap_config mcp251xfd_regmap_crc = { + .name = "crc", + .reg_bits = 16, + .reg_stride = 4, + .pad_bits = 16, /* keep data bits aligned */ + .val_bits = 32, + .max_register = 0xffc, + .wr_table = &mcp251xfd_reg_table, + .rd_table = &mcp251xfd_reg_table, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_bus mcp251xfd_bus_crc = { + .write = mcp251xfd_regmap_crc_write, + .gather_write = mcp251xfd_regmap_crc_gather_write, + .read = mcp251xfd_regmap_crc_read, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, + .max_raw_read = sizeof_field(struct mcp251xfd_map_buf_crc, data), + .max_raw_write = sizeof_field(struct mcp251xfd_map_buf_crc, data), +}; + +static inline bool +mcp251xfd_regmap_use_nocrc(struct mcp251xfd_priv *priv) +{ + return (!(priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_REG)) || + (!(priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_RX)); +} + +static inline bool +mcp251xfd_regmap_use_crc(struct mcp251xfd_priv *priv) +{ + return (priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_REG) || + (priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_RX); +} + +static int +mcp251xfd_regmap_init_nocrc(struct mcp251xfd_priv *priv) +{ + if (!priv->map_nocrc) { + struct regmap *map; + + map = devm_regmap_init(&priv->spi->dev, &mcp251xfd_bus_nocrc, + priv->spi, &mcp251xfd_regmap_nocrc); + if (IS_ERR(map)) + return PTR_ERR(map); + + priv->map_nocrc = map; + } + + if (!priv->map_buf_nocrc_rx) { + priv->map_buf_nocrc_rx = + devm_kzalloc(&priv->spi->dev, + sizeof(*priv->map_buf_nocrc_rx), + GFP_KERNEL); + if (!priv->map_buf_nocrc_rx) + return -ENOMEM; + } + + if (!priv->map_buf_nocrc_tx) { + priv->map_buf_nocrc_tx = + devm_kzalloc(&priv->spi->dev, + sizeof(*priv->map_buf_nocrc_tx), + GFP_KERNEL); + if (!priv->map_buf_nocrc_tx) + return -ENOMEM; + } + + if (!(priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_REG)) + priv->map_reg = priv->map_nocrc; + + if (!(priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_RX)) + priv->map_rx = priv->map_nocrc; + + return 0; +} + +static void mcp251xfd_regmap_destroy_nocrc(struct mcp251xfd_priv *priv) +{ + if (priv->map_buf_nocrc_rx) { + devm_kfree(&priv->spi->dev, priv->map_buf_nocrc_rx); + priv->map_buf_nocrc_rx = NULL; + } + if (priv->map_buf_nocrc_tx) { + devm_kfree(&priv->spi->dev, priv->map_buf_nocrc_tx); + priv->map_buf_nocrc_tx = NULL; + } +} + +static int +mcp251xfd_regmap_init_crc(struct mcp251xfd_priv *priv) +{ + if (!priv->map_crc) { + struct regmap *map; + + map = devm_regmap_init(&priv->spi->dev, &mcp251xfd_bus_crc, + priv->spi, &mcp251xfd_regmap_crc); + if (IS_ERR(map)) + return PTR_ERR(map); + + priv->map_crc = map; + } + + if (!priv->map_buf_crc_rx) { + priv->map_buf_crc_rx = + devm_kzalloc(&priv->spi->dev, + sizeof(*priv->map_buf_crc_rx), + GFP_KERNEL); + if (!priv->map_buf_crc_rx) + return -ENOMEM; + } + + if (!priv->map_buf_crc_tx) { + priv->map_buf_crc_tx = + devm_kzalloc(&priv->spi->dev, + sizeof(*priv->map_buf_crc_tx), + GFP_KERNEL); + if (!priv->map_buf_crc_tx) + return -ENOMEM; + } + + if (priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_REG) + priv->map_reg = priv->map_crc; + + if (priv->devtype_data.quirks & MCP251XFD_QUIRK_CRC_RX) + priv->map_rx = priv->map_crc; + + return 0; +} + +static void mcp251xfd_regmap_destroy_crc(struct mcp251xfd_priv *priv) +{ + if (priv->map_buf_crc_rx) { + devm_kfree(&priv->spi->dev, priv->map_buf_crc_rx); + priv->map_buf_crc_rx = NULL; + } + if (priv->map_buf_crc_tx) { + devm_kfree(&priv->spi->dev, priv->map_buf_crc_tx); + priv->map_buf_crc_tx = NULL; + } +} + +int mcp251xfd_regmap_init(struct mcp251xfd_priv *priv) +{ + int err; + + if (mcp251xfd_regmap_use_nocrc(priv)) { + err = mcp251xfd_regmap_init_nocrc(priv); + + if (err) + return err; + } else { + mcp251xfd_regmap_destroy_nocrc(priv); + } + + if (mcp251xfd_regmap_use_crc(priv)) { + err = mcp251xfd_regmap_init_crc(priv); + + if (err) + return err; + } else { + mcp251xfd_regmap_destroy_crc(priv); + } + + return 0; +} |