summaryrefslogtreecommitdiff
path: root/drivers/media/video/tegra/tps61050.c
blob: cc442324fdc76584ec9c97f6913ed246a589a902 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
/*
 * tps61050.c - tps61050 flash/torch kernel driver
 *
 * Copyright (C) 2011 NVIDIA Corporation.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307, USA
 */

/* Implementation
 * --------------
 * The board level details about the device need to be provided in the board
 * file with the tps61050_platform_data structure.
 * Standard among NVC kernel drivers in this structure is:
 * .cfg = Use the NVC_CFG_ defines that are in nvc_torch.h.
 *        Descriptions of the configuration options are with the defines.
 *        This value is typically 0.
 * .num = The number of the instance of the device.  This should start at 1 and
 *        and increment for each device on the board.  This number will be
 *        appended to the MISC driver name, Example: /dev/tps61050.1
 * .sync = If there is a need to synchronize two devices, then this value is
 *         the number of the device instance this device is allowed to sync to.
 *         This is typically used for stereo applications.
 * .dev_name = The MISC driver name the device registers as.  If not used,
 *             then the part number of the device is used for the driver name.
 *             If using the NVC user driver then use the name found in this
 *             driver under _default_pdata.
 *
 * The following is specific to NVC kernel flash/torch drivers:
 * .pinstate = a pointer to the nvc_torch_pin_state structure.  This
 *             structure gives the details of which VI GPIO to use to trigger
 *             the flash.  The mask tells which pin and the values is the
 *             level.  For example, if VI GPIO pin 6 is used, then
 *             .mask = 0x0040
 *             .values = 0x0040
 *             If VI GPIO pin 0 is used, then
 *             .mask = 0x0001
 *             .values = 0x0001
 *             This is typically just one pin but there is some legacy
 *             here that insinuates more than one pin can be used.
 *             When the flash level is set, then the driver will return the
 *             value in values.  When the flash level is off, the driver will
 *             return 0 for the values to deassert the signal.
 *             If a VI GPIO is not used, then the mask and values must be set
 *             to 0.  The flash may then be triggered via I2C instead.
 *             However, a VI GPIO is strongly encouraged since it allows
 *             tighter timing with the picture taken as well as reduced power
 *             by asserting the trigger signal for only when needed.
 * .max_amp_torch = Is the maximum torch value allowed.  The value is 0 to
 *                  _MAX_TORCH_LEVEL.  This is to allow a limit to the amount
 *                  of amps used.  If left blank then _MAX_TORCH_LEVEL will be
 *                  used.
 * .max_amp_flash = Is the maximum flash value allowed.  The value is 0 to
 *                  _MAX_FLASH_LEVEL.  This is to allow a limit to the amount
 *                  of amps used.  If left blank then _MAX_FLASH_LEVEL will be
 *                  used.
 *
 * The following is specific to only this NVC kernel flash/torch driver:
 * N/A
 *
 * Power Requirements
 * The board power file must contain the following labels for the power
 * regulator(s) of this device:
 * "vdd_i2c" = the power regulator for the I2C power.
 * Note that this device is typically connected directly to the battery rail
 * and does not need a source power regulator (vdd).
 *
 * The above values should be all that is needed to use the device with this
 * driver.  Modifications of this driver should not be needed.
 */


#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/regulator/consumer.h>
#include <linux/module.h>
#include <media/nvc.h>
#include <media/tps61050.h>

#define TPS61050_REG0		0x00
#define TPS61050_REG1		0x01
#define TPS61050_REG2		0x02
#define TPS61050_REG3		0x03
#define tps61050_flash_cap_size	(sizeof(tps61050_flash_cap.numberoflevels) \
				+ (sizeof(tps61050_flash_cap.levels[0]) \
				* (TPS61050_MAX_FLASH_LEVEL + 1)))
#define tps61050_torch_cap_size	(sizeof(tps61050_torch_cap.numberoflevels) \
				+ (sizeof(tps61050_torch_cap.guidenum[0]) \
				* (TPS61050_MAX_TORCH_LEVEL + 1)))


