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