/* Copyright 2019 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/*
 * IDT P9221-R7 Wireless Power Receiver driver.
 */

#include "p9221.h"
#include "charge_manager.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "power.h"
#include "tcpm.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_pd.h"
#include "util.h"
#include <stdbool.h>
#include "printf.h"

#define CPRINTS(format, args...) cprints(CC_USBPD, "WPC " format, ## args)

#define P9221_TX_TIMEOUT_MS		(20 * 1000*1000)
#define P9221_DCIN_TIMEOUT_MS		(2 * 1000*1000)
#define P9221_VRECT_TIMEOUT_MS		(2 * 1000*1000)
#define P9221_NOTIFIER_DELAY_MS		(80*1000)
#define P9221R7_ILIM_MAX_UA		(1600 * 1000)
#define P9221R7_OVER_CHECK_NUM		3

#define OVC_LIMIT			1
#define OVC_THRESHOLD			1400000
#define OVC_BACKOFF_LIMIT		900000
#define OVC_BACKOFF_AMOUNT		100000

/* P9221  parameters */
static struct wpc_charger_info p9221_charger_info = {
	.online = false,
	.i2c_port = I2C_PORT_WPC,
	.pp_buf_valid = false,
};

static struct wpc_charger_info *wpc = &p9221_charger_info;

static void p9221_set_offline(void);

static const uint32_t p9221_ov_set_lut[] = {
	17000000, 20000000, 15000000, 13000000,
	11000000, 11000000, 11000000, 11000000
};

static int p9221_reg_is_8_bit(uint16_t reg)
{
	switch (reg) {
	case P9221_CHIP_REVISION_REG:
	case P9221R7_VOUT_SET_REG:
	case P9221R7_ILIM_SET_REG:
	case P9221R7_CHARGE_STAT_REG:
	case P9221R7_EPT_REG:
	case P9221R7_SYSTEM_MODE_REG:
	case P9221R7_COM_CHAN_RESET_REG:
	case P9221R7_COM_CHAN_SEND_SIZE_REG:
	case P9221R7_COM_CHAN_SEND_IDX_REG:
	case P9221R7_COM_CHAN_RECV_SIZE_REG:
	case P9221R7_COM_CHAN_RECV_IDX_REG:
	case P9221R7_DEBUG_REG:
	case P9221R7_EPP_Q_FACTOR_REG:
	case P9221R7_EPP_TX_GUARANTEED_POWER_REG:
	case P9221R7_EPP_TX_POTENTIAL_POWER_REG:
	case P9221R7_EPP_TX_CAPABILITY_FLAGS_REG:
	case P9221R7_EPP_RENEGOTIATION_REG:
	case P9221R7_EPP_CUR_RPP_HEADER_REG:
	case P9221R7_EPP_CUR_NEGOTIATED_POWER_REG:
	case P9221R7_EPP_CUR_MAXIMUM_POWER_REG:
	case P9221R7_EPP_CUR_FSK_MODULATION_REG:
	case P9221R7_EPP_REQ_RPP_HEADER_REG:
	case P9221R7_EPP_REQ_NEGOTIATED_POWER_REG:
	case P9221R7_EPP_REQ_MAXIMUM_POWER_REG:
	case P9221R7_EPP_REQ_FSK_MODULATION_REG:
	case P9221R7_VRECT_TARGET_REG:
	case P9221R7_VRECT_KNEE_REG:
	case P9221R7_FOD_SECTION_REG:
	case P9221R7_VRECT_ADJ_REG:
	case P9221R7_ALIGN_X_ADC_REG:
	case P9221R7_ALIGN_Y_ADC_REG:
	case P9221R7_ASK_MODULATION_DEPTH_REG:
	case P9221R7_OVSET_REG:
	case P9221R7_EPP_TX_SPEC_REV_REG:
		return true;
	default:
		return false;
	}
}

static int p9221_read8(uint16_t reg, int *val)
{
	return i2c_read_offset16(wpc->i2c_port, P9221_R7_ADDR_FLAGS,
				 reg, val, 1);
}

static int p9221_write8(uint16_t reg, int val)
{
	return i2c_write_offset16(wpc->i2c_port, P9221_R7_ADDR_FLAGS,
				  reg, val, 1);
}

