blob: 877107fbedf5bb7bfe31c4f7c7d8cc9d7ecb42c3 [file] [log] [blame]
/*
* Copyrights (C) 2017 Samsung Electronics, Inc.
* Copyrights (C) 2017 Maxim Integrated Products, Inc.
*
* 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.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/mod_devicetable.h>
#include <linux/power_supply.h>
#include <linux/of.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/mfd/max77705-private.h>
#include <linux/platform_device.h>
#include <linux/ccic/max77705_usbc.h>
#if defined(CONFIG_BATTERY_NOTIFIER)
#include <linux/battery/battery_notifier.h>
#endif
#if defined(CONFIG_CCIC_NOTIFIER)
#include <linux/ccic/ccic_core.h>
#include <linux/ccic/ccic_notifier.h>
#include <linux/ccic/max77705_alternate.h>
#endif
#if defined(CONFIG_USB_HOST_NOTIFY)
#include <linux/usb_notify.h>
#endif
#if defined(CONFIG_DUAL_ROLE_USB_INTF)
#include <linux/usb/class-dual-role.h>
#elif defined(CONFIG_TYPEC)
#include <linux/usb/typec.h>
#endif
#if defined(CONFIG_USE_SECOND_MUIC)
#include <linux/muic/muic.h>
#endif
#include "../battery_v2/include/sec_charging_common.h"
extern struct pdic_notifier_struct pd_noti;
extern void (*fp_select_pdo)(int num);
#if defined(CONFIG_PDIC_PD30)
extern int (*fp_sec_pd_select_pps)(int num, int ppsVol, int ppsCur);
extern int (*fp_sec_pd_get_apdo_max_power)(unsigned int *pdo_pos, unsigned int *taMaxVol, unsigned int *taMaxCur, unsigned int *taMaxPwr);
#endif
#if defined(CONFIG_PDIC_PD30)
static void max77705_process_pd(struct max77705_usbc_platform_data *usbc_data)
{
if (usbc_data->pd_data->cc_sbu_short) {
pd_noti.sink_status.available_pdo_num = 1;
pd_noti.sink_status.power_list[1].max_current =
pd_noti.sink_status.power_list[1].max_current > 1800 ?
1800 : pd_noti.sink_status.power_list[1].max_current;
pd_noti.sink_status.has_apdo = false;
}
pr_info("%s : current_pdo_num(%d), available_pdo_num(%d), has_apdo(%d)\n", __func__,
pd_noti.sink_status.current_pdo_num, pd_noti.sink_status.available_pdo_num, pd_noti.sink_status.has_apdo);
max77705_ccic_event_work(usbc_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_POWER_STATUS, 1/*attach*/, 0, 0);
}
#else
static void max77705_process_pd(struct max77705_usbc_platform_data *usbc_data)
{
int i;
if (usbc_data->pd_data->cc_sbu_short) {
pd_noti.sink_status.available_pdo_num = 1;
pd_noti.sink_status.power_list[1].max_current =
pd_noti.sink_status.power_list[1].max_current > 1800 ?
1800 : pd_noti.sink_status.power_list[1].max_current;
}
pr_info("%s : CURRENT PDO NUM(%d), available_pdo_num[%d]",
__func__, pd_noti.sink_status.current_pdo_num,
pd_noti.sink_status.available_pdo_num);
for (i = 1; i <= pd_noti.sink_status.available_pdo_num; i++) {
pr_info("%s : PDO_Num[%d] MAX_CURR(%d) MAX_VOLT(%d)\n", __func__,
i, pd_noti.sink_status.power_list[i].max_current,
pd_noti.sink_status.power_list[i].max_voltage);
}
max77705_ccic_event_work(usbc_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_POWER_STATUS, 1/*attach*/, 0, 0);
}
#endif
void max77705_select_pdo(int num)
{
struct max77705_usbc_platform_data *pusbpd = pd_noti.pusbpd;
usbc_cmd_data value;
u8 temp;
init_usbc_cmd_data(&value);
pr_info("%s : NUM(%d)\n", __func__, num);
temp = num;
pd_noti.sink_status.selected_pdo_num = num;
if (pd_noti.event != PDIC_NOTIFY_EVENT_PD_SINK)
pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK;
if (pd_noti.sink_status.current_pdo_num == pd_noti.sink_status.selected_pdo_num) {
max77705_process_pd(pusbpd);
} else {
pusbpd->pn_flag = false;
value.opcode = OPCODE_SRCCAP_REQUEST;
value.write_data[0] = temp;
value.write_length = 1;
value.read_length = 1;
max77705_usbc_opcode_write(pusbpd, &value);
}
pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n",
__func__, value.opcode, value.write_length, value.read_length, num);
}
void max77705_response_pdo_request(struct max77705_usbc_platform_data *usbc_data,
unsigned char *data)
{
u8 result = data[1];
pr_info("%s: %s (0x%02X)\n", __func__, result ? "Error," : "Sent,", result);
switch (result) {
case 0x00:
pr_info("%s: Sent PDO Request Message to Port Partner(0x%02X)\n", __func__, result);
break;
case 0xFE:
pr_info("%s: Error, SinkTxNg(0x%02X)\n", __func__, result);
break;
case 0xFF:
pr_info("%s: Error, Not in SNK Ready State(0x%02X)\n", __func__, result);
break;
default:
break;
}
/* retry if the state of sink is not stable yet */
if (result == 0xFE || result == 0xFF) {
cancel_delayed_work(&usbc_data->pd_data->retry_work);
queue_delayed_work(usbc_data->pd_data->wqueue, &usbc_data->pd_data->retry_work, 0);
}
}
#if defined(CONFIG_PDIC_PD30)
void max77705_set_enable_pps(bool enable, int ppsVol, int ppsCur)
{
usbc_cmd_data value;
init_usbc_cmd_data(&value);
value.opcode = OPCODE_SET_PPS;
if (enable) {
value.write_data[0] = 0x1; //PPS_ON On
value.write_data[1] = (ppsVol / 20) & 0xFF; //Default Output Voltage (Low), 20mV
value.write_data[2] = ((ppsVol / 20) >> 8) & 0xFF; //Default Output Voltage (High), 20mV
value.write_data[3] = (ppsCur / 50) & 0x7F; //Default Operating Current, 50mA
value.write_length = 4;
value.read_length = 1;
pr_info("%s : PPS_On (Vol:%dmV, Cur:%dmA)\n", __func__, ppsVol, ppsCur);
} else {
value.write_data[0] = 0x0; //PPS_ON Off
value.write_length = 1;
value.read_length = 1;
pr_info("%s : PPS_Off\n", __func__);
}
max77705_usbc_opcode_write(pd_noti.pusbpd, &value);
}
void max77705_response_set_pps(struct max77705_usbc_platform_data *usbc_data,
unsigned char *data)
{
u8 result = data[1];
if (result & 0x01)
usbc_data->pd_data->bPPS_on = true;
else
usbc_data->pd_data->bPPS_on = false;
pr_info("%s : PPS_%s (0x%02X)\n",
__func__, usbc_data->pd_data->bPPS_on ? "On" : "Off", result);
}
void max77705_response_apdo_request(struct max77705_usbc_platform_data *usbc_data,
unsigned char *data)
{
u8 result = data[1];
pr_info("%s: %s (0x%02X)\n", __func__, result ? "Error," : "Sent,", result);
switch (result) {
case 0x00:
pr_info("%s: Sent APDO Request Message to Port Partner(0x%02X)\n", __func__, result);
break;
case 0x01:
pr_info("%s: Error, Invalid APDO position(0x%02X)\n", __func__, result);
break;
case 0x02:
pr_info("%s: Error, Invalid Output Voltage(0x%02X)\n", __func__, result);
break;
case 0x03:
pr_info("%s: Error, Invalid Operating Current(0x%02X)\n", __func__, result);
break;
case 0x04:
pr_info("%s: Error, PPS Function Off(0x%02X)\n", __func__, result);
break;
case 0x05:
pr_info("%s: Error, Not in SNK Ready State(0x%02X)\n", __func__, result);
break;
case 0x06:
pr_info("%s: Error, PD2.0 Contract(0x%02X)\n", __func__, result);
break;
case 0x07:
pr_info("%s: Error, SinkTxNg(0x%02X)\n", __func__, result);
break;
default:
break;
}
/* retry if the state of sink is not stable yet */
if (result == 0x05 || result == 0x07) {
cancel_delayed_work(&usbc_data->pd_data->retry_work);
queue_delayed_work(usbc_data->pd_data->wqueue, &usbc_data->pd_data->retry_work, 0);
}
}
int max77705_select_pps(int num, int ppsVol, int ppsCur)
{
struct max77705_usbc_platform_data *pusbpd = pd_noti.pusbpd;
usbc_cmd_data value;
/* [dchg] TODO: check more below option */
if (num > pd_noti.sink_status.available_pdo_num) {
pr_info("%s: request pdo num(%d) is higher taht available pdo.\n", __func__, num);
return -EINVAL;
}
if (!pd_noti.sink_status.power_list[num].apdo) {
pr_info("%s: request pdo num(%d) is not apdo.\n", __func__, num);
return -EINVAL;
} else
pd_noti.sink_status.selected_pdo_num = num;
if (ppsVol > pd_noti.sink_status.power_list[num].max_voltage) {
pr_info("%s: ppsVol is over(%d, max:%d)\n",
__func__, ppsVol, pd_noti.sink_status.power_list[num].max_voltage);
ppsVol = pd_noti.sink_status.power_list[num].max_voltage;
} else if (ppsVol < pd_noti.sink_status.power_list[num].min_voltage) {
pr_info("%s: ppsVol is under(%d, min:%d)\n",
__func__, ppsVol, pd_noti.sink_status.power_list[num].min_voltage);
ppsVol = pd_noti.sink_status.power_list[num].min_voltage;
}
if (ppsCur > pd_noti.sink_status.power_list[num].max_current) {
pr_info("%s: ppsCur is over(%d, max:%d)\n",
__func__, ppsCur, pd_noti.sink_status.power_list[num].max_current);
ppsCur = pd_noti.sink_status.power_list[num].max_current;
} else if (ppsCur < 0) {
pr_info("%s: ppsCur is under(%d, 0)\n",
__func__, ppsCur);
ppsCur = 0;
}
pd_noti.sink_status.pps_voltage = ppsVol;
pd_noti.sink_status.pps_current = ppsCur;
pr_info(" %s : PPS PDO(%d), voltage(%d), current(%d) is selected to change\n",
__func__, pd_noti.sink_status.selected_pdo_num, ppsVol, ppsCur);
init_usbc_cmd_data(&value);
pusbpd->pn_flag = false;
value.opcode = OPCODE_APDO_SRCCAP_REQUEST;
value.write_data[0] = (num & 0xFF); /* APDO Position */
value.write_data[1] = (ppsVol / 20) & 0xFF; /* Output Voltage(Low) */
value.write_data[2] = ((ppsVol / 20) >> 8) & 0xFF; /* Output Voltage(High) */
value.write_data[3] = (ppsCur / 50) & 0x7F; /* Operating Current */
value.write_length = 4;
value.read_length = 1; /* Result */
max77705_usbc_opcode_write(pusbpd, &value);
/* [dchg] TODO: add return value */
return 0;
}
int max77705_get_apdo_max_power(unsigned int *pdo_pos, unsigned int *taMaxVol, unsigned int *taMaxCur, unsigned int *taMaxPwr)
{
int i;
int ret = 0;
int max_current = 0, max_voltage = 0, max_power = 0;
if (!pd_noti.sink_status.has_apdo) {
pr_info("%s: pd don't have apdo\n", __func__);
return -1;
}
/* First, get TA maximum power from the fixed PDO */
for (i = 1; i <= pd_noti.sink_status.available_pdo_num; i++) {
if (!(pd_noti.sink_status.power_list[i].apdo)) {
max_voltage = pd_noti.sink_status.power_list[i].max_voltage;
max_current = pd_noti.sink_status.power_list[i].max_current;
max_power = max_voltage*max_current; /* uW */
*taMaxPwr = max_power; /* mW */
}
}
if (*pdo_pos == 0) {
/* Get the proper PDO */
for (i = 1; i <= pd_noti.sink_status.available_pdo_num; i++) {
if (pd_noti.sink_status.power_list[i].apdo) {
if (pd_noti.sink_status.power_list[i].max_voltage >= *taMaxVol) {
*pdo_pos = i;
*taMaxVol = pd_noti.sink_status.power_list[i].max_voltage;
*taMaxCur = pd_noti.sink_status.power_list[i].max_current;
break;
}
}
if (*pdo_pos)
break;
}
if (*pdo_pos == 0) {
pr_info("mv (%d) and ma (%d) out of range of APDO\n",
*taMaxVol, *taMaxCur);
ret = -EINVAL;
}
} else {
/* If we already have pdo object position, we don't need to search max current */
ret = -ENOTSUPP;
}
if (!ret)
max77705_set_enable_pps(true, 5000, *taMaxCur); /* request as default 5V when enable first */
else
max77705_set_enable_pps(false, 0, 0);
pr_info("%s : *pdo_pos(%d), *taMaxVol(%d), *maxCur(%d), *maxPwr(%d)\n",
__func__, *pdo_pos, *taMaxVol, *taMaxCur, *taMaxPwr);
return ret;
}
#endif
void max77705_pd_retry_work(struct work_struct *work)
{
struct max77705_usbc_platform_data *pusbpd = pd_noti.pusbpd;
usbc_cmd_data value;
u8 num;
if (pd_noti.event == PDIC_NOTIFY_EVENT_DETACH)
return;
init_usbc_cmd_data(&value);
num = pd_noti.sink_status.selected_pdo_num;
pr_info("%s : latest selected_pdo_num(%d)\n", __func__, num);
pusbpd->pn_flag = false;
#if defined(CONFIG_PDIC_PD30)
if (pd_noti.sink_status.power_list[num].apdo) {
value.opcode = OPCODE_APDO_SRCCAP_REQUEST;
value.write_data[0] = (num & 0xFF); /* APDO Position */
value.write_data[1] = (pd_noti.sink_status.pps_voltage / 20) & 0xFF; /* Output Voltage(Low) */
value.write_data[2] = ((pd_noti.sink_status.pps_voltage / 20) >> 8) & 0xFF; /* Output Voltage(High) */
value.write_data[3] = (pd_noti.sink_status.pps_current / 50) & 0x7F; /* Operating Current */
value.write_length = 4;
value.read_length = 1; /* Result */
max77705_usbc_opcode_write(pusbpd, &value);
} else {
value.opcode = OPCODE_SRCCAP_REQUEST;
value.write_data[0] = num;
value.write_length = 1;
value.read_length = 1;
max77705_usbc_opcode_write(pusbpd, &value);
}
#else
value.opcode = OPCODE_SRCCAP_REQUEST;
value.write_data[0] = num;
value.write_length = 1;
value.read_length = 1;
max77705_usbc_opcode_write(pusbpd, &value);
#endif
pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n",
__func__, value.opcode, value.write_length, value.read_length, num);
}
void max77705_usbc_icurr(u8 curr)
{
usbc_cmd_data value;
init_usbc_cmd_data(&value);
value.opcode = OPCODE_CHGIN_ILIM_W;
value.write_data[0] = curr;
value.write_length = 1;
value.read_length = 0;
max77705_usbc_opcode_write(pd_noti.pusbpd, &value);
pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) USBC_ILIM(0x%x)\n",
__func__, value.opcode, value.write_length, value.read_length, curr);
}
void max77705_set_gpio5_control(int direction, int output)
{
struct max77705_usbc_platform_data *pusbpd = pd_noti.pusbpd;
usbc_cmd_data value;
u8 op_data1, op_data2;
if (direction)
op_data1 = 0xFF; // OUTPUT
else
op_data1 = 0x00; // INPUT
if (output)
op_data2 = 0xFF; // HIGH
else
op_data2 = 0x00; // LOW
init_usbc_cmd_data(&value);
value.opcode = OPCODE_SAMSUNG_GPIO5_CONTROL;
value.write_data[0] = op_data1;
value.write_data[1] = op_data2;
value.write_length = 2;
value.read_length = 0;
max77705_usbc_opcode_write(pusbpd, &value);
pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) gpio5(0x%x, 0x%x)\n",
__func__, value.opcode, value.write_length, value.read_length, op_data1, op_data2);
}
void max77705_set_fw_noautoibus(int enable)
{
struct max77705_usbc_platform_data *pusbpd = pd_noti.pusbpd;
usbc_cmd_data value;
u8 op_data;
switch (enable) {
case MAX77705_AUTOIBUS_FW_AT_OFF:
op_data = 0x03; /* usbc fw off & auto off(manual on) */
break;
case MAX77705_AUTOIBUS_FW_OFF:
op_data = 0x02; /* usbc fw off & auto on(manual off) */
break;
case MAX77705_AUTOIBUS_AT_OFF:
op_data = 0x01; /* usbc fw on & auto off(manual on) */
break;
case MAX77705_AUTOIBUS_ON:
default:
op_data = 0x00; /* usbc fw on & auto on(manual off) */
break;
}
init_usbc_cmd_data(&value);
value.opcode = OPCODE_SAMSUNG_FW_AUTOIBUS;
value.write_data[0] = op_data;
value.write_length = 1;
value.read_length = 0;
max77705_usbc_opcode_write(pusbpd, &value);
pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) AUTOIBUS(0x%x)\n",
__func__, value.opcode, value.write_length, value.read_length, op_data);
}
void max77705_vbus_turn_on_ctrl(struct max77705_usbc_platform_data *usbc_data, bool enable, bool swaped)
{
struct power_supply *psy_otg;
union power_supply_propval val;
int on = !!enable;
int ret = 0;
int count = 5;
#if defined(CONFIG_USB_HOST_NOTIFY)
struct otg_notify *o_notify = get_otg_notify();
bool must_block_host = is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST);
pr_info("%s : enable=%d, auto_vbus_en=%d, must_block_host=%d, swaped=%d\n",
__func__, enable, usbc_data->auto_vbus_en, must_block_host, swaped);
// turn on
if (enable) {
// auto-mode
if (usbc_data->auto_vbus_en) {
// mpsm
if (must_block_host) {
if (swaped) {
// turn off vbus because of swaped and blocked host
enable = false;
pr_info("%s : turn off vbus because of blocked host\n",
__func__);
} else {
enable = false;
pr_info("%s : turn off vbus because of blocked host\n",
__func__);
}
} else {
// don't turn on because of auto-mode
return;
}
// non auto-mode
} else {
if (must_block_host) {
if (swaped) {
enable = false;
pr_info("%s : turn off vbus because of blocked host\n",
__func__);
} else {
enable = false;
pr_info("%s : turn off vbus because of blocked host\n",
__func__);
}
}
}
// turn off
} else {
// don't turn off because of auto-mode or blocked (already off)
if (usbc_data->auto_vbus_en || must_block_host)
return;
}
#endif
pr_info("%s : enable=%d\n", __func__, enable);
while (count) {
psy_otg = power_supply_get_by_name("otg");
if (psy_otg) {
val.intval = enable;
#if defined(CONFIG_USE_SECOND_MUIC)
muic_hv_charger_disable(enable);
#endif
ret = psy_otg->desc->set_property(psy_otg, POWER_SUPPLY_PROP_ONLINE, &val);
if (ret == -ENODEV) {
pr_err("%s: fail to set power_suppy ONLINE property %d) retry (%d)\n",__func__, ret, count);
count--;
} else {
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);
}
break;
}
} else {
pr_err("%s: fail to get psy battery\n", __func__);
count--;
msleep(200);
}
}
}
void max77705_pdo_list(struct max77705_usbc_platform_data *usbc_data, unsigned char *data)
{
u8 temp = 0x00;
int i;
bool do_power_nego = false;
pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK;
temp = (data[1] >> 5);
#if defined(CONFIG_BATTERY_NOTIFIER)
if (temp > MAX_PDO_NUM) {
pr_info("%s : update available_pdo_num[%d -> %d]",
__func__, temp, MAX_PDO_NUM);
temp = MAX_PDO_NUM;
}
#endif
pd_noti.sink_status.available_pdo_num = temp;
pr_info("%s : Temp[0x%02x] Data[0x%02x] available_pdo_num[%d]\n",
__func__, temp, data[1], pd_noti.sink_status.available_pdo_num);
for (i = 0; i < temp; i++) {
u32 pdo_temp;
int max_current = 0, max_voltage = 0;
pdo_temp = (data[2 + (i * 4)]
| (data[3 + (i * 4)] << 8)
| (data[4 + (i * 4)] << 16)
| (data[5 + (i * 4)] << 24));
pr_info("%s : PDO[%d] = 0x%x\n", __func__, i, pdo_temp);
max_current = (0x3FF & pdo_temp);
max_voltage = (0x3FF & (pdo_temp >> 10));
if (!(do_power_nego) &&
(pd_noti.sink_status.power_list[i + 1].max_current != max_current * UNIT_FOR_CURRENT ||
pd_noti.sink_status.power_list[i + 1].max_voltage != max_voltage * UNIT_FOR_VOLTAGE))
do_power_nego = true;
pd_noti.sink_status.power_list[i + 1].max_current = max_current * UNIT_FOR_CURRENT;
pd_noti.sink_status.power_list[i + 1].max_voltage = max_voltage * UNIT_FOR_VOLTAGE;
pr_info("%s : PDO_Num[%d] MAX_CURR(%d) MAX_VOLT(%d), AVAILABLE_PDO_Num(%d)\n", __func__,
i, pd_noti.sink_status.power_list[i + 1].max_current,
pd_noti.sink_status.power_list[i + 1].max_voltage,
pd_noti.sink_status.available_pdo_num);
}
if (usbc_data->pd_data->pdo_list && do_power_nego) {
pr_info("%s : PDO list is changed, so power negotiation is need\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;
}
if (pd_noti.sink_status.current_pdo_num != 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__);
}
usbc_data->pd_data->pdo_list = true;
max77705_process_pd(usbc_data);
}
#if defined(CONFIG_PDIC_PD30)
void max77705_current_pdo(struct max77705_usbc_platform_data *usbc_data, unsigned char *data)
{
u8 sel_pdo_pos = 0x00, num_of_pdo = 0x00;
int i, available_pdo_num = 0;
bool do_power_nego = false;
U_SEC_PDO_OBJECT pdo_obj;
POWER_LIST* pPower_list;
POWER_LIST prev_power_list;
if (!pd_noti.sink_status.available_pdo_num)
do_power_nego = true;
sel_pdo_pos = ((data[1] >> 3) & 0x07);
pd_noti.sink_status.current_pdo_num = sel_pdo_pos;
num_of_pdo = (data[1] & 0x07);
if (num_of_pdo > MAX_PDO_NUM) {
pr_info("%s : update available_pdo_num[%d -> %d]",
__func__, num_of_pdo, MAX_PDO_NUM);
num_of_pdo = MAX_PDO_NUM;
}
pd_noti.sink_status.has_apdo = false;
for (i = 0; i < num_of_pdo; ++i) {
pPower_list = &pd_noti.sink_status.power_list[available_pdo_num + 1];
pdo_obj.data = (data[2 + (i * 4)]
| (data[3 + (i * 4)] << 8)
| (data[4 + (i * 4)] << 16)
| (data[5 + (i * 4)] << 24));
if (!do_power_nego)
prev_power_list = pd_noti.sink_status.power_list[available_pdo_num + 1];
switch (pdo_obj.BITS_supply.type) {
case PDO_TYPE_FIXED:
pPower_list->apdo = false;
pPower_list->max_voltage = pdo_obj.BITS_pdo_fixed.voltage * UNIT_FOR_VOLTAGE;
pPower_list->min_voltage = 0;
pPower_list->max_current = pdo_obj.BITS_pdo_fixed.max_current * UNIT_FOR_CURRENT;
if (pPower_list->max_voltage > AVAILABLE_VOLTAGE)
pPower_list->accept = false;
else
pPower_list->accept = true;
available_pdo_num++;
break;
case PDO_TYPE_APDO:
pd_noti.sink_status.has_apdo = true;
available_pdo_num++;
pPower_list->apdo = true;
pPower_list->max_voltage = pdo_obj.BITS_pdo_programmable.max_voltage * UNIT_FOR_APDO_VOLTAGE;
pPower_list->min_voltage = pdo_obj.BITS_pdo_programmable.min_voltage * UNIT_FOR_APDO_VOLTAGE;
pPower_list->max_current = pdo_obj.BITS_pdo_programmable.max_current * UNIT_FOR_APDO_CURRENT;
pPower_list->accept = true;
break;
case PDO_TYPE_BATTERY:
case PDO_TYPE_VARIABLE:
default:
break;
}
if (!(do_power_nego) &&
(pPower_list->max_current != prev_power_list.max_current ||
pPower_list->max_voltage != prev_power_list.max_voltage ||
pPower_list->min_voltage != prev_power_list.min_voltage))
do_power_nego = true;
}
if (!do_power_nego && (pd_noti.sink_status.available_pdo_num != available_pdo_num))
do_power_nego = true;
pd_noti.sink_status.available_pdo_num = available_pdo_num;
pr_info("%s : current_pdo_num(%d), available_pdo_num(%d/%d)\n", __func__,
pd_noti.sink_status.current_pdo_num, pd_noti.sink_status.available_pdo_num, num_of_pdo);
pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK;
if (usbc_data->pd_data->pdo_list && do_power_nego) {
pr_info("%s : PDO list is changed, so power negotiation is need\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;
}
if (pd_noti.sink_status.current_pdo_num != 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__);
}
if (do_power_nego || pd_noti.sink_status.selected_pdo_num == 0) {
for (i = 0; i < num_of_pdo; ++i) {
pdo_obj.data = (data[2 + (i * 4)]
| (data[3 + (i * 4)] << 8)
| (data[4 + (i * 4)] << 16)
| (data[5 + (i * 4)] << 24));
pr_info("%s : PDO[%d] = 0x%08X\n", __func__, i + 1, pdo_obj.data);
}
for (i = 0; i < pd_noti.sink_status.available_pdo_num; ++i) {
pPower_list = &pd_noti.sink_status.power_list[i + 1];
pr_info("%s : PDO[%d,%s,%s] max_vol(%dmV),min_vol(%dmV),max_cur(%dmA)\n",
__func__, i + 1,
pPower_list->apdo ? "APDO" : "FIXED", pPower_list->accept ? "O" : "X",
pPower_list->max_voltage, pPower_list->min_voltage, pPower_list->max_current);
}
}
usbc_data->pd_data->pdo_list = true;
max77705_process_pd(usbc_data);
}
#else
void max77705_current_pdo(struct max77705_usbc_platform_data *usbc_data, unsigned char *data)
{
u8 temp = 0x00;
int i;
bool do_power_nego = false;
pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK;
temp = ((data[1] >> 3) & 0x07);
pd_noti.sink_status.current_pdo_num = temp;
pr_info("%s : CURRENT PDO NUM(%d)\n",
__func__, pd_noti.sink_status.current_pdo_num);
temp = (data[1] & 0x07);
#if defined(CONFIG_BATTERY_NOTIFIER)
if (temp > MAX_PDO_NUM) {
pr_info("%s : update available_pdo_num[%d -> %d]",
__func__, temp, MAX_PDO_NUM);
temp = MAX_PDO_NUM;
}
#endif
pd_noti.sink_status.available_pdo_num = temp;
pr_info("%s : Temp[0x%02x] Data[0x%02x] available_pdo_num[%d]\n",
__func__, temp, data[1], pd_noti.sink_status.available_pdo_num);
for (i = 0; i < temp; i++) {
u32 pdo_temp;
int max_current = 0, max_voltage = 0;
pdo_temp = (data[2 + (i * 4)]
| (data[3 + (i * 4)] << 8)
| (data[4 + (i * 4)] << 16)
| (data[5 + (i * 4)] << 24));
pr_info("%s : PDO[%d] = 0x%x\n", __func__, i, pdo_temp);
max_current = (0x3FF & pdo_temp);
max_voltage = (0x3FF & (pdo_temp >> 10));
if (!(do_power_nego) &&
(pd_noti.sink_status.power_list[i + 1].max_current != max_current * UNIT_FOR_CURRENT ||
pd_noti.sink_status.power_list[i + 1].max_voltage != max_voltage * UNIT_FOR_VOLTAGE))
do_power_nego = true;
pd_noti.sink_status.power_list[i + 1].max_current = max_current * UNIT_FOR_CURRENT;
pd_noti.sink_status.power_list[i + 1].max_voltage = max_voltage * UNIT_FOR_VOLTAGE;
pr_info("%s : PDO_Num[%d] MAX_CURR(%d) MAX_VOLT(%d), AVAILABLE_PDO_Num(%d)\n", __func__,
i, pd_noti.sink_status.power_list[i + 1].max_current,
pd_noti.sink_status.power_list[i + 1].max_voltage,
pd_noti.sink_status.available_pdo_num);
}
if (usbc_data->pd_data->pdo_list && do_power_nego) {
pr_info("%s : PDO list is changed, so power negotiation is need\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;
}
if (pd_noti.sink_status.current_pdo_num != 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__);
}
usbc_data->pd_data->pdo_list = true;
max77705_process_pd(usbc_data);
}
#endif
void max77705_detach_pd(struct max77705_usbc_platform_data *usbc_data)
{
pr_info("%s : Detach PD CHARGER\n", __func__);
if (pd_noti.event != PDIC_NOTIFY_EVENT_DETACH) {
cancel_delayed_work(&usbc_data->pd_data->retry_work);
#if defined(CONFIG_PDIC_PD30)
if (pd_noti.sink_status.available_pdo_num)
memset(pd_noti.sink_status.power_list, 0, (sizeof(POWER_LIST) * (MAX_PDO_NUM + 1)));
#endif
pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE;
pd_noti.sink_status.selected_pdo_num = 0;
pd_noti.sink_status.available_pdo_num = 0;
pd_noti.sink_status.current_pdo_num = 0;
#if defined(CONFIG_PDIC_PD30)
pd_noti.sink_status.pps_voltage = 0;
pd_noti.sink_status.pps_current = 0;
pd_noti.sink_status.request_apdo = false;
pd_noti.sink_status.has_apdo = false;
max77705_set_enable_pps(false, 0, 0);
#endif
pd_noti.event = PDIC_NOTIFY_EVENT_DETACH;
usbc_data->pd_data->psrdy_received = false;
usbc_data->pd_data->pdo_list = false;
usbc_data->pd_data->cc_sbu_short = false;
max77705_ccic_event_work(usbc_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0);
}
}
static void max77705_notify_prswap(struct max77705_usbc_platform_data *usbc_data, u8 pd_msg)
{
pr_info("%s : PR SWAP pd_msg [%x]\n", __func__, pd_msg);
switch(pd_msg) {
case PRSWAP_SNKTOSWAP:
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;
usbc_data->pd_data->psrdy_received = false;
usbc_data->pd_data->pdo_list = false;
usbc_data->pd_data->cc_sbu_short = false;
max77705_ccic_event_work(usbc_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0);
break;
default:
break;
}
}
void max77705_check_pdo(struct max77705_usbc_platform_data *usbc_data)
{
usbc_cmd_data value;
init_usbc_cmd_data(&value);
value.opcode = OPCODE_CURRENT_SRCCAP;
value.write_length = 0x0;
value.read_length = 31;
max77705_usbc_opcode_read(usbc_data, &value);
pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n",
__func__, value.opcode, value.write_length, value.read_length);
}
void max77705_notify_rp_current_level(struct max77705_usbc_platform_data *usbc_data)
{
unsigned int rp_currentlvl;
switch (usbc_data->cc_data->ccistat) {
case CCI_500mA:
rp_currentlvl = RP_CURRENT_LEVEL_DEFAULT;
break;
case CCI_1_5A:
rp_currentlvl = RP_CURRENT_LEVEL2;
break;
case CCI_3_0A:
rp_currentlvl = RP_CURRENT_LEVEL3;
break;
case CCI_SHORT:
rp_currentlvl = RP_CURRENT_ABNORMAL;
break;
default:
rp_currentlvl = RP_CURRENT_LEVEL_NONE;
break;
}
if (usbc_data->plug_attach_done && !usbc_data->pd_data->psrdy_received &&
usbc_data->cc_data->current_pr == SNK &&
usbc_data->pd_state == max77705_State_PE_SNK_Wait_for_Capabilities &&
rp_currentlvl != pd_noti.sink_status.rp_currentlvl &&
rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT) {
pd_noti.sink_status.rp_currentlvl = rp_currentlvl;
pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH;
pr_info("%s : rp_currentlvl(%d)\n", __func__, pd_noti.sink_status.rp_currentlvl);
max77705_ccic_event_work(usbc_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0);
}
}
static void max77705_pd_check_pdmsg(struct max77705_usbc_platform_data *usbc_data, u8 pd_msg)
{
struct power_supply *psy_charger;
union power_supply_propval val;
usbc_cmd_data value;
/*int dr_swap, pr_swap, vcon_swap = 0; u8 value[2], rc = 0;*/
MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State;
#ifdef CONFIG_USB_NOTIFY_PROC_LOG
int event;
#endif
VDM_MSG_IRQ_State.DATA = 0x0;
init_usbc_cmd_data(&value);
msg_maxim(" pd_msg [%x]", pd_msg);
switch (pd_msg) {
case Nothing_happened:
break;
case Sink_PD_PSRdy_received:
/* currently, do nothing
* calling max77705_check_pdo() has been moved to max77705_psrdy_irq()
* for specific PD charger issue
*/
break;
case Sink_PD_Error_Recovery:
break;
case Sink_PD_SenderResponseTimer_Timeout:
msg_maxim("Sink_PD_SenderResponseTimer_Timeout received.");
/* queue_work(usbc_data->op_send_queue, &usbc_data->op_send_work); */
break;
case Source_PD_PSRdy_Sent:
if (usbc_data->mpsm_mode && (usbc_data->pd_pr_swap == cc_SOURCE)) {
max77705_usbc_disable_auto_vbus(usbc_data);
max77705_vbus_turn_on_ctrl(usbc_data, OFF, false);
}
break;
case Source_PD_Error_Recovery:
break;
case Source_PD_SenderResponseTimer_Timeout:
max77705_vbus_turn_on_ctrl(usbc_data, OFF, false);
schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(800));
break;
case PD_DR_Swap_Request_Received:
msg_maxim("DR_SWAP received.");
/* currently, do nothing
* calling max77705_check_pdo() has been moved to max77705_psrdy_irq()
* for specific PD charger issue
*/
break;
case PD_PR_Swap_Request_Received:
msg_maxim("PR_SWAP received.");
break;
case PD_VCONN_Swap_Request_Received:
msg_maxim("VCONN_SWAP received.");
break;
case Received_PD_Message_in_illegal_state:
break;
case Samsung_Accessory_is_attached:
break;
case VDM_Attention_message_Received:
break;
case Sink_PD_Disabled:
#if 0
/* to do */
/* AFC HV */
value[0] = 0x20;
rc = max77705_ccpd_write_command(chip, value, 1);
if (rc > 0)
pr_err("failed to send command\n");
#endif
break;
case Source_PD_Disabled:
break;
case Prswap_Snktosrc_Sent:
usbc_data->pd_pr_swap = cc_SOURCE;
break;
case Prswap_Srctosnk_Sent:
usbc_data->pd_pr_swap = cc_SINK;
break;
case HARDRESET_RECEIVED:
/*turn off the vbus both Source and Sink*/
if (usbc_data->cc_data->current_pr == SRC) {
max77705_vbus_turn_on_ctrl(usbc_data, OFF, false);
schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(760));
}
#ifdef CONFIG_USB_NOTIFY_PROC_LOG
event = NOTIFY_EXTRA_HARDRESET_RECEIVED;
store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL);
#endif
break;
case HARDRESET_SENT:
/*turn off the vbus both Source and Sink*/
if (usbc_data->cc_data->current_pr == SRC) {
max77705_vbus_turn_on_ctrl(usbc_data, OFF, false);
schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(760));
}
#ifdef CONFIG_USB_NOTIFY_PROC_LOG
event = NOTIFY_EXTRA_HARDRESET_SENT;
store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL);
#endif
break;
case Get_Vbus_turn_on:
break;
case Get_Vbus_turn_off:
max77705_vbus_turn_on_ctrl(usbc_data, OFF, false);
break;
case PRSWAP_SRCTOSWAP:
max77705_vbus_turn_on_ctrl(usbc_data, OFF, false);
msg_maxim("PRSWAP_SRCTOSWAP : [%x]", pd_msg);
break;
case PRSWAP_SWAPTOSNK:
max77705_vbus_turn_on_ctrl(usbc_data, OFF, false);
msg_maxim("PRSWAP_SWAPTOSNK : [%x]", pd_msg);
break;
case PRSWAP_SNKTOSWAP:
msg_maxim("PRSWAP_SNKTOSWAP : [%x]", pd_msg);
max77705_notify_prswap(usbc_data, PRSWAP_SNKTOSWAP);
/* CHGINSEL disable */
psy_charger = power_supply_get_by_name("max77705-charger");
if (psy_charger) {
val.intval = 0;
psy_do_property("max77705-charger", set, POWER_SUPPLY_EXT_PROP_CHGINSEL, val);
} else {
pr_err("%s: Fail to get psy charger\n", __func__);
}
break;
case PRSWAP_SWAPTOSRC:
max77705_vbus_turn_on_ctrl(usbc_data, ON, false);
msg_maxim("PRSWAP_SNKTOSRC : [%x]", pd_msg);
break;
case Current_Cable_Connected:
max77705_manual_jig_on(usbc_data, 1);
usbc_data->manual_lpm_mode = 1;
msg_maxim("Current_Cable_Connected : [%x]", pd_msg);
break;
case SRC_CAP_RECEIVED:
msg_maxim("SRC_CAP_RECEIVED : [%x]", pd_msg);
break;
case Status_Received:
value.opcode = OPCODE_SAMSUNG_READ_MESSAGE;
value.write_data[0] = 0x02;
value.write_length = 1;
value.read_length = 32;
max77705_usbc_opcode_write(usbc_data, &value);
msg_maxim("@TA_ALERT: Status Receviced : [%x]", pd_msg);
break;
case Alert_Message:
value.opcode = OPCODE_SAMSUNG_READ_MESSAGE;
value.write_data[0] = 0x01;
value.write_length = 1;
value.read_length = 32;
max77705_usbc_opcode_write(usbc_data, &value);
msg_maxim("@TA_ALERT: Alert Message : [%x]", pd_msg);
break;
default:
break;
}
}
void max77705_pd_check_pdmsg_callback(void *data, u8 pdmsg)
{
struct max77705_usbc_platform_data *usbc_data = data;
if (!usbc_data) {
msg_maxim("usbc_data is null");
return;
}
if (!usbc_data->pd_data->psrdy_received &&
(pdmsg == Sink_PD_PSRdy_received || pdmsg == SRC_CAP_RECEIVED)) {
union power_supply_propval val;
msg_maxim("pdmsg=%x", pdmsg);
val.intval = 1;
psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SRCCAP, val);
}
}
static void max77705_pd_rid(struct max77705_usbc_platform_data *usbc_data, u8 fct_id)
{
struct max77705_pd_data *pd_data = usbc_data->pd_data;
u8 prev_rid = pd_data->device;
#if defined(CONFIG_CCIC_NOTIFIER)
static int rid = RID_OPEN;
#endif
switch (fct_id) {
case FCT_GND:
msg_maxim(" RID_000K");
pd_data->device = DEV_FCT_0;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_000K;
#endif
break;
case FCT_1Kohm:
msg_maxim(" RID_001K");
pd_data->device = DEV_FCT_1K;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_001K;
#endif
break;
case FCT_255Kohm:
msg_maxim(" RID_255K");
pd_data->device = DEV_FCT_255K;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_255K;
#endif
break;
case FCT_301Kohm:
msg_maxim(" RID_301K");
pd_data->device = DEV_FCT_301K;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_301K;
#endif
break;
case FCT_523Kohm:
msg_maxim(" RID_523K");
pd_data->device = DEV_FCT_523K;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_523K;
#endif
break;
case FCT_619Kohm:
msg_maxim(" RID_619K");
pd_data->device = DEV_FCT_619K;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_619K;
#endif
break;
case FCT_OPEN:
msg_maxim(" RID_OPEN");
pd_data->device = DEV_FCT_OPEN;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_OPEN;
#endif
break;
default:
msg_maxim(" RID_UNDEFINED");
pd_data->device = DEV_UNKNOWN;
#if defined(CONFIG_CCIC_NOTIFIER)
rid = RID_UNDEFINED;
#endif
break;
}
if (prev_rid != pd_data->device) {
#if defined(CONFIG_CCIC_NOTIFIER)
/* RID */
max77705_ccic_event_work(usbc_data,
CCIC_NOTIFY_DEV_MUIC, CCIC_NOTIFY_ID_RID,
rid, 0, 0);
usbc_data->cur_rid = rid;
/* turn off USB */
if (pd_data->device == DEV_FCT_OPEN || pd_data->device == DEV_UNKNOWN
|| pd_data->device == DEV_FCT_523K || pd_data->device == DEV_FCT_619K) {
#if defined(CONFIG_DUAL_ROLE_USB_INTF)
usbc_data->power_role = DUAL_ROLE_PROP_PR_NONE;
#elif defined(CONFIG_TYPEC)
usbc_data->typec_power_role = TYPEC_SINK;
#endif
/* usb or otg */
max77705_ccic_event_work(usbc_data,
CCIC_NOTIFY_DEV_USB, CCIC_NOTIFY_ID_USB,
0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0);
}
#endif
}
}
static irqreturn_t max77705_pdmsg_irq(int irq, void *data)
{
struct max77705_usbc_platform_data *usbc_data = data;
struct max77705_pd_data *pd_data = usbc_data->pd_data;
u8 pdmsg = 0;
max77705_read_reg(usbc_data->muic, REG_PD_STATUS0, &pd_data->pd_status0);
pdmsg = pd_data->pd_status0;
msg_maxim("IRQ(%d)_IN pdmsg: %02x", irq, pdmsg);
max77705_pd_check_pdmsg(usbc_data, pdmsg);
pd_data->pdsmg = pdmsg;
msg_maxim("IRQ(%d)_OUT", irq);
return IRQ_HANDLED;
}
static irqreturn_t max77705_psrdy_irq(int irq, void *data)
{
struct max77705_usbc_platform_data *usbc_data = data;
u8 psrdy_received = 0;
#if defined(CONFIG_TYPEC)
enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB;
#endif
msg_maxim("IN");
max77705_read_reg(usbc_data->muic, REG_PD_STATUS1, &usbc_data->pd_status1);
psrdy_received = (usbc_data->pd_status1 & BIT_PD_PSRDY)
>> FFS(BIT_PD_PSRDY);
if (psrdy_received && !usbc_data->pd_support
&& usbc_data->pd_data->cc_status != CC_NO_CONN)
usbc_data->pd_support = true;
if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_PR &&
usbc_data->pd_support) {
msg_maxim("typec_reverse_completion");
usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE;
complete(&usbc_data->typec_reverse_completion);
}
msg_maxim("psrdy_received=%d, usbc_data->pd_support=%d, cc_status=%d",
psrdy_received, usbc_data->pd_support, usbc_data->pd_data->cc_status);
#if defined(CONFIG_TYPEC)
mode = max77705_get_pd_support(usbc_data);
typec_set_pwr_opmode(usbc_data->port, mode);
#endif
if (usbc_data->pd_data->cc_status == CC_SNK && psrdy_received) {
max77705_check_pdo(usbc_data);
usbc_data->pd_data->psrdy_received = true;
}
if (psrdy_received && usbc_data->pd_data->cc_status != CC_NO_CONN) {
usbc_data->pn_flag = true;
complete(&usbc_data->psrdy_wait);
}
msg_maxim("OUT");
return IRQ_HANDLED;
}
bool max77705_sec_pps_control(int en)
{
#if defined(CONFIG_PDIC_PD30)
struct max77705_usbc_platform_data *pusbpd = pd_noti.pusbpd;
union power_supply_propval val = {0,};
msg_maxim(": %d", en);
val.intval = en; /* 0: stop pps, 1: start pps */
psy_do_property("battery", set,
POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM, val);
if (!en && !pusbpd->pn_flag) {
reinit_completion(&pusbpd->psrdy_wait);
if (!wait_for_completion_timeout(&pusbpd->psrdy_wait, msecs_to_jiffies(1000))) {
msg_maxim("PSRDY COMPLETION TIMEOUT");
return false;
}
}
return true;
#else
return true;
#endif
}
static void max77705_datarole_irq_handler(void *data, int irq)
{
struct max77705_usbc_platform_data *usbc_data = data;
struct max77705_pd_data *pd_data = usbc_data->pd_data;
u8 datarole = 0;
max77705_read_reg(usbc_data->muic, REG_PD_STATUS1, &pd_data->pd_status1);
datarole = (pd_data->pd_status1 & BIT_PD_DataRole)
>> FFS(BIT_PD_DataRole);
/* abnormal data role without setting power role */
if (usbc_data->cc_data->current_pr == 0xFF) {
msg_maxim("INVALID IRQ IRQ(%d)_OUT", irq);
return;
}
if (irq == CCIC_IRQ_INIT_DETECT) {
if (usbc_data->pd_data->cc_status == CC_SNK)
msg_maxim("initial time : SNK");
else
return;
}
switch (datarole) {
case UFP:
if (pd_data->current_dr != UFP) {
pd_data->previous_dr = pd_data->current_dr;
pd_data->current_dr = UFP;
if (pd_data->previous_dr != 0xFF)
msg_maxim("%s detach previous usb connection\n", __func__);
max77705_notify_dr_status(usbc_data, 1);
#if defined(CONFIG_TYPEC)
if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR ||
usbc_data->typec_try_state_change == TRY_ROLE_SWAP_TYPE) {
msg_maxim("typec_reverse_completion");
usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE;
complete(&usbc_data->typec_reverse_completion);
}
#endif
}
msg_maxim(" UFP");
break;
case DFP:
if (pd_data->current_dr != DFP) {
pd_data->previous_dr = pd_data->current_dr;
pd_data->current_dr = DFP;
if (pd_data->previous_dr != 0xFF)
msg_maxim("%s detach previous usb connection\n", __func__);
max77705_notify_dr_status(usbc_data, 1);
#if defined(CONFIG_TYPEC)
if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR ||
usbc_data->typec_try_state_change == TRY_ROLE_SWAP_TYPE) {
msg_maxim("typec_reverse_completion");
usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE;
complete(&usbc_data->typec_reverse_completion);
}
#endif
if (usbc_data->cc_data->current_pr == SNK && !(usbc_data->is_first_booting)) {
max77705_vdm_process_set_identity_req(usbc_data);
msg_maxim("SEND THE IDENTITY REQUEST FROM DFP HANDLER");
}
}
msg_maxim(" DFP");
break;
default:
msg_maxim(" DATAROLE(Never Call this routine)");
break;
}
}
static irqreturn_t max77705_datarole_irq(int irq, void *data)
{
pr_debug("%s: IRQ(%d)_IN\n", __func__, irq);
max77705_datarole_irq_handler(data, irq);
pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq);
return IRQ_HANDLED;
}
static irqreturn_t max77705_ssacc_irq(int irq, void *data)
{
struct max77705_usbc_platform_data *usbc_data = data;
struct max77705_pd_data *pd_data = usbc_data->pd_data;
pr_debug("%s: IRQ(%d)_IN\n", __func__, irq);
msg_maxim(" SSAcc command received");
/* Read through Opcode command 0x50 */
pd_data->ssacc = 1;
pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq);
return IRQ_HANDLED;
}
static void max77705_check_cc_sbu_short(void *data)
{
u8 cc_status1 = 0;
struct max77705_usbc_platform_data *usbc_data = data;
struct max77705_pd_data *pd_data = usbc_data->pd_data;
max77705_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_status1);
/* 0b01: CC-5V, 0b10: SBU-5V, 0b11: SBU-GND Short */
cc_status1 = (cc_status1 & BIT_CCSBUSHORT) >> FFS(BIT_CCSBUSHORT);
if (cc_status1)
pd_data->cc_sbu_short = true;
msg_maxim("%s cc_status1 : %x, cc_sbu_short : %d\n", __func__, cc_status1, pd_data->cc_sbu_short);
}
static u8 max77705_check_rid(void *data)
{
u8 fct_id = 0;
struct max77705_usbc_platform_data *usbc_data = data;
struct max77705_pd_data *pd_data = usbc_data->pd_data;
max77705_read_reg(usbc_data->muic, REG_PD_STATUS1, &pd_data->pd_status1);
fct_id = (pd_data->pd_status1 & BIT_FCT_ID) >> FFS(BIT_FCT_ID);
#if defined(CONFIG_SEC_FACTORY)
factory_execute_monitor(FAC_ABNORMAL_REPEAT_RID);
#endif
max77705_pd_rid(usbc_data, fct_id);
pd_data->fct_id = fct_id;
msg_maxim("%s rid : %d, fct_id : %d\n", __func__, usbc_data->cur_rid, fct_id);
return fct_id;
}
static irqreturn_t max77705_fctid_irq(int irq, void *data)
{
pr_debug("%s: IRQ(%d)_IN\n", __func__, irq);
max77705_check_rid(data);
pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq);
return IRQ_HANDLED;
}
int max77705_pd_init(struct max77705_usbc_platform_data *usbc_data)
{
struct max77705_pd_data *pd_data = NULL;
int ret;
msg_maxim(" IN");
pd_data = usbc_data->pd_data;
pd_noti.pusbpd = usbc_data;
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;
#if defined(CONFIG_PDIC_PD30)
pd_noti.sink_status.pps_voltage = 0;
pd_noti.sink_status.pps_current = 0;
pd_noti.sink_status.request_apdo = false;
pd_noti.sink_status.has_apdo = false;
#endif
pd_noti.event = PDIC_NOTIFY_EVENT_DETACH;
pd_data->pdo_list = false;
pd_data->psrdy_received = false;
pd_data->cc_sbu_short = false;
fp_select_pdo = max77705_select_pdo;
#if defined(CONFIG_PDIC_PD30)
fp_sec_pd_select_pps = max77705_select_pps;
fp_sec_pd_get_apdo_max_power = max77705_get_apdo_max_power;
#endif
pd_data->wqueue = create_singlethread_workqueue("max77705_pd");
if (!pd_data->wqueue) {
pr_err("%s: Fail to Create Workqueue\n", __func__);
goto err_irq;
}
INIT_DELAYED_WORK(&pd_data->retry_work, max77705_pd_retry_work);
wake_lock_init(&pd_data->pdmsg_wake_lock, WAKE_LOCK_SUSPEND,
"pd->pdmsg");
wake_lock_init(&pd_data->datarole_wake_lock, WAKE_LOCK_SUSPEND,
"pd->datarole");
wake_lock_init(&pd_data->ssacc_wake_lock, WAKE_LOCK_SUSPEND,
"pd->ssacc");
wake_lock_init(&pd_data->fct_id_wake_lock, WAKE_LOCK_SUSPEND,
"pd->fctid");
pd_data->irq_pdmsg = usbc_data->irq_base + MAX77705_PD_IRQ_PDMSG_INT;
if (pd_data->irq_pdmsg) {
ret = request_threaded_irq(pd_data->irq_pdmsg,
NULL, max77705_pdmsg_irq,
0,
"pd-pdmsg-irq", usbc_data);
if (ret) {
pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret);
goto err_irq;
}
}
pd_data->irq_psrdy = usbc_data->irq_base + MAX77705_PD_IRQ_PS_RDY_INT;
if (pd_data->irq_psrdy) {
ret = request_threaded_irq(pd_data->irq_psrdy,
NULL, max77705_psrdy_irq,
0,
"pd-psrdy-irq", usbc_data);
if (ret) {
pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret);
goto err_irq;
}
}
pd_data->irq_datarole = usbc_data->irq_base + MAX77705_PD_IRQ_DATAROLE_INT;
if (pd_data->irq_datarole) {
ret = request_threaded_irq(pd_data->irq_datarole,
NULL, max77705_datarole_irq,
0,
"pd-datarole-irq", usbc_data);
if (ret) {
pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret);
goto err_irq;
}
}
pd_data->irq_ssacc = usbc_data->irq_base + MAX77705_PD_IRQ_SSACCI_INT;
if (pd_data->irq_ssacc) {
ret = request_threaded_irq(pd_data->irq_ssacc,
NULL, max77705_ssacc_irq,
0,
"pd-ssacci-irq", usbc_data);
if (ret) {
pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret);
goto err_irq;
}
}
pd_data->irq_fct_id = usbc_data->irq_base + MAX77705_PD_IRQ_FCTIDI_INT;
if (pd_data->irq_fct_id) {
ret = request_threaded_irq(pd_data->irq_fct_id,
NULL, max77705_fctid_irq,
0,
"pd-fctid-irq", usbc_data);
if (ret) {
pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret);
goto err_irq;
}
}
/* check RID value for booting time */
max77705_check_rid(usbc_data);
max77705_set_fw_noautoibus(MAX77705_AUTOIBUS_AT_OFF);
/* check CC Pin state for cable attach booting scenario */
max77705_datarole_irq_handler(usbc_data, CCIC_IRQ_INIT_DETECT);
max77705_check_cc_sbu_short(usbc_data);
max77705_register_pdmsg_func(usbc_data->max77705,
max77705_pd_check_pdmsg_callback, (void *)usbc_data);
msg_maxim(" OUT");
return 0;
err_irq:
kfree(pd_data);
return ret;
}