Re: [PATCH v2 1/3] ECI: input: introduce ECI accessory input driver

From: Dmitry Torokhov
Date: Sun Jan 02 2011 - 03:47:18 EST


Hi Tapio,

On Thu, Dec 30, 2010 at 03:36:57PM +0200, tapio.vihuri@xxxxxxxxx wrote:
> From: Tapio Vihuri <tapio.vihuri@xxxxxxxxx>
>
> ECI stands for (Enhancement Control Interface).
>
> ECI is better known as Multimedia Headset for Nokia phones.
> If headset has many buttons, like play, vol+, vol- etc. then it is propably
> ECI accessory.
> Among several buttons ECI accessory contains memory for storing several
> parameters.
>
> ECI input driver provides the following features:
> - reading ECI configuration memory
> - ECI buttons as input events
>
> Signed-off-by: Tapio Vihuri <tapio.vihuri@xxxxxxxxx>
> ---
> drivers/input/misc/Kconfig | 18 +
> drivers/input/misc/Makefile | 2 +-
> drivers/input/misc/eci.c | 1002 +++++++++++++++++++++++++++++++++++++++++++
> include/linux/input/eci.h | 157 +++++++
> 4 files changed, 1178 insertions(+), 1 deletions(-)
> create mode 100644 drivers/input/misc/eci.c
> create mode 100644 include/linux/input/eci.h
>
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index b99b8cb..7a15bc6 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -448,4 +448,22 @@ config INPUT_ADXL34X_SPI
> To compile this driver as a module, choose M here: the
> module will be called adxl34x-spi.
>
> +config INPUT_ECI
> + tristate "AV ECI (Enhancement Control Interface) input driver"
> + help
> + The Enhancement Control Interface functionality
> + ECI is better known as Multimedia Headset for Nokia phones.
> + If headset has many buttons, like play, vol+, vol- etc. then
> + it is propably ECI accessory.
> + Among several buttons ECI accessory contains memory for storing
> + several parameters.
> +
> + ECI input driver provides the following features:
> + - reading ECI configuration memory
> + - ECI buttons as input events
> +
> + Say 'y' here to statically link this module into the kernel or 'm'
> + to build it as a dynamically loadable module. The module will be
> + called eci.ko
> +
> endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 1fe1f6c..99d2289 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -42,4 +42,4 @@ obj-$(CONFIG_INPUT_WINBOND_CIR) += winbond-cir.o
> obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
> obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
> -
> +obj-$(CONFIG_INPUT_ECI) += eci.o

Please keep Makefile and Kconfig sorted alphabetically.

