root/drivers/input/misc/yealink.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * drivers/usb/input/yealink.c
 *
 * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com>
 */
/*
 * 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/mutex.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     */

        struct mutex sysfs_mutex;

        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
 *       *      0      #
 *
 * The "up" and "down" keys, are symbolised by arrows on the button.
 * The "pickup" and "hangup" keys are symbolised by a green and red phone
 * on the button.
 */
static int map_p1k_to_key(int scancode)
{
        switch(scancode) {              /* phone key:   */
        case 0x23: return KEY_LEFT;     /*   IN         */
        case 0x33: return KEY_UP;       /*   up         */
        case 0x04: return KEY_RIGHT;    /*   OUT        */
        case 0x24: return KEY_DOWN;     /*   down       */
        case 0x03: return KEY_ENTER;    /*   pickup     */
        case 0x14: return KEY_BACKSPACE; /*  C          */
        case 0x13: return KEY_ESC;      /*   hangup     */
        case 0x00: return KEY_1;        /*   1          */
        case 0x01: return KEY_2;        /*   2          */
        case 0x02: return KEY_3;        /*   3          */
        case 0x10: return KEY_4;        /*   4          */
        case 0x11: return KEY_5;        /*   5          */
        case 0x12: return KEY_6;        /*   6          */
        case 0x20: return KEY_7;        /*   7          */
        case 0x21: return KEY_8;        /*   8          */
        case 0x22: return KEY_9;        /*   9          */
        case 0x30: return KEY_KPASTERISK; /* *          */
        case 0x31: return KEY_0;        /*   0          */
        case 0x32: return KEY_LEFTSHIFT |
                          KEY_3 << 8;   /*   #          */
        }
        return -EINVAL;
}

/* Completes a request by converting the data into events for the
 * input subsystem.
 *
 * The key parameter can be cascaded: key2 << 8 | key1
 */
static void report_key(struct yealink_dev *yld, int key)
{
        struct input_dev *idev = yld->idev;

        if (yld->key_code >= 0) {
                /* old key up */
                input_report_key(idev, yld->key_code & 0xff, 0);
                if (yld->key_code >> 8)
                        input_report_key(idev, yld->key_code >> 8, 0);
        }

        yld->key_code = key;
        if (key >= 0) {
                /* new valid key */
                input_report_key(idev, key & 0xff, 1);
                if (key >> 8)
                        input_report_key(idev, key >> 8, 1);
        }
        input_sync(idev);
}

/*******************************************************************************
 * Yealink usb communication interface
 ******************************************************************************/

static int yealink_cmd(struct yealink_dev *yld, struct yld_ctl_packet *p)
{
        u8      *buf = (u8 *)p;
        int     i;
        u8      sum = 0;

        for(i=0; i<USB_PKT_LEN-1; i++)
                sum -= buf[i];
        p->sum = sum;
        return usb_control_msg(yld->udev,
                        usb_sndctrlpipe(yld->udev, 0),
                        USB_REQ_SET_CONFIGURATION,
                        USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
                        0x200, 3,
                        p, sizeof(*p),
                        USB_CTRL_SET_TIMEOUT);
}

static u8 default_ringtone[] = {
        0xEF,                   /* volume [0-255] */
        0xFB, 0x1E, 0x00, 0x0C, /* 1250 [hz], 12/100 [s] */
        0xFC, 0x18, 0x00, 0x0C, /* 1000 [hz], 12/100 [s] */
        0xFB, 0x1E, 0x00, 0x0C,
        0xFC, 0x18, 0x00, 0x0C,
        0xFB, 0x1E, 0x00, 0x0C,
        0xFC, 0x18, 0x00, 0x0C,
        0xFB, 0x1E, 0x00, 0x0C,
        0xFC, 0x18, 0x00, 0x0C,
        0xFF, 0xFF, 0x01, 0x90, /* silent, 400/100 [s] */
        0x00, 0x00              /* end of sequence */
};