static struct nvc_torch_flash_capabilities tps61050_flash_cap = {
	TPS61050_MAX_FLASH_LEVEL + 1,
	{
		{ 0,   0xFFFFFFFF, 0 },
		{ 150, 558,        2 },
		{ 200, 558,        2 },
		{ 300, 558,        2 },
		{ 400, 558,        2 },
		{ 500, 558,        2 },
		{ 700, 558,        2 },
		{ 900, 558,        2 },
		{ 900, 558,        2 }
	}
};

static struct nvc_torch_torch_capabilities tps61050_torch_cap = {
	TPS61050_MAX_TORCH_LEVEL + 1,
	{
		0,
		50,
		75,
		100,
		150,
		200,
		491,
		491
	}
};

struct tps61050_info {
	atomic_t in_use;
	struct i2c_client *i2c_client;
	struct tps61050_platform_data *pdata;
	struct miscdevice miscdev;
	struct list_head list;
	int pwr_api;
	int pwr_dev;
	struct nvc_regulator vreg_i2c;
	u8 s_mode;
	struct tps61050_info *s_info;
};

static struct nvc_torch_pin_state tps61050_default_pinstate = {
	.mask		= 0x0000,
	.values		= 0x0000,
};

static struct tps61050_platform_data tps61050_default_pdata = {
	.cfg		= 0,
	.num		= 0,
	.sync		= 0,
	.dev_name	= "torch",
	.pinstate	= &tps61050_default_pinstate,
	.max_amp_torch	= TPS61050_MAX_TORCH_LEVEL,
	.max_amp_flash	= TPS61050_MAX_FLASH_LEVEL,
};

static LIST_HEAD(tps61050_info_list);
static DEFINE_SPINLOCK(tps61050_spinlock);


static int tps61050_i2c_rd(struct tps61050_info *info, u8 reg, u8 *val)
{
	struct i2c_msg msg[2];
	u8 buf[2];

	buf[0] = reg;
	msg[0].addr = info->i2c_client->addr;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &buf[0];
	msg[1].addr = info->i2c_client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].len = 1;
	msg[1].buf = &buf[1];
	*val = 0;
	if (i2c_transfer(info->i2c_client->adapter, msg, 2) != 2)
		return -EIO;

	*val = buf[1];
	return 0;
}

static int tps61050_i2c_wr(struct tps61050_info *info, u8 reg, u8 val)
{
	struct i2c_msg msg;
	u8 buf[2];

	buf[0] = reg;
	buf[1] = val;
	msg.addr = info->i2c_client->addr;
	msg.flags = 0;
	msg.len = 2;
	msg.buf = &buf[0];
	if (i2c_transfer(info->i2c_client->adapter, &msg, 1) != 1)
		return -EIO;

	return 0;
}

static void tps61050_pm_regulator_put(struct nvc_regulator *sreg)
{
	regulator_put(sreg->vreg);
	sreg->vreg = NULL;
}

static int tps61050_pm_regulator_get(struct tps61050_info *info,
				     struct nvc_regulator *sreg,
				     char vreg_name[])
{
	int err = 0;

	sreg->vreg_flag = 0;
	sreg->vreg = regulator_get(&info->i2c_client->dev, vreg_name);
	if (IS_ERR_OR_NULL(sreg->vreg)) {
		dev_err(&info->i2c_client->dev,
				"%s err for regulator: %s err: %d\n",
				__func__, vreg_name, (int)sreg->vreg);
		err = PTR_ERR(sreg->vreg);
		sreg->vreg = NULL;
	} else {
		sreg->vreg_name = vreg_name;
		dev_dbg(&info->i2c_client->dev,
				"%s vreg_name: %s\n",
				__func__, sreg->vreg_name);
	}
	return err;
}

static int tps61050_pm_regulator_en(struct tps61050_info *info,
				    struct nvc_regulator *sreg)
{
	int err = 0;

