Linux Audio

Check our new training course

Loading...
v4.6
  1/*
  2 * TPD12S015 HDMI ESD protection & level shifter chip driver
  3 *
  4 * Copyright (C) 2013 Texas Instruments
  5 * Author: Tomi Valkeinen <tomi.valkeinen@ti.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 version 2 as published by
  9 * the Free Software Foundation.
 10 */
 11
 12#include <linux/completion.h>
 13#include <linux/delay.h>
 14#include <linux/module.h>
 15#include <linux/slab.h>
 16#include <linux/platform_device.h>
 17#include <linux/gpio/consumer.h>
 
 18
 19#include <video/omapdss.h>
 20#include <video/omap-panel-data.h>
 21
 22struct panel_drv_data {
 23	struct omap_dss_device dssdev;
 24	struct omap_dss_device *in;
 
 
 
 
 25
 26	struct gpio_desc *ct_cp_hpd_gpio;
 27	struct gpio_desc *ls_oe_gpio;
 28	struct gpio_desc *hpd_gpio;
 29
 30	struct omap_video_timings timings;
 31};
 32
 33#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
 34
 35static int tpd_connect(struct omap_dss_device *dssdev,
 36		struct omap_dss_device *dst)
 37{
 38	struct panel_drv_data *ddata = to_panel_data(dssdev);
 39	struct omap_dss_device *in = ddata->in;
 40	int r;
 41
 
 
 
 
 
 
 42	r = in->ops.hdmi->connect(in, dssdev);
 43	if (r)
 
 44		return r;
 
 45
 46	dst->src = dssdev;
 47	dssdev->dst = dst;
 48
 49	gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1);
 
 
 50	/* DC-DC converter needs at max 300us to get to 90% of 5V */
 51	udelay(300);
 52
 
 53	return 0;
 54}
 55
 56static void tpd_disconnect(struct omap_dss_device *dssdev,
 57		struct omap_dss_device *dst)
 58{
 59	struct panel_drv_data *ddata = to_panel_data(dssdev);
 60	struct omap_dss_device *in = ddata->in;
 61
 62	WARN_ON(dst != dssdev->dst);
 63
 64	if (dst != dssdev->dst)
 65		return;
 66
 67	gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0);
 
 68
 69	dst->src = NULL;
 70	dssdev->dst = NULL;
 71
 72	in->ops.hdmi->disconnect(in, &ddata->dssdev);
 
 
 
 73}
 74
 75static int tpd_enable(struct omap_dss_device *dssdev)
 76{
 77	struct panel_drv_data *ddata = to_panel_data(dssdev);
 78	struct omap_dss_device *in = ddata->in;
 79	int r;
 80
 81	if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
 82		return 0;
 83
 84	in->ops.hdmi->set_timings(in, &ddata->timings);
 85
 86	r = in->ops.hdmi->enable(in);
 87	if (r)
 88		return r;
 89
 90	dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
 91
 92	return r;
 93}
 94
 95static void tpd_disable(struct omap_dss_device *dssdev)
 96{
 97	struct panel_drv_data *ddata = to_panel_data(dssdev);
 98	struct omap_dss_device *in = ddata->in;
 99
100	if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
101		return;
102
103	in->ops.hdmi->disable(in);
104
105	dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
106}
107
108static void tpd_set_timings(struct omap_dss_device *dssdev,
109		struct omap_video_timings *timings)
110{
111	struct panel_drv_data *ddata = to_panel_data(dssdev);
112	struct omap_dss_device *in = ddata->in;
113
114	ddata->timings = *timings;
115	dssdev->panel.timings = *timings;
116
117	in->ops.hdmi->set_timings(in, timings);
118}
119
120static void tpd_get_timings(struct omap_dss_device *dssdev,
121		struct omap_video_timings *timings)
122{
123	struct panel_drv_data *ddata = to_panel_data(dssdev);
124
125	*timings = ddata->timings;
126}
127
128static int tpd_check_timings(struct omap_dss_device *dssdev,
129		struct omap_video_timings *timings)
130{
131	struct panel_drv_data *ddata = to_panel_data(dssdev);
132	struct omap_dss_device *in = ddata->in;
133	int r;
134
135	r = in->ops.hdmi->check_timings(in, timings);
136
137	return r;
138}
139
140static int tpd_read_edid(struct omap_dss_device *dssdev,
141		u8 *edid, int len)
142{
143	struct panel_drv_data *ddata = to_panel_data(dssdev);
144	struct omap_dss_device *in = ddata->in;
145	int r;
146
147	if (!gpiod_get_value_cansleep(ddata->hpd_gpio))
148		return -ENODEV;
149
150	gpiod_set_value_cansleep(ddata->ls_oe_gpio, 1);
 
151
152	r = in->ops.hdmi->read_edid(in, edid, len);
 
 
 
 
153
154	gpiod_set_value_cansleep(ddata->ls_oe_gpio, 0);
 
 
 
155
156	return r;
 
 
 
 
 
 
 
 
 
 
 
 
157}
158
159static bool tpd_detect(struct omap_dss_device *dssdev)
160{
161	struct panel_drv_data *ddata = to_panel_data(dssdev);
162
163	return gpiod_get_value_cansleep(ddata->hpd_gpio);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164}
165
166static int tpd_set_infoframe(struct omap_dss_device *dssdev,
167		const struct hdmi_avi_infoframe *avi)
168{
169	struct panel_drv_data *ddata = to_panel_data(dssdev);
170	struct omap_dss_device *in = ddata->in;
171
172	return in->ops.hdmi->set_infoframe(in, avi);
173}
174
175static int tpd_set_hdmi_mode(struct omap_dss_device *dssdev,
176		bool hdmi_mode)
177{
178	struct panel_drv_data *ddata = to_panel_data(dssdev);
179	struct omap_dss_device *in = ddata->in;
180
181	return in->ops.hdmi->set_hdmi_mode(in, hdmi_mode);
182}
183
184static const struct omapdss_hdmi_ops tpd_hdmi_ops = {
185	.connect		= tpd_connect,
186	.disconnect		= tpd_disconnect,
187
188	.enable			= tpd_enable,
189	.disable		= tpd_disable,
190
191	.check_timings		= tpd_check_timings,
192	.set_timings		= tpd_set_timings,
193	.get_timings		= tpd_get_timings,
194
195	.read_edid		= tpd_read_edid,
196	.detect			= tpd_detect,
 
 
 
 
197	.set_infoframe		= tpd_set_infoframe,
198	.set_hdmi_mode		= tpd_set_hdmi_mode,
199};
200
201static int tpd_probe_of(struct platform_device *pdev)
202{
203	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
204	struct device_node *node = pdev->dev.of_node;
205	struct omap_dss_device *in;
206
207	in = omapdss_of_find_source_for_first_ep(node);
208	if (IS_ERR(in)) {
209		dev_err(&pdev->dev, "failed to find video source\n");
210		return PTR_ERR(in);
211	}
212
213	ddata->in = in;
 
 
 
214
215	return 0;
 
 
 
 
216}
217
218static int tpd_probe(struct platform_device *pdev)
219{
220	struct omap_dss_device *in, *dssdev;
221	struct panel_drv_data *ddata;
222	int r;
223	struct gpio_desc *gpio;
224
225	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
226	if (!ddata)
227		return -ENOMEM;
228
229	platform_set_drvdata(pdev, ddata);
230
231	if (!pdev->dev.of_node)
232		return -ENODEV;
233
234	r = tpd_probe_of(pdev);
235	if (r)
236		return r;
237
238
239	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
240		 GPIOD_OUT_LOW);
241	if (IS_ERR(gpio))
242		goto err_gpio;
243
244	ddata->ct_cp_hpd_gpio = gpio;
245
246	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
247		 GPIOD_OUT_LOW);
248	if (IS_ERR(gpio))
249		goto err_gpio;
250
251	ddata->ls_oe_gpio = gpio;
252
253	gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2,
254		GPIOD_IN);
255	if (IS_ERR(gpio))
256		goto err_gpio;
257
258	ddata->hpd_gpio = gpio;
259
 
 
 
 
 
 
 
 
 
