blob: 605683fc0e5ecf3879b099abcbf2d9d5e2d2cc94 [file] [log] [blame]
Mateusz Kulikowski5b472712016-03-31 23:12:29 +02001/*
2 * Qualcomm SPMI bus driver
3 *
4 * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com>
5 *
6 * Loosely based on Little Kernel driver
7 *
8 * SPDX-License-Identifier: BSD-3-Clause
9 */
10
11#include <common.h>
12#include <dm.h>
13#include <errno.h>
14#include <fdtdec.h>
15#include <asm/io.h>
16#include <spmi/spmi.h>
17
18DECLARE_GLOBAL_DATA_PTR;
19
20#define ARB_CHANNEL_OFFSET(n) (0x4 * (n))
21#define SPMI_CH_OFFSET(chnl) ((chnl) * 0x8000)
22
23#define SPMI_REG_CMD0 0x0
24#define SPMI_REG_CONFIG 0x4
25#define SPMI_REG_STATUS 0x8
26#define SPMI_REG_WDATA 0x10
27#define SPMI_REG_RDATA 0x18
28
29#define SPMI_CMD_OPCODE_SHIFT 27
30#define SPMI_CMD_SLAVE_ID_SHIFT 20
31#define SPMI_CMD_ADDR_SHIFT 12
32#define SPMI_CMD_ADDR_OFFSET_SHIFT 4
33#define SPMI_CMD_BYTE_CNT_SHIFT 0
34
35#define SPMI_CMD_EXT_REG_WRITE_LONG 0x00
36#define SPMI_CMD_EXT_REG_READ_LONG 0x01
37
38#define SPMI_STATUS_DONE 0x1
39
40#define SPMI_MAX_CHANNELS 128
41#define SPMI_MAX_SLAVES 16
42#define SPMI_MAX_PERIPH 256
43
44struct msm_spmi_priv {
45 phys_addr_t arb_chnl; /* ARB channel mapping base */
46 phys_addr_t spmi_core; /* SPMI core */
47 phys_addr_t spmi_obs; /* SPMI observer */
48 /* SPMI channel map */
49 uint8_t channel_map[SPMI_MAX_SLAVES][SPMI_MAX_PERIPH];
50};
51
52static int msm_spmi_write(struct udevice *dev, int usid, int pid, int off,
53 uint8_t val)
54{
55 struct msm_spmi_priv *priv = dev_get_priv(dev);
56 unsigned channel;
57 uint32_t reg = 0;
58
59 if (usid >= SPMI_MAX_SLAVES)
60 return -EIO;
61 if (pid >= SPMI_MAX_PERIPH)
62 return -EIO;
63
64 channel = priv->channel_map[usid][pid];
65
66 /* Disable IRQ mode for the current channel*/
67 writel(0x0, priv->spmi_core + SPMI_CH_OFFSET(channel) +
68 SPMI_REG_CONFIG);
69
70 /* Write single byte */
71 writel(val, priv->spmi_core + SPMI_CH_OFFSET(channel) + SPMI_REG_WDATA);
72
73 /* Prepare write command */
74 reg |= SPMI_CMD_EXT_REG_WRITE_LONG << SPMI_CMD_OPCODE_SHIFT;
75 reg |= (usid << SPMI_CMD_SLAVE_ID_SHIFT);
76 reg |= (pid << SPMI_CMD_ADDR_SHIFT);
77 reg |= (off << SPMI_CMD_ADDR_OFFSET_SHIFT);
78 reg |= 1; /* byte count */
79
80 /* Send write command */
81 writel(reg, priv->spmi_core + SPMI_CH_OFFSET(channel) + SPMI_REG_CMD0);
82
83 /* Wait till CMD DONE status */
84 reg = 0;
85 while (!reg) {
86 reg = readl(priv->spmi_core + SPMI_CH_OFFSET(channel) +
87 SPMI_REG_STATUS);
88 }
89
90 if (reg ^ SPMI_STATUS_DONE) {
91 printf("SPMI write failure.\n");
92 return -EIO;
93 }
94
95 return 0;
96}
97
98static int msm_spmi_read(struct udevice *dev, int usid, int pid, int off)
99{
100 struct msm_spmi_priv *priv = dev_get_priv(dev);
101 unsigned channel;
102 uint32_t reg = 0;
103
104 if (usid >= SPMI_MAX_SLAVES)
105 return -EIO;
106 if (pid >= SPMI_MAX_PERIPH)
107 return -EIO;
108
109 channel = priv->channel_map[usid][pid];
110
111 /* Disable IRQ mode for the current channel*/
112 writel(0x0, priv->spmi_obs + SPMI_CH_OFFSET(channel) + SPMI_REG_CONFIG);
113
114 /* Prepare read command */
115 reg |= SPMI_CMD_EXT_REG_READ_LONG << SPMI_CMD_OPCODE_SHIFT;
116 reg |= (usid << SPMI_CMD_SLAVE_ID_SHIFT);
117 reg |= (pid << SPMI_CMD_ADDR_SHIFT);
118 reg |= (off << SPMI_CMD_ADDR_OFFSET_SHIFT);
119 reg |= 1; /* byte count */
120
121 /* Request read */
122 writel(reg, priv->spmi_obs + SPMI_CH_OFFSET(channel) + SPMI_REG_CMD0);
123
124 /* Wait till CMD DONE status */
125 reg = 0;
126 while (!reg) {
127 reg = readl(priv->spmi_obs + SPMI_CH_OFFSET(channel) +
128 SPMI_REG_STATUS);
129 }
130
131 if (reg ^ SPMI_STATUS_DONE) {
132 printf("SPMI read failure.\n");
133 return -EIO;
134 }
135
136 /* Read the data */
137 return readl(priv->spmi_obs + SPMI_CH_OFFSET(channel) +
138 SPMI_REG_RDATA) & 0xFF;
139}
140
141static struct dm_spmi_ops msm_spmi_ops = {
142 .read = msm_spmi_read,
143 .write = msm_spmi_write,
144};
145
146static int msm_spmi_probe(struct udevice *dev)
147{
148 struct udevice *parent = dev->parent;
149 struct msm_spmi_priv *priv = dev_get_priv(dev);
Simon Glasse160f7d2017-01-17 16:52:55 -0700150 int node = dev_of_offset(dev);
Mateusz Kulikowski5b472712016-03-31 23:12:29 +0200151 int i;
152
153 priv->arb_chnl = dev_get_addr(dev);
154 priv->spmi_core = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
Simon Glasse160f7d2017-01-17 16:52:55 -0700155 dev_of_offset(parent), node, "reg", 1, NULL, false);
Mateusz Kulikowski5b472712016-03-31 23:12:29 +0200156 priv->spmi_obs = fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
Simon Glasse160f7d2017-01-17 16:52:55 -0700157 dev_of_offset(parent), node, "reg", 2, NULL, false);
Mateusz Kulikowski5b472712016-03-31 23:12:29 +0200158 if (priv->arb_chnl == FDT_ADDR_T_NONE ||
159 priv->spmi_core == FDT_ADDR_T_NONE ||
160 priv->spmi_obs == FDT_ADDR_T_NONE)
161 return -EINVAL;
162
163 /* Scan peripherals connected to each SPMI channel */
164 for (i = 0; i < SPMI_MAX_CHANNELS ; i++) {
165 uint32_t periph = readl(priv->arb_chnl + ARB_CHANNEL_OFFSET(i));
166 uint8_t slave_id = (periph & 0xf0000) >> 16;
167 uint8_t pid = (periph & 0xff00) >> 8;
168
169 priv->channel_map[slave_id][pid] = i;
170 }
171 return 0;
172}
173
174static const struct udevice_id msm_spmi_ids[] = {
175 { .compatible = "qcom,spmi-pmic-arb" },
176 { }
177};
178
179U_BOOT_DRIVER(msm_spmi) = {
180 .name = "msm_spmi",
181 .id = UCLASS_SPMI,
182 .of_match = msm_spmi_ids,
183 .ops = &msm_spmi_ops,
184 .probe = msm_spmi_probe,
185 .priv_auto_alloc_size = sizeof(struct msm_spmi_priv),
186};