hisi-spmi-controller.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spmi.h>
/*
* SPMI register addr
*/
#define SPMI_CHANNEL_OFFSET 0x0300
#define SPMI_SLAVE_OFFSET 0x20
#define SPMI_APB_SPMI_CMD_BASE_ADDR 0x0100
#define SPMI_APB_SPMI_WDATA0_BASE_ADDR 0x0104
#define SPMI_APB_SPMI_WDATA1_BASE_ADDR 0x0108
#define SPMI_APB_SPMI_WDATA2_BASE_ADDR 0x010c
#define SPMI_APB_SPMI_WDATA3_BASE_ADDR 0x0110
#define SPMI_APB_SPMI_STATUS_BASE_ADDR 0x0200
#define SPMI_APB_SPMI_RDATA0_BASE_ADDR 0x0204
#define SPMI_APB_SPMI_RDATA1_BASE_ADDR 0x0208
#define SPMI_APB_SPMI_RDATA2_BASE_ADDR 0x020c
#define SPMI_APB_SPMI_RDATA3_BASE_ADDR 0x0210
#define SPMI_PER_DATAREG_BYTE 4
/*
* SPMI cmd register
*/
#define SPMI_APB_SPMI_CMD_EN BIT(31)
#define SPMI_APB_SPMI_CMD_TYPE_OFFSET 24
#define SPMI_APB_SPMI_CMD_LENGTH_OFFSET 20
#define SPMI_APB_SPMI_CMD_SLAVEID_OFFSET 16
#define SPMI_APB_SPMI_CMD_ADDR_OFFSET 0
/* Command Opcodes */
enum spmi_controller_cmd_op_code {
SPMI_CMD_REG_ZERO_WRITE = 0,
SPMI_CMD_REG_WRITE = 1,
SPMI_CMD_REG_READ = 2,
SPMI_CMD_EXT_REG_WRITE = 3,
SPMI_CMD_EXT_REG_READ = 4,
SPMI_CMD_EXT_REG_WRITE_L = 5,
SPMI_CMD_EXT_REG_READ_L = 6,
SPMI_CMD_REG_RESET = 7,
SPMI_CMD_REG_SLEEP = 8,
SPMI_CMD_REG_SHUTDOWN = 9,
SPMI_CMD_REG_WAKEUP = 10,
};
/*
* SPMI status register
*/
#define SPMI_APB_TRANS_DONE BIT(0)
#define SPMI_APB_TRANS_FAIL BIT(2)
/* Command register fields */
#define SPMI_CONTROLLER_CMD_MAX_BYTE_COUNT 16
/* Maximum number of support PMIC peripherals */
#define SPMI_CONTROLLER_TIMEOUT_US 1000
#define SPMI_CONTROLLER_MAX_TRANS_BYTES 16
struct spmi_controller_dev {
struct spmi_controller *controller;
struct device *dev;
void __iomem *base;
spinlock_t lock;
u32 channel;
};
static int spmi_controller_wait_for_done(struct device *dev,
struct spmi_controller_dev *ctrl_dev,
void __iomem *base, u8 sid, u16 addr)
{
u32 timeout = SPMI_CONTROLLER_TIMEOUT_US;
u32 status, offset;
offset = SPMI_APB_SPMI_STATUS_BASE_ADDR;
offset += SPMI_CHANNEL_OFFSET * ctrl_dev->channel + SPMI_SLAVE_OFFSET * sid;
do {
status = readl(base + offset);
if (status & SPMI_APB_TRANS_DONE) {
if (status & SPMI_APB_TRANS_FAIL) {
dev_err(dev, "%s: transaction failed (0x%x)\n",
__func__, status);
return -EIO;
}
dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
return 0;
}
udelay(1);
} while (timeout--);
dev_err(dev, "%s: timeout, status 0x%x\n", __func__, status);
return -ETIMEDOUT;
}
static int spmi_read_cmd(struct spmi_controller *ctrl,
u8 opc, u8 slave_id, u16 slave_addr, u8 *__buf, size_t bc)
{
struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev);
u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel;
unsigned long flags;
u8 *buf = __buf;
u32 cmd, data;
int rc;
u8 op_code, i;
if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
dev_err(&ctrl->dev,
"spmi_controller supports 1..%d bytes per trans, but:%zu requested\n",
SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
return -EINVAL;
}
switch (opc) {
case SPMI_CMD_READ:
op_code = SPMI_CMD_REG_READ;
break;
case SPMI_CMD_EXT_READ:
op_code = SPMI_CMD_EXT_REG_READ;
break;
case SPMI_CMD_EXT_READL:
op_code = SPMI_CMD_EXT_REG_READ_L;
break;
default:
dev_err(&ctrl->dev, "invalid read cmd 0x%x\n", opc);
return -EINVAL;
}
cmd = SPMI_APB_SPMI_CMD_EN |
(op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |
((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |
((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) | /* slvid */
((slave_addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET); /* slave_addr */
spin_lock_irqsave(&spmi_controller->lock, flags);
writel(cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR);
rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller,
spmi_controller->base, slave_id, slave_addr);
if (rc)
goto done;
for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) {
data = readl(spmi_controller->base + chnl_ofst +
SPMI_SLAVE_OFFSET * slave_id +
SPMI_APB_SPMI_RDATA0_BASE_ADDR +
i * SPMI_PER_DATAREG_BYTE);
data = be32_to_cpu((__be32 __force)data);
if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) {
memcpy(buf, &data, sizeof(data));
buf += sizeof(data);
} else {
memcpy(buf, &data, bc % SPMI_PER_DATAREG_BYTE);
buf += (bc % SPMI_PER_DATAREG_BYTE);
}
}
done:
spin_unlock_irqrestore(&spmi_controller->lock, flags);
if (rc)
dev_err(&ctrl->dev,
"spmi read wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n",
opc, slave_id, slave_addr, bc + 1);
else
dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, read value: %*ph\n",
__func__, slave_id, slave_addr, (int)bc, __buf);
return rc;
}
static int spmi_write_cmd(struct spmi_controller *ctrl,
u8 opc, u8 slave_id, u16 slave_addr, const u8 *__buf, size_t bc)
{
struct spmi_controller_dev *spmi_controller = dev_get_drvdata(&ctrl->dev);
u32 chnl_ofst = SPMI_CHANNEL_OFFSET * spmi_controller->channel;
const u8 *buf = __buf;
unsigned long flags;
u32 cmd, data;
int rc;
u8 op_code, i;
if (bc > SPMI_CONTROLLER_MAX_TRANS_BYTES) {
dev_err(&ctrl->dev,
"spmi_controller supports 1..%d bytes per trans, but:%zu requested\n",
SPMI_CONTROLLER_MAX_TRANS_BYTES, bc);
return -EINVAL;
}
switch (opc) {
case SPMI_CMD_WRITE:
op_code = SPMI_CMD_REG_WRITE;
break;
case SPMI_CMD_EXT_WRITE:
op_code = SPMI_CMD_EXT_REG_WRITE;
break;
case SPMI_CMD_EXT_WRITEL:
op_code = SPMI_CMD_EXT_REG_WRITE_L;
break;
default:
dev_err(&ctrl->dev, "invalid write cmd 0x%x\n", opc);
return -EINVAL;
}
cmd = SPMI_APB_SPMI_CMD_EN |
(op_code << SPMI_APB_SPMI_CMD_TYPE_OFFSET) |
((bc - 1) << SPMI_APB_SPMI_CMD_LENGTH_OFFSET) |
((slave_id & 0xf) << SPMI_APB_SPMI_CMD_SLAVEID_OFFSET) |
((slave_addr & 0xffff) << SPMI_APB_SPMI_CMD_ADDR_OFFSET);
/* Write data to FIFOs */
spin_lock_irqsave(&spmi_controller->lock, flags);
for (i = 0; bc > i * SPMI_PER_DATAREG_BYTE; i++) {
data = 0;
if ((bc - i * SPMI_PER_DATAREG_BYTE) >> 2) {
memcpy(&data, buf, sizeof(data));
buf += sizeof(data);
} else {
memcpy(&data, buf, bc % SPMI_PER_DATAREG_BYTE);
buf += (bc % SPMI_PER_DATAREG_BYTE);
}
writel((u32 __force)cpu_to_be32(data),
spmi_controller->base + chnl_ofst +
SPMI_APB_SPMI_WDATA0_BASE_ADDR +
SPMI_PER_DATAREG_BYTE * i);
}
/* Start the transaction */
writel(cmd, spmi_controller->base + chnl_ofst + SPMI_APB_SPMI_CMD_BASE_ADDR);
rc = spmi_controller_wait_for_done(&ctrl->dev, spmi_controller,
spmi_controller->base, slave_id,
slave_addr);
spin_unlock_irqrestore(&spmi_controller->lock, flags);
if (rc)
dev_err(&ctrl->dev, "spmi write wait timeout op:0x%x slave_id:%d slave_addr:0x%x bc:%zu\n",
opc, slave_id, slave_addr, bc);
else
dev_dbg(&ctrl->dev, "%s: id:%d slave_addr:0x%x, wrote value: %*ph\n",
__func__, slave_id, slave_addr, (int)bc, __buf);
return rc;
}
static int spmi_controller_probe(struct platform_device *pdev)
{
struct spmi_controller_dev *spmi_controller;
struct spmi_controller *ctrl;
struct resource *iores;
int ret;
ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*spmi_controller));
if (!ctrl) {
dev_err(&pdev->dev, "can not allocate spmi_controller data\n");
return -ENOMEM;
}
spmi_controller = spmi_controller_get_drvdata(ctrl);
spmi_controller->controller = ctrl;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores) {
dev_err(&pdev->dev, "can not get resource!\n");
ret = -EINVAL;
goto err_put_controller;
}
spmi_controller->base = devm_ioremap(&pdev->dev, iores->start,
resource_size(iores));
if (!spmi_controller->base) {
dev_err(&pdev->dev, "can not remap base addr!\n");
ret = -EADDRNOTAVAIL;
goto err_put_controller;
}
ret = of_property_read_u32(pdev->dev.of_node, "hisilicon,spmi-channel",
&spmi_controller->channel);
if (ret) {
dev_err(&pdev->dev, "can not get channel\n");
ret = -ENODEV;
goto err_put_controller;
}
platform_set_drvdata(pdev, spmi_controller);
dev_set_drvdata(&ctrl->dev, spmi_controller);
spin_lock_init(&spmi_controller->lock);
ctrl->nr = spmi_controller->channel;
ctrl->dev.parent = pdev->dev.parent;
ctrl->dev.of_node = of_node_get(pdev->dev.of_node);
/* Callbacks */
ctrl->read_cmd = spmi_read_cmd;
ctrl->write_cmd = spmi_write_cmd;
ret = spmi_controller_add(ctrl);
if (ret) {
dev_err(&pdev->dev, "spmi_controller_add failed with error %d!\n", ret);
goto err_put_controller;
}
return 0;
err_put_controller:
spmi_controller_put(ctrl);
return ret;
}
static int spmi_del_controller(struct platform_device *pdev)
{
struct spmi_controller *ctrl = platform_get_drvdata(pdev);
spmi_controller_remove(ctrl);
spmi_controller_put(ctrl);
return 0;
}
static const struct of_device_id spmi_controller_match_table[] = {
{
.compatible = "hisilicon,kirin970-spmi-controller",
},
{}
};
MODULE_DEVICE_TABLE(of, spmi_controller_match_table);
static struct platform_driver spmi_controller_driver = {
.probe = spmi_controller_probe,
.remove = spmi_del_controller,
.driver = {
.name = "hisi_spmi_controller",
.of_match_table = spmi_controller_match_table,
},
};
static int __init spmi_controller_init(void)
{
return platform_driver_register(&spmi_controller_driver);
}
postcore_initcall(spmi_controller_init);
static void __exit spmi_controller_exit(void)
{
platform_driver_unregister(&spmi_controller_driver);
}
module_exit(spmi_controller_exit);
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");
MODULE_ALIAS("platform:spmi_controller");