| /* |
| * driver/../s2mm005.c - S2MM005 USB PD function driver |
| * |
| * Copyright (C) 2015 Samsung Electronics |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| #include <linux/ccic/s2mm005_ext.h> |
| #include <linux/power_supply.h> |
| #if defined(CONFIG_BATTERY_NOTIFIER) |
| #include <linux/battery/battery_notifier.h> |
| #endif |
| #if defined(CONFIG_USB_HOST_NOTIFY) |
| #include <linux/usb_notify.h> |
| #endif |
| #if defined(CONFIG_CCIC_ALTERNATE_MODE) |
| #include <linux/ccic/ccic_alternate.h> |
| #endif |
| |
| struct pdic_notifier_struct pd_noti; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // function definition |
| //////////////////////////////////////////////////////////////////////////////// |
| void select_pdo(int num); |
| void s2mm005_select_pdo(int num); |
| void select_pdo(int num); |
| void (*fp_select_pdo)(int num); |
| void vbus_turn_on_ctrl(bool enable); |
| void process_pd(void *data, u8 plug_attach_done, u8 *pdic_attach, MSG_IRQ_STATUS_Type *MSG_IRQ_State); |
| //////////////////////////////////////////////////////////////////////////////// |
| // PD function will be merged |
| //////////////////////////////////////////////////////////////////////////////// |
| static inline struct power_supply *get_power_supply_by_name(char *name) |
| { |
| if (!name) |
| return (struct power_supply *)NULL; |
| else |
| return power_supply_get_by_name(name); |
| } |
| |
| void s2mm005_select_pdo(int num) |
| { |
| uint8_t CMD_DATA[3]; |
| |
| if (pd_noti.sink_status.selected_pdo_num == num) |
| return; |
| else if (num > pd_noti.sink_status.available_pdo_num) |
| pd_noti.sink_status.selected_pdo_num = pd_noti.sink_status.available_pdo_num; |
| else if (num < 1) |
| pd_noti.sink_status.selected_pdo_num = 1; |
| else |
| pd_noti.sink_status.selected_pdo_num = num; |
| pr_info(" %s : PDO(%d) is selected to change\n", __func__, pd_noti.sink_status.selected_pdo_num); |
| |
| CMD_DATA[0] = 0x3; |
| CMD_DATA[1] = 0x3; |
| CMD_DATA[2] = pd_noti.sink_status.selected_pdo_num; |
| s2mm005_write_byte(pd_noti.pusbpd->i2c, REG_I2C_SLV_CMD, &CMD_DATA[0], 3); |
| |
| CMD_DATA[0] = 0x3; |
| CMD_DATA[1] = 0x2; |
| CMD_DATA[2] = State_PE_SNK_Wait_for_Capabilities; |
| s2mm005_write_byte(pd_noti.pusbpd->i2c, REG_I2C_SLV_CMD, &CMD_DATA[0], 3); |
| } |
| |
| void select_pdo(int num) |
| { |
| if (fp_select_pdo) |
| fp_select_pdo(num); |
| } |
| |
| void vbus_turn_on_ctrl(bool enable) |
| { |
| struct power_supply *psy_otg; |
| union power_supply_propval val; |
| int on = !!enable; |
| int ret = 0; |
| |
| #if defined(CONFIG_USB_HOST_NOTIFY) |
| struct otg_notify *o_notify = get_otg_notify(); |
| bool must_block_host = 0; |
| bool unsupport_host = 0; |
| if (o_notify) |
| must_block_host = is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST); |
| |
| pr_info("%s : enable=%d, must_block_host=%d\n", |
| __func__, enable, must_block_host); |
| |
| if (enable) { |
| if (o_notify) |
| unsupport_host = !is_usb_host(o_notify); |
| pr_info("%s : unsupport_host=%d\n", __func__, unsupport_host); |
| |
| if (must_block_host || unsupport_host) { |
| enable = false; |
| pr_info("%s : turn off vbus because of blocked host\n", |
| __func__); |
| } |
| } else { |
| // don't turn off because of blocked (already off) |
| if (must_block_host) |
| return; |
| } |
| #endif |
| |
| pr_info("%s %d, enable=%d\n", __func__, __LINE__, enable); |
| psy_otg = get_power_supply_by_name("otg"); |
| if (psy_otg) { |
| val.intval = enable; |
| ret = psy_otg->desc->set_property(psy_otg, POWER_SUPPLY_PROP_ONLINE, &val); |
| } else { |
| pr_err("%s: Fail to get psy battery\n", __func__); |
| } |
| if (ret) { |
| pr_err("%s: fail to set power_suppy ONLINE property(%d)\n", |
| __func__, ret); |
| } else { |
| pr_info("otg accessory power = %d\n", on); |
| } |
| |
| } |
| |
| static int s2mm005_src_capacity_information(const struct i2c_client *i2c, uint32_t *RX_SRC_CAPA_MSG, |
| PDIC_SINK_STATUS * pd_sink_status, uint8_t *do_power_nego) |
| { |
| uint32_t RdCnt; |
| uint32_t PDO_cnt; |
| uint32_t PDO_sel; |
| int available_pdo_num = 0; |
| int num_of_obj = 0; |
| |
| MSG_HEADER_Type *MSG_HDR; |
| SRC_FIXED_SUPPLY_Typedef *MSG_FIXED_SUPPLY; |
| SRC_VAR_SUPPLY_Typedef *MSG_VAR_SUPPLY; |
| SRC_BAT_SUPPLY_Typedef *MSG_BAT_SUPPLY; |
| |
| dev_info(&i2c->dev, "\n"); |
| for(RdCnt=0;RdCnt<8;RdCnt++) |
| { |
| dev_info(&i2c->dev, "Rd_SRC_CAPA_%d : 0x%X\n", RdCnt, RX_SRC_CAPA_MSG[RdCnt]); |
| } |
| |
| MSG_HDR = (MSG_HEADER_Type *)&RX_SRC_CAPA_MSG[0]; |
| dev_info(&i2c->dev, "=======================================\n"); |
| dev_info(&i2c->dev, " MSG Header\n"); |
| |
| dev_info(&i2c->dev, " Rsvd_msg_header : %d\n",MSG_HDR->Rsvd_msg_header ); |
| dev_info(&i2c->dev, " Number_of_obj : %d\n",MSG_HDR->Number_of_obj ); |
| dev_info(&i2c->dev, " Message_ID : %d\n",MSG_HDR->Message_ID ); |
| dev_info(&i2c->dev, " Port_Power_Role : %d\n",MSG_HDR->Port_Power_Role ); |
| dev_info(&i2c->dev, " Specification_Revision : %d\n",MSG_HDR->Specification_Revision ); |
| dev_info(&i2c->dev, " Port_Data_Role : %d\n",MSG_HDR->Port_Data_Role ); |
| dev_info(&i2c->dev, " Rsvd2_msg_header : %d\n",MSG_HDR->Rsvd2_msg_header ); |
| dev_info(&i2c->dev, " Message_Type : %d\n",MSG_HDR->Message_Type ); |
| |
| num_of_obj = MSG_HDR->Number_of_obj > MAX_PDO_NUM ? MAX_PDO_NUM : MSG_HDR->Number_of_obj; |
| for(PDO_cnt = 0;PDO_cnt < num_of_obj;PDO_cnt++) |
| { |
| PDO_sel = (RX_SRC_CAPA_MSG[PDO_cnt + 1] >> 30) & 0x3; |
| dev_info(&i2c->dev, " =================\n"); |
| dev_info(&i2c->dev, " PDO_Num : %d\n", (PDO_cnt + 1)); |
| |
| if(PDO_sel == 0) // *MSG_FIXED_SUPPLY |
| { |
| MSG_FIXED_SUPPLY = (SRC_FIXED_SUPPLY_Typedef *)&RX_SRC_CAPA_MSG[PDO_cnt + 1]; |
| if(MSG_FIXED_SUPPLY->Voltage_Unit <= (AVAILABLE_VOLTAGE/UNIT_FOR_VOLTAGE)) |
| available_pdo_num = PDO_cnt + 1; |
| if (!(*do_power_nego) && |
| (pd_sink_status->power_list[PDO_cnt+1].max_voltage != MSG_FIXED_SUPPLY->Voltage_Unit * UNIT_FOR_VOLTAGE || |
| pd_sink_status->power_list[PDO_cnt+1].max_current != MSG_FIXED_SUPPLY->Maximum_Current * UNIT_FOR_CURRENT)) |
| *do_power_nego = 1; |
| pd_sink_status->power_list[PDO_cnt+1].max_voltage = MSG_FIXED_SUPPLY->Voltage_Unit * UNIT_FOR_VOLTAGE; |
| pd_sink_status->power_list[PDO_cnt+1].max_current = MSG_FIXED_SUPPLY->Maximum_Current * UNIT_FOR_CURRENT; |
| |
| dev_info(&i2c->dev, " PDO_Parameter(FIXED_SUPPLY) : %d\n",MSG_FIXED_SUPPLY->PDO_Parameter ); |
| dev_info(&i2c->dev, " Dual_Role_Power : %d\n",MSG_FIXED_SUPPLY->Dual_Role_Power ); |
| dev_info(&i2c->dev, " USB_Suspend_Support : %d\n",MSG_FIXED_SUPPLY->USB_Suspend_Support ); |
| dev_info(&i2c->dev, " Externally_POW : %d\n",MSG_FIXED_SUPPLY->Externally_POW ); |
| dev_info(&i2c->dev, " USB_Comm_Capable : %d\n",MSG_FIXED_SUPPLY->USB_Comm_Capable ); |
| dev_info(&i2c->dev, " Data_Role_Swap : %d\n",MSG_FIXED_SUPPLY->Data_Role_Swap ); |
| dev_info(&i2c->dev, " Reserved : %d\n",MSG_FIXED_SUPPLY->Reserved ); |
| dev_info(&i2c->dev, " Peak_Current : %d\n",MSG_FIXED_SUPPLY->Peak_Current ); |
| dev_info(&i2c->dev, " Voltage_Unit : %d\n",MSG_FIXED_SUPPLY->Voltage_Unit ); |
| dev_info(&i2c->dev, " Maximum_Current : %d\n",MSG_FIXED_SUPPLY->Maximum_Current ); |
| } |
| else if(PDO_sel == 2) // *MSG_VAR_SUPPLY |
| { |
| MSG_VAR_SUPPLY = (SRC_VAR_SUPPLY_Typedef *)&RX_SRC_CAPA_MSG[PDO_cnt + 1]; |
| |
| dev_info(&i2c->dev, " PDO_Parameter(VAR_SUPPLY) : %d\n",MSG_VAR_SUPPLY->PDO_Parameter ); |
| dev_info(&i2c->dev, " Maximum_Voltage : %d\n",MSG_VAR_SUPPLY->Maximum_Voltage ); |
| dev_info(&i2c->dev, " Minimum_Voltage : %d\n",MSG_VAR_SUPPLY->Minimum_Voltage ); |
| dev_info(&i2c->dev, " Maximum_Current : %d\n",MSG_VAR_SUPPLY->Maximum_Current ); |
| } |
| else if(PDO_sel == 1) // *MSG_BAT_SUPPLY |
| { |
| MSG_BAT_SUPPLY = (SRC_BAT_SUPPLY_Typedef *)&RX_SRC_CAPA_MSG[PDO_cnt + 1]; |
| |
| dev_info(&i2c->dev, " PDO_Parameter(BAT_SUPPLY) : %d\n",MSG_BAT_SUPPLY->PDO_Parameter ); |
| dev_info(&i2c->dev, " Maximum_Voltage : %d\n",MSG_BAT_SUPPLY->Maximum_Voltage ); |
| dev_info(&i2c->dev, " Minimum_Voltage : %d\n",MSG_BAT_SUPPLY->Minimum_Voltage ); |
| dev_info(&i2c->dev, " Maximum_Allow_Power : %d\n",MSG_BAT_SUPPLY->Maximum_Allow_Power ); |
| } |
| |
| } |
| |
| /* the number of available pdo list */ |
| pd_sink_status->available_pdo_num = available_pdo_num; |
| dev_info(&i2c->dev, "=======================================\n"); |
| return available_pdo_num; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Processing message role |
| //////////////////////////////////////////////////////////////////////////////// |
| void process_message_role(void *data) |
| { |
| struct s2mm005_data *usbpd_data = data; |
| struct otg_notify *o_notify = get_otg_notify(); |
| int is_dfp = 0; |
| int is_src = 0; |
| |
| // 1. read pd state |
| is_dfp = usbpd_data->func_state & (0x1 << 26) ? 1 : 0; |
| is_src = usbpd_data->func_state & (0x1 << 25) ? 1 : 0; |
| pr_info("%s func_state :0x%X, is_dfp : %d, is_src : %d\n", __func__, |
| usbpd_data->func_state, is_dfp, is_src); |
| #if defined(CONFIG_TYPEC) |
| pr_info("%s current port data_role : %d, power_role : %d\n", __func__, |
| usbpd_data->typec_data_role, usbpd_data->typec_power_role); |
| |
| if (is_src == usbpd_data->typec_power_role) { |
| pr_info("%s skip. already power role is set.\n", __func__); |
| return; |
| } |
| #endif |
| // 2. process power role |
| if (usbpd_data->pd_state != State_PE_PRS_SNK_SRC_Source_on) { |
| pr_info("%s pd_state is not PE_PRS_SNK_SRC_Source_on\n", __func__); |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| if (is_src && (usbpd_data->power_role == DUAL_ROLE_PROP_PR_SNK)) { |
| ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_BATTERY, |
| CCIC_NOTIFY_ID_ATTACH, 0, 0, 0); |
| } |
| #elif defined (CONFIG_TYPEC) |
| if (is_src && (usbpd_data->typec_power_role == TYPEC_SINK)) { |
| pd_noti.event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC; |
| pd_noti.sink_status.selected_pdo_num = 0; |
| pd_noti.sink_status.available_pdo_num = 0; |
| pd_noti.sink_status.current_pdo_num = 0; |
| ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_BATTERY, |
| CCIC_NOTIFY_ID_POWER_STATUS, 0, 0, 0); |
| } |
| #endif |
| } |
| |
| #if defined(CONFIG_USB_HOST_NOTIFY) |
| if (is_src) |
| send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 1); |
| else |
| send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); |
| #endif |
| vbus_turn_on_ctrl(is_src); |
| |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| usbpd_data->power_role = is_src ? |
| DUAL_ROLE_PROP_PR_SRC : DUAL_ROLE_PROP_PR_SNK; |
| ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_PDIC, |
| CCIC_NOTIFY_ID_ROLE_SWAP, 0, 0, 0); |
| #elif defined (CONFIG_TYPEC) |
| usbpd_data->typec_power_role = is_src ? TYPEC_SOURCE : TYPEC_SINK; |
| typec_set_pwr_role(usbpd_data->port, usbpd_data->typec_power_role); |
| |
| if (usbpd_data->typec_try_state_change == TRY_ROLE_SWAP_PR) { |
| pr_info("%s : power role is changed %s\n", |
| __func__, is_src ? "SOURCE" : "SINK"); |
| usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; |
| complete(&usbpd_data->typec_reverse_completion); |
| pr_info("%s typec_reverse_completion!\n", __func__); |
| } |
| #endif |
| } |
| |
| void process_pd(void *data, u8 plug_attach_done, u8 *pdic_attach, MSG_IRQ_STATUS_Type *MSG_IRQ_State) |
| { |
| struct s2mm005_data *usbpd_data = data; |
| struct i2c_client *i2c = usbpd_data->i2c; |
| uint16_t REG_ADD; |
| uint8_t rp_currentlvl, is_src, i; |
| REQUEST_FIXED_SUPPLY_STRUCT_Typedef *request_power_number; |
| |
| pr_info("%s\n",__func__); |
| rp_currentlvl = ((usbpd_data->func_state >> 27) & 0x3); |
| is_src = (usbpd_data->func_state & (0x1 << 25) ? 1 : 0); |
| dev_info(&i2c->dev, "rp_currentlvl:0x%02X, is_source:0x%02X\n", rp_currentlvl, is_src); |
| |
| if (MSG_IRQ_State->BITS.Ctrl_Flag_PR_Swap) |
| { |
| usbpd_data->is_pr_swap++; |
| dev_info(&i2c->dev, "PR_Swap requested to %s, receive\n", is_src ? "SOURCE" : "SINK"); |
| process_message_role(usbpd_data); |
| } |
| #if defined(CONFIG_TYPEC) |
| else if (usbpd_data->typec_try_state_change == TRY_ROLE_SWAP_PR) { |
| dev_info(&i2c->dev, "PR_Swap requested to %s, send\n", is_src ? "SOURCE" : "SINK"); |
| process_message_role(usbpd_data); |
| } |
| #endif |
| else ; |
| |
| if (MSG_IRQ_State->BITS.Data_Flag_SRC_Capability) |
| { |
| uint8_t ReadMSG[32]; |
| int available_pdo_num; |
| uint8_t do_power_nego = 0; |
| pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; |
| |
| REG_ADD = REG_RX_SRC_CAPA_MSG; |
| s2mm005_read_byte(i2c, REG_ADD, ReadMSG, 32); |
| available_pdo_num = s2mm005_src_capacity_information(i2c, (uint32_t *)ReadMSG, &pd_noti.sink_status, &do_power_nego); |
| |
| REG_ADD = REG_TX_REQUEST_MSG; |
| s2mm005_read_byte(i2c, REG_ADD, ReadMSG, 32); |
| request_power_number = (REQUEST_FIXED_SUPPLY_STRUCT_Typedef *)&ReadMSG[4]; |
| |
| pr_info(" %s : Object_posision(%d), available_pdo_num(%d), selected_pdo_num(%d) \n", __func__, |
| request_power_number->Object_Position, available_pdo_num, pd_noti.sink_status.selected_pdo_num); |
| pd_noti.sink_status.current_pdo_num = request_power_number->Object_Position; |
| |
| if(available_pdo_num > 0) |
| { |
| if(request_power_number->Object_Position != pd_noti.sink_status.selected_pdo_num) |
| { |
| if (pd_noti.sink_status.selected_pdo_num == 0) |
| { |
| pr_info(" %s : PDO is not selected yet by default\n", __func__); |
| pd_noti.sink_status.selected_pdo_num = pd_noti.sink_status.current_pdo_num; |
| } |
| } else { |
| if (do_power_nego) { |
| pr_info(" %s : PDO(%d) is selected, but power negotiation is requested\n", |
| __func__, pd_noti.sink_status.selected_pdo_num); |
| pd_noti.sink_status.selected_pdo_num = 0; |
| pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK_CAP; |
| } else { |
| pr_info(" %s : PDO(%d) is selected, but same with previous list, so skip\n", |
| __func__, pd_noti.sink_status.selected_pdo_num); |
| } |
| } |
| *pdic_attach = 1; |
| } else { |
| pr_info(" %s : PDO is not selected\n", __func__); |
| } |
| } |
| |
| if (MSG_IRQ_State->BITS.Ctrl_Flag_Get_Sink_Cap) |
| { |
| pr_info(" %s : SRC requested SINK Cap\n", __func__); |
| } |
| |
| /* notify to battery */ |
| #ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER |
| if (plug_attach_done) { |
| if (*pdic_attach) { |
| /* PD charger is detected by PDIC */ |
| } else if (!is_src && (usbpd_data->pd_state == State_PE_SNK_Wait_for_Capabilities || |
| usbpd_data->pd_state == State_ErrorRecovery) && |
| rp_currentlvl != pd_noti.sink_status.rp_currentlvl && |
| rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT) { |
| if (rp_currentlvl == RP_CURRENT_LEVEL3) { |
| /* 5V/3A RP charger is detected by CCIC */ |
| pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL3; |
| pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH; |
| } else if (rp_currentlvl == RP_CURRENT_LEVEL2) { |
| /* 5V/1.5A RP charger is detected by CCIC */ |
| pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL2; |
| pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH; |
| } else if (rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT) { |
| /* 5V/0.5A RP charger is detected by CCIC */ |
| pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_DEFAULT; |
| pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH; |
| } else |
| return; |
| } else |
| return; |
| #ifdef CONFIG_SEC_FACTORY |
| pr_info(" %s : debug pdic_attach(%d) event(%d)\n", __func__, *pdic_attach, pd_noti.event); |
| #endif |
| ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_BATTERY, CCIC_NOTIFY_ID_POWER_STATUS, *pdic_attach, 0, 0); |
| } else { |
| for (i = 0; i < MAX_PDO_NUM + 1; i++) { |
| pd_noti.sink_status.power_list[i].max_current = 0; |
| pd_noti.sink_status.power_list[i].max_voltage = 0; |
| } |
| pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; |
| pd_noti.sink_status.available_pdo_num = 0; |
| pd_noti.sink_status.selected_pdo_num = 0; |
| pd_noti.sink_status.current_pdo_num = 0; |
| pd_noti.event = PDIC_NOTIFY_EVENT_DETACH; |
| } |
| #else |
| if(plug_attach_done) |
| { |
| /* PD notify */ |
| if(*pdic_attach) |
| pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; |
| else |
| pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH; |
| } |
| else |
| { |
| pd_noti.sink_status.selected_pdo_num = 0; |
| pd_noti.event = PDIC_NOTIFY_EVENT_DETACH; |
| } |
| pdic_notifier_call(pd_noti); |
| #endif |
| } |