Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ba15fa75 authored by Yimin Peng's avatar Yimin Peng
Browse files

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: default avatarYimin Peng <yiminp@codeaurora.org>
parent 989e608b
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
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>;
	};
+9 −0
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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
+368 −0
Original line number Diff line number Diff line
/*
 * 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 <linux/bitmap.h>
#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/slab.h>
#include <linux/spmi.h>

/* 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");