1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MIPI Camera Control Interface (CCI) register access helpers. |
4 | * |
5 | * Copyright (C) 2023 Hans de Goede <hansg@kernel.org> |
6 | */ |
7 | |
8 | #include <linux/bitfield.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/dev_printk.h> |
11 | #include <linux/module.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/types.h> |
14 | |
15 | #include <asm/unaligned.h> |
16 | |
17 | #include <media/v4l2-cci.h> |
18 | |
19 | int cci_read(struct regmap *map, u32 reg, u64 *val, int *err) |
20 | { |
21 | bool little_endian; |
22 | unsigned int len; |
23 | u8 buf[8]; |
24 | int ret; |
25 | |
26 | if (err && *err) |
27 | return *err; |
28 | |
29 | little_endian = reg & CCI_REG_LE; |
30 | len = CCI_REG_WIDTH_BYTES(reg); |
31 | reg = CCI_REG_ADDR(reg); |
32 | |
33 | ret = regmap_bulk_read(map, reg, val: buf, val_count: len); |
34 | if (ret) { |
35 | dev_err(regmap_get_device(map), "Error reading reg 0x%04x: %d\n" , |
36 | reg, ret); |
37 | goto out; |
38 | } |
39 | |
40 | switch (len) { |
41 | case 1: |
42 | *val = buf[0]; |
43 | break; |
44 | case 2: |
45 | if (little_endian) |
46 | *val = get_unaligned_le16(p: buf); |
47 | else |
48 | *val = get_unaligned_be16(p: buf); |
49 | break; |
50 | case 3: |
51 | if (little_endian) |
52 | *val = get_unaligned_le24(p: buf); |
53 | else |
54 | *val = get_unaligned_be24(p: buf); |
55 | break; |
56 | case 4: |
57 | if (little_endian) |
58 | *val = get_unaligned_le32(p: buf); |
59 | else |
60 | *val = get_unaligned_be32(p: buf); |
61 | break; |
62 | case 8: |
63 | if (little_endian) |
64 | *val = get_unaligned_le64(p: buf); |
65 | else |
66 | *val = get_unaligned_be64(p: buf); |
67 | break; |
68 | default: |
69 | dev_err(regmap_get_device(map), "Error invalid reg-width %u for reg 0x%04x\n" , |
70 | len, reg); |
71 | ret = -EINVAL; |
72 | break; |
73 | } |
74 | |
75 | out: |
76 | if (ret && err) |
77 | *err = ret; |
78 | |
79 | return ret; |
80 | } |
81 | EXPORT_SYMBOL_GPL(cci_read); |
82 | |
83 | int cci_write(struct regmap *map, u32 reg, u64 val, int *err) |
84 | { |
85 | bool little_endian; |
86 | unsigned int len; |
87 | u8 buf[8]; |
88 | int ret; |
89 | |
90 | if (err && *err) |
91 | return *err; |
92 | |
93 | little_endian = reg & CCI_REG_LE; |
94 | len = CCI_REG_WIDTH_BYTES(reg); |
95 | reg = CCI_REG_ADDR(reg); |
96 | |
97 | switch (len) { |
98 | case 1: |
99 | buf[0] = val; |
100 | break; |
101 | case 2: |
102 | if (little_endian) |
103 | put_unaligned_le16(val, p: buf); |
104 | else |
105 | put_unaligned_be16(val, p: buf); |
106 | break; |
107 | case 3: |
108 | if (little_endian) |
109 | put_unaligned_le24(val, p: buf); |
110 | else |
111 | put_unaligned_be24(val, p: buf); |
112 | break; |
113 | case 4: |
114 | if (little_endian) |
115 | put_unaligned_le32(val, p: buf); |
116 | else |
117 | put_unaligned_be32(val, p: buf); |
118 | break; |
119 | case 8: |
120 | if (little_endian) |
121 | put_unaligned_le64(val, p: buf); |
122 | else |
123 | put_unaligned_be64(val, p: buf); |
124 | break; |
125 | default: |
126 | dev_err(regmap_get_device(map), "Error invalid reg-width %u for reg 0x%04x\n" , |
127 | len, reg); |
128 | ret = -EINVAL; |
129 | goto out; |
130 | } |
131 | |
132 | ret = regmap_bulk_write(map, reg, val: buf, val_count: len); |
133 | if (ret) |
134 | dev_err(regmap_get_device(map), "Error writing reg 0x%04x: %d\n" , |
135 | reg, ret); |
136 | |
137 | out: |
138 | if (ret && err) |
139 | *err = ret; |
140 | |
141 | return ret; |
142 | } |
143 | EXPORT_SYMBOL_GPL(cci_write); |
144 | |
145 | int cci_update_bits(struct regmap *map, u32 reg, u64 mask, u64 val, int *err) |
146 | { |
147 | u64 readval; |
148 | int ret; |
149 | |
150 | ret = cci_read(map, reg, &readval, err); |
151 | if (ret) |
152 | return ret; |
153 | |
154 | val = (readval & ~mask) | (val & mask); |
155 | |
156 | return cci_write(map, reg, val, err); |
157 | } |
158 | EXPORT_SYMBOL_GPL(cci_update_bits); |
159 | |
160 | int cci_multi_reg_write(struct regmap *map, const struct cci_reg_sequence *regs, |
161 | unsigned int num_regs, int *err) |
162 | { |
163 | unsigned int i; |
164 | int ret; |
165 | |
166 | for (i = 0; i < num_regs; i++) { |
167 | ret = cci_write(map, regs[i].reg, regs[i].val, err); |
168 | if (ret) |
169 | return ret; |
170 | } |
171 | |
172 | return 0; |
173 | } |
174 | EXPORT_SYMBOL_GPL(cci_multi_reg_write); |
175 | |
176 | #if IS_ENABLED(CONFIG_V4L2_CCI_I2C) |
177 | struct regmap *devm_cci_regmap_init_i2c(struct i2c_client *client, |
178 | int reg_addr_bits) |
179 | { |
180 | struct regmap_config config = { |
181 | .reg_bits = reg_addr_bits, |
182 | .val_bits = 8, |
183 | .reg_format_endian = REGMAP_ENDIAN_BIG, |
184 | .disable_locking = true, |
185 | }; |
186 | |
187 | return devm_regmap_init_i2c(client, &config); |
188 | } |
189 | EXPORT_SYMBOL_GPL(devm_cci_regmap_init_i2c); |
190 | #endif |
191 | |
192 | MODULE_LICENSE("GPL" ); |
193 | MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>" ); |
194 | MODULE_DESCRIPTION("MIPI Camera Control Interface (CCI) support" ); |
195 | |