> diff --git a/drivers/input/misc/eci.c b/drivers/input/misc/eci.c
> new file mode 100644
> index 0000000..72efccf
> --- /dev/null
> +++ b/drivers/input/misc/eci.c
> @@ -0,0 +1,1002 @@
> +/*
> + * This file is part of ECI (Enhancement Control Interface) accessory input
> + * driver
> + *
> + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
> + *
> + * Contact: Tapio Vihuri <tapio.vihuri@xxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * 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., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +/*
> + * ECI stands for (Enhancement Control Interface).
> + *
> + * ECI is better known as Multimedia Headset for Nokia phones.
> + * If headset has many buttons, like play, vol+, vol- etc. then it is propably
> + * ECI accessory.
> + * Among several buttons ECI accessory contains memory for storing several
> + * parameters.
> + *
> + * ECI input driver provides the following features:
> + * - reading ECI configuration memory
> + * - ECI buttons as input events
> + */
> +
> +#include <linux/init.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/debugfs.h>
> +
> +#include <linux/input.h>
> +#include <linux/input/eci.h>
> +#include <linux/miscdevice.h>
> +
> +#define ECI_DRIVERNAME "ECI_accessory"
> +
> +#define ECI_WAIT_SEND_BUTTON 5 /* ms */
> +#define ECI_WAIT_BUS_SETTLE 40 /* ms */
> +#define ECI_TRY_GET_MEMORY 2000 /* ms */
> +#define ECI_TRY_INIT_IO 200 /* ms */
> +#define ECI_TRY_SET_MIC 200 /* ms */
> +#define ECI_KEY_REPEAT_INTERVAL 400 /* ms */
> +
> +#define ECI_EKEY_BLOCK_ID 0xb3
> +#define ECI_ENHANCEMENT_FEATURE_BLOCK_ID 0x02
> +
> +/* ECI Inputs */
> +#define ECI_NIL_FEATURE 0x00 /* No feature */
> +#define ECI_IGNS 0x01 /* Ignition Sense */
> +#define ECI_CK_HANDSET_HOOK 0x02 /* Car-Kit Handset Hook */
> +#define ECI_POWER_SUPPLY 0x03 /* Power Supply/Car Battery Det */
> +#define ECI_EXT_AUD_IN 0x06 /* External audio In */
> +#define ECI_SEND_END_VR 0x07 /* Send, End, and Voice Recogn */
> +#define ECI_HD_PLUG 0x08 /* Headphone plug */
> +#define ECI_DEV_POWER_REQ 0x0a /* Device Power Request */
> +#define ECI_VOL_UP 0x0b /* Volume Up */
> +#define ECI_VOL_DOWN 0x0c /* Volume Down */
> +#define ECI_PLAY_PAUSE_CTRL 0x0d /* Play / Pause */
> +#define ECI_STOP 0x0e /* Stop */
> +#define ECI_NEXT_FF_AUTOSRC_UP 0x0f /* Next/Fast Fward/Autosearch up */
> +#define ECI_PREV_REW_AUTOSEARCH_DOWN 0x10 /* Prev/Rewind/Autosearch down */
> +#define ECI_POC 0x11 /* Push to Talk over Cellular */
> +#define ECI_SYNC_BTN 0x14 /* Synchronization Button */
> +#define ECI_MUSIC_RADIO_OFF_SELECTOR 0x15 /* Music/Radio/Off Selector */
> +#define ECI_REDIAL 0x16 /* Redial */
> +#define ECI_LEFT_SOFT_KEY 0x17 /* Left Soft Key */
> +#define ECI_RIGHT_SOFT_KEY 0x18 /* Right Soft key */
> +#define ECI_SEND_KEY 0x19 /* Send key */
> +#define ECI_END_KEY 0x1a /* End key */
> +#define ECI_MIDDLE_SOFT_KEY 0x1b /* Middle Soft key */
> +#define ECI_UP 0x1c /* UP key/joystick direction */
> +#define ECI_DOWN 0x1d /* DOWN key/joystick direction */
> +#define ECI_RIGHT 0x1e /* RIGHT key/joystick direction */
> +#define ECI_LEFT 0x1f /* LEFT key/joystick direction */
> +#define ECI_SYMBIAN_NAVY_KEY 0x20 /* Symbian Application key */
> +#define ECI_TERMINAL_APP_CTRL_IN 0x21 /* Terminal Applicat Ctrl Input */
> +#define ECI_USB_CLASS_SWITCHING 0x23 /* USB Class Switching */
> +#define ECI_MUTE 0x24 /* Mute */
> +/* ECI Outputs */
> +#define ECI_CRM 0x82 /* Car Radio Mute */
> +#define ECI_PWR 0x83 /* Power */
> +#define ECI_AUD_AMP 0x85 /* Audio Amplifier */
> +#define ECI_EXT_AUD_SWITCH 0x86 /* External Audio Switch */
> +#define ECI_HANDSET_AUDIO 0x87 /* Handset Audio */
> +#define ECI_RING_INDICATOR 0x88 /* Ringing Indicator */
> +#define ECI_CALL_ACTIVE 0x89 /* Call Active */
> +#define ECI_ENHANCEMENT_DETECTED 0x8b /* Enhancement Detected */
> +#define ECI_AUDIO_BLOCK_IN_USE 0x8e /* Audio Block In Use */
> +#define ECI_STEREO_AUDIO_ACTIVE 0x8f /* stereo audio used in terminal */
> +#define ECI_MONO_AUDIO_ACTIVE 0x90 /* mono audio used in terminal */
> +#define ECI_TERMINAL_APP_CTRL_OUT 0x91 /* Terminal Applicat Ctrl Output */
> +
> +/*
> + * Most of these are key events.
> + * Switch event codes are put on top of keys (KEY_MAX ->)
> + */
> +static int eci_codes[] = {
> + KEY_UNKNOWN, /* 0 ECI_NIL_FEATURE */
> + KEY_UNKNOWN, /* 1 ECI_IGNS */
> + KEY_UNKNOWN, /* 2 ECI_CK_HANDSET_HOOK */
> + KEY_BATTERY, /* 3 ECI_POWER_SUPPLY */
> + KEY_RESERVED, /* 4 ECI feature not defined */
> + KEY_RESERVED, /* 5 ECI feature not defined */
> + KEY_AUDIO, /* 6 ECI_EXT_AUD_IN */
> + KEY_PHONE, /* 7 ECI_SEND_END_VR */
> + KEY_MAX + SW_HEADPHONE_INSERT, /* 8 ECI_HD_PLUG, type switchs */
> + KEY_RESERVED, /* 9 ECI feature not defined */
> + KEY_UNKNOWN, /* 10 ECI_DEV_POWER_REQ */
> + KEY_VOLUMEUP, /* 11 ECI_VOL_UP */
> + KEY_VOLUMEDOWN, /* 12 ECI_VOL_DOWN */
> + KEY_PLAYPAUSE, /* 13 ECI_PLAY_PAUSE_CTRL */
> + KEY_STOP, /* 14 ECI_STOP */
> + KEY_FORWARD, /* 15 ECI_NEXT_FF_AUTOSRC_UP */
> + KEY_REWIND, /* 16 ECI_PREV_REW_AUTOSEARCH_DOWN */
> + KEY_UNKNOWN, /* 17 ECI_POC */
> + KEY_RESERVED, /* 18 ECI feature not defined */
> + KEY_RESERVED, /* 19 ECI feature not defined */
> + KEY_UNKNOWN, /* 20 ECI_SYNC_BTN */
> + KEY_RADIO, /* 21 ECI_MUSIC_RADIO_OFF_SELECTOR */
> + KEY_UNKNOWN, /* 22 ECI_REDIAL */
> + KEY_UNKNOWN, /* 23 ECI_LEFT_SOFT_KEY */
> + KEY_UNKNOWN, /* 24 ECI_RIGHT_SOFT_KEY */
> + KEY_SEND, /* 25 ECI_SEND_KEY */
> + KEY_END, /* 26 ECI_END_KEY */
> + KEY_UNKNOWN, /* 27 ECI_MIDDLE_SOFT_KEY */
> + KEY_UP, /* 28 ECI_UP */
> + KEY_DOWN, /* 29 ECI_DOWN */
> + KEY_RIGHT, /* 30 ECI_RIGHT */
> + KEY_LEFT, /* 31 ECI_LEFT */
> + KEY_UNKNOWN, /* 32 ECI_SYMBIAN_NAVY_KEY */
> + KEY_UNKNOWN, /* 33 ECI_TERMINAL_APP_CTRL_IN */
> + KEY_RESERVED, /* 34 ECI feature not defined */
> + KEY_UNKNOWN, /* 35 ECI_USB_CLASS_SWITCHING */
> + KEY_MUTE, /* 36 ECI_MUTE */
> +};

