From ba15fa752d09b86607660546117af09e575f47c9 Mon Sep 17 00:00:00 2001 From: Yimin Peng Date: Thu, 8 Feb 2018 16:23:18 +0800 Subject: [PATCH] spmi: msm: Add virtual SPMI PMIC front end driver Add basic infrastructure for SPMI front end driver. Frontend driver communicates with backend to service SPMI request from clients. Change-Id: Icff567a2f224fb49f370bf760287d9d3615a3325 Signed-off-by: Yimin Peng --- .../bindings/spmi/qcom,virtspmi-pmic-arb.txt | 24 ++ drivers/spmi/Kconfig | 9 + drivers/spmi/Makefile | 2 + drivers/spmi/virtspmi-pmic-arb.c | 368 ++++++++++++++++++ 4 files changed, 403 insertions(+) create mode 100644 Documentation/devicetree/bindings/spmi/qcom,virtspmi-pmic-arb.txt create mode 100644 drivers/spmi/virtspmi-pmic-arb.c diff --git a/Documentation/devicetree/bindings/spmi/qcom,virtspmi-pmic-arb.txt b/Documentation/devicetree/bindings/spmi/qcom,virtspmi-pmic-arb.txt new file mode 100644 index 000000000000..cd9fbe3a1a5c --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/qcom,virtspmi-pmic-arb.txt @@ -0,0 +1,24 @@ +QTI Virtual SPMI controller (Virtual PMIC Arbiter) + +The Virtual SPMI PMIC Arbiter is a frontend proxy based on backend virtual device. + +Required properties: +- compatible : should be "qcom,virtspmi-pmic-arb". +- reg-names : must contain: + "core" - core registers + +- reg : address + size pairs describing the Virtual PMIC arb register sets; + order must correspond with the order of entries in reg-names +- #address-cells : must be set to 2 +- #size-cells : must be set to 0 + +Example Virtual PMIC-Arbiter: + + spmi { + compatible = "qcom,virtspmi-pmic-arb"; + reg-names = "core"; + reg = <0xfc4cf000 0x1000>; + + #address-cells = <2>; + #size-cells = <0>; + }; diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig index 0d3b70b3bda8..a2fd1ce6bf14 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig @@ -24,4 +24,13 @@ config SPMI_MSM_PMIC_ARB This is required for communicating with Qualcomm PMICs and other devices that have the SPMI interface. +config VIRTSPMI_MSM_PMIC_ARB + tristate "QTI MSM Virtual SPMI Controller (Virtual PMIC Arbiter)" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAS_IOMEM + default ARCH_QCOM + help + This is a virtual SPMI frontend driver for QTI MSM virtual + platform. + endif diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile index fc75104a5aab..0355be22e630 100644 --- a/drivers/spmi/Makefile +++ b/drivers/spmi/Makefile @@ -4,3 +4,5 @@ obj-$(CONFIG_SPMI) += spmi.o obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o + +obj-$(CONFIG_VIRTSPMI_MSM_PMIC_ARB) += virtspmi-pmic-arb.o diff --git a/drivers/spmi/virtspmi-pmic-arb.c b/drivers/spmi/virtspmi-pmic-arb.c new file mode 100644 index 000000000000..59fc76149ba0 --- /dev/null +++ b/drivers/spmi/virtspmi-pmic-arb.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PMIC Arbiter configuration registers */ +#define VPMIC_ARB_VERSION 0x0000 + +/* Virtual PMIC Arbiter registers offset*/ +#define VPMIC_ARB_CMD 0x00 +#define VPMIC_ARB_STATUS 0x04 +#define VPMIC_ARB_DATA0 0x08 +#define VPMIC_ARB_DATA1 0x10 + +/* Channel Status fields */ +enum pmic_arb_chnl_status { + PMIC_ARB_STATUS_DONE = BIT(0), + PMIC_ARB_STATUS_FAILURE = BIT(1), + PMIC_ARB_STATUS_DENIED = BIT(2), + PMIC_ARB_STATUS_DROPPED = BIT(3), +}; + +/* Command register fields */ +#define PMIC_ARB_CMD_MAX_BYTE_COUNT 8 + +/* Command Opcodes */ +enum pmic_arb_cmd_op_code { + PMIC_ARB_OP_EXT_WRITEL = 0, + PMIC_ARB_OP_EXT_READL = 1, + PMIC_ARB_OP_EXT_WRITE = 2, + PMIC_ARB_OP_RESET = 3, + PMIC_ARB_OP_SLEEP = 4, + PMIC_ARB_OP_SHUTDOWN = 5, + PMIC_ARB_OP_WAKEUP = 6, + PMIC_ARB_OP_AUTHENTICATE = 7, + PMIC_ARB_OP_MSTR_READ = 8, + PMIC_ARB_OP_MSTR_WRITE = 9, + PMIC_ARB_OP_EXT_READ = 13, + PMIC_ARB_OP_WRITE = 14, + PMIC_ARB_OP_READ = 15, + PMIC_ARB_OP_ZERO_WRITE = 16, +}; + +/* + * PMIC arbiter version 5 uses different register offsets for read/write vs + * observer channels. + */ +enum pmic_arb_channel { + PMIC_ARB_CHANNEL_RW, + PMIC_ARB_CHANNEL_OBS, +}; + +/* Maximum number of support PMIC peripherals */ +#define PMIC_ARB_MAX_PERIPHS 512 +#define PMIC_ARB_TIMEOUT_US 100 +#define PMIC_ARB_MAX_TRANS_BYTES (8) + +struct vspmi_backend_driver_ver_ops; + +/** + * vspmi_pmic_arb - Virtual SPMI PMIC Arbiter object + * + * @lock: lock to synchronize accesses. + * @spmic: SPMI controller object + * @ver_ops: backend version dependent operations. + */ +struct vspmi_pmic_arb { + void __iomem *core; + resource_size_t core_size; + raw_spinlock_t lock; + struct spmi_controller *spmic; + const struct vspmi_backend_driver_ver_ops *ver_ops; +}; +static struct vspmi_pmic_arb *the_pa; + +/** + * pmic_arb_ver: version dependent functionality. + * + * @ver_str: version string. + * @fmt_cmd: formats a GENI/SPMI command. + */ +struct vspmi_backend_driver_ver_ops { + const char *ver_str; + u32 (*fmt_cmd)(u8 opc, u8 sid, u16 addr, u8 bc); +}; + +/** + * vspmi_pa_read_data: reads vspmi backend's register and copy 1..4 bytes to buf + * @bc: byte count -1. range: 0..3 + * @reg: register's address + * @buf: output parameter, length must be bc + 1 + */ +static void +vspmi_pa_read_data(struct vspmi_pmic_arb *pa, u8 *buf, u32 reg, u8 bc) +{ + u32 data = __raw_readl(pa->core + reg); + + memcpy(buf, &data, (bc & 3) + 1); +} + +/** + * vspmi_pa_write_data: write 1..4 bytes from buf to vspmi backend's register + * @bc: byte-count -1. range: 0..3. + * @reg: register's address. + * @buf: buffer to write. length must be bc + 1. + */ +static void +vspmi_pa_write_data(struct vspmi_pmic_arb *pa, const u8 *buf, u32 reg, u8 bc) +{ + u32 data = 0; + + memcpy(&data, buf, (bc & 3) + 1); + writel_relaxed(data, pa->core + reg); +} + +static int vspmi_pmic_arb_wait_for_done(struct spmi_controller *ctrl, + void __iomem *base, u8 sid, u16 addr, + enum pmic_arb_channel ch_type) +{ + u32 status = 0; + u32 timeout = PMIC_ARB_TIMEOUT_US; + u32 offset; + + offset = VPMIC_ARB_STATUS; + + while (timeout--) { + status = readl_relaxed(base + offset); + + if (status & PMIC_ARB_STATUS_DONE) { + if (status & PMIC_ARB_STATUS_DENIED) { + dev_err(&ctrl->dev, + "%s: transaction denied (0x%x)\n", + __func__, status); + return -EPERM; + } + + if (status & PMIC_ARB_STATUS_FAILURE) { + dev_err(&ctrl->dev, + "%s: transaction failed (0x%x)\n", + __func__, status); + return -EIO; + } + + if (status & PMIC_ARB_STATUS_DROPPED) { + dev_err(&ctrl->dev, + "%s: transaction dropped (0x%x)\n", + __func__, status); + return -EIO; + } + + return 0; + } + udelay(1); + } + + dev_err(&ctrl->dev, + "%s: timeout, status 0x%x\n", + __func__, status); + return -ETIMEDOUT; +} + +static int vspmi_pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, + u16 addr, u8 *buf, size_t len) +{ + struct vspmi_pmic_arb *pa = spmi_controller_get_drvdata(ctrl); + unsigned long flags; + u8 bc = len; + u32 cmd; + int rc; + + if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { + dev_err(&ctrl->dev, + "pmic-arb supports 1..%d bytes per trans, but:%zu requested", + PMIC_ARB_MAX_TRANS_BYTES, len); + return -EINVAL; + } + + /* Check the opcode */ + if (opc >= 0x60 && opc <= 0x7F) + opc = PMIC_ARB_OP_READ; + else if (opc >= 0x20 && opc <= 0x2F) + opc = PMIC_ARB_OP_EXT_READ; + else if (opc >= 0x38 && opc <= 0x3F) + opc = PMIC_ARB_OP_EXT_READL; + else + return -EINVAL; + + cmd = pa->ver_ops->fmt_cmd(opc, sid, addr, bc); + + raw_spin_lock_irqsave(&pa->lock, flags); + writel_relaxed(cmd, pa->core + VPMIC_ARB_CMD); + rc = vspmi_pmic_arb_wait_for_done(ctrl, pa->core, sid, addr, + PMIC_ARB_CHANNEL_OBS); + if (rc) + goto done; + + vspmi_pa_read_data(pa, buf, VPMIC_ARB_DATA0, min_t(u8, bc, 3)); + + if (bc > 3) + vspmi_pa_read_data(pa, buf + 4, VPMIC_ARB_DATA1, bc - 4); + +done: + raw_spin_unlock_irqrestore(&pa->lock, flags); + return rc; +} + +static int vspmi_pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, + u8 sid, u16 addr, const u8 *buf, size_t len) +{ + struct vspmi_pmic_arb *pa = spmi_controller_get_drvdata(ctrl); + unsigned long flags; + u8 bc = len; + u32 cmd; + int rc; + + if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { + dev_err(&ctrl->dev, + "pmic-arb supports 1..%d bytes per trans, but:%zu requested", + PMIC_ARB_MAX_TRANS_BYTES, len); + return -EINVAL; + } + + /* Check the opcode */ + if (opc >= 0x40 && opc <= 0x5F) + opc = PMIC_ARB_OP_WRITE; + else if (opc <= 0x0F) + opc = PMIC_ARB_OP_EXT_WRITE; + else if (opc >= 0x30 && opc <= 0x37) + opc = PMIC_ARB_OP_EXT_WRITEL; + else if (opc >= 0x80) + opc = PMIC_ARB_OP_ZERO_WRITE; + else + return -EINVAL; + + cmd = pa->ver_ops->fmt_cmd(opc, sid, addr, bc); + + /* Write data to FIFOs */ + raw_spin_lock_irqsave(&pa->lock, flags); + vspmi_pa_write_data(pa, buf, VPMIC_ARB_DATA0, min_t(u8, bc, 3)); + if (bc > 3) + vspmi_pa_write_data(pa, buf + 4, VPMIC_ARB_DATA1, bc - 4); + + /* Start the transaction */ + writel_relaxed(cmd, pa->core + VPMIC_ARB_CMD); + rc = vspmi_pmic_arb_wait_for_done(ctrl, pa->core, sid, addr, + PMIC_ARB_CHANNEL_RW); + raw_spin_unlock_irqrestore(&pa->lock, flags); + + return rc; +} + +static u32 vspmi_pmic_arb_fmt_cmd_v1(u8 opc, u8 sid, u16 addr, u8 bc) +{ + return (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); +} + +static const struct vspmi_backend_driver_ver_ops pmic_arb_v1 = { + .ver_str = "v1", + .fmt_cmd = vspmi_pmic_arb_fmt_cmd_v1, +}; + +static int vspmi_pmic_arb_probe(struct platform_device *pdev) +{ + struct vspmi_pmic_arb *pa; + struct spmi_controller *ctrl; + struct resource *res; + u32 backend_ver; + int err; + + ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*pa)); + if (!ctrl) + return -ENOMEM; + + pa = spmi_controller_get_drvdata(ctrl); + pa->spmic = ctrl; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!res) { + dev_err(&pdev->dev, "vdev resource not specified\n"); + err = -EINVAL; + goto err_put_ctrl; + } + + pa->core = devm_ioremap_resource(&ctrl->dev, res); + if (IS_ERR(pa->core)) { + err = PTR_ERR(pa->core); + goto err_put_ctrl; + } + pa->core_size = resource_size(res); + + backend_ver = VPMIC_ARB_VERSION; + + if (backend_ver == VPMIC_ARB_VERSION) + pa->ver_ops = &pmic_arb_v1; + + dev_info(&ctrl->dev, "PMIC arbiter version %s (0x%x)\n", + pa->ver_ops->ver_str, backend_ver); + + platform_set_drvdata(pdev, ctrl); + raw_spin_lock_init(&pa->lock); + + ctrl->read_cmd = vspmi_pmic_arb_read_cmd; + ctrl->write_cmd = vspmi_pmic_arb_write_cmd; + + err = spmi_controller_add(ctrl); + if (err) + goto err_put_ctrl; + + the_pa = pa; + return 0; + +err_put_ctrl: + spmi_controller_put(ctrl); + return err; +} + +static int vspmi_pmic_arb_remove(struct platform_device *pdev) +{ + struct spmi_controller *ctrl = platform_get_drvdata(pdev); + + spmi_controller_remove(ctrl); + the_pa = NULL; + spmi_controller_put(ctrl); + return 0; +} + +static const struct of_device_id vspmi_pmic_arb_match_table[] = { + { .compatible = "qcom,virtspmi-pmic-arb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, vspmi_pmic_arb_match_table); + +static struct platform_driver vspmi_pmic_arb_driver = { + .probe = vspmi_pmic_arb_probe, + .remove = vspmi_pmic_arb_remove, + .driver = { + .name = "virtspmi_pmic_arb", + .of_match_table = vspmi_pmic_arb_match_table, + }, +}; + +static int __init vspmi_pmic_arb_init(void) +{ + return platform_driver_register(&vspmi_pmic_arb_driver); +} +arch_initcall(vspmi_pmic_arb_init); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:virtspmi_pmic_arb");