	if (!sreg->vreg_flag && (sreg->vreg != NULL)) {
		err = regulator_enable(sreg->vreg);
		if (!err) {
			dev_dbg(&info->i2c_client->dev,
					"%s vreg_name: %s\n",
					__func__, sreg->vreg_name);
			sreg->vreg_flag = 1;
			err = 1; /* flag regulator state change */
			mdelay(5); /* device powerup delay */
		} else {
			dev_err(&info->i2c_client->dev,
					"%s err, regulator: %s\n",
					__func__, sreg->vreg_name);
		}
	}
	return err;
}

static int tps61050_pm_regulator_dis(struct tps61050_info *info,
				     struct nvc_regulator *sreg)
{
	int err = 0;

	if (sreg->vreg_flag && (sreg->vreg != NULL)) {
		err = regulator_disable(sreg->vreg);
		if (!err)
			dev_dbg(&info->i2c_client->dev,
					"%s vreg_name: %s\n",
					__func__, sreg->vreg_name);
		else
			dev_err(&info->i2c_client->dev,
					"%s err, regulator: %s\n",
					__func__, sreg->vreg_name);
	}
	sreg->vreg_flag = 0;
	return err;
}

static int tps61050_pm_wr(struct tps61050_info *info, int pwr)
{
	int err = 0;
	u8 reg;

	if (pwr == info->pwr_dev)
		return 0;

	switch (pwr) {
	case NVC_PWR_OFF:
		if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) ||
			     (info->pdata->cfg & NVC_CFG_BOOT_INIT)) {
			pwr = NVC_PWR_STDBY;
		} else {
			err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
			err |= tps61050_i2c_wr(info, TPS61050_REG0, 0x00);
			err |= tps61050_i2c_wr(info, TPS61050_REG1, 0x00);
			err |= tps61050_pm_regulator_dis(info, &info->vreg_i2c);
			break;
		}
	case NVC_PWR_STDBY_OFF:
		if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) ||
			     (info->pdata->cfg & NVC_CFG_BOOT_INIT)) {
			pwr = NVC_PWR_STDBY;
		} else {
			err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
			err |= tps61050_i2c_wr(info, TPS61050_REG0, 0x00);
			err |= tps61050_i2c_wr(info, TPS61050_REG1, 0x00);
			break;
		}
	case NVC_PWR_STDBY:
		err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
		err |= tps61050_i2c_rd(info, TPS61050_REG0, &reg);
		reg &= 0x3F; /* 7:6 = mode */
		err |= tps61050_i2c_wr(info, TPS61050_REG0, reg);
		break;

	case NVC_PWR_COMM:
	case NVC_PWR_ON:
		err = tps61050_pm_regulator_en(info, &info->vreg_i2c);
		break;

	default:
		err = -EINVAL;
		break;
	}

	if (err < 0) {
		dev_err(&info->i2c_client->dev, "%s error\n", __func__);
		pwr = NVC_PWR_ERR;
	}
	info->pwr_dev = pwr;
	if (err > 0)
		return 0;

	return err;
}

static int tps61050_pm_wr_s(struct tps61050_info *info, int pwr)
{
	int err1 = 0;
	int err2 = 0;

	if ((info->s_mode == NVC_SYNC_OFF) ||
			(info->s_mode == NVC_SYNC_MASTER) ||
			(info->s_mode == NVC_SYNC_STEREO))
		err1 = tps61050_pm_wr(info, pwr);
	if ((info->s_mode == NVC_SYNC_SLAVE) ||
			(info->s_mode == NVC_SYNC_STEREO))
		err2 = tps61050_pm_wr(info->s_info, pwr);
	return err1 | err2;
}

static int tps61050_pm_api_wr(struct tps61050_info *info, int pwr)
{
	int err = 0;

	if (!pwr || (pwr > NVC_PWR_ON))
		return 0;

	if (pwr > info->pwr_dev)
		err = tps61050_pm_wr_s(info, pwr);
	if (!err)
		info->pwr_api = pwr;
	else
		info->pwr_api = NVC_PWR_ERR;
	if (info->pdata->cfg & NVC_CFG_NOERR)
		return 0;

	return err;
}

