15b47271cSMateusz Kulikowski /*
25b47271cSMateusz Kulikowski * Qualcomm SPMI bus driver
35b47271cSMateusz Kulikowski *
45b47271cSMateusz Kulikowski * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com>
55b47271cSMateusz Kulikowski *
65b47271cSMateusz Kulikowski * Loosely based on Little Kernel driver
75b47271cSMateusz Kulikowski *
85b47271cSMateusz Kulikowski * SPDX-License-Identifier: BSD-3-Clause
95b47271cSMateusz Kulikowski */
105b47271cSMateusz Kulikowski
115b47271cSMateusz Kulikowski #include <common.h>
125b47271cSMateusz Kulikowski #include <dm.h>
135b47271cSMateusz Kulikowski #include <errno.h>
145b47271cSMateusz Kulikowski #include <fdtdec.h>
155b47271cSMateusz Kulikowski #include <asm/io.h>
165b47271cSMateusz Kulikowski #include <spmi/spmi.h>
175b47271cSMateusz Kulikowski
185b47271cSMateusz Kulikowski DECLARE_GLOBAL_DATA_PTR;
195b47271cSMateusz Kulikowski
205b47271cSMateusz Kulikowski #define ARB_CHANNEL_OFFSET(n) (0x4 * (n))
215b47271cSMateusz Kulikowski #define SPMI_CH_OFFSET(chnl) ((chnl) * 0x8000)
225b47271cSMateusz Kulikowski
235b47271cSMateusz Kulikowski #define SPMI_REG_CMD0 0x0
245b47271cSMateusz Kulikowski #define SPMI_REG_CONFIG 0x4
255b47271cSMateusz Kulikowski #define SPMI_REG_STATUS 0x8
265b47271cSMateusz Kulikowski #define SPMI_REG_WDATA 0x10
275b47271cSMateusz Kulikowski #define SPMI_REG_RDATA 0x18
285b47271cSMateusz Kulikowski
295b47271cSMateusz Kulikowski #define SPMI_CMD_OPCODE_SHIFT 27
305b47271cSMateusz Kulikowski #define SPMI_CMD_SLAVE_ID_SHIFT 20
315b47271cSMateusz Kulikowski #define SPMI_CMD_ADDR_SHIFT 12
325b47271cSMateusz Kulikowski #define SPMI_CMD_ADDR_OFFSET_SHIFT 4
335b47271cSMateusz Kulikowski #define SPMI_CMD_BYTE_CNT_SHIFT 0
345b47271cSMateusz Kulikowski
355b47271cSMateusz Kulikowski #define SPMI_CMD_EXT_REG_WRITE_LONG 0x00
365b47271cSMateusz Kulikowski #define SPMI_CMD_EXT_REG_READ_LONG 0x01
375b47271cSMateusz Kulikowski
385b47271cSMateusz Kulikowski #define SPMI_STATUS_DONE 0x1
395b47271cSMateusz Kulikowski
405b47271cSMateusz Kulikowski #define SPMI_MAX_CHANNELS 128
415b47271cSMateusz Kulikowski #define SPMI_MAX_SLAVES 16
425b47271cSMateusz Kulikowski #define SPMI_MAX_PERIPH 256
435b47271cSMateusz Kulikowski
445b47271cSMateusz Kulikowski struct msm_spmi_priv {
455b47271cSMateusz Kulikowski phys_addr_t arb_chnl; /* ARB channel mapping base */
465b47271cSMateusz Kulikowski phys_addr_t spmi_core; /* SPMI core */
475b47271cSMateusz Kulikowski phys_addr_t spmi_obs; /* SPMI observer */
485b47271cSMateusz Kulikowski /* SPMI channel map */
495b47271cSMateusz Kulikowski uint8_t channel_map[SPMI_MAX_SLAVES][SPMI_MAX_PERIPH];
505b47271cSMateusz Kulikowski };
515b47271cSMateusz Kulikowski
msm_spmi_write(struct udevice * dev,int usid,int pid,int off,uint8_t val)525b47271cSMateusz Kulikowski static int msm_spmi_write(struct udevice *dev, int usid, int pid, int off,
535b47271cSMateusz Kulikowski uint8_t val)
545b47271cSMateusz Kulikowski {
555b47271cSMateusz Kulikowski struct msm_spmi_priv *priv = dev_get_priv(dev);
565b47271cSMateusz Kulikowski unsigned channel;
575b47271cSMateusz Kulikowski uint32_t reg = 0;
585b47271cSMateusz Kulikowski
595b47271cSMateusz Kulikowski if (usid >= SPMI_MAX_SLAVES)
605b47271cSMateusz Kulikowski return -EIO;
615b47271cSMateusz Kulikowski if (pid >= SPMI_MAX_PERIPH)
625b47271cSMateusz Kulikowski return -EIO;
635b47271cSMateusz Kulikowski
645b47271cSMateusz Kulikowski channel = priv->channel_map[usid][pid];
655b47271cSMateusz Kulikowski
665b47271cSMateusz Kulikowski /* Disable IRQ mode for the current channel*/
675b47271cSMateusz Kulikowski writel(0x0, priv->spmi_core + SPMI_CH_OFFSET(channel) +
685b47271cSMateusz Kulikowski SPMI_REG_CONFIG);
695b47271cSMateusz Kulikowski
705b47271cSMateusz Kulikowski /* Write single byte */
715b47271cSMateusz Kulikowski writel(val, priv->spmi_core + SPMI_CH_OFFSET(channel) + SPMI_REG_WDATA);
725b47271cSMateusz Kulikowski
735b47271cSMateusz Kulikowski /* Prepare write command */
745b47271cSMateusz Kulikowski reg |= SPMI_CMD_EXT_REG_WRITE_LONG << SPMI_CMD_OPCODE_SHIFT;
755b47271cSMateusz Kulikowski reg |= (usid << SPMI_CMD_SLAVE_ID_SHIFT);
765b47271cSMateusz Kulikowski reg |= (pid << SPMI_CMD_ADDR_SHIFT);
775b47271cSMateusz Kulikowski reg |= (off << SPMI_CMD_ADDR_OFFSET_SHIFT);
785b47271cSMateusz Kulikowski reg |= 1; /* byte count */
795b47271cSMateusz Kulikowski
805b47271cSMateusz Kulikowski /* Send write command */
815b47271cSMateusz Kulikowski writel(reg, priv->spmi_core + SPMI_CH_OFFSET(channel) + SPMI_REG_CMD0);
825b47271cSMateusz Kulikowski
835b47271cSMateusz Kulikowski /* Wait till CMD DONE status */
845b47271cSMateusz Kulikowski reg = 0;
855b47271cSMateusz Kulikowski while (!reg) {
865b47271cSMateusz Kulikowski reg = readl(priv->spmi_core + SPMI_CH_OFFSET(channel) +
875b47271cSMateusz Kulikowski SPMI_REG_STATUS);
885b47271cSMateusz Kulikowski }
895b47271cSMateusz Kulikowski
905b47271cSMateusz Kulikowski if (reg ^ SPMI_STATUS_DONE) {
915b47271cSMateusz Kulikowski printf("SPMI write failure.\n");
925b47271cSMateusz Kulikowski return -EIO;
935b47271cSMateusz Kulikowski }
945b47271cSMateusz Kulikowski
955b47271cSMateusz Kulikowski return 0;
965b47271cSMateusz Kulikowski }
975b47271cSMateusz Kulikowski
msm_spmi_read(struct udevice * dev,int usid,int pid,int off)985b47271cSMateusz Kulikowski static int msm_spmi_read(struct udevice *dev, int usid, int pid, int off)
995b47271cSMateusz Kulikowski {
1005b47271cSMateusz Kulikowski struct msm_spmi_priv *priv = dev_get_priv(dev);
1015b47271cSMateusz Kulikowski unsigned channel;
1025b47271cSMateusz Kulikowski uint32_t reg = 0;
1035b47271cSMateusz Kulikowski
1045b47271cSMateusz Kulikowski if (usid >= SPMI_MAX_SLAVES)
1055b47271cSMateusz Kulikowski return -EIO;
1065b47271cSMateusz Kulikowski if (pid >= SPMI_MAX_PERIPH)
1075b47271cSMateusz Kulikowski return -EIO;
1085b47271cSMateusz Kulikowski
1095b47271cSMateusz Kulikowski channel = priv->channel_map[usid][pid];
1105b47271cSMateusz Kulikowski
1115b47271cSMateusz Kulikowski /* Disable IRQ mode for the current channel*/
1125b47271cSMateusz Kulikowski writel(0x0, priv->spmi_obs + SPMI_CH_OFFSET(channel) + SPMI_REG_CONFIG);
1135b47271cSMateusz Kulikowski
1145b47271cSMateusz Kulikowski /* Prepare read command */
1155b47271cSMateusz Kulikowski reg |= SPMI_CMD_EXT_REG_READ_LONG << SPMI_CMD_OPCODE_SHIFT;
1165b47271cSMateusz Kulikowski reg |= (usid << SPMI_CMD_SLAVE_ID_SHIFT);
1175b47271cSMateusz Kulikowski reg |= (pid << SPMI_CMD_ADDR_SHIFT);
1185b47271cSMateusz Kulikowski reg |= (off << SPMI_CMD_ADDR_OFFSET_SHIFT);
1195b47271cSMateusz Kulikowski reg |= 1; /* byte count */
1205b47271cSMateusz Kulikowski
1215b47271cSMateusz Kulikowski /* Request read */
1225b47271cSMateusz Kulikowski writel(reg, priv->spmi_obs + SPMI_CH_OFFSET(channel) + SPMI_REG_CMD0);
1235b47271cSMateusz Kulikowski
1245b47271cSMateusz Kulikowski /* Wait till CMD DONE status */
1255b47271cSMateusz Kulikowski reg = 0;
1265b47271cSMateusz Kulikowski while (!reg) {
1275b47271cSMateusz Kulikowski reg = readl(priv->spmi_obs + SPMI_CH_OFFSET(channel) +
1285b47271cSMateusz Kulikowski SPMI_REG_STATUS);
1295b47271cSMateusz Kulikowski }
1305b47271cSMateusz Kulikowski
1315b47271cSMateusz Kulikowski if (reg ^ SPMI_STATUS_DONE) {
1325b47271cSMateusz Kulikowski printf("SPMI read failure.\n");
1335b47271cSMateusz Kulikowski return -EIO;
1345b47271cSMateusz Kulikowski }
1355b47271cSMateusz Kulikowski
1365b47271cSMateusz Kulikowski /* Read the data */
1375b47271cSMateusz Kulikowski return readl(priv->spmi_obs + SPMI_CH_OFFSET(channel) +
1385b47271cSMateusz Kulikowski SPMI_REG_RDATA) & 0xFF;
1395b47271cSMateusz Kulikowski }
1405b47271cSMateusz Kulikowski
1415b47271cSMateusz Kulikowski static struct dm_spmi_ops msm_spmi_ops = {
1425b47271cSMateusz Kulikowski .read = msm_spmi_read,
1435b47271cSMateusz Kulikowski .write = msm_spmi_write,
1445b47271cSMateusz Kulikowski };
1455b47271cSMateusz Kulikowski
msm_spmi_probe(struct udevice * dev)1465b47271cSMateusz Kulikowski static int msm_spmi_probe(struct udevice *dev)
1475b47271cSMateusz Kulikowski {
1485b47271cSMateusz Kulikowski struct udevice *parent = dev->parent;
1495b47271cSMateusz Kulikowski struct msm_spmi_priv *priv = dev_get_priv(dev);
150e160f7d4SSimon Glass int node = dev_of_offset(dev);
1515b47271cSMateusz Kulikowski int i;
1525b47271cSMateusz Kulikowski
153*a821c4afSSimon Glass priv->arb_chnl = devfdt_get_addr(dev);
1545b47271cSMateusz Kulikowski priv->spmi_core = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
155e160f7d4SSimon Glass dev_of_offset(parent), node, "reg", 1, NULL, false);
1565b47271cSMateusz Kulikowski priv->spmi_obs = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
157e160f7d4SSimon Glass dev_of_offset(parent), node, "reg", 2, NULL, false);
1585b47271cSMateusz Kulikowski if (priv->arb_chnl == FDT_ADDR_T_NONE ||
1595b47271cSMateusz Kulikowski priv->spmi_core == FDT_ADDR_T_NONE ||
1605b47271cSMateusz Kulikowski priv->spmi_obs == FDT_ADDR_T_NONE)
1615b47271cSMateusz Kulikowski return -EINVAL;
1625b47271cSMateusz Kulikowski
1635b47271cSMateusz Kulikowski /* Scan peripherals connected to each SPMI channel */
1645b47271cSMateusz Kulikowski for (i = 0; i < SPMI_MAX_CHANNELS ; i++) {
1655b47271cSMateusz Kulikowski uint32_t periph = readl(priv->arb_chnl + ARB_CHANNEL_OFFSET(i));
1665b47271cSMateusz Kulikowski uint8_t slave_id = (periph & 0xf0000) >> 16;
1675b47271cSMateusz Kulikowski uint8_t pid = (periph & 0xff00) >> 8;
1685b47271cSMateusz Kulikowski
1695b47271cSMateusz Kulikowski priv->channel_map[slave_id][pid] = i;
1705b47271cSMateusz Kulikowski }
1715b47271cSMateusz Kulikowski return 0;
1725b47271cSMateusz Kulikowski }
1735b47271cSMateusz Kulikowski
1745b47271cSMateusz Kulikowski static const struct udevice_id msm_spmi_ids[] = {
1755b47271cSMateusz Kulikowski { .compatible = "qcom,spmi-pmic-arb" },
1765b47271cSMateusz Kulikowski { }
1775b47271cSMateusz Kulikowski };
1785b47271cSMateusz Kulikowski
1795b47271cSMateusz Kulikowski U_BOOT_DRIVER(msm_spmi) = {
1805b47271cSMateusz Kulikowski .name = "msm_spmi",
1815b47271cSMateusz Kulikowski .id = UCLASS_SPMI,
1825b47271cSMateusz Kulikowski .of_match = msm_spmi_ids,
1835b47271cSMateusz Kulikowski .ops = &msm_spmi_ops,
1845b47271cSMateusz Kulikowski .probe = msm_spmi_probe,
1855b47271cSMateusz Kulikowski .priv_auto_alloc_size = sizeof(struct msm_spmi_priv),
1865b47271cSMateusz Kulikowski };
187