static int p9221_read16(uint16_t reg, int *val)
{
	return i2c_read_offset16(wpc->i2c_port, P9221_R7_ADDR_FLAGS,
				 reg, val, 2);
}

static int p9221_write16(uint16_t reg, int val)
{
	return i2c_write_offset16(wpc->i2c_port, P9221_R7_ADDR_FLAGS,
				  reg, val, 2);
}

static int p9221_block_read(uint16_t reg, uint8_t *data, int len)
{
	return i2c_read_offset16_block(wpc->i2c_port, P9221_R7_ADDR_FLAGS,
				       reg, data, len);
}

static int p9221_block_write(uint16_t reg, uint8_t *data, int len)
{
	return i2c_write_offset16_block(wpc->i2c_port, P9221_R7_ADDR_FLAGS,
					reg, data, len);
}

static int p9221_set_cmd_reg(uint8_t cmd)
{
	int cur_cmd;
	int retry;
	int ret;

	for (retry = 0; retry < P9221_COM_CHAN_RETRIES; retry++) {
		ret = p9221_read8(P9221_COM_REG, &cur_cmd);
		if (ret == EC_SUCCESS && cur_cmd == 0)
			break;
		msleep(25);
	}

	if (retry >= P9221_COM_CHAN_RETRIES) {
		CPRINTS("Failed to wait for cmd free %02x", cur_cmd);
		return EC_ERROR_TIMEOUT;
	}

	ret = p9221_write8(P9221_COM_REG, cmd);
	if (ret)
		CPRINTS("Failed to set cmd reg %02x: %d", cmd, ret);

	return ret;
}

/* Convert a register value to uV, Hz, or uA */
static int p9221_convert_reg_r7(uint16_t reg, uint16_t raw_data, uint32_t *val)
{
	switch (reg) {
	case P9221R7_ALIGN_X_ADC_REG:	/* raw */
	case P9221R7_ALIGN_Y_ADC_REG:	/* raw */
		*val = raw_data;
		break;
	case P9221R7_VOUT_ADC_REG:	/* 12-bit ADC raw */
	case P9221R7_IOUT_ADC_REG:	/* 12-bit ADC raw */
	case P9221R7_DIE_TEMP_ADC_REG:	/* 12-bit ADC raw */
	case P9221R7_EXT_TEMP_REG:
		*val = raw_data & 0xFFF;
		break;
	case P9221R7_VOUT_SET_REG:	/* 0.1V -> uV */
		*val = raw_data * 100 * 1000;
		break;
	case P9221R7_IOUT_REG:		/* mA -> uA */
	case P9221R7_VRECT_REG:		/* mV -> uV */
	case P9221R7_VOUT_REG:		/* mV -> uV */
	case P9221R7_OP_FREQ_REG:	/* kHz -> Hz */
	case P9221R7_TX_PINGFREQ_REG:	/* kHz -> Hz */
		*val = raw_data * 1000;
		break;
	case P9221R7_ILIM_SET_REG:	/* 100mA -> uA, 200mA offset */
		*val = ((raw_data * 100) + 200) * 1000;
		break;
	case P9221R7_OVSET_REG:		/* uV */
		raw_data &= P9221R7_OVSET_MASK;
		*val = p9221_ov_set_lut[raw_data];
		break;
	default:
		return -2;
	}

	return 0;
}

static int p9221_reg_read_converted(uint16_t reg, uint32_t *val)
{
	int ret;
	int data;

	if (p9221_reg_is_8_bit(reg))
		ret = p9221_read8(reg, &data);
	else
		ret = p9221_read16(reg, &data);

	if (ret)
		return ret;

	return p9221_convert_reg_r7(reg, data, val);
}

static int p9221_is_online(void)
{
	int chip_id;

	if (p9221_read16(P9221_CHIP_ID_REG, &chip_id)
			|| chip_id != P9221_CHIP_ID)
		return false;
	else
		return true;
}

int wpc_chip_is_online(void)
{
	return p9221_is_online();
}


void p9221_interrupt(enum gpio_signal signal)
{
	task_wake(TASK_ID_WPC);
}

static int p9221r7_clear_interrupts(uint16_t mask)
{
	int ret;

	ret = p9221_write16(P9221R7_INT_CLEAR_REG, mask);
	if (ret) {
		CPRINTS("Failed to clear INT reg: %d", ret);
		return ret;
	}

	ret = p9221_set_cmd_reg(P9221_COM_CLEAR_INT_MASK);
	if (ret)
		CPRINTS("Failed to reset INT: %d", ret);

	return ret;
}