static int tps61050_pm_dev_wr(struct tps61050_info *info, int pwr)
{
	if (pwr < info->pwr_api)
		pwr = info->pwr_api;
	return tps61050_pm_wr(info, pwr);
}

static void tps61050_pm_exit(struct tps61050_info *info)
{
	tps61050_pm_wr_s(info, NVC_PWR_OFF);
	tps61050_pm_regulator_put(&info->vreg_i2c);
}

static void tps61050_pm_init(struct tps61050_info *info)
{
	tps61050_pm_regulator_get(info, &info->vreg_i2c, "vdd_i2c");
}

struct tps61050_reg_init {
	u8 mask;
	u8 val;
};

static struct tps61050_reg_init tps61050_reg_init_id[] = {
	{0xC0, 0x00},
	{0xC0, 0x00},
	{0x87, 0x00},
	{0xFF, 0xD1},
};

static int tps61050_dev_id(struct tps61050_info *info)
{
	u8 reg;
	u8 i;
	int err;

	tps61050_pm_dev_wr(info, NVC_PWR_COMM);
	/* There isn't a device ID so we just check that all the registers
	 * equal their startup defaults.
	 */
	for (i = TPS61050_REG0; i <= TPS61050_REG3; i++) {
		err = tps61050_i2c_rd(info, i, &reg);
		if (err) {
			break;
		} else {
			reg &= tps61050_reg_init_id[i].mask;
			if (reg != tps61050_reg_init_id[i].val) {
				err = -ENODEV;
				break;
			}
		}
	}
	tps61050_pm_dev_wr(info, NVC_PWR_OFF);
	return err;
}

static int tps61050_param_rd(struct tps61050_info *info, long arg)
{
	struct nvc_param params;
	struct nvc_torch_pin_state pinstate;
	const void *data_ptr;
	u8 reg;
	u32 data_size = 0;
	int err;

	if (copy_from_user(&params,
			(const void __user *)arg,
			sizeof(struct nvc_param))) {
		dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
				__func__, __LINE__);
		return -EINVAL;
	}

	if (info->s_mode == NVC_SYNC_SLAVE)
		info = info->s_info;
	switch (params.param) {
	case NVC_PARAM_FLASH_CAPS:
		dev_dbg(&info->i2c_client->dev, "%s FLASH_CAPS\n", __func__);
		data_ptr = &tps61050_flash_cap;
		data_size = tps61050_flash_cap_size;
		break;

	case NVC_PARAM_FLASH_LEVEL:
		tps61050_pm_dev_wr(info, NVC_PWR_COMM);
		err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
		tps61050_pm_dev_wr(info, NVC_PWR_OFF);
		if (err < 0)
			return err;

		if (reg & 0x80) { /* 7:7 flash on/off */
			reg &= 0x07; /* 2:0 flash setting */
			reg++; /* flash setting +1 if flash on */
		} else {
			reg = 0; /* flash is off */
		}
		dev_dbg(&info->i2c_client->dev, "%s FLASH_LEVEL: %u\n",
			    __func__,
			    (unsigned)tps61050_flash_cap.levels[reg].guidenum);
		data_ptr = &tps61050_flash_cap.levels[reg].guidenum;
		data_size = sizeof(tps61050_flash_cap.levels[reg].guidenum);
		break;

	case NVC_PARAM_TORCH_CAPS:
		dev_dbg(&info->i2c_client->dev, "%s TORCH_CAPS\n", __func__);
		data_ptr = &tps61050_torch_cap;
		data_size = tps61050_torch_cap_size;
		break;

	case NVC_PARAM_TORCH_LEVEL:
		tps61050_pm_dev_wr(info, NVC_PWR_COMM);
		err = tps61050_i2c_rd(info, TPS61050_REG0, &reg);
		tps61050_pm_dev_wr(info, NVC_PWR_OFF);
		if (err < 0)
			return err;

		reg &= 0x07;
		dev_dbg(&info->i2c_client->dev, "%s TORCH_LEVEL: %u\n",
				__func__,
				(unsigned)tps61050_torch_cap.guidenum[reg]);
		data_ptr = &tps61050_torch_cap.guidenum[reg];
		data_size = sizeof(tps61050_torch_cap.guidenum[reg]);
		break;

	case NVC_PARAM_FLASH_PIN_STATE:
		pinstate.mask = info->pdata->pinstate->mask;
		tps61050_pm_dev_wr(info, NVC_PWR_COMM);
		err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
		tps61050_pm_dev_wr(info, NVC_PWR_OFF);
		if (err < 0)
			return err;

		reg &= 0x80; /* 7:7=flash enable */
		if (reg)
			/* assert strobe */
			pinstate.values = info->pdata->pinstate->values;
		else
			pinstate.values = 0; /* deassert strobe */
		dev_dbg(&info->i2c_client->dev, "%s FLASH_PIN_STATE: %x&%x\n",
				__func__, pinstate.mask, pinstate.values);
		data_ptr = &pinstate;
		data_size = sizeof(struct nvc_torch_pin_state);
		break;

	case NVC_PARAM_STEREO:
		dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n",
				__func__, (int)info->s_mode);
		data_ptr = &info->s_mode;
		data_size = sizeof(info->s_mode);
		break;

	default:
		dev_err(&info->i2c_client->dev,
				"%s unsupported parameter: %d\n",
				__func__, params.param);
		return -EINVAL;
	}

	if (params.sizeofvalue < data_size) {
		dev_err(&info->i2c_client->dev,
				"%s data size mismatch %d != %d\n",
				__func__, params.sizeofvalue, data_size);
		return -EINVAL;
	}

	if (copy_to_user((void __user *)params.p_value,
			 data_ptr,
			 data_size)) {
		dev_err(&info->i2c_client->dev,
				"%s copy_to_user err line %d\n",
				__func__, __LINE__);
		return -EFAULT;
	}

	return 0;
}

