usbsevseg.c (9692B)
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 */ 25static const struct usb_device_id id_table[] = { 26 { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, 27 { }, 28}; 29MODULE_DEVICE_TABLE(usb, id_table); 30 31/* the different text display modes the device is capable of */ 32static const char *display_textmodes[] = {"raw", "hex", "ascii"}; 33 34struct 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 */ 55static 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 63static 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_send(mydev->udev, 0, 0x12, 0x48, 78 (80 * 0x100) + 10, /* (power mode) */ 79 (0x00 * 0x100) + (mydev->powered ? 1 : 0), 80 NULL, 0, 2000, GFP_KERNEL); 81 if (rc < 0) 82 dev_dbg(&mydev->udev->dev, "power retval = %d\n", rc); 83 84 if (!mydev->powered && mydev->has_interface_pm) { 85 usb_autopm_put_interface(mydev->intf); 86 mydev->has_interface_pm = 0; 87 } 88} 89 90static void update_display_mode(struct usb_sevsegdev *mydev) 91{ 92 int rc; 93 94 if(mydev->shadow_power != 1) 95 return; 96 97 rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48, 98 (82 * 0x100) + 10, /* (set mode) */ 99 (mydev->mode_msb * 0x100) + mydev->mode_lsb, 100 NULL, 0, 2000, GFP_NOIO); 101 102 if (rc < 0) 103 dev_dbg(&mydev->udev->dev, "mode retval = %d\n", rc); 104} 105 106static void update_display_visual(struct usb_sevsegdev *mydev, gfp_t mf) 107{ 108 int rc; 109 int i; 110 unsigned char buffer[MAXLEN] = {0}; 111 u8 decimals = 0; 112 113 if(mydev->shadow_power != 1) 114 return; 115 116 /* The device is right to left, where as you write left to right */ 117 for (i = 0; i < mydev->textlength; i++) 118 buffer[i] = mydev->text[mydev->textlength-1-i]; 119 120 rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48, 121 (85 * 0x100) + 10, /* (write text) */ 122 (0 * 0x100) + mydev->textmode, /* mode */ 123 &buffer, mydev->textlength, 2000, mf); 124 125 if (rc < 0) 126 dev_dbg(&mydev->udev->dev, "write retval = %d\n", rc); 127 128 /* The device is right to left, where as you write left to right */ 129 for (i = 0; i < sizeof(mydev->decimals); i++) 130 decimals |= mydev->decimals[i] << i; 131 132 rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48, 133 (86 * 0x100) + 10, /* (set decimal) */ 134 (0 * 0x100) + decimals, /* decimals */ 135 NULL, 0, 2000, mf); 136 137 if (rc < 0) 138 dev_dbg(&mydev->udev->dev, "decimal retval = %d\n", rc); 139} 140 141#define MYDEV_ATTR_SIMPLE_UNSIGNED(name, update_fcn) \ 142static ssize_t name##_show(struct device *dev, \ 143 struct device_attribute *attr, char *buf) \ 144{ \ 145 struct usb_interface *intf = to_usb_interface(dev); \ 146 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \ 147 \ 148 return sprintf(buf, "%u\n", mydev->name); \ 149} \ 150 \ 151static ssize_t name##_store(struct device *dev, \ 152 struct device_attribute *attr, const char *buf, size_t count) \ 153{ \ 154 struct usb_interface *intf = to_usb_interface(dev); \ 155 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \ 156 \ 157 mydev->name = simple_strtoul(buf, NULL, 10); \ 158 update_fcn(mydev); \ 159 \ 160 return count; \ 161} \ 162static DEVICE_ATTR_RW(name); 163 164static ssize_t text_show(struct device *dev, 165 struct device_attribute *attr, char *buf) 166{ 167 struct usb_interface *intf = to_usb_interface(dev); 168 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); 169 170 return snprintf(buf, mydev->textlength, "%s\n", mydev->text); 171} 172 173static ssize_t text_store(struct device *dev, 174 struct device_attribute *attr, const char *buf, size_t count) 175{ 176 struct usb_interface *intf = to_usb_interface(dev); 177 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); 178 size_t end = my_memlen(buf, count); 179 180 if (end > sizeof(mydev->text)) 181 return -EINVAL; 182 183 memset(mydev->text, 0, sizeof(mydev->text)); 184 mydev->textlength = end; 185 186 if (end > 0) 187 memcpy(mydev->text, buf, end); 188 189 update_display_visual(mydev, GFP_KERNEL); 190 return count; 191} 192 193static DEVICE_ATTR_RW(text); 194 195static ssize_t decimals_show(struct device *dev, 196 struct device_attribute *attr, char *buf) 197{ 198 struct usb_interface *intf = to_usb_interface(dev); 199 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); 200 int i; 201 int pos; 202 203 for (i = 0; i < sizeof(mydev->decimals); i++) { 204 pos = sizeof(mydev->decimals) - 1 - i; 205 if (mydev->decimals[i] == 0) 206 buf[pos] = '0'; 207 else if (mydev->decimals[i] == 1) 208 buf[pos] = '1'; 209 else 210 buf[pos] = 'x'; 211 } 212 213 buf[sizeof(mydev->decimals)] = '\n'; 214 return sizeof(mydev->decimals) + 1; 215} 216 217static ssize_t decimals_store(struct device *dev, 218 struct device_attribute *attr, const char *buf, size_t count) 219{ 220 struct usb_interface *intf = to_usb_interface(dev); 221 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); 222 size_t end = my_memlen(buf, count); 223 int i; 224 225 if (end > sizeof(mydev->decimals)) 226 return -EINVAL; 227 228 for (i = 0; i < end; i++) 229 if (buf[i] != '0' && buf[i] != '1') 230 return -EINVAL; 231 232 memset(mydev->decimals, 0, sizeof(mydev->decimals)); 233 for (i = 0; i < end; i++) 234 if (buf[i] == '1') 235 mydev->decimals[end-1-i] = 1; 236 237 update_display_visual(mydev, GFP_KERNEL); 238 239 return count; 240} 241 242static DEVICE_ATTR_RW(decimals); 243 244static ssize_t textmode_show(struct device *dev, 245 struct device_attribute *attr, char *buf) 246{ 247 struct usb_interface *intf = to_usb_interface(dev); 248 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); 249 int i; 250 251 buf[0] = 0; 252 253 for (i = 0; i < ARRAY_SIZE(display_textmodes); i++) { 254 if (mydev->textmode == i) { 255 strcat(buf, " ["); 256 strcat(buf, display_textmodes[i]); 257 strcat(buf, "] "); 258 } else { 259 strcat(buf, " "); 260 strcat(buf, display_textmodes[i]); 261 strcat(buf, " "); 262 } 263 } 264 strcat(buf, "\n"); 265 266 267 return strlen(buf); 268} 269 270static ssize_t textmode_store(struct device *dev, 271 struct device_attribute *attr, const char *buf, size_t count) 272{ 273 struct usb_interface *intf = to_usb_interface(dev); 274 struct usb_sevsegdev *mydev = usb_get_intfdata(intf); 275 int i; 276 277 i = sysfs_match_string(display_textmodes, buf); 278 if (i < 0) 279 return i; 280 281 mydev->textmode = i; 282 update_display_visual(mydev, GFP_KERNEL); 283 return count; 284} 285 286static DEVICE_ATTR_RW(textmode); 287 288 289MYDEV_ATTR_SIMPLE_UNSIGNED(powered, update_display_powered); 290MYDEV_ATTR_SIMPLE_UNSIGNED(mode_msb, update_display_mode); 291MYDEV_ATTR_SIMPLE_UNSIGNED(mode_lsb, update_display_mode); 292 293static struct attribute *sevseg_attrs[] = { 294 &dev_attr_powered.attr, 295 &dev_attr_text.attr, 296 &dev_attr_textmode.attr, 297 &dev_attr_decimals.attr, 298 &dev_attr_mode_msb.attr, 299 &dev_attr_mode_lsb.attr, 300 NULL 301}; 302ATTRIBUTE_GROUPS(sevseg); 303 304static int sevseg_probe(struct usb_interface *interface, 305 const struct usb_device_id *id) 306{ 307 struct usb_device *udev = interface_to_usbdev(interface); 308 struct usb_sevsegdev *mydev = NULL; 309 int rc = -ENOMEM; 310 311 mydev = kzalloc(sizeof(struct usb_sevsegdev), GFP_KERNEL); 312 if (!mydev) 313 goto error_mem; 314 315 mydev->udev = usb_get_dev(udev); 316 mydev->intf = interface; 317 usb_set_intfdata(interface, mydev); 318 319 /* PM */ 320 mydev->shadow_power = 1; /* currently active */ 321 mydev->has_interface_pm = 0; /* have not issued autopm_get */ 322 323 /*set defaults */ 324 mydev->textmode = 0x02; /* ascii mode */ 325 mydev->mode_msb = 0x06; /* 6 characters */ 326 mydev->mode_lsb = 0x3f; /* scanmode for 6 chars */ 327 328 dev_info(&interface->dev, "USB 7 Segment device now attached\n"); 329 return 0; 330 331error_mem: 332 return rc; 333} 334 335static void sevseg_disconnect(struct usb_interface *interface) 336{ 337 struct usb_sevsegdev *mydev; 338 339 mydev = usb_get_intfdata(interface); 340 usb_set_intfdata(interface, NULL); 341 usb_put_dev(mydev->udev); 342 kfree(mydev); 343 dev_info(&interface->dev, "USB 7 Segment now disconnected\n"); 344} 345 346static int sevseg_suspend(struct usb_interface *intf, pm_message_t message) 347{ 348 struct usb_sevsegdev *mydev; 349 350 mydev = usb_get_intfdata(intf); 351 mydev->shadow_power = 0; 352 353 return 0; 354} 355 356static int sevseg_resume(struct usb_interface *intf) 357{ 358 struct usb_sevsegdev *mydev; 359 360 mydev = usb_get_intfdata(intf); 361 mydev->shadow_power = 1; 362 update_display_mode(mydev); 363 update_display_visual(mydev, GFP_NOIO); 364 365 return 0; 366} 367 368static int sevseg_reset_resume(struct usb_interface *intf) 369{ 370 struct usb_sevsegdev *mydev; 371 372 mydev = usb_get_intfdata(intf); 373 mydev->shadow_power = 1; 374 update_display_mode(mydev); 375 update_display_visual(mydev, GFP_NOIO); 376 377 return 0; 378} 379 380static struct usb_driver sevseg_driver = { 381 .name = "usbsevseg", 382 .probe = sevseg_probe, 383 .disconnect = sevseg_disconnect, 384 .suspend = sevseg_suspend, 385 .resume = sevseg_resume, 386 .reset_resume = sevseg_reset_resume, 387 .id_table = id_table, 388 .dev_groups = sevseg_groups, 389 .supports_autosuspend = 1, 390}; 391 392module_usb_driver(sevseg_driver); 393 394MODULE_AUTHOR(DRIVER_AUTHOR); 395MODULE_DESCRIPTION(DRIVER_DESC); 396MODULE_LICENSE("GPL");