static int yealink_set_ringtone(struct yealink_dev *yld, u8 *buf, size_t size)
{
        struct yld_ctl_packet *p = yld->ctl_data;
        int     ix, len;

        if (size <= 0)
                return -EINVAL;

        /* Set the ringtone volume */
        memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
        yld->ctl_data->cmd      = CMD_RING_VOLUME;
        yld->ctl_data->size     = 1;
        yld->ctl_data->data[0]  = buf[0];
        yealink_cmd(yld, p);

        buf++;
        size--;

        p->cmd = CMD_RING_NOTE;
        ix = 0;
        while (size != ix) {
                len = size - ix;
                if (len > sizeof(p->data))
                        len = sizeof(p->data);
                p->size   = len;
                p->offset = cpu_to_be16(ix);
                memcpy(p->data, &buf[ix], len);
                yealink_cmd(yld, p);
                ix += len;
        }
        return 0;
}

/* keep stat_master & stat_copy in sync.
 */
static int yealink_do_idle_tasks(struct yealink_dev *yld)
{
        u8 val;
        int i, ix, len;

        ix = yld->stat_ix;

        memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
        yld->ctl_data->cmd  = CMD_KEYPRESS;
        yld->ctl_data->size = 1;
        yld->ctl_data->sum  = 0xff - CMD_KEYPRESS;

        /* If state update pointer wraps do a KEYPRESS first. */
        if (ix >= sizeof(yld->master)) {
                yld->stat_ix = 0;
                return 0;
        }

        /* find update candidates: copy != master */
        do {
                val = yld->master.b[ix];
                if (val != yld->copy.b[ix])
                        goto send_update;
        } while (++ix < sizeof(yld->master));

        /* nothing todo, wait a bit and poll for a KEYPRESS */
        yld->stat_ix = 0;
        /* TODO how can we wait abit. ??
         * msleep_interruptible(1000 / YEALINK_POLLING_FREQUENCY);
         */
        return 0;

send_update:

        /* Setup an appropriate update request */
        yld->copy.b[ix] = val;
        yld->ctl_data->data[0] = val;

        switch(ix) {
        case offsetof(struct yld_status, led):
                yld->ctl_data->cmd      = CMD_LED;
                yld->ctl_data->sum      = -1 - CMD_LED - val;
                break;
        case offsetof(struct yld_status, dialtone):
                yld->ctl_data->cmd      = CMD_DIALTONE;
                yld->ctl_data->sum      = -1 - CMD_DIALTONE - val;
                break;
        case offsetof(struct yld_status, ringtone):
                yld->ctl_data->cmd      = CMD_RINGTONE;
                yld->ctl_data->sum      = -1 - CMD_RINGTONE - val;
                break;
        case offsetof(struct yld_status, keynum):
                val--;
                val &= 0x1f;
                yld->ctl_data->cmd      = CMD_SCANCODE;
                yld->ctl_data->offset   = cpu_to_be16(val);
                yld->ctl_data->data[0]  = 0;
                yld->ctl_data->sum      = -1 - CMD_SCANCODE - val;
                break;
        default:
                len = sizeof(yld->master.s.lcd) - ix;
                if (len > sizeof(yld->ctl_data->data))
                        len = sizeof(yld->ctl_data->data);

                /* Combine up to <len> consecutive LCD bytes in a single request
                 */
                yld->ctl_data->cmd      = CMD_LCD;
                yld->ctl_data->offset   = cpu_to_be16(ix);
                yld->ctl_data->size     = len;
                yld->ctl_data->sum      = -CMD_LCD - ix - val - len;
                for(i=1; i<len; i++) {
                        ix++;
                        val = yld->master.b[ix];
                        yld->copy.b[ix]         = val;
                        yld->ctl_data->data[i]  = val;
                        yld->ctl_data->sum     -= val;
                }
        }
        yld->stat_ix = ix + 1;
        return 1;
}

/* Decide on how to handle responses
 *
 * The state transition diagram is somethhing like:
 *
 *          syncState<--+
 *               |      |
 *               |    idle
 *              \|/     |
 * init --ok--> waitForKey --ok--> getKey
 *  ^               ^                |
 *  |               +-------ok-------+
 * error,start
 *
 */