static int tps61050_param_wr_s(struct tps61050_info *info,
			       struct nvc_param *params,
			       u8 val)
{
	u8 reg;
	int err = 0;

	/*
	 * 7:6 flash/torch mode
	 * 0 0 = off (power save)
	 * 0 1 = torch only (torch power is 2:0 REG0 where 0 = off)
	 * 1 0 = flash and torch (flash power is 2:0 REG1 (0 is a power level))
	 * 1 1 = N/A
	 * Note that 7:6 of REG0 and REG1 are shadowed with each other.
	 * In the code below we want to turn on/off one
	 * without affecting the other.
	 */
	switch (params->param) {
	case NVC_PARAM_FLASH_LEVEL:
		dev_dbg(&info->i2c_client->dev, "%s FLASH_LEVEL: %d\n",
				__func__, val);
		tps61050_pm_dev_wr(info, NVC_PWR_ON);
		if (val) {
			val--;
			if (val > tps61050_default_pdata.max_amp_flash)
				val = tps61050_default_pdata.max_amp_flash;
			/* Amp limit values are in the board-sensors file. */
			if (info->pdata->max_amp_flash &&
					(val > info->pdata->max_amp_flash))
				val = info->pdata->max_amp_flash;
			val |= 0x80; /* 7:7=flash mode */
		} else {
			err = tps61050_i2c_rd(info, TPS61050_REG0, &reg);
			if (reg & 0x07) /* 2:0=torch setting */
				val = 0x40; /* 6:6 enable just torch */
		}
		err |= tps61050_i2c_wr(info, TPS61050_REG1, val);
		val &= 0xC0; /* 7:6=flash/torch mode */
		if (!val) /* turn pwr off if no flash && no pwr_api */
			tps61050_pm_dev_wr(info, NVC_PWR_OFF);
		return err;

	case NVC_PARAM_TORCH_LEVEL:
		dev_dbg(&info->i2c_client->dev, "%s TORCH_LEVEL: %d\n",
				__func__, val);
		tps61050_pm_dev_wr(info, NVC_PWR_ON);
		err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
		reg &= 0x80; /* 7:7=flash */
		if (val) {
			if (val > tps61050_default_pdata.max_amp_torch)
				val = tps61050_default_pdata.max_amp_torch;
			/* Amp limit values are in the board-sensors file. */
			if (info->pdata->max_amp_torch &&
					(val > info->pdata->max_amp_torch))
				val = info->pdata->max_amp_torch;
			if (!reg) /* test if flash/torch off */
				val |= (0x40); /* 6:6=torch only mode */
		} else {
			val |= reg;
		}
		err |= tps61050_i2c_wr(info, TPS61050_REG0, val);
		val &= 0xC0; /* 7:6=mode */
		if (!val) /* turn pwr off if no torch && no pwr_api */
			tps61050_pm_dev_wr(info, NVC_PWR_OFF);
		return err;

	case NVC_PARAM_FLASH_PIN_STATE:
		dev_dbg(&info->i2c_client->dev, "%s FLASH_PIN_STATE: %d\n",
				__func__, val);
		if (val)
			val = 0x08; /* 3:3=soft trigger */
		err = tps61050_i2c_rd(info, TPS61050_REG1, &reg);
		val |= reg;
		err |= tps61050_i2c_wr(info, TPS61050_REG1, val);
		return err;

	default:
		dev_err(&info->i2c_client->dev,
				"%s unsupported parameter: %d\n",
				__func__, params->param);
		return -EINVAL;
	}
}

