linux/drivers/usb/misc/usbsevseg.c
<<
>>
Prefs
   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");
 397