1/*
2 * Samsung MHL interface driver
3 *
4 * Copyright (C) 2011 Samsung Electronics Co.Ltd
5 * Author: Tomasz Stanislawski <t.stanislaws@samsung.com>
6 *
7 * This program is free software; you can redistribute  it and/or modify it
8 * under  the terms of  the GNU General  Public License as published by the
9 * Free Software Foundation;  either version 2 of the  License, or (at your
10 * option) any later version.
11 */
12
13#include <linux/delay.h>
14#include <linux/err.h>
15#include <linux/freezer.h>
16#include <linux/gpio.h>
17#include <linux/i2c.h>
18#include <linux/interrupt.h>
19#include <linux/irq.h>
20#include <linux/kthread.h>
21#include <linux/module.h>
22#include <linux/pm_runtime.h>
23#include <linux/regulator/machine.h>
24#include <linux/slab.h>
25
26#include <mach/gpio.h>
27#include <plat/gpio-cfg.h>
28
29#include <media/sii9234.h>
30#include <media/v4l2-subdev.h>
31
32MODULE_AUTHOR("Tomasz Stanislawski <t.stanislaws@samsung.com>");
33MODULE_DESCRIPTION("Samsung MHL interface driver");
34MODULE_LICENSE("GPL");
35
36struct sii9234_context {
37	struct i2c_client *client;
38	struct regulator *power;
39	int gpio_n_reset;
40	struct v4l2_subdev sd;
41};
42
43static inline struct sii9234_context *sd_to_context(struct v4l2_subdev *sd)
44{
45	return container_of(sd, struct sii9234_context, sd);
46}
47
48static inline int sii9234_readb(struct i2c_client *client, int addr)
49{
50	return i2c_smbus_read_byte_data(client, addr);
51}
52
53static inline int sii9234_writeb(struct i2c_client *client, int addr, int value)
54{
55	return i2c_smbus_write_byte_data(client, addr, value);
56}
57
58static inline int sii9234_writeb_mask(struct i2c_client *client, int addr,
59	int value, int mask)
60{
61	int ret;
62
63	ret = i2c_smbus_read_byte_data(client, addr);
64	if (ret < 0)
65		return ret;
66	ret = (ret & ~mask) | (value & mask);
67	return i2c_smbus_write_byte_data(client, addr, ret);
68}
69
70static inline int sii9234_readb_idx(struct i2c_client *client, int addr)
71{
72	int ret;
73	ret = i2c_smbus_write_byte_data(client, 0xbc, addr >> 8);
74	if (ret < 0)
75		return ret;
76	ret = i2c_smbus_write_byte_data(client, 0xbd, addr & 0xff);
77	if (ret < 0)
78		return ret;
79	return i2c_smbus_read_byte_data(client, 0xbe);
80}
81
82static inline int sii9234_writeb_idx(struct i2c_client *client, int addr,
83	int value)
84{
85	int ret;
86	ret = i2c_smbus_write_byte_data(client, 0xbc, addr >> 8);
87	if (ret < 0)
88		return ret;
89	ret = i2c_smbus_write_byte_data(client, 0xbd, addr & 0xff);
90	if (ret < 0)
91		return ret;
92	ret = i2c_smbus_write_byte_data(client, 0xbe, value);
93	return ret;
94}
95
96static inline int sii9234_writeb_idx_mask(struct i2c_client *client, int addr,
97	int value, int mask)
98{
99	int ret;
100
101	ret = sii9234_readb_idx(client, addr);
102	if (ret < 0)
103		return ret;
104	ret = (ret & ~mask) | (value & mask);
105	return sii9234_writeb_idx(client, addr, ret);
106}
107
108static int sii9234_reset(struct sii9234_context *ctx)
109{
110	struct i2c_client *client = ctx->client;
111	struct device *dev = &client->dev;
112	int ret, tries;
113
114	gpio_direction_output(ctx->gpio_n_reset, 1);
115	mdelay(1);
116	gpio_direction_output(ctx->gpio_n_reset, 0);
117	mdelay(1);
118	gpio_direction_output(ctx->gpio_n_reset, 1);
119	mdelay(1);
120
121	/* going to TTPI mode */
122	ret = sii9234_writeb(client, 0xc7, 0);
123	if (ret < 0) {
124		dev_err(dev, "failed to set TTPI mode\n");
125		return ret;
126	}
127	for (tries = 0; tries < 100 ; ++tries) {
128		ret = sii9234_readb(client, 0x1b);
129		if (ret > 0)
130			break;
131		if (ret < 0) {
132			dev_err(dev, "failed to reset device\n");
133			return -EIO;
134		}
135		mdelay(1);
136	}
137	if (tries == 100) {
138		dev_err(dev, "maximal number of tries reached\n");
139		return -EIO;
140	}
141
142	return 0;
143}
144
145static int sii9234_verify_version(struct i2c_client *client)
146{
147	struct device *dev = &client->dev;
148	int family, rev, tpi_rev, dev_id, sub_id, hdcp, id;
149
150	family = sii9234_readb(client, 0x1b);
151	rev = sii9234_readb(client, 0x1c) & 0x0f;
152	tpi_rev = sii9234_readb(client, 0x1d) & 0x7f;
153	dev_id = sii9234_readb_idx(client, 0x0103);
154	sub_id = sii9234_readb_idx(client, 0x0102);
155	hdcp = sii9234_readb(client, 0x30);
156
157	if (family < 0 || rev < 0 || tpi_rev < 0 || dev_id < 0 ||
158		sub_id < 0 || hdcp < 0) {
159		dev_err(dev, "failed to read chip's version\n");
160		return -EIO;
161	}
162
163	id = (dev_id << 8) | sub_id;
164
165	dev_info(dev, "chip: SiL%02x family: %02x, rev: %02x\n",
166		id, family, rev);
167	dev_info(dev, "tpi_rev:%02x, hdcp: %02x\n", tpi_rev, hdcp);
168	if (id != 0x9234) {
169		dev_err(dev, "not supported chip\n");
170		return -ENODEV;
171	}
172
173	return 0;
174}
175
176static u8 data[][3] = {
177/* setup from driver created by doonsoo45.kim */
178	{ 0x01, 0x05, 0x04 }, /* Enable Auto soft reset on SCDT = 0 */
179	{ 0x01, 0x08, 0x35 }, /* Power Up TMDS Tx Core */
180	{ 0x01, 0x0d, 0x1c }, /* HDMI Transcode mode enable */
181	{ 0x01, 0x2b, 0x01 }, /* Enable HDCP Compliance workaround */
182	{ 0x01, 0x79, 0x40 }, /* daniel test...MHL_INT */
183	{ 0x01, 0x80, 0x34 }, /* Enable Rx PLL Clock Value */
184	{ 0x01, 0x90, 0x27 }, /* Enable CBUS discovery */
185	{ 0x01, 0x91, 0xe5 }, /* Skip RGND detection */
186	{ 0x01, 0x92, 0x46 }, /* Force MHD mode */
187	{ 0x01, 0x93, 0xdc }, /* Disable CBUS pull-up during RGND measurement */
188	{ 0x01, 0x94, 0x66 }, /* 1.8V CBUS VTH & GND threshold */
189	{ 0x01, 0x95, 0x31 }, /* RGND block & single discovery attempt */
190	{ 0x01, 0x96, 0x22 }, /* use 1K and 2K setting */
191	{ 0x01, 0xa0, 0x10 }, /* SIMG: Term mode */
192	{ 0x01, 0xa1, 0xfc }, /* Disable internal Mobile HD driver */
193	{ 0x01, 0xa3, 0xfa }, /* SIMG: Output Swing  default EB, 3x Clk Mult */
194	{ 0x01, 0xa5, 0x80 }, /* SIMG: RGND Hysterisis, 3x mode for Beast */
195	{ 0x01, 0xa6, 0x0c }, /* SIMG: Swing Offset */
196	{ 0x02, 0x3d, 0x3f }, /* Power up CVCC 1.2V core */
197	{ 0x03, 0x00, 0x00 }, /* SIMG: correcting HW default */
198	{ 0x03, 0x11, 0x01 }, /* Enable TxPLL Clock */
199	{ 0x03, 0x12, 0x15 }, /* Enable Tx Clock Path & Equalizer */
200	{ 0x03, 0x13, 0x60 }, /* SIMG: Set termination value */
201	{ 0x03, 0x14, 0xf0 }, /* SIMG: Change CKDT level */
202	{ 0x03, 0x17, 0x07 }, /* SIMG: PLL Calrefsel */
203	{ 0x03, 0x1a, 0x20 }, /* VCO Cal */
204	{ 0x03, 0x22, 0xe0 }, /* SIMG: Auto EQ */
205	{ 0x03, 0x23, 0xc0 }, /* SIMG: Auto EQ */
206	{ 0x03, 0x24, 0xa0 }, /* SIMG: Auto EQ */
207	{ 0x03, 0x25, 0x80 }, /* SIMG: Auto EQ */
208	{ 0x03, 0x26, 0x60 }, /* SIMG: Auto EQ */
209	{ 0x03, 0x27, 0x40 }, /* SIMG: Auto EQ */
210	{ 0x03, 0x28, 0x20 }, /* SIMG: Auto EQ */
211	{ 0x03, 0x29, 0x00 }, /* SIMG: Auto EQ */
212	{ 0x03, 0x31, 0x0b }, /* SIMG: Rx PLL BW value from I2C BW ~ 4MHz */
213	{ 0x03, 0x45, 0x06 }, /* SIMG: DPLL Mode */
214	{ 0x03, 0x4b, 0x06 }, /* SIMG: Correcting HW default */
215	{ 0x03, 0x4c, 0xa0 }, /* Manual zone control */
216	{ 0x03, 0x4d, 0x02 }, /* SIMG: PLL Mode Value (order is important) */
217};
218
219static int sii9234_set_internal(struct sii9234_context *ctx)
220{
221	struct i2c_client *client = ctx->client;
222	int i, ret;
223
224	for (i = 0; i < ARRAY_SIZE(data); ++i) {
225		int addr = (data[i][0] << 8) | data[i][1];
226		ret = sii9234_writeb_idx(client, addr, data[i][2]);
227		if (ret < 0)
228			return ret;
229	}
230	return 0;
231}
232
233static int sii9234_runtime_suspend(struct device *dev)
234{
235	struct v4l2_subdev *sd = dev_get_drvdata(dev);
236	struct sii9234_context *ctx = sd_to_context(sd);
237	struct i2c_client *client = ctx->client;
238
239	dev_info(dev, "suspend start\n");
240
241	sii9234_writeb_mask(client, 0x1e, 3, 3);
242	regulator_disable(ctx->power);
243
244	return 0;
245}
246
247static int sii9234_runtime_resume(struct device *dev)
248{
249	struct v4l2_subdev *sd = dev_get_drvdata(dev);
250	struct sii9234_context *ctx = sd_to_context(sd);
251	struct i2c_client *client = ctx->client;
252	int ret;
253
254	dev_info(dev, "resume start\n");
255	regulator_enable(ctx->power);
256
257	ret = sii9234_reset(ctx);
258	if (ret)
259		goto fail;
260
261	/* enable tpi */
262	ret = sii9234_writeb_mask(client, 0x1e, 1, 0);
263	if (ret < 0)
264		goto fail;
265	ret = sii9234_set_internal(ctx);
266	if (ret < 0)
267		goto fail;
268
269	return 0;
270
271fail:
272	dev_err(dev, "failed to resume\n");
273	regulator_disable(ctx->power);
274
275	return ret;
276}
277
278static const struct dev_pm_ops sii9234_pm_ops = {
279	.runtime_suspend = sii9234_runtime_suspend,
280	.runtime_resume	 = sii9234_runtime_resume,
281};
282
283static int sii9234_s_power(struct v4l2_subdev *sd, int on)
284{
285	struct sii9234_context *ctx = sd_to_context(sd);
286	int ret;
287
288	if (on)
289		ret = pm_runtime_get_sync(&ctx->client->dev);
290	else
291		ret = pm_runtime_put(&ctx->client->dev);
292	/* only values < 0 indicate errors */
293	return IS_ERR_VALUE(ret) ? ret : 0;
294}
295
296static int sii9234_s_stream(struct v4l2_subdev *sd, int enable)
297{
298	struct sii9234_context *ctx = sd_to_context(sd);
299
300	/* (dis/en)able TDMS output */
301	sii9234_writeb_mask(ctx->client, 0x1a, enable ? 0 : ~0 , 1 << 4);
302	return 0;
303}
304
305static const struct v4l2_subdev_core_ops sii9234_core_ops = {
306	.s_power =  sii9234_s_power,
307};
308
309static const struct v4l2_subdev_video_ops sii9234_video_ops = {
310	.s_stream =  sii9234_s_stream,
311};
312
313static const struct v4l2_subdev_ops sii9234_ops = {
314	.core = &sii9234_core_ops,
315	.video = &sii9234_video_ops,
316};
317
318static int __devinit sii9234_probe(struct i2c_client *client,
319	const struct i2c_device_id *id)
320{
321	struct device *dev = &client->dev;
322	struct sii9234_platform_data *pdata = dev->platform_data;
323	struct sii9234_context *ctx;
324	int ret;
325
326	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
327	if (!ctx) {
328		dev_err(dev, "out of memory\n");
329		ret = -ENOMEM;
330		goto fail;
331	}
332	ctx->client = client;
333
334	ctx->power = regulator_get(dev, "hdmi-en");
335	if (IS_ERR(ctx->power)) {
336		dev_err(dev, "failed to acquire regulator hdmi-en\n");
337		ret = PTR_ERR(ctx->power);
338		goto fail_ctx;
339	}
340
341	ctx->gpio_n_reset = pdata->gpio_n_reset;
342	ret = gpio_request(ctx->gpio_n_reset, "MHL_RST");
343	if (ret) {
344		dev_err(dev, "failed to acquire MHL_RST gpio\n");
345		goto fail_power;
346	}
347
348	v4l2_i2c_subdev_init(&ctx->sd, client, &sii9234_ops);
349
350	pm_runtime_enable(dev);
351
352	/* enable device */
353	ret = pm_runtime_get_sync(dev);
354	if (ret)
355		goto fail_pm;
356
357	/* verify chip version */
358	ret = sii9234_verify_version(client);
359	if (ret)
360		goto fail_pm_get;
361
362	/* stop processing */
363	pm_runtime_put(dev);
364
365	dev_info(dev, "probe successful\n");
366
367	return 0;
368
369fail_pm_get:
370	pm_runtime_put_sync(dev);
371
372fail_pm:
373	pm_runtime_disable(dev);
374	gpio_free(ctx->gpio_n_reset);
375
376fail_power:
377	regulator_put(ctx->power);
378
379fail_ctx:
380	kfree(ctx);
381
382fail:
383	dev_err(dev, "probe failed\n");
384
385	return ret;
386}
387
388static int __devexit sii9234_remove(struct i2c_client *client)
389{
390	struct device *dev = &client->dev;
391	struct v4l2_subdev *sd = i2c_get_clientdata(client);
392	struct sii9234_context *ctx = sd_to_context(sd);
393
394	pm_runtime_disable(dev);
395	gpio_free(ctx->gpio_n_reset);
396	regulator_put(ctx->power);
397	kfree(ctx);
398
399	dev_info(dev, "remove successful\n");
400
401	return 0;
402}
403
404
405static const struct i2c_device_id sii9234_id[] = {
406	{ "SII9234", 0 },
407	{ },
408};
409
410MODULE_DEVICE_TABLE(i2c, sii9234_id);
411static struct i2c_driver sii9234_driver = {
412	.driver = {
413		.name	= "sii9234",
414		.owner	= THIS_MODULE,
415		.pm = &sii9234_pm_ops,
416	},
417	.probe		= sii9234_probe,
418	.remove		= __devexit_p(sii9234_remove),
419	.id_table = sii9234_id,
420};
421
422static int __init sii9234_init(void)
423{
424	return i2c_add_driver(&sii9234_driver);
425}
426module_init(sii9234_init);
427
428static void __exit sii9234_exit(void)
429{
430	i2c_del_driver(&sii9234_driver);
431}
432module_exit(sii9234_exit);
433