static int tps61050_param_wr(struct tps61050_info *info, long arg)
{
	struct nvc_param params;
	u8 val;
	int err = 0;

	if (copy_from_user(&params, (const void __user *)arg,
			   sizeof(struct nvc_param))) {
		dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
				__func__, __LINE__);
		return -EINVAL;
	}

	if (copy_from_user(&val, (const void __user *)params.p_value,
			   sizeof(val))) {
		dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
				__func__, __LINE__);
		return -EINVAL;
	}

	/* parameters independent of sync mode */
	switch (params.param) {
	case NVC_PARAM_STEREO:
		dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n",
				__func__, (int)val);
		if (val == info->s_mode)
			return 0;

		switch (val) {
		case NVC_SYNC_OFF:
			info->s_mode = val;
			if (info->s_info != NULL) {
				info->s_info->s_mode = val;
				tps61050_pm_wr(info->s_info, NVC_PWR_OFF);
			}
			break;

		case NVC_SYNC_MASTER:
			info->s_mode = val;
			if (info->s_info != NULL)
				info->s_info->s_mode = val;
			break;

		case NVC_SYNC_SLAVE:
		case NVC_SYNC_STEREO:
			if (info->s_info != NULL) {
				/* sync power */
				info->s_info->pwr_api = info->pwr_api;
				err = tps61050_pm_wr(info->s_info,
						     info->pwr_dev);
				if (!err) {
					info->s_mode = val;
					info->s_info->s_mode = val;
				} else {
					tps61050_pm_wr(info->s_info,
						       NVC_PWR_OFF);
					err = -EIO;
				}
			} else {
				err = -EINVAL;
			}
			break;

		default:
			err = -EINVAL;
		}
		if (info->pdata->cfg & NVC_CFG_NOERR)
			return 0;

		return err;

	default:
	/* parameters dependent on sync mode */
		switch (info->s_mode) {
		case NVC_SYNC_OFF:
		case NVC_SYNC_MASTER:
			return tps61050_param_wr_s(info, &params, val);

		case NVC_SYNC_SLAVE:
			return tps61050_param_wr_s(info->s_info,
						 &params,
						 val);

		case NVC_SYNC_STEREO:
			err = tps61050_param_wr_s(info, &params, val);
			if (!(info->pdata->cfg & NVC_CFG_SYNC_I2C_MUX))
				err |= tps61050_param_wr_s(info->s_info,
							 &params,
							 val);
			return err;

		default:
			dev_err(&info->i2c_client->dev, "%s %d internal err\n",
					__func__, __LINE__);
			return -EINVAL;
		}
	}
}