static void urb_irq_callback(struct urb *urb)
{
        struct yealink_dev *yld = urb->context;
        int ret, status = urb->status;

        if (status)
                dev_err(&yld->intf->dev, "%s - urb status %d\n",
                        __func__, status);

        switch (yld->irq_data->cmd) {
        case CMD_KEYPRESS:

                yld->master.s.keynum = yld->irq_data->data[0];
                break;

        case CMD_SCANCODE:
                dev_dbg(&yld->intf->dev, "get scancode %x\n",
                        yld->irq_data->data[0]);

                report_key(yld, map_p1k_to_key(yld->irq_data->data[0]));
                break;

        default:
                dev_err(&yld->intf->dev, "unexpected response %x\n",
                        yld->irq_data->cmd);
        }

        yealink_do_idle_tasks(yld);

        if (!yld->shutdown) {
                ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC);
                if (ret && ret != -EPERM)
                        dev_err(&yld->intf->dev,
                                "%s - usb_submit_urb failed %d\n",
                                __func__, ret);
        }
}

static void urb_ctl_callback(struct urb *urb)
{
        struct yealink_dev *yld = urb->context;
        int ret = 0, status = urb->status;

        if (status)
                dev_err(&yld->intf->dev, "%s - urb status %d\n",
                        __func__, status);

        switch (yld->ctl_data->cmd) {
        case CMD_KEYPRESS:
        case CMD_SCANCODE:
                /* ask for a response */
                if (!yld->shutdown)
                        ret = usb_submit_urb(yld->urb_irq, GFP_ATOMIC);
                break;
        default:
                /* send new command */
                yealink_do_idle_tasks(yld);
                if (!yld->shutdown)
                        ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC);
                break;
        }

        if (ret && ret != -EPERM)
                dev_err(&yld->intf->dev, "%s - usb_submit_urb failed %d\n",
                        __func__, ret);
}

/*******************************************************************************
 * input event interface
 ******************************************************************************/

/* TODO should we issue a ringtone on a SND_BELL event?
static int input_ev(struct input_dev *dev, unsigned int type,
                unsigned int code, int value)
{

        if (type != EV_SND)
                return -EINVAL;

        switch (code) {
        case SND_BELL:
        case SND_TONE:
                break;
        default:
                return -EINVAL;
        }

        return 0;
}
*/

static int input_open(struct input_dev *dev)
{
        struct yealink_dev *yld = input_get_drvdata(dev);
        int i, ret;

        dev_dbg(&yld->intf->dev, "%s\n", __func__);

        /* force updates to device */
        for (i = 0; i<sizeof(yld->master); i++)
                yld->copy.b[i] = ~yld->master.b[i];
        yld->key_code = -1;     /* no keys pressed */

        yealink_set_ringtone(yld, default_ringtone, sizeof(default_ringtone));

        /* issue INIT */
        memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data)));
        yld->ctl_data->cmd      = CMD_INIT;
        yld->ctl_data->size     = 10;
        yld->ctl_data->sum      = 0x100-CMD_INIT-10;
        if ((ret = usb_submit_urb(yld->urb_ctl, GFP_KERNEL)) != 0) {
                dev_dbg(&yld->intf->dev,
                        "%s - usb_submit_urb failed with result %d\n",
                        __func__, ret);
                return ret;
        }
        return 0;
}

static void input_close(struct input_dev *dev)
{
        struct yealink_dev *yld = input_get_drvdata(dev);

        yld->shutdown = 1;
        /*
         * Make sure the flag is seen by other CPUs before we start
         * killing URBs so new URBs won't be submitted
         */
        smp_wmb();

        usb_kill_urb(yld->urb_ctl);
        usb_kill_urb(yld->urb_irq);

        yld->shutdown = 0;
        smp_wmb();
}

/*******************************************************************************
 * sysfs interface
 ******************************************************************************/

/* Interface to the 7-segments translation table aka. char set.
 */
static ssize_t show_map(struct device *dev, struct device_attribute *attr,
                                char *buf)
{
        memcpy(buf, &map_seg7, sizeof(map_seg7));
        return sizeof(map_seg7);
}

static ssize_t store_map(struct device *dev, struct device_attribute *attr,
                                const char *buf, size_t cnt)
{
        if (cnt != sizeof(map_seg7))
                return -EINVAL;
        memcpy(&map_seg7, buf, sizeof(map_seg7));
        return sizeof(map_seg7);
}

/* Interface to the LCD.
 */

/* Reading /sys/../lineX will return the format string with its settings:
 *
 * Example:
 * cat ./line3
 * 888888888888
 * Linux Rocks!
 */
