diff options
author | Pavel <pavel@ucw.cz> | 2018-10-28 13:34:45 +0100 |
---|---|---|
committer | Pavel <pavel@ucw.cz> | 2019-01-07 11:23:44 +0100 |
commit | 6957598e7c6f5d9519fe695a4d92290f9aeee320 (patch) | |
tree | 025d5021016f44b5053faf90cc048e49b1c3e07c | |
parent | ae666a3e97b46aa3eccbfcec77dc1b365522dfc4 (diff) | |
download | linux-k-6957598e7c6f5d9519fe695a4d92290f9aeee320.tar.gz |
n900: ECI support.
-rw-r--r-- | drivers/input/misc/eci.c | 826 | ||||
-rw-r--r-- | include/linux/input/eci.h | 96 |
2 files changed, 922 insertions, 0 deletions
diff --git a/drivers/input/misc/eci.c b/drivers/input/misc/eci.c new file mode 100644 index 00000000000000..1ff717c63814f1 --- /dev/null +++ b/drivers/input/misc/eci.c @@ -0,0 +1,826 @@ +/* + * This file is combines HW independent part of AV ECI (Enhancement Control + * Interface) and ACI (Accessory Control Interface) drivers + * + * 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 + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/seq_file.h> +#include <linux/mfd/twl5031-aci.h> +#include <sound/n9.h> +#include <linux/input.h> +#include <linux/sched.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <sound/jack.h> +#include <linux/input/eci.h> +#include <linux/slab.h> + +#define ECI_DRIVERNAME "ECI_accessory" + +#define ECI_WAIT_SEND_BUTTON 5 /* ms */ +#define ECI_WAIT_BUS_SETTLE_LO 20000 /* us */ +#define ECI_WAIT_BUS_SETTLE_HI 20100 /* us */ +#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 switch codes that 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 switch */ + 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 */ +}; + +static struct aci_cb aci_callback; +static struct eci_cb eci_callback; +static struct dfl61audio_hsmic_event hsmic_event; + +static struct eci_data *the_eci; + +/* Returns size of accessory memory or error. */ +static int eci_get_ekey(struct eci_data *eci, int *key) +{ + u8 buf[4]; + struct block { + u8 id; + u8 len; + u16 size; + } *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 = be16_to_cpu(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 DEVICE_ATTR(memory, S_IRUGO, show_eci_memory, NULL); + +static struct attribute *eci_attributes[] = { + &dev_attr_memory.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 often these are just buttons. + */ +static int eci_init_buttons(struct eci_data *eci) +{ + struct enhancement_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_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) { + dev_err(eci->dev, "Unable to set accessory interrupts\n"); + return ret; + } + + usleep_range(ECI_WAIT_BUS_SETTLE_LO, ECI_WAIT_BUS_SETTLE_HI); + mireg |= ECI_INT_ENABLE; + ret = eci->eci_hw_ops->acc_write_reg(ECICMD_MASTER_INT_REG, mireg); + if (ret) { + dev_err(eci->dev, "Unable to set accessory interrupts\n"); + return ret; + } + + usleep_range(ECI_WAIT_BUS_SETTLE_LO, ECI_WAIT_BUS_SETTLE_HI); + + return ret; +} + +/* Find "enhancement features" block from buffer. */ +static int eci_get_enhancement_features(struct eci_data *eci) +{ + u8 *mem = (void *)eci->memory; + struct block { + u8 id; + u8 len; + } *b = (void *)mem; + struct block *mem_end = (void *)(eci->memory + eci->mem_size); + + if (b->id != ECI_EKEY_BLOCK_ID) + return -ENODEV; + + do { + dev_vdbg(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_vdbg(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 is done here, be careful as pointers to memory + * tend to go wrong easily. ECI "Enhancement Features" block is variable size, + * so we try to catch out of block pointers 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_enhancement_features(struct eci_data *eci) +{ + struct enhancement_features_fixed *eff = eci->e_features_fix; + struct enhancement_features_variable *efv = &eci->e_features_var; + int i; + u8 n, k; + void *mem_end = (void *)((u8 *)eff + eff->length); + + dev_vdbg(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_vdbg(eci->dev, "number of features %d\n", n); + + if (n > ECI_MAX_FEATURE_COUNT) + return -EINVAL; + + k = DIV_ROUND_UP(n, 8); + dev_vdbg(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_vdbg(eci->dev, "active_state[%d] 0x%02x\n", i, + efv->active_state[i]); + + eci->buttons_data.buttons_up_mask = ~be32_to_cpu( + (*(u32 *)efv->active_state)) & (BIT_MASK(n + 1) - 1); + dev_dbg(eci->dev, "buttons mask 0x%08x\n", + eci->buttons_data.buttons_up_mask); + /* + * ECI accessory responses use as many bytes needed for I/O pins + * up to four bytes, when lines 24..31 are used. All tested ECI + * accessories, however, return two data bytes event though there + * are less than eight I/O pins. + * + * So we get always a 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; + usleep_range(ECI_WAIT_BUS_SETTLE_LO, ECI_WAIT_BUS_SETTLE_HI); + + ret = eci->eci_hw_ops->acc_reset(); + if (ret) { + dev_err(eci->dev, "Acc_reset fails\n"); + return ret; + } + + usleep_range(ECI_WAIT_BUS_SETTLE_LO, ECI_WAIT_BUS_SETTLE_HI); + + ret = eci->eci_hw_ops->acc_write_reg(ECICMD_MIC_CTRL, ECI_MIC_OFF); + if (ret) { + dev_err(eci->dev, "Unable to control headset microphone\n"); + 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) { + dev_err(eci->dev, "Unable to get ekey\n"); + return ret; + } + + eci->mem_size = key; + if (eci->mem_size > ECI_MAX_MEM_SIZE) { + dev_err(eci->dev, "eci->mem_size too big: %d\n", eci->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) { + dev_err(eci->dev, "Unable to get memory\n"); + return ret; + } + + if (eci_get_enhancement_features(eci)) { + dev_err(eci->dev, "Cannot get enhancement features\n"); + return -EIO; + } + + if (eci_parse_enhancement_features(eci)) { + dev_err(eci->dev, "Cannot parse enhancement features\n"); + return -EIO; + } + + /* + * Configure ECI buttons now when we know how after parsing + * enhancement features. + */ + usleep_range(ECI_WAIT_BUS_SETTLE_LO, ECI_WAIT_BUS_SETTLE_HI); + 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) { + dev_err(eci->dev, "Unable to init buttons\n"); + return ret; + } + + eci->mem_ok = true; + usleep_range(ECI_WAIT_BUS_SETTLE_LO, ECI_WAIT_BUS_SETTLE_HI); + + 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(). */ + 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 button */ +static int eci_get_button(struct eci_data *eci) +{ + struct eci_buttons_data *b = &eci->buttons_data; + + 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 be used ONLY inside eci_parse_button() ! */ +#define ACTIVE_STATE(x) (be32_to_cpu(*(u32 *)efv->active_state) & BIT(x-1)) +#define BUTTON_STATE(x) ((buttons & BIT(x))>>1) + +static void eci_parse_button(struct eci_data *eci, u32 buttons) +{ + int pin, code, state = 0; + u8 n, io_fun; + struct enhancement_features_variable *efv = &eci->e_features_var; + struct enhancement_features_fixed *eff = eci->e_features_fix; + + n = eff->number_of_features; + + for (pin = 1; pin <= n; pin++) + state += (BUTTON_STATE(pin) == ACTIVE_STATE(pin)); + + if (state == n) { + dev_err(eci->dev, "report all buttons down, reject\n"); + return; + } + + 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 " + "at pin %d\n", io_fun, pin); + 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); +} + +static int eci_send_button(struct eci_data *eci) +{ + int i; + struct enhancement_features_fixed *eff = eci->e_features_fix; + struct eci_buttons_data *b = &eci->buttons_data; + u8 n; + + n = eff->number_of_features; + + if (n > ECI_MAX_FEATURE_COUNT) + return -EINVAL; + /* + * Codes on top of KEY_MAX are switch events, let the input + * system take care multiple key events. + */ + for (i = 0; i < ECI_BUTTON_BUF_SIZE; i++) { + if (b->buttons_buf[b->rindex] == 0) + break; + + eci_parse_button(eci, b->buttons_buf[b->rindex]); + + b->buttons_buf[b->rindex] = 0; + b->rindex++; + if (b->rindex == ECI_BUTTON_BUF_SIZE) + b->rindex = 0; + } + + return 0; +} + +static void aci_button_event(bool button_down, void *priv) +{ + struct eci_data *eci = priv; + + if (!eci) + return; + + if (button_down) + input_report_key(eci->acc_input, KEY_PHONE/* BTN_0 */, 1); + else + input_report_key(eci->acc_input, KEY_PHONE/* BTN_0 */, 0); + + input_sync(eci->acc_input); +} + +/* ACI driver calls this in ECI accessory event. */ +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_PLUG_IN: + dev_dbg(eci->dev, "ECI_EVENT_PLUG_IN\n"); + 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; + } + if (eci->jack_report) + eci->jack_report(SND_JACK_HEADSET); + dev_dbg(eci->dev, "ECI jack event reported\n"); + break; + case ECI_EVENT_PLUG_OUT: + eci->mem_ok = false; + break; + case ECI_EVENT_BUTTON: + dev_dbg(eci->dev, "buttons 0x%08x\n", b->buttons); + + ret = eci_get_button(eci); + if (ret) + dev_err(eci->dev, "error %d getting buttons\n", ret); + + 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 || !eci->mem_ok) + return; + + dev_vdbg(eci->dev, "ECI mic %s\n", on ? "auto" : "off"); + + 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 function for several tasks. */ +static void eci_work(struct work_struct *ws) +{ + struct eci_data *eci = container_of(ws, struct eci_data, eci_ws.work); + int ret; + + if (!eci->mem_ok) + return; + + 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 aci_cb *aci_register(void) +{ + if (!the_eci) + return ERR_PTR(-EBUSY); + + return &aci_callback; +} +EXPORT_SYMBOL(aci_register); + +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 eci_probe(struct platform_device *pdev) +{ + struct eci_data *eci; + struct eci_platform_data *pdata = pdev->dev.platform_data; + struct device_node *np = pdev->dev.of_node; + int ret; + + eci = devm_kzalloc(&pdev->dev, 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(&pdev->dev, "could not register misc_device: %d\n", + __LINE__); + goto err_misc; + } + + the_eci = eci; + + aci_callback.button_event = aci_button_event; + aci_callback.priv = 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(&pdev->dev, "could not create sysfs entries: %d\n", + __LINE__); + goto err_sysfs; + } + + ret = init_accessory_input(eci); + if (ret) { + dev_err(&pdev->dev, "ERROR initializing accessory input: %d\n", + __LINE__); + goto err_input; + } + + 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); + } + + eci->jack_report = pdata->jack_report; + } else if (np) { + /* HACK + * Simple way of setting this trought DT? + * Use of_dev_auxdata only? + */ + hsmic_event.private = eci; + hsmic_event.event = eci_hsmic_event; + dfl61_register_hsmic_event_cb(&hsmic_event); + eci->jack_report = dfl61_jack_report; + } + + init_waitqueue_head(&eci->wait); + INIT_DELAYED_WORK(&eci->eci_ws, eci_work); + + eci->mem_ok = false; + 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: + sysfs_remove_group(&pdev->dev.kobj, &eci_attr_group); + +err_sysfs: + misc_deregister(&eci_device); + +err_misc: + + return ret; +} + +static int eci_remove(struct platform_device *pdev) +{ + struct eci_data *eci = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&eci->eci_ws); + sysfs_remove_group(&pdev->dev.kobj, &eci_attr_group); + remove_accessory_input(eci); + kfree(eci); + misc_deregister(&eci_device); + + return 0; +} + +static const struct of_device_id eci_of_match[] = { + {.compatible = "nokia,eci", }, + { } +}; +MODULE_DEVICE_TABLE(of, eci_of_match); + +static struct platform_driver eci_driver = { + .probe = eci_probe, + .remove = eci_remove, + .driver = { + .name = ECI_DRIVERNAME, + .owner = THIS_MODULE, + .of_match_table = eci_of_match, + }, +}; +module_platform_driver(eci_driver); + +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 00000000000000..4015437db7d436 --- /dev/null +++ b/include/linux/input/eci.h @@ -0,0 +1,96 @@ +/* + * 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__ + +#include <sound/n9.h> + +#define ECI_MAX_MEM_SIZE 0x7c +#define ECI_BUTTON_BUF_SIZE 32 +#define ECI_MAX_FEATURE_COUNT 31 + +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 aci_cb { + void *priv; + void (*button_event)(bool button_down, void *priv); +}; + +struct eci_cb { + void *priv; + void (*event)(int event, void *priv); +}; + +struct eci_platform_data { + void (*register_hsmic_event_cb)(struct dfl61audio_hsmic_event *); + void (*jack_report) (int status); +}; + +struct enhancement_features_fixed { + u8 block_id; + u8 length; + u8 connector_conf; + u8 number_of_features; +}; + +struct enhancement_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 mem_ok; + u16 mem_size; + u8 memory[ECI_MAX_MEM_SIZE]; + struct enhancement_features_fixed *e_features_fix; + struct enhancement_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; + + void (*jack_report) (int status); +}; + +struct aci_cb *aci_register(void); +struct eci_cb *eci_register(struct eci_hw_ops *eci_ops); +#endif |