static long tps61050_ioctl(struct file *file,
		unsigned int cmd,
		unsigned long arg)
{
	struct tps61050_info *info = file->private_data;
	int pwr;

	switch (cmd) {
	case NVC_IOCTL_PARAM_WR:
		return tps61050_param_wr(info, arg);

	case NVC_IOCTL_PARAM_RD:
		return tps61050_param_rd(info, arg);

	case NVC_IOCTL_PWR_WR:
		/* This is a Guaranteed Level of Service (GLOS) call */
		pwr = (int)arg * 2;
		dev_dbg(&info->i2c_client->dev, "%s PWR_WR: %d\n",
				__func__, pwr);
		return tps61050_pm_api_wr(info, pwr);

	case NVC_IOCTL_PWR_RD:
		if (info->s_mode == NVC_SYNC_SLAVE)
			pwr = info->s_info->pwr_api / 2;
		else
			pwr = info->pwr_api / 2;
		dev_dbg(&info->i2c_client->dev, "%s PWR_RD: %d\n",
				__func__, pwr);
		if (copy_to_user((void __user *)arg, (const void *)&pwr,
				 sizeof(pwr))) {
			dev_err(&info->i2c_client->dev,
					"%s copy_to_user err line %d\n",
					__func__, __LINE__);
			return -EFAULT;
		}
		return 0;

	default:
		dev_err(&info->i2c_client->dev, "%s unsupported ioctl: %x\n",
				__func__, cmd);
		return -EINVAL;
	}
}

static int tps61050_sync_en(int dev1, int dev2)
{
	struct tps61050_info *sync1 = NULL;
	struct tps61050_info *sync2 = NULL;
	struct tps61050_info *pos = NULL;

	rcu_read_lock();
	list_for_each_entry_rcu(pos, &tps61050_info_list, list) {
		if (pos->pdata->num == dev1) {
			sync1 = pos;
			break;
		}
	}
	pos = NULL;
	list_for_each_entry_rcu(pos, &tps61050_info_list, list) {
		if (pos->pdata->num == dev2) {
			sync2 = pos;
			break;
		}
	}
	rcu_read_unlock();
	if (sync1 != NULL)
		sync1->s_info = NULL;
	if (sync2 != NULL)
		sync2->s_info = NULL;
	if (!dev1 && !dev2)
		return 0; /* no err if default instance 0's used */

	if (dev1 == dev2)
		return -EINVAL; /* err if sync instance is itself */

	if ((sync1 != NULL) && (sync2 != NULL)) {
		sync1->s_info = sync2;
		sync2->s_info = sync1;
	}
	return 0;
}

static int tps61050_sync_dis(struct tps61050_info *info)
{
	if (info->s_info != NULL) {
		info->s_info->s_mode = 0;
		info->s_info->s_info = NULL;
		info->s_mode = 0;
		info->s_info = NULL;
		return 0;
	}

	return -EINVAL;
}

static int tps61050_open(struct inode *inode, struct file *file)
{
	struct tps61050_info *info = NULL;
	struct tps61050_info *pos = NULL;
	int err;

	rcu_read_lock();
	list_for_each_entry_rcu(pos, &tps61050_info_list, list) {
		if (pos->miscdev.minor == iminor(inode)) {
			info = pos;
			break;
		}
	}
	rcu_read_unlock();
	if (!info)
		return -ENODEV;

	err = tps61050_sync_en(info->pdata->num, info->pdata->sync);
	if (err == -EINVAL)
		dev_err(&info->i2c_client->dev,
			 "%s err: invalid num (%u) and sync (%u) instance\n",
			 __func__, info->pdata->num, info->pdata->sync);
	if (atomic_xchg(&info->in_use, 1))
		return -EBUSY;

	if (info->s_info != NULL) {
		if (atomic_xchg(&info->s_info->in_use, 1))
			return -EBUSY;
	}

	file->private_data = info;
	dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
	return 0;
}

