| /* arch/arm/mach-msm/rpc_server_handset.c |
| * |
| * Copyright (c) 2008-2010,2012 The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only 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. |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/platform_device.h> |
| #include <linux/input.h> |
| #include <linux/switch.h> |
| |
| #include <asm/mach-types.h> |
| |
| #include <mach/msm_rpcrouter.h> |
| #include <mach/board.h> |
| #include <mach/rpc_server_handset.h> |
| |
| #define DRIVER_NAME "msm-handset" |
| |
| #define HS_SERVER_PROG 0x30000062 |
| #define HS_SERVER_VERS 0x00010001 |
| |
| #define HS_RPC_PROG 0x30000091 |
| |
| #define HS_PROCESS_CMD_PROC 0x02 |
| #define HS_SUBSCRIBE_SRVC_PROC 0x03 |
| #define HS_REPORT_EVNT_PROC 0x05 |
| #define HS_EVENT_CB_PROC 1 |
| #define HS_EVENT_DATA_VER 1 |
| |
| #define RPC_KEYPAD_NULL_PROC 0 |
| #define RPC_KEYPAD_PASS_KEY_CODE_PROC 2 |
| #define RPC_KEYPAD_SET_PWR_KEY_STATE_PROC 3 |
| |
| #define HS_PWR_K 0x6F /* Power key */ |
| #define HS_END_K 0x51 /* End key or Power key */ |
| #define HS_STEREO_HEADSET_K 0x82 |
| #define HS_HEADSET_SWITCH_K 0x84 |
| #define HS_HEADSET_SWITCH_2_K 0xF0 |
| #define HS_HEADSET_SWITCH_3_K 0xF1 |
| #define HS_HEADSET_HEADPHONE_K 0xF6 |
| #define HS_HEADSET_MICROPHONE_K 0xF7 |
| #define HS_REL_K 0xFF /* key release */ |
| |
| #define SW_HEADPHONE_INSERT_W_MIC 1 /* HS with mic */ |
| |
| #define KEY(hs_key, input_key) ((hs_key << 24) | input_key) |
| |
| enum hs_event { |
| HS_EVNT_EXT_PWR = 0, /* External Power status */ |
| HS_EVNT_HSD, /* Headset Detection */ |
| HS_EVNT_HSTD, /* Headset Type Detection */ |
| HS_EVNT_HSSD, /* Headset Switch Detection */ |
| HS_EVNT_KPD, |
| HS_EVNT_FLIP, /* Flip / Clamshell status (open/close) */ |
| HS_EVNT_CHARGER, /* Battery is being charged or not */ |
| HS_EVNT_ENV, /* Events from runtime environment like DEM */ |
| HS_EVNT_REM, /* Events received from HS counterpart on a |
| remote processor*/ |
| HS_EVNT_DIAG, /* Diag Events */ |
| HS_EVNT_LAST, /* Should always be the last event type */ |
| HS_EVNT_MAX /* Force enum to be an 32-bit number */ |
| }; |
| |
| enum hs_src_state { |
| HS_SRC_STATE_UNKWN = 0, |
| HS_SRC_STATE_LO, |
| HS_SRC_STATE_HI, |
| }; |
| |
| struct hs_event_data { |
| uint32_t ver; /* Version number */ |
| enum hs_event event_type; /* Event Type */ |
| enum hs_event enum_disc; /* discriminator */ |
| uint32_t data_length; /* length of the next field */ |
| enum hs_src_state data; /* Pointer to data */ |
| uint32_t data_size; /* Elements to be processed in data */ |
| }; |
| |
| enum hs_return_value { |
| HS_EKPDLOCKED = -2, /* Operation failed because keypad is locked */ |
| HS_ENOTSUPPORTED = -1, /* Functionality not supported */ |
| HS_FALSE = 0, /* Inquired condition is not true */ |
| HS_FAILURE = 0, /* Requested operation was not successful */ |
| HS_TRUE = 1, /* Inquired condition is true */ |
| HS_SUCCESS = 1, /* Requested operation was successful */ |
| HS_MAX_RETURN = 0x7FFFFFFF/* Force enum to be a 32 bit number */ |
| }; |
| |
| struct hs_key_data { |
| uint32_t ver; /* Version number to track sturcture changes */ |
| uint32_t code; /* which key? */ |
| uint32_t parm; /* key status. Up/down or pressed/released */ |
| }; |
| |
| enum hs_subs_srvc { |
| HS_SUBS_SEND_CMD = 0, /* Subscribe to send commands to HS */ |
| HS_SUBS_RCV_EVNT, /* Subscribe to receive Events from HS */ |
| HS_SUBS_SRVC_MAX |
| }; |
| |
| enum hs_subs_req { |
| HS_SUBS_REGISTER, /* Subscribe */ |
| HS_SUBS_CANCEL, /* Unsubscribe */ |
| HS_SUB_STATUS_MAX |
| }; |
| |
| enum hs_event_class { |
| HS_EVNT_CLASS_ALL = 0, /* All HS events */ |
| HS_EVNT_CLASS_LAST, /* Should always be the last class type */ |
| HS_EVNT_CLASS_MAX |
| }; |
| |
| enum hs_cmd_class { |
| HS_CMD_CLASS_LCD = 0, /* Send LCD related commands */ |
| HS_CMD_CLASS_KPD, /* Send KPD related commands */ |
| HS_CMD_CLASS_LAST, /* Should always be the last class type */ |
| HS_CMD_CLASS_MAX |
| }; |
| |
| /* |
| * Receive events or send command |
| */ |
| union hs_subs_class { |
| enum hs_event_class evnt; |
| enum hs_cmd_class cmd; |
| }; |
| |
| struct hs_subs { |
| uint32_t ver; |
| enum hs_subs_srvc srvc; /* commands or events */ |
| enum hs_subs_req req; /* subscribe or unsubscribe */ |
| uint32_t host_os; |
| enum hs_subs_req disc; /* discriminator */ |
| union hs_subs_class id; |
| }; |
| |
| struct hs_event_cb_recv { |
| uint32_t cb_id; |
| uint32_t hs_key_data_ptr; |
| struct hs_key_data key; |
| }; |
| enum hs_ext_cmd_type { |
| HS_EXT_CMD_KPD_SEND_KEY = 0, /* Send Key */ |
| HS_EXT_CMD_KPD_BKLT_CTRL, /* Keypad backlight intensity */ |
| HS_EXT_CMD_LCD_BKLT_CTRL, /* LCD Backlight intensity */ |
| HS_EXT_CMD_DIAG_KEYMAP, /* Emulating a Diag key sequence */ |
| HS_EXT_CMD_DIAG_LOCK, /* Device Lock/Unlock */ |
| HS_EXT_CMD_GET_EVNT_STATUS, /* Get the status for one of the drivers */ |
| HS_EXT_CMD_KPD_GET_KEYS_STATUS,/* Get a list of keys status */ |
| HS_EXT_CMD_KPD_SET_PWR_KEY_RST_THOLD, /* PWR Key HW Reset duration */ |
| HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD, /* Set pwr key threshold duration */ |
| HS_EXT_CMD_LAST, /* Should always be the last command type */ |
| HS_EXT_CMD_MAX = 0x7FFFFFFF /* Force enum to be an 32-bit number */ |
| }; |
| |
| struct hs_cmd_data_type { |
| uint32_t hs_cmd_data_type_ptr; /* hs_cmd_data_type ptr length */ |
| uint32_t ver; /* version */ |
| enum hs_ext_cmd_type id; /* command id */ |
| uint32_t handle; /* handle returned from subscribe proc */ |
| enum hs_ext_cmd_type disc_id1; /* discriminator id */ |
| uint32_t input_ptr; /* input ptr length */ |
| uint32_t input_val; /* command specific data */ |
| uint32_t input_len; /* length of command input */ |
| enum hs_ext_cmd_type disc_id2; /* discriminator id */ |
| uint32_t output_len; /* length of output data */ |
| uint32_t delayed; /* execution context for modem |
| true - caller context |
| false - hs task context*/ |
| }; |
| |
| static const uint32_t hs_key_map[] = { |
| KEY(HS_PWR_K, KEY_POWER), |
| KEY(HS_END_K, KEY_END), |
| KEY(HS_STEREO_HEADSET_K, SW_HEADPHONE_INSERT_W_MIC), |
| KEY(HS_HEADSET_HEADPHONE_K, SW_HEADPHONE_INSERT), |
| KEY(HS_HEADSET_MICROPHONE_K, SW_MICROPHONE_INSERT), |
| KEY(HS_HEADSET_SWITCH_K, KEY_MEDIA), |
| KEY(HS_HEADSET_SWITCH_2_K, KEY_VOLUMEUP), |
| KEY(HS_HEADSET_SWITCH_3_K, KEY_VOLUMEDOWN), |
| 0 |
| }; |
| |
| enum { |
| NO_DEVICE = 0, |
| MSM_HEADSET = 1, |
| }; |
| /* Add newer versions at the top of array */ |
| static const unsigned int rpc_vers[] = { |
| 0x00030001, |
| 0x00020001, |
| 0x00010001, |
| }; |
| /* hs subscription request parameters */ |
| struct hs_subs_rpc_req { |
| uint32_t hs_subs_ptr; |
| struct hs_subs hs_subs; |
| uint32_t hs_cb_id; |
| uint32_t hs_handle_ptr; |
| uint32_t hs_handle_data; |
| }; |
| |
| static struct hs_subs_rpc_req *hs_subs_req; |
| |
| struct msm_handset { |
| struct input_dev *ipdev; |
| struct switch_dev sdev; |
| struct msm_handset_platform_data *hs_pdata; |
| bool mic_on, hs_on; |
| }; |
| |
| static struct msm_rpc_client *rpc_client; |
| static struct msm_handset *hs; |
| |
| static int hs_find_key(uint32_t hscode) |
| { |
| int i, key; |
| |
| key = KEY(hscode, 0); |
| |
| for (i = 0; hs_key_map[i] != 0; i++) { |
| if ((hs_key_map[i] & 0xff000000) == key) |
| return hs_key_map[i] & 0x00ffffff; |
| } |
| return -1; |
| } |
| |
| static void update_state(void) |
| { |
| int state; |
| |
| if (hs->mic_on && hs->hs_on) |
| state = 1 << 0; |
| else if (hs->hs_on) |
| state = 1 << 1; |
| else if (hs->mic_on) |
| state = 1 << 2; |
| else |
| state = 0; |
| |
| switch_set_state(&hs->sdev, state); |
| } |
| |
| /* |
| * tuple format: (key_code, key_param) |
| * |
| * old-architecture: |
| * key-press = (key_code, 0) |
| * key-release = (0xff, key_code) |
| * |
| * new-architecutre: |
| * key-press = (key_code, 0) |
| * key-release = (key_code, 0xff) |
| */ |
| static void report_hs_key(uint32_t key_code, uint32_t key_parm) |
| { |
| int key, temp_key_code; |
| |
| if (key_code == HS_REL_K) |
| key = hs_find_key(key_parm); |
| else |
| key = hs_find_key(key_code); |
| |
| temp_key_code = key_code; |
| |
| if (key_parm == HS_REL_K) |
| key_code = key_parm; |
| |
| switch (key) { |
| case KEY_POWER: |
| case KEY_END: |
| if (hs->hs_pdata->ignore_end_key) |
| input_report_key(hs->ipdev, KEY_POWER, |
| (key_code != HS_REL_K)); |
| else |
| input_report_key(hs->ipdev, key, |
| (key_code != HS_REL_K)); |
| break; |
| case KEY_MEDIA: |
| case KEY_VOLUMEUP: |
| case KEY_VOLUMEDOWN: |
| input_report_key(hs->ipdev, key, (key_code != HS_REL_K)); |
| break; |
| case SW_HEADPHONE_INSERT_W_MIC: |
| hs->mic_on = hs->hs_on = (key_code != HS_REL_K) ? 1 : 0; |
| input_report_switch(hs->ipdev, SW_HEADPHONE_INSERT, |
| hs->hs_on); |
| input_report_switch(hs->ipdev, SW_MICROPHONE_INSERT, |
| hs->mic_on); |
| update_state(); |
| break; |
| |
| case SW_HEADPHONE_INSERT: |
| hs->hs_on = (key_code != HS_REL_K) ? 1 : 0; |
| input_report_switch(hs->ipdev, key, hs->hs_on); |
| update_state(); |
| break; |
| case SW_MICROPHONE_INSERT: |
| hs->mic_on = (key_code != HS_REL_K) ? 1 : 0; |
| input_report_switch(hs->ipdev, key, hs->mic_on); |
| update_state(); |
| break; |
| case -1: |
| printk(KERN_ERR "%s: No mapping for remote handset event %d\n", |
| __func__, temp_key_code); |
| return; |
| } |
| input_sync(hs->ipdev); |
| } |
| |
| static int handle_hs_rpc_call(struct msm_rpc_server *server, |
| struct rpc_request_hdr *req, unsigned len) |
| { |
| struct rpc_keypad_pass_key_code_args { |
| uint32_t key_code; |
| uint32_t key_parm; |
| }; |
| |
| switch (req->procedure) { |
| case RPC_KEYPAD_NULL_PROC: |
| return 0; |
| |
| case RPC_KEYPAD_PASS_KEY_CODE_PROC: { |
| struct rpc_keypad_pass_key_code_args *args; |
| |
| args = (struct rpc_keypad_pass_key_code_args *)(req + 1); |
| args->key_code = be32_to_cpu(args->key_code); |
| args->key_parm = be32_to_cpu(args->key_parm); |
| |
| report_hs_key(args->key_code, args->key_parm); |
| |
| return 0; |
| } |
| |
| case RPC_KEYPAD_SET_PWR_KEY_STATE_PROC: |
| /* This RPC function must be available for the ARM9 |
| * to function properly. This function is redundant |
| * when RPC_KEYPAD_PASS_KEY_CODE_PROC is handled. So |
| * input_report_key is not needed. |
| */ |
| return 0; |
| default: |
| return -ENODEV; |
| } |
| } |
| |
| static struct msm_rpc_server hs_rpc_server = { |
| .prog = HS_SERVER_PROG, |
| .vers = HS_SERVER_VERS, |
| .rpc_call = handle_hs_rpc_call, |
| }; |
| |
| static int process_subs_srvc_callback(struct hs_event_cb_recv *recv) |
| { |
| if (!recv) |
| return -ENODATA; |
| |
| report_hs_key(be32_to_cpu(recv->key.code), be32_to_cpu(recv->key.parm)); |
| |
| return 0; |
| } |
| |
| static void process_hs_rpc_request(uint32_t proc, void *data) |
| { |
| if (proc == HS_EVENT_CB_PROC) |
| process_subs_srvc_callback(data); |
| else |
| pr_err("%s: unknown rpc proc %d\n", __func__, proc); |
| } |
| |
| static int hs_rpc_report_event_arg(struct msm_rpc_client *client, |
| void *buffer, void *data) |
| { |
| struct hs_event_rpc_req { |
| uint32_t hs_event_data_ptr; |
| struct hs_event_data data; |
| }; |
| |
| struct hs_event_rpc_req *req = buffer; |
| |
| req->hs_event_data_ptr = cpu_to_be32(0x1); |
| req->data.ver = cpu_to_be32(HS_EVENT_DATA_VER); |
| req->data.event_type = cpu_to_be32(HS_EVNT_HSD); |
| req->data.enum_disc = cpu_to_be32(HS_EVNT_HSD); |
| req->data.data_length = cpu_to_be32(0x1); |
| req->data.data = cpu_to_be32(*(enum hs_src_state *)data); |
| req->data.data_size = cpu_to_be32(sizeof(enum hs_src_state)); |
| |
| return sizeof(*req); |
| } |
| |
| static int hs_rpc_report_event_res(struct msm_rpc_client *client, |
| void *buffer, void *data) |
| { |
| enum hs_return_value result; |
| |
| result = be32_to_cpu(*(enum hs_return_value *)buffer); |
| pr_debug("%s: request completed: 0x%x\n", __func__, result); |
| |
| if (result == HS_SUCCESS) |
| return 0; |
| |
| return 1; |
| } |
| |
| void report_headset_status(bool connected) |
| { |
| int rc = -1; |
| enum hs_src_state status; |
| |
| if (connected == true) |
| status = HS_SRC_STATE_HI; |
| else |
| status = HS_SRC_STATE_LO; |
| |
| rc = msm_rpc_client_req(rpc_client, HS_REPORT_EVNT_PROC, |
| hs_rpc_report_event_arg, &status, |
| hs_rpc_report_event_res, NULL, -1); |
| |
| if (rc) |
| pr_err("%s: couldn't send rpc client request\n", __func__); |
| } |
| EXPORT_SYMBOL(report_headset_status); |
| |
| static int hs_rpc_pwr_cmd_arg(struct msm_rpc_client *client, |
| void *buffer, void *data) |
| { |
| struct hs_cmd_data_type *hs_pwr_cmd = buffer; |
| |
| hs_pwr_cmd->hs_cmd_data_type_ptr = cpu_to_be32(0x01); |
| |
| hs_pwr_cmd->ver = cpu_to_be32(0x03); |
| hs_pwr_cmd->id = cpu_to_be32(HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD); |
| hs_pwr_cmd->handle = cpu_to_be32(hs_subs_req->hs_handle_data); |
| hs_pwr_cmd->disc_id1 = cpu_to_be32(HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD); |
| hs_pwr_cmd->input_ptr = cpu_to_be32(0x01); |
| hs_pwr_cmd->input_val = cpu_to_be32(hs->hs_pdata->pwr_key_delay_ms); |
| hs_pwr_cmd->input_len = cpu_to_be32(0x01); |
| hs_pwr_cmd->disc_id2 = cpu_to_be32(HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD); |
| hs_pwr_cmd->output_len = cpu_to_be32(0x00); |
| hs_pwr_cmd->delayed = cpu_to_be32(0x00); |
| |
| return sizeof(*hs_pwr_cmd); |
| } |
| |
| static int hs_rpc_pwr_cmd_res(struct msm_rpc_client *client, |
| void *buffer, void *data) |
| { |
| uint32_t result; |
| |
| result = be32_to_cpu(*((uint32_t *)buffer)); |
| pr_debug("%s: request completed: 0x%x\n", __func__, result); |
| |
| return 0; |
| } |
| |
| static int hs_rpc_register_subs_arg(struct msm_rpc_client *client, |
| void *buffer, void *data) |
| { |
| hs_subs_req = buffer; |
| |
| hs_subs_req->hs_subs_ptr = cpu_to_be32(0x1); |
| hs_subs_req->hs_subs.ver = cpu_to_be32(0x1); |
| hs_subs_req->hs_subs.srvc = cpu_to_be32(HS_SUBS_RCV_EVNT); |
| hs_subs_req->hs_subs.req = cpu_to_be32(HS_SUBS_REGISTER); |
| hs_subs_req->hs_subs.host_os = cpu_to_be32(0x4); /* linux */ |
| hs_subs_req->hs_subs.disc = cpu_to_be32(HS_SUBS_RCV_EVNT); |
| hs_subs_req->hs_subs.id.evnt = cpu_to_be32(HS_EVNT_CLASS_ALL); |
| |
| hs_subs_req->hs_cb_id = cpu_to_be32(0x1); |
| |
| hs_subs_req->hs_handle_ptr = cpu_to_be32(0x1); |
| hs_subs_req->hs_handle_data = cpu_to_be32(0x0); |
| |
| return sizeof(*hs_subs_req); |
| } |
| |
| static int hs_rpc_register_subs_res(struct msm_rpc_client *client, |
| void *buffer, void *data) |
| { |
| uint32_t result; |
| |
| result = be32_to_cpu(*((uint32_t *)buffer)); |
| pr_debug("%s: request completed: 0x%x\n", __func__, result); |
| |
| return 0; |
| } |
| |
| static int hs_cb_func(struct msm_rpc_client *client, void *buffer, int in_size) |
| { |
| int rc = -1; |
| |
| struct rpc_request_hdr *hdr = buffer; |
| |
| hdr->type = be32_to_cpu(hdr->type); |
| hdr->xid = be32_to_cpu(hdr->xid); |
| hdr->rpc_vers = be32_to_cpu(hdr->rpc_vers); |
| hdr->prog = be32_to_cpu(hdr->prog); |
| hdr->vers = be32_to_cpu(hdr->vers); |
| hdr->procedure = be32_to_cpu(hdr->procedure); |
| |
| process_hs_rpc_request(hdr->procedure, |
| (void *) (hdr + 1)); |
| |
| msm_rpc_start_accepted_reply(client, hdr->xid, |
| RPC_ACCEPTSTAT_SUCCESS); |
| rc = msm_rpc_send_accepted_reply(client, 0); |
| if (rc) { |
| pr_err("%s: sending reply failed: %d\n", __func__, rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int __devinit hs_rpc_cb_init(void) |
| { |
| int rc = 0, i, num_vers; |
| |
| num_vers = ARRAY_SIZE(rpc_vers); |
| |
| for (i = 0; i < num_vers; i++) { |
| rpc_client = msm_rpc_register_client("hs", |
| HS_RPC_PROG, rpc_vers[i], 0, hs_cb_func); |
| |
| if (IS_ERR(rpc_client)) |
| pr_debug("%s: RPC Client version %d failed, fallback\n", |
| __func__, rpc_vers[i]); |
| else |
| break; |
| } |
| |
| if (IS_ERR(rpc_client)) { |
| pr_err("%s: Incompatible RPC version error %ld\n", |
| __func__, PTR_ERR(rpc_client)); |
| return PTR_ERR(rpc_client); |
| } |
| |
| rc = msm_rpc_client_req(rpc_client, HS_SUBSCRIBE_SRVC_PROC, |
| hs_rpc_register_subs_arg, NULL, |
| hs_rpc_register_subs_res, NULL, -1); |
| if (rc) { |
| pr_err("%s: RPC client request failed for subscribe services\n", |
| __func__); |
| goto err_client_req; |
| } |
| |
| rc = msm_rpc_client_req(rpc_client, HS_PROCESS_CMD_PROC, |
| hs_rpc_pwr_cmd_arg, NULL, |
| hs_rpc_pwr_cmd_res, NULL, -1); |
| if (rc) |
| pr_err("%s: RPC client request failed for pwr key" |
| " delay cmd, using normal mode\n", __func__); |
| return 0; |
| err_client_req: |
| msm_rpc_unregister_client(rpc_client); |
| return rc; |
| } |
| |
| static int __devinit hs_rpc_init(void) |
| { |
| int rc; |
| |
| rc = hs_rpc_cb_init(); |
| if (rc) { |
| pr_err("%s: failed to initialize rpc client, try server...\n", |
| __func__); |
| |
| rc = msm_rpc_create_server(&hs_rpc_server); |
| if (rc) { |
| pr_err("%s: failed to create rpc server\n", __func__); |
| return rc; |
| } |
| } |
| |
| return rc; |
| } |
| |
| static void __devexit hs_rpc_deinit(void) |
| { |
| if (rpc_client) |
| msm_rpc_unregister_client(rpc_client); |
| } |
| |
| static ssize_t msm_headset_print_name(struct switch_dev *sdev, char *buf) |
| { |
| switch (switch_get_state(&hs->sdev)) { |
| case NO_DEVICE: |
| return sprintf(buf, "No Device\n"); |
| case MSM_HEADSET: |
| return sprintf(buf, "Headset\n"); |
| } |
| return -EINVAL; |
| } |
| |
| static int __devinit hs_probe(struct platform_device *pdev) |
| { |
| int rc = 0; |
| struct input_dev *ipdev; |
| |
| hs = kzalloc(sizeof(struct msm_handset), GFP_KERNEL); |
| if (!hs) |
| return -ENOMEM; |
| |
| hs->sdev.name = "h2w"; |
| hs->sdev.print_name = msm_headset_print_name; |
| |
| rc = switch_dev_register(&hs->sdev); |
| if (rc) |
| goto err_switch_dev_register; |
| |
| ipdev = input_allocate_device(); |
| if (!ipdev) { |
| rc = -ENOMEM; |
| goto err_alloc_input_dev; |
| } |
| input_set_drvdata(ipdev, hs); |
| |
| hs->ipdev = ipdev; |
| |
| if (pdev->dev.platform_data) |
| hs->hs_pdata = pdev->dev.platform_data; |
| |
| if (hs->hs_pdata->hs_name) |
| ipdev->name = hs->hs_pdata->hs_name; |
| else |
| ipdev->name = DRIVER_NAME; |
| |
| ipdev->id.vendor = 0x0001; |
| ipdev->id.product = 1; |
| ipdev->id.version = 1; |
| |
| input_set_capability(ipdev, EV_KEY, KEY_MEDIA); |
| input_set_capability(ipdev, EV_KEY, KEY_VOLUMEUP); |
| input_set_capability(ipdev, EV_KEY, KEY_VOLUMEDOWN); |
| input_set_capability(ipdev, EV_SW, SW_HEADPHONE_INSERT); |
| input_set_capability(ipdev, EV_SW, SW_MICROPHONE_INSERT); |
| input_set_capability(ipdev, EV_KEY, KEY_POWER); |
| input_set_capability(ipdev, EV_KEY, KEY_END); |
| |
| rc = input_register_device(ipdev); |
| if (rc) { |
| dev_err(&ipdev->dev, |
| "hs_probe: input_register_device rc=%d\n", rc); |
| goto err_reg_input_dev; |
| } |
| |
| platform_set_drvdata(pdev, hs); |
| |
| rc = hs_rpc_init(); |
| if (rc) { |
| dev_err(&ipdev->dev, "rpc init failure\n"); |
| goto err_hs_rpc_init; |
| } |
| |
| return 0; |
| |
| err_hs_rpc_init: |
| input_unregister_device(ipdev); |
| ipdev = NULL; |
| err_reg_input_dev: |
| input_free_device(ipdev); |
| err_alloc_input_dev: |
| switch_dev_unregister(&hs->sdev); |
| err_switch_dev_register: |
| kfree(hs); |
| return rc; |
| } |
| |
| static int __devexit hs_remove(struct platform_device *pdev) |
| { |
| struct msm_handset *hs = platform_get_drvdata(pdev); |
| |
| input_unregister_device(hs->ipdev); |
| switch_dev_unregister(&hs->sdev); |
| kfree(hs); |
| hs_rpc_deinit(); |
| return 0; |
| } |
| |
| static struct platform_driver hs_driver = { |
| .probe = hs_probe, |
| .remove = __devexit_p(hs_remove), |
| .driver = { |
| .name = DRIVER_NAME, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init hs_init(void) |
| { |
| return platform_driver_register(&hs_driver); |
| } |
| late_initcall(hs_init); |
| |
| static void __exit hs_exit(void) |
| { |
| platform_driver_unregister(&hs_driver); |
| } |
| module_exit(hs_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("platform:msm-handset"); |