/*
 * Enable interrupts on the P9221 R7, note we don't really need to disable
 * interrupts since when the device goes out of field, the P9221 is reset.
 */
static int p9221_enable_interrupts_r7(void)
{
	uint16_t mask = 0;
	int ret;

	CPRINTS("Enable interrupts");

	mask = P9221R7_STAT_LIMIT_MASK | P9221R7_STAT_CC_MASK
		| P9221_STAT_VRECT;

	p9221r7_clear_interrupts(mask);

	ret = p9221_write8(P9221_INT_ENABLE_REG, mask);
	if (ret)
		CPRINTS("Failed to enable INTs: %d", ret);
	return ret;
}

static int p9221_send_csp(uint8_t status)
{
	int ret;

	CPRINTS("Send CSP=%d", status);
	mutex_lock(&wpc->cmd_lock);

	ret = p9221_write8(P9221R7_CHARGE_STAT_REG, status);
	if (ret == EC_SUCCESS)
		ret = p9221_set_cmd_reg(P9221R7_COM_SENDCSP);

	mutex_unlock(&wpc->cmd_lock);
	return ret;
}

static int p9221_send_eop(uint8_t reason)
{
	int rv;

	CPRINTS("Send EOP reason=%d", reason);
	mutex_lock(&wpc->cmd_lock);

	rv = p9221_write8(P9221R7_EPT_REG, reason);
	if (rv == EC_SUCCESS)
		rv = p9221_set_cmd_reg(P9221R7_COM_SENDEPT);

	mutex_unlock(&wpc->cmd_lock);
	return rv;
}

static void print_current_samples(uint32_t *iout_val, int count)
{
	int i;
	char temp[P9221R7_OVER_CHECK_NUM * 9 + 1] = { 0 };

	for (i = 0; i < count ; i++)
		snprintf(temp + i * 9, sizeof(temp) - i * 9,
			  "%08x ", iout_val[i]);
	CPRINTS("OVER IOUT_SAMPLES: %s", temp);
}


/*
 * Number of times to poll the status to see if the current limit condition
 * was transient or not.
 */
static void p9221_limit_handler_r7(uint16_t orign_irq_src)
{
	uint8_t reason;
	int i;
	int ret;
	int ovc_count = 0;
	uint32_t iout_val[P9221R7_OVER_CHECK_NUM] = { 0 };
	int irq_src = (int)orign_irq_src;

	CPRINTS("OVER INT: %02x", irq_src);

	if (irq_src & P9221R7_STAT_OVV) {
		reason = P9221_EOP_OVER_VOLT;
		goto send_eop;
	}

	if (irq_src & P9221R7_STAT_OVT) {
		reason = P9221_EOP_OVER_TEMP;
		goto send_eop;
	}

	if ((irq_src & P9221R7_STAT_UV) && !(irq_src & P9221R7_STAT_OVC))
		return;

	reason = P9221_EOP_OVER_CURRENT;
	for (i = 0; i < P9221R7_OVER_CHECK_NUM; i++) {
		ret = p9221r7_clear_interrupts(
				irq_src & P9221R7_STAT_LIMIT_MASK);
		msleep(50);
		if (ret)
			continue;

		ret = p9221_reg_read_converted(P9221R7_IOUT_REG, &iout_val[i]);
		if (ret) {
			CPRINTS("Failed to read IOUT[%d]: %d", i, ret);
			continue;
		} else if (iout_val[i] > OVC_THRESHOLD) {
			ovc_count++;
		}

		ret = p9221_read16(P9221_STATUS_REG, &irq_src);
		if (ret) {
			CPRINTS("Failed to read status: %d", ret);
			continue;
		}

		if ((irq_src & P9221R7_STAT_OVC) == 0) {
			print_current_samples(iout_val, i + 1);
			CPRINTS("OVER condition %04x cleared after %d tries",
				irq_src, i);
			return;
		}

		CPRINTS("OVER status is still %04x, retry", irq_src);
	}

	if (ovc_count < OVC_LIMIT) {
		print_current_samples(iout_val, P9221R7_OVER_CHECK_NUM);
		CPRINTS("ovc_threshold=%d, ovc_count=%d, ovc_limit=%d",
			OVC_THRESHOLD, ovc_count, OVC_LIMIT);
		return;
	}

send_eop:
	CPRINTS("OVER is %04x, sending EOP %d", irq_src, reason);

	ret = p9221_send_eop(reason);
	if (ret)
		CPRINTS("Failed to send EOP %d: %d", reason, ret);
}

