162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * SPI access driver for TI TPS6594/TPS6593/LP8764 PMICs
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2023 BayLibre Incorporated - https://www.baylibre.com/
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/crc8.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1162306a36Sopenharmony_ci#include <linux/of_device.h>
1262306a36Sopenharmony_ci#include <linux/regmap.h>
1362306a36Sopenharmony_ci#include <linux/spi/spi.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/mfd/tps6594.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define TPS6594_SPI_PAGE_SHIFT	5
1862306a36Sopenharmony_ci#define TPS6594_SPI_READ_BIT	BIT(4)
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic bool enable_crc;
2162306a36Sopenharmony_cimodule_param(enable_crc, bool, 0444);
2262306a36Sopenharmony_ciMODULE_PARM_DESC(enable_crc, "Enable CRC feature for SPI interface");
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ciDECLARE_CRC8_TABLE(tps6594_spi_crc_table);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic int tps6594_spi_reg_read(void *context, unsigned int reg, unsigned int *val)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct spi_device *spi = context;
2962306a36Sopenharmony_ci	struct tps6594 *tps = spi_get_drvdata(spi);
3062306a36Sopenharmony_ci	u8 buf[4] = { 0 };
3162306a36Sopenharmony_ci	size_t count_rx = 1;
3262306a36Sopenharmony_ci	int ret;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	buf[0] = reg;
3562306a36Sopenharmony_ci	buf[1] = TPS6594_REG_TO_PAGE(reg) << TPS6594_SPI_PAGE_SHIFT | TPS6594_SPI_READ_BIT;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (tps->use_crc)
3862306a36Sopenharmony_ci		count_rx++;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	ret = spi_write_then_read(spi, buf, 2, buf + 2, count_rx);
4162306a36Sopenharmony_ci	if (ret < 0)
4262306a36Sopenharmony_ci		return ret;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	if (tps->use_crc && buf[3] != crc8(tps6594_spi_crc_table, buf, 3, CRC8_INIT_VALUE))
4562306a36Sopenharmony_ci		return -EIO;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	*val = buf[2];
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	return 0;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int tps6594_spi_reg_write(void *context, unsigned int reg, unsigned int val)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct spi_device *spi = context;
5562306a36Sopenharmony_ci	struct tps6594 *tps = spi_get_drvdata(spi);
5662306a36Sopenharmony_ci	u8 buf[4] = { 0 };
5762306a36Sopenharmony_ci	size_t count = 3;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	buf[0] = reg;
6062306a36Sopenharmony_ci	buf[1] = TPS6594_REG_TO_PAGE(reg) << TPS6594_SPI_PAGE_SHIFT;
6162306a36Sopenharmony_ci	buf[2] = val;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (tps->use_crc)
6462306a36Sopenharmony_ci		buf[3] = crc8(tps6594_spi_crc_table, buf, count++, CRC8_INIT_VALUE);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return spi_write(spi, buf, count);
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic const struct regmap_config tps6594_spi_regmap_config = {
7062306a36Sopenharmony_ci	.reg_bits = 16,
7162306a36Sopenharmony_ci	.val_bits = 8,
7262306a36Sopenharmony_ci	.max_register = TPS6594_REG_DWD_FAIL_CNT_REG,
7362306a36Sopenharmony_ci	.volatile_reg = tps6594_is_volatile_reg,
7462306a36Sopenharmony_ci	.reg_read = tps6594_spi_reg_read,
7562306a36Sopenharmony_ci	.reg_write = tps6594_spi_reg_write,
7662306a36Sopenharmony_ci	.use_single_read = true,
7762306a36Sopenharmony_ci	.use_single_write = true,
7862306a36Sopenharmony_ci};
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic const struct of_device_id tps6594_spi_of_match_table[] = {
8162306a36Sopenharmony_ci	{ .compatible = "ti,tps6594-q1", .data = (void *)TPS6594, },
8262306a36Sopenharmony_ci	{ .compatible = "ti,tps6593-q1", .data = (void *)TPS6593, },
8362306a36Sopenharmony_ci	{ .compatible = "ti,lp8764-q1",  .data = (void *)LP8764,  },
8462306a36Sopenharmony_ci	{}
8562306a36Sopenharmony_ci};
8662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tps6594_spi_of_match_table);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int tps6594_spi_probe(struct spi_device *spi)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	struct device *dev = &spi->dev;
9162306a36Sopenharmony_ci	struct tps6594 *tps;
9262306a36Sopenharmony_ci	const struct of_device_id *match;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	tps = devm_kzalloc(dev, sizeof(*tps), GFP_KERNEL);
9562306a36Sopenharmony_ci	if (!tps)
9662306a36Sopenharmony_ci		return -ENOMEM;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	spi_set_drvdata(spi, tps);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	tps->dev = dev;
10162306a36Sopenharmony_ci	tps->reg = spi->chip_select;
10262306a36Sopenharmony_ci	tps->irq = spi->irq;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	tps->regmap = devm_regmap_init(dev, NULL, spi, &tps6594_spi_regmap_config);
10562306a36Sopenharmony_ci	if (IS_ERR(tps->regmap))
10662306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(tps->regmap), "Failed to init regmap\n");
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	match = of_match_device(tps6594_spi_of_match_table, dev);
10962306a36Sopenharmony_ci	if (!match)
11062306a36Sopenharmony_ci		return dev_err_probe(dev, -EINVAL, "Failed to find matching chip ID\n");
11162306a36Sopenharmony_ci	tps->chip_id = (unsigned long)match->data;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	crc8_populate_msb(tps6594_spi_crc_table, TPS6594_CRC8_POLYNOMIAL);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return tps6594_device_init(tps, enable_crc);
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic struct spi_driver tps6594_spi_driver = {
11962306a36Sopenharmony_ci	.driver	= {
12062306a36Sopenharmony_ci		.name = "tps6594",
12162306a36Sopenharmony_ci		.of_match_table = tps6594_spi_of_match_table,
12262306a36Sopenharmony_ci	},
12362306a36Sopenharmony_ci	.probe = tps6594_spi_probe,
12462306a36Sopenharmony_ci};
12562306a36Sopenharmony_cimodule_spi_driver(tps6594_spi_driver);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ciMODULE_AUTHOR("Julien Panis <jpanis@baylibre.com>");
12862306a36Sopenharmony_ciMODULE_DESCRIPTION("TPS6594 SPI Interface Driver");
12962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
130