| /* |
| * driver/../s2mm003.c - S2MM003 USBPD device 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/s2mm003.h> |
| #include <linux/power_supply.h> |
| #include <linux/battery/sec_charger.h> |
| #if defined(CONFIG_BATTERY_NOTIFIER) |
| #include <linux/battery/battery_notifier.h> |
| #endif |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| #include <linux/usb/class-dual-role.h> |
| #endif |
| |
| /* switch device header */ |
| #ifdef CONFIG_SWITCH |
| #include <linux/switch.h> |
| #endif /* CONFIG_SWITCH */ |
| |
| #ifdef CONFIG_SWITCH |
| static struct switch_dev switch_dock = { |
| .name = "ccic_dock", |
| }; |
| #endif /* CONFIG_SWITCH */ |
| |
| /* CCIC Dock Observer Callback parameter */ |
| enum { |
| CCIC_DOCK_DETACHED = 0, |
| CCIC_DOCK_HMT = 105, |
| CCIC_DOCK_ABNORMAL = 106, |
| }; |
| |
| extern struct device *ccic_device; |
| |
| static void ccic_send_dock_intent(int type) |
| { |
| pr_info("%s: CCIC dock type(%d)\n", __func__, type); |
| #ifdef CONFIG_SWITCH |
| switch_set_state(&switch_dock, type); |
| #endif |
| } |
| |
| static int ccic_dock_attach_notify(int type, const char *name) |
| { |
| pr_info("%s: %s\n", __func__, name); |
| ccic_send_dock_intent(type); |
| return NOTIFY_OK; |
| } |
| |
| static int ccic_dock_detach_notify(void) |
| { |
| pr_info("%s\n", __func__); |
| ccic_send_dock_intent(CCIC_DOCK_DETACHED); |
| return NOTIFY_OK; |
| } |
| |
| static int s2mm003_read_byte(const struct i2c_client *i2c, u8 reg, u8 *val) |
| { |
| int ret; u8 wbuf; |
| struct i2c_msg msg[2]; |
| struct s2mm003_data *usbpd_data = i2c_get_clientdata(i2c); |
| |
| mutex_lock(&usbpd_data->i2c_mutex); |
| msg[0].addr = i2c->addr; |
| msg[0].flags = i2c->flags; |
| msg[0].len = 1; |
| msg[0].buf = &wbuf; |
| msg[1].addr = i2c->addr; |
| msg[1].flags = I2C_M_RD; |
| msg[1].len = 2; |
| msg[1].buf = val; |
| |
| wbuf = (reg & 0xFF); |
| |
| ret = i2c_transfer(i2c->adapter, msg, 2); |
| if (ret < 0) |
| dev_err(&i2c->dev, "i2c reading fail reg(0x%x), error %d\n", |
| reg, ret); |
| mutex_unlock(&usbpd_data->i2c_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mm003_read_byte_16(const struct i2c_client *i2c, u16 reg, u8 *val) |
| { |
| int ret; u8 wbuf[2], rbuf; |
| struct i2c_msg msg[2]; |
| struct s2mm003_data *usbpd_data = i2c_get_clientdata(i2c); |
| |
| mutex_lock(&usbpd_data->i2c_mutex); |
| msg[0].addr = 0x43; |
| msg[0].flags = i2c->flags; |
| msg[0].len = 2; |
| msg[0].buf = wbuf; |
| msg[1].addr = 0x43; |
| msg[1].flags = I2C_M_RD; |
| msg[1].len = 1; |
| msg[1].buf = &rbuf; |
| |
| wbuf[0] = (reg & 0xFF00) >> 8; |
| wbuf[1] = (reg & 0xFF); |
| |
| ret = i2c_transfer(i2c->adapter, msg, 2); |
| if (ret < 0) |
| dev_err(&i2c->dev, "i2c read16 fail reg(0x%x), error %d\n", |
| reg, ret); |
| mutex_unlock(&usbpd_data->i2c_mutex); |
| |
| *val = rbuf; |
| return rbuf; |
| } |
| |
| static int s2mm003_write_byte(const struct i2c_client *i2c, u8 reg, u8 val) |
| { |
| int ret = 0; u8 wbuf[2]; |
| struct i2c_msg msg[1]; |
| struct s2mm003_data *usbpd_data = i2c_get_clientdata(i2c); |
| |
| mutex_lock(&usbpd_data->i2c_mutex); |
| msg[0].addr = i2c->addr; |
| msg[0].flags = 0; |
| msg[0].len = 2; |
| msg[0].buf = wbuf; |
| |
| wbuf[0] = (reg & 0xFF); |
| wbuf[1] = (val & 0xFF); |
| |
| ret = i2c_transfer(i2c->adapter, msg, 1); |
| if (ret < 0) |
| dev_err(&i2c->dev, "i2c write fail reg(0x%x:%x), error %d\n", |
| reg, val, ret); |
| mutex_unlock(&usbpd_data->i2c_mutex); |
| |
| return ret; |
| } |
| |
| static int s2mm003_write_byte_16(const struct i2c_client *i2c, u16 reg, u8 val) |
| { |
| int ret = 0; u8 wbuf[3]; |
| struct i2c_msg msg[1]; |
| struct s2mm003_data *usbpd_data = i2c_get_clientdata(i2c); |
| |
| mutex_lock(&usbpd_data->i2c_mutex); |
| msg[0].addr = 0x43; |
| msg[0].flags = 0; |
| msg[0].len = 3; |
| msg[0].buf = wbuf; |
| |
| wbuf[0] = (reg & 0xFF00) >> 8; |
| wbuf[1] = (reg & 0xFF); |
| wbuf[2] = (val & 0xFF); |
| |
| ret = i2c_transfer(i2c->adapter, msg, 1); |
| if (ret < 0) |
| dev_err(&i2c->dev, "i2c write fail reg(0x%x:%x), error %d\n", |
| reg, val, ret); |
| mutex_unlock(&usbpd_data->i2c_mutex); |
| |
| return ret; |
| } |
| |
| static void s2mm003_int_clear(struct s2mm003_data *usbpd_data) |
| { |
| struct i2c_client *i2c = usbpd_data->i2c; |
| |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x1); |
| s2mm003_write_byte(i2c, IRQ_RD_ADDR, 0xFF); |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x0); |
| |
| } |
| |
| static int s2mm003_indirect_read(struct s2mm003_data *usbpd_data, u8 address) |
| { |
| u8 value = 0; |
| int ret = 0; |
| struct i2c_client *i2c = usbpd_data->i2c; |
| |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x1); |
| s2mm003_write_byte(i2c, IRQ_RD_ADDR, address); |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x0); |
| |
| ret = s2mm003_read_byte(i2c, IRQ_RD_DATA, &value); |
| if (ret < 0) |
| dev_err(&i2c->dev, "indirect read error value:%02x ret:%d\n", value, ret); |
| |
| return value; |
| } |
| |
| static void s2mm003_indirect_write(struct s2mm003_data *usbpd_data, u8 address, u8 val) |
| { |
| struct i2c_client *i2c = usbpd_data->i2c; |
| |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x1); |
| s2mm003_write_byte(i2c, IRQ_WR_ADDR, address); |
| s2mm003_write_byte(i2c, IRQ_WR_DATA, val); |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x0); |
| } |
| |
| static int s2mm003_src_capacity_information(const struct i2c_client *i2c, uint32_t *RX_SRC_CAPA_MSG, |
| PDIC_SINK_STATUS * pd_sink_status) |
| { |
| uint32_t RdCnt; |
| uint32_t PDO_cnt; |
| uint32_t PDO_sel; |
| int available_pdo_num = 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; |
| |
| 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 ); |
| |
| for(PDO_cnt = 0;PDO_cnt < MSG_HDR->Number_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; |
| 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\r"); |
| dev_info(&i2c->dev, "\n\r"); |
| return available_pdo_num; |
| } |
| #if 0 |
| static void s2mm003_request_select_type(uint32_t * REQ_MSG , int num) |
| { |
| REQUEST_FIXED_SUPPLY_STRUCT_Typedef *REQ_FIXED_SPL; |
| |
| REQ_FIXED_SPL = (REQUEST_FIXED_SUPPLY_STRUCT_Typedef *)REQ_MSG; |
| |
| //REQ_FIXED_SPL->Reserved_2 = 0; |
| REQ_FIXED_SPL->Object_Position = (num & 0x7); |
| //REQ_FIXED_SPL->GiveBack_Flag = 0; // GiveBack Support set to 1 |
| //REQ_FIXED_SPL->Capa_Mismatch = 0; |
| //REQ_FIXED_SPL->USB_Comm_Capable = 0; |
| //REQ_FIXED_SPL->No_USB_Suspend = 0; |
| //REQ_FIXED_SPL->Reserved_1 = 0; // Set to Zero |
| REQ_FIXED_SPL->OP_Current = 200; // 10mA |
| REQ_FIXED_SPL->Maximum_OP_Current = 200; // 10mA |
| } |
| #endif |
| static void Modify_TX_SRC_CAPA(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_SRC_FIXED_SUPPLY_Typedef *MSG_TX_SRC_FIXED_SUPPLY; |
| uint8_t data_arr[8]={0,}; |
| |
| // Set Message Index - TX Source Capability |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_TX_SRC_CAPA); |
| |
| // Read Default TX source Capability message - Size 8 Byte |
| for(i=0; i<8; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| |
| // Link TX Source Capability Data struct |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| MSG_TX_SRC_FIXED_SUPPLY = (U_SRC_FIXED_SUPPLY_Typedef *)&data_arr[4]; |
| |
| // ===== For Debug Print |
| printk(KERN_ERR "MSG_TX_SRC_FIXED_SUPPLY->BITS.Maximum_Current) : %d\n", MSG_TX_SRC_FIXED_SUPPLY->BITS.Maximum_Current); |
| // ===================== |
| |
| // ===== For Modified Data Field |
| MSG_TX_SRC_FIXED_SUPPLY->BITS.Maximum_Current = 50; // 10mA unit * 50 (500mA) |
| // ===================== |
| |
| // Write TX source Capability message - Size 8 Byte |
| for(i=0; i<8; i++) |
| s2mm003_indirect_write(usbpd_data, (i+0x50), data_arr[i]); |
| |
| return; |
| } |
| |
| static void VBUS_TURN_ON_CTRL(bool enable) |
| { |
| union power_supply_propval val; |
| printk(KERN_ERR "%s -> enable : %d\n", __func__, enable); |
| val.intval = enable; |
| psy_do_property("otg", set, |
| POWER_SUPPLY_PROP_ONLINE, val); |
| } |
| |
| static void PDIC_Function_State_for_VBUS(u8 val) |
| { |
| switch(val) |
| { |
| case 0: // PE_Initial_Detach |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| // case 1: // PE_SRC_Startup |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| // case 2: // PE_SRC_Discovery |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| case 3: // PE_SRC_Send_Capabilities |
| VBUS_TURN_ON_CTRL(1); |
| break; |
| case 4: // PE_SRC_Negotiate_Capability |
| VBUS_TURN_ON_CTRL(1); |
| break; |
| case 5: // PE_SRC_Transition_Supply |
| VBUS_TURN_ON_CTRL(1); |
| break; |
| case 6: // PE_SRC_Ready |
| VBUS_TURN_ON_CTRL(1); |
| break; |
| case 7: // PE_SRC_Disabled |
| VBUS_TURN_ON_CTRL(1); |
| break; |
| // case 8: // PE_SRC_Capability_Response |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| // case 9: // PE_SRC_Hard_Reset |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| // case 10: // PE_SRC_Hard_Reset_Received |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| // case 11: // PE_SRC_Transition_to_default |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| // case 12: // PE_SRC_Give_Source_Cap |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| // case 13: // PE_SRC_Get_Sink_Cap |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| // case 14: // PE_SRC_Wait_New_Capabilities |
| // VBUS_TURN_ON_CTRL(VBUS_ON); |
| // break; |
| |
| // case 15: // PE_SNK_Startup |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| // case 16: // PE_SNK_Discovery |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| case 17: // PE_SNK_Wait_for_Capabilities |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| case 18: // PE_SNK_Evaluate_Capability |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| case 19: // PE_SNK_Select_Capability |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| case 20: // PE_SNK_Transition_Sink |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| case 21: // PE_SNK_Ready |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| // case 22: // PE_SNK_Hard_Reset |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| // case 23: // PE_SNK_Transition_to_default |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| // case 24: // PE_SNK_Give_Sink_Cap |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| // case 25: // PE_SNK_Get_Source_Cap |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| // case 26: // PE_SRC_CABLE_VDM_Identity_Request |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| // case 27: // PE_SRC_CABLE_VDM_Identity_ACKed |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| // case 28: // PE_SRC_CABLE_VDM_Identity_NAKed |
| // VBUS_TURN_ON_CTRL(VBUS_OFF); |
| // break; |
| case 29: // ErrorRecovery |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| default: |
| VBUS_TURN_ON_CTRL(0); |
| break; |
| } |
| |
| |
| return; |
| } |
| |
| static struct pdic_notifier_struct pd_noti; |
| |
| |
| static void send_usb_notify_message(struct s2mm003_data *usbpd_data, u8 mode) |
| { |
| CC_NOTI_USB_STATUS_TYPEDEF usb_status_notifier; |
| usb_status_notifier.id = CCIC_NOTIFY_ID_USB; |
| usb_status_notifier.src = CCIC_NOTIFY_DEV_CCIC; |
| usb_status_notifier.dest = CCIC_NOTIFY_DEV_USB; |
| if(mode == USB_STATUS_NOTIFY_DETACH) |
| usb_status_notifier.attach = CCIC_NOTIFY_DETACH; |
| else |
| usb_status_notifier.attach = CCIC_NOTIFY_ATTACH; |
| usb_status_notifier.drp = mode; |
| usbpd_data->Pdic_usb_state = mode; |
| ccic_notifier_notify((CC_NOTI_TYPEDEF*)&usb_status_notifier, NULL, 0); |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| if (usbpd_data->try_state_change && (usbpd_data->Pdic_usb_state != USB_STATUS_NOTIFY_DETACH)) |
| { |
| // Role change try and new mode detected |
| complete(&usbpd_data->reverse_completion); |
| } |
| dual_role_instance_changed(usbpd_data->dual_role); |
| #endif |
| } |
| |
| /* for alternate mode */ |
| #if defined(CONFIG_CCIC_ALTERNATE_MODE) |
| static void Msg_Read_Discover_Identity(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| U_DATA_MSG_ID_HEADER_Type *DATA_MSG_ID; |
| U_CERT_STAT_VDO_Type *DATA_MSG_CERT; |
| U_PRODUCT_VDO_Type *DATA_MSG_PRODUCT; |
| uint8_t data_arr[32]={0,}; |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| DATA_MSG_ID = (U_DATA_MSG_ID_HEADER_Type *)&data_arr[8]; |
| DATA_MSG_CERT = (U_CERT_STAT_VDO_Type *)&data_arr[12]; |
| DATA_MSG_PRODUCT = (U_PRODUCT_VDO_Type *)&data_arr[16]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_RX_DISC_ID_RESP); |
| for(i=0; i<32; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| usbpd_data->Vendor_ID = DATA_MSG_ID->BITS.USB_Vendor_ID; |
| usbpd_data->Product_ID = DATA_MSG_PRODUCT->BITS.Product_ID; |
| printk(KERN_ERR "[%s] Vendor_ID : 0x%X, Product_ID : 0x%X\n", __func__, usbpd_data->Vendor_ID, usbpd_data->Product_ID); |
| if(usbpd_data->Vendor_ID ==0x04E8 && usbpd_data->Product_ID==0xA500) // Gear VR VID, PID |
| usbpd_data->acc_type = CCIC_DOCK_HMT; |
| if( (DATA_MSG_ID->BITS.Data_Capable_USB_Host == 0x01) |
| && (DATA_MSG_ID->BITS.Data_Capable_USB_Device == 0x00) |
| && (DATA_MSG_ID->BITS.Product_Type == 0x02) |
| ){ |
| printk(KERN_ERR "[%s] Working with USB Host Device.\n", __func__); |
| s2mm003_indirect_write(usbpd_data, 0x4C, 0x10); // Detach and re-attach |
| VBUS_TURN_ON_CTRL(0); |
| usbpd_data->Pdic_state_machine = b_Cmd_Intial_State; |
| usbpd_data->func_state = FUNCTION_STATUS_INITIAL_DETACH; |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_DETACH); |
| } |
| else |
| usbpd_data->Pdic_state_machine = b_Cmd_Discover_Identity; |
| } |
| |
| static void Msg_Read_Discover_SVIDs(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| U_VDO1_Type *DATA_MSG_VDO1; |
| uint8_t data_arr[32]={0,}; |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| DATA_MSG_VDO1 = (U_VDO1_Type *)&data_arr[8]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_RX_DISC_SVIDs_RESP); |
| for(i=0; i<32; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| usbpd_data->SVID_0 = DATA_MSG_VDO1->BITS.SVID_0; |
| usbpd_data->SVID_1 = DATA_MSG_VDO1->BITS.SVID_1; |
| printk(KERN_ERR "[%s] SVID_0 : 0x%X, SVID_1 : 0x%X\n", __func__, usbpd_data->SVID_0, usbpd_data->SVID_1); |
| usbpd_data->Pdic_state_machine = b_Cmd_Discover_SVIDs; |
| } |
| |
| static void Msg_Read_Discover_Modes(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| uint8_t data_arr[32]={0,}; |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_RX_DISC_MODE_RESP); |
| for(i=0; i<32; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| printk(KERN_ERR "[%s]\n", __func__); |
| usbpd_data->Pdic_state_machine = b_Cmd_Discover_Modes; |
| } |
| |
| static void Msg_Read_Enter_Mode(struct s2mm003_data *usbpd_data) |
| { |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| int i; |
| uint8_t data_arr[32]={0,}; |
| const char name[11] = "HMT Attach"; |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_RX_DISC_ENTER_MODE_RESP); |
| for(i=0; i<32; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| if(DATA_MSG_VDM->BITS.VDM_command_type == 1) |
| printk(KERN_ERR "[%s] --> EnterMode Ack\n", __func__); |
| else |
| printk(KERN_ERR "[%s] --> EnterMode Nak\n", __func__); |
| usbpd_data->Pdic_state_machine = b_Cmd_Enter_Mode; |
| if (usbpd_data->acc_type != CCIC_DOCK_DETACHED) |
| ccic_dock_attach_notify(usbpd_data->acc_type, name); |
| } |
| |
| static void Msg_Send_Discover_Idendity(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i=0; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| uint8_t data_arr[32]={0,}; |
| |
| printk(KERN_ERR "[%s]\n", __func__); |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_VDM_MSG_REQUEST); |
| for(i=0; i<8; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| MSG_HEADER->BITS.Number_of_obj = 1; |
| DATA_MSG_VDM->BITS.Standard_Vendor_ID = 0xFF00; |
| DATA_MSG_VDM->BITS.Object_Position = 0; |
| for(i = 0; i < 8; i++) |
| s2mm003_indirect_write(usbpd_data, i+0x50, data_arr[i]); |
| s2mm003_indirect_write(usbpd_data, 0x4E, 1); |
| } |
| |
| static void Msg_Send_Discover_SVIDs(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| uint8_t data_arr[32]={0,}; |
| |
| printk(KERN_ERR "[%s]\n", __func__); |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_VDM_MSG_REQUEST); |
| for(i=0; i<8; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| MSG_HEADER->BITS.Number_of_obj = 1; |
| DATA_MSG_VDM->BITS.Standard_Vendor_ID = 0xFF00; |
| DATA_MSG_VDM->BITS.Object_Position = 0; |
| for(i=0; i<8; i++) |
| s2mm003_indirect_write(usbpd_data, i+0x50, data_arr[i]); |
| s2mm003_indirect_write(usbpd_data, 0x4E, 2); |
| } |
| |
| static void Msg_Send_Discover_Modes(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| uint8_t data_arr[32]={0,}; |
| |
| printk(KERN_ERR "[%s]\n", __func__); |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_VDM_MSG_REQUEST); |
| for(i=0; i<8; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| MSG_HEADER->BITS.Number_of_obj = 1; |
| DATA_MSG_VDM->BITS.Standard_Vendor_ID = usbpd_data->SVID_1; |
| DATA_MSG_VDM->BITS.Object_Position = 0; |
| for(i=0; i<8; i++) |
| s2mm003_indirect_write(usbpd_data, i+0x50, data_arr[i]); |
| s2mm003_indirect_write(usbpd_data, 0x4E, 3); |
| } |
| |
| static void Msg_Send_Enter_Mode(struct s2mm003_data *usbpd_data) |
| { |
| uint32_t i; |
| U_MSG_HEADER_Type *MSG_HEADER; |
| U_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM; |
| uint8_t data_arr[32]={0,}; |
| |
| printk(KERN_ERR "[%s]\n", __func__); |
| |
| MSG_HEADER = (U_MSG_HEADER_Type *)&data_arr[0]; |
| DATA_MSG_VDM = (U_DATA_MSG_VDM_HEADER_Type *)&data_arr[4]; |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_VDM_MSG_REQUEST); |
| for(i=0; i<8; i++) |
| data_arr[i] = s2mm003_indirect_read(usbpd_data, i+0x50); |
| MSG_HEADER->BITS.Number_of_obj = 1; |
| DATA_MSG_VDM->BITS.Standard_Vendor_ID = usbpd_data->SVID_1; |
| DATA_MSG_VDM->BITS.Object_Position = 1; |
| for(i=0; i<8; i++) |
| s2mm003_indirect_write(usbpd_data, i+0x50, data_arr[i]); |
| s2mm003_indirect_write(usbpd_data, 0x4E, 4); |
| } |
| #endif |
| |
| static irqreturn_t s2mm003_usbpd_irq_thread(int irq, void *data) |
| { |
| struct s2mm003_data *usbpd_data = data; |
| struct i2c_client *i2c = usbpd_data->i2c; |
| unsigned char rid, plug_state_monitor; |
| int cc1_valid, cc2_valid, plug_attach_done; |
| int value, usbpd_state; |
| int pdic_attach = 0; |
| int is_dr_swap = 0; |
| static CC_NOTI_ATTACH_TYPEDEF attach_notifier; |
| static CC_NOTI_RID_TYPEDEF rid_notifier; |
| REQUEST_FIXED_SUPPLY_STRUCT_Typedef *request_power_number; |
| |
| dev_err(&i2c->dev, "%d times\n", ++usbpd_data->wq_times); |
| |
| usbpd_state = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_PD_FUNC_STATE); |
| dev_info(&i2c->dev, "USBPD_STATE I2C read: 0x%02d\n", usbpd_state); |
| plug_state_monitor = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_PLUG_STATE); |
| cc1_valid = S2MM003_REG_MASK(plug_state_monitor, S_CC1_VALID); |
| cc2_valid = S2MM003_REG_MASK(plug_state_monitor, S_CC2_VALID); |
| usbpd_data->plug_rprd_sel = S2MM003_REG_MASK(plug_state_monitor, PLUG_RPRD_SEL_MONITOR); |
| plug_attach_done = S2MM003_REG_MASK(plug_state_monitor, PLUG_ATTACH_DONE); |
| dev_info(&i2c->dev, "PLUG_STATE_MONITOR I2C read:%x\n" |
| "CC1:%x CC2:%x rprd:%x attach:%x\n", |
| plug_state_monitor, |
| cc1_valid, cc2_valid, usbpd_data->plug_rprd_sel, plug_attach_done); |
| |
| /* To confirm whether PD charger is attached or not */ |
| value = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_MAIN_INT_NUM); |
| dev_info(&i2c->dev, "INT_STATUS I2C read: 0x%02X\n", value); |
| |
| if((value & b_RESET_START) != 0x00) |
| { |
| dev_err(&i2c->dev, "START\n"); |
| s2mm003_indirect_write(usbpd_data, IND_REG_HOST_CMD_ON, 0x01); |
| return IRQ_HANDLED; |
| } |
| |
| if( (value & b_PD_FUNC_FLAG) != 0x00) { // Need PD Function State Read |
| int address = IND_REG_PDIC_PD_FUNC_STATE; // Function State Number Read |
| int ret = 0x00; |
| ret = s2mm003_indirect_read(usbpd_data, address); |
| usbpd_data->func_state = ret; // store the function status |
| |
| PDIC_Function_State_for_VBUS(usbpd_data->func_state); // For VBUS control |
| |
| /* If it isn't PD charger, return value is 29*/ |
| dev_info(&i2c->dev, " %s : func_state = %d\n", __func__, usbpd_data->func_state); |
| #if defined(CONFIG_CCIC_ALTERNATE_MODE) |
| if(usbpd_data->func_state == FUNCTION_STATUS_INITIAL_DETACH) |
| { |
| usbpd_data->Pdic_state_machine = b_Cmd_Intial_State; |
| if(usbpd_data->acc_type != CCIC_DOCK_DETACHED) |
| { |
| ccic_dock_detach_notify(); |
| usbpd_data->acc_type = CCIC_DOCK_DETACHED; |
| } |
| } |
| #endif |
| } |
| |
| if( (value & b_INT_1_FLAG) != 0x00) |
| { |
| int INT_1_value = 0; |
| INT_1_value = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_1_INT); // Interrupt State 1 Read |
| dev_info(&i2c->dev, "INT_State1 = 0x%X\n", INT_1_value); |
| #if defined(CONFIG_CCIC_ALTERNATE_MODE) |
| if(INT_1_value & b_Cmd_Discover_Identity){ |
| Msg_Read_Discover_Identity(usbpd_data); |
| Msg_Send_Discover_SVIDs(usbpd_data); |
| }else if(INT_1_value & b_Cmd_Discover_SVIDs){ |
| Msg_Read_Discover_SVIDs(usbpd_data); |
| Msg_Send_Discover_Modes(usbpd_data); |
| }else if(INT_1_value & b_Cmd_Discover_Modes){ |
| Msg_Read_Discover_Modes(usbpd_data); |
| Msg_Send_Enter_Mode(usbpd_data); |
| }else if(INT_1_value & b_Cmd_Enter_Mode){ |
| Msg_Read_Enter_Mode(usbpd_data); |
| } |
| #endif |
| } |
| |
| if( (value & b_INT_2_FLAG) != 0x00) |
| { |
| int INT_2_value = 0; |
| INT_2_value = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_2_INT); // Interrupt State 2 Read |
| dev_info(&i2c->dev, "INT_State2 = 0x%X\n", INT_2_value); |
| if(INT_2_value & b_Msg_PR_SWAP) |
| { |
| dev_err(&i2c->dev, "PR_Swap Wait Delay (Any)ms\n"); |
| // msleep(300); |
| s2mm003_indirect_write(usbpd_data, 0x4C, 6); // Call PS_RDY Command |
| VBUS_TURN_ON_CTRL(0); |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| usbpd_data->power_role = DUAL_ROLE_PROP_PR_SNK; |
| #endif |
| } |
| else if(INT_2_value & b_Msg_DR_SWAP) |
| { |
| dev_err(&i2c->dev, "DR_Swap\n"); |
| is_dr_swap = 1; |
| // toggling the state for usb host and device |
| #if defined(CONFIG_CCIC_NOTIFIER) |
| if(usbpd_data->Pdic_usb_state == USB_STATUS_NOTIFY_ATTACH_DFP) |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_ATTACH_UFP); |
| else if(usbpd_data->Pdic_usb_state == USB_STATUS_NOTIFY_ATTACH_UFP) |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_ATTACH_DFP); |
| #endif |
| |
| } |
| } |
| if((value & b_INT_3_FLAG) != 0x00) |
| { |
| int INT_3_value = 0; |
| INT_3_value = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_3_INT); // Interrupt State 3 Read |
| dev_info(&i2c->dev, "INT_State3 = 0x%X\n", INT_3_value); |
| |
| if((INT_3_value & b_RID_Detect_done) != 0x00) // RID Message Detect |
| { |
| rid = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_RID); // RID Value Read |
| dev_info(&i2c->dev, "\nRID_Value = 0x%X\n", rid); |
| usbpd_data->cur_rid = rid; |
| if (usbpd_data->cur_rid == usbpd_data->prev_rid) { |
| dev_err(&i2c->dev, "same rid detected, -ignore-\n"); |
| } |
| usbpd_data->p_prev_rid = usbpd_data->prev_rid; |
| usbpd_data->prev_rid = usbpd_data->cur_rid; |
| |
| #if defined(CONFIG_CCIC_NOTIFIER) |
| rid_notifier.src = CCIC_NOTIFY_DEV_CCIC; |
| rid_notifier.dest = CCIC_NOTIFY_DEV_MUIC; |
| rid_notifier.id = CCIC_NOTIFY_ID_RID; |
| rid_notifier.rid = rid; |
| ccic_notifier_notify((CC_NOTI_TYPEDEF*)&rid_notifier, NULL, pdic_attach); |
| if(rid == RID_000K) |
| { |
| VBUS_TURN_ON_CTRL(1); |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_ATTACH_DFP); |
| } |
| else if(rid == RID_OPEN || rid == RID_UNDEFINED || rid == RID_523K || rid == RID_619K) |
| { |
| VBUS_TURN_ON_CTRL(0); |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_DETACH); |
| } |
| #endif |
| } |
| |
| if((INT_3_value & b_Msg_SRC_CAP) != 0x00) // Read Source Capability MSG. |
| { |
| int cnt; |
| uint32_t ReadMSG[8]; |
| int current_pdo_num; |
| u8 *p_MSG_BUF; |
| |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_RX_SRC_CAPA);// Idx Transceiver Request |
| // Select PDO |
| if(usbpd_data->func_state == FUNCTION_STATUS_SINK_READY) |
| { |
| p_MSG_BUF = (u8 *)ReadMSG; |
| for(cnt = 0; cnt < 32; cnt++) |
| { |
| *(p_MSG_BUF + cnt) = s2mm003_indirect_read(usbpd_data, IND_REG_MSG_ACCESS_BASE+cnt); |
| } |
| current_pdo_num = s2mm003_src_capacity_information(i2c, ReadMSG, &pd_noti.sink_status); |
| |
| s2mm003_indirect_write(usbpd_data, IND_REG_READ_MSG_INDEX, MSG_Idx_TX_REQUEST);// Idx Transceiver Request |
| p_MSG_BUF = (u8 *)ReadMSG; |
| |
| for(cnt = 0; cnt < MSG_BUF_REQUEST_SIZE; cnt++) |
| { |
| *(p_MSG_BUF + cnt) = s2mm003_indirect_read(usbpd_data, IND_REG_MSG_ACCESS_BASE+cnt); |
| } |
| request_power_number = (REQUEST_FIXED_SUPPLY_STRUCT_Typedef *)&p_MSG_BUF[4]; // |
| |
| pr_info(" %s : Object_posision(%d),current pdo_num(%d), selected_pdo_num(%d) \n", __func__, |
| request_power_number->Object_Position, current_pdo_num, pd_noti.sink_status.selected_pdo_num); |
| |
| if(current_pdo_num > 0) { |
| if((current_pdo_num != pd_noti.sink_status.selected_pdo_num) && (current_pdo_num > 0)) |
| { |
| pd_noti.sink_status.selected_pdo_num = current_pdo_num; |
| // choice : 1, 2, 3 |
| pr_info(" %s : PDO(%d) is selected\n", __func__, pd_noti.sink_status.selected_pdo_num); |
| /* Idx Transceiver Request */ |
| s2mm003_indirect_write(usbpd_data, 0x40, pd_noti.sink_status.selected_pdo_num); |
| s2mm003_indirect_write(usbpd_data, 0x10, 0x11); // Function state 17 setting |
| } 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( (value & b_INT_4_FLAG) != 0x00) |
| { |
| int INT_4_value = 0; |
| INT_4_value = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_4_INT); // Interrupt State 4 Read |
| dev_info(&i2c->dev, "INT_State4 = 0x%X\n", INT_4_value); |
| } |
| if( (value & b_INT_5_FLAG) != 0x00) |
| { |
| int INT_5_value = 0; |
| INT_5_value = s2mm003_indirect_read(usbpd_data, IND_REG_PDIC_5_INT); // Interrupt State 5 Read |
| dev_info(&i2c->dev, "INT_State5 = 0x%X\n", INT_5_value); |
| } |
| #if defined(CONFIG_CCIC_ALTERNATE_MODE) |
| if (usbpd_data->func_state == FUNCTION_STATUS_SRC_READY && usbpd_data->Pdic_state_machine == b_Cmd_Intial_State){ |
| Msg_Send_Discover_Idendity(usbpd_data); |
| } |
| #endif |
| #if defined(CONFIG_CCIC_NOTIFIER) |
| attach_notifier.attach = plug_attach_done; |
| |
| #ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER |
| if(pdic_attach) |
| attach_notifier.id = CCIC_NOTIFY_ID_POWER_STATUS; |
| else |
| attach_notifier.id = CCIC_NOTIFY_ID_ATTACH; |
| #else |
| attach_notifier.id = CCIC_NOTIFY_ID_ATTACH; |
| #endif |
| attach_notifier.src = CCIC_NOTIFY_DEV_CCIC; |
| attach_notifier.dest = CCIC_NOTIFY_DEV_MUIC; |
| attach_notifier.rprd = usbpd_data->plug_rprd_sel; |
| |
| #ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER |
| ccic_notifier_notify((CC_NOTI_TYPEDEF*)&attach_notifier, &pd_noti, pdic_attach); |
| #else |
| ccic_notifier_notify((CC_NOTI_TYPEDEF*)&attach_notifier, NULL, pdic_attach); |
| #endif |
| |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| if(usbpd_data->func_state == FUNCTION_STATUS_SRC_SEND_CAPABILITY && !is_dr_swap) |
| { |
| usbpd_data->power_role = DUAL_ROLE_PROP_PR_SRC; |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_ATTACH_DFP); |
| } |
| else if(usbpd_data->func_state == FUNCTION_STATUS_SINK_DISCOVERY && !is_dr_swap) |
| { |
| usbpd_data->power_role = DUAL_ROLE_PROP_PR_SNK; |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_ATTACH_UFP); |
| } |
| else if(usbpd_data->func_state == FUNCTION_STATUS_INITIAL_DETACH) |
| { |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_DETACH); |
| |
| // OTP mode set to DRP when capble pluged out |
| if (!usbpd_data->try_state_change) |
| { |
| s2mm003_indirect_write(usbpd_data, IND_REG_PDIC_MODE, TYPE_C_ATTACH_DRP); |
| } |
| } |
| #else |
| if(usbpd_data->func_state == FUNCTION_STATUS_SRC_SEND_CAPABILITY && !is_dr_swap) |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_ATTACH_DFP); |
| else if(usbpd_data->func_state == FUNCTION_STATUS_SINK_DISCOVERY && !is_dr_swap) |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_ATTACH_UFP); |
| else if(usbpd_data->func_state == FUNCTION_STATUS_INITIAL_DETACH) |
| send_usb_notify_message(usbpd_data, USB_STATUS_NOTIFY_DETACH); |
| #endif |
| |
| #ifndef CONFIG_USB_TYPEC_MANAGER_NOTIFIER |
| 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 |
| #endif |
| s2mm003_int_clear(usbpd_data); |
| return IRQ_HANDLED; |
| } |
| |
| #if defined(CONFIG_OF) |
| static int of_s2mm003_usbpd_dt(struct device *dev, |
| struct s2mm003_data *usbpd_data) |
| { |
| struct device_node *np_usbpd = dev->of_node; |
| |
| usbpd_data->irq_gpio = of_get_named_gpio(np_usbpd, "usbpd,usbpd_int", 0); |
| usbpd_data->redriver_en = of_get_named_gpio(np_usbpd, "usbpd,redriver_en", 0); |
| dev_err(dev, "usbpd_irq = %d redriver_en = %d\n", |
| usbpd_data->irq_gpio, usbpd_data->redriver_en); |
| |
| return 0; |
| } |
| #endif /* CONFIG_OF */ |
| |
| #if defined(CONFIG_SEC_CCIC_FW_FIX) |
| void s2mm003_firmware_ver_check(struct s2mm003_data *usbpd_data, char *name) |
| { |
| struct i2c_client *i2c = usbpd_data->i2c; |
| |
| usbpd_data->firm_ver[0] = (u8)s2mm003_indirect_read(usbpd_data, 0x01); |
| usbpd_data->firm_ver[1] = (u8)s2mm003_indirect_read(usbpd_data, 0x02); |
| usbpd_data->firm_ver[2] = (u8)s2mm003_indirect_read(usbpd_data, 0x03); |
| usbpd_data->firm_ver[3] = (u8)s2mm003_indirect_read(usbpd_data, 0x04); |
| dev_err(&i2c->dev, "%s %s version %02x %02x %02x %02x\n", |
| USBPD_DEV_NAME, name, usbpd_data->firm_ver[0], usbpd_data->firm_ver[1], |
| usbpd_data->firm_ver[2], usbpd_data->firm_ver[3]); |
| } |
| #endif |
| |
| static int s2mm003_firmware_update(struct s2mm003_data *usbpd_data) |
| { |
| const struct firmware *fw_entry; |
| const unsigned char *p; |
| int ret, i; |
| struct i2c_client *i2c = usbpd_data->i2c; |
| u8 test, reg_check[3] = { 0, }; |
| |
| ret = s2mm003_read_byte(i2c, I2C_SYSREG_SET, ®_check[0]); |
| ret = s2mm003_read_byte(i2c, I2C_SRAM_SET, ®_check[1]); |
| ret = s2mm003_read_byte(i2c, IF_S_CODE_E, ®_check[2]); |
| if ((reg_check[0] != 0) || (reg_check[1] != 0) || (reg_check[2] != 0)) |
| { |
| dev_err(&usbpd_data->i2c->dev, "firmware register error %02x %02x %02x\n", |
| reg_check[0], reg_check[1], reg_check[2]); |
| s2mm003_write_byte(i2c, USB_PD_RST, 0x40); /*FULL RESET*/ |
| msleep(20); |
| } |
| |
| |
| s2mm003_write_byte(i2c, I2C_SRAM_SET, 0x1); /* 32 */ |
| s2mm003_read_byte_16(i2c, 0x1854, &test); |
| s2mm003_write_byte(i2c, I2C_SRAM_SET, 0x0); /* 32 */ |
| if (test > 7 || test < 0) |
| dev_err(&usbpd_data->i2c->dev, "fw update err rid : %02x\n", test); |
| |
| ret = request_firmware(&fw_entry, |
| FIRMWARE_PATH, usbpd_data->dev); |
| if (ret < 0) |
| goto done; |
| |
| msleep(5); |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x1); /* 31 */ |
| s2mm003_write_byte(i2c, OTP_DUMP_DISABLE, 0x1); /* 30 */ |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x0); /* 31 */ |
| msleep(10); |
| |
| s2mm003_write_byte(i2c, I2C_SRAM_SET, 0x1); /* 32 */ |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x1); /* 31 */ |
| s2mm003_write_byte(i2c, IF_S_CODE_E, 0x4); /* sram update enable */ |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x0); /* 31 */ |
| |
| /* update firmware */ |
| for(p = fw_entry->data, i=0; i < fw_entry->size; p++, i++) |
| s2mm003_write_byte_16(i2c,(u16)i, *p); |
| |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x1); /* 31 */ |
| s2mm003_write_byte(i2c, IF_S_CODE_E, 0x0); |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x0); /* 31 */ |
| |
| s2mm003_write_byte(i2c, I2C_SRAM_SET, 0x0); /* 32 */ |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x1); /* 31 */ |
| s2mm003_write_byte(i2c, USB_PD_RST, 0x80); /* 0D */ |
| s2mm003_write_byte(i2c, OTP_BLK_RST, 0x1); /* 0C OTP controller |
| system register reset |
| to stop otp copy */ |
| msleep(5); |
| s2mm003_write_byte(i2c, USB_PD_RST, 0x80); /* 0D */ |
| s2mm003_write_byte(i2c, I2C_SYSREG_SET, 0x0); /* 31 */ |
| |
| done: |
| dev_err(&usbpd_data->i2c->dev, "firmware size: %d, error %d\n", |
| (int)fw_entry->size, ret); |
| if (fw_entry) |
| release_firmware(fw_entry); |
| return ret; |
| } |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| |
| static enum dual_role_property fusb_drp_properties[] = { |
| DUAL_ROLE_PROP_MODE, |
| DUAL_ROLE_PROP_PR, |
| DUAL_ROLE_PROP_DR, |
| }; |
| |
| /* Callback for "cat /sys/class/dual_role_usb/otg_default/<property>" */ |
| static int dual_role_get_local_prop(struct dual_role_phy_instance *dual_role, |
| enum dual_role_property prop, |
| unsigned int *val) |
| { |
| struct s2mm003_data *usbpd_data = dual_role_get_drvdata(dual_role); |
| USB_STATUS attached_state; |
| int power_role; |
| |
| if (!usbpd_data) { |
| pr_err("%s : usbpd_data is null : request prop = %d \n",\ |
| __func__, prop); |
| return -EINVAL; |
| } |
| attached_state = usbpd_data->Pdic_usb_state; |
| power_role = usbpd_data->power_role; |
| pr_info("%s : request prop = %d , attached_state = %d, prop_mode =%d, prop_pr = %d \n",\ |
| __func__, prop, attached_state,usbpd_data->Pdic_usb_state, power_role); |
| |
| if (attached_state == USB_STATUS_NOTIFY_ATTACH_DFP) { |
| if (prop == DUAL_ROLE_PROP_MODE) |
| *val = DUAL_ROLE_PROP_MODE_DFP; |
| else if (prop == DUAL_ROLE_PROP_PR) { |
| if (power_role) |
| *val = DUAL_ROLE_PROP_PR_SNK; |
| else |
| *val = DUAL_ROLE_PROP_PR_SRC; |
| } |
| else if (prop == DUAL_ROLE_PROP_DR) |
| *val = DUAL_ROLE_PROP_DR_HOST; |
| else |
| return -EINVAL; |
| } else if (attached_state == USB_STATUS_NOTIFY_ATTACH_UFP) { |
| if (prop == DUAL_ROLE_PROP_MODE) |
| *val = DUAL_ROLE_PROP_MODE_UFP; |
| else if (prop == DUAL_ROLE_PROP_PR) { |
| if (power_role) |
| *val = DUAL_ROLE_PROP_PR_SNK; |
| else |
| *val = DUAL_ROLE_PROP_PR_SRC; |
| } |
| else if (prop == DUAL_ROLE_PROP_DR) |
| *val = DUAL_ROLE_PROP_DR_DEVICE; |
| else |
| return -EINVAL; |
| } else { |
| if (prop == DUAL_ROLE_PROP_MODE) |
| *val = DUAL_ROLE_PROP_MODE_NONE; |
| else if (prop == DUAL_ROLE_PROP_PR) |
| *val = DUAL_ROLE_PROP_PR_NONE; |
| else if (prop == DUAL_ROLE_PROP_DR) |
| *val = DUAL_ROLE_PROP_DR_NONE; |
| else |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* Decides whether userspace can change a specific property */ |
| static int dual_role_is_writeable(struct dual_role_phy_instance *drp, |
| enum dual_role_property prop) |
| { |
| if (prop == DUAL_ROLE_PROP_MODE) |
| return 1; |
| else |
| return 0; |
| } |
| |
| |
| int set_data_role(struct dual_role_phy_instance *dual_role, |
| enum dual_role_property prop, |
| const unsigned int *val) |
| { |
| struct s2mm003_data *usbpd_data = dual_role_get_drvdata(dual_role); |
| struct i2c_client *i2c = usbpd_data->i2c; |
| |
| USB_STATUS attached_state; |
| int mode; |
| int timeout = 0; |
| int ret = 0; |
| |
| if (!usbpd_data) { |
| pr_err("%s : usbpd_data is null \n", __func__); |
| return -EINVAL; |
| } |
| attached_state = usbpd_data->Pdic_usb_state; |
| pr_info("%s: request prop = %d , attached_state = %d , %d \n",__func__,\ |
| prop, attached_state, usbpd_data->Pdic_usb_state); |
| |
| if (attached_state != USB_STATUS_NOTIFY_ATTACH_DFP |
| && attached_state != USB_STATUS_NOTIFY_ATTACH_UFP) { |
| pr_info("%s : current mode : %d - just return \n",__func__,\ |
| attached_state); |
| return 0; |
| } |
| |
| if (attached_state == USB_STATUS_NOTIFY_ATTACH_DFP |
| && *val == DUAL_ROLE_PROP_MODE_DFP) { |
| pr_info("%s : current mode : %d - request mode : %d just return \n",__func__, attached_state, *val); |
| return 0; |
| } |
| |
| if (attached_state == USB_STATUS_NOTIFY_ATTACH_UFP |
| && *val == DUAL_ROLE_PROP_MODE_UFP) { |
| pr_info("%s : current mode : %d - request mode : %d just return \n",__func__, attached_state, *val); |
| return 0; |
| } |
| |
| /* Current mode DFP and Source */ |
| if ( attached_state == USB_STATUS_NOTIFY_ATTACH_DFP) |
| { |
| pr_info("%s: try reversing, form Source to Sink\n", __func__); |
| |
| disable_irq(usbpd_data->irq_gpio); |
| /* turns off VBUS first */ |
| VBUS_TURN_ON_CTRL(0); |
| |
| usbpd_data->power_role = 0; |
| /* exit from Disabled state and set mode to UFP */ |
| mode = TYPE_C_ATTACH_UFP; |
| s2mm003_indirect_write(usbpd_data, IND_REG_PDIC_MODE, mode); |
| usbpd_data->try_state_change = TYPE_C_ATTACH_UFP; |
| |
| enable_irq(usbpd_data->irq_gpio); |
| } |
| /* Current mode UFP and Sink */ |
| else |
| { |
| pr_info("%s: try reversing, form Sink to Source\n", __func__); |
| /* transition to Disabled state */ |
| disable_irq(usbpd_data->irq_gpio); |
| /* exit from Disabled state and set mode to UFP */ |
| mode = TYPE_C_ATTACH_DFP; |
| s2mm003_indirect_write(usbpd_data, IND_REG_PDIC_MODE, mode); |
| usbpd_data->try_state_change = TYPE_C_ATTACH_DFP; |
| |
| enable_irq(usbpd_data->irq_gpio); |
| } |
| |
| reinit_completion(&usbpd_data->reverse_completion); |
| timeout = |
| wait_for_completion_timeout(&usbpd_data->reverse_completion, |
| msecs_to_jiffies |
| (DUAL_ROLE_SET_MODE_WAIT_MS)); |
| // clear try_state_change |
| usbpd_data->try_state_change = 0; |
| if (!timeout) { |
| pr_err("%s: reverse failed, set mode to DRP\n", __func__); |
| disable_irq(usbpd_data->irq_gpio); |
| |
| /* exit from Disabled state and set mode to DRP */ |
| mode = TYPE_C_ATTACH_DRP; |
| s2mm003_indirect_write(usbpd_data, IND_REG_PDIC_MODE, mode); |
| usbpd_data->try_state_change = TYPE_C_ATTACH_DRP; |
| |
| enable_irq(usbpd_data->irq_gpio); |
| |
| pr_info("%s: wait for the attached state\n", __func__); |
| reinit_completion(&usbpd_data->reverse_completion); |
| wait_for_completion_timeout(&usbpd_data->reverse_completion, |
| msecs_to_jiffies |
| (DUAL_ROLE_SET_MODE_WAIT_MS)); |
| usbpd_data->try_state_change = 0; |
| ret = -EIO; |
| } |
| dev_info(&i2c->dev, "%s -> data role : %d\n", __func__, *val); |
| return ret; |
| } |
| |
| /* Callback for "echo <value> > |
| * /sys/class/dual_role_usb/<name>/<property>" |
| * Block until the entire final state is reached. |
| * Blocking is one of the better ways to signal when the operation |
| * is done. |
| * This function tries to switch to Attached.SRC or Attached.SNK |
| * by forcing the mode into SRC or SNK. |
| * On failure, we fall back to Try.SNK state machine. |
| */ |
| static int dual_role_set_prop(struct dual_role_phy_instance *dual_role, |
| enum dual_role_property prop, |
| const unsigned int *val) |
| { |
| if (prop == DUAL_ROLE_PROP_MODE) |
| return set_data_role(dual_role, prop, val); |
| else |
| return -EINVAL; |
| } |
| |
| #endif |
| |
| static int s2mm003_usbpd_probe(struct i2c_client *i2c, |
| const struct i2c_device_id *id) |
| { |
| struct i2c_adapter *adapter = to_i2c_adapter(i2c->dev.parent); |
| struct s2mm003_data *usbpd_data; |
| int ret = 0; |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| struct dual_role_phy_desc *desc; |
| struct dual_role_phy_instance *dual_role; |
| #endif |
| if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { |
| dev_err(&i2c->dev, "i2c functionality check error\n"); |
| return -EIO; |
| } |
| usbpd_data = devm_kzalloc(&i2c->dev, sizeof(struct s2mm003_data), GFP_KERNEL); |
| if (!usbpd_data) { |
| dev_err(&i2c->dev, "Failed to allocate driver data\n"); |
| return -ENOMEM; |
| } |
| |
| #if defined(CONFIG_OF) |
| if (i2c->dev.of_node) |
| of_s2mm003_usbpd_dt(&i2c->dev, usbpd_data); |
| else |
| dev_err(&i2c->dev, "not found ccic dt! ret:%d\n", ret); |
| #endif |
| ret = gpio_request(usbpd_data->irq_gpio, "s2mm003_irq"); |
| if (ret) |
| goto err_free_irq_gpio; |
| ret = gpio_request(usbpd_data->redriver_en, "s2mm003_redriver_en"); |
| if (ret) |
| goto err_free_redriver_gpio; |
| |
| gpio_direction_input(usbpd_data->irq_gpio); |
| i2c->irq = gpio_to_irq(usbpd_data->irq_gpio); |
| dev_info(&i2c->dev, "%s:IRQ NUM %d\n", __func__, i2c->irq); |
| |
| /* TODO REMOVE redriver always enable, Add sleep/resume */ |
| ret = gpio_direction_output(usbpd_data->redriver_en, 1); |
| if (ret) { |
| dev_err(&i2c->dev, "Unable to set input gpio direction, error %d\n", ret); |
| goto err_free_redriver_gpio; |
| } |
| |
| usbpd_data->i2c = i2c; |
| i2c_set_clientdata(i2c, usbpd_data); |
| dev_set_drvdata(ccic_device, usbpd_data); |
| device_init_wakeup(&i2c->dev, 1); |
| mutex_init(&usbpd_data->i2c_mutex); |
| |
| /* Init */ |
| usbpd_data->p_prev_rid = -1; |
| usbpd_data->prev_rid = -1; |
| usbpd_data->cur_rid = -1; |
| #if defined(CONFIG_CCIC_ALTERNATE_MODE) |
| usbpd_data->Pdic_state_machine = 0; |
| usbpd_data->acc_type = 0; |
| #endif |
| usbpd_data->func_state = 0; |
| usbpd_data->Pdic_usb_state = 0; |
| |
| #if defined(CONFIG_SEC_CCIC_FW_FIX) |
| s2mm003_firmware_ver_check(usbpd_data, "otp"); |
| #else |
| usbpd_data->firm_ver[0] = s2mm003_indirect_read(usbpd_data, 0x01); |
| usbpd_data->firm_ver[1] = s2mm003_indirect_read(usbpd_data, 0x02); |
| usbpd_data->firm_ver[2] = s2mm003_indirect_read(usbpd_data, 0x03); |
| usbpd_data->firm_ver[3] = s2mm003_indirect_read(usbpd_data, 0x04); |
| dev_err(&i2c->dev, "%s otp version %02x %02x %02x %02x\n", |
| USBPD_DEV_NAME, usbpd_data->firm_ver[0], usbpd_data->firm_ver[1], |
| usbpd_data->firm_ver[2], usbpd_data->firm_ver[3]); |
| #endif |
| |
| s2mm003_firmware_update(usbpd_data); |
| |
| #if defined(CONFIG_SEC_CCIC_FW_FIX) |
| s2mm003_firmware_ver_check(usbpd_data, "fw"); |
| #else |
| usbpd_data->firm_ver[0] = s2mm003_indirect_read(usbpd_data, 0x01); |
| usbpd_data->firm_ver[1] = s2mm003_indirect_read(usbpd_data, 0x02); |
| usbpd_data->firm_ver[2] = s2mm003_indirect_read(usbpd_data, 0x03); |
| usbpd_data->firm_ver[3] = s2mm003_indirect_read(usbpd_data, 0x04); |
| dev_err(&i2c->dev, "%s fw version %02x %02x %02x %02x\n", |
| USBPD_DEV_NAME, usbpd_data->firm_ver[0], usbpd_data->firm_ver[1], |
| usbpd_data->firm_ver[2], usbpd_data->firm_ver[3]); |
| #endif |
| Modify_TX_SRC_CAPA(usbpd_data); |
| |
| ret = request_threaded_irq(i2c->irq, NULL, s2mm003_usbpd_irq_thread, |
| (IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND | IRQF_ONESHOT), "s2mm003-usbpd", usbpd_data); |
| if (ret) { |
| dev_err(&i2c->dev, "Failed to request IRQ %d, error %d\n", i2c->irq, ret); |
| goto err_init_irq; |
| } |
| |
| |
| dev_err(&i2c->dev, "probed, irq %d\n", usbpd_data->irq_gpio); |
| |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| desc = |
| devm_kzalloc(&i2c->dev, |
| sizeof(struct dual_role_phy_desc), GFP_KERNEL); |
| if (!desc) { |
| pr_err("unable to allocate dual role descriptor\n"); |
| goto err_init_irq; |
| } |
| |
| desc->name = "otg_default"; |
| desc->supported_modes = DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP; |
| desc->get_property = dual_role_get_local_prop; |
| desc->set_property = dual_role_set_prop; |
| desc->properties = fusb_drp_properties; |
| desc->num_properties = ARRAY_SIZE(fusb_drp_properties); |
| desc->property_is_writeable = dual_role_is_writeable; |
| dual_role = |
| devm_dual_role_instance_register(&i2c->dev, desc); |
| dual_role->drv_data = usbpd_data; |
| usbpd_data->dual_role = dual_role; |
| usbpd_data->desc = desc; |
| init_completion(&usbpd_data->reverse_completion); |
| #endif |
| #ifdef CONFIG_SWITCH |
| /* for DockObserver */ |
| ret = switch_dev_register(&switch_dock); |
| if (ret < 0) { |
| pr_err("%s: Failed to register dock switch(%d)\n", |
| __func__, ret); |
| return -ENODEV; |
| } |
| #endif /* CONFIG_SWITCH */ |
| |
| return ret; |
| |
| err_init_irq: |
| if (i2c->irq) |
| free_irq(i2c->irq, usbpd_data); |
| err_free_redriver_gpio: |
| gpio_free(usbpd_data->redriver_en); |
| err_free_irq_gpio: |
| gpio_free(usbpd_data->irq_gpio); |
| kfree(usbpd_data); |
| return ret; |
| } |
| |
| static int s2mm003_usbpd_remove(struct i2c_client *i2c) |
| { |
| #if defined(CONFIG_DUAL_ROLE_USB_INTF) |
| struct s2mm003_data *usbpd_data = dev_get_drvdata(ccic_device); |
| |
| devm_dual_role_instance_unregister(usbpd_data->dev, usbpd_data->dual_role); |
| devm_kfree(usbpd_data->dev, usbpd_data->desc); |
| #endif |
| #ifdef CONFIG_SWITCH |
| /* for DockObserver */ |
| switch_dev_unregister(&switch_dock); |
| #endif /* CONFIG_SWITCH */ |
| return 0; |
| } |
| |
| static const struct i2c_device_id s2mm003_usbpd_id[] = { |
| { USBPD_DEV_NAME, 0 }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(i2c, s2mm003_usbpd_id); |
| |
| #if defined(CONFIG_OF) |
| static struct of_device_id s2mm003_i2c_dt_ids[] = { |
| { .compatible = "sec-s2mm003,i2c" }, |
| { } |
| }; |
| #endif /* CONFIG_OF */ |
| |
| static struct i2c_driver s2mm003_usbpd_driver = { |
| .driver = { |
| .name = USBPD_DEV_NAME, |
| #if defined(CONFIG_OF) |
| .of_match_table = s2mm003_i2c_dt_ids, |
| #endif /* CONFIG_OF */ |
| }, |
| .probe = s2mm003_usbpd_probe, |
| //.remove = __devexit_p(s2mm003_usbpd_remove), |
| .remove = s2mm003_usbpd_remove, |
| .id_table = s2mm003_usbpd_id, |
| }; |
| |
| static int __init s2mm003_usbpd_init(void) |
| { |
| return i2c_add_driver(&s2mm003_usbpd_driver); |
| } |
| module_init(s2mm003_usbpd_init); |
| |
| static void __exit s2mm003_usbpd_exit(void) |
| { |
| |
| i2c_del_driver(&s2mm003_usbpd_driver); |
| } |
| module_exit(s2mm003_usbpd_exit); |
| |
| MODULE_DESCRIPTION("S2MM003 USB PD driver"); |
| MODULE_LICENSE("GPL"); |