static void p9221_abort_transfers(void)
{
	wpc->tx_busy = false;
	wpc->tx_done = true;
	wpc->rx_done = true;
	wpc->rx_len = 0;
}

/* Handler for r7 and R7 chips */
static void p9221r7_irq_handler(uint16_t irq_src)
{
	int res;

	if (irq_src & P9221R7_STAT_LIMIT_MASK)
		p9221_limit_handler_r7(irq_src);

	/* Receive complete */
	if (irq_src & P9221R7_STAT_CCDATARCVD) {
		int rxlen = 0;

		res = p9221_read8(P9221R7_COM_CHAN_RECV_SIZE_REG, &rxlen);
		if (res)
			CPRINTS("Failed to read len: %d", res);

		if (rxlen) {
			res = p9221_block_read(P9221R7_DATA_RECV_BUF_START,
					       wpc->rx_buf, rxlen);
			if (res) {
				CPRINTS("Failed to read CC data: %d", res);
				rxlen = 0;
			}

			wpc->rx_len = rxlen;
			wpc->rx_done = true;
		}
	}

	/* Send complete */
	if (irq_src & P9221R7_STAT_CCSENDBUSY) {
		wpc->tx_busy = false;
		wpc->tx_done = true;
	}

	/* Proprietary packet */
	if (irq_src & P9221R7_STAT_PPRCVD) {
		res = p9221_block_read(P9221R7_DATA_RECV_BUF_START,
				       wpc->pp_buf, sizeof(wpc->pp_buf));
		if (res) {
			CPRINTS("Failed to read PP: %d", res);
			wpc->pp_buf_valid = false;
			return;
		}

		/* We only care about PP which come with 0x4F header */
		wpc->pp_buf_valid = (wpc->pp_buf[0] == 0x4F);

		hexdump(wpc->pp_buf, sizeof(wpc->pp_buf));
	}

	/* CC Reset complete */
	if (irq_src & P9221R7_STAT_CCRESET)
		p9221_abort_transfers();
}

static int p9221_is_epp(void)
{
	int ret, reg;
	uint32_t vout_uv;

	if (p9221_read8(P9221R7_SYSTEM_MODE_REG, &reg) == EC_SUCCESS)
		return reg & P9221R7_SYSTEM_MODE_EXTENDED_MASK;

	/* Check based on power supply voltage */
	ret = p9221_reg_read_converted(P9221R7_VOUT_ADC_REG, &vout_uv);
	if (ret) {
		CPRINTS("Failed to read VOUT_ADC: %d", ret);
		return false;
	}

	CPRINTS("Voltage is %duV", vout_uv);
	if (vout_uv > P9221_EPP_THRESHOLD_UV)
		return true;

	return false;
}

static void p9221_config_fod(void)
{

	int epp;
	uint8_t *fod;
	int fod_len;
	int ret;
	int retries = 3;

	CPRINTS("Config FOD");

	epp = p9221_is_epp();
	fod_len = epp ? board_get_epp_fod(&fod) : board_get_fod(&fod);
	if (!fod_len || !fod) {
		CPRINTS("FOD data not found");
		return;
	}

	while (retries) {
		uint8_t fod_read[fod_len];

		CPRINTS("Writing %s FOD (n=%d try=%d)",
			epp ? "EPP" : "BPP", fod_len, retries);

		ret = p9221_block_write(P9221R7_FOD_REG, fod, fod_len);
		if (ret)
			goto no_fod;

		/* Verify the FOD has been written properly */
		ret = p9221_block_read(P9221R7_FOD_REG, fod_read, fod_len);
		if (ret)
			goto no_fod;

		if (memcmp(fod, fod_read, fod_len) == 0)
			return;

		hexdump(fod_read, fod_len);

		retries--;
		msleep(100);
	}

no_fod:
	CPRINTS("Failed to set FOD. retries:%d ret:%d", retries, ret);
}