static ssize_t show_line(struct device *dev, char *buf, int a, int b)
{
        struct yealink_dev *yld = dev_get_drvdata(dev);
        int i;

        guard(mutex)(&yld->sysfs_mutex);

        for (i = a; i < b; i++)
                *buf++ = lcdMap[i].type;
        *buf++ = '\n';
        for (i = a; i < b; i++)
                *buf++ = yld->lcdMap[i];
        *buf++ = '\n';
        *buf = 0;

        return 3 + ((b - a) << 1);
}

static ssize_t show_line1(struct device *dev, struct device_attribute *attr,
                        char *buf)
{
        return show_line(dev, buf, LCD_LINE1_OFFSET, LCD_LINE2_OFFSET);
}

static ssize_t show_line2(struct device *dev, struct device_attribute *attr,
                        char *buf)
{
        return show_line(dev, buf, LCD_LINE2_OFFSET, LCD_LINE3_OFFSET);
}

static ssize_t show_line3(struct device *dev, struct device_attribute *attr,
                        char *buf)
{
        return show_line(dev, buf, LCD_LINE3_OFFSET, LCD_LINE4_OFFSET);
}

/* Writing to /sys/../lineX will set the corresponding LCD line.
 * - Excess characters are ignored.
 * - If less characters are written than allowed, the remaining digits are
 *   unchanged.
 * - The '\n' or '\t' char is a placeholder, it does not overwrite the
 *   original content.
 */
static ssize_t store_line(struct device *dev, const char *buf, size_t count,
                int el, size_t len)
{
        struct yealink_dev *yld = dev_get_drvdata(dev);
        int i;

        guard(mutex)(&yld->sysfs_mutex);

        if (len > count)
                len = count;
        for (i = 0; i < len; i++)
                setChar(yld, el++, buf[i]);

        return count;
}

static ssize_t store_line1(struct device *dev, struct device_attribute *attr,
                                const char *buf, size_t count)
{
        return store_line(dev, buf, count, LCD_LINE1_OFFSET, LCD_LINE1_SIZE);
}

static ssize_t store_line2(struct device *dev, struct device_attribute *attr,
                                const char *buf, size_t count)
{
        return store_line(dev, buf, count, LCD_LINE2_OFFSET, LCD_LINE2_SIZE);
}

static ssize_t store_line3(struct device *dev, struct device_attribute *attr,
                                const char *buf, size_t count)
{
        return store_line(dev, buf, count, LCD_LINE3_OFFSET, LCD_LINE3_SIZE);
}

/* Interface to visible and audible "icons", these include:
 * pictures on the LCD, the LED, and the dialtone signal.
 */

/* Get a list of "switchable elements" with their current state. */
static ssize_t get_icons(struct device *dev, struct device_attribute *attr,
                        char *buf)
{
        struct yealink_dev *yld = dev_get_drvdata(dev);
        int i, ret = 1;

        guard(mutex)(&yld->sysfs_mutex);

        for (i = 0; i < ARRAY_SIZE(lcdMap); i++) {
                if (lcdMap[i].type != '.')
                        continue;
                ret += sprintf(&buf[ret], "%s %s\n",
                                yld->lcdMap[i] == ' ' ? "  " : "on",
                                lcdMap[i].u.p.name);
        }

        return ret;
}

/* Change the visibility of a particular element. */
static ssize_t set_icon(struct device *dev, const char *buf, size_t count,
                        int chr)
{
        struct yealink_dev *yld = dev_get_drvdata(dev);
        int i;

        guard(mutex)(&yld->sysfs_mutex);

        for (i = 0; i < ARRAY_SIZE(lcdMap); i++) {
                if (lcdMap[i].type != '.')
                        continue;
                if (strncmp(buf, lcdMap[i].u.p.name, count) == 0) {
                        setChar(yld, i, chr);
                        break;
                }
        }

        return count;
}

static ssize_t show_icon(struct device *dev, struct device_attribute *attr,
                const char *buf, size_t count)
{
        return set_icon(dev, buf, count, buf[0]);
}

static ssize_t hide_icon(struct device *dev, struct device_attribute *attr,
                const char *buf, size_t count)
{
        return set_icon(dev, buf, count, ' ');
}

/* Upload a ringtone to the device.
 */

