| 1 | |
| 2 | |
| 3 | * drivers/usb/input/yealink.c
|
| 4 | *
|
| 5 | * Copyright (c) 2005 Henk Vergonet <[email protected]>
|
| 6 |
|
| 7 | |
| 8 | * Description:
|
| 9 | * Driver for the USB-P1K voip usb phone.
|
| 10 | * This device is produced by Yealink Network Technology Co Ltd
|
| 11 | * but may be branded under several names:
|
| 12 | * - Yealink usb-p1k
|
| 13 | * - Tiptel 115
|
| 14 | * - ...
|
| 15 | *
|
| 16 | * This driver is based on:
|
| 17 | * - the usbb2k-api http://savannah.nongnu.org/projects/usbb2k-api/
|
| 18 | * - information from http://memeteau.free.fr/usbb2k
|
| 19 | * - the xpad-driver drivers/input/joystick/xpad.c
|
| 20 | *
|
| 21 | * Thanks to:
|
| 22 | * - Olivier Vandorpe, for providing the usbb2k-api.
|
| 23 | * - Martin Diehl, for spotting my memory allocation bug.
|
| 24 | *
|
| 25 |
|
| 26 |
|
| 27 | #include <linux/kernel.h>
|
| 28 | #include <linux/slab.h>
|
| 29 | #include <linux/module.h>
|
| 30 | #include <linux/mutex.h>
|
| 31 | #include <linux/usb/input.h>
|
| 32 | #include <linux/map_to_7segment.h>
|
| 33 |
|
| 34 | #include "yealink.h"
|
| 35 |
|
| 36 | #define DRIVER_VERSION "yld-20051230"
|
| 37 |
|
| 38 | #define YEALINK_POLLING_FREQUENCY 10
|
| 39 |
|
| 40 | struct yld_status {
|
| 41 | u8 lcd[24];
|
| 42 | u8 led;
|
| 43 | u8 dialtone;
|
| 44 | u8 ringtone;
|
| 45 | u8 keynum;
|
| 46 | } __attribute__ ((packed));
|
| 47 |
|
| 48 | |
| 49 | * Register the LCD segment and icon map
|
| 50 |
|
| 51 | #define _LOC(k,l) { .a = (k), .m = (l) }
|
| 52 | #define _SEG(t, a, am, b, bm, c, cm, d, dm, e, em, f, fm, g, gm) \
|
| 53 | { .type = (t), \
|
| 54 | .u = { .s = { _LOC(a, am), _LOC(b, bm), _LOC(c, cm), \
|
| 55 | _LOC(d, dm), _LOC(e, em), _LOC(g, gm), \
|
| 56 | _LOC(f, fm) } } }
|
| 57 | #define _PIC(t, h, hm, n) \
|
| 58 | { .type = (t), \
|
| 59 | .u = { .p = { .name = (n), .a = (h), .m = (hm) } } }
|
| 60 |
|
| 61 | static const struct lcd_segment_map {
|
| 62 | char type;
|
| 63 | union {
|
| 64 | struct pictogram_map {
|
| 65 | u8 a,m;
|
| 66 | char name[10];
|
| 67 | } p;
|
| 68 | struct segment_map {
|
| 69 | u8 a,m;
|
| 70 | } s[7];
|
| 71 | } u;
|
| 72 | } lcdMap[] = {
|
| 73 | #include "yealink.h"
|
| 74 | };
|
| 75 |
|
| 76 | struct yealink_dev {
|
| 77 | struct input_dev *idev;
|
| 78 | struct usb_device *udev;
|
| 79 | struct usb_interface *intf;
|
| 80 |
|
| 81 |
|
| 82 | struct yld_ctl_packet *irq_data;
|
| 83 | dma_addr_t irq_dma;
|
| 84 | struct urb *urb_irq;
|
| 85 |
|
| 86 |
|
| 87 | struct yld_ctl_packet *ctl_data;
|
| 88 | dma_addr_t ctl_dma;
|
| 89 | struct usb_ctrlrequest *ctl_req;
|
| 90 | struct urb *urb_ctl;
|
| 91 |
|
| 92 | char phys[64];
|
| 93 |
|
| 94 | u8 lcdMap[ARRAY_SIZE(lcdMap)];
|
| 95 | int key_code;
|
| 96 |
|
| 97 | struct mutex sysfs_mutex;
|
| 98 |
|
| 99 | unsigned int shutdown:1;
|
| 100 |
|
| 101 | int stat_ix;
|
| 102 | union {
|
| 103 | struct yld_status s;
|
| 104 | u8 b[sizeof(struct yld_status)];
|
| 105 | } master, copy;
|
| 106 | };
|
| 107 |
|
| 108 |
|
| 109 | |
| 110 | * Yealink lcd interface
|
| 111 | *****************************************************************************
|
| 112 |
|
| 113 | |
| 114 | * Register a default 7 segment character set
|
| 115 |
|
| 116 | static SEG7_DEFAULT_MAP(map_seg7);
|
| 117 |
|
| 118 | |
| 119 | * char '\9' and '\n' are placeholders and do not overwrite the original text.
|
| 120 | * A space will always hide an icon.
|
| 121 |
|
| 122 | static int setChar(struct yealink_dev *yld, int el, int chr)
|
| 123 | {
|
| 124 | int i, a, m, val;
|
| 125 |
|
| 126 | if (el >= ARRAY_SIZE(lcdMap))
|
| 127 | return -EINVAL;
|
| 128 |
|
| 129 | if (chr == '\t' || chr == '\n')
|
| 130 | return 0;
|
| 131 |
|
| 132 | yld->lcdMap[el] = chr;
|
| 133 |
|
| 134 | if (lcdMap[el].type == '.') {
|
| 135 | a = lcdMap[el].u.p.a;
|
| 136 | m = lcdMap[el].u.p.m;
|
| 137 | if (chr != ' ')
|
| 138 | yld->master.b[a] |= m;
|
| 139 | else
|
| 140 | yld->master.b[a] &= ~m;
|
| 141 | return 0;
|
| 142 | }
|
| 143 |
|
| 144 | val = map_to_seg7(&map_seg7, chr);
|
| 145 | for (i = 0; i < ARRAY_SIZE(lcdMap[0].u.s); i++) {
|
| 146 | m = lcdMap[el].u.s[i].m;
|
| 147 |
|
| 148 | if (m == 0)
|
| 149 | continue;
|
| 150 |
|
| 151 | a = lcdMap[el].u.s[i].a;
|
| 152 | if (val & 1)
|
| 153 | yld->master.b[a] |= m;
|
| 154 | else
|
| 155 | yld->master.b[a] &= ~m;
|
| 156 | val = val >> 1;
|
| 157 | }
|
| 158 | return 0;
|
| 159 | };
|
| 160 |
|
| 161 | |
| 162 | * Yealink key interface
|
| 163 | *****************************************************************************
|
| 164 |
|
| 165 | |
| 166 | *
|
| 167 | * USB-P1K button layout:
|
| 168 | *
|
| 169 | * up
|
| 170 | * IN OUT
|
| 171 | * down
|
| 172 | *
|
| 173 | * pickup C hangup
|
| 174 | * 1 2 3
|
| 175 | * 4 5 6
|
| 176 | * 7 8 9
|
| 177 | * * 0 #
|
| 178 | *
|
| 179 | * The "up" and "down" keys, are symbolised by arrows on the button.
|
| 180 | * The "pickup" and "hangup" keys are symbolised by a green and red phone
|
| 181 | * on the button.
|
| 182 |
|
| 183 | static int map_p1k_to_key(int scancode)
|
| 184 | {
|
| 185 | switch(scancode) {
|
| 186 | case 0x23: return KEY_LEFT;
|
| 187 | case 0x33: return KEY_UP;
|
| 188 | case 0x04: return KEY_RIGHT;
|
| 189 | case 0x24: return KEY_DOWN;
|
| 190 | case 0x03: return KEY_ENTER;
|
| 191 | case 0x14: return KEY_BACKSPACE;
|
| 192 | case 0x13: return KEY_ESC;
|
| 193 | case 0x00: return KEY_1;
|
| 194 | case 0x01: return KEY_2;
|
| 195 | case 0x02: return KEY_3;
|
| 196 | case 0x10: return KEY_4;
|
| 197 | case 0x11: return KEY_5;
|
| 198 | case 0x12: return KEY_6;
|
| 199 | case 0x20: return KEY_7;
|
| 200 | case 0x21: return KEY_8;
|
| 201 | case 0x22: return KEY_9;
|
| 202 | case 0x30: return KEY_KPASTERISK;
|
| 203 | case 0x31: return KEY_0;
|
| 204 | case 0x32: return KEY_LEFTSHIFT |
|
| 205 | KEY_3 << 8;
|
| 206 | }
|
| 207 | return -EINVAL;
|
| 208 | }
|
| 209 |
|
| 210 | |
| 211 | * input subsystem.
|
| 212 | *
|
| 213 | * The key parameter can be cascaded: key2 << 8 | key1
|
| 214 |
|
| 215 | static void report_key(struct yealink_dev *yld, int key)
|
| 216 | {
|
| 217 | struct input_dev *idev = yld->idev;
|
| 218 |
|
| 219 | if (yld->key_code >= 0) {
|
| 220 |
|
| 221 | input_report_key(idev, yld->key_code & 0xff, 0);
|
| 222 | if (yld->key_code >> 8)
|
| 223 | input_report_key(idev, yld->key_code >> 8, 0);
|
| 224 | }
|
| 225 |
|
| 226 | yld->key_code = key;
|
| 227 | if (key >= 0) {
|
| 228 |
|
| 229 | input_report_key(idev, key & 0xff, 1);
|
| 230 | if (key >> 8)
|
| 231 | input_report_key(idev, key >> 8, 1);
|
| 232 | }
|
| 233 | input_sync(idev);
|
| 234 | }
|
| 235 |
|
| 236 | |
| 237 | * Yealink usb communication interface
|
| 238 | *****************************************************************************
|
| 239 |
|
| 240 | static int yealink_cmd(struct yealink_dev *yld, struct yld_ctl_packet *p)
|
| 241 | {
|
| 242 | u8 *buf = (u8 *)p;
|
| 243 | int i;
|
| 244 | u8 sum = 0;
|
| 245 |
|
| 246 | for(i=0; i<USB_PKT_LEN-1; i++)
|
| 247 | sum -= buf[i];
|
| 248 | p->sum = sum;
|
| 249 | return usb_control_msg(yld->udev,
|
| 250 | usb_sndctrlpipe(yld->udev, 0),
|
| 251 | USB_REQ_SET_CONFIGURATION,
|
| 252 | USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
|
| 253 | 0x200, 3,
|
| 254 | p, sizeof(*p),
|
| 255 | USB_CTRL_SET_TIMEOUT);
|
| 256 | }
|
| 257 |
|
| 258 | static u8 default_ringtone[] = {
|
| 259 | 0xEF,
|
| 260 | 0xFB, 0x1E, 0x00, 0x0C,
|
| 261 | 0xFC, 0x18, 0x00, 0x0C,
|
| 262 | 0xFB, 0x1E, 0x00, 0x0C,
|
| 263 | 0xFC, 0x18, 0x00, 0x0C,
|
| 264 | 0xFB, 0x1E, 0x00, 0x0C,
|
| 265 | 0xFC, 0x18, 0x00, 0x0C,
|
| 266 | 0xFB, 0x1E, 0x00, 0x0C,
|
| 267 | 0xFC, 0x18, 0x00, 0x0C,
|
| 268 | 0xFF, 0xFF, 0x01, 0x90,
|
| 269 | 0x00, 0x00
|
| 270 | };
|
| 271 |
|
| 272 | static int yealink_set_ringtone(struct yealink_dev *yld, u8 *buf, size_t size)
|
| 273 | {
|
| 274 | struct yld_ctl_packet *p = yld->ctl_data;
|
| 275 | int ix, len;
|
| 276 |
|
| 277 | if (size <= 0)
|
| 278 | return -EINVAL;
|
| 279 |
|
| 280 |
|
| 281 | memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
|
| 282 | yld->ctl_data->cmd = CMD_RING_VOLUME;
|
| 283 | yld->ctl_data->size = 1;
|
| 284 | yld->ctl_data->data[0] = buf[0];
|
| 285 | yealink_cmd(yld, p);
|
| 286 |
|
| 287 | buf++;
|
| 288 | size--;
|
| 289 |
|
| 290 | p->cmd = CMD_RING_NOTE;
|
| 291 | ix = 0;
|
| 292 | while (size != ix) {
|
| 293 | len = size - ix;
|
| 294 | if (len > sizeof(p->data))
|
| 295 | len = sizeof(p->data);
|
| 296 | p->size = len;
|
| 297 | p->offset = cpu_to_be16(ix);
|
| 298 | memcpy(p->data, &buf[ix], len);
|
| 299 | yealink_cmd(yld, p);
|
| 300 | ix += len;
|
| 301 | }
|
| 302 | return 0;
|
| 303 | }
|
| 304 |
|
| 305 | |
| 306 |
|
| 307 | static int yealink_do_idle_tasks(struct yealink_dev *yld)
|
| 308 | {
|
| 309 | u8 val;
|
| 310 | int i, ix, len;
|
| 311 |
|
| 312 | ix = yld->stat_ix;
|
| 313 |
|
| 314 | memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
|
| 315 | yld->ctl_data->cmd = CMD_KEYPRESS;
|
| 316 | yld->ctl_data->size = 1;
|
| 317 | yld->ctl_data->sum = 0xff - CMD_KEYPRESS;
|
| 318 |
|
| 319 |
|
| 320 | if (ix >= sizeof(yld->master)) {
|
| 321 | yld->stat_ix = 0;
|
| 322 | return 0;
|
| 323 | }
|
| 324 |
|
| 325 |
|
| 326 | do {
|
| 327 | val = yld->master.b[ix];
|
| 328 | if (val != yld->copy.b[ix])
|
| 329 | goto send_update;
|
| 330 | } while (++ix < sizeof(yld->master));
|
| 331 |
|
| 332 |
|
| 333 | yld->stat_ix = 0;
|
| 334 | |
| 335 | * msleep_interruptible(1000 / YEALINK_POLLING_FREQUENCY);
|
| 336 |
|
| 337 | return 0;
|
| 338 |
|
| 339 | send_update:
|
| 340 |
|
| 341 |
|
| 342 | yld->copy.b[ix] = val;
|
| 343 | yld->ctl_data->data[0] = val;
|
| 344 |
|
| 345 | switch(ix) {
|
| 346 | case offsetof(struct yld_status, led):
|
| 347 | yld->ctl_data->cmd = CMD_LED;
|
| 348 | yld->ctl_data->sum = -1 - CMD_LED - val;
|
| 349 | break;
|
| 350 | case offsetof(struct yld_status, dialtone):
|
| 351 | yld->ctl_data->cmd = CMD_DIALTONE;
|
| 352 | yld->ctl_data->sum = -1 - CMD_DIALTONE - val;
|
| 353 | break;
|
| 354 | case offsetof(struct yld_status, ringtone):
|
| 355 | yld->ctl_data->cmd = CMD_RINGTONE;
|
| 356 | yld->ctl_data->sum = -1 - CMD_RINGTONE - val;
|
| 357 | break;
|
| 358 | case offsetof(struct yld_status, keynum):
|
| 359 | val--;
|
| 360 | val &= 0x1f;
|
| 361 | yld->ctl_data->cmd = CMD_SCANCODE;
|
| 362 | yld->ctl_data->offset = cpu_to_be16(val);
|
| 363 | yld->ctl_data->data[0] = 0;
|
| 364 | yld->ctl_data->sum = -1 - CMD_SCANCODE - val;
|
| 365 | break;
|
| 366 | default:
|
| 367 | len = sizeof(yld->master.s.lcd) - ix;
|
| 368 | if (len > sizeof(yld->ctl_data->data))
|
| 369 | len = sizeof(yld->ctl_data->data);
|
| 370 |
|
| 371 | |
| 372 |
|
| 373 | yld->ctl_data->cmd = CMD_LCD;
|
| 374 | yld->ctl_data->offset = cpu_to_be16(ix);
|
| 375 | yld->ctl_data->size = len;
|
| 376 | yld->ctl_data->sum = -CMD_LCD - ix - val - len;
|
| 377 | for(i=1; i<len; i++) {
|
| 378 | ix++;
|
| 379 | val = yld->master.b[ix];
|
| 380 | yld->copy.b[ix] = val;
|
| 381 | yld->ctl_data->data[i] = val;
|
| 382 | yld->ctl_data->sum -= val;
|
| 383 | }
|
| 384 | }
|
| 385 | yld->stat_ix = ix + 1;
|
| 386 | return 1;
|
| 387 | }
|
| 388 |
|
| 389 | |
| 390 | *
|
| 391 | * The state transition diagram is somethhing like:
|
| 392 | *
|
| 393 | * syncState<--+
|
| 394 | * | |
|
| 395 | * | idle
|
| 396 | * \|/ |
|
| 397 | * init --ok--> waitForKey --ok--> getKey
|
| 398 | * ^ ^ |
|
| 399 | * | +-------ok-------+
|
| 400 | * error,start
|
| 401 | *
|
| 402 |
|
| 403 | static void urb_irq_callback(struct urb *urb)
|
| 404 | {
|
| 405 | struct yealink_dev *yld = urb->context;
|
| 406 | int ret, status = urb->status;
|
| 407 |
|
| 408 | if (status)
|
| 409 | dev_err(&yld->intf->dev, "%s - urb status %d\n",
|
| 410 | __func__, status);
|
| 411 |
|
| 412 | switch (yld->irq_data->cmd) {
|
| 413 | case CMD_KEYPRESS:
|
| 414 |
|
| 415 | yld->master.s.keynum = yld->irq_data->data[0];
|
| 416 | break;
|
| 417 |
|
| 418 | case CMD_SCANCODE:
|
| 419 | dev_dbg(&yld->intf->dev, "get scancode %x\n",
|
| 420 | yld->irq_data->data[0]);
|
| 421 |
|
| 422 | report_key(yld, map_p1k_to_key(yld->irq_data->data[0]));
|
| 423 | break;
|
| 424 |
|
| 425 | default:
|
| 426 | dev_err(&yld->intf->dev, "unexpected response %x\n",
|
| 427 | yld->irq_data->cmd);
|
| 428 | }
|
| 429 |
|
| 430 | yealink_do_idle_tasks(yld);
|
| 431 |
|
| 432 | if (!yld->shutdown) {
|
| 433 | ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC);
|
| 434 | if (ret && ret != -EPERM)
|
| 435 | dev_err(&yld->intf->dev,
|
| 436 | "%s - usb_submit_urb failed %d\n",
|
| 437 | __func__, ret);
|
| 438 | }
|
| 439 | }
|
| 440 |
|
| 441 | static void urb_ctl_callback(struct urb *urb)
|
| 442 | {
|
| 443 | struct yealink_dev *yld = urb->context;
|
| 444 | int ret = 0, status = urb->status;
|
| 445 |
|
| 446 | if (status)
|
| 447 | dev_err(&yld->intf->dev, "%s - urb status %d\n",
|
| 448 | __func__, status);
|
| 449 |
|
| 450 | switch (yld->ctl_data->cmd) {
|
| 451 | case CMD_KEYPRESS:
|
| 452 | case CMD_SCANCODE:
|
| 453 |
|
| 454 | if (!yld->shutdown)
|
| 455 | ret = usb_submit_urb(yld->urb_irq, GFP_ATOMIC);
|
| 456 | break;
|
| 457 | default:
|
| 458 |
|
| 459 | yealink_do_idle_tasks(yld);
|
| 460 | if (!yld->shutdown)
|
| 461 | ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC);
|
| 462 | break;
|
| 463 | }
|
| 464 |
|
| 465 | if (ret && ret != -EPERM)
|
| 466 | dev_err(&yld->intf->dev, "%s - usb_submit_urb failed %d\n",
|
| 467 | __func__, ret);
|
| 468 | }
|
| 469 |
|
| 470 | |
| 471 | * input event interface
|
| 472 | *****************************************************************************
|
| 473 |
|
| 474 | |
| 475 | static int input_ev(struct input_dev *dev, unsigned int type,
|
| 476 | unsigned int code, int value)
|
| 477 | {
|
| 478 |
|
| 479 | if (type != EV_SND)
|
| 480 | return -EINVAL;
|
| 481 |
|
| 482 | switch (code) {
|
| 483 | case SND_BELL:
|
| 484 | case SND_TONE:
|
| 485 | break;
|
| 486 | default:
|
| 487 | return -EINVAL;
|
| 488 | }
|
| 489 |
|
| 490 | return 0;
|
| 491 | }
|
| 492 |
|
| 493 |
|
| 494 | static int input_open(struct input_dev *dev)
|
| 495 | {
|
| 496 | struct yealink_dev *yld = input_get_drvdata(dev);
|
| 497 | int i, ret;
|
| 498 |
|
| 499 | dev_dbg(&yld->intf->dev, "%s\n", __func__);
|
| 500 |
|
| 501 |
|
| 502 | for (i = 0; i<sizeof(yld->master); i++)
|
| 503 | yld->copy.b[i] = ~yld->master.b[i];
|
| 504 | yld->key_code = -1;
|
| 505 |
|
| 506 | yealink_set_ringtone(yld, default_ringtone, sizeof(default_ringtone));
|
| 507 |
|
| 508 |
|
| 509 | memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
|
| 510 | yld->ctl_data->cmd = CMD_INIT;
|
| 511 | yld->ctl_data->size = 10;
|
| 512 | yld->ctl_data->sum = 0x100-CMD_INIT-10;
|
| 513 | if ((ret = usb_submit_urb(yld->urb_ctl, GFP_KERNEL)) != 0) {
|
| 514 | dev_dbg(&yld->intf->dev,
|
| 515 | "%s - usb_submit_urb failed with result %d\n",
|
| 516 | __func__, ret);
|
| 517 | return ret;
|
| 518 | }
|
| 519 | return 0;
|
| 520 | }
|
| 521 |
|
| 522 | static void input_close(struct input_dev *dev)
|
| 523 | {
|
| 524 | struct yealink_dev *yld = input_get_drvdata(dev);
|
| 525 |
|
| 526 | yld->shutdown = 1;
|
| 527 | |
| 528 | * Make sure the flag is seen by other CPUs before we start
|
| 529 | * killing URBs so new URBs won't be submitted
|
| 530 |
|
| 531 | smp_wmb();
|
| 532 |
|
| 533 | usb_kill_urb(yld->urb_ctl);
|
| 534 | usb_kill_urb(yld->urb_irq);
|
| 535 |
|
| 536 | yld->shutdown = 0;
|
| 537 | smp_wmb();
|
| 538 | }
|
| 539 |
|
| 540 | |
| 541 | * sysfs interface
|
| 542 | *****************************************************************************
|
| 543 |
|
| 544 | |
| 545 |
|
| 546 | static ssize_t show_map(struct device *dev, struct device_attribute *attr,
|
| 547 | char *buf)
|
| 548 | {
|
| 549 | memcpy(buf, &map_seg7, sizeof(map_seg7));
|
| 550 | return sizeof(map_seg7);
|
| 551 | }
|
| 552 |
|
| 553 | static ssize_t store_map(struct device *dev, struct device_attribute *attr,
|
| 554 | const char *buf, size_t cnt)
|
| 555 | {
|
| 556 | if (cnt != sizeof(map_seg7))
|
| 557 | return -EINVAL;
|
| 558 | memcpy(&map_seg7, buf, sizeof(map_seg7));
|
| 559 | return sizeof(map_seg7);
|
| 560 | }
|
| 561 |
|
| 562 | |
| 563 |
|
| 564 |
|
| 565 | |
| 566 | *
|
| 567 | * Example:
|
| 568 | * cat ./line3
|
| 569 | * 888888888888
|
| 570 | * Linux Rocks!
|
| 571 |
|
| 572 | static ssize_t show_line(struct device *dev, char *buf, int a, int b)
|
| 573 | {
|
| 574 | struct yealink_dev *yld = dev_get_drvdata(dev);
|
| 575 | int i;
|
| 576 |
|
| 577 | guard(mutex)(&yld->sysfs_mutex);
|
| 578 |
|
| 579 | for (i = a; i < b; i++)
|
| 580 | *buf++ = lcdMap[i].type;
|
| 581 | *buf++ = '\n';
|
| 582 | for (i = a; i < b; i++)
|
| 583 | *buf++ = yld->lcdMap[i];
|
| 584 | *buf++ = '\n';
|
| 585 | *buf = 0;
|
| 586 |
|
| 587 | return 3 + ((b - a) << 1);
|
| 588 | }
|
| 589 |
|
| 590 | static ssize_t show_line1(struct device *dev, struct device_attribute *attr,
|
| 591 | char *buf)
|
| 592 | {
|
| 593 | return show_line(dev, buf, LCD_LINE1_OFFSET, LCD_LINE2_OFFSET);
|
| 594 | }
|
| 595 |
|
| 596 | static ssize_t show_line2(struct device *dev, struct device_attribute *attr,
|
| 597 | char *buf)
|
| 598 | {
|
| 599 | return show_line(dev, buf, LCD_LINE2_OFFSET, LCD_LINE3_OFFSET);
|
| 600 | }
|
| 601 |
|
| 602 | static ssize_t show_line3(struct device *dev, struct device_attribute *attr,
|
| 603 | char *buf)
|
| 604 | {
|
| 605 | return show_line(dev, buf, LCD_LINE3_OFFSET, LCD_LINE4_OFFSET);
|
| 606 | }
|
| 607 |
|
| 608 | |
| 609 | * - Excess characters are ignored.
|
| 610 | * - If less characters are written than allowed, the remaining digits are
|
| 611 | * unchanged.
|
| 612 | * - The '\n' or '\t' char is a placeholder, it does not overwrite the
|
| 613 | * original content.
|
| 614 |
|
| 615 | static ssize_t store_line(struct device *dev, const char *buf, size_t count,
|
| 616 | int el, size_t len)
|
| 617 | {
|
| 618 | struct yealink_dev *yld = dev_get_drvdata(dev);
|
| 619 | int i;
|
| 620 |
|
| 621 | guard(mutex)(&yld->sysfs_mutex);
|
| 622 |
|
| 623 | if (len > count)
|
| 624 | len = count;
|
| 625 | for (i = 0; i < len; i++)
|
| 626 | setChar(yld, el++, buf[i]);
|
| 627 |
|
| 628 | return count;
|
| 629 | }
|
| 630 |
|
| 631 | static ssize_t store_line1(struct device *dev, struct device_attribute *attr,
|
| 632 | const char *buf, size_t count)
|
| 633 | {
|
| 634 | return store_line(dev, buf, count, LCD_LINE1_OFFSET, LCD_LINE1_SIZE);
|
| 635 | }
|
| 636 |
|
| 637 | static ssize_t store_line2(struct device *dev, struct device_attribute *attr,
|
| 638 | const char *buf, size_t count)
|
| 639 | {
|
| 640 | return store_line(dev, buf, count, LCD_LINE2_OFFSET, LCD_LINE2_SIZE);
|
| 641 | }
|
| 642 |
|
| 643 | static ssize_t store_line3(struct device *dev, struct device_attribute *attr,
|
| 644 | const char *buf, size_t count)
|
| 645 | {
|
| 646 | return store_line(dev, buf, count, LCD_LINE3_OFFSET, LCD_LINE3_SIZE);
|
| 647 | }
|
| 648 |
|
| 649 | |
| 650 | * pictures on the LCD, the LED, and the dialtone signal.
|
| 651 |
|
| 652 |
|
| 653 |
|
| 654 | static ssize_t get_icons(struct device *dev, struct device_attribute *attr,
|
| 655 | char *buf)
|
| 656 | {
|
| 657 | struct yealink_dev *yld = dev_get_drvdata(dev);
|
| 658 | int i, ret = 1;
|
| 659 |
|
| 660 | guard(mutex)(&yld->sysfs_mutex);
|
| 661 |
|
| 662 | for (i = 0; i < ARRAY_SIZE(lcdMap); i++) {
|
| 663 | if (lcdMap[i].type != '.')
|
| 664 | continue;
|
| 665 | ret += sprintf(&buf[ret], "%s %s\n",
|
| 666 | yld->lcdMap[i] == ' ' ? " " : "on",
|
| 667 | lcdMap[i].u.p.name);
|
| 668 | }
|
| 669 |
|
| 670 | return ret;
|
| 671 | }
|
| 672 |
|
| 673 |
|
| 674 | static ssize_t set_icon(struct device *dev, const char *buf, size_t count,
|
| 675 | int chr)
|
| 676 | {
|
| 677 | struct yealink_dev *yld = dev_get_drvdata(dev);
|
| 678 | int i;
|
| 679 |
|
| 680 | guard(mutex)(&yld->sysfs_mutex);
|
| 681 |
|
| 682 | for (i = 0; i < ARRAY_SIZE(lcdMap); i++) {
|
| 683 | if (lcdMap[i].type != '.')
|
| 684 | continue;
|
| 685 | if (strncmp(buf, lcdMap[i].u.p.name, count) == 0) {
|
| 686 | setChar(yld, i, chr);
|
| 687 | break;
|
| 688 | }
|
| 689 | }
|
| 690 |
|
| 691 | return count;
|
| 692 | }
|
| 693 |
|
| 694 | static ssize_t show_icon(struct device *dev, struct device_attribute *attr,
|
| 695 | const char *buf, size_t count)
|
| 696 | {
|
| 697 | return set_icon(dev, buf, count, buf[0]);
|
| 698 | }
|
| 699 |
|
| 700 | static ssize_t hide_icon(struct device *dev, struct device_attribute *attr,
|
| 701 | const char *buf, size_t count)
|
| 702 | {
|
| 703 | return set_icon(dev, buf, count, ' ');
|
| 704 | }
|
| 705 |
|
| 706 | |
| 707 |
|
| 708 |
|
| 709 |
|
| 710 | static ssize_t store_ringtone(struct device *dev, struct device_attribute *attr,
|
| 711 | const char *buf, size_t count)
|
| 712 | {
|
| 713 | struct yealink_dev *yld = dev_get_drvdata(dev);
|
| 714 |
|
| 715 | guard(mutex)(&yld->sysfs_mutex);
|
| 716 |
|
| 717 |
|
| 718 | yealink_set_ringtone(yld, (char *)buf, count);
|
| 719 |
|
| 720 | return count;
|
| 721 | }
|
| 722 |
|
| 723 | #define _M444 S_IRUGO
|
| 724 | #define _M664 S_IRUGO|S_IWUSR|S_IWGRP
|
| 725 | #define _M220 S_IWUSR|S_IWGRP
|
| 726 |
|
| 727 | static DEVICE_ATTR(map_seg7 , _M664, show_map , store_map );
|
| 728 | static DEVICE_ATTR(line1 , _M664, show_line1 , store_line1 );
|
| 729 | static DEVICE_ATTR(line2 , _M664, show_line2 , store_line2 );
|
| 730 | static DEVICE_ATTR(line3 , _M664, show_line3 , store_line3 );
|
| 731 | static DEVICE_ATTR(get_icons , _M444, get_icons , NULL );
|
| 732 | static DEVICE_ATTR(show_icon , _M220, NULL , show_icon );
|
| 733 | static DEVICE_ATTR(hide_icon , _M220, NULL , hide_icon );
|
| 734 | static DEVICE_ATTR(ringtone , _M220, NULL , store_ringtone);
|
| 735 |
|
| 736 | static struct attribute *yld_attrs[] = {
|
| 737 | &dev_attr_line1.attr,
|
| 738 | &dev_attr_line2.attr,
|
| 739 | &dev_attr_line3.attr,
|
| 740 | &dev_attr_get_icons.attr,
|
| 741 | &dev_attr_show_icon.attr,
|
| 742 | &dev_attr_hide_icon.attr,
|
| 743 | &dev_attr_map_seg7.attr,
|
| 744 | &dev_attr_ringtone.attr,
|
| 745 | NULL
|
| 746 | };
|
| 747 | ATTRIBUTE_GROUPS(yld);
|
| 748 |
|
| 749 | |
| 750 | * Linux interface and usb initialisation
|
| 751 | *****************************************************************************
|
| 752 |
|
| 753 | struct driver_info {
|
| 754 | char *name;
|
| 755 | };
|
| 756 |
|
| 757 | static const struct driver_info info_P1K = {
|
| 758 | .name = "Yealink usb-p1k",
|
| 759 | };
|
| 760 |
|
| 761 | static const struct usb_device_id usb_table [] = {
|
| 762 | {
|
| 763 | .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
|
| 764 | USB_DEVICE_ID_MATCH_INT_INFO,
|
| 765 | .idVendor = 0x6993,
|
| 766 | .idProduct = 0xb001,
|
| 767 | .bInterfaceClass = USB_CLASS_HID,
|
| 768 | .bInterfaceSubClass = 0,
|
| 769 | .bInterfaceProtocol = 0,
|
| 770 | .driver_info = (kernel_ulong_t)&info_P1K
|
| 771 | },
|
| 772 | { }
|
| 773 | };
|
| 774 |
|
| 775 | static int usb_cleanup(struct yealink_dev *yld, int err)
|
| 776 | {
|
| 777 | if (yld == NULL)
|
| 778 | return err;
|
| 779 |
|
| 780 | if (yld->idev) {
|
| 781 | if (err)
|
| 782 | input_free_device(yld->idev);
|
| 783 | else
|
| 784 | input_unregister_device(yld->idev);
|
| 785 | }
|
| 786 |
|
| 787 | usb_free_urb(yld->urb_irq);
|
| 788 | usb_free_urb(yld->urb_ctl);
|
| 789 |
|
| 790 | kfree(yld->ctl_req);
|
| 791 | usb_free_coherent(yld->udev, USB_PKT_LEN, yld->ctl_data, yld->ctl_dma);
|
| 792 | usb_free_coherent(yld->udev, USB_PKT_LEN, yld->irq_data, yld->irq_dma);
|
| 793 |
|
| 794 | kfree(yld);
|
| 795 | return err;
|
| 796 | }
|
| 797 |
|
| 798 | static void usb_disconnect(struct usb_interface *intf)
|
| 799 | {
|
| 800 | struct yealink_dev *yld = usb_get_intfdata(intf);
|
| 801 |
|
| 802 | usb_cleanup(yld, 0);
|
| 803 | usb_set_intfdata(intf, NULL);
|
| 804 | }
|
| 805 |
|
| 806 | static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
| 807 | {
|
| 808 | struct usb_device *udev = interface_to_usbdev (intf);
|
| 809 | struct driver_info *nfo = (struct driver_info *)id->driver_info;
|
| 810 | struct usb_host_interface *interface;
|
| 811 | struct usb_endpoint_descriptor *endpoint;
|
| 812 | struct yealink_dev *yld;
|
| 813 | struct input_dev *input_dev;
|
| 814 | int ret, pipe, i;
|
| 815 |
|
| 816 | interface = intf->cur_altsetting;
|
| 817 |
|
| 818 | if (interface->desc.bNumEndpoints < 1)
|
| 819 | return -ENODEV;
|
| 820 |
|
| 821 | endpoint = &interface->endpoint[0].desc;
|
| 822 | if (!usb_endpoint_is_int_in(endpoint))
|
| 823 | return -ENODEV;
|
| 824 |
|
| 825 | yld = kzalloc_obj(*yld);
|
| 826 | if (!yld)
|
| 827 | return -ENOMEM;
|
| 828 |
|
| 829 | yld->udev = udev;
|
| 830 | yld->intf = intf;
|
| 831 | mutex_init(&yld->sysfs_mutex);
|
| 832 |
|
| 833 | yld->idev = input_dev = input_allocate_device();
|
| 834 | if (!input_dev)
|
| 835 | return usb_cleanup(yld, -ENOMEM);
|
| 836 |
|
| 837 |
|
| 838 | yld->irq_data = usb_alloc_coherent(udev, USB_PKT_LEN,
|
| 839 | GFP_KERNEL, &yld->irq_dma);
|
| 840 | if (yld->irq_data == NULL)
|
| 841 | return usb_cleanup(yld, -ENOMEM);
|
| 842 |
|
| 843 | yld->ctl_data = usb_alloc_coherent(udev, USB_PKT_LEN,
|
| 844 | GFP_KERNEL, &yld->ctl_dma);
|
| 845 | if (!yld->ctl_data)
|
| 846 | return usb_cleanup(yld, -ENOMEM);
|
| 847 |
|
| 848 | yld->ctl_req = kmalloc_obj(*(yld->ctl_req));
|
| 849 | if (yld->ctl_req == NULL)
|
| 850 | return usb_cleanup(yld, -ENOMEM);
|
| 851 |
|
| 852 |
|
| 853 | yld->urb_irq = usb_alloc_urb(0, GFP_KERNEL);
|
| 854 | if (yld->urb_irq == NULL)
|
| 855 | return usb_cleanup(yld, -ENOMEM);
|
| 856 |
|
| 857 | yld->urb_ctl = usb_alloc_urb(0, GFP_KERNEL);
|
| 858 | if (yld->urb_ctl == NULL)
|
| 859 | return usb_cleanup(yld, -ENOMEM);
|
| 860 |
|
| 861 |
|
| 862 | pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
|
| 863 | ret = usb_maxpacket(udev, pipe);
|
| 864 | if (ret != USB_PKT_LEN)
|
| 865 | dev_err(&intf->dev, "invalid payload size %d, expected %zd\n",
|
| 866 | ret, USB_PKT_LEN);
|
| 867 |
|
| 868 |
|
| 869 | usb_fill_int_urb(yld->urb_irq, udev, pipe, yld->irq_data,
|
| 870 | USB_PKT_LEN,
|
| 871 | urb_irq_callback,
|
| 872 | yld, endpoint->bInterval);
|
| 873 | yld->urb_irq->transfer_dma = yld->irq_dma;
|
| 874 | yld->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
| 875 | yld->urb_irq->dev = udev;
|
| 876 |
|
| 877 |
|
| 878 | yld->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE |
|
| 879 | USB_DIR_OUT;
|
| 880 | yld->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION;
|
| 881 | yld->ctl_req->wValue = cpu_to_le16(0x200);
|
| 882 | yld->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
|
| 883 | yld->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN);
|
| 884 |
|
| 885 | usb_fill_control_urb(yld->urb_ctl, udev, usb_sndctrlpipe(udev, 0),
|
| 886 | (void *)yld->ctl_req, yld->ctl_data, USB_PKT_LEN,
|
| 887 | urb_ctl_callback, yld);
|
| 888 | yld->urb_ctl->transfer_dma = yld->ctl_dma;
|
| 889 | yld->urb_ctl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
| 890 | yld->urb_ctl->dev = udev;
|
| 891 |
|
| 892 |
|
| 893 | usb_make_path(udev, yld->phys, sizeof(yld->phys));
|
| 894 | strlcat(yld->phys, "/input0", sizeof(yld->phys));
|
| 895 |
|
| 896 |
|
| 897 | input_dev->name = nfo->name;
|
| 898 | input_dev->phys = yld->phys;
|
| 899 | usb_to_input_id(udev, &input_dev->id);
|
| 900 | input_dev->dev.parent = &intf->dev;
|
| 901 |
|
| 902 | input_set_drvdata(input_dev, yld);
|
| 903 |
|
| 904 | input_dev->open = input_open;
|
| 905 | input_dev->close = input_close;
|
| 906 |
|
| 907 |
|
| 908 |
|
| 909 | input_dev->evbit[0] = BIT_MASK(EV_KEY);
|
| 910 | for (i = 0; i < 256; i++) {
|
| 911 | int k = map_p1k_to_key(i);
|
| 912 | if (k >= 0) {
|
| 913 | set_bit(k & 0xff, input_dev->keybit);
|
| 914 | if (k >> 8)
|
| 915 | set_bit(k >> 8, input_dev->keybit);
|
| 916 | }
|
| 917 | }
|
| 918 |
|
| 919 | ret = input_register_device(yld->idev);
|
| 920 | if (ret)
|
| 921 | return usb_cleanup(yld, ret);
|
| 922 |
|
| 923 | usb_set_intfdata(intf, yld);
|
| 924 |
|
| 925 |
|
| 926 | for (i = 0; i < ARRAY_SIZE(lcdMap); i++)
|
| 927 | setChar(yld, i, ' ');
|
| 928 |
|
| 929 |
|
| 930 | store_line3(&intf->dev, NULL,
|
| 931 | DRIVER_VERSION, sizeof(DRIVER_VERSION));
|
| 932 |
|
| 933 | return 0;
|
| 934 | }
|
| 935 |
|
| 936 | static struct usb_driver yealink_driver = {
|
| 937 | .name = "yealink",
|
| 938 | .probe = usb_probe,
|
| 939 | .disconnect = usb_disconnect,
|
| 940 | .id_table = usb_table,
|
| 941 | .dev_groups = yld_groups,
|
| 942 | };
|
| 943 |
|
| 944 | module_usb_driver(yealink_driver);
|
| 945 |
|
| 946 | MODULE_DEVICE_TABLE (usb, usb_table);
|
| 947 |
|
| 948 | MODULE_AUTHOR("Henk Vergonet");
|
| 949 | MODULE_DESCRIPTION("Yealink phone driver");
|
| 950 | MODULE_LICENSE("GPL");
|