static int tps61050_release(struct inode *inode, struct file *file)
{
	struct tps61050_info *info = file->private_data;

	dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
	tps61050_pm_wr_s(info, NVC_PWR_OFF);
	file->private_data = NULL;
	WARN_ON(!atomic_xchg(&info->in_use, 0));
	if (info->s_info != NULL)
		WARN_ON(!atomic_xchg(&info->s_info->in_use, 0));
	tps61050_sync_dis(info);
	return 0;
}

static const struct file_operations tps61050_fileops = {
	.owner = THIS_MODULE,
	.open = tps61050_open,
	.unlocked_ioctl = tps61050_ioctl,
	.release = tps61050_release,
};

static void tps61050_del(struct tps61050_info *info)
{
	tps61050_pm_exit(info);
	tps61050_sync_dis(info);
	spin_lock(&tps61050_spinlock);
	list_del_rcu(&info->list);
	spin_unlock(&tps61050_spinlock);
	synchronize_rcu();
}

static int tps61050_remove(struct i2c_client *client)
{
	struct tps61050_info *info = i2c_get_clientdata(client);

	dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
	misc_deregister(&info->miscdev);
	tps61050_del(info);
	return 0;
}

static int tps61050_probe(
	struct i2c_client *client,
	const struct i2c_device_id *id)
{
	struct tps61050_info *info;
	char dname[16];
	int err;

	dev_dbg(&client->dev, "%s\n", __func__);
	info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
	if (info == NULL) {
		dev_err(&client->dev, "%s: kzalloc error\n", __func__);
		return -ENOMEM;
	}

	info->i2c_client = client;
	if (client->dev.platform_data) {
		info->pdata = client->dev.platform_data;
	} else {
		info->pdata = &tps61050_default_pdata;
		dev_dbg(&client->dev,
			"%s No platform data.  Using defaults.\n",
			__func__);
	}
	i2c_set_clientdata(client, info);
	INIT_LIST_HEAD(&info->list);
	spin_lock(&tps61050_spinlock);
	list_add_rcu(&info->list, &tps61050_info_list);
	spin_unlock(&tps61050_spinlock);
	tps61050_pm_init(info);
	err = tps61050_dev_id(info);
	if (err < 0) {
		dev_err(&client->dev, "%s device not found\n", __func__);
		if (info->pdata->cfg & NVC_CFG_NODEV) {
			tps61050_del(info);
			return -ENODEV;
		}
	} else {
		dev_dbg(&client->dev, "%s device found\n", __func__);
	}

	if (info->pdata->dev_name != 0)
		strcpy(dname, info->pdata->dev_name);
	else
		strcpy(dname, "tps61050");
	if (info->pdata->num)
		snprintf(dname, sizeof(dname), "%s.%u",
			 dname, info->pdata->num);
	info->miscdev.name = dname;
	info->miscdev.fops = &tps61050_fileops;
	info->miscdev.minor = MISC_DYNAMIC_MINOR;
	if (misc_register(&info->miscdev)) {
		dev_err(&client->dev, "%s unable to register misc device %s\n",
				__func__, dname);
		tps61050_del(info);
		return -ENODEV;
	}

	return 0;
}

static const struct i2c_device_id tps61050_id[] = {
	{ "tps61050", 0 },
	{ },
};

MODULE_DEVICE_TABLE(i2c, tps61050_id);

static struct i2c_driver tps61050_i2c_driver = {
	.driver = {
		.name = "tps61050",
		.owner = THIS_MODULE,
	},
	.id_table = tps61050_id,
	.probe = tps61050_probe,
	.remove = tps61050_remove,
};

static int __init tps61050_init(void)
{
	return i2c_add_driver(&tps61050_i2c_driver);
}

static void __exit tps61050_exit(void)
{
	i2c_del_driver(&tps61050_i2c_driver);
}

module_init(tps61050_init);
module_exit(tps61050_exit);
MODULE_LICENSE("GPL");