[alsa-devel] [PATCH v2 0/3] input: Add support for ECI (multimedia) accessories
From: Tapio Vihuri tapio.vihuri@nokia.com
Thank you for comments.
In series two there Kconfig dependency problem fixed, preventing compilation if X86_MRST (Intel Moorestown) or INPUT_MISC is not selected.
Also created against linux-2.6.37-rc8
--- v1 message ---
Hi all
This patch set introduce Multimedia Headset Accessory support for Nokia phones. Technically those are known as ECI (Enhancement Control Interface) If headset has many buttons, like play, vol+, vol- etc. then it is propably ECI accessory.
Among several buttons ECI accessories contains memory for storing several parameters.
This ECI input driver provides the following features: - reading ECI configuration memory - ECI buttons as input events
Drive is constructed as follows: - ECI accessory input driver deals with headset accessory - ECI bus control driver deals the HW transfering data to/from headset - platform data match used HW
In the future accessory detection logic will be added using ALSA jack reporting.
Created against linux-2.6.37-rc6
Please review.
Tapio Vihuri (3): ECI: input: introduce ECI accessory input driver ECI: introducing ECI bus driver ECI: adding platform data for ECI driver
arch/x86/platform/mrst/mrst.c | 59 +++ drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/ecibus/Kconfig | 35 ++ drivers/ecibus/Makefile | 10 + drivers/ecibus/ecibus.c | 583 ++++++++++++++++++++++++ drivers/input/misc/Kconfig | 18 + drivers/input/misc/Makefile | 2 +- drivers/input/misc/eci.c | 1002 +++++++++++++++++++++++++++++++++++++++++ include/linux/input/eci.h | 165 +++++++ 10 files changed, 1876 insertions(+), 1 deletions(-) create mode 100644 drivers/ecibus/Kconfig create mode 100644 drivers/ecibus/Makefile create mode 100644 drivers/ecibus/ecibus.c create mode 100644 drivers/input/misc/eci.c create mode 100644 include/linux/input/eci.h
From: Tapio Vihuri tapio.vihuri@nokia.com
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@nokia.com --- 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 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@nokia.com + * + * 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 */ +}; + +/* 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"); + } + + 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 +#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); + + 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"); +} + +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); + } + + 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 + */ + 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); + + 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, +}; + +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@nokia.com + * + * 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
From: Tapio Vihuri tapio.vihuri@nokia.com
ECI bus controller is kind of bridge between host CPU I2C and ECI accessory ECI communication.
Signed-off-by: Tapio Vihuri tapio.vihuri@nokia.com --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/ecibus/Kconfig | 35 +++ drivers/ecibus/Makefile | 10 + drivers/ecibus/ecibus.c | 583 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/input/eci.h | 8 + 6 files changed, 639 insertions(+), 0 deletions(-) create mode 100644 drivers/ecibus/Kconfig create mode 100644 drivers/ecibus/Makefile create mode 100644 drivers/ecibus/ecibus.c
diff --git a/drivers/Kconfig b/drivers/Kconfig index a2b902f..f450f98 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -111,4 +111,6 @@ source "drivers/xen/Kconfig" source "drivers/staging/Kconfig"
source "drivers/platform/Kconfig" + +source "drivers/ecibus/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index f3ebb30..11f5d57 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -113,5 +113,6 @@ obj-$(CONFIG_SSB) += ssb/ obj-$(CONFIG_VHOST_NET) += vhost/ obj-$(CONFIG_VLYNQ) += vlynq/ obj-$(CONFIG_STAGING) += staging/ +obj-$(CONFIG_ECI) += ecibus/ obj-y += platform/ obj-y += ieee802154/ diff --git a/drivers/ecibus/Kconfig b/drivers/ecibus/Kconfig new file mode 100644 index 0000000..f2fc8a4 --- /dev/null +++ b/drivers/ecibus/Kconfig @@ -0,0 +1,35 @@ +# +# ECI driver configuration +# +menuconfig ECI + bool "ECI support" + help + ECI (Enhancement Control Interface) accessory support + + 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. + + Enable ECI support in terminal so that ECI input driver is able + to communicate with ECI accessory + +if ECI + +config ECI_BUS + tristate "ECI bus controller driver" + select INPUT_ECI + depends on X86_MRST && INPUT_MISC + help + This selects a driver for the ECI bus controller + + ECI bus controller is kind of bridge between host CPU I2C and + ECI accessory ECI communication. + + 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 ecibus.ko + +endif # ECI diff --git a/drivers/ecibus/Makefile b/drivers/ecibus/Makefile new file mode 100644 index 0000000..ce4206b --- /dev/null +++ b/drivers/ecibus/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for kernel ECI drivers. +# + +ifeq ($(CONFIG_ECI_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + +# ECI master controller drivers (bus) +obj-$(CONFIG_ECI_BUS) += ecibus.o diff --git a/drivers/ecibus/ecibus.c b/drivers/ecibus/ecibus.c new file mode 100644 index 0000000..93266b3 --- /dev/null +++ b/drivers/ecibus/ecibus.c @@ -0,0 +1,583 @@ +/* + * This file is part of ECI (Enhancement Control Interface) bus controller + * driver + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Tapio Vihuri tapio.vihuri@nokia.com + * + * 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. + * + * ECI bus controller is kind of bridge between host CPU I2C and ECI accessory + * ECI communication. + */ + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/input/eci.h> + +#include <asm/intel_scu_ipc.h> + +/* + * VPROG2CNT - VPROG2 Control Register + * 2.5V | normal | normal + * 10 | 111 | 111 + * 2.5V | off | off + * 10 | 100 | 100 + */ +#define AvP_MSIC_VPROG2 0xd7 +#define AvP_MSIC_VPROG2_2V5_ON 0xbf +#define AvP_MSIC_VPROG2_2V5_OFF 0xa4 + +#define ECIBUS_I2C_ADDRS 0x6d +#define ECIBUS_STATUS_DATA_READY 0x01 +#define ECIBUS_STATUS_ACCESSORY_INT 0x02 + +#define ECIBUS_WAIT_IRQ 100 /* msec */ +#define ECI_RST_MIN 62 +#define ECI_RST_WAIT 10 /* msec */ + +struct ecibus_data { + struct i2c_client *client; + struct device *dev; + struct eci_cb *eci_callback; + int ecibus_rst_gpio; + int ecibus_sw_ctrl_gpio; + int ecibus_int_gpio; + wait_queue_head_t wait; + bool wait_eci_buttons; + bool wait_data; +}; + +static struct ecibus_data *the_ed; + +#ifdef CONFIG_DEBUG_FS +static int ecibus_ctrl_read_reg(u8 reg); +static int ecibus_ctrl_write_reg(u8 reg, u8 param); + +static struct dentry *ecibus_debugfs_dir; + +static ssize_t debug_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[80]; + int len = 0; + int ret; + + if (*ppos == 0) { + ret = ecibus_ctrl_read_reg(ECIREG_TEST_IN); + if (ret < 0) + return ret; + len += snprintf(buf + len, sizeof(buf) - len, "%02x\n", ret); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + return ret; +} + +static ssize_t debug_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[80]; + int buf_size; + unsigned long val; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + buf[buf_size] = '\0'; + if (!strict_strtoul(buf, 0, &val)) + ecibus_ctrl_write_reg(ECIREG_TEST_OUT, val); + else + return -EINVAL; + + return count; +} + +static ssize_t reset_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecibus_data *ed = file->private_data; + char buf[80]; + int len = 0; + int ret; + + if (*ppos == 0) { + ret = !!gpio_get_value(ed->ecibus_rst_gpio); + len = snprintf(buf, sizeof(buf), "%d\n", ret); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + return ret; +} + +static ssize_t reset_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + /* Assosiated in default_open() */ + struct ecibus_data *ed = file->private_data; + char buf[32]; + int buf_size; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (!memcmp(buf, "0", 1)) + gpio_set_value(ed->ecibus_rst_gpio, 0); + else if (!memcmp(buf, "1", 1)) + gpio_set_value(ed->ecibus_rst_gpio, 1); + + 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 debug_fops = { + .open = default_open, + .read = debug_read, + .write = debug_write, +}; + +static const struct file_operations reset_fops = { + .open = default_open, + .read = reset_read, + .write = reset_write, +}; + +static void ecibus_uninitialize_debugfs(void) +{ + if (ecibus_debugfs_dir) + debugfs_remove_recursive(ecibus_debugfs_dir); +} + +static long ecibus_initialize_debugfs(struct ecibus_data *ed) +{ + void *ok; + + /* /sys/kernel/debug/ecibus.# */ + ecibus_debugfs_dir = debugfs_create_dir(dev_name(ed->dev), NULL); + if (!ecibus_debugfs_dir) + return -ENOENT; + + /* Struct ed assosiated to inode->i_private */ + ok = debugfs_create_file("debug", S_IRUGO | S_IWUSR, + ecibus_debugfs_dir, ed, &debug_fops); + if (!ok) + goto fail; + + ok = debugfs_create_file("reset", S_IRUGO | S_IWUSR, + ecibus_debugfs_dir, ed, &reset_fops); + if (!ok) + goto fail; + + return 0; +fail: + ecibus_uninitialize_debugfs(); + return -ENOENT; +} +#else +#define ecibus_initialize_debugfs(ed) 1 +#define ecibus_uninitialize_debugfs() +#endif + +static struct i2c_board_info ecibus_i2c = { + I2C_BOARD_INFO("ecibus", ECIBUS_I2C_ADDRS) }; + +/* For ecibus controller internal registers */ +static int ecibus_ctrl_read_reg(u8 reg) +{ + + if (reg <= ECICMD_RESERVED) + return -EINVAL; + + return i2c_smbus_read_byte_data(the_ed->client, reg); +} + +static int ecibus_ctrl_write_reg(u8 reg, u8 param) +{ + + if (reg <= ECICMD_RESERVED) + return -EINVAL; + + return i2c_smbus_write_byte_data(the_ed->client, reg, param); +} + +/* Reset and learn ECI accessory, ie. get speed */ +static int ecibus_acc_reset(void) +{ + s32 ret; + + ecibus_ctrl_write_reg(ECIREG_RST_LEARN, 0); + + msleep(ECI_RST_WAIT); + + ret = ecibus_ctrl_read_reg(ECIREG_RST_LEARN); + if (ret < ECI_RST_MIN) + return -EIO; + + return 0; +} +/* Read always four bytes, as stated in ECI specification */ +static int ecibus_acc_read_direct(u8 addr, char *buf) +{ + s32 ret; + int i; + + /* Initiate ECI accessory memory read */ + the_ed->wait_data = false; + if (!ecibus_ctrl_write_reg(ECIREG_READ_COUNT, 4)) + if (ecibus_ctrl_write_reg(ECIREG_READ_DIRECT, addr)) + return -EIO; + + if (!wait_event_timeout(the_ed->wait, the_ed->wait_data == true, + msecs_to_jiffies(ECIBUS_WAIT_IRQ))) + return -EIO; + + for (i = 0; i < 4; i++) { + ret = ecibus_ctrl_read_reg(ECIREG_READ_DIRECT + i); + if (ret < 0) + return ret; + buf[i] = ret; + usleep_range(2000, 10000); + } + return 0; +} + +/* Trigger ECI accessory register data write (from accessory) */ +static int ecibus_fire_acc_read_reg(u8 reg, int count) +{ + if (!ecibus_ctrl_write_reg(ECIREG_READ_COUNT, count)) + return i2c_smbus_read_byte_data(the_ed->client, reg); + else + return -EIO; +} + +/* For ECI accessory internal registers */ +static int ecibus_acc_read_reg(u8 reg, u8 *buf, int count) +{ + s32 ret; + int i; + + if (reg > ECICMD_RESERVED) + return -EINVAL; + + the_ed->wait_data = false; + if (ecibus_fire_acc_read_reg(reg, count)) + return -EIO; + + if (!wait_event_timeout(the_ed->wait, the_ed->wait_data == true, + msecs_to_jiffies(ECIBUS_WAIT_IRQ))) + return -EIO; + + for (i = 0; i < count; i++) { + ret = ecibus_ctrl_read_reg(ECIREG_READ_DIRECT + i); + if (ret < 0) + return ret; + + buf[i] = ret; + } + + return 0; +} + +/* ECI accessory register write */ +static int ecibus_acc_write_reg(u8 reg, u8 param) +{ + if (reg > ECICMD_RESERVED) + return -EINVAL; + + return i2c_smbus_write_byte_data(the_ed->client, reg, param); +} + + +/* + * Struct eci_data is ECI input driver (dealing ECI accessories) data. + * Struct ecibus_data is this driver data, dealing just ECI communication. + * Global eci_register() pairs structs so that we can call ECI input driver + * event function with eci_data + */ +static void ecibus_emit_buttons(struct ecibus_data *ed, bool force_up) +{ + struct eci_data *eci = ed->eci_callback->priv; + struct eci_buttons_data *b = &eci->buttons_data; + + if (force_up) + b->buttons = b->buttons_up_mask; + + ed->eci_callback->event(ECI_EVENT_BUTTON, eci); +} + +static void ecibus_get_buttons(u8 *buf) +{ + int i, ret; + + for (i = 0; i < 2; i++) { + ret = ecibus_ctrl_read_reg(ECIREG_READ_DIRECT + i); + buf[i] = ret; + } +} + +static irqreturn_t ecibus_irq_handler(int irq, void *_ed) +{ + struct ecibus_data *ed = _ed; + struct eci_data *eci = ed->eci_callback->priv; + struct eci_buttons_data *b = &eci->buttons_data; + int status; + char buf[4]; + + /* Clears ecibus DATA interrupt */ + status = ecibus_ctrl_read_reg(ECIREG_STATUS); + + if (status & ECIBUS_STATUS_DATA_READY) { + /* + * Buttons are special case as we want be fast with them + * and this way we cope with nested button and data interrupts + */ + if (ed->wait_eci_buttons) { + ecibus_get_buttons(buf); + b->buttons = cpu_to_le32(*(u32 *)buf); + ecibus_emit_buttons(ed, ECI_REAL_BUTTONS); + ed->wait_eci_buttons = false; + } + /* Complete ECI data reading */ + ed->wait_data = true; + wake_up(&ed->wait); + } + + /* Accessory interrupt, ie. button pressed */ + if (status & ECIBUS_STATUS_ACCESSORY_INT) { + if (eci->mem_ok) { + ecibus_fire_acc_read_reg(ECICMD_PORT_DATA_0, 2); + ed->wait_eci_buttons = true; + } + } + + return IRQ_HANDLED; +} + +static struct eci_hw_ops ecibus_hw_ops = { + .acc_reset = ecibus_acc_reset, + .acc_read_direct = ecibus_acc_read_direct, + .acc_read_reg = ecibus_acc_read_reg, + .acc_write_reg = ecibus_acc_write_reg, +}; + +static int __init ecibus_probe(struct platform_device *pdev) +{ + struct ecibus_data *ed; + struct ecibus_platform_data *pdata = pdev->dev.platform_data; + struct i2c_adapter *adapter; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "platform_data not available: %d\n", + __LINE__); + return -EINVAL; + } + + ed = kzalloc(sizeof(*ed), GFP_KERNEL); + if (!ed) + return -ENOMEM; + + platform_set_drvdata(pdev, ed); + ed->dev = &pdev->dev; + ed->ecibus_rst_gpio = pdata->ecibus_rst_gpio; + ed->ecibus_sw_ctrl_gpio = pdata->ecibus_sw_ctrl_gpio; + ed->ecibus_int_gpio = pdata->ecibus_int_gpio; + + if (ed->ecibus_rst_gpio == 0 || ed->ecibus_sw_ctrl_gpio == 0 || + ed->ecibus_int_gpio == 0) { + ret = -ENXIO; + goto gpio_err; + } + + the_ed = ed; + + adapter = i2c_get_adapter(1); + if (adapter) + ed->client = i2c_new_device(adapter, &ecibus_i2c); + if (!ed->client) { + dev_err(ed->dev, "could not get i2c device %s at 0x%02x: %d\n", + ecibus_i2c.type, ecibus_i2c.addr, __LINE__); + kfree(ed); + + return -ENODEV; + } + + ret = ecibus_initialize_debugfs(ed); + if (ret) + dev_err(ed->dev, "could not create debugfs entries: %d\n", + __LINE__); + + ret = gpio_request(ed->ecibus_rst_gpio, "ECI_RSTn"); + if (ret) { + dev_err(ed->dev, "could not request ECI_RSTn gpio %d: %d\n", + ed->ecibus_rst_gpio, __LINE__); + goto rst_gpio_err; + } + + gpio_direction_output(ed->ecibus_rst_gpio, 0); + gpio_set_value(ed->ecibus_rst_gpio, 1); + + ret = gpio_request(ed->ecibus_sw_ctrl_gpio, "ECI_SW_CTRL"); + if (ret) { + dev_err(ed->dev, "could not request ECI_SW_CTRL gpio %d: %d\n", + ed->ecibus_sw_ctrl_gpio, __LINE__); + goto sw_ctrl_gpio_err; + } + + gpio_direction_input(ed->ecibus_sw_ctrl_gpio); + + ret = gpio_request(ed->ecibus_int_gpio, "ECI_INT"); + if (ret) { + dev_err(ed->dev, "could not request ECI_INT gpio %d: %d\n", + ed->ecibus_int_gpio, __LINE__); + goto int_gpio_err; + } + + gpio_direction_input(ed->ecibus_int_gpio); + + ret = request_threaded_irq(gpio_to_irq(ed->ecibus_int_gpio), NULL, + ecibus_irq_handler, IRQF_TRIGGER_RISING, "ECI_INT", ed); + if (ret) { + dev_err(ed->dev, "could not request irq %d: %d\n", + gpio_to_irq(ed->ecibus_int_gpio), __LINE__); + goto int_irq_err; + } + + /* Register itself to the ECI accessory driver */ + ed->eci_callback = eci_register(&ecibus_hw_ops); + if (IS_ERR(ed->eci_callback)) { + ret = PTR_ERR(ed->eci_callback); + goto eci_register_err; + + } + + /* + * Turn on vprog2 + * Some decent power ctrl interface, please + */ + intel_scu_ipc_iowrite8(AvP_MSIC_VPROG2, AvP_MSIC_VPROG2_2V5_ON); + + init_waitqueue_head(&ed->wait); + + return 0; + +eci_register_err: + free_irq(gpio_to_irq(ed->ecibus_int_gpio), ed); +int_irq_err: + gpio_free(ed->ecibus_int_gpio); +int_gpio_err: + gpio_free(ed->ecibus_sw_ctrl_gpio); +sw_ctrl_gpio_err: + gpio_set_value(ed->ecibus_rst_gpio, 0); + gpio_free(ed->ecibus_rst_gpio); +rst_gpio_err: + ecibus_uninitialize_debugfs(); + i2c_unregister_device(ed->client); +gpio_err: + kfree(ed); + + return ret; +} + +static int __exit ecibus_remove(struct platform_device *pdev) +{ + struct ecibus_data *ed = platform_get_drvdata(pdev); + + gpio_set_value(ed->ecibus_rst_gpio, 0); + gpio_free(ed->ecibus_rst_gpio); + + gpio_free(ed->ecibus_sw_ctrl_gpio); + + free_irq(gpio_to_irq(ed->ecibus_int_gpio), ed); + gpio_free(ed->ecibus_int_gpio); + + ecibus_uninitialize_debugfs(); + i2c_unregister_device(ed->client); + + /* + * Turn off vprog2 + * Some decent power ctrl interface, please + */ + intel_scu_ipc_iowrite8(AvP_MSIC_VPROG2, AvP_MSIC_VPROG2_2V5_OFF); + + kfree(ed); + + return 0; +} + +#ifdef CONFIG_PM + +static int ecibus_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + return -ENOSYS; +} +#else +#define ecibus_suspend NULL +#endif + +static struct platform_driver ecibus_driver = { + .driver = { + .name = "ecibus", + .owner = THIS_MODULE, + }, + .suspend = ecibus_suspend, + .remove = __exit_p(ecibus_remove), +}; + +static int __init ecibus_init(void) +{ + return platform_driver_probe(&ecibus_driver, ecibus_probe); +} +module_init(ecibus_init); + +static void __exit ecibus_exit(void) +{ + + platform_driver_unregister(&ecibus_driver); +} +module_exit(ecibus_exit); + +MODULE_DESCRIPTION("ECI accessory controller driver"); +MODULE_AUTHOR("Nokia Corporation"); +MODULE_ALIAS("platform:ecibus"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/input/eci.h b/include/linux/input/eci.h index b8be99c..a9d1008 100644 --- a/include/linux/input/eci.h +++ b/include/linux/input/eci.h @@ -154,4 +154,12 @@ struct eci_data { };
struct eci_cb *eci_register(struct eci_hw_ops *eci_ops); + +/* ecibus controller data */ +struct ecibus_platform_data { + int ecibus_rst_gpio; + int ecibus_sw_ctrl_gpio; + int ecibus_int_gpio; +}; + #endif
From: Tapio Vihuri tapio.vihuri@nokia.com
Gives platform data for ECI accessory input driver and ECI bus controller driver.
Signed-off-by: Tapio Vihuri tapio.vihuri@nokia.com --- arch/x86/platform/mrst/mrst.c | 59 +++++++++++++++++++++++++++++++++++++++++ 1 files changed, 59 insertions(+), 0 deletions(-)
diff --git a/arch/x86/platform/mrst/mrst.c b/arch/x86/platform/mrst/mrst.c index 79ae681..60dca78 100644 --- a/arch/x86/platform/mrst/mrst.c +++ b/arch/x86/platform/mrst/mrst.c @@ -14,6 +14,7 @@ #include <linux/sfi.h> #include <linux/irq.h> #include <linux/module.h> +#include <linux/platform_device.h>
#include <asm/setup.h> #include <asm/mpspec_def.h> @@ -309,3 +310,61 @@ static inline int __init setup_x86_mrst_timer(char *arg) return 0; } __setup("x86_mrst_timer=", setup_x86_mrst_timer); + +#if defined(CONFIG_ECI) || defined(CONFIG_ECI_MODULE) +#include <linux/input/eci.h> + +#define GPIO_ECI_RSTn 126 /* GP_CORE_030 + 96 */ +#define GPIO_ECI_SW_CTRL 178 /* GP_CORE_082 + 96 */ +#define GPIO_ECI_INT 16 /* GP_AON_016 */ + +static struct ecibus_platform_data medfield_ecibus_control = { + .ecibus_rst_gpio = GPIO_ECI_RSTn, + .ecibus_sw_ctrl_gpio = GPIO_ECI_SW_CTRL, + .ecibus_int_gpio = GPIO_ECI_INT, +}; + +/* + * This is just example, should be used in platform audio driver + * hsmic_event->event(hsmic_event->private, true) + */ +static void medfield_register_hsmic_event_cb(struct audio_hsmic_event *event) +{ + struct audio_hsmic_event *hsmic_event; + + hsmic_event = event; +} + +static struct eci_platform_data medfield_eci_platform_data = { + .register_hsmic_event_cb = medfield_register_hsmic_event_cb, +}; + +static struct platform_device medfield_ecibus_device = { + .name = "ecibus", + .id = 1, + .dev = { + .platform_data = &medfield_ecibus_control, + }, +}; + +static struct platform_device medfield_eci_device = { + .name = "ECI_accessory", + .dev = { + .platform_data = &medfield_eci_platform_data, + }, +}; + +static int __init medfield_ecibus_init(void) +{ + int retval; + + retval = platform_device_register(&medfield_ecibus_device); + if (retval < 0) + return retval; + + retval = platform_device_register(&medfield_eci_device); + + return retval; +} +device_initcall(medfield_ecibus_init); +#endif
Hi,
On 12/30/10 15:36, ext tapio.vihuri@nokia.com wrote:
From: Tapio Vihuri tapio.vihuri@nokia.com
ECI bus controller is kind of bridge between host CPU I2C and ECI accessory ECI communication.
It is kind of misleading to call this driver as a bus driver IMHO. For what I gathered, this is a driver for a micro-controller, which implements the ECI communication. This could be a generic driver for the micro-controller, but two lines makes it x86 specific...
...
+#include <asm/intel_scu_ipc.h>
...
+static int __init ecibus_probe(struct platform_device *pdev) +{
struct ecibus_data *ed;
struct ecibus_platform_data *pdata = pdev->dev.platform_data;
struct i2c_adapter *adapter;
int ret;
if (!pdata) {
dev_err(&pdev->dev, "platform_data not available: %d\n",
__LINE__);
return -EINVAL;
}
ed = kzalloc(sizeof(*ed), GFP_KERNEL);
if (!ed)
return -ENOMEM;
platform_set_drvdata(pdev, ed);
ed->dev = &pdev->dev;
ed->ecibus_rst_gpio = pdata->ecibus_rst_gpio;
ed->ecibus_sw_ctrl_gpio = pdata->ecibus_sw_ctrl_gpio;
ed->ecibus_int_gpio = pdata->ecibus_int_gpio;
if (ed->ecibus_rst_gpio == 0 || ed->ecibus_sw_ctrl_gpio == 0 ||
ed->ecibus_int_gpio == 0) {
ret = -ENXIO;
goto gpio_err;
}
the_ed = ed;
adapter = i2c_get_adapter(1);
What happens if the mc is on another bus? Shall you have platform data for this? Or even better, use i2c device/driver probing for this driver?
if (adapter)
ed->client = i2c_new_device(adapter, &ecibus_i2c);
if (!ed->client) {
dev_err(ed->dev, "could not get i2c device %s at 0x%02x: %d\n",
ecibus_i2c.type, ecibus_i2c.addr, __LINE__);
kfree(ed);
return -ENODEV;
}
ret = ecibus_initialize_debugfs(ed);
if (ret)
dev_err(ed->dev, "could not create debugfs entries: %d\n",
__LINE__);
ret = gpio_request(ed->ecibus_rst_gpio, "ECI_RSTn");
if (ret) {
dev_err(ed->dev, "could not request ECI_RSTn gpio %d: %d\n",
ed->ecibus_rst_gpio, __LINE__);
goto rst_gpio_err;
}
gpio_direction_output(ed->ecibus_rst_gpio, 0);
gpio_set_value(ed->ecibus_rst_gpio, 1);
ret = gpio_request(ed->ecibus_sw_ctrl_gpio, "ECI_SW_CTRL");
if (ret) {
dev_err(ed->dev, "could not request ECI_SW_CTRL gpio %d: %d\n",
ed->ecibus_sw_ctrl_gpio, __LINE__);
goto sw_ctrl_gpio_err;
}
gpio_direction_input(ed->ecibus_sw_ctrl_gpio);
ret = gpio_request(ed->ecibus_int_gpio, "ECI_INT");
if (ret) {
dev_err(ed->dev, "could not request ECI_INT gpio %d: %d\n",
ed->ecibus_int_gpio, __LINE__);
goto int_gpio_err;
}
gpio_direction_input(ed->ecibus_int_gpio);
ret = request_threaded_irq(gpio_to_irq(ed->ecibus_int_gpio), NULL,
ecibus_irq_handler, IRQF_TRIGGER_RISING, "ECI_INT", ed);
if (ret) {
dev_err(ed->dev, "could not request irq %d: %d\n",
gpio_to_irq(ed->ecibus_int_gpio), __LINE__);
goto int_irq_err;
}
/* Register itself to the ECI accessory driver */
ed->eci_callback = eci_register(&ecibus_hw_ops);
if (IS_ERR(ed->eci_callback)) {
ret = PTR_ERR(ed->eci_callback);
goto eci_register_err;
}
/*
* Turn on vprog2
* Some decent power ctrl interface, please
*/
intel_scu_ipc_iowrite8(AvP_MSIC_VPROG2, AvP_MSIC_VPROG2_2V5_ON);
Is this some sort of power enable for the controller itself? If it is, why not use a callback from the platform to enable/disable the power, so the driver will be platform independent, and you do this intel specific thing in arch/ platform code. If this is not related to the mc itself, then this shall not be part of this driver.
...
+static int __exit ecibus_remove(struct platform_device *pdev) +{
struct ecibus_data *ed = platform_get_drvdata(pdev);
gpio_set_value(ed->ecibus_rst_gpio, 0);
gpio_free(ed->ecibus_rst_gpio);
gpio_free(ed->ecibus_sw_ctrl_gpio);
free_irq(gpio_to_irq(ed->ecibus_int_gpio), ed);
gpio_free(ed->ecibus_int_gpio);
ecibus_uninitialize_debugfs();
i2c_unregister_device(ed->client);
/*
* Turn off vprog2
* Some decent power ctrl interface, please
*/
intel_scu_ipc_iowrite8(AvP_MSIC_VPROG2, AvP_MSIC_VPROG2_2V5_OFF);
Same applies here for the intel dependency.
Hi Tapio,
On Thu, Dec 30, 2010 at 03:36:57PM +0200, tapio.vihuri@nokia.com wrote:
From: Tapio Vihuri tapio.vihuri@nokia.com
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@nokia.com
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@nokia.com
- 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@nokia.com
- 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.
On Sun, 2011-01-02 at 00:19 -0800, ext Dmitry Torokhov wrote:
Hi Tapio,
Hi Dmitry
Thank you for good comments. I sent v3 patch set right after this email.
--------8<-------
Please keep Makefile and Kconfig sorted alphabetically.
Corrected in patch set.
--------8<-------
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.
Much nicer solution, thank you.
--------8<-------
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");
Corrected in patch set.
--------8<-------
+#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; }
I take this static inline solution, but I needed the parameter.
--------8<-------
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.
The ECI specification says that data in ECI accessory's memory is in big endian order. This simply get 16-bit size parameter right in any endianes machine.
--------8<-------
+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)
+{
--------8<-------
Same here. Should it be in debugfs probably?
I made it now as 0/1. It's in sysfs as this is user space's interface for ECI acessories inserting.
The other way is via ALSA sound driver providing jack detection, but it's not there yet.
--------8<-------
/*
* Configure ECI buttons now as we have after parsed
* enchancement features table
*/
I do not understand this comment.
I have loose some word somewhere... Now it goes: /* * Configure ECI buttons now as we know how after * enchancement features table has been parsed */
--------8<-------
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.
Corrected in patch set.
--------8<-------
+static struct miscdevice eci_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = ECI_DRIVERNAME,
+};
What does this device do?
Nothing, and I can't even remember why I have put it there first place. Removed now.
--------8<-------
Thank you.
-- Dmitry
Thank you, now driver is better.
participants (4)
-
Dmitry Torokhov
-
Peter Ujfalusi
-
Tapio Vihuri
-
tapio.vihuri@nokia.com