linux-stable/drivers/mfd/rk8xx-spi.c
Sebastian Reichel 210f418f8a mfd: rk8xx: Add rk806 support
Add support for SPI connected rk806, which is used by the RK3588
evaluation boards. The PMIC is advertised to support I2C and SPI,
but the evaluation boards all use SPI. Thus only SPI support is
added here.

Tested-by: Diederik de Haas <didi.debian@cknow.org> # Rock64, Quartz64 Model A + B
Tested-by: Vincent Legoll <vincent.legoll@gmail.com> # Pine64 QuartzPro64
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Link: https://lore.kernel.org/r/20230504173618.142075-9-sebastian.reichel@collabora.com
Signed-off-by: Lee Jones <lee@kernel.org>
2023-05-15 16:20:22 +01:00

124 lines
3.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Rockchip RK806 Core (SPI) driver
*
* Copyright (c) 2021 Rockchip Electronics Co., Ltd.
* Copyright (c) 2023 Collabora Ltd.
*
* Author: Xu Shengfei <xsf@rock-chips.com>
* Author: Sebastian Reichel <sebastian.reichel@collabora.com>
*/
#include <linux/interrupt.h>
#include <linux/mfd/core.h>
#include <linux/mfd/rk808.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#define RK806_ADDR_SIZE 2
#define RK806_CMD_WITH_SIZE(CMD, VALUE_BYTES) \
(RK806_CMD_##CMD | RK806_CMD_CRC_DIS | (VALUE_BYTES - 1))
static const struct regmap_range rk806_volatile_ranges[] = {
regmap_reg_range(RK806_POWER_EN0, RK806_POWER_EN5),
regmap_reg_range(RK806_DVS_START_CTRL, RK806_INT_MSK1),
};
static const struct regmap_access_table rk806_volatile_table = {
.yes_ranges = rk806_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(rk806_volatile_ranges),
};
static const struct regmap_config rk806_regmap_config_spi = {
.reg_bits = 16,
.val_bits = 8,
.max_register = RK806_BUCK_RSERVE_REG5,
.cache_type = REGCACHE_RBTREE,
.volatile_table = &rk806_volatile_table,
};
static int rk806_spi_bus_write(void *context, const void *vdata, size_t count)
{
struct device *dev = context;
struct spi_device *spi = to_spi_device(dev);
struct spi_transfer xfer[2] = { 0 };
/* data and thus count includes the register address */
size_t val_size = count - RK806_ADDR_SIZE;
char cmd;
if (val_size < 1 || val_size > (RK806_CMD_LEN_MSK + 1))
return -EINVAL;
cmd = RK806_CMD_WITH_SIZE(WRITE, val_size);
xfer[0].tx_buf = &cmd;
xfer[0].len = sizeof(cmd);
xfer[1].tx_buf = vdata;
xfer[1].len = count;
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
}
static int rk806_spi_bus_read(void *context, const void *vreg, size_t reg_size,
void *val, size_t val_size)
{
struct device *dev = context;
struct spi_device *spi = to_spi_device(dev);
char txbuf[3] = { 0 };
if (reg_size != RK806_ADDR_SIZE ||
val_size < 1 || val_size > (RK806_CMD_LEN_MSK + 1))
return -EINVAL;
/* TX buffer contains command byte followed by two address bytes */
txbuf[0] = RK806_CMD_WITH_SIZE(READ, val_size);
memcpy(txbuf+1, vreg, reg_size);
return spi_write_then_read(spi, txbuf, sizeof(txbuf), val, val_size);
}
static const struct regmap_bus rk806_regmap_bus_spi = {
.write = rk806_spi_bus_write,
.read = rk806_spi_bus_read,
.reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
};
static int rk8xx_spi_probe(struct spi_device *spi)
{
struct regmap *regmap;
regmap = devm_regmap_init(&spi->dev, &rk806_regmap_bus_spi,
&spi->dev, &rk806_regmap_config_spi);
if (IS_ERR(regmap))
return dev_err_probe(&spi->dev, PTR_ERR(regmap),
"Failed to init regmap\n");
return rk8xx_probe(&spi->dev, RK806_ID, spi->irq, regmap);
}
static const struct of_device_id rk8xx_spi_of_match[] = {
{ .compatible = "rockchip,rk806", },
{ }
};
MODULE_DEVICE_TABLE(of, rk8xx_spi_of_match);
static const struct spi_device_id rk8xx_spi_id_table[] = {
{ "rk806", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, rk8xx_spi_id_table);
static struct spi_driver rk8xx_spi_driver = {
.driver = {
.name = "rk8xx-spi",
.of_match_table = rk8xx_spi_of_match,
},
.probe = rk8xx_spi_probe,
.id_table = rk8xx_spi_id_table,
};
module_spi_driver(rk8xx_spi_driver);
MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>");
MODULE_DESCRIPTION("RK8xx SPI PMIC driver");
MODULE_LICENSE("GPL");