blob: 6d8504c9c346b261fe5ce5872f03f81dd84b0c15 [file] [log] [blame]
/*****************************************************************************
* Copyright 2001 - 20012 Broadcom Corporation. All rights reserved.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2, available at
* http://www.gnu.org/licenses/old-license/gpl-2.0.html (the "GPL").
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a
* license other than the GPL, without Broadcom's express prior written
* consent.
*
*****************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#ifdef CONFIG_HAS_WAKELOCK
#include <linux/wakelock.h>
#endif
#include <linux/stringify.h>
#include <linux/mfd/bcmpmu59xxx.h>
#include <linux/mfd/bcmpmu59xxx_reg.h>
#include <linux/usb/bcm_hsotgctrl.h>
#include <plat/pi_mgr.h>
#include <mach/pm.h>
#include <plat/kona_reset_reason.h>
#include <linux/io.h>
#include <mach/io_map.h>
#include <mach/rdb_A0/brcm_rdb_hsotg_ctrl.h>
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#endif
#define MAX_EVENT_QUEUE 30
#define BC_MAX_RETRIES 10
#define TYPE2_RETRY 1
#ifdef CONFIG_RT8973
#define ACCY_WORK_DELAY msecs_to_jiffies(500)
#else
#define ACCY_WORK_DELAY msecs_to_jiffies(5)
#endif
#define ACCY_RETRY_DELAY msecs_to_jiffies(500)
static int debug_mask = BCMPMU_PRINT_ERROR |
BCMPMU_PRINT_INIT | BCMPMU_PRINT_FLOW;
static int prev_chrgr_type;
static u32 bc_start;
#define pr_accy(debug_level, args...) \
do { \
if (debug_mask & BCMPMU_PRINT_##debug_level) { \
pr_info(args); \
} \
} while (0)
/* TPROBE_MAX definition */
#define TPROBE_MAX_USEC 16000
#define TPROBE_MAX_MSB_MASK 0xC0
#define TPROBE_MAX_DATA(msb, lsb) ((((msb >> 6) & 0x3) << 8) | \
(lsb & 0xFF))
#define TPROBE_MAX_GET_MSB(data) (((data >> 8) & 0x3) << 6)
#define TPROBE_MAX_GET_LSB(data) (data & 0xFF)
/* pattern to write before accessing PMU BC CTRL reg */
#define PMU_BC_CTRL_OVWR_PATTERN 0x5
static const char *const chrgr_type_str[] = {
[PMU_CHRGR_TYPE_NONE] = __stringify(PMU_CHRGR_TYPE_NONE),
[PMU_CHRGR_TYPE_SDP] = __stringify(PMU_CHRGR_TYPE_SDP),
[PMU_CHRGR_TYPE_CDP] = __stringify(PMU_CHRGR_TYPE_CDP),
[PMU_CHRGR_TYPE_DCP] = __stringify(PMU_CHRGR_TYPE_DCP),
[PMU_CHRGR_TYPE_TYPE1] = __stringify(PMU_CHRGR_TYPE_TYPE1),
[PMU_CHRGR_TYPE_TYPE2] = __stringify(PMU_CHRGR_TYPE_TYPE2),
[PMU_CHRGR_TYPE_PS2] = __stringify(PMU_CHRGR_TYPE_PS2),
[PMU_CHRGR_TYPE_ACA] = __stringify(PMU_CHRGR_TYPE_ACA),
};
#define log_charger_type(x) \
do {\
if ((x) < PMU_CHRGR_TYPE_MAX) \
pr_accy(FLOW, "Charger type = %s\n",\
chrgr_type_str[x]);\
else\
pr_accy(FLOW, "Charger type = unknown\n");\
} while (0)
/* Values in this table need to be revisited */
static int chrgr_curr_lmt[PMU_CHRGR_TYPE_MAX] = {
[PMU_CHRGR_TYPE_NONE] = 0,
[PMU_CHRGR_TYPE_SDP] = 500,
[PMU_CHRGR_TYPE_CDP] = 1500,
[PMU_CHRGR_TYPE_DCP] = 1500,
[PMU_CHRGR_TYPE_TYPE1] = 700,
[PMU_CHRGR_TYPE_TYPE2] = 700,
[PMU_CHRGR_TYPE_PS2] = 100,
[PMU_CHRGR_TYPE_ACA] = 1000,
};
static const int bcmpmu_usb_id_map[PMU_USB_ID_LVL_MAX] = {
[0] = PMU_USB_ID_GROUND,
[1] = PMU_USB_B_DEVICE,
[2] = PMU_USB_ID_RESERVED1,
[3] = PMU_USB_ID_RID_A,
[4] = PMU_USB_ID_RID_B,
[5] = PMU_USB_ID_RID_C,
[6] = PMU_USB_ID_RESERVED2,
[7] = PMU_USB_ID_FLOAT,
};
static struct bcmpmu_accy *bcmpmu_accy;
static void bcmpmu_accy_isr(enum bcmpmu59xxx_irq irq, void *data);
static void usb_handle_state(struct bcmpmu_accy *paccy);
static void bcmpmu_notify_charger_state(struct bcmpmu_accy *paccy);
static void bcmpmu_accy_set_pmu_BC12
(struct bcmpmu59xxx *bcmpmu, int val);
static void bcmpmu_accy_check_BC12_EN(struct bcmpmu59xxx *bcmpmu);
static void bcmpmu_paccy_latch_event(struct bcmpmu_accy *accy,
u32 event, void *para);
static void bcdldo_cycle_power(struct bcmpmu_accy *paccy);
const int *bcmpmu_get_usb_id_map(struct bcmpmu59xxx *bcmpmu, int *len)
{
*len = PMU_USB_ID_LVL_MAX;
return bcmpmu_usb_id_map;
}
static bool bcmpmu_supported_chrgr_usbid
(struct bcmpmu_accy *paccy, int usb_id)
{
bool id_valid = true;
if (paccy->bc == BC_EXT_DETECT)
return id_valid;
switch (usb_id) {
case PMU_USB_ID_GROUND:
case PMU_USB_ID_RID_A:
case PMU_USB_ID_RID_C:
case PMU_USB_ID_RID_B:
case PMU_USB_ID_RESERVED1:
case PMU_USB_ID_RESERVED2:
id_valid = false;
break;
case PMU_USB_ID_FLOAT:
id_valid = true;
break;
default:
id_valid = false;
}
if (!id_valid)
pr_accy(FLOW, "----Charger not supported\n");
return id_valid;
}
static void bcmpmu_set_chrgr_type(struct bcmpmu_accy *paccy, int chrgr)
{
unsigned long flags;
spin_lock_irqsave(&paccy->accy_lock, flags);
paccy->usb_accy_data.chrgr_type = chrgr;
spin_unlock_irqrestore(&paccy->accy_lock, flags);
}
int bcmpmu_get_chrgr_type(struct bcmpmu_accy *paccy)
{
int chrgr = 0;
unsigned long flags;
spin_lock_irqsave(&paccy->accy_lock, flags);
chrgr = paccy->usb_accy_data.chrgr_type;
spin_unlock_irqrestore(&paccy->accy_lock, flags);
return chrgr;
}
EXPORT_SYMBOL(bcmpmu_get_chrgr_type);
static void enable_bc_clock(struct bcmpmu_accy *paccy, bool en)
{
if (paccy->bc == BCMPMU_BC_BB_BC12) {
bcm_hsotgctrl_en_clock(en);
paccy->clock_en = en;
pr_accy(FLOW, "======<%s> paccy clock %x\n"
, __func__, paccy->clock_en);
}
}
static void reset_bc(struct bcmpmu_accy *paccy)
{
if (paccy->bc == BCMPMU_BC_BB_BC12)
bcm_hsotgctrl_bc_reset();
}
static int is_bb_detection_done(u32 status)
{
int ret_status = 0;
if (status & BB_BC_STS_BC_DONE_MSK) {
/*Make sure that only one type bit is set*/
u32 type = status & 0x7F;
if (type && ((type & (type - 1)) == 0))
ret_status = 1;
}
return ret_status;
}
/* for external charger detection apart from PMU/BB*/
__weak int bcm_ext_bc_status(void)
{
return 0;
}
static unsigned int get_bc_status(struct bcmpmu_accy *paccy)
{
u32 status = 0;
unsigned long st;
struct bcmpmu59xxx *bcmpmu = paccy->bcmpmu;
if (paccy->bc == BCMPMU_BC_PMU_BC12) {
bcmpmu->read_dev(bcmpmu,
PMU_REG_MBCCTRL5, (u8 *)&status);
pr_accy(FLOW, " MBCCTRL5 %x\n", status);
status &= MBCCTRL5_CHP_TYP_MASK;
pr_accy(FLOW, "MBCCTRL5 %x mask %x\n",
status, MBCCTRL5_CHP_TYP_MASK);
status = status >> MBCCTRL5_CHP_TYP_SHIFT;
pr_accy(FLOW, " MBCCTRL5 %x shift % d\n",
status, MBCCTRL5_CHP_TYP_SHIFT);
return status;
} else if (paccy->bc == BCMPMU_BC_BB_BC12) {
bcm_hsotgctrl_bc_status(&st);
pr_accy(FLOW, "<%s> = status %x\n", __func__, (u32)st);
if (is_bb_detection_done(st))
return (u32)st;
else
return 0;
} else if (paccy->bc == BC_EXT_DETECT) {
status = bcm_ext_bc_status();
}
return status;
}
/* for external charger detection apart from PMU/BB*/
__weak enum bcmpmu_chrgr_type_t
get_ext_charger_type(struct bcmpmu_accy *paccy, unsigned int bc_status)
{
return 0;
}
static enum bcmpmu_chrgr_type_t get_charger_type(struct bcmpmu_accy *paccy,
unsigned int bc_status)
{
enum bcmpmu_chrgr_type_t type;
u8 chp_typ;
if (paccy->bc == BCMPMU_BC_PMU_BC12) {
chp_typ = (bc_status & PMU_CHP_TYPE_MASK);
if (chp_typ) {
if (chp_typ == PMU_BC_DCP)
type = PMU_CHRGR_TYPE_DCP;
else if (chp_typ == PMU_BC_SDP)
type = PMU_CHRGR_TYPE_SDP;
else if (chp_typ == PMU_BC_CDP)
type = PMU_CHRGR_TYPE_CDP;
} else
type = PMU_CHRGR_TYPE_NONE;
} else if (paccy->bc == BCMPMU_BC_BB_BC12) {
if (bc_status & BB_BC_STS_DCP_MSK)
type = PMU_CHRGR_TYPE_DCP;
else if (bc_status & BB_BC_STS_SDP_MSK)
type = PMU_CHRGR_TYPE_SDP;
else if (bc_status & BB_BC_STS_CDP_MSK)
type = PMU_CHRGR_TYPE_CDP;
else if (bc_status & BB_BC_STS_PS2_MSK)
type = PMU_CHRGR_TYPE_PS2;
else if (bc_status & BB_BC_STS_TYPE1_MSK)
type = PMU_CHRGR_TYPE_TYPE1;
else if (bc_status & BB_BC_STS_TYPE2_MSK)
type = PMU_CHRGR_TYPE_TYPE2;
else if (bc_status & BB_BC_STS_ACA_MSK)
type = PMU_CHRGR_TYPE_ACA;
else
type = PMU_CHRGR_TYPE_NONE;
} else if (paccy->bc == BC_EXT_DETECT) {
type = get_ext_charger_type(paccy, bc_status);
} else
type = PMU_CHRGR_TYPE_NONE;
return type;
}
int bcmpmu_check_vbus()
{
struct bcmpmu59xxx *bcmpmu = bcmpmu_accy->bcmpmu;
u8 reg;
if (!bcmpmu) {
pr_accy(ERROR, "%s: BCMPMU Accy driver not initialized\n",
__func__);
return -1;
}
bcmpmu->read_dev(bcmpmu, PMU_REG_ENV2, &reg);
if (reg)
return 1;
else
return 0;
}
EXPORT_SYMBOL_GPL(bcmpmu_check_vbus);
static void send_usb_event(struct bcmpmu59xxx *pmu,
enum bcmpmu_event_t event, void *para)
{
struct bcmpmu_accy *paccy = (struct bcmpmu_accy *)pmu->accyinfo;
pr_accy(FLOW, "Event send %x\n", event);
blocking_notifier_call_chain(&paccy->bcmpmu->event[event].notifiers,
event, para);
}
char *get_supply_type_str(int chrgr_type)
{
switch (chrgr_type) {
case PMU_CHRGR_TYPE_SDP:
case PMU_CHRGR_TYPE_CDP:
case PMU_CHRGR_TYPE_ACA:
return"bcmpmu_usb";
case PMU_CHRGR_TYPE_DCP:
case PMU_CHRGR_TYPE_TYPE1:
case PMU_CHRGR_TYPE_TYPE2:
case PMU_CHRGR_TYPE_PS2:
return "bcmpmu_ac";
default:
break;
}
return NULL;
}
EXPORT_SYMBOL_GPL(get_supply_type_str);
u32 bcmpmu_get_chrgr_curr_lmt(u32 chrgr_type)
{
if (PMU_CHRGR_TYPE_MAX > chrgr_type)
return chrgr_curr_lmt[chrgr_type];
return 0;
}
EXPORT_SYMBOL_GPL(bcmpmu_get_chrgr_curr_lmt);
static void send_chrgr_event(struct bcmpmu59xxx *pmu,
enum bcmpmu_event_t event, void *para)
{
struct bcmpmu_accy *paccy = (struct bcmpmu_accy *)pmu->accyinfo;
pr_accy(FLOW, "Event send %x\n", event);
blocking_notifier_call_chain(&paccy->bcmpmu->event[event].notifiers,
event, para);
}
static void paccy_set_ldo_bit(struct bcmpmu_accy *paccy, int val)
{
u8 status;
paccy->bcmpmu->read_dev(paccy->bcmpmu,
PMU_REG_MBCCTRL5, &status);
if (val)
status |= USB_DET_LDO_EN_MASK;
else
status &= ~USB_DET_LDO_EN_MASK;
paccy->bcmpmu->write_dev(paccy->bcmpmu,
PMU_REG_MBCCTRL5, status);
pr_accy(FLOW, "###<%s> MBCCTRL5 %x\n", __func__, status);
}
static int paccy_get_ldo_bit(struct bcmpmu_accy *paccy)
{
u8 reg = 0;
paccy->bcmpmu->read_dev(paccy->bcmpmu,
PMU_REG_MBCCTRL5, &reg);
pr_accy(FLOW, "###<%s> MBCCTRL5 %x\n",
__func__, reg);
return (reg & USB_DET_LDO_EN_MASK) ? 1 : 0;
}
static void usb_detect_state(struct bcmpmu_accy *paccy)
{
enum bcmpmu_chrgr_type_t chrgr_type = PMU_USB_TYPE_NONE;
enum bcmpmu_usb_type_t usb_type = PMU_CHRGR_TYPE_NONE;
u32 vbus_status = 0, id_status = 0, bc_status;
struct bcmpmu59xxx *bcmpmu = paccy->bcmpmu;
static u8 type2;
bool id_check = true;
bcmpmu_usb_get(bcmpmu,
BCMPMU_USB_CTRL_GET_SESSION_STATUS,
(void *)&vbus_status);
pr_accy(FLOW, "%s, vbus %d\n",
__func__, vbus_status);
if (paccy->bc != BC_EXT_DETECT) {
bcmpmu_usb_get(bcmpmu,
BCMPMU_USB_CTRL_GET_ID_VALUE,
(void *)&id_status);
pr_accy(FLOW, "%s, vbus %d, id %d\n",
__func__, vbus_status, id_status);
id_check = bcmpmu_supported_chrgr_usbid(paccy, id_status);
}
if (vbus_status && id_check) {
bc_status = get_bc_status(paccy);
pr_accy(FLOW, "%s, bc_status=0x%X, retry=%d\n",
__func__, bc_status, paccy->retry_cnt);
if (bc_status) {
chrgr_type = get_charger_type(paccy,
bc_status);
prev_chrgr_type = chrgr_type;
log_charger_type(chrgr_type);
if (chrgr_type != PMU_CHRGR_TYPE_NONE) {
if ((chrgr_type == PMU_CHRGR_TYPE_TYPE2) &&
(type2 < TYPE2_RETRY) &&
(paccy->retry_cnt < BC_MAX_RETRIES)) {
paccy->det_state = USB_RETRY;
chrgr_type =
PMU_CHRGR_TYPE_NONE;
type2++;
} else {
type2 = 0;
paccy->det_state = USB_CONNECTED;
#ifdef CONFIG_HAS_WAKELOCK
if (!wake_lock_active
(&paccy->wake_lock))
wake_lock(&paccy->wake_lock);
#endif
#ifdef CONFIG_KONA_PI_MGR
pi_mgr_qos_request_update(&paccy->qos,
DEEP_SLEEP_LATENCY);
#endif
switch (chrgr_type) {
case PMU_CHRGR_TYPE_SDP:
usb_type = PMU_USB_TYPE_SDP;
break;
case PMU_CHRGR_TYPE_CDP:
usb_type = PMU_USB_TYPE_CDP;
break;
case PMU_CHRGR_TYPE_ACA:
usb_type = PMU_USB_TYPE_ACA;
break;
default:
usb_type = PMU_USB_TYPE_NONE;
break;
}
}
} else {
paccy->det_state = USB_RETRY;
}
} else {
paccy->det_state = USB_RETRY;
}
paccy->usb_accy_data.usb_type = usb_type;
bcmpmu_set_chrgr_type(paccy, chrgr_type);
} else {
paccy->usb_accy_data.usb_type = PMU_USB_TYPE_NONE;
bcmpmu_set_chrgr_type(paccy, PMU_CHRGR_TYPE_NONE);
paccy->det_state = USB_IDLE;
}
pr_accy(FLOW, "<%s>det_state %d chrgr_type %d\n",
__func__, paccy->det_state,
paccy->usb_accy_data.chrgr_type);
usb_handle_state(paccy);
}
static void usb_handle_state(struct bcmpmu_accy *paccy)
{
int id_status;
bool id_check = true;
switch (paccy->det_state) {
case USB_DETECT:
if (!atomic_read(&paccy->usb_allow_bc_detect))
break;
if (paccy->bc != BC_EXT_DETECT) {
bcmpmu_usb_get(paccy->bcmpmu,
BCMPMU_USB_CTRL_GET_ID_VALUE,
(void *)&id_status);
pr_accy(FLOW, "%s,id =%d\n",
__func__, id_status);
id_check = bcmpmu_supported_chrgr_usbid
(paccy, id_status);
}
if (!id_check)
break;
if (!paccy->clock_en)
enable_bc_clock(paccy, 1);
if (paccy->bc == BCMPMU_BC_BB_BC12) {
bc_start = PMU_BC_DETECTION_START;
bcmpmu_call_notifier(paccy->bcmpmu,
BCMPMU_USB_EVENT_USB_DETECTION,
&bc_start);
}
usb_detect_state(paccy);
break;
case USB_IDLE:
if (paccy->clock_en)
enable_bc_clock(paccy, 0);
if (bc_start == PMU_BC_DETECTION_START) {
bc_start = PMU_BC_DETECTION_END;
bcmpmu_call_notifier(paccy->bcmpmu,
BCMPMU_USB_EVENT_USB_DETECTION,
&bc_start);
}
break;
case USB_RETRY:
pr_accy(FLOW, "%s, retry =%d\n",
__func__, paccy->retry_cnt);
if (paccy->retry_cnt < BC_MAX_RETRIES) {
if (paccy->bc != BC_EXT_DETECT) {
bcdldo_cycle_power(paccy);
reset_bc(paccy);
}
schedule_delayed_work(&paccy->det_work,
ACCY_RETRY_DELAY);
paccy->retry_cnt++;
} else {
pr_accy(ERROR, "%s, failed, retry times=%d\n",
__func__, paccy->retry_cnt);
paccy->usb_accy_data.usb_type = PMU_USB_TYPE_NONE;
bcmpmu_set_chrgr_type(paccy, PMU_CHRGR_TYPE_NONE);
paccy->det_state = USB_IDLE;
paccy->retry_cnt = 0;
if (bc_start == PMU_BC_DETECTION_START) {
bc_start = PMU_BC_DETECTION_END;
bcmpmu_call_notifier(paccy->bcmpmu,
BCMPMU_USB_EVENT_USB_DETECTION,
&bc_start);
}
}
break;
case USB_CONNECTED:
pr_accy(FLOW, "*** Charger Connected event\n");
if (bc_start == PMU_BC_DETECTION_START) {
bc_start = PMU_BC_DETECTION_END;
bcmpmu_call_notifier(paccy->bcmpmu,
BCMPMU_USB_EVENT_USB_DETECTION,
&bc_start);
}
if (paccy->clock_en)
enable_bc_clock(paccy, 0);
if (paccy->bc != BC_EXT_DETECT)
bcmpmu_accy_set_pmu_BC12(paccy->bcmpmu, 0);
bcmpmu_notify_charger_state(paccy);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
case USB_DISCONNECTED:
pr_accy(FLOW, "*** Charger disconnected event\n");
if (bc_start == PMU_BC_DETECTION_START) {
bc_start = PMU_BC_DETECTION_END;
bcmpmu_call_notifier(paccy->bcmpmu,
BCMPMU_USB_EVENT_USB_DETECTION,
&bc_start);
}
paccy->retry_cnt = 0;
paccy->det_state = USB_IDLE;
paccy->usb_accy_data.usb_type = PMU_USB_TYPE_NONE;
bcmpmu_set_chrgr_type(paccy, PMU_CHRGR_TYPE_NONE);
bcmpmu_notify_charger_state(paccy);
/* Disable BC12 for cust platform */
if (paccy->bc != BC_EXT_DETECT) {
bcdldo_cycle_power(paccy);
reset_bc(paccy);
bcmpmu_accy_set_pmu_BC12(paccy->bcmpmu, 1);
}
#ifdef CONFIG_HAS_WAKELOCK
if (wake_lock_active(&paccy->wake_lock))
wake_unlock(&paccy->wake_lock);
/* Make sure the app can process the
* uevent of usb_disconnect
*/
wake_lock_timeout(&paccy->usb_plug_out_wake_lock, HZ * 2);
#endif
#ifdef CONFIG_KONA_PI_MGR
pi_mgr_qos_request_update(&paccy->qos,
PI_MGR_QOS_DEFAULT_VALUE);
#endif
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
default:
break;
}
}
static void bcmpmu_paccy_latch_event(struct bcmpmu_accy *accy,
u32 event, void *para)
{
unsigned long flags;
struct event_list *entry;
if (!list_empty(&accy->free_list->node)) {
entry = list_first_entry(&accy->free_list->node,
struct event_list, node);
spin_lock_irqsave(&accy->accy_lock, flags);
list_del(&entry->node);
/* Add to work List */
entry->event = event;
entry->para = para;
list_add_tail(&entry->node, &accy->dispatch_list->node);
spin_unlock_irqrestore(&accy->accy_lock, flags);
pr_accy(FLOW, "Event Latch %x para %p\n", event, para);
} else {
flush_delayed_work(&accy->det_work);
entry = list_first_entry(&accy->free_list->node,
struct event_list, node);
spin_lock_irqsave(&accy->accy_lock, flags);
list_del(&entry->node);
/* Add to work List */
entry->event = event;
list_add_tail(&entry->node, &accy->dispatch_list->node);
spin_unlock_irqrestore(&accy->accy_lock, flags);
pr_accy(FLOW, "Event Latch %x\n", event);
}
}
static void bcmpmu_accy_isr(enum bcmpmu59xxx_irq irq, void *data)
{
struct bcmpmu_accy *paccy = data;
struct bcmpmu59xxx *bcmpmu = paccy->bcmpmu;
int board_id = bcmpmu->pdata->board_id;
int ret, id_status;
pr_accy(INIT, "#### %s interrupt = %d det state %d\n",
__func__, irq, paccy->det_state);
if (paccy->bc == BC_EXT_DETECT)
bcmpmu_accy_check_BC12_EN(bcmpmu);
switch (irq) {
case PMU_IRQ_VA_SESS_VALID_R:
bc_start = 0;
pr_accy(INIT, "### ISR PMU_IRQ_VA_SESS_VALID_R: %x\n",
PMU_IRQ_VA_SESS_VALID_R);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_SESSION_VALID, NULL);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
case PMU_IRQ_USBINS:
pr_accy(INIT, "### ISR PMU_IRQ_USBINS: %x\n",
PMU_IRQ_USBINS);
if (paccy->bc == BC_EXT_DETECT) {
/* In customer platform(CONFIG_USB_SWITCH_TSU6111),
CHGDET_LATCH and CHGDET_TO are both disabled by BC_ENABLE
because the BC status will be read from TSU. So we need to
move the detection code from CHGDET_LATCH to USBINS. Even
though this problem is found for customer platform, but I
think this move should be good for other platform too, (no
longer need the workaround). */
if (paccy->det_state != USB_CONNECTED) {
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_CHGDET_LATCH,
NULL);
paccy->det_state = USB_DETECT;
usb_handle_state(paccy);
}
}
break;
case PMU_IRQ_USBRM:
pr_accy(INIT, "### ISR PMU_IRQ_USBRM: %x\n",
PMU_IRQ_USBRM);
paccy->det_state = USB_DISCONNECTED;
usb_handle_state(paccy);
break;
case PMU_IRQ_CHGDET_TO:
pr_accy(INIT, "### ISR PMU_IRQ_CHGDET_TO: %x\n",
PMU_IRQ_CHGDET_TO);
if (board_id != EDN01x)
break;
case PMU_IRQ_CHGDET_LATCH:
/* Because of the FFB EDN00 wiring issue (CQ245795),
the PMU charger detection doesn't work and we will get
CHGDET_TO interrupt. To resolve this issue, we
tempararily treat the CHGDET_TO interrupt as
CHGDET_LATCH as a workaround
*/
pr_accy(INIT, "### ISR PMU_IRQ_CHGDET_LATCH: %x\n",
PMU_IRQ_CHGDET_LATCH);
if (paccy->bc != BC_EXT_DETECT) {
if ((paccy->det_state != USB_CONNECTED) &&
(paccy->det_state != USB_RETRY)) {
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_CHGDET_LATCH, NULL);
paccy->det_state = USB_DETECT;
usb_handle_state(paccy);
}
}
break;
case PMU_IRQ_OTG_SESS_VALID_F:
pr_accy(INIT, "### ISR PMU_IRQ_OTG_SESS_VALID_F : %x\n",
PMU_IRQ_OTG_SESS_VALID_F);
if ((get_supply_type_str(prev_chrgr_type) != NULL) &&
(strcmp(get_supply_type_str
(prev_chrgr_type), "bcmpmu_usb") == 0)) {
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_SESSION_INVALID, NULL);
schedule_delayed_work(&paccy->det_work,
ACCY_WORK_DELAY);
}
break;
case PMU_IRQ_VBUS_VALID_R:
pr_accy(INIT, "### ISR PMU_IRQ_VBUS_VALID_R: %x\n",
PMU_IRQ_VBUS_VALID_R);
if (paccy->bc != BC_EXT_DETECT) {
ret = bcmpmu_usb_get(bcmpmu,
BCMPMU_USB_CTRL_GET_ID_VALUE,
&id_status);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_VBUS_VALID, NULL);
if (id_status != PMU_USB_ID_GROUND) {
paccy->det_state = USB_IDLE;
schedule_delayed_work(
&paccy->det_work, ACCY_WORK_DELAY);
} else {
pr_accy(FLOW,
"%s, USB Host mode skipping BC detect\n",
__func__);
}
} else {
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_VBUS_VALID, NULL);
}
break;
case PMU_IRQ_VBUS_VALID_F:
pr_accy(INIT, "### ISR PMU_IRQ_VBUS_VALID_F:%x\n",
PMU_IRQ_VBUS_VALID_F);
if ((get_supply_type_str(prev_chrgr_type) != NULL) &&
(strcmp(get_supply_type_str
(prev_chrgr_type), "bcmpmu_usb") == 0)) {
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_VBUS_INVALID, NULL);
schedule_delayed_work(&paccy->det_work,
ACCY_WORK_DELAY);
}
break;
case PMU_IRQ_IDCHG:
pr_accy(FLOW, "### ISR PMU_IRQ_IDCHG: %x\n",
PMU_IRQ_IDCHG);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_ID_CHANGE, NULL);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
case PMU_IRQ_ADP_CHANGE:
pr_accy(FLOW, "### ISR PMU_IRQ_ADP_CHANGE: %x\n",
PMU_IRQ_ADP_CHANGE);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_ADP_CHANGE, NULL);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
case PMU_IRQ_ADP_SNS_END:
pr_accy(FLOW, "### ISR PMU_IRQ_ADP_SNS_END: %x\n",
PMU_IRQ_ADP_SNS_END);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_ADP_SENSE_END, NULL);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
case PMU_IRQ_VB_SESS_END_F:
pr_accy(FLOW, "### ISR PMU_IRQ_VB_SESS_END_F: %x\n",
PMU_IRQ_VB_SESS_END_F);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_SESSION_END_VALID, NULL);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
case PMU_IRQ_VB_SESS_END_R:
pr_accy(FLOW, "### ISR PMU_IRQ_VB_SESS_END_R: %x\n",
PMU_IRQ_VB_SESS_END_R);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_SESSION_END_VALID, NULL);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
#if 0 /* remove the TO interrupt for the time being */
case PMU_IRQ_CHGDET_TO:
pr_accy(FLOW, "### ISR PMU_IRQ_CHGDET_TO: %x\n",
PMU_IRQ_CHGDET_TO);
break;
#endif
case PMU_IRQ_RESUME_VBUS:
pr_accy(FLOW, "### ISR PMU_IRQ_RESUME_VBUS: %x\n",
PMU_IRQ_RESUME_VBUS);
bcmpmu_paccy_latch_event(paccy,
BCMPMU_CHRGR_EVENT_CHRG_RESUME_VBUS, NULL);
schedule_delayed_work(&paccy->det_work, ACCY_WORK_DELAY);
break;
case PMU_IRQ_USBOV:
pr_accy(FLOW, "### ISR PMU_IRQ_USBOV: %x\n",
PMU_IRQ_USBOV);
bcmpmu_chrgr_usb_en(bcmpmu, 0);
break;
case PMU_IRQ_USBOV_DIS:
case PMU_IRQ_CHGERRDIS:
pr_accy(FLOW, "### ISR %x\n", irq);
if (paccy->det_state == USB_CONNECTED) {
if (!bcmpmu_is_usb_host_enabled(bcmpmu))
bcmpmu_chrgr_usb_en(bcmpmu, 1);
}
break;
default:
break;
}
}
#ifdef CONFIG_DEBUG_FS
static void bcmpmu_accy_debug_init(struct bcmpmu_accy *accy)
{
struct dentry *dentry;
struct bcmpmu59xxx *bcmpmu = accy->bcmpmu;
if (!bcmpmu->dent_bcmpmu) {
pr_err("Failed to initialize debugfs\n");
return;
}
dentry = accy->dent_accy;
dentry = debugfs_create_dir("accy", bcmpmu->dent_bcmpmu);
if (!dentry)
return;
if (!debugfs_create_u32("dbg_mask", S_IWUSR | S_IRUSR,
dentry, &debug_mask))
goto err ;
if (!debugfs_create_u32("chrgr_type", S_IRUSR,
dentry, &accy->usb_accy_data.chrgr_type))
goto err ;
return;
err:
debugfs_remove(dentry);
}
#endif
static void bcmpmu_notify_charger_state(struct bcmpmu_accy *paccy)
{
enum bcmpmu_usb_type_t usb_type = paccy->usb_accy_data.usb_type;
enum bcmpmu_chrgr_type_t chrgr_type;
struct bcmpmu59xxx *bcmpmu = paccy->bcmpmu;
int board_id = bcmpmu->pdata->board_id;
chrgr_type = bcmpmu_get_chrgr_type(paccy);
pr_accy(FLOW, "===chrgr_type %d, usb_type %d\n", chrgr_type, usb_type);
paccy->usb_accy_data.max_curr_chrgr = chrgr_curr_lmt[chrgr_type];
bcmpmu_paccy_latch_event(paccy, BCMPMU_CHRGR_EVENT_CHGR_DETECTION,
&paccy->usb_accy_data.chrgr_type);
if ((usb_type < PMU_USB_TYPE_MAX) &&
(usb_type > PMU_USB_TYPE_NONE)) {
bcmpmu_paccy_latch_event(paccy,
BCMPMU_USB_EVENT_USB_DETECTION,
&paccy->usb_accy_data.usb_type);
if (is_charging_state() || (board_id == EDN01x))
bcmpmu_paccy_latch_event(paccy,
BCMPMU_CHRGR_EVENT_CHRG_CURR_LMT,
&chrgr_curr_lmt[chrgr_type]);
} else
bcmpmu_paccy_latch_event(paccy,
BCMPMU_CHRGR_EVENT_CHRG_CURR_LMT,
&chrgr_curr_lmt[chrgr_type]);
}
void send_usb_insert_event(enum bcmpmu_event_t event, void *para)
{
struct bcmpmu_accy *paccy = bcmpmu_accy;
int usb_type = *(int *)para;
if (!paccy)
return;
paccy->usb_accy_data.usb_type = usb_type;
pr_accy(INIT, "====EXT USB Event send %x usb %d =====\n",
event, usb_type);
}
/* cb for extern charger detection */
void send_chrgr_insert_event(enum bcmpmu_event_t event, void *para)
{
struct bcmpmu_accy *paccy = bcmpmu_accy;
struct bcmpmu59xxx *bcmpmu = paccy->bcmpmu;
int chrgr_type = *(int *)para;
u32 vbus_status = 0;
int valid = 0;
if (!paccy)
return;
pr_accy(INIT, "==== EXT Charger Insert Event ====para %d\n",
*(int *)para);
bcmpmu_usb_get(bcmpmu,
BCMPMU_USB_CTRL_GET_SESSION_STATUS,
(void *)&vbus_status);
if (event == BCMPMU_CHRGR_EVENT_CHGR_DETECTION) {
if ((chrgr_type == PMU_CHRGR_TYPE_SDP) && vbus_status) {
if (paccy->det_state != USB_CONNECTED) {
paccy->usb_accy_data.
chrgr_type = PMU_CHRGR_TYPE_SDP;
#if 0 /*Remove to fix enumeration failure when boot with USB */
paccy->usb_accy_data.
usb_type = PMU_USB_TYPE_SDP;
paccy->det_state = USB_CONNECTED;
send_chrgr_event(paccy->bcmpmu,
BCMPMU_USB_EVENT_USB_DETECTION,
&paccy->usb_accy_data.usb_type);
valid = 1;
#endif
}
} else if ((chrgr_type == PMU_CHRGR_TYPE_DCP) && vbus_status) {
if (paccy->det_state != USB_CONNECTED) {
paccy->usb_accy_data.
chrgr_type = PMU_CHRGR_TYPE_DCP;
paccy->usb_accy_data.
usb_type = PMU_USB_TYPE_NONE;
paccy->det_state = USB_CONNECTED;
valid = 1;
}
} else if (chrgr_type == PMU_CHRGR_TYPE_NONE) {
if (paccy->det_state == USB_CONNECTED) {
paccy->usb_accy_data.
chrgr_type = PMU_CHRGR_TYPE_NONE;
paccy->usb_accy_data.
usb_type = PMU_USB_TYPE_NONE;
paccy->det_state = USB_DISCONNECTED;
valid = 1;
}
}
pr_accy(FLOW, "%s, chrgr change, chrgr_type=0x%X\n",
__func__, paccy->usb_accy_data.chrgr_type);
if (valid) {
bcmpmu_paccy_latch_event(paccy, event,
&paccy->usb_accy_data.chrgr_type);
schedule_delayed_work(&paccy->det_work,
ACCY_WORK_DELAY);
}
}
}
EXPORT_SYMBOL_GPL(send_chrgr_insert_event);
static void bcmpmu_accy_set_pmu_BC12(struct bcmpmu59xxx *bcmpmu, int val)
{
u8 reg;
bcmpmu->read_dev(bcmpmu, PMU_REG_MBCCTRL5, &reg);
pr_accy(FLOW, "###<%s> MBCCTRL5 %x\n", __func__, reg);
if (val)
reg |= MBCCTRL5_BC12_EN_MASK;
else
reg &= ~MBCCTRL5_BC12_EN_MASK;
bcmpmu->write_dev(bcmpmu, PMU_REG_MBCCTRL5, reg);
pr_accy(FLOW, "###<%s> MBCCTRL5 %x\n", __func__, reg);
}
static void bcmpmu_accy_check_BC12_EN(struct bcmpmu59xxx *bcmpmu)
{
u8 reg;
bcmpmu->read_dev(bcmpmu, PMU_REG_MBCCTRL5, &reg);
pr_accy(FLOW, "###<%s> MBCCTRL5 %x\n", __func__, reg);
if (reg & MBCCTRL5_BC12_EN_MASK)
pr_accy(FLOW, "<%s> BC12 is enabled\n", __func__);
else
pr_accy(FLOW, "<%s> BC12 is disabled\n", __func__);
}
static void bcdldo_cycle_power(struct bcmpmu_accy *paccy)
{
pr_accy(FLOW, "<%s> MBCCTRL5 %x\n", __func__);
paccy_set_ldo_bit(paccy, 0);
/* Scope trace shows the decay time of the BCDLDO line
* is about 4ms. Hence a 5ms delay here to ensure proper
* power cycle.
*/
usleep_range(6000, 8000);
paccy_set_ldo_bit(paccy, 1);
}
/*
static void bcmpmu_update_pmu_chrgr_type(struct bcmpmu_accy *paccy)
{
struct bcmpmu59xxx *bcmpmu = paccy->bcmpmu;
enum bcmpmu_chrgr_type_t type = paccy->usb_accy_data.chrgr_type;
u8 chrgr = 0;
if (paccy->bc == BCMPMU_BC_BB_BC12) {
if (type)
chrgr = type << MBCCTRL5_CHP_TYP_SHIFT;
pr_accy(FLOW, "###<%s> MBCCTRL5 charger type bc %x pmu %x\n",
__func__, type, chrgr);
bcmpmu->write_dev(bcmpmu,
PMU_REG_MBCCTRL5, chrgr);
}
}
*/
/* workq func */
static void usb_deferred_work(struct work_struct *work)
{
struct list_head *pos, *temp;
u32 event, id_status = 0;
void *para;
unsigned long flags;
struct event_list *entry ;
struct bcmpmu_accy *paccy =
container_of(work, struct bcmpmu_accy, det_work.work);
struct bcmpmu59xxx *bcmpmu = paccy->bcmpmu;
bool id_check = true;
u32 vbus_status;
if (paccy->bc != BC_EXT_DETECT) {
bcmpmu_usb_get(bcmpmu,
BCMPMU_USB_CTRL_GET_ID_VALUE,
(void *)&id_status);
id_check = bcmpmu_supported_chrgr_usbid
(paccy, id_status);
pr_accy(FLOW, "%s, id %x, vbus %d\n",
__func__, id_status, vbus_status);
bcmpmu_usb_get(bcmpmu,
BCMPMU_USB_CTRL_GET_SESSION_STATUS,
(void *)&vbus_status);
}
pr_accy(FLOW, "###<%s> , DEV_STATE %x\n",
__func__, paccy->det_state);
if (paccy->bc != BC_EXT_DETECT && id_check && vbus_status &&
(bc_start != PMU_BC_DETECTION_END)) {
schedule_delayed_work(&paccy->det_work,
ACCY_RETRY_DELAY);
}
list_for_each_safe(pos, temp, (&paccy->dispatch_list->node)) {
if (!list_empty(&paccy->dispatch_list->node)) {
entry = list_first_entry(&paccy->dispatch_list->node,
struct event_list, node);
if (!entry)
break;
/* Add to work List */
pr_accy(FLOW, "###<%s> , Event %x\n",
__func__, entry->event);
spin_lock_irqsave(&paccy->accy_lock, flags);
event = entry->event;
para = entry->para;
list_del(&entry->node);
list_add_tail(&entry->node, &paccy->free_list->node);
spin_unlock_irqrestore(&paccy->accy_lock, flags);
bcmpmu_call_notifier(bcmpmu, event, para);
}
}
switch (paccy->det_state) {
case USB_IDLE:
/*
bcdldo_cycle_power(paccy);
reset_bc(paccy);
if (paccy->clock_en)
enable_bc_clock(paccy, 0);
*/
break;
case USB_DETECT:
break;
case USB_RETRY:
usb_detect_state(paccy);
break;
default:
break;
}
}
static void usb_adp_work(struct work_struct *work)
{
struct bcmpmu_accy *data =
container_of(work, struct bcmpmu_accy, adp_work);
if (data->adp_cal_done == 0) {
data->adp_cal_done = 1;
bcmpmu_call_notifier(data->bcmpmu,
BCMPMU_USB_EVENT_ADP_CALIBRATION_DONE, NULL);
}
}
static void usb_delayed_init_work(struct work_struct *work)
{
struct bcmpmu_accy *paccy =
container_of(work, struct bcmpmu_accy, init_work.work);
usb_handle_state(paccy);
}
int bcmpmu_usb_otg_bost_en(struct bcmpmu59xxx *bcmpmu, bool en)
{
int ret = 0;
u8 reg;
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL1, &reg);
/*
OTGCTRL1_OTG_SHUTDOWNB = 1 => Enable the OTG block
OTGCTRL1_OFFVBUSB = 1 => enable Vbus boost
*/
if (en)
reg |= OTGCTRL1_OFFVBUSB_MASK |
OTGCTRL1_OTG_SHUTDOWNB_MASK;
else
reg &= ~(OTGCTRL1_OFFVBUSB_MASK |
OTGCTRL1_OTG_SHUTDOWNB_MASK);
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL1, reg);
pr_accy(FLOW, "%s,en = %d, reg = %x\n", __func__, en,
reg);
return ret;
}
int bcmpmu_usb_set(struct bcmpmu59xxx *bcmpmu,
int ctrl, unsigned long data)
{
struct bcmpmu_accy *paccy = (struct bcmpmu_accy *)bcmpmu->accyinfo;
int ret = 0;
u8 temp;
u32 val;
pr_accy(FLOW, "%s, ctrl=%d, val=0x%lX\n", __func__, ctrl, data);
switch (ctrl) {
case BCMPMU_USB_CTRL_CHRG_CURR_LMT:
if (!is_charging_state()) {
paccy->usb_accy_data.max_curr_chrgr = (int)data;
bcmpmu_call_notifier(paccy->bcmpmu,
BCMPMU_CHRGR_EVENT_CHRG_CURR_LMT,
&paccy->usb_accy_data.max_curr_chrgr);
}
break;
case BCMPMU_USB_CTRL_VBUS_ON_OFF:
ret = bcmpmu_usb_otg_bost_en(bcmpmu, !!data);
break;
case BCMPMU_USB_CTRL_SET_VBUS_DEB_TIME:
ret = bcmpmu->read_dev(bcmpmu, PMU_REG_OTGCTRL2, &temp);
temp &= (~OTGCTRL2_VBUS_ADP_COMP_DB_MASK);
temp |= (data & OTGCTRL2_VBUS_ADP_COMP_DB_MASK);
ret = bcmpmu->write_dev(bcmpmu, PMU_REG_OTGCTRL2, temp);
break;
case BCMPMU_USB_CTRL_SRP_VBUS_PULSE:
ret = bcmpmu->read_dev(bcmpmu, PMU_REG_OTGCTRL1, &temp);
if (data == 0)
temp &= (~OTGCTRL1_EN_VBUS_PULSE_MASK);
else
temp |= (OTGCTRL1_EN_VBUS_PULSE_MASK);
ret = bcmpmu->write_dev(bcmpmu, PMU_REG_OTGCTRL1, temp);
break;
case BCMPMU_USB_CTRL_DISCHRG_VBUS:
ret = bcmpmu->read_dev(bcmpmu, PMU_REG_OTGCTRL1, &temp);
if (data == 0)
temp &= (~OTGCTRL1_EN_PD_SRP_MASK);
else
temp |= (OTGCTRL1_EN_PD_SRP_MASK);
ret = bcmpmu->write_dev(bcmpmu, PMU_REG_OTGCTRL1, temp);
break;
case BCMPMU_USB_CTRL_START_STOP_ADP_SENS_PRB:
if (data == 0)
ret = bcmpmu->write_dev(bcmpmu, PMU_REG_OTGCTRL2, 0);
else {
if (paccy->otg_block_enabled == 0) {
bcmpmu->read_dev(bcmpmu, PMU_REG_OTGCTRL1,
&temp);
temp |= (1 << OTGCTRL1_OTG_SHUTDOWNB_SHIFT);
ret =
bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL1,
temp);
paccy->otg_block_enabled = 1;
}
if (paccy->adp_block_enabled == 0) {
bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL12,
&temp);
temp |= (1 << OTGCTRL12_ADP_THR_CTRL_SHIFT);
ret =
bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL12,
temp);
paccy->adp_block_enabled = 1;
}
if (paccy->adp_sns_comp == 0) {
bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL12,
&temp);
temp |= (1 << OTGCTRL12_EN_ADP_SNS_COMP_SHIFT);
ret =
bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL12,
temp);
paccy->adp_sns_comp = 1;
}
bcmpmu->read_dev(bcmpmu, PMU_REG_OTGCTRL2,
&temp);
temp |= (1 << OTGCTRL2_ADP_SNS_SHIFT);
ret = bcmpmu->write_dev(bcmpmu, PMU_REG_OTGCTRL2,
temp);
}
break;
case BCMPMU_USB_CTRL_START_STOP_ADP_PRB:
if (data == 0) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL11, &temp);
temp &= ~OTGCTRL11_ADP_PRB_MASK;
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL11, temp);
} else {
if (paccy->otg_block_enabled == 0) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL11, &temp);
temp |= OTGCTRL1_OTG_SHUTDOWNB_MASK;
ret = bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL11, temp);
paccy->otg_block_enabled = 1;
}
if (paccy->adp_block_enabled == 0) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL12,
&temp);
temp |= OTGCTRL12_ADP_SHUTDOWNB_MASK;
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL12,
temp);
paccy->adp_block_enabled = 1;
}
if (paccy->adp_prob_comp == 0) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL12, &temp);
temp |= OTGCTRL12_EN_ADP_PRB_COMPS_MASK;
ret = bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL12, temp);
paccy->adp_prob_comp = 1;
}
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL11, &temp);
temp |= OTGCTRL11_ADP_PRB_MASK;
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL11, temp);
}
break;
case BCMPMU_USB_CTRL_START_ADP_CAL_PRB:
if (paccy->otg_block_enabled == 0) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL1, &temp);
temp |= OTGCTRL1_OTG_SHUTDOWNB_MASK;
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL1, temp);
paccy->otg_block_enabled = 1;
}
if (paccy->adp_block_enabled == 0) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL12, &temp);
temp |= OTGCTRL12_ADP_SHUTDOWNB_MASK;
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL12, temp);
paccy->adp_block_enabled = 1;
}
if (paccy->adp_prob_comp == 0) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL12, &temp);
temp |= OTGCTRL12_EN_ADP_PRB_COMPS_MASK ;
ret = bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL12, temp);
paccy->adp_prob_comp = 1;
}
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL11, &temp);
temp |= (PMU_USB_ADP_MODE_CALIBRATE <<
OTGCTRL11_ADP_PRB_MODE_MASK);
temp |= OTGCTRL11_ADP_PRB_MASK;
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL11, temp);
/* start timer */
schedule_work(&paccy->adp_work);
break;
case BCMPMU_USB_CTRL_SET_ADP_PRB_MOD:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL11, &temp);
temp &= ~OTGCTRL11_ADP_PRB_MODE_MASK;
temp |= (data << OTGCTRL11_ADP_PRB_MODE_SHIFT);
ret = bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL11, temp);
break;
case BCMPMU_USB_CTRL_SET_ADP_PRB_CYC_TIME:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL2, &temp);
temp &= ~OTGCTRL2_ADP_PRB_CYCLE_TIME_MASK;
temp |= (data << OTGCTRL2_ADP_PRB_CYCLE_TIME_SHIFT);
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL2, temp);
break;
case BCMPMU_USB_CTRL_SET_ADP_COMP_METHOD:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL12, &temp);
temp &= ~OTGCTRL12_ADP_COMP_METHOD_MASK;
temp |= (data << OTGCTRL12_ADP_COMP_METHOD_SHIFT);
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL12, temp);
break;
case BCMPMU_USB_CTRL_SW_UP:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_MBCCTRL3, &temp);
if (data == 0)
temp &= ~MBCCTRL3_SWUP_MASK;
else
temp |= MBCCTRL3_SWUP_MASK;
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_MBCCTRL3, temp);
break;
case BCMPMU_USB_CTRL_TPROBE_MAX:
if (data <= TPROBE_MAX_USEC) {
val = (data * 2 * 1000) / 31250;
bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL9, &temp);
temp &= ~TPROBE_MAX_MSB_MASK;
temp |= TPROBE_MAX_GET_MSB(val);
ret = bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL9, temp);
ret |= bcmpmu->write_dev(bcmpmu,
PMU_REG_OTGCTRL10,
TPROBE_MAX_GET_LSB(val));
}
break;
case BCMPMU_USB_CTRL_ALLOW_BC_DETECT:
if (data == 1) {
atomic_set(&paccy->usb_allow_bc_detect, 1);
if (paccy->det_state != USB_CONNECTED) {
pr_accy(FLOW, "== usb_allow_bc_detect\n");
paccy->retry_cnt = 0;
paccy->det_state = USB_DETECT;
schedule_delayed_work(&paccy->init_work,
ACCY_RETRY_DELAY);
}
} else
atomic_set(&paccy->usb_allow_bc_detect, 0);
break;
case BCMPMU_USB_CTRL_BCDLDO:
if (data == 1)
paccy_set_ldo_bit(paccy, 1);
else
paccy_set_ldo_bit(paccy, 0);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(bcmpmu_usb_set);
int bcmpmu_usb_get(struct bcmpmu59xxx *bcmpmu,
int ctrl, void *data)
{
int ret = -EINVAL;
struct bcmpmu_accy *paccy = (struct bcmpmu_accy *)bcmpmu->accyinfo;
u8 val, val1;
int tprobe;
switch (ctrl) {
case BCMPMU_USB_CTRL_TPROBE_MAX:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL9, &val);
ret |= bcmpmu->read_dev(bcmpmu,
PMU_REG_OTGCTRL10, &val1);
tprobe = TPROBE_MAX_DATA(val, val1);
val = ((tprobe * 31250) / (2 * 1000));
break;
case BCMPMU_USB_CTRL_GET_ADP_CHANGE_STATUS:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTG_STS1,
&val);
val &= OTG_STS1_ADP_ATTACH_DET_MASK;
val = val >> OTG_STS1_ADP_ATTACH_DET_SHIFT;
break;
/*
case BCMPMU_USB_CTRL_GET_ADP_SENSE_TIMER_VALUE:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ADP_SNS_TM,
&val);
val &= ADP_SNS_TM_MASK;
val = val >> ADP_SNS_TM_SHIFT;
break;
*/
case BCMPMU_USB_CTRL_GET_ADP_SENSE_STATUS:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_OTG_STS1,
&val);
val &= val & OTG_STS1_ADP_SNS_DET_MASK;
val = val >> OTG_STS1_ADP_SNS_DET_SHIFT;
break;
#ifdef CONFIG_MFD_BCM59055
case BCMPMU_USB_CTRL_GET_ADP_PRB_RISE_TIMES:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ADP_STATUS_RISE_TIMES_LSB,
&val,
bcmpmu->
regmap
[PMU_REG_ADP_STATUS_RISE_TIMES_LSB].
mask);
val = val >>
bcmpmu->
regmap[PMU_REG_ADP_STATUS_RISE_TIMES_LSB].shift;
if (ret != 0)
break;
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ADP_STATUS_RISE_TIMES_MSB,
&val1,
bcmpmu->
regmap
[PMU_REG_ADP_STATUS_RISE_TIMES_MSB].
mask);
val1 = val1 >>
bcmpmu->regmap[PMU_REG_ADP_STATUS_RISE_TIMES_MSB].shift;
val = val | val1 << 8;
break;
#endif
case BCMPMU_USB_CTRL_GET_VBUS_STATUS:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ENV4,
&val);
val &= ENV4_VBUS_STATUS_MASK;
val = val >> ENV4_VBUS_STATUS_SHIFT;
break;
case BCMPMU_USB_CTRL_GET_SESSION_STATUS:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ENV2,
&val);
pr_accy(FLOW, "ENV2 val %x , usb valid mask %x\n",
val, ENV2_P_UBPD_CHG_SHIFT);
val &= ENV2_P_UBPD_CHG;
val = val >> ENV2_P_UBPD_CHG_SHIFT;
break;
case BCMPMU_USB_CTRL_GET_USB_VALID:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ENV2,
&val);
val &= ENV2_USB_VALID;
val = (val >> ENV2_USB_VALID_SHIFT);
break;
case BCMPMU_USB_CTRL_GET_UBPD_INT:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ENV2,
&val);
val &= ENV2_P_UBPD_INT;
val = (val >> ENV2_P_UBPD_INT_SHIFT);
break;
case BCMPMU_USB_CTRL_GET_SESSION_END_STATUS:
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ENV7,
&val);
val &= OTG_VBUS_STAT_MASK;
val = val >> OTG_VBUS_STAT_SHIFT;
break;
case BCMPMU_USB_CTRL_GET_ID_VALUE:
if (paccy->bc != BC_EXT_DETECT) {
ret = bcmpmu->read_dev(bcmpmu,
PMU_REG_ENV4,
&val);
val &= ENV4_ID_CODE_MASK;
val = val >> ENV4_ID_CODE_SHIFT;
if ((paccy->usb_id_map) &&
(val < paccy->usb_id_map_len))
val = paccy->usb_id_map[val];
pr_accy(FLOW, "ENV4 %x val, id mak %x\n",
val, ENV4_ID_CODE_SHIFT);
} else
val = PMU_USB_ID_FLOAT;
break;
/* client for bcmpmu-accy notification can not register
*before accy driver get probed if, USB/Charger is
*plugged while system boot up, em driver
*will miss the first notification. So,
* BCMPMU_USB_CTRL_GET_CHRGR_TYPE is required
*to know the type explicitly
*/
case BCMPMU_USB_CTRL_GET_CHRGR_TYPE:
ret = 0;
val = bcmpmu_get_chrgr_type(paccy);
break;
case BCMPMU_USB_CTRL_GET_USB_TYPE:
ret = 0;
val = paccy->usb_accy_data.usb_type;
break;
case BCMPMU_USB_CTRL_BCDLDO:
val = paccy_get_ldo_bit(paccy);
break;
default:
ret = -EINVAL;
break;
}
if ((ret == 0) && (data != NULL))
*(unsigned int *)data = val;
pr_accy(FLOW, "%s, ctrl=%d, val=0x%X\n", __func__, ctrl,
((data) ? (*(unsigned int *)data) : 0));
return ret;
}
EXPORT_SYMBOL_GPL(bcmpmu_usb_get);
static int bcmpmu_accy_eventq_init(struct bcmpmu_accy *accy)
{
struct event_list *ev_list;
int i;
accy->free_list = kmalloc(sizeof(struct event_list), GFP_ATOMIC);
if (!accy->free_list)
return -ENOMEM;
accy->dispatch_list = kmalloc(sizeof(struct event_list), GFP_ATOMIC);
if (!accy->dispatch_list)
goto err;
INIT_LIST_HEAD(&accy->free_list->node);
INIT_LIST_HEAD(&accy->dispatch_list->node);
ev_list = kmalloc(sizeof(struct event_list) *
MAX_EVENT_QUEUE, GFP_ATOMIC);
if (!ev_list)
goto err;
for (i = 0; i < MAX_EVENT_QUEUE; i++)
list_add_tail(&ev_list[i].node, &accy->free_list->node);
return 0;
err:
kfree(accy->free_list);
return -ENOMEM;
}
static void bcmpmu_accy_dinit_eventq(struct bcmpmu_accy *accy)
{
struct list_head *pos, *temp;
struct event_list *entry ;
list_for_each_safe(pos, temp, (&accy->dispatch_list->node)) {
if (!list_empty(&accy->dispatch_list->node)) {
entry = list_first_entry(&accy->dispatch_list->node,
struct event_list, node);
if (!entry)
break;
list_del(&entry->node);
kfree(entry);
}
}
list_for_each_safe(pos, temp, (&accy->free_list->node)) {
if (!list_empty(&accy->free_list->node)) {
entry = list_first_entry(&accy->free_list->node,
struct event_list, node);
if (!entry)
break;
list_del(&entry->node);
kfree(entry);
}
}
kfree(accy->free_list);
}
static int bcmpmu_accy_probe(struct platform_device *pdev)
{
int ret;
struct bcmpmu59xxx *bcmpmu = dev_get_drvdata(pdev->dev.parent);
struct bcmpmu_accy *paccy;
struct bcmpmu59xxx_platform_data *pdata ;
pdata = bcmpmu->pdata;
pr_accy(INIT, "%s, called\n", __func__);
paccy = kzalloc(sizeof(struct bcmpmu_accy), GFP_KERNEL);
if (paccy == NULL) {
pr_accy(INIT, "%s: failed to alloc mem.\n", __func__);
ret = -ENOMEM;
goto err;
}
paccy->bcmpmu = bcmpmu;
bcmpmu->accyinfo = paccy;
bcmpmu_accy = paccy;
paccy->usb_get = bcmpmu_usb_get;
paccy->usb_set = bcmpmu_usb_set;
paccy->usb_accy_data.usb_type = PMU_USB_TYPE_NONE;
paccy->usb_accy_data.chrgr_type = PMU_CHRGR_TYPE_NONE;
paccy->usb_accy_data.max_curr_chrgr = 0;
paccy->adp_cal_done = 0;
paccy->clock_en = 0;
paccy->usb_id_map = bcmpmu_get_usb_id_map
(bcmpmu, &paccy->usb_id_map_len);
paccy->otg_block_enabled = 0;
paccy->adp_block_enabled = 0;
paccy->adp_prob_comp = 0;
paccy->adp_sns_comp = 0;
paccy->bc = pdata->bc;
paccy->piggyback_chrg = pdata->piggyback_chrg;
spin_lock_init(&paccy->accy_lock);
INIT_WORK(&paccy->adp_work, usb_adp_work);
INIT_DELAYED_WORK(&paccy->det_work, usb_deferred_work);
INIT_DELAYED_WORK(&paccy->init_work, usb_delayed_init_work);
ret = bcmpmu_accy_eventq_init(paccy);
if (ret)
goto err;
#ifdef CONFIG_HAS_WAKELOCK
wake_lock_init(&paccy->wake_lock, WAKE_LOCK_SUSPEND, "usb_accy");
wake_lock_init(&paccy->usb_plug_out_wake_lock,
WAKE_LOCK_SUSPEND, "usb_accy_plug_out");
#endif
#ifdef CONFIG_KONA_PI_MGR
pi_mgr_qos_add_request(&paccy->qos, "usb_accy",
PI_MGR_PI_ID_ARM_CORE,
PI_MGR_QOS_DEFAULT_VALUE);
#endif
bcmpmu->register_irq(bcmpmu, PMU_IRQ_USBINS,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_USBRM,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_CHGDET_LATCH,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_CHGDET_TO,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_VA_SESS_VALID_R,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_VBUS_VALID_R,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_OTG_SESS_VALID_F,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_VBUS_VALID_F,
bcmpmu_accy_isr, paccy);
#ifndef CONFIG_MACH_HAWAII_SS_COMMON
bcmpmu->register_irq(bcmpmu, PMU_IRQ_IDCHG,
bcmpmu_accy_isr, paccy);
#endif
bcmpmu->register_irq(bcmpmu, PMU_IRQ_RESUME_VBUS,
bcmpmu_accy_isr, paccy);
#ifndef CONFIG_SEC_CHARGING_FEATURE
/* register_irq for PMU_IRQ_USBOV/PMU_IRQ_USBOV_DIS is called by
bcmpmu59xxx-spa-pb.c, so disable USBOV ISR in bcmpmu59xxx-accy.c */
bcmpmu->register_irq(bcmpmu, PMU_IRQ_USBOV,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_USBOV_DIS,
bcmpmu_accy_isr, paccy);
bcmpmu->register_irq(bcmpmu, PMU_IRQ_CHGERRDIS,
bcmpmu_accy_isr, paccy);
#endif
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_USBINS);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_USBRM);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_CHGDET_LATCH);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_CHGDET_TO);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_VA_SESS_VALID_R);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_VBUS_VALID_R);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_OTG_SESS_VALID_F);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_VBUS_VALID_F);
#ifndef CONFIG_MACH_HAWAII_SS_COMMON
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_IDCHG);
#endif
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_RESUME_VBUS);
#ifndef CONFIG_SEC_CHARGING_FEATURE
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_USBOV);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_USBOV_DIS);
bcmpmu->unmask_irq(bcmpmu, PMU_IRQ_CHGERRDIS);
#endif
/*
* For BB's BC detection we need to select BCDAVDD33 pin
*/
if (paccy->bc == BCMPMU_BC_BB_BC12) {
bcm_hsotgctrl_set_bc_iso(false);
paccy_set_ldo_bit(paccy, 1);
bcmpmu_accy_set_pmu_BC12(paccy->bcmpmu, 1);
reset_bc(paccy);
} else
bcm_hsotgctrl_set_bc_iso(true);
/* Disable BC12 for cust platform */
if (paccy->bc == BC_EXT_DETECT) {
bcmpmu_accy_check_BC12_EN(paccy->bcmpmu);
bcmpmu_accy_set_pmu_BC12(paccy->bcmpmu, 0);
paccy_set_ldo_bit(paccy, 0);
}
#ifdef CONFIG_DEBUG_FS
bcmpmu_accy_debug_init(paccy);
#endif
paccy->det_state = USB_DETECT;
if (paccy->bc == BC_EXT_DETECT) {
schedule_delayed_work(&paccy->init_work,
msecs_to_jiffies(15000));
} else {
schedule_delayed_work(&paccy->init_work, ACCY_RETRY_DELAY);
}
return 0;
err:
kfree(paccy);
return ret;
}
static int bcmpmu_accy_remove(struct platform_device *pdev)
{
struct bcmpmu59xxx *bcmpmu = pdev->dev.platform_data;
struct bcmpmu_accy *paccy = bcmpmu->accyinfo;
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_USBINS);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_USBRM);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_CHGDET_LATCH);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_CHGDET_TO);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_VA_SESS_VALID_R);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_VBUS_VALID_R);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_OTG_SESS_VALID_F);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_VBUS_VALID_F);
#ifndef CONFIG_MACH_HAWAII_SS_COMMON
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_IDCHG);
#endif
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_RESUME_VBUS);
#ifndef CONFIG_SEC_CHARGING_FEATURE
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_USBOV);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_USBOV_DIS);
bcmpmu->unregister_irq(bcmpmu, PMU_IRQ_CHGERRDIS);
#endif
cancel_delayed_work_sync(&paccy->det_work);
cancel_work_sync(&paccy->adp_work);
#ifdef CONFIG_HAS_WAKELOCK
wake_lock_destroy(&paccy->wake_lock);
wake_lock_destroy(&paccy->usb_plug_out_wake_lock);
#endif
#ifdef CONFIG_KONA_PI_MGR
pi_mgr_qos_request_remove(&paccy->qos);
#endif
bcmpmu_accy_dinit_eventq(paccy);
#ifdef CONFIG_DEBUG_FS
debugfs_remove(paccy->dent_accy);
#endif
kfree(paccy);
return 0;
}
static struct platform_driver bcmpmu_accy_driver = {
.driver = {
.name = "bcmpmu_accy",
},
.probe = bcmpmu_accy_probe,
.remove = bcmpmu_accy_remove,
};
static int __init bcmpmu_accy_init(void)
{
return platform_driver_register(&bcmpmu_accy_driver);
}
subsys_initcall(bcmpmu_accy_init);
static void __exit bcmpmu_accy_exit(void)
{
platform_driver_unregister(&bcmpmu_accy_driver);
}
module_exit(bcmpmu_accy_exit);
MODULE_DESCRIPTION("BCM PMIC accy driver");
MODULE_LICENSE("GPL");