static void p9221_set_online(void)
{
	int ret;

	CPRINTS("Set online");

	wpc->online = true;

	wpc->tx_busy = false;
	wpc->tx_done = true;
	wpc->rx_done = false;
	wpc->charge_supplier = CHARGE_SUPPLIER_WPC_BPP;

	ret = p9221_enable_interrupts_r7();
	if (ret)
		CPRINTS("Failed to enable INT: %d", ret);

	/* NOTE: depends on _is_epp() which is not valid until DC_IN */
	p9221_config_fod();
}

static void p9221_vbus_check_timeout(void)
{
	CPRINTS("Timeout VBUS, online=%d", wpc->online);
	if (wpc->online)
		p9221_set_offline();

}
DECLARE_DEFERRED(p9221_vbus_check_timeout);

static void p9221_set_offline(void)
{
	CPRINTS("Set offline");

	wpc->online = false;
	/* Reset PP buf so we can get a new serial number next time around */
	wpc->pp_buf_valid = false;

	p9221_abort_transfers();

	hook_call_deferred(&p9221_vbus_check_timeout_data, -1);
}

/* P9221_NOTIFIER_DELAY_MS from VRECTON */
static int p9221_notifier_check_det(void)
{
	if (wpc->online)
		goto done;

	/* send out a FOD but is_epp() is still invalid */
	p9221_set_online();

	/* Give the vbus 2 seconds to come up. */
	CPRINTS("Waiting VBUS");
	hook_call_deferred(&p9221_vbus_check_timeout_data, -1);
	hook_call_deferred(&p9221_vbus_check_timeout_data,
			   P9221_DCIN_TIMEOUT_MS);

done:
	wpc->p9221_check_det = false;
	return 0;
}

static int p9221_get_charge_supplier(void)
{
	if (!wpc->online)
		return EC_ERROR_UNKNOWN;

	if (p9221_is_epp()) {
		uint32_t tx_id;
		int txmf_id;
		int ret;

		wpc->charge_supplier = CHARGE_SUPPLIER_WPC_EPP;

		ret = p9221_read16(P9221R7_EPP_TX_MFG_CODE_REG, &txmf_id);
		if (ret || txmf_id != P9221_GPP_TX_MF_ID)
			return ret;

		ret = p9221_block_read(P9221R7_PROP_TX_ID_REG,
				       (uint8_t *) &tx_id,
				       P9221R7_PROP_TX_ID_SIZE);
		if (ret)
			return ret;

		if (tx_id & P9221R7_PROP_TX_ID_GPP_MASK)
			wpc->charge_supplier = CHARGE_SUPPLIER_WPC_GPP;

		CPRINTS("txmf_id=0x%04x tx_id=0x%08x supplier=%d",
			txmf_id, tx_id, wpc->charge_supplier);
	} else {
		wpc->charge_supplier = CHARGE_SUPPLIER_WPC_BPP;
		CPRINTS("supplier=%d", wpc->charge_supplier);
	}

	return EC_SUCCESS;
}

static int p9221_get_icl(int charge_supplier)
{
	switch (charge_supplier) {
	case CHARGE_SUPPLIER_WPC_EPP:
	case CHARGE_SUPPLIER_WPC_GPP:
		return P9221_DC_ICL_EPP_MA;
	case CHARGE_SUPPLIER_WPC_BPP:
	default:
		return P9221_DC_ICL_BPP_MA;
	}
}

static int p9221_get_ivl(int charge_supplier)
{
	switch (charge_supplier) {
	case CHARGE_SUPPLIER_WPC_EPP:
	case CHARGE_SUPPLIER_WPC_GPP:
		return P9221_DC_IVL_EPP_MV;
	case CHARGE_SUPPLIER_WPC_BPP:
	default:
		return P9221_DC_IVL_BPP_MV;
	}
}

static void p9221_update_charger(int type, struct charge_port_info *chg)
{
	if (!chg)
		charge_manager_update_dualrole(0, CAP_UNKNOWN);
	else
		charge_manager_update_dualrole(0, CAP_DEDICATED);

	charge_manager_update_charge(type, 0, chg);
}

