@@ -16,6 +16,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spmi.h>
+#include <linux/soc/qcom/spmi-pmic-arb.h>
/* PMIC Arbiter configuration registers */
#define PMIC_ARB_VERSION 0x0000
@@ -124,6 +125,8 @@ struct apid_data {
*
* @rd_base: on v1 "core", on v2 "observer" register base off DT.
* @wr_base: on v1 "core", on v2 "chnls" register base off DT.
+ * @wr_base_phys: base physical address of the register range used for
+ * SPMI write commands.
* @intr: address of the SPMI interrupt control registers.
* @cnfg: address of the PMIC Arbiter configuration registers.
* @lock: lock to synchronize accesses.
@@ -141,6 +144,7 @@ struct apid_data {
struct spmi_pmic_arb {
void __iomem *rd_base;
void __iomem *wr_base;
+ phys_addr_t wr_base_phys;
void __iomem *intr;
void __iomem *cnfg;
void __iomem *core;
@@ -180,6 +184,9 @@ struct spmi_pmic_arb {
* @irq_clear: on v1 address of PMIC_ARB_SPMI_PIC_IRQ_CLEARn
* on v2 address of SPMI_PIC_IRQ_CLEARn.
* @apid_map_offset: offset of PMIC_ARB_REG_CHNLn
+ * @wr_addr_map: maps from an SPMI address to the physical address
+ * range of the registers used to perform an SPMI write
+ * command to the SPMI address.
*/
struct pmic_arb_ver_ops {
const char *ver_str;
@@ -196,6 +203,8 @@ struct pmic_arb_ver_ops {
void __iomem *(*irq_status)(struct spmi_pmic_arb *pmic_arb, u16 n);
void __iomem *(*irq_clear)(struct spmi_pmic_arb *pmic_arb, u16 n);
u32 (*apid_map_offset)(u16 n);
+ int (*wr_addr_map)(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
+ struct resource *res_out);
};
static inline void pmic_arb_base_write(struct spmi_pmic_arb *pmic_arb,
@@ -972,6 +981,21 @@ static int pmic_arb_offset_v1(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
return 0x800 + 0x80 * pmic_arb->channel;
}
+static int pmic_arb_wr_addr_map_v1(struct spmi_pmic_arb *pmic_arb, u8 sid,
+ u16 addr, struct resource *res_out)
+{
+ int rc;
+
+ rc = pmic_arb_offset_v1(pmic_arb, sid, addr, PMIC_ARB_CHANNEL_RW);
+ if (rc < 0)
+ return rc;
+
+ res_out->start = pmic_arb->wr_base_phys + rc;
+ res_out->end = res_out->start + 0x80 - 1;
+
+ return 0;
+}
+
static u16 pmic_arb_find_apid(struct spmi_pmic_arb *pmic_arb, u16 ppid)
{
struct apid_data *apidd = &pmic_arb->apid_data[pmic_arb->last_apid];
@@ -1111,6 +1135,21 @@ static int pmic_arb_offset_v2(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
return 0x1000 * pmic_arb->ee + 0x8000 * apid;
}
+static int pmic_arb_wr_addr_map_v2(struct spmi_pmic_arb *pmic_arb, u8 sid,
+ u16 addr, struct resource *res_out)
+{
+ int rc;
+
+ rc = pmic_arb_offset_v2(pmic_arb, sid, addr, PMIC_ARB_CHANNEL_RW);
+ if (rc < 0)
+ return rc;
+
+ res_out->start = pmic_arb->wr_base_phys + rc;
+ res_out->end = res_out->start + 0x1000 - 1;
+
+ return 0;
+}
+
/*
* v5 offset per ee and per apid for observer channels and per apid for
* read/write channels.
@@ -1145,6 +1184,21 @@ static int pmic_arb_offset_v5(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
return offset;
}
+static int pmic_arb_wr_addr_map_v5(struct spmi_pmic_arb *pmic_arb, u8 sid,
+ u16 addr, struct resource *res_out)
+{
+ int rc;
+
+ rc = pmic_arb_offset_v5(pmic_arb, sid, addr, PMIC_ARB_CHANNEL_RW);
+ if (rc < 0)
+ return rc;
+
+ res_out->start = pmic_arb->wr_base_phys + rc;
+ res_out->end = res_out->start + 0x10000 - 1;
+
+ return 0;
+}
+
static u32 pmic_arb_fmt_cmd_v1(u8 opc, u8 sid, u16 addr, u8 bc)
{
return (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7);
@@ -1254,6 +1308,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v1 = {
.irq_status = pmic_arb_irq_status_v1,
.irq_clear = pmic_arb_irq_clear_v1,
.apid_map_offset = pmic_arb_apid_map_offset_v2,
+ .wr_addr_map = pmic_arb_wr_addr_map_v1,
};
static const struct pmic_arb_ver_ops pmic_arb_v2 = {
@@ -1267,6 +1322,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v2 = {
.irq_status = pmic_arb_irq_status_v2,
.irq_clear = pmic_arb_irq_clear_v2,
.apid_map_offset = pmic_arb_apid_map_offset_v2,
+ .wr_addr_map = pmic_arb_wr_addr_map_v2,
};
static const struct pmic_arb_ver_ops pmic_arb_v3 = {
@@ -1280,6 +1336,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v3 = {
.irq_status = pmic_arb_irq_status_v2,
.irq_clear = pmic_arb_irq_clear_v2,
.apid_map_offset = pmic_arb_apid_map_offset_v2,
+ .wr_addr_map = pmic_arb_wr_addr_map_v2,
};
static const struct pmic_arb_ver_ops pmic_arb_v5 = {
@@ -1293,6 +1350,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v5 = {
.irq_status = pmic_arb_irq_status_v5,
.irq_clear = pmic_arb_irq_clear_v5,
.apid_map_offset = pmic_arb_apid_map_offset_v5,
+ .wr_addr_map = pmic_arb_wr_addr_map_v5,
};
static const struct irq_domain_ops pmic_arb_irq_domain_ops = {
@@ -1302,6 +1360,50 @@ static const struct irq_domain_ops pmic_arb_irq_domain_ops = {
.translate = qpnpint_irq_domain_translate,
};
+static int _spmi_pmic_arb_map_address(struct spmi_pmic_arb *pmic_arb,
+ u32 spmi_address, struct resource *res_out)
+{
+ u32 sid, addr;
+
+ sid = (spmi_address >> 16) & 0xF;
+ addr = spmi_address & 0xFFFF;
+
+ return pmic_arb->ver_ops->wr_addr_map(pmic_arb, sid, addr, res_out);
+}
+
+/**
+ * spmi_pmic_arb_map_address() - returns physical addresses of registers used to
+ * write to the PMIC peripheral at spmi_address
+ * @ctrl: SPMI controller device pointer
+ * @spmi_address: 20-bit SPMI address of the form: 0xSPPPP
+ * where S = global PMIC SID and
+ * PPPP = SPMI address within the slave
+ * @res_out: Resource struct (allocated by the caller) in which
+ * physical addresses for the range are passed via start
+ * and end elements
+ *
+ * Returns: 0 on success or an errno on failure.
+ */
+int spmi_pmic_arb_map_address(struct spmi_controller *ctrl, u32 spmi_address,
+ struct resource *res_out)
+{
+ struct spmi_pmic_arb *pmic_arb;
+
+ if (!ctrl || !res_out) {
+ pr_err("%s: Invalid pointer\n", __func__);
+ return -EINVAL;
+ }
+
+ pmic_arb = spmi_controller_get_drvdata(ctrl);
+ if (!pmic_arb) {
+ pr_err("Missing PMIC arbiter device\n");
+ return -ENODEV;
+ }
+
+ return _spmi_pmic_arb_map_address(pmic_arb, spmi_address, res_out);
+}
+EXPORT_SYMBOL(spmi_pmic_arb_map_address);
+
static int spmi_pmic_arb_probe(struct platform_device *pdev)
{
struct spmi_pmic_arb *pmic_arb;
@@ -1341,6 +1443,7 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
if (hw_ver < PMIC_ARB_VERSION_V2_MIN) {
pmic_arb->ver_ops = &pmic_arb_v1;
pmic_arb->wr_base = core;
+ pmic_arb->wr_base_phys = res->start;
pmic_arb->rd_base = core;
} else {
pmic_arb->core = core;
@@ -1367,6 +1470,7 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
err = PTR_ERR(pmic_arb->wr_base);
goto err_put_ctrl;
}
+ pmic_arb->wr_base_phys = res->start;
}
dev_info(&ctrl->dev, "PMIC arbiter version %s (0x%x)\n",
new file mode 100644
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
+
+#ifndef _SPMI_PMIC_ARB_H
+#define _SPMI_PMIC_ARB_H
+
+#include <linux/spmi.h>
+#include <linux/ioport.h>
+#include <linux/types.h>
+
+
+#if IS_ENABLED(CONFIG_SPMI_MSM_PMIC_ARB)
+int spmi_pmic_arb_map_address(struct spmi_controller *ctrl, u32 spmi_address,
+ struct resource *res_out);
+#else
+static inline int spmi_pmic_arb_map_address(struct spmi_controller *ctrl,
+ u32 spmi_address, struct resource *res_out)
+{
+ return -ENODEV;
+}
+#endif
+
+#endif