diff options
Diffstat (limited to 'drivers/media/video/gspca/stv06xx/stv06xx_hdcs.c')
-rw-r--r-- | drivers/media/video/gspca/stv06xx/stv06xx_hdcs.c | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.c b/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.c new file mode 100644 index 000000000000..1cfe58504553 --- /dev/null +++ b/drivers/media/video/gspca/stv06xx/stv06xx_hdcs.c | |||
@@ -0,0 +1,533 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher | ||
3 | * Mark Cave-Ayland, Carlo E Prelz, Dick Streefland | ||
4 | * Copyright (c) 2002, 2003 Tuukka Toivonen | ||
5 | * Copyright (c) 2008 Erik Andrén | ||
6 | * Copyright (c) 2008 Chia-I Wu | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | * | ||
22 | * P/N 861037: Sensor HDCS1000 ASIC STV0600 | ||
23 | * P/N 861050-0010: Sensor HDCS1000 ASIC STV0600 | ||
24 | * P/N 861050-0020: Sensor Photobit PB100 ASIC STV0600-1 - QuickCam Express | ||
25 | * P/N 861055: Sensor ST VV6410 ASIC STV0610 - LEGO cam | ||
26 | * P/N 861075-0040: Sensor HDCS1000 ASIC | ||
27 | * P/N 961179-0700: Sensor ST VV6410 ASIC STV0602 - Dexxa WebCam USB | ||
28 | * P/N 861040-0000: Sensor ST VV6410 ASIC STV0610 - QuickCam Web | ||
29 | */ | ||
30 | |||
31 | #include "stv06xx_hdcs.h" | ||
32 | |||
33 | enum hdcs_power_state { | ||
34 | HDCS_STATE_SLEEP, | ||
35 | HDCS_STATE_IDLE, | ||
36 | HDCS_STATE_RUN | ||
37 | }; | ||
38 | |||
39 | /* no lock? */ | ||
40 | struct hdcs { | ||
41 | enum hdcs_power_state state; | ||
42 | int w, h; | ||
43 | |||
44 | /* visible area of the sensor array */ | ||
45 | struct { | ||
46 | int left, top; | ||
47 | int width, height; | ||
48 | int border; | ||
49 | } array; | ||
50 | |||
51 | struct { | ||
52 | /* Column timing overhead */ | ||
53 | u8 cto; | ||
54 | /* Column processing overhead */ | ||
55 | u8 cpo; | ||
56 | /* Row sample period constant */ | ||
57 | u16 rs; | ||
58 | /* Exposure reset duration */ | ||
59 | u16 er; | ||
60 | } exp; | ||
61 | |||
62 | int psmp; | ||
63 | }; | ||
64 | |||
65 | static int hdcs_reg_write_seq(struct sd *sd, u8 reg, u8 *vals, u8 len) | ||
66 | { | ||
67 | u8 regs[I2C_MAX_BYTES * 2]; | ||
68 | int i; | ||
69 | |||
70 | if (unlikely((len <= 0) || (len >= I2C_MAX_BYTES) || | ||
71 | (reg + len > 0xff))) | ||
72 | return -EINVAL; | ||
73 | |||
74 | for (i = 0; i < len; i++, reg++) { | ||
75 | regs[2*i] = reg; | ||
76 | regs[2*i+1] = vals[i]; | ||
77 | } | ||
78 | |||
79 | return stv06xx_write_sensor_bytes(sd, regs, len); | ||
80 | } | ||
81 | |||
82 | static int hdcs_set_state(struct sd *sd, enum hdcs_power_state state) | ||
83 | { | ||
84 | struct hdcs *hdcs = sd->sensor_priv; | ||
85 | u8 val; | ||
86 | int ret; | ||
87 | |||
88 | if (hdcs->state == state) | ||
89 | return 0; | ||
90 | |||
91 | /* we need to go idle before running or sleeping */ | ||
92 | if (hdcs->state != HDCS_STATE_IDLE) { | ||
93 | ret = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 0); | ||
94 | if (ret) | ||
95 | return ret; | ||
96 | } | ||
97 | |||
98 | hdcs->state = HDCS_STATE_IDLE; | ||
99 | |||
100 | if (state == HDCS_STATE_IDLE) | ||
101 | return 0; | ||
102 | |||
103 | switch (state) { | ||
104 | case HDCS_STATE_SLEEP: | ||
105 | val = HDCS_SLEEP_MODE; | ||
106 | break; | ||
107 | |||
108 | case HDCS_STATE_RUN: | ||
109 | val = HDCS_RUN_ENABLE; | ||
110 | break; | ||
111 | |||
112 | default: | ||
113 | return -EINVAL; | ||
114 | } | ||
115 | |||
116 | ret = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), val); | ||
117 | if (ret < 0) | ||
118 | hdcs->state = state; | ||
119 | |||
120 | return ret; | ||
121 | } | ||
122 | |||
123 | static int hdcs_reset(struct sd *sd) | ||
124 | { | ||
125 | struct hdcs *hdcs = sd->sensor_priv; | ||
126 | int err; | ||
127 | |||
128 | err = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 1); | ||
129 | if (err < 0) | ||
130 | return err; | ||
131 | |||
132 | err = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 0); | ||
133 | if (err < 0) | ||
134 | hdcs->state = HDCS_STATE_IDLE; | ||
135 | |||
136 | return err; | ||
137 | } | ||
138 | |||
139 | static int hdcs_get_exposure(struct gspca_dev *gspca_dev, __s32 *val) | ||
140 | { | ||
141 | struct sd *sd = (struct sd *) gspca_dev; | ||
142 | struct hdcs *hdcs = sd->sensor_priv; | ||
143 | |||
144 | /* Column time period */ | ||
145 | int ct; | ||
146 | /* Column processing period */ | ||
147 | int cp; | ||
148 | /* Row processing period */ | ||
149 | int rp; | ||
150 | int cycles; | ||
151 | int err; | ||
152 | int rowexp; | ||
153 | u16 data[2]; | ||
154 | |||
155 | err = stv06xx_read_sensor(sd, HDCS_ROWEXPL, &data[0]); | ||
156 | if (err < 0) | ||
157 | return err; | ||
158 | |||
159 | err = stv06xx_read_sensor(sd, HDCS_ROWEXPH, &data[1]); | ||
160 | if (err < 0) | ||
161 | return err; | ||
162 | |||
163 | rowexp = (data[1] << 8) | data[0]; | ||
164 | |||
165 | ct = hdcs->exp.cto + hdcs->psmp + (HDCS_ADC_START_SIG_DUR + 2); | ||
166 | cp = hdcs->exp.cto + (hdcs->w * ct / 2); | ||
167 | rp = hdcs->exp.rs + cp; | ||
168 | |||
169 | cycles = rp * rowexp; | ||
170 | *val = cycles / HDCS_CLK_FREQ_MHZ; | ||
171 | PDEBUG(D_V4L2, "Read exposure %d", *val); | ||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | static int hdcs_set_exposure(struct gspca_dev *gspca_dev, __s32 val) | ||
176 | { | ||
177 | struct sd *sd = (struct sd *) gspca_dev; | ||
178 | struct hdcs *hdcs = sd->sensor_priv; | ||
179 | int rowexp, srowexp; | ||
180 | int max_srowexp; | ||
181 | /* Column time period */ | ||
182 | int ct; | ||
183 | /* Column processing period */ | ||
184 | int cp; | ||
185 | /* Row processing period */ | ||
186 | int rp; | ||
187 | /* Minimum number of column timing periods | ||
188 | within the column processing period */ | ||
189 | int mnct; | ||
190 | int cycles, err; | ||
191 | u8 exp[4]; | ||
192 | |||
193 | cycles = val * HDCS_CLK_FREQ_MHZ; | ||
194 | |||
195 | ct = hdcs->exp.cto + hdcs->psmp + (HDCS_ADC_START_SIG_DUR + 2); | ||
196 | cp = hdcs->exp.cto + (hdcs->w * ct / 2); | ||
197 | |||
198 | /* the cycles one row takes */ | ||
199 | rp = hdcs->exp.rs + cp; | ||
200 | |||
201 | rowexp = cycles / rp; | ||
202 | |||
203 | /* the remaining cycles */ | ||
204 | cycles -= rowexp * rp; | ||
205 | |||
206 | /* calculate sub-row exposure */ | ||
207 | if (IS_1020(sd)) { | ||
208 | /* see HDCS-1020 datasheet 3.5.6.4, p. 63 */ | ||
209 | srowexp = hdcs->w - (cycles + hdcs->exp.er + 13) / ct; | ||
210 | |||
211 | mnct = (hdcs->exp.er + 12 + ct - 1) / ct; | ||
212 | max_srowexp = hdcs->w - mnct; | ||
213 | } else { | ||
214 | /* see HDCS-1000 datasheet 3.4.5.5, p. 61 */ | ||
215 | srowexp = cp - hdcs->exp.er - 6 - cycles; | ||
216 | |||
217 | mnct = (hdcs->exp.er + 5 + ct - 1) / ct; | ||
218 | max_srowexp = cp - mnct * ct - 1; | ||
219 | } | ||
220 | |||
221 | if (srowexp < 0) | ||
222 | srowexp = 0; | ||
223 | else if (srowexp > max_srowexp) | ||
224 | srowexp = max_srowexp; | ||
225 | |||
226 | if (IS_1020(sd)) { | ||
227 | exp[0] = rowexp & 0xff; | ||
228 | exp[1] = rowexp >> 8; | ||
229 | exp[2] = (srowexp >> 2) & 0xff; | ||
230 | /* this clears exposure error flag */ | ||
231 | exp[3] = 0x1; | ||
232 | err = hdcs_reg_write_seq(sd, HDCS_ROWEXPL, exp, 4); | ||
233 | } else { | ||
234 | exp[0] = rowexp & 0xff; | ||
235 | exp[1] = rowexp >> 8; | ||
236 | exp[2] = srowexp & 0xff; | ||
237 | exp[3] = srowexp >> 8; | ||
238 | err = hdcs_reg_write_seq(sd, HDCS_ROWEXPL, exp, 4); | ||
239 | if (err < 0) | ||
240 | return err; | ||
241 | |||
242 | /* clear exposure error flag */ | ||
243 | err = stv06xx_write_sensor(sd, | ||
244 | HDCS_STATUS, BIT(4)); | ||
245 | } | ||
246 | PDEBUG(D_V4L2, "Writing exposure %d, rowexp %d, srowexp %d", | ||
247 | val, rowexp, srowexp); | ||
248 | return err; | ||
249 | } | ||
250 | |||
251 | static int hdcs_set_gains(struct sd *sd, u8 r, u8 g, u8 b) | ||
252 | { | ||
253 | u8 gains[4]; | ||
254 | |||
255 | /* the voltage gain Av = (1 + 19 * val / 127) * (1 + bit7) */ | ||
256 | if (r > 127) | ||
257 | r = 0x80 | (r / 2); | ||
258 | if (g > 127) | ||
259 | g = 0x80 | (g / 2); | ||
260 | if (b > 127) | ||
261 | b = 0x80 | (b / 2); | ||
262 | |||
263 | gains[0] = g; | ||
264 | gains[1] = r; | ||
265 | gains[2] = b; | ||
266 | gains[3] = g; | ||
267 | |||
268 | return hdcs_reg_write_seq(sd, HDCS_ERECPGA, gains, 4); | ||
269 | } | ||
270 | |||
271 | static int hdcs_get_gain(struct gspca_dev *gspca_dev, __s32 *val) | ||
272 | { | ||
273 | struct sd *sd = (struct sd *) gspca_dev; | ||
274 | int err; | ||
275 | u16 data; | ||
276 | |||
277 | err = stv06xx_read_sensor(sd, HDCS_ERECPGA, &data); | ||
278 | |||
279 | /* Bit 7 doubles the gain */ | ||
280 | if (data & 0x80) | ||
281 | *val = (data & 0x7f) * 2; | ||
282 | else | ||
283 | *val = data; | ||
284 | |||
285 | PDEBUG(D_V4L2, "Read gain %d", *val); | ||
286 | return err; | ||
287 | } | ||
288 | |||
289 | static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val) | ||
290 | { | ||
291 | PDEBUG(D_V4L2, "Writing gain %d", val); | ||
292 | return hdcs_set_gains((struct sd *) gspca_dev, | ||
293 | val & 0xff, val & 0xff, val & 0xff); | ||
294 | } | ||
295 | |||
296 | static int hdcs_set_size(struct sd *sd, | ||
297 | unsigned int width, unsigned int height) | ||
298 | { | ||
299 | struct hdcs *hdcs = sd->sensor_priv; | ||
300 | u8 win[4]; | ||
301 | unsigned int x, y; | ||
302 | int err; | ||
303 | |||
304 | /* must be multiple of 4 */ | ||
305 | width = (width + 3) & ~0x3; | ||
306 | height = (height + 3) & ~0x3; | ||
307 | |||
308 | if (width > hdcs->array.width) | ||
309 | width = hdcs->array.width; | ||
310 | |||
311 | if (IS_1020(sd)) { | ||
312 | /* the borders are also invalid */ | ||
313 | if (height + 2 * hdcs->array.border + HDCS_1020_BOTTOM_Y_SKIP | ||
314 | > hdcs->array.height) | ||
315 | height = hdcs->array.height - 2 * hdcs->array.border - | ||
316 | HDCS_1020_BOTTOM_Y_SKIP; | ||
317 | |||
318 | y = (hdcs->array.height - HDCS_1020_BOTTOM_Y_SKIP - height) / 2 | ||
319 | + hdcs->array.top; | ||
320 | } else if (height > hdcs->array.height) { | ||
321 | height = hdcs->array.height; | ||
322 | y = hdcs->array.top + (hdcs->array.height - height) / 2; | ||
323 | } | ||
324 | |||
325 | x = hdcs->array.left + (hdcs->array.width - width) / 2; | ||
326 | |||
327 | win[0] = y / 4; | ||
328 | win[1] = x / 4; | ||
329 | win[2] = (y + height) / 4 - 1; | ||
330 | win[3] = (x + width) / 4 - 1; | ||
331 | |||
332 | err = hdcs_reg_write_seq(sd, HDCS_FWROW, win, 4); | ||
333 | if (err < 0) | ||
334 | return err; | ||
335 | |||
336 | /* Update the current width and height */ | ||
337 | hdcs->w = width; | ||
338 | hdcs->h = height; | ||
339 | return err; | ||
340 | } | ||
341 | |||
342 | static int hdcs_probe_1x00(struct sd *sd) | ||
343 | { | ||
344 | struct hdcs *hdcs; | ||
345 | u16 sensor; | ||
346 | int ret; | ||
347 | |||
348 | ret = stv06xx_read_sensor(sd, HDCS_IDENT, &sensor); | ||
349 | if (ret < 0 || sensor != 0x08) | ||
350 | return -ENODEV; | ||
351 | |||
352 | info("HDCS-1000/1100 sensor detected"); | ||
353 | |||
354 | sd->gspca_dev.cam.cam_mode = stv06xx_sensor_hdcs1x00.modes; | ||
355 | sd->gspca_dev.cam.nmodes = stv06xx_sensor_hdcs1x00.nmodes; | ||
356 | sd->desc.ctrls = stv06xx_sensor_hdcs1x00.ctrls; | ||
357 | sd->desc.nctrls = stv06xx_sensor_hdcs1x00.nctrls; | ||
358 | |||
359 | hdcs = kmalloc(sizeof(struct hdcs), GFP_KERNEL); | ||
360 | if (!hdcs) | ||
361 | return -ENOMEM; | ||
362 | |||
363 | hdcs->array.left = 8; | ||
364 | hdcs->array.top = 8; | ||
365 | hdcs->array.width = HDCS_1X00_DEF_WIDTH; | ||
366 | hdcs->array.height = HDCS_1X00_DEF_HEIGHT; | ||
367 | hdcs->array.border = 4; | ||
368 | |||
369 | hdcs->exp.cto = 4; | ||
370 | hdcs->exp.cpo = 2; | ||
371 | hdcs->exp.rs = 186; | ||
372 | hdcs->exp.er = 100; | ||
373 | |||
374 | /* | ||
375 | * Frame rate on HDCS-1000 0x46D:0x840 depends on PSMP: | ||
376 | * 4 = doesn't work at all | ||
377 | * 5 = 7.8 fps, | ||
378 | * 6 = 6.9 fps, | ||
379 | * 8 = 6.3 fps, | ||
380 | * 10 = 5.5 fps, | ||
381 | * 15 = 4.4 fps, | ||
382 | * 31 = 2.8 fps | ||
383 | * | ||
384 | * Frame rate on HDCS-1000 0x46D:0x870 depends on PSMP: | ||
385 | * 15 = doesn't work at all | ||
386 | * 18 = doesn't work at all | ||
387 | * 19 = 7.3 fps | ||
388 | * 20 = 7.4 fps | ||
389 | * 21 = 7.4 fps | ||
390 | * 22 = 7.4 fps | ||
391 | * 24 = 6.3 fps | ||
392 | * 30 = 5.4 fps | ||
393 | */ | ||
394 | hdcs->psmp = IS_870(sd) ? 20 : 5; | ||
395 | |||
396 | sd->sensor_priv = hdcs; | ||
397 | |||
398 | return 0; | ||
399 | } | ||
400 | |||
401 | static int hdcs_probe_1020(struct sd *sd) | ||
402 | { | ||
403 | struct hdcs *hdcs; | ||
404 | u16 sensor; | ||
405 | int ret; | ||
406 | |||
407 | ret = stv06xx_read_sensor(sd, HDCS_IDENT, &sensor); | ||
408 | if (ret < 0 || sensor != 0x10) | ||
409 | return -ENODEV; | ||
410 | |||
411 | info("HDCS-1020 sensor detected"); | ||
412 | |||
413 | sd->gspca_dev.cam.cam_mode = stv06xx_sensor_hdcs1020.modes; | ||
414 | sd->gspca_dev.cam.nmodes = stv06xx_sensor_hdcs1020.nmodes; | ||
415 | sd->desc.ctrls = stv06xx_sensor_hdcs1020.ctrls; | ||
416 | sd->desc.nctrls = stv06xx_sensor_hdcs1020.nctrls; | ||
417 | |||
418 | hdcs = kmalloc(sizeof(struct hdcs), GFP_KERNEL); | ||
419 | if (!hdcs) | ||
420 | return -ENOMEM; | ||
421 | |||
422 | /* | ||
423 | * From Andrey's test image: looks like HDCS-1020 upper-left | ||
424 | * visible pixel is at 24,8 (y maybe even smaller?) and lower-right | ||
425 | * visible pixel at 375,299 (x maybe even larger?) | ||
426 | */ | ||
427 | hdcs->array.left = 24; | ||
428 | hdcs->array.top = 4; | ||
429 | hdcs->array.width = HDCS_1020_DEF_WIDTH; | ||
430 | hdcs->array.height = 304; | ||
431 | hdcs->array.border = 4; | ||
432 | |||
433 | hdcs->psmp = 6; | ||
434 | |||
435 | hdcs->exp.cto = 3; | ||
436 | hdcs->exp.cpo = 3; | ||
437 | hdcs->exp.rs = 155; | ||
438 | hdcs->exp.er = 96; | ||
439 | |||
440 | sd->sensor_priv = hdcs; | ||
441 | |||
442 | return 0; | ||
443 | } | ||
444 | |||
445 | static int hdcs_start(struct sd *sd) | ||
446 | { | ||
447 | PDEBUG(D_STREAM, "Starting stream"); | ||
448 | |||
449 | return hdcs_set_state(sd, HDCS_STATE_RUN); | ||
450 | } | ||
451 | |||
452 | static int hdcs_stop(struct sd *sd) | ||
453 | { | ||
454 | PDEBUG(D_STREAM, "Halting stream"); | ||
455 | |||
456 | return hdcs_set_state(sd, HDCS_STATE_SLEEP); | ||
457 | } | ||
458 | |||
459 | static void hdcs_disconnect(struct sd *sd) | ||
460 | { | ||
461 | PDEBUG(D_PROBE, "Disconnecting the sensor"); | ||
462 | kfree(sd->sensor_priv); | ||
463 | } | ||
464 | |||
465 | static int hdcs_init(struct sd *sd) | ||
466 | { | ||
467 | struct hdcs *hdcs = sd->sensor_priv; | ||
468 | int i, err = 0; | ||
469 | |||
470 | /* Set the STV0602AA in STV0600 emulation mode */ | ||
471 | if (IS_870(sd)) | ||
472 | stv06xx_write_bridge(sd, STV_STV0600_EMULATION, 1); | ||
473 | |||
474 | /* Execute the bridge init */ | ||
475 | for (i = 0; i < ARRAY_SIZE(stv_bridge_init) && !err; i++) { | ||
476 | err = stv06xx_write_bridge(sd, stv_bridge_init[i][0], | ||
477 | stv_bridge_init[i][1]); | ||
478 | } | ||
479 | if (err < 0) | ||
480 | return err; | ||
481 | |||
482 | /* sensor soft reset */ | ||
483 | hdcs_reset(sd); | ||
484 | |||
485 | /* Execute the sensor init */ | ||
486 | for (i = 0; i < ARRAY_SIZE(stv_sensor_init) && !err; i++) { | ||
487 | err = stv06xx_write_sensor(sd, stv_sensor_init[i][0], | ||
488 | stv_sensor_init[i][1]); | ||
489 | } | ||
490 | if (err < 0) | ||
491 | return err; | ||
492 | |||
493 | /* Enable continous frame capture, bit 2: stop when frame complete */ | ||
494 | err = stv06xx_write_sensor(sd, HDCS_REG_CONFIG(sd), BIT(3)); | ||
495 | if (err < 0) | ||
496 | return err; | ||
497 | |||
498 | /* Set PGA sample duration | ||
499 | (was 0x7E for IS_870, but caused slow framerate with HDCS-1020) */ | ||
500 | if (IS_1020(sd)) | ||
501 | err = stv06xx_write_sensor(sd, HDCS_TCTRL, | ||
502 | (HDCS_ADC_START_SIG_DUR << 6) | hdcs->psmp); | ||
503 | else | ||
504 | err = stv06xx_write_sensor(sd, HDCS_TCTRL, | ||
505 | (HDCS_ADC_START_SIG_DUR << 5) | hdcs->psmp); | ||
506 | if (err < 0) | ||
507 | return err; | ||
508 | |||
509 | err = hdcs_set_gains(sd, HDCS_DEFAULT_GAIN, HDCS_DEFAULT_GAIN, | ||
510 | HDCS_DEFAULT_GAIN); | ||
511 | if (err < 0) | ||
512 | return err; | ||
513 | |||
514 | err = hdcs_set_exposure(&sd->gspca_dev, HDCS_DEFAULT_EXPOSURE); | ||
515 | if (err < 0) | ||
516 | return err; | ||
517 | |||
518 | err = hdcs_set_size(sd, hdcs->array.width, hdcs->array.height); | ||
519 | return err; | ||
520 | } | ||
521 | |||
522 | static int hdcs_dump(struct sd *sd) | ||
523 | { | ||
524 | u16 reg, val; | ||
525 | |||
526 | info("Dumping sensor registers:"); | ||
527 | |||
528 | for (reg = HDCS_IDENT; reg <= HDCS_ROWEXPH; reg++) { | ||
529 | stv06xx_read_sensor(sd, reg, &val); | ||
530 | info("reg 0x%02x = 0x%02x", reg, val); | ||
531 | } | ||
532 | return 0; | ||
533 | } | ||