Consider switching to sparse keymap library. You won't be needing ugly
KEY_MAX + SW_XXX hacks and it will also support remapping keys from
userspace.

> +
> +/* ECI accessory register's bits */
> +#define ECI_MIC_AUTO 0x00
> +#define ECI_MIC_OFF 0x5a
> +#define ECI_MIC_ON 0xff
> +
> +#define ECI_INT_ENABLE 1
> +#define ECI_INT_DELAY_ENABLE (1<<1)
> +#define ECI_INT_LEN_76MS 0
> +#define ECI_INT_LEN_82MS (1<<5)
> +#define ECI_INT_LEN_37MS (2<<5)
> +#define ECI_INT_LEN_19MS (3<<5)
> +#define ECI_INT_LEN_10MS (4<<5)
> +#define ECI_INT_LEN_5MS (5<<5)
> +#define ECI_INT_LEN_2MS (6<<5)
> +#define ECI_INT_LEN_120US (7<<5)
> +
> +struct eci_mem_block {
> + u8 id;
> + u8 len;
> + u16 size;
> +};
> +
> +static struct eci_cb eci_callback;
> +static struct audio_hsmic_event hsmic_event;
> +
> +static struct eci_data *the_eci;
> +
> +#ifdef CONFIG_DEBUG_FS
> +static void eci_accessory_event(int event, void *priv);
> +static void eci_hsmic_event(void *priv, bool on);
> +
> +static struct dentry *eci_debugfs_dir;
> +
> +static ssize_t mic_read(struct file *file, char __user *user_buf,
> + size_t count, loff_t *ppos)
> +{
> + /* Assosiated in default_open() */
> + struct eci_data *eci = file->private_data;
> + char buf[80], *state;
> + int len = 0;
> + int ret;
> +
> + /* Do not run twice */
> + if (*ppos == 0) {
> + ret = eci->eci_hw_ops->acc_read_reg(ECICMD_MIC_CTRL, buf, 1);
> + if (ret)
> + return ret;
> +
> + eci->mic_state = buf[0];
> + switch (eci->mic_state) {
> + case ECI_MIC_AUTO:
> + state = "auto";
> + break;
> + case ECI_MIC_OFF:
> + state = "off";
> + break;
> + case ECI_MIC_ON:
> + state = "on";
> + break;
> + default:
> + state = "unknown";
> + break;
> + }
> +
> + len = snprintf(buf, sizeof(buf), "microphone %s\n", state);
> + }
> +
> + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
> +
> + return ret;
> +}
> +
> +static ssize_t mic_write(struct file *file, const char __user *user_buf,
> + size_t count, loff_t *ppos)
> +{
> + /* Assosiated in default_open() */
> + struct eci_data *eci = file->private_data;
> + char buf[80];
> + int buf_size;
> +
> + buf_size = min(count, (sizeof(buf) - 1));
> + if (copy_from_user(buf, user_buf, buf_size))
> + return -EFAULT;
> +
> + if (!memcmp(buf, "auto", 4))
> + eci_hsmic_event(eci, true);
> + else if (!memcmp(buf, "off", 3))
> + eci_hsmic_event(eci, false);
> + else if (!memcmp(buf, "on", 2)) {
> + eci->mic_state = ECI_MIC_ON;
> + if (eci->eci_hw_ops->acc_write_reg(ECICMD_MIC_CTRL,
> + eci->mic_state))
> + dev_err(eci->dev, "Unable to control headset"
> + "microphone\n");

Do not break strings on 80 column boundaries, wither align the arguments
differently or just go past 80 columns, like this:

dev_err(eci->dev,
"Unable to control headset microphone\n");

> + }
> +
> + return count;
> +}
> +
> +static int default_open(struct inode *inode, struct file *file)
> +{
> + /* Assosiated in debugfs_create_file() */
> + if (inode->i_private)
> + file->private_data = inode->i_private;
> +
> + return 0;
> +}
> +
> +static const struct file_operations mic_fops = {
> + .open = default_open,
> + .read = mic_read,
> + .write = mic_write,
> +};
> +
> +static void eci_uninitialize_debugfs(void)
> +{
> + if (eci_debugfs_dir)
> + debugfs_remove_recursive(eci_debugfs_dir);
> +}
> +
> +static long eci_initialize_debugfs(struct eci_data *eci)
> +{
> + void *ok;
> +
> + /* /sys/kernel/debug/ECI_accessory.# */
> + eci_debugfs_dir = debugfs_create_dir(dev_name(eci->dev), NULL);
> + if (!eci_debugfs_dir)
> + return -ENOENT;
> +
> + /* Struct eci assosiated to inode->i_private */
> + ok = debugfs_create_file("mic", S_IRUGO | S_IWUSR,
> + eci_debugfs_dir, eci, &mic_fops);
> + if (!ok)
> + goto fail;
> +
> + return 0;
> +fail:
> + eci_uninitialize_debugfs();
> + return -ENOENT;
> +}
> +#else
> +#define eci_initialize_debugfs(eci) 1

Should be 0 I think, and not have a parameter, preferably

static inline int eci_initialize_debugfs(void)
{
return 0;
}