static int p9221_reg_write_converted_r7(uint16_t reg, uint32_t val)
{
	int ret = 0;
	uint16_t data;
	int i;
	/* Do the appropriate conversion */
	switch (reg) {
	case P9221R7_ILIM_SET_REG:
		/* uA -> 0.1A, offset 0.2A */
		if ((val < 200000) || (val > 1600000))
			return -EC_ERROR_INVAL;
		data = (val / (100 * 1000)) - 2;
		break;
	case P9221R7_VOUT_SET_REG:
		/* uV -> 0.1V */
		val /= 1000;
		if (val < 3500 || val > 9000)
			return -EC_ERROR_INVAL;
		data = val / 100;
		break;
	case P9221R7_OVSET_REG:
		/* uV */
		for (i = 0; i < ARRAY_SIZE(p9221_ov_set_lut); i++) {
			if (val == p9221_ov_set_lut[i])
				break;
		}
		if (i == ARRAY_SIZE(p9221_ov_set_lut))
			return -EC_ERROR_INVAL;
		data = i;
		break;
	default:
		return -EC_ERROR_INVAL;
	}
	if (p9221_reg_is_8_bit(reg))
		ret = p9221_write8(reg, data);
	else
		ret = p9221_write16(reg, data);
	return ret;
}

static int p9221_set_dc_icl(void)
{
	/* Increase the IOUT limit */
	if (p9221_reg_write_converted_r7(P9221R7_ILIM_SET_REG,
					 P9221R7_ILIM_MAX_UA))
		CPRINTS("%s set rx_iout limit fail.", __func__);

	return EC_SUCCESS;
}


static void p9221_notifier_check_vbus(void)
{
	struct charge_port_info chg;

	wpc->p9221_check_vbus = false;

	CPRINTS("%s online:%d vbus:%d is_online:%d", __func__, wpc->online,
		wpc->vbus_status);

	/*
	 * We now have confirmation from DC_IN, kill the timer, p9221_online
	 * will be set by this function.
	 */
	hook_call_deferred(&p9221_vbus_check_timeout_data, -1);

	if (wpc->vbus_status) {
		/* WPC VBUS on ,Always write FOD, check dc_icl, send CSP */
		p9221_set_dc_icl();
		p9221_config_fod();

		p9221_send_csp(1);

		/* when wpc vbus attached after 2s, set wpc online */
		if (!wpc->online)
			p9221_set_online();

		/* WPC VBUS on , update charge voltage and current */
		p9221_get_charge_supplier();
		chg.voltage = p9221_get_ivl(wpc->charge_supplier);
		chg.current = p9221_get_icl(wpc->charge_supplier);

		p9221_update_charger(wpc->charge_supplier, &chg);
	} else {
		/*
		 * Vbus detached, set wpc offline and update wpc charge voltage
		 * and current to zero.
		 */
		if (wpc->online) {
			p9221_set_offline();
			p9221_update_charger(wpc->charge_supplier, NULL);
		}
	}

	CPRINTS("check_vbus changed on:%d vbus:%d", wpc->online,
		wpc->vbus_status);

}

static void p9221_detect_work(void)
{

	CPRINTS("%s online:%d check_vbus:%d check_det:%d vbus:%d", __func__,
		wpc->online, wpc->p9221_check_vbus, wpc->p9221_check_det,
		wpc->vbus_status);

	/* Step 1 */
	if (wpc->p9221_check_det)
		p9221_notifier_check_det();

	/* Step 2 */
	if (wpc->p9221_check_vbus)
		p9221_notifier_check_vbus();

}
DECLARE_DEFERRED(p9221_detect_work);

void p9221_notify_vbus_change(int vbus)
{
	wpc->p9221_check_vbus = true;
	wpc->vbus_status = vbus;
	hook_call_deferred(&p9221_detect_work_data, P9221_NOTIFIER_DELAY_MS);
}

void wireless_power_charger_task(void *u)
{
	while (1) {
		int ret, irq_src;
		task_wait_event(-1);

		ret = p9221_read16(P9221_INT_REG, &irq_src);
		if (ret) {
			CPRINTS("Failed to read INT REG");
			continue;
		}

		CPRINTS("INT SRC 0x%04x", irq_src);

		if (p9221r7_clear_interrupts(irq_src))
			continue;

		if (irq_src & P9221_STAT_VRECT) {
			CPRINTS("VRECTON, online=%d", wpc->online);
			if (!wpc->online) {
				wpc->p9221_check_det = true;
				hook_call_deferred(&p9221_detect_work_data,
						   P9221_NOTIFIER_DELAY_MS);
			}
		}

		p9221r7_irq_handler(irq_src);
	}
}