/* Stores raw ringtone data in the phone */
static ssize_t store_ringtone(struct device *dev, struct device_attribute *attr,
                              const char *buf, size_t count)
{
        struct yealink_dev *yld = dev_get_drvdata(dev);

        guard(mutex)(&yld->sysfs_mutex);

        /* TODO locking with async usb control interface??? */
        yealink_set_ringtone(yld, (char *)buf, count);

        return count;
}

#define _M444   S_IRUGO
#define _M664   S_IRUGO|S_IWUSR|S_IWGRP
#define _M220   S_IWUSR|S_IWGRP

static DEVICE_ATTR(map_seg7     , _M664, show_map       , store_map     );
static DEVICE_ATTR(line1        , _M664, show_line1     , store_line1   );
static DEVICE_ATTR(line2        , _M664, show_line2     , store_line2   );
static DEVICE_ATTR(line3        , _M664, show_line3     , store_line3   );
static DEVICE_ATTR(get_icons    , _M444, get_icons      , NULL          );
static DEVICE_ATTR(show_icon    , _M220, NULL           , show_icon     );
static DEVICE_ATTR(hide_icon    , _M220, NULL           , hide_icon     );
static DEVICE_ATTR(ringtone     , _M220, NULL           , store_ringtone);

static struct attribute *yld_attrs[] = {
        &dev_attr_line1.attr,
        &dev_attr_line2.attr,
        &dev_attr_line3.attr,
        &dev_attr_get_icons.attr,
        &dev_attr_show_icon.attr,
        &dev_attr_hide_icon.attr,
        &dev_attr_map_seg7.attr,
        &dev_attr_ringtone.attr,
        NULL
};
ATTRIBUTE_GROUPS(yld);

/*******************************************************************************
 * Linux interface and usb initialisation
 ******************************************************************************/

struct driver_info {
        char *name;
};

static const struct driver_info info_P1K = {
        .name   = "Yealink usb-p1k",
};

static const struct usb_device_id usb_table [] = {
        {
                .match_flags            = USB_DEVICE_ID_MATCH_DEVICE |
                                                USB_DEVICE_ID_MATCH_INT_INFO,
                .idVendor               = 0x6993,
                .idProduct              = 0xb001,
                .bInterfaceClass        = USB_CLASS_HID,
                .bInterfaceSubClass     = 0,
                .bInterfaceProtocol     = 0,
                .driver_info            = (kernel_ulong_t)&info_P1K
        },
        { }
};

static int usb_cleanup(struct yealink_dev *yld, int err)
{
        if (yld == NULL)
                return err;

        if (yld->idev) {
                if (err)
                        input_free_device(yld->idev);
                else
                        input_unregister_device(yld->idev);
        }

        usb_free_urb(yld->urb_irq);
        usb_free_urb(yld->urb_ctl);

        kfree(yld->ctl_req);
        usb_free_coherent(yld->udev, USB_PKT_LEN, yld->ctl_data, yld->ctl_dma);
        usb_free_coherent(yld->udev, USB_PKT_LEN, yld->irq_data, yld->irq_dma);

        kfree(yld);
        return err;
}

static void usb_disconnect(struct usb_interface *intf)
{
        struct yealink_dev *yld = usb_get_intfdata(intf);

        usb_cleanup(yld, 0);
        usb_set_intfdata(intf, NULL);
}

