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