260	dssdev = &ddata->dssdev;
261	dssdev->ops.hdmi = &tpd_hdmi_ops;
262	dssdev->dev = &pdev->dev;
263	dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
264	dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI;
265	dssdev->owner = THIS_MODULE;
266	dssdev->port_num = 1;
267
268	in = ddata->in;
269
270	r = omapdss_register_output(dssdev);
271	if (r) {
272		dev_err(&pdev->dev, "Failed to register output\n");
273		goto err_reg;
274	}
275
276	return 0;
277err_reg:
278err_gpio:
279	omap_dss_put_device(ddata->in);
280	return r;
281}
282
283static int __exit tpd_remove(struct platform_device *pdev)
284{
285	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
286	struct omap_dss_device *dssdev = &ddata->dssdev;
287	struct omap_dss_device *in = ddata->in;
288
289	omapdss_unregister_output(&ddata->dssdev);
290
291	WARN_ON(omapdss_device_is_enabled(dssdev));
292	if (omapdss_device_is_enabled(dssdev))
293		tpd_disable(dssdev);
294
295	WARN_ON(omapdss_device_is_connected(dssdev));
296	if (omapdss_device_is_connected(dssdev))
297		tpd_disconnect(dssdev, dssdev->dst);
298
299	omap_dss_put_device(in);
300
301	return 0;
302}
303
304static const struct of_device_id tpd_of_match[] = {
305	{ .compatible = "omapdss,ti,tpd12s015", },
306	{},
307};
308
309MODULE_DEVICE_TABLE(of, tpd_of_match);
310
311static struct platform_driver tpd_driver = {
312	.probe	= tpd_probe,
313	.remove	= __exit_p(tpd_remove),
314	.driver	= {
315		.name	= "tpd12s015",
316		.of_match_table = tpd_of_match,
317		.suppress_bind_attrs = true,
318	},
319};
320
321module_platform_driver(tpd_driver);
322
323MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
324MODULE_DESCRIPTION("TPD12S015 driver");
325MODULE_LICENSE("GPL");
v4.17
  1/*
  2 * TPD12S015 HDMI ESD protection & level shifter chip driver
  3 *
  4 * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
  5 * Author: Tomi Valkeinen <tomi.valkeinen@ti.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 version 2 as published by
  9 * the Free Software Foundation.
 10 */
 11
 12#include <linux/completion.h>
 13#include <linux/delay.h>
 14#include <linux/module.h>
 15#include <linux/slab.h>
 16#include <linux/platform_device.h>
 17#include <linux/gpio/consumer.h>
 18#include <linux/mutex.h>
 19
 20#include "../dss/omapdss.h"
 
 21
 22struct panel_drv_data {
 23	struct omap_dss_device dssdev;
 24	struct omap_dss_device *in;
 25	void (*hpd_cb)(void *cb_data, enum drm_connector_status status);
 26	void *hpd_cb_data;
 27	bool hpd_enabled;
 28	struct mutex hpd_lock;
 29
 30	struct gpio_desc *ct_cp_hpd_gpio;
 31	struct gpio_desc *ls_oe_gpio;
 32	struct gpio_desc *hpd_gpio;
 33
 34	struct videomode vm;
 35};
 36
 37#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
 38
 39static int tpd_connect(struct omap_dss_device *dssdev,
 40		struct omap_dss_device *dst)
 41{
 42	struct panel_drv_data *ddata = to_panel_data(dssdev);
 43	struct omap_dss_device *in;
 44	int r;
 45
 46	in = omapdss_of_find_source_for_first_ep(dssdev->dev->of_node);
 47	if (IS_ERR(in)) {
 48		dev_err(dssdev->dev, "failed to find video source\n");
 49		return PTR_ERR(in);
 50	}
 51
 52	r = in->ops.hdmi->connect(in, dssdev);
 53	if (r) {
 54		omap_dss_put_device(in);
 55		return r;
 56	}
 57
 58	dst->src = dssdev;
 59	dssdev->dst = dst;
 60
 61	gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1);
 62	gpiod_set_value_cansleep(ddata->ls_oe_gpio, 1);
 63
 64	/* DC-DC converter needs at max 300us to get to 90% of 5V */
 65	udelay(300);
 66
 67	ddata->in = in;
 68	return 0;
 69}
 70
 71static void tpd_disconnect(struct omap_dss_device *dssdev,
 72		struct omap_dss_device *dst)
 73{
 74	struct panel_drv_data *ddata = to_panel_data(dssdev);
 75	struct omap_dss_device *in = ddata->in;
 76
 77	WARN_ON(dst != dssdev->dst);
 78
 79	if (dst != dssdev->dst)
 80		return;
 81
 82	gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0);
 83	gpiod_set_value_cansleep(ddata->ls_oe_gpio, 0);
 84
 85	dst->src = NULL;
 86	dssdev->dst = NULL;
 87
 88	in->ops.hdmi->disconnect(in, &ddata->dssdev);
 89
 90	omap_dss_put_device(in);
 91	ddata->in = NULL;
 92}
 93
 94static int tpd_enable(struct omap_dss_device *dssdev)
 95{
 96	struct panel_drv_data *ddata = to_panel_data(dssdev);
 97	struct omap_dss_device *in = ddata->in;
 98	int r;
 99
100	if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
101		return 0;
102
103	in->ops.hdmi->set_timings(in, &ddata->vm);
104
105	r = in->ops.hdmi->enable(in);
106	if (r)
107		return r;
108
109	dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
110
111	return r;
112}
113
114static void tpd_disable(struct omap_dss_device *dssdev)
115{
116	struct panel_drv_data *ddata = to_panel_data(dssdev);
117	struct omap_dss_device *in = ddata->in;
118
119	if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
120		return;
121
122	in->ops.hdmi->disable(in);
123
124	dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
125}
126
127static void tpd_set_timings(struct omap_dss_device *dssdev,
128			    struct videomode *vm)
129{
130	struct panel_drv_data *ddata = to_panel_data(dssdev);
131	struct omap_dss_device *in = ddata->in;
132
133	ddata->vm = *vm;
134	dssdev->panel.vm = *vm;
135
136	in->ops.hdmi->set_timings(in, vm);
137}
138
139static void tpd_get_timings(struct omap_dss_device *dssdev,
140			    struct videomode *vm)
141{
142	struct panel_drv_data *ddata = to_panel_data(dssdev);
143
144	*vm = ddata->vm;
145}
146
147static int tpd_check_timings(struct omap_dss_device *dssdev,
148			     struct videomode *vm)
149{
150	struct panel_drv_data *ddata = to_panel_data(dssdev);
151	struct omap_dss_device *in = ddata->in;
152	int r;
153
154	r = in->ops.hdmi->check_timings(in, vm);
155
156	return r;
157}
158
159static int tpd_read_edid(struct omap_dss_device *dssdev,
160		u8 *edid, int len)
161{
162	struct panel_drv_data *ddata = to_panel_data(dssdev);
163	struct omap_dss_device *in = ddata->in;
 
164
165	if (!gpiod_get_value_cansleep(ddata->hpd_gpio))
166		return -ENODEV;
167
168	return in->ops.hdmi->read_edid(in, edid, len);
169}
170
171static bool tpd_detect(struct omap_dss_device *dssdev)
172{
173	struct panel_drv_data *ddata = to_panel_data(dssdev);
174	struct omap_dss_device *in = ddata->in;
175	bool connected = gpiod_get_value_cansleep(ddata->hpd_gpio);
176
177	if (!connected && in->ops.hdmi->lost_hotplug)
178		in->ops.hdmi->lost_hotplug(in);
179	return connected;
180}
181
182static int tpd_register_hpd_cb(struct omap_dss_device *dssdev,
183			       void (*cb)(void *cb_data,
184					  enum drm_connector_status status),
185			       void *cb_data)
186{
187	struct panel_drv_data *ddata = to_panel_data(dssdev);
188
189	mutex_lock(&ddata->hpd_lock);
190	ddata->hpd_cb = cb;
191	ddata->hpd_cb_data = cb_data;
192	mutex_unlock(&ddata->hpd_lock);
193
194	return 0;
195}
196
197static void tpd_unregister_hpd_cb(struct omap_dss_device *dssdev)
198{
199	struct panel_drv_data *ddata = to_panel_data(dssdev);
200
201	mutex_lock(&ddata->hpd_lock);
202	ddata->hpd_cb = NULL;
203	ddata->hpd_cb_data = NULL;
204	mutex_unlock(&ddata->hpd_lock);
205}
206
207static void tpd_enable_hpd(struct omap_dss_device *dssdev)
208{
209	struct panel_drv_data *ddata = to_panel_data(dssdev);
210
211	mutex_lock(&ddata->hpd_lock);
212	ddata->hpd_enabled = true;
213	mutex_unlock(&ddata->hpd_lock);
214}
215
216static void tpd_disable_hpd(struct omap_dss_device *dssdev)
217{
218	struct panel_drv_data *ddata = to_panel_data(dssdev);
219
220	mutex_lock(&ddata->hpd_lock);
221	ddata->hpd_enabled = false;
222	mutex_unlock(&ddata->hpd_lock);
223}
224
225static int tpd_set_infoframe(struct omap_dss_device *dssdev,
226		const struct hdmi_avi_infoframe *avi)
227{
228	struct panel_drv_data *ddata = to_panel_data(dssdev);
229	struct omap_dss_device *in = ddata->in;
230
231	return in->ops.hdmi->set_infoframe(in, avi);
232}
233
234static int tpd_set_hdmi_mode(struct omap_dss_device *dssdev,
235		bool hdmi_mode)
236{
237	struct panel_drv_data *ddata = to_panel_data(dssdev);
238	struct omap_dss_device *in = ddata->in;
239
240	return in->ops.hdmi->set_hdmi_mode(in, hdmi_mode);
241}
242
243static const struct omapdss_hdmi_ops tpd_hdmi_ops = {
244	.connect		= tpd_connect,
245	.disconnect		= tpd_disconnect,
246
247	.enable			= tpd_enable,
248	.disable		= tpd_disable,
249
250	.check_timings		= tpd_check_timings,
251	.set_timings		= tpd_set_timings,
252	.get_timings		= tpd_get_timings,
253
254	.read_edid		= tpd_read_edid,
255	.detect			= tpd_detect,
256	.register_hpd_cb	= tpd_register_hpd_cb,
257	.unregister_hpd_cb	= tpd_unregister_hpd_cb,
258	.enable_hpd		= tpd_enable_hpd,
259	.disable_hpd		= tpd_disable_hpd,
260	.set_infoframe		= tpd_set_infoframe,
261	.set_hdmi_mode		= tpd_set_hdmi_mode,
262};
263
264static irqreturn_t tpd_hpd_isr(int irq, void *data)
265{
266	struct panel_drv_data *ddata = data;
 
 
267
268	mutex_lock(&ddata->hpd_lock);
269	if (ddata->hpd_enabled && ddata->hpd_cb) {
270		enum drm_connector_status status;
 
 
271
272		if (tpd_detect(&ddata->dssdev))
273			status = connector_status_connected;
274		else
275			status = connector_status_disconnected;
276
277		ddata->hpd_cb(ddata->hpd_cb_data, status);
278	}
279	mutex_unlock(&ddata->hpd_lock);
280
281	return IRQ_HANDLED;
282}
283
284static int tpd_probe(struct platform_device *pdev)
285{
286	struct omap_dss_device *in, *dssdev;
287	struct panel_drv_data *ddata;
288	int r;
289	struct gpio_desc *gpio;
290
291	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
292	if (!ddata)
293		return -ENOMEM;
294
295	platform_set_drvdata(pdev, ddata);
296
 
 
 
 
 
 
 
 
297	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
298		 GPIOD_OUT_LOW);
299	if (IS_ERR(gpio))
300		return PTR_ERR(gpio);
301
302	ddata->ct_cp_hpd_gpio = gpio;
303
304	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
305		 GPIOD_OUT_LOW);
306	if (IS_ERR(gpio))
307		return PTR_ERR(gpio);
308
309	ddata->ls_oe_gpio = gpio;
310
311	gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2,
312		GPIOD_IN);
313	if (IS_ERR(gpio))
314		return PTR_ERR(gpio);
315
316	ddata->hpd_gpio = gpio;
317
318	mutex_init(&ddata->hpd_lock);
319
320	r = devm_request_threaded_irq(&pdev->dev, gpiod_to_irq(ddata->hpd_gpio),
321		NULL, tpd_hpd_isr,
322		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
323		"tpd12s015 hpd", ddata);
324	if (r)
325		return r;
326
327	dssdev = &ddata->dssdev;
328	dssdev->ops.hdmi = &tpd_hdmi_ops;
329	dssdev->dev = &pdev->dev;
330	dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
331	dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI;
332	dssdev->owner = THIS_MODULE;
333	dssdev->port_num = 1;
334
335	in = ddata->in;
336
337	r = omapdss_register_output(dssdev);
338	if (r) {
339		dev_err(&pdev->dev, "Failed to register output\n");
340		return r;
341	}
342
343	return 0;
 
 
 
 
344}
345
346static int __exit tpd_remove(struct platform_device *pdev)
347{
348	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
349	struct omap_dss_device *dssdev = &ddata->dssdev;
 
350
351	omapdss_unregister_output(&ddata->dssdev);
352
353	WARN_ON(omapdss_device_is_enabled(dssdev));
354	if (omapdss_device_is_enabled(dssdev))
355		tpd_disable(dssdev);
356
357	WARN_ON(omapdss_device_is_connected(dssdev));
358	if (omapdss_device_is_connected(dssdev))
359		tpd_disconnect(dssdev, dssdev->dst);
 
 
360
361	return 0;
362}
363
364static const struct of_device_id tpd_of_match[] = {
365	{ .compatible = "omapdss,ti,tpd12s015", },
366	{},
367};
368
369MODULE_DEVICE_TABLE(of, tpd_of_match);
370
371static struct platform_driver tpd_driver = {
372	.probe	= tpd_probe,
373	.remove	= __exit_p(tpd_remove),
374	.driver	= {
375		.name	= "tpd12s015",
376		.of_match_table = tpd_of_match,
377		.suppress_bind_attrs = true,
378	},
379};
380
381module_platform_driver(tpd_driver);
382
383MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
384MODULE_DESCRIPTION("TPD12S015 driver");
385MODULE_LICENSE("GPL");