> +#define eci_uninitialize_debugfs()
> +#endif
> +
> +/* Returns size of accessory memory or error */
> +static int eci_get_ekey(struct eci_data *eci, int *key)
> +{
> + u8 buf[4];
> + struct eci_mem_block *ekey = (void *)buf;
> + int ret;
> +
> + /* Read always four bytes */
> + ret = eci->eci_hw_ops->acc_read_direct(0, buf);
> +
> + if (ret)
> + return ret;
> +
> + if (ekey->id != ECI_EKEY_BLOCK_ID)
> + return -ENODEV;
> +
> + *key = cpu_to_be16(ekey->size);

cpu_to_be16()??? This looks really wierd.

> +
> + return 0;
> +}
> +
> +static ssize_t show_eci_memory(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + if (!the_eci->mem_ok)
> + return -ENXIO;
> +
> + memcpy(buf, the_eci->memory, the_eci->mem_size);
> +
> + return the_eci->mem_size;
> +}
> +
> +static ssize_t show_cable_plugged(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct eci_data *eci = dev_get_drvdata(dev);
> +
> + return snprintf(buf, sizeof(buf), "Cable plugged %s\n",
> + eci->plugged ? "in" : "out");

Should it be dsimple 0/1? How is this attribute supposed to be used?

> +}
> +
> +static ssize_t store_cable_plugged(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct eci_data *eci = dev_get_drvdata(dev);
> +
> + if (!memcmp(buf, "in", 2)) {
> + eci->plugged = true;
> + eci_accessory_event(ECI_EVENT_PLUG_IN, eci);
> + } else if (!memcmp(buf, "out", 3)) {
> + eci->plugged = false;
> + eci_accessory_event(ECI_EVENT_PLUG_OUT, eci);
> + }

Same here. Should it be in debugfs probably?

> +
> + return len;
> +}
> +
> +static DEVICE_ATTR(memory, S_IRUGO, show_eci_memory, NULL);
> +static DEVICE_ATTR(cable, S_IRUGO | S_IWUSR , show_cable_plugged,
> + store_cable_plugged);
> +
> +static struct attribute *eci_attributes[] = {
> + &dev_attr_memory.attr,
> + &dev_attr_cable.attr,
> + NULL
> +};
> +
> +static struct attribute_group eci_attr_group = {
> + .attrs = eci_attributes
> +};
> +
> +/* Read ECI device memory into buffer */
> +static int eci_get_memory(struct eci_data *eci, int *restart)
> +{
> + int i, ret;
> +
> + for (i = *restart; i < eci->mem_size; i += 4) {
> + ret = eci->eci_hw_ops->acc_read_direct(i, eci->memory + i);
> + *restart = i;
> + if (ret)
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +/*
> + * This should be really init_features, but most oftens these are just buttons
> + */
> +static int eci_init_buttons(struct eci_data *eci)
> +{
> + struct enchancement_features_fixed *eff = eci->e_features_fix;
> + u8 n, mireg;
> + int ret;
> + u8 buf[4];
> +
> + n = eff->number_of_features;
> +
> + if (n > ECI_MAX_FEATURE_COUNT)
> + return -EINVAL;
> +
> + ret = eci->eci_hw_ops->acc_write_reg(ECICMD_MIC_CTRL, ECI_MIC_OFF);
> + if (ret)
> + return ret;
> +
> + ret = eci->eci_hw_ops->acc_read_reg(ECICMD_MASTER_INT_REG, buf, 1);
> + if (ret)
> + return ret;
> +
> + mireg = buf[0];
> + mireg &= ~ECI_INT_ENABLE;
> + mireg |= ECI_INT_LEN_120US | ECI_INT_DELAY_ENABLE;
> +
> + ret = eci->eci_hw_ops->acc_write_reg(ECICMD_MASTER_INT_REG, mireg);
> + if (ret)
> + return ret;
> +
> + msleep(ECI_WAIT_BUS_SETTLE);
> + mireg |= ECI_INT_ENABLE;
> + ret = eci->eci_hw_ops->acc_write_reg(ECICMD_MASTER_INT_REG, mireg);
> + if (ret)
> + return ret;
> +
> + msleep(ECI_WAIT_BUS_SETTLE);
> +
> + return ret;
> +}
> +
> +/* Find "enchangement features" block from buffer */
> +static int eci_get_enchancement_features(struct eci_data *eci)
> +{
> + u8 *mem = (void *)eci->memory;
> + struct eci_mem_block *b = (void *)mem;
> + struct eci_mem_block *mem_end = (void *)(eci->memory + eci->mem_size);
> +
> + if (b->id != ECI_EKEY_BLOCK_ID)
> + return -ENODEV;
> +
> + do {
> + dev_dbg(eci->dev, "skip BLOCK 0x%02x, LEN 0x%02x\n",
> + b->id, b->len);
> + if (!b->len)
> + return -EINVAL;
> +
> + mem += b->len;
> + b = (void *)mem;
> + eci->e_features_fix = (void *)b;
> + dev_dbg(eci->dev, "found BLOCK 0x%02x, LEN 0x%02x\n",
> + b->id, b->len);
> + if (b->id == ECI_ENHANCEMENT_FEATURE_BLOCK_ID)
> + return 0;
> + } while (b < mem_end);
> +
> + return -ENFILE;
> +}
> +
> +/*
> + * Find out ECI features.
> + * All ECI memory block parsing are done here, be carefull as
> + * pointers to memory tend to go wrong easily.
> + * ECI "Enhancement Features block has variable size, so we try to
> + * catch pointers out of block due memory reading errors etc.
> + *
> + * I/O support field is not implemented.
> + * Data direction field is not implemented, nor writing to the ECI I/O
> + */
> +static int eci_parse_enchancement_features(struct eci_data *eci)
> +{
> + struct enchancement_features_fixed *eff = eci->e_features_fix;
> + struct enchancement_features_variable *efv = &eci->e_features_var;
> + int i;
> + u8 n, k;
> + void *mem_end = (void *)((u8 *)eff + eff->length);
> +
> + dev_dbg(eci->dev, "block id 0x%02x length 0x%02x connector "
> + "configuration 0x%02x\n", eff->block_id, eff->length,
> + eff->connector_conf);
> + n = eff->number_of_features;
> + dev_dbg(eci->dev, "number of features %d\n", n);
> +
> + if (n > ECI_MAX_FEATURE_COUNT)
> + return -EINVAL;
> +
> + k = DIV_ROUND_UP(n, 8);
> + dev_dbg(eci->dev, "I/O support bytes count %d\n", k);
> +
> + efv->io_support = &eff->number_of_features + 1;
> + /* efv->io_functionality[0] is not used! pins are in 1..31 range */
> + efv->io_functionality = efv->io_support + k - 1;
> + efv->active_state = efv->io_functionality + n + 1;
> +
> + if ((void *)&efv->active_state[k] > mem_end)
> + return -EINVAL;
> +
> + /* Last part of block */
> + for (i = 0; i < k; i++)
> + dev_dbg(eci->dev, "active_state[%d] 0x%02x\n", i,
> + efv->active_state[i]);
> +
> + eci->buttons_data.buttons_up_mask =
> + ~(u32)(cpu_to_le32(*(u32 *)efv->active_state));
> +
> + /*
> + * ECI accessory responces as many bytes needed for used I/O pins
> + * up to four bytes, when lines 24..31 are used
> + * all tested ECI accessories how ever return two data bytes
> + * event though there are less than eight I/O pins
> + *
> + * so we get alway reading error if there are less than eight I/Os
> + * meanwhile just use this kludge, FIXME
> + */
> + k = DIV_ROUND_UP(n + 1, 8);
> + if (k == 1)
> + k = 2;
> + eci->port_reg_count = k;
> +
> + return 0;
> +}
> +
> +static int eci_init_accessory(struct eci_data *eci)
> +{
> + int ret, key = 0, restart = 0;
> + unsigned long future;
> +
> + eci->mem_ok = false;
> +
> + if (!eci->eci_hw_ops)
> + return -ENXIO;
> +
> + ret = eci->eci_hw_ops->acc_reset();
> + if (ret)
> + return ret;
> +
> + msleep(ECI_WAIT_BUS_SETTLE);
> +
> + ret = eci->eci_hw_ops->acc_write_reg(ECICMD_MIC_CTRL, ECI_MIC_OFF);
> + if (ret)
> + return ret;
> +
> + /* Get ECI ekey block to determine memory size */
> + future = jiffies + msecs_to_jiffies(ECI_TRY_GET_MEMORY);
> + do {
> + ret = eci_get_ekey(eci, &key);
> + if (time_is_before_jiffies(future))
> + break;
> + } while (ret);
> +
> + if (ret)
> + return ret;
> +
> + eci->mem_size = key;
> + if (eci->mem_size > ECI_MAX_MEM_SIZE)
> + return -EINVAL;
> +
> + /* Get ECI memory */
> + future = jiffies + msecs_to_jiffies(ECI_TRY_GET_MEMORY);
> + do {
> + ret = eci_get_memory(eci, &restart);
> + if (time_is_before_jiffies(future))
> + break;
> + } while (ret);
> +
> + if (ret)
> + return ret;
> +
> + if (eci_get_enchancement_features(eci))
> + return -EIO;
> +
> + if (eci_parse_enchancement_features(eci))
> + return -EIO;
> +
> + /*
> + * Configure ECI buttons now as we have after parsed
> + * enchancement features table
> + */

I do not understand this comment.

> + msleep(ECI_WAIT_BUS_SETTLE);
> + future = jiffies + msecs_to_jiffies(ECI_TRY_INIT_IO);
> + do {
> + ret = eci_init_buttons(eci);
> + if (time_is_before_jiffies(future))
> + break;
> + } while (ret);
> +
> + if (ret)
> + return ret;
> +
> + eci->mem_ok = true;
> + msleep(ECI_WAIT_BUS_SETTLE);
> +
> + if (eci->eci_hw_ops->acc_write_reg(ECICMD_MIC_CTRL, eci->mic_state))
> + dev_err(eci->dev, "Unable to control headset microphone\n");
> +
> + return 0;
> +}
> +
> +static int init_accessory_input(struct eci_data *eci)
> +{
> + int err, i, code;
> +
> + eci->acc_input = input_allocate_device();
> + if (!eci->acc_input) {
> + dev_err(eci->dev, "Error allocating input device: %d\n",
> + __LINE__);
> + return -ENOMEM;
> + }
> +
> + eci->acc_input->name = "ECI Accessory";
> +
> + /* Codes on top of KEY_MAX are switch events */
> + for (i = 0; i < ARRAY_SIZE(eci_codes); i++) {
> + code = eci_codes[i];
> + if (code >= KEY_MAX) {
> + code -= KEY_MAX;
> + set_bit(code, eci->acc_input->swbit);
> + } else {
> + set_bit(code, eci->acc_input->keybit);
> + }
> + }
> +
> + set_bit(EV_KEY, eci->acc_input->evbit);
> + set_bit(EV_SW, eci->acc_input->evbit);
> + set_bit(EV_REP, eci->acc_input->evbit);

__set_bit(), no neded to lock bus.

> +
> + err = input_register_device(eci->acc_input);
> + if (err) {
> + dev_err(eci->dev, "Error registering input device: %d\n",
> + __LINE__);
> + goto err_free_dev;
> + }
> +
> + /* Must set after input_register_device() to take effect */
> + eci->acc_input->rep[REP_PERIOD] = ECI_KEY_REPEAT_INTERVAL;
> +
> + return 0;
> +
> +err_free_dev:
> + input_free_device(eci->acc_input);
> + return err;
> +}
> +
> +static void remove_accessory_input(struct eci_data *eci)
> +{
> + input_unregister_device(eci->acc_input);
> +}
> +
> +/* Press/release ccessory button(s) */
> +static int eci_get_button(struct eci_data *eci)
> +{
> + struct enchancement_features_fixed *eff = eci->e_features_fix;
> + struct eci_buttons_data *b = &eci->buttons_data;
> +
> + if (!eci->mem_ok)
> + return -ENXIO;
> +
> + if (((b->buttons & 0x0000ffff) == 0) && (eff->number_of_features > 2)) {
> + dev_err(eci->dev, "ECI report all buttons down, rejected %d\n",
> + __LINE__);
> + return -EINVAL;
> + }
> +
> + if (b->windex < ECI_BUTTON_BUF_SIZE) {
> + if (b->buttons_buf[b->windex] == 0)
> + b->buttons_buf[b->windex] = b->buttons;
> + else
> + dev_err(eci->dev, "ECI button queue owerflow %d\n",
> + __LINE__);
> + }
> + b->windex++;
> + if (b->windex == ECI_BUTTON_BUF_SIZE)
> + b->windex = 0;
> +
> + return 0;
> +}
> +
> +/* Intended to use ONLY inside eci_parse_button() ! */
> +#define ACTIVE_STATE(x) (u32)(cpu_to_le32(*(u32 *)efv->active_state) & BIT(x-1))
> +#define BUTTON_STATE(x) ((buttons & BIT(x))>>1)
> +
> +static int eci_parse_button(struct eci_data *eci, u32 buttons)
> +{
> + int pin, code, state;
> + u8 n, io_fun;
> + struct enchancement_features_variable *efv = &eci->e_features_var;
> + struct enchancement_features_fixed *eff = eci->e_features_fix;
> +
> + if (!eci->mem_ok)
> + return -ENXIO;
> +
> + n = eff->number_of_features;
> +
> + for (pin = 1; pin <= n; pin++) {
> + io_fun = efv->io_functionality[pin] & ~BIT(7);
> + if (io_fun > ECI_MUTE)
> + break;
> + code = eci_codes[io_fun];
> + state = (BUTTON_STATE(pin) == ACTIVE_STATE(pin));
> + if (state)
> + dev_dbg(eci->dev, "I/O functionality 0x%02x\n", io_fun);
> + if (code >= KEY_MAX)
> + input_report_switch(eci->acc_input, code - KEY_MAX,
> + state);
> + else
> + input_report_key(eci->acc_input, code, state);
> + }
> + input_sync(eci->acc_input);
> +
> + return 0;
> +}
> +
> +static int eci_send_button(struct eci_data *eci)
> +{
> + int i;
> + struct enchancement_features_fixed *eff = eci->e_features_fix;
> + struct eci_buttons_data *b = &eci->buttons_data;
> + u8 n;
> +
> + if (!eci->mem_ok)
> + return -ENXIO;
> +
> + n = eff->number_of_features;
> +
> + if (n > ECI_MAX_FEATURE_COUNT)
> + return -EINVAL;
> + /*
> + * Codes on top of KEY_MAX are switch events.
> + * Let input system take care multiple key events
> + */
> + for (i = 0; i < ECI_BUTTON_BUF_SIZE; i++) {
> + if (b->buttons_buf[b->rindex] == 0)
> + break;
> +
> + if (eci_parse_button(eci, b->buttons_buf[b->rindex]))
> + return -ENXIO;
> +
> + b->buttons_buf[b->rindex] = 0;
> + b->rindex++;
> + if (b->rindex == ECI_BUTTON_BUF_SIZE)
> + b->rindex = 0;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Other driver(s) can call this after registering themselves using
> + * eci_register()
> + */
> +static void eci_accessory_event(int event, void *priv)
> +{
> + struct eci_data *eci = priv;
> + struct eci_buttons_data *b = &eci->buttons_data;
> + int delay = 0;
> + int ret = 0;
> +
> + eci->event = event;
> + switch (event) {
> + case ECI_EVENT_IS_ECI:
> + eci->is_eci = true;
> + ret = eci->eci_hw_ops->acc_reset();
> + if (ret)
> + eci->is_eci = false;
> + break;
> + case ECI_EVENT_PLUG_IN:
> + eci->first_event = true;
> + ret = eci_init_accessory(eci);
> + if (ret < 0)
> + ret = eci_init_accessory(eci);
> + if (ret) {
> + dev_err(eci->dev, "Accessory init %s%s%s%sat: %d\n",
> + ret & ACI_COMMERR ? "COMMERR " : "",
> + ret & ACI_FRAERR ? "FRAERR " : "",
> + ret & ACI_RESERR ? "RESERR " : "",
> + ret & ACI_COLL ? "COLLERR " : "",
> + __LINE__);
> + break;
> + }
> + break;
> + case ECI_EVENT_PLUG_OUT:
> + eci->mem_ok = false;
> + break;
> + case ECI_EVENT_BUTTON:
> + /*
> + * First event might not be valid due plug insertion
> + * so we filter it out if it seems garbage
> + */
> + if (eci->first_event) {
> + eci->first_event = false;
> + if ((b->buttons & 0xff) == 0)
> + break;
> + }
> + eci_get_button(eci);
> + delay = msecs_to_jiffies(ECI_WAIT_SEND_BUTTON);
> + schedule_delayed_work(&eci->eci_ws, delay);
> + break;
> + default:
> + dev_err(eci->dev, "unknown event %d: %d\n", event, __LINE__);
> + break;
> + }
> +
> + return;
> +}
> +
> +static void eci_hsmic_event(void *priv, bool on)
> +{
> + struct eci_data *eci = priv;
> + unsigned long future;
> + int ret;
> +
> + if (!eci)
> + return;
> +
> + if (on)
> + eci->mic_state = ECI_MIC_AUTO;
> + else
> + eci->mic_state = ECI_MIC_OFF;
> +
> + future = jiffies + msecs_to_jiffies(ECI_TRY_SET_MIC);
> + do {
> + ret = eci->eci_hw_ops->acc_write_reg(ECICMD_MIC_CTRL,
> + eci->mic_state);
> + if (time_is_before_jiffies(future))
> + break;
> + } while (ret);
> +
> + if (ret)
> + dev_err(eci->dev, "Unable to control headset microphone\n");
> +}
> +
> +/* General work func (eci_ws) for several tasks */
> +static void eci_work(struct work_struct *ws)
> +{
> + struct eci_data *eci;
> + int ret;
> +
> + eci = container_of((struct delayed_work *)ws, struct eci_data,
> + eci_ws);
> +
> + ret = eci_send_button(eci);
> + if (ret)
> + dev_err(eci->dev, "Error sending event: %d\n", __LINE__);
> +}
> +
> +static struct miscdevice eci_device = {
> + .minor = MISC_DYNAMIC_MINOR,
> + .name = ECI_DRIVERNAME,
> +};

What does this device do?

> +
> +struct eci_cb *eci_register(struct eci_hw_ops *eci_ops)
> +{
> + if (!the_eci)
> + return ERR_PTR(-EBUSY);
> +
> + if (!eci_ops || !eci_ops->acc_read_direct ||
> + !eci_ops->acc_read_reg || !eci_ops->acc_write_reg ||
> + !eci_ops->acc_reset)
> + return ERR_PTR(-EINVAL);
> +
> + the_eci->eci_hw_ops = eci_ops;
> +
> + return &eci_callback;
> +}
> +EXPORT_SYMBOL(eci_register);
> +
> +static int __init eci_probe(struct platform_device *pdev)
> +{
> + struct eci_data *eci;
> + struct eci_platform_data *pdata = pdev->dev.platform_data;
> + int ret;
> +
> + eci = kzalloc(sizeof(*eci), GFP_KERNEL);
> + if (!eci)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, eci);
> + eci->dev = &pdev->dev;
> +
> + ret = misc_register(&eci_device);
> + if (ret) {
> + dev_err(eci->dev, "could not register misc_device: %d\n",
> + __LINE__);
> + goto err_misc;
> + }
> +
> + the_eci = eci;
> +
> + eci_callback.event = eci_accessory_event;
> + eci_callback.priv = eci;
> +
> + ret = sysfs_create_group(&pdev->dev.kobj, &eci_attr_group);
> + if (ret) {
> + dev_err(eci->dev, "could not create sysfs entries: %d\n",
> + __LINE__);
> + goto err_sysfs;
> + }
> +
> + ret = eci_initialize_debugfs(eci);
> + if (ret)
> + dev_err(eci->dev, "could not create debugfs entries: %d\n",
> + __LINE__);
> +
> + ret = init_accessory_input(eci);
> + if (ret) {
> + dev_err(eci->dev, "ERROR initializing accessory input: %d\n",
> + __LINE__);
> + goto err_input;
> + }
> +
> + /*
> + * If platform machine has audio driver providing
> + * register_hsmic_event_cb, we should give accessory microphone control,
> + * ie. eci_hsmic_event to it.
> + * This way audio driver get control to ECI accessory microphone and
> + * we can save power
> + */
> + if (pdata) {
> + if (pdata->register_hsmic_event_cb) {
> + hsmic_event.private = eci;
> + hsmic_event.event = eci_hsmic_event;
> + pdata->register_hsmic_event_cb(&hsmic_event);
> + }
> + }
> +
> + init_waitqueue_head(&eci->wait);
> + INIT_DELAYED_WORK(&eci->eci_ws, eci_work);
> +
> + eci->mem_ok = false;
> + /*
> + * By default ECI driver leaves microphone off, to save power.
> + * Audio driver can set microphone on by using
> + * hsmic_event.event
> + */
> + eci->mic_state = ECI_MIC_OFF;
> +
> + /* Init buttons_data indexes and buffer */
> + memset(&eci->buttons_data, 0, sizeof(struct eci_buttons_data));
> + eci->buttons_data.buttons = 0xffffffff;
> +
> + return 0;
> +
> +err_input:
> + eci_uninitialize_debugfs();
> + sysfs_remove_group(&pdev->dev.kobj, &eci_attr_group);
> +
> +err_sysfs:
> + misc_deregister(&eci_device);
> +
> +err_misc:
> + kfree(eci);
> +
> + return ret;
> +}
> +
> +static int __exit eci_remove(struct platform_device *pdev)
> +{
> + struct eci_data *eci = platform_get_drvdata(pdev);
> + struct eci_platform_data *pdata = pdev->dev.platform_data;
> +
> + pdata->register_hsmic_event_cb(NULL);
> + cancel_delayed_work_sync(&eci->eci_ws);
> + eci_uninitialize_debugfs();
> + sysfs_remove_group(&pdev->dev.kobj, &eci_attr_group);
> + remove_accessory_input(eci);
> + kfree(eci);
> + misc_deregister(&eci_device);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +
> +static int eci_suspend(struct platform_device *pdev, pm_message_t mesg)
> +{
> + return -ENOSYS;
> +}
> +#else
> +#define eci_suspend NULL
> +#endif
> +
> +static struct platform_driver eci_driver = {
> + .probe = eci_probe,
> + .remove = __exit_p(eci_remove),
> + .suspend = eci_suspend,
> + .driver = {
> + .name = ECI_DRIVERNAME,
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init eci_init(void)
> +{
> + return platform_driver_register(&eci_driver);
> +}
> +device_initcall(eci_init);
> +
> +static void __exit eci_exit(void)
> +{
> + platform_driver_unregister(&eci_driver);
> +}
> +module_exit(eci_exit);
> +
> +MODULE_ALIAS("platform:" ECI_DRIVERNAME);
> +MODULE_AUTHOR("Nokia Corporation");
> +MODULE_DESCRIPTION("ECI accessory driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/input/eci.h b/include/linux/input/eci.h
> new file mode 100644
> index 0000000..b8be99c
> --- /dev/null
> +++ b/include/linux/input/eci.h
> @@ -0,0 +1,157 @@
> +/*
> + * This file is part of ECI (Enhancement Control Interface) driver
> + *
> + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
> + *
> + * Contact: Tapio Vihuri <tapio.vihuri@xxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * 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., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +#ifndef __ECI_H__
> +#define __ECI_H__
> +
> +#define ECI_MAX_MEM_SIZE 0x7c
> +#define ECI_BUTTON_BUF_SIZE 32
> +#define ECI_MAX_FEATURE_COUNT 31
> +
> +#define ACI_COMMERR 0x010
> +#define ACI_FRAERR 0x020
> +#define ACI_RESERR 0x040
> +#define ACI_COLL 0x080
> +
> +#define ECI_REAL_BUTTONS 0
> +#define ECI_FORCE_BUTTONS_UP 1
> +
> +/* fixed in ECI HW, do not change */
> +enum {
> + ECICMD_HWID,
> + ECICMD_SWID,
> + ECICMD_ECI_BUS_SPEED,
> + ECICMD_MIC_CTRL,
> + ECICMD_MASTER_INT_REG,
> + ECICMD_HW_CONF_MEM_ACCESS,
> + ECICMD_EXTENDED_MEM_ACCESS,
> + ECICMD_INDIRECT_MEM_ACCESS,
> + ECICMD_PORT_DATA_0,
> + ECICMD_PORT_DATA_1,
> + ECICMD_PORT_DATA_2,
> + ECICMD_PORT_DATA_3,
> + ECICMD_LATCHED_PORT_DATA_0,
> + ECICMD_LATCHED_PORT_DATA_1,
> + ECICMD_LATCHED_PORT_DATA_2,
> + ECICMD_LATCHED_PORT_DATA_3,
> + ECICMD_DATA_DIR_0,
> + ECICMD_DATA_DIR_1,
> + ECICMD_DATA_DIR_2,
> + ECICMD_DATA_DIR_3,
> + ECICMD_INT_CONFIG_0_LOW,
> + ECICMD_INT_CONFIG_0_HIGH,
> + ECICMD_INT_CONFIG_1_LOW,
> + ECICMD_INT_CONFIG_1_HIGH,
> + ECICMD_INT_CONFIG_2_LOW,
> + ECICMD_INT_CONFIG_2_HIGH,
> + ECICMD_INT_CONFIG_3_LOW,
> + ECICMD_INT_CONFIG_3_HIGH,
> + /*
> + * 0x1c - 0x2f reserved for future
> + * 0x30 - 0x3d reserved
> + */
> + ECICMD_EEPROM_LOCK = 0x3e,
> + ECICMD_RESERVED, /* 0x3f */
> + ECIREG_STATUS, /* 0x40 */
> + ECIREG_READ_COUNT, /* 0x41 */
> + ECIREG_BUF_COUNT, /* 0x42 */
> + ECIREG_RST_LEARN, /* 0x43 */
> + /* 0x44 - 0xdf as data buffer */
> + ECIREG_READ_DIRECT, /* 0x44 */
> + ECIREG_HW_ID = 0xe0, /* 0xe0 */
> + ECIREG_FW_ID, /* 0xe1 */
> + ECIREG_TEST_IN, /* 0xe2 */
> + ECIREG_TEST_OUT, /* 0xe3 */
> +};
> +
> +enum {
> + ECI_EVENT_IS_ECI,
> + ECI_EVENT_PLUG_IN,
> + ECI_EVENT_PLUG_OUT,
> + ECI_EVENT_BUTTON,
> + ECI_EVENT_NO,
> +};
> +
> +struct eci_hw_ops {
> + int (*acc_reset)(void);
> + int (*acc_read_direct)(u8 addr, char *buf);
> + int (*acc_read_reg)(u8 reg, u8 *buf, int count);
> + int (*acc_write_reg)(u8 reg, u8 param);
> +};
> +
> +struct eci_cb {
> + void *priv;
> + void (*event)(int event, void *priv);
> +};
> +
> +struct audio_hsmic_event {
> + void *private;
> + void (*event)(void *priv, bool on);
> +};
> +
> +struct eci_platform_data {
> + void (*register_hsmic_event_cb)(struct audio_hsmic_event *);
> +};
> +
> +struct enchancement_features_fixed {
> + u8 block_id;
> + u8 length;
> + u8 connector_conf;
> + u8 number_of_features;
> +};
> +
> +struct enchancement_features_variable {
> + u8 *io_support;
> + u8 *io_functionality;
> + u8 *active_state;
> +};
> +
> +struct eci_buttons_data {
> + u32 buttons;
> + int windex;
> + int rindex;
> + u32 buttons_up_mask;
> + u32 buttons_buf[ECI_BUTTON_BUF_SIZE];
> +};
> +
> +struct eci_data {
> + struct device *dev;
> + struct delayed_work eci_ws;
> + wait_queue_head_t wait;
> + struct input_dev *acc_input;
> + int event;
> + bool first_event;
> + bool mem_ok;
> + u16 mem_size;
> + u8 memory[ECI_MAX_MEM_SIZE];
> + struct enchancement_features_fixed *e_features_fix;
> + struct enchancement_features_variable e_features_var;
> + u8 port_reg_count;
> + struct eci_buttons_data buttons_data;
> + struct eci_hw_ops *eci_hw_ops;
> + u8 mic_state;
> + bool plugged;
> + bool is_eci;
> +};
> +
> +struct eci_cb *eci_register(struct eci_hw_ops *eci_ops);
> +#endif
> --
> 1.6.5
>

Thank you.

--
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/