xref: /openbmc/linux/drivers/usb/misc/usbsevseg.c (revision bdeeed09)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * USB 7 Segment Driver
4  *
5  * Copyright (C) 2008 Harrison Metzger <harrisonmetz@gmail.com>
6  * Based on usbled.c by Greg Kroah-Hartman (greg@kroah.com)
7  */
8 
9 #include <linux/kernel.h>
10 #include <linux/errno.h>
11 #include <linux/slab.h>
12 #include <linux/module.h>
13 #include <linux/string.h>
14 #include <linux/usb.h>
15 
16 
17 #define DRIVER_AUTHOR "Harrison Metzger <harrisonmetz@gmail.com>"
18 #define DRIVER_DESC "USB 7 Segment Driver"
19 
20 #define VENDOR_ID	0x0fc5
21 #define PRODUCT_ID	0x1227
22 #define MAXLEN		8
23 
24 /* table of devices that work with this driver */
25 static const struct usb_device_id id_table[] = {
26 	{ USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
27 	{ },
28 };
29 MODULE_DEVICE_TABLE(usb, id_table);
30 
31 /* the different text display modes the device is capable of */
32 static const char *display_textmodes[] = {"raw", "hex", "ascii"};
33 
34 struct usb_sevsegdev {
35 	struct usb_device *udev;
36 	struct usb_interface *intf;
37 
38 	u8 powered;
39 	u8 mode_msb;
40 	u8 mode_lsb;
41 	u8 decimals[MAXLEN];
42 	u8 textmode;
43 	u8 text[MAXLEN];
44 	u16 textlength;
45 
46 	u8 shadow_power; /* for PM */
47 	u8 has_interface_pm;
48 };
49 
50 /* sysfs_streq can't replace this completely
51  * If the device was in hex mode, and the user wanted a 0,
52  * if str commands are used, we would assume the end of string
53  * so mem commands are used.
54  */
55 static inline size_t my_memlen(const char *buf, size_t count)
56 {
57 	if (count > 0 && buf[count-1] == '\n')
58 		return count - 1;
59 	else
60 		return count;
61 }
62 
63 static void update_display_powered(struct usb_sevsegdev *mydev)
64 {
65 	int rc;
66 
67 	if (mydev->powered && !mydev->has_interface_pm) {
68 		rc = usb_autopm_get_interface(mydev->intf);
69 		if (rc < 0)
70 			return;
71 		mydev->has_interface_pm = 1;
72 	}
73 
74 	if (mydev->shadow_power != 1)
75 		return;
76 
77 	rc = usb_control_msg(mydev->udev,
78 			usb_sndctrlpipe(mydev->udev, 0),
79 			0x12,
80 			0x48,
81 			(80 * 0x100) + 10, /*  (power mode) */
82 			(0x00 * 0x100) + (mydev->powered ? 1 : 0),
83 			NULL,
84 			0,
85 			2000);
86 	if (rc < 0)
87 		dev_dbg(&mydev->udev->dev, "power retval = %d\n", rc);
88 
89 	if (!mydev->powered && mydev->has_interface_pm) {
90 		usb_autopm_put_interface(mydev->intf);
91 		mydev->has_interface_pm = 0;
92 	}
93 }
94 
95 static void update_display_mode(struct usb_sevsegdev *mydev)
96 {
97 	int rc;
98 
99 	if(mydev->shadow_power != 1)
100 		return;
101 
102 	rc = usb_control_msg(mydev->udev,
103 			usb_sndctrlpipe(mydev->udev, 0),
104 			0x12,
105 			0x48,
106 			(82 * 0x100) + 10, /* (set mode) */
107 			(mydev->mode_msb * 0x100) + mydev->mode_lsb,
108 			NULL,
109 			0,
110 			2000);
111 
112 	if (rc < 0)
113 		dev_dbg(&mydev->udev->dev, "mode retval = %d\n", rc);
114 }
115 
116 static void update_display_visual(struct usb_sevsegdev *mydev, gfp_t mf)
117 {
118 	int rc;
119 	int i;
120 	unsigned char *buffer;
121 	u8 decimals = 0;
122 
123 	if(mydev->shadow_power != 1)
124 		return;
125 
126 	buffer = kzalloc(MAXLEN, mf);
127 	if (!buffer)
128 		return;
129 
130 	/* The device is right to left, where as you write left to right */
131 	for (i = 0; i < mydev->textlength; i++)
132 		buffer[i] = mydev->text[mydev->textlength-1-i];
133 
134 	rc = usb_control_msg(mydev->udev,
135 			usb_sndctrlpipe(mydev->udev, 0),
136 			0x12,
137 			0x48,
138 			(85 * 0x100) + 10, /* (write text) */
139 			(0 * 0x100) + mydev->textmode, /* mode  */
140 			buffer,
141 			mydev->textlength,
142 			2000);
143 
144 	if (rc < 0)
145 		dev_dbg(&mydev->udev->dev, "write retval = %d\n", rc);
146 
147 	kfree(buffer);
148 
149 	/* The device is right to left, where as you write left to right */
150 	for (i = 0; i < sizeof(mydev->decimals); i++)
151 		decimals |= mydev->decimals[i] << i;
152 
153 	rc = usb_control_msg(mydev->udev,
154 			usb_sndctrlpipe(mydev->udev, 0),
155 			0x12,
156 			0x48,
157 			(86 * 0x100) + 10, /* (set decimal) */
158 			(0 * 0x100) + decimals, /* decimals */
159 			NULL,
160 			0,
161 			2000);
162 
163 	if (rc < 0)
164 		dev_dbg(&mydev->udev->dev, "decimal retval = %d\n", rc);
165 }
166 
167 #define MYDEV_ATTR_SIMPLE_UNSIGNED(name, update_fcn)		\
168 static ssize_t name##_show(struct device *dev,			\
169 	struct device_attribute *attr, char *buf) 		\
170 {								\
171 	struct usb_interface *intf = to_usb_interface(dev);	\
172 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);	\
173 								\
174 	return sprintf(buf, "%u\n", mydev->name);		\
175 }								\
176 								\
177 static ssize_t name##_store(struct device *dev,			\
178 	struct device_attribute *attr, const char *buf, size_t count) \
179 {								\
180 	struct usb_interface *intf = to_usb_interface(dev);	\
181 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);	\
182 								\
183 	mydev->name = simple_strtoul(buf, NULL, 10);		\
184 	update_fcn(mydev); 					\
185 								\
186 	return count;						\
187 }								\
188 static DEVICE_ATTR_RW(name);
189 
190 static ssize_t text_show(struct device *dev,
191 	struct device_attribute *attr, char *buf)
192 {
193 	struct usb_interface *intf = to_usb_interface(dev);
194 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
195 
196 	return snprintf(buf, mydev->textlength, "%s\n", mydev->text);
197 }
198 
199 static ssize_t text_store(struct device *dev,
200 	struct device_attribute *attr, const char *buf, size_t count)
201 {
202 	struct usb_interface *intf = to_usb_interface(dev);
203 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
204 	size_t end = my_memlen(buf, count);
205 
206 	if (end > sizeof(mydev->text))
207 		return -EINVAL;
208 
209 	memset(mydev->text, 0, sizeof(mydev->text));
210 	mydev->textlength = end;
211 
212 	if (end > 0)
213 		memcpy(mydev->text, buf, end);
214 
215 	update_display_visual(mydev, GFP_KERNEL);
216 	return count;
217 }
218 
219 static DEVICE_ATTR_RW(text);
220 
221 static ssize_t decimals_show(struct device *dev,
222 	struct device_attribute *attr, char *buf)
223 {
224 	struct usb_interface *intf = to_usb_interface(dev);
225 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
226 	int i;
227 	int pos;
228 
229 	for (i = 0; i < sizeof(mydev->decimals); i++) {
230 		pos = sizeof(mydev->decimals) - 1 - i;
231 		if (mydev->decimals[i] == 0)
232 			buf[pos] = '0';
233 		else if (mydev->decimals[i] == 1)
234 			buf[pos] = '1';
235 		else
236 			buf[pos] = 'x';
237 	}
238 
239 	buf[sizeof(mydev->decimals)] = '\n';
240 	return sizeof(mydev->decimals) + 1;
241 }
242 
243 static ssize_t decimals_store(struct device *dev,
244 	struct device_attribute *attr, const char *buf, size_t count)
245 {
246 	struct usb_interface *intf = to_usb_interface(dev);
247 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
248 	size_t end = my_memlen(buf, count);
249 	int i;
250 
251 	if (end > sizeof(mydev->decimals))
252 		return -EINVAL;
253 
254 	for (i = 0; i < end; i++)
255 		if (buf[i] != '0' && buf[i] != '1')
256 			return -EINVAL;
257 
258 	memset(mydev->decimals, 0, sizeof(mydev->decimals));
259 	for (i = 0; i < end; i++)
260 		if (buf[i] == '1')
261 			mydev->decimals[end-1-i] = 1;
262 
263 	update_display_visual(mydev, GFP_KERNEL);
264 
265 	return count;
266 }
267 
268 static DEVICE_ATTR_RW(decimals);
269 
270 static ssize_t textmode_show(struct device *dev,
271 	struct device_attribute *attr, char *buf)
272 {
273 	struct usb_interface *intf = to_usb_interface(dev);
274 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
275 	int i;
276 
277 	buf[0] = 0;
278 
279 	for (i = 0; i < ARRAY_SIZE(display_textmodes); i++) {
280 		if (mydev->textmode == i) {
281 			strcat(buf, " [");
282 			strcat(buf, display_textmodes[i]);
283 			strcat(buf, "] ");
284 		} else {
285 			strcat(buf, " ");
286 			strcat(buf, display_textmodes[i]);
287 			strcat(buf, " ");
288 		}
289 	}
290 	strcat(buf, "\n");
291 
292 
293 	return strlen(buf);
294 }
295 
296 static ssize_t textmode_store(struct device *dev,
297 	struct device_attribute *attr, const char *buf, size_t count)
298 {
299 	struct usb_interface *intf = to_usb_interface(dev);
300 	struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
301 	int i;
302 
303 	i = sysfs_match_string(display_textmodes, buf);
304 	if (i < 0)
305 		return i;
306 
307 	mydev->textmode = i;
308 	update_display_visual(mydev, GFP_KERNEL);
309 	return count;
310 }
311 
312 static DEVICE_ATTR_RW(textmode);
313 
314 
315 MYDEV_ATTR_SIMPLE_UNSIGNED(powered, update_display_powered);
316 MYDEV_ATTR_SIMPLE_UNSIGNED(mode_msb, update_display_mode);
317 MYDEV_ATTR_SIMPLE_UNSIGNED(mode_lsb, update_display_mode);
318 
319 static struct attribute *dev_attrs[] = {
320 	&dev_attr_powered.attr,
321 	&dev_attr_text.attr,
322 	&dev_attr_textmode.attr,
323 	&dev_attr_decimals.attr,
324 	&dev_attr_mode_msb.attr,
325 	&dev_attr_mode_lsb.attr,
326 	NULL
327 };
328 
329 static const struct attribute_group dev_attr_grp = {
330 	.attrs = dev_attrs,
331 };
332 
333 static int sevseg_probe(struct usb_interface *interface,
334 	const struct usb_device_id *id)
335 {
336 	struct usb_device *udev = interface_to_usbdev(interface);
337 	struct usb_sevsegdev *mydev = NULL;
338 	int rc = -ENOMEM;
339 
340 	mydev = kzalloc(sizeof(struct usb_sevsegdev), GFP_KERNEL);
341 	if (!mydev)
342 		goto error_mem;
343 
344 	mydev->udev = usb_get_dev(udev);
345 	mydev->intf = interface;
346 	usb_set_intfdata(interface, mydev);
347 
348 	/* PM */
349 	mydev->shadow_power = 1; /* currently active */
350 	mydev->has_interface_pm = 0; /* have not issued autopm_get */
351 
352 	/*set defaults */
353 	mydev->textmode = 0x02; /* ascii mode */
354 	mydev->mode_msb = 0x06; /* 6 characters */
355 	mydev->mode_lsb = 0x3f; /* scanmode for 6 chars */
356 
357 	rc = sysfs_create_group(&interface->dev.kobj, &dev_attr_grp);
358 	if (rc)
359 		goto error;
360 
361 	dev_info(&interface->dev, "USB 7 Segment device now attached\n");
362 	return 0;
363 
364 error:
365 	usb_set_intfdata(interface, NULL);
366 	usb_put_dev(mydev->udev);
367 	kfree(mydev);
368 error_mem:
369 	return rc;
370 }
371 
372 static void sevseg_disconnect(struct usb_interface *interface)
373 {
374 	struct usb_sevsegdev *mydev;
375 
376 	mydev = usb_get_intfdata(interface);
377 	sysfs_remove_group(&interface->dev.kobj, &dev_attr_grp);
378 	usb_set_intfdata(interface, NULL);
379 	usb_put_dev(mydev->udev);
380 	kfree(mydev);
381 	dev_info(&interface->dev, "USB 7 Segment now disconnected\n");
382 }
383 
384 static int sevseg_suspend(struct usb_interface *intf, pm_message_t message)
385 {
386 	struct usb_sevsegdev *mydev;
387 
388 	mydev = usb_get_intfdata(intf);
389 	mydev->shadow_power = 0;
390 
391 	return 0;
392 }
393 
394 static int sevseg_resume(struct usb_interface *intf)
395 {
396 	struct usb_sevsegdev *mydev;
397 
398 	mydev = usb_get_intfdata(intf);
399 	mydev->shadow_power = 1;
400 	update_display_mode(mydev);
401 	update_display_visual(mydev, GFP_NOIO);
402 
403 	return 0;
404 }
405 
406 static int sevseg_reset_resume(struct usb_interface *intf)
407 {
408 	struct usb_sevsegdev *mydev;
409 
410 	mydev = usb_get_intfdata(intf);
411 	mydev->shadow_power = 1;
412 	update_display_mode(mydev);
413 	update_display_visual(mydev, GFP_NOIO);
414 
415 	return 0;
416 }
417 
418 static struct usb_driver sevseg_driver = {
419 	.name =		"usbsevseg",
420 	.probe =	sevseg_probe,
421 	.disconnect =	sevseg_disconnect,
422 	.suspend =	sevseg_suspend,
423 	.resume =	sevseg_resume,
424 	.reset_resume =	sevseg_reset_resume,
425 	.id_table =	id_table,
426 	.supports_autosuspend = 1,
427 };
428 
429 module_usb_driver(sevseg_driver);
430 
431 MODULE_AUTHOR(DRIVER_AUTHOR);
432 MODULE_DESCRIPTION(DRIVER_DESC);
433 MODULE_LICENSE("GPL");
434