/*
* drivers/usb/input/yealink.c
*
* Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Description:
* Driver for the USB-P1K voip usb phone.
* This device is produced by Yealink Network Technology Co Ltd
* but may be branded under several names:
* - Yealink usb-p1k
* - Tiptel 115
* - ...
*
* This driver is based on:
* - the usbb2k-api http://savannah.nongnu.org/projects/usbb2k-api/
* - information from http://memeteau.free.fr/usbb2k
* - the xpad-driver drivers/input/joystick/xpad.c
*
* Thanks to:
* - Olivier Vandorpe, for providing the usbb2k-api.
* - Martin Diehl, for spotting my memory allocation bug.
*
* History:
* 20050527 henk First version, functional keyboard. Keyboard events
* will pop-up on the ../input/eventX bus.
* 20050531 henk Added led, LCD, dialtone and sysfs interface.
* 20050610 henk Cleanups, make it ready for public consumption.
* 20050630 henk Cleanups, fixes in response to comments.
* 20050701 henk sysfs write serialisation, fix potential unload races
* 20050801 henk Added ringtone, restructure USB
* 20050816 henk Merge 2.6.13-rc6
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/rwsem.h>
#include <linux/usb/input.h>
#include <linux/map_to_7segment.h>
#include "yealink.h"
#define DRIVER_VERSION "yld-20051230"
#define YEALINK_POLLING_FREQUENCY 10 /* in [Hz] */
struct yld_status {
u8 lcd[24];
u8 led;
u8 dialtone;
u8 ringtone;
u8 keynum;
} __attribute__ ((packed));
/*
* Register the LCD segment and icon map
*/
#define _LOC(k,l) { .a = (k), .m = (l) }
#define _SEG(t, a, am, b, bm, c, cm, d, dm, e, em, f, fm, g, gm) \
{ .type = (t), \
.u = { .s = { _LOC(a, am), _LOC(b, bm), _LOC(c, cm), \
_LOC(d, dm), _LOC(e, em), _LOC(g, gm), \
_LOC(f, fm) } } }
#define _PIC(t, h, hm, n) \
{ .type = (t), \
.u = { .p = { .name = (n), .a = (h), .m = (hm) } } }
static const struct lcd_segment_map {
char type;
union {
struct pictogram_map {
u8 a,m;
char name[10];
} p;
struct segment_map {
u8 a,m;
} s[7];
} u;
} lcdMap[] = {
#include "yealink.h"
};
struct yealink_dev {
struct input_dev *idev; /* input device */
struct usb_device *udev; /* usb device */
struct usb_interface *intf; /* usb interface */
/* irq input channel */
struct yld_ctl_packet *irq_data;
dma_addr_t irq_dma;
struct urb *urb_irq;
/* control output channel */
struct yld_ctl_packet *ctl_data;
dma_addr_t ctl_dma;
struct usb_ctrlrequest *ctl_req;
struct urb *urb_ctl;
char phys[64]; /* physical device path */
u8 lcdMap[ARRAY_SIZE(lcdMap)]; /* state of LCD, LED ... */
int key_code; /* last reported key */
unsigned int shutdown:1;
int stat_ix;
union {
struct yld_status s;
u8 b[sizeof(struct yld_status)];
} master, copy;
};
/*******************************************************************************
* Yealink lcd interface
******************************************************************************/
/*
* Register a default 7 segment character set
*/
static SEG7_DEFAULT_MAP(map_seg7);
/* Display a char,
* char '\9' and '\n' are placeholders and do not overwrite the original text.
* A space will always hide an icon.
*/
static int setChar(struct yealink_dev *yld, int el, int chr)
{
int i, a, m, val;
if (el >= ARRAY_SIZE(lcdMap))
return -EINVAL;
if (chr == '\t' || chr == '\n')
return 0;
yld->lcdMap[el] = chr;
if (lcdMap[el].type == '.') {
a = lcdMap[el].u.p.a;
m = lcdMap[el].u.p.m;
if (chr != ' ')
yld->master.b[a] |= m;
else
yld->master.b[a] &= ~m;
return 0;
}
val = map_to_seg7(&map_seg7, chr);
for (i = 0; i < ARRAY_SIZE(lcdMap[0].u.s); i++) {
m = lcdMap[el].u.s[i].m;
if (m == 0)
continue;
a = lcdMap[el].u.s[i].a;
if (val & 1)
yld->master.b[a] |= m;
else
yld->master.b[a] &= ~m;
val = val >> 1;
}
return 0;
};
/*******************************************************************************
* Yealink key interface
******************************************************************************/
/* Map device buttons to internal key events.
*
* USB-P1K button layout:
*
* up
* IN OUT
* down
*
* pickup C hangup
* 1 2 3
* 4 5 6
* 7 8 9