/* linux/arch/arm/mach-msm/rpc_hsusb.c * * Copyright (c) 2008-2012, The Linux Foundation. All rights reserved. * * All source code in this file is licensed under the following license except * where indicated. * * 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, you can find it at http://www.fsf.org */ #include #include #include #include #include static struct msm_rpc_endpoint *usb_ep; static struct msm_rpc_endpoint *chg_ep; #define MSM_RPC_CHG_PROG 0x3000001a struct msm_chg_rpc_ids { unsigned long vers_comp; unsigned chg_usb_charger_connected_proc; unsigned chg_usb_charger_disconnected_proc; unsigned chg_usb_i_is_available_proc; unsigned chg_usb_i_is_not_available_proc; }; struct msm_hsusb_rpc_ids { unsigned long prog; unsigned long vers_comp; unsigned long init_phy; unsigned long vbus_pwr_up; unsigned long vbus_pwr_down; unsigned long update_product_id; unsigned long update_serial_num; unsigned long update_is_serial_num_null; unsigned long reset_rework_installed; unsigned long enable_pmic_ulpi_data0; unsigned long disable_pmic_ulpi_data0; }; static struct msm_hsusb_rpc_ids usb_rpc_ids; static struct msm_chg_rpc_ids chg_rpc_ids; static int msm_hsusb_init_rpc_ids(unsigned long vers) { if (vers == 0x00010001) { usb_rpc_ids.prog = 0x30000064; usb_rpc_ids.vers_comp = 0x00010001; usb_rpc_ids.init_phy = 2; usb_rpc_ids.vbus_pwr_up = 6; usb_rpc_ids.vbus_pwr_down = 7; usb_rpc_ids.update_product_id = 8; usb_rpc_ids.update_serial_num = 9; usb_rpc_ids.update_is_serial_num_null = 10; usb_rpc_ids.reset_rework_installed = 17; usb_rpc_ids.enable_pmic_ulpi_data0 = 18; usb_rpc_ids.disable_pmic_ulpi_data0 = 19; return 0; } else if (vers == 0x00010002) { usb_rpc_ids.prog = 0x30000064; usb_rpc_ids.vers_comp = 0x00010002; usb_rpc_ids.init_phy = 2; usb_rpc_ids.vbus_pwr_up = 6; usb_rpc_ids.vbus_pwr_down = 7; usb_rpc_ids.update_product_id = 8; usb_rpc_ids.update_serial_num = 9; usb_rpc_ids.update_is_serial_num_null = 10; usb_rpc_ids.reset_rework_installed = 17; usb_rpc_ids.enable_pmic_ulpi_data0 = 18; usb_rpc_ids.disable_pmic_ulpi_data0 = 19; return 0; } else { pr_err("%s: no matches found for version\n", __func__); return -ENODATA; } } static int msm_chg_init_rpc(unsigned long vers) { if (((vers & RPC_VERSION_MAJOR_MASK) == 0x00010000) || ((vers & RPC_VERSION_MAJOR_MASK) == 0x00020000) || ((vers & RPC_VERSION_MAJOR_MASK) == 0x00030000) || ((vers & RPC_VERSION_MAJOR_MASK) == 0x00040000)) { chg_ep = msm_rpc_connect_compatible(MSM_RPC_CHG_PROG, vers, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(chg_ep)) return -ENODATA; chg_rpc_ids.vers_comp = vers; chg_rpc_ids.chg_usb_charger_connected_proc = 7; chg_rpc_ids.chg_usb_charger_disconnected_proc = 8; chg_rpc_ids.chg_usb_i_is_available_proc = 9; chg_rpc_ids.chg_usb_i_is_not_available_proc = 10; return 0; } else return -ENODATA; } /* rpc connect for hsusb */ int msm_hsusb_rpc_connect(void) { if (usb_ep && !IS_ERR(usb_ep)) { pr_debug("%s: usb_ep already connected\n", __func__); return 0; } /* Initialize rpc ids */ if (msm_hsusb_init_rpc_ids(0x00010001)) { pr_err("%s: rpc ids initialization failed\n" , __func__); return -ENODATA; } usb_ep = msm_rpc_connect_compatible(usb_rpc_ids.prog, usb_rpc_ids.vers_comp, MSM_RPC_UNINTERRUPTIBLE); if (IS_ERR(usb_ep)) { pr_err("%s: connect compatible failed vers = %lx\n", __func__, usb_rpc_ids.vers_comp); /* Initialize rpc ids */ if (msm_hsusb_init_rpc_ids(0x00010002)) { pr_err("%s: rpc ids initialization failed\n", __func__); return -ENODATA; } usb_ep = msm_rpc_connect_compatible(usb_rpc_ids.prog, usb_rpc_ids.vers_comp, MSM_RPC_UNINTERRUPTIBLE); } if (IS_ERR(usb_ep)) { pr_err("%s: connect compatible failed vers = %lx\n", __func__, usb_rpc_ids.vers_comp); return -EAGAIN; } else pr_debug("%s: rpc connect success vers = %lx\n", __func__, usb_rpc_ids.vers_comp); return 0; } EXPORT_SYMBOL(msm_hsusb_rpc_connect); /* rpc connect for charging */ int msm_chg_rpc_connect(void) { uint32_t chg_vers; if (machine_is_msm7x27_surf() || machine_is_qsd8x50_surf()) return -ENOTSUPP; if (chg_ep && !IS_ERR(chg_ep)) { pr_debug("%s: chg_ep already connected\n", __func__); return 0; } chg_vers = 0x00040001; if (!msm_chg_init_rpc(chg_vers)) goto chg_found; chg_vers = 0x00030001; if (!msm_chg_init_rpc(chg_vers)) goto chg_found; chg_vers = 0x00020001; if (!msm_chg_init_rpc(chg_vers)) goto chg_found; chg_vers = 0x00010001; if (!msm_chg_init_rpc(chg_vers)) goto chg_found; pr_err("%s: connect compatible failed \n", __func__); return -EAGAIN; chg_found: pr_debug("%s: connected to rpc vers = %x\n", __func__, chg_vers); return 0; } EXPORT_SYMBOL(msm_chg_rpc_connect); /* rpc call for phy_reset */ int msm_hsusb_phy_reset(void) { int rc = 0; struct hsusb_phy_start_req { struct rpc_request_hdr hdr; } req; if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: phy_reset rpc failed before call," "rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } rc = msm_rpc_call(usb_ep, usb_rpc_ids.init_phy, &req, sizeof(req), 5 * HZ); if (rc < 0) { pr_err("%s: phy_reset rpc failed! rc = %d\n", __func__, rc); } else pr_debug("msm_hsusb_phy_reset\n"); return rc; } EXPORT_SYMBOL(msm_hsusb_phy_reset); /* rpc call for vbus powerup */ int msm_hsusb_vbus_powerup(void) { int rc = 0; struct hsusb_phy_start_req { struct rpc_request_hdr hdr; } req; if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: vbus_powerup rpc failed before call," "rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } rc = msm_rpc_call(usb_ep, usb_rpc_ids.vbus_pwr_up, &req, sizeof(req), 5 * HZ); if (rc < 0) { pr_err("%s: vbus_powerup failed! rc = %d\n", __func__, rc); } else pr_debug("msm_hsusb_vbus_powerup\n"); return rc; } EXPORT_SYMBOL(msm_hsusb_vbus_powerup); /* rpc call for vbus shutdown */ int msm_hsusb_vbus_shutdown(void) { int rc = 0; struct hsusb_phy_start_req { struct rpc_request_hdr hdr; } req; if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: vbus_shutdown rpc failed before call," "rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } rc = msm_rpc_call(usb_ep, usb_rpc_ids.vbus_pwr_down, &req, sizeof(req), 5 * HZ); if (rc < 0) { pr_err("%s: vbus_shutdown failed! rc = %d\n", __func__, rc); } else pr_debug("msm_hsusb_vbus_shutdown\n"); return rc; } EXPORT_SYMBOL(msm_hsusb_vbus_shutdown); int msm_hsusb_send_productID(uint32_t product_id) { int rc = 0; struct hsusb_phy_start_req { struct rpc_request_hdr hdr; uint32_t product_id; } req; if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: rpc connect failed: rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } req.product_id = cpu_to_be32(product_id); rc = msm_rpc_call(usb_ep, usb_rpc_ids.update_product_id, &req, sizeof(req), 5 * HZ); if (rc < 0) pr_err("%s: rpc call failed! error: %d\n", __func__, rc); else pr_debug("%s: rpc call success\n" , __func__); return rc; } EXPORT_SYMBOL(msm_hsusb_send_productID); int msm_hsusb_send_serial_number(const char *serial_number) { int rc = 0, serial_len, rlen; struct hsusb_send_sn_req { struct rpc_request_hdr hdr; uint32_t length; char sn[0]; } *req; if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: rpc connect failed: rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } /* * USB driver passes null terminated string to us. Modem processor * expects serial number to be 32 bit aligned. */ serial_len = strlen(serial_number)+1; rlen = sizeof(struct rpc_request_hdr) + sizeof(uint32_t) + ((serial_len + 3) & ~3); req = kmalloc(rlen, GFP_KERNEL); if (!req) return -ENOMEM; req->length = cpu_to_be32(serial_len); strncpy(req->sn , serial_number, serial_len); rc = msm_rpc_call(usb_ep, usb_rpc_ids.update_serial_num, req, rlen, 5 * HZ); if (rc < 0) pr_err("%s: rpc call failed! error: %d\n", __func__, rc); else pr_debug("%s: rpc call success\n", __func__); kfree(req); return rc; } EXPORT_SYMBOL(msm_hsusb_send_serial_number); int msm_hsusb_is_serial_num_null(uint32_t val) { int rc = 0; struct hsusb_phy_start_req { struct rpc_request_hdr hdr; uint32_t value; } req; if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: rpc connect failed: rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } if (!usb_rpc_ids.update_is_serial_num_null) { pr_err("%s: proc id not supported \n", __func__); return -ENODATA; } req.value = cpu_to_be32(val); rc = msm_rpc_call(usb_ep, usb_rpc_ids.update_is_serial_num_null, &req, sizeof(req), 5 * HZ); if (rc < 0) pr_err("%s: rpc call failed! error: %d\n" , __func__, rc); else pr_debug("%s: rpc call success\n", __func__); return rc; } EXPORT_SYMBOL(msm_hsusb_is_serial_num_null); int msm_chg_usb_charger_connected(uint32_t device) { int rc = 0; struct hsusb_start_req { struct rpc_request_hdr hdr; uint32_t otg_dev; } req; if (!chg_ep || IS_ERR(chg_ep)) return -EAGAIN; req.otg_dev = cpu_to_be32(device); rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_charger_connected_proc, &req, sizeof(req), 5 * HZ); if (rc < 0) { pr_err("%s: charger_connected failed! rc = %d\n", __func__, rc); } else pr_debug("msm_chg_usb_charger_connected\n"); return rc; } EXPORT_SYMBOL(msm_chg_usb_charger_connected); int msm_chg_usb_i_is_available(uint32_t sample) { int rc = 0; struct hsusb_start_req { struct rpc_request_hdr hdr; uint32_t i_ma; } req; if (!chg_ep || IS_ERR(chg_ep)) return -EAGAIN; req.i_ma = cpu_to_be32(sample); rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_i_is_available_proc, &req, sizeof(req), 5 * HZ); if (rc < 0) { pr_err("%s: charger_i_available failed! rc = %d\n", __func__, rc); } else pr_debug("msm_chg_usb_i_is_available(%u)\n", sample); return rc; } EXPORT_SYMBOL(msm_chg_usb_i_is_available); int msm_chg_usb_i_is_not_available(void) { int rc = 0; struct hsusb_start_req { struct rpc_request_hdr hdr; } req; if (!chg_ep || IS_ERR(chg_ep)) return -EAGAIN; rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_i_is_not_available_proc, &req, sizeof(req), 5 * HZ); if (rc < 0) { pr_err("%s: charger_i_not_available failed! rc =" "%d \n", __func__, rc); } else pr_debug("msm_chg_usb_i_is_not_available\n"); return rc; } EXPORT_SYMBOL(msm_chg_usb_i_is_not_available); int msm_chg_usb_charger_disconnected(void) { int rc = 0; struct hsusb_start_req { struct rpc_request_hdr hdr; } req; if (!chg_ep || IS_ERR(chg_ep)) return -EAGAIN; rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_charger_disconnected_proc, &req, sizeof(req), 5 * HZ); if (rc < 0) { pr_err("%s: charger_disconnected failed! rc = %d\n", __func__, rc); } else pr_debug("msm_chg_usb_charger_disconnected\n"); return rc; } EXPORT_SYMBOL(msm_chg_usb_charger_disconnected); /* rpc call to close connection */ int msm_hsusb_rpc_close(void) { int rc = 0; if (IS_ERR(usb_ep)) { pr_err("%s: rpc_close failed before call, rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } rc = msm_rpc_close(usb_ep); usb_ep = NULL; if (rc < 0) { pr_err("%s: close rpc failed! rc = %d\n", __func__, rc); return -EAGAIN; } else pr_debug("rpc close success\n"); return rc; } EXPORT_SYMBOL(msm_hsusb_rpc_close); /* rpc call to close charging connection */ int msm_chg_rpc_close(void) { int rc = 0; if (IS_ERR(chg_ep)) { pr_err("%s: rpc_close failed before call, rc = %ld\n", __func__, PTR_ERR(chg_ep)); return -EAGAIN; } rc = msm_rpc_close(chg_ep); chg_ep = NULL; if (rc < 0) { pr_err("%s: close rpc failed! rc = %d\n", __func__, rc); return -EAGAIN; } else pr_debug("rpc close success\n"); return rc; } EXPORT_SYMBOL(msm_chg_rpc_close); int msm_hsusb_reset_rework_installed(void) { int rc = 0; struct hsusb_start_req { struct rpc_request_hdr hdr; } req; struct hsusb_rpc_rep { struct rpc_reply_hdr hdr; uint32_t rework; } rep; memset(&rep, 0, sizeof(rep)); if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: hsusb rpc connection not initialized, rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } rc = msm_rpc_call_reply(usb_ep, usb_rpc_ids.reset_rework_installed, &req, sizeof(req), &rep, sizeof(rep), 5 * HZ); if (rc < 0) { pr_err("%s: rpc call failed! error: (%d)" "proc id: (%lx)\n", __func__, rc, usb_rpc_ids.reset_rework_installed); return rc; } pr_info("%s: rework: (%d)\n", __func__, rep.rework); return be32_to_cpu(rep.rework); } EXPORT_SYMBOL(msm_hsusb_reset_rework_installed); static int msm_hsusb_pmic_ulpidata0_config(int enable) { int rc = 0; struct hsusb_start_req { struct rpc_request_hdr hdr; } req; if (!usb_ep || IS_ERR(usb_ep)) { pr_err("%s: hsusb rpc connection not initialized, rc = %ld\n", __func__, PTR_ERR(usb_ep)); return -EAGAIN; } if (enable) rc = msm_rpc_call(usb_ep, usb_rpc_ids.enable_pmic_ulpi_data0, &req, sizeof(req), 5 * HZ); else rc = msm_rpc_call(usb_ep, usb_rpc_ids.disable_pmic_ulpi_data0, &req, sizeof(req), 5 * HZ); if (rc < 0) pr_err("%s: rpc call failed! error: %d\n", __func__, rc); return rc; } int msm_hsusb_enable_pmic_ulpidata0(void) { return msm_hsusb_pmic_ulpidata0_config(1); } EXPORT_SYMBOL(msm_hsusb_enable_pmic_ulpidata0); int msm_hsusb_disable_pmic_ulpidata0(void) { return msm_hsusb_pmic_ulpidata0_config(0); } EXPORT_SYMBOL(msm_hsusb_disable_pmic_ulpidata0); /* wrapper for sending pid and serial# info to bootloader */ int usb_diag_update_pid_and_serial_num(uint32_t pid, const char *snum) { int ret; ret = msm_hsusb_send_productID(pid); if (ret) return ret; if (!snum) { ret = msm_hsusb_is_serial_num_null(1); if (ret) return ret; } ret = msm_hsusb_is_serial_num_null(0); if (ret) return ret; ret = msm_hsusb_send_serial_number(snum); if (ret) return ret; return 0; }