static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
        struct usb_device *udev = interface_to_usbdev (intf);
        struct driver_info *nfo = (struct driver_info *)id->driver_info;
        struct usb_host_interface *interface;
        struct usb_endpoint_descriptor *endpoint;
        struct yealink_dev *yld;
        struct input_dev *input_dev;
        int ret, pipe, i;

        interface = intf->cur_altsetting;

        if (interface->desc.bNumEndpoints < 1)
                return -ENODEV;

        endpoint = &interface->endpoint[0].desc;
        if (!usb_endpoint_is_int_in(endpoint))
                return -ENODEV;

        yld = kzalloc_obj(*yld);
        if (!yld)
                return -ENOMEM;

        yld->udev = udev;
        yld->intf = intf;
        mutex_init(&yld->sysfs_mutex);

        yld->idev = input_dev = input_allocate_device();
        if (!input_dev)
                return usb_cleanup(yld, -ENOMEM);

        /* allocate usb buffers */
        yld->irq_data = usb_alloc_coherent(udev, USB_PKT_LEN,
                                           GFP_KERNEL, &yld->irq_dma);
        if (yld->irq_data == NULL)
                return usb_cleanup(yld, -ENOMEM);

        yld->ctl_data = usb_alloc_coherent(udev, USB_PKT_LEN,
                                           GFP_KERNEL, &yld->ctl_dma);
        if (!yld->ctl_data)
                return usb_cleanup(yld, -ENOMEM);

        yld->ctl_req = kmalloc_obj(*(yld->ctl_req));
        if (yld->ctl_req == NULL)
                return usb_cleanup(yld, -ENOMEM);

        /* allocate urb structures */
        yld->urb_irq = usb_alloc_urb(0, GFP_KERNEL);
        if (yld->urb_irq == NULL)
                return usb_cleanup(yld, -ENOMEM);

        yld->urb_ctl = usb_alloc_urb(0, GFP_KERNEL);
        if (yld->urb_ctl == NULL)
                return usb_cleanup(yld, -ENOMEM);

        /* get a handle to the interrupt data pipe */
        pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
        ret = usb_maxpacket(udev, pipe);
        if (ret != USB_PKT_LEN)
                dev_err(&intf->dev, "invalid payload size %d, expected %zd\n",
                        ret, USB_PKT_LEN);

        /* initialise irq urb */
        usb_fill_int_urb(yld->urb_irq, udev, pipe, yld->irq_data,
                        USB_PKT_LEN,
                        urb_irq_callback,
                        yld, endpoint->bInterval);
        yld->urb_irq->transfer_dma = yld->irq_dma;
        yld->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
        yld->urb_irq->dev = udev;

        /* initialise ctl urb */
        yld->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE |
                                      USB_DIR_OUT;
        yld->ctl_req->bRequest  = USB_REQ_SET_CONFIGURATION;
        yld->ctl_req->wValue    = cpu_to_le16(0x200);
        yld->ctl_req->wIndex    = cpu_to_le16(interface->desc.bInterfaceNumber);
        yld->ctl_req->wLength   = cpu_to_le16(USB_PKT_LEN);

        usb_fill_control_urb(yld->urb_ctl, udev, usb_sndctrlpipe(udev, 0),
                        (void *)yld->ctl_req, yld->ctl_data, USB_PKT_LEN,
                        urb_ctl_callback, yld);
        yld->urb_ctl->transfer_dma      = yld->ctl_dma;
        yld->urb_ctl->transfer_flags    |= URB_NO_TRANSFER_DMA_MAP;
        yld->urb_ctl->dev = udev;

        /* find out the physical bus location */
        usb_make_path(udev, yld->phys, sizeof(yld->phys));
        strlcat(yld->phys,  "/input0", sizeof(yld->phys));

        /* register settings for the input device */
        input_dev->name = nfo->name;
        input_dev->phys = yld->phys;
        usb_to_input_id(udev, &input_dev->id);
        input_dev->dev.parent = &intf->dev;

        input_set_drvdata(input_dev, yld);

        input_dev->open = input_open;
        input_dev->close = input_close;
        /* input_dev->event = input_ev; TODO */

        /* register available key events */
        input_dev->evbit[0] = BIT_MASK(EV_KEY);
        for (i = 0; i < 256; i++) {
                int k = map_p1k_to_key(i);
                if (k >= 0) {
                        set_bit(k & 0xff, input_dev->keybit);
                        if (k >> 8)
                                set_bit(k >> 8, input_dev->keybit);
                }
        }

        ret = input_register_device(yld->idev);
        if (ret)
                return usb_cleanup(yld, ret);

        usb_set_intfdata(intf, yld);

        /* clear visible elements */
        for (i = 0; i < ARRAY_SIZE(lcdMap); i++)
                setChar(yld, i, ' ');

        /* display driver version on LCD line 3 */
        store_line3(&intf->dev, NULL,
                        DRIVER_VERSION, sizeof(DRIVER_VERSION));

        return 0;
}

static struct usb_driver yealink_driver = {
        .name           = "yealink",
        .probe          = usb_probe,
        .disconnect     = usb_disconnect,
        .id_table       = usb_table,
        .dev_groups     = yld_groups,
};

module_usb_driver(yealink_driver);

MODULE_DEVICE_TABLE (usb, usb_table);

MODULE_AUTHOR("Henk Vergonet");
MODULE_DESCRIPTION("Yealink phone driver");
MODULE_LICENSE("GPL");