xref: /linux/drivers/firmware/samsung/exynos-acpm-pmic.c (revision b615879dbfea6cf1236acbc3f2fb25ae84e07071)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright 2020 Samsung Electronics Co., Ltd.
4  * Copyright 2020 Google LLC.
5  * Copyright 2024 Linaro Ltd.
6  */
7 #include <linux/array_size.h>
8 #include <linux/bitfield.h>
9 #include <linux/errno.h>
10 #include <linux/firmware/samsung/exynos-acpm-protocol.h>
11 #include <linux/ktime.h>
12 #include <linux/types.h>
13 
14 #include "exynos-acpm.h"
15 #include "exynos-acpm-pmic.h"
16 
17 #define ACPM_PMIC_CHANNEL		GENMASK(15, 12)
18 #define ACPM_PMIC_TYPE			GENMASK(11, 8)
19 #define ACPM_PMIC_REG			GENMASK(7, 0)
20 
21 #define ACPM_PMIC_RETURN		GENMASK(31, 24)
22 #define ACPM_PMIC_MASK			GENMASK(23, 16)
23 #define ACPM_PMIC_VALUE			GENMASK(15, 8)
24 #define ACPM_PMIC_FUNC			GENMASK(7, 0)
25 
26 #define ACPM_PMIC_BULK_SHIFT		8
27 #define ACPM_PMIC_BULK_MASK		GENMASK(7, 0)
28 #define ACPM_PMIC_BULK_MAX_COUNT	8
29 
30 enum exynos_acpm_pmic_func {
31 	ACPM_PMIC_READ,
32 	ACPM_PMIC_WRITE,
33 	ACPM_PMIC_UPDATE,
34 	ACPM_PMIC_BULK_READ,
35 	ACPM_PMIC_BULK_WRITE,
36 };
37 
38 static const int acpm_pmic_linux_errmap[] = {
39 	[0] = 0, /* ACPM_PMIC_SUCCESS */
40 	[1] = -EACCES, /* Read register can't be accessed or issues to access it. */
41 	[2] = -EACCES, /* Write register can't be accessed or issues to access it. */
42 };
43 
44 static int acpm_pmic_to_linux_err(int err)
45 {
46 	if (err >= 0 && err < ARRAY_SIZE(acpm_pmic_linux_errmap))
47 		return acpm_pmic_linux_errmap[err];
48 	return -EIO;
49 }
50 
51 static inline u32 acpm_pmic_set_bulk(u32 data, unsigned int i)
52 {
53 	return (data & ACPM_PMIC_BULK_MASK) << (ACPM_PMIC_BULK_SHIFT * i);
54 }
55 
56 static inline u32 acpm_pmic_get_bulk(u32 data, unsigned int i)
57 {
58 	return (data >> (ACPM_PMIC_BULK_SHIFT * i)) & ACPM_PMIC_BULK_MASK;
59 }
60 
61 static void acpm_pmic_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
62 			       unsigned int acpm_chan_id)
63 {
64 	xfer->txd = cmd;
65 	xfer->rxd = cmd;
66 	xfer->txlen = cmdlen;
67 	xfer->rxlen = cmdlen;
68 	xfer->acpm_chan_id = acpm_chan_id;
69 }
70 
71 static void acpm_pmic_init_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan)
72 {
73 	cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
74 		 FIELD_PREP(ACPM_PMIC_REG, reg) |
75 		 FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
76 	cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_READ);
77 	cmd[3] = ktime_to_ms(ktime_get());
78 }
79 
80 int acpm_pmic_read_reg(const struct acpm_handle *handle,
81 		       unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
82 		       u8 *buf)
83 {
84 	struct acpm_xfer xfer;
85 	u32 cmd[4] = {0};
86 	int ret;
87 
88 	acpm_pmic_init_read_cmd(cmd, type, reg, chan);
89 	acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id);
90 
91 	ret = acpm_do_xfer(handle, &xfer);
92 	if (ret)
93 		return ret;
94 
95 	*buf = FIELD_GET(ACPM_PMIC_VALUE, xfer.rxd[1]);
96 
97 	return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
98 }
99 
100 static void acpm_pmic_init_bulk_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
101 					 u8 count)
102 {
103 	cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
104 		 FIELD_PREP(ACPM_PMIC_REG, reg) |
105 		 FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
106 	cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_BULK_READ) |
107 		 FIELD_PREP(ACPM_PMIC_VALUE, count);
108 }
109 
110 int acpm_pmic_bulk_read(const struct acpm_handle *handle,
111 			unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
112 			u8 count, u8 *buf)
113 {
114 	struct acpm_xfer xfer;
115 	u32 cmd[4] = {0};
116 	int i, ret;
117 
118 	if (count > ACPM_PMIC_BULK_MAX_COUNT)
119 		return -EINVAL;
120 
121 	acpm_pmic_init_bulk_read_cmd(cmd, type, reg, chan, count);
122 	acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id);
123 
124 	ret = acpm_do_xfer(handle, &xfer);
125 	if (ret)
126 		return ret;
127 
128 	ret = acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
129 	if (ret)
130 		return ret;
131 
132 	for (i = 0; i < count; i++) {
133 		if (i < 4)
134 			buf[i] = acpm_pmic_get_bulk(xfer.rxd[2], i);
135 		else
136 			buf[i] = acpm_pmic_get_bulk(xfer.rxd[3], i - 4);
137 	}
138 
139 	return 0;
140 }
141 
142 static void acpm_pmic_init_write_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
143 				     u8 value)
144 {
145 	cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
146 		 FIELD_PREP(ACPM_PMIC_REG, reg) |
147 		 FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
148 	cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_WRITE) |
149 		 FIELD_PREP(ACPM_PMIC_VALUE, value);
150 	cmd[3] = ktime_to_ms(ktime_get());
151 }
152 
153 int acpm_pmic_write_reg(const struct acpm_handle *handle,
154 			unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
155 			u8 value)
156 {
157 	struct acpm_xfer xfer;
158 	u32 cmd[4] = {0};
159 	int ret;
160 
161 	acpm_pmic_init_write_cmd(cmd, type, reg, chan, value);
162 	acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id);
163 
164 	ret = acpm_do_xfer(handle, &xfer);
165 	if (ret)
166 		return ret;
167 
168 	return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
169 }
170 
171 static void acpm_pmic_init_bulk_write_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
172 					  u8 count, const u8 *buf)
173 {
174 	int i;
175 
176 	cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
177 		 FIELD_PREP(ACPM_PMIC_REG, reg) |
178 		 FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
179 	cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_BULK_WRITE) |
180 		 FIELD_PREP(ACPM_PMIC_VALUE, count);
181 
182 	for (i = 0; i < count; i++) {
183 		if (i < 4)
184 			cmd[2] |= acpm_pmic_set_bulk(buf[i], i);
185 		else
186 			cmd[3] |= acpm_pmic_set_bulk(buf[i], i - 4);
187 	}
188 }
189 
190 int acpm_pmic_bulk_write(const struct acpm_handle *handle,
191 			 unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
192 			 u8 count, const u8 *buf)
193 {
194 	struct acpm_xfer xfer;
195 	u32 cmd[4] = {0};
196 	int ret;
197 
198 	if (count > ACPM_PMIC_BULK_MAX_COUNT)
199 		return -EINVAL;
200 
201 	acpm_pmic_init_bulk_write_cmd(cmd, type, reg, chan, count, buf);
202 	acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id);
203 
204 	ret = acpm_do_xfer(handle, &xfer);
205 	if (ret)
206 		return ret;
207 
208 	return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
209 }
210 
211 static void acpm_pmic_init_update_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan,
212 				      u8 value, u8 mask)
213 {
214 	cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
215 		 FIELD_PREP(ACPM_PMIC_REG, reg) |
216 		 FIELD_PREP(ACPM_PMIC_CHANNEL, chan);
217 	cmd[1] = FIELD_PREP(ACPM_PMIC_FUNC, ACPM_PMIC_UPDATE) |
218 		 FIELD_PREP(ACPM_PMIC_VALUE, value) |
219 		 FIELD_PREP(ACPM_PMIC_MASK, mask);
220 	cmd[3] = ktime_to_ms(ktime_get());
221 }
222 
223 int acpm_pmic_update_reg(const struct acpm_handle *handle,
224 			 unsigned int acpm_chan_id, u8 type, u8 reg, u8 chan,
225 			 u8 value, u8 mask)
226 {
227 	struct acpm_xfer xfer;
228 	u32 cmd[4] = {0};
229 	int ret;
230 
231 	acpm_pmic_init_update_cmd(cmd, type, reg, chan, value, mask);
232 	acpm_pmic_set_xfer(&xfer, cmd, sizeof(cmd), acpm_chan_id);
233 
234 	ret = acpm_do_xfer(handle, &xfer);
235 	if (ret)
236 		return ret;
237 
238 	return acpm_pmic_to_linux_err(FIELD_GET(ACPM_PMIC_RETURN, xfer.rxd[1]));
239 }
240