mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-11-01 02:21:16 +00:00
fcea656ff7
Request the BSI RX, TX and ERR interrupts using level high triggering instead of rising edge triggering. If an interrupt is edge triggered, then its interrupt handler will be called upon enabling the interrupt if it physically triggered while it was disabled in software. This leads to unexpected interrupt handler calls in the qpnp-bsi driver when the following sequence occurs: BIF transaction using interrupts (IRQs enabled), BIF transaction using polling (IRQs disabled), BIF transaction to enter interrupt mode bus state (IRQs enabled). Switching the interrupts to use level triggering fixes the issue because any interrupt physically triggered during a polling transaction with the BSI interrupts disabled will be ignored by the irq framework. Change-Id: I57a6fb6f75af14970b5a186009ec506e240c151d Signed-off-by: David Collins <collinsd@codeaurora.org>
1783 lines
45 KiB
C
1783 lines
45 KiB
C
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spmi.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/bif/driver.h>
|
|
#include <linux/qpnp/qpnp-adc.h>
|
|
|
|
enum qpnp_bsi_irq {
|
|
QPNP_BSI_IRQ_ERR,
|
|
QPNP_BSI_IRQ_RX,
|
|
QPNP_BSI_IRQ_TX,
|
|
QPNP_BSI_IRQ_COUNT,
|
|
};
|
|
|
|
enum qpnp_bsi_com_mode {
|
|
QPNP_BSI_COM_MODE_IRQ,
|
|
QPNP_BSI_COM_MODE_POLL,
|
|
};
|
|
|
|
struct qpnp_bsi_chip {
|
|
struct bif_ctrl_desc bdesc;
|
|
struct spmi_device *spmi_dev;
|
|
struct bif_ctrl_dev *bdev;
|
|
struct work_struct slave_irq_work;
|
|
u16 base_addr;
|
|
u16 batt_id_stat_addr;
|
|
int r_pullup_ohm;
|
|
int vid_ref_uV;
|
|
int tau_index;
|
|
int tau_sampling_mask;
|
|
enum bif_bus_state state;
|
|
enum qpnp_bsi_com_mode com_mode;
|
|
int irq[QPNP_BSI_IRQ_COUNT];
|
|
atomic_t irq_flag[QPNP_BSI_IRQ_COUNT];
|
|
int batt_present_irq;
|
|
enum qpnp_vadc_channels batt_id_adc_channel;
|
|
struct qpnp_vadc_chip *vadc_dev;
|
|
};
|
|
|
|
#define QPNP_BSI_DRIVER_NAME "qcom,qpnp-bsi"
|
|
|
|
enum qpnp_bsi_registers {
|
|
QPNP_BSI_REG_TYPE = 0x04,
|
|
QPNP_BSI_REG_SUBTYPE = 0x05,
|
|
QPNP_BSI_REG_STATUS = 0x08,
|
|
QPNP_BSI_REG_ENABLE = 0x46,
|
|
QPNP_BSI_REG_CLEAR_ERROR = 0x4F,
|
|
QPNP_BSI_REG_FORCE_BCL_LOW = 0x51,
|
|
QPNP_BSI_REG_TAU_CONFIG = 0x52,
|
|
QPNP_BSI_REG_MODE = 0x53,
|
|
QPNP_BSI_REG_RX_TX_ENABLE = 0x54,
|
|
QPNP_BSI_REG_TX_DATA_LOW = 0x5A,
|
|
QPNP_BSI_REG_TX_DATA_HIGH = 0x5B,
|
|
QPNP_BSI_REG_TX_CTRL = 0x5D,
|
|
QPNP_BSI_REG_RX_DATA_LOW = 0x60,
|
|
QPNP_BSI_REG_RX_DATA_HIGH = 0x61,
|
|
QPNP_BSI_REG_RX_SOURCE = 0x62,
|
|
QPNP_BSI_REG_BSI_ERROR = 0x70,
|
|
};
|
|
|
|
#define QPNP_BSI_TYPE 0x02
|
|
#define QPNP_BSI_SUBTYPE 0x10
|
|
|
|
#define QPNP_BSI_STATUS_ERROR 0x10
|
|
#define QPNP_BSI_STATUS_TX_BUSY 0x08
|
|
#define QPNP_BSI_STATUS_RX_BUSY 0x04
|
|
#define QPNP_BSI_STATUS_TX_GO_BUSY 0x02
|
|
#define QPNP_BSI_STATUS_RX_DATA_READY 0x01
|
|
|
|
#define QPNP_BSI_ENABLE_MASK 0x80
|
|
#define QPNP_BSI_ENABLE 0x80
|
|
#define QPNP_BSI_DISABLE 0x00
|
|
|
|
#define QPNP_BSI_TAU_CONFIG_SAMPLE_MASK 0x10
|
|
#define QPNP_BSI_TAU_CONFIG_SAMPLE_8X 0x10
|
|
#define QPNP_BSI_TAU_CONFIG_SAMPLE_4X 0x00
|
|
#define QPNP_BSI_TAU_CONFIG_SPEED_MASK 0x07
|
|
|
|
#define QPNP_BSI_MODE_TX_PULSE_MASK 0x10
|
|
#define QPNP_BSI_MODE_TX_PULSE_INT 0x10
|
|
#define QPNP_BSI_MODE_TX_PULSE_DATA 0x00
|
|
#define QPNP_BSI_MODE_RX_PULSE_MASK 0x08
|
|
#define QPNP_BSI_MODE_RX_PULSE_INT 0x08
|
|
#define QPNP_BSI_MODE_RX_PULSE_DATA 0x00
|
|
#define QPNP_BSI_MODE_TX_PULSE_T_MASK 0x04
|
|
#define QPNP_BSI_MODE_TX_PULSE_T_WAKE 0x04
|
|
#define QPNP_BSI_MODE_TX_PULSE_T_1_TAU 0x00
|
|
#define QPNP_BSI_MODE_RX_FORMAT_MASK 0x02
|
|
#define QPNP_BSI_MODE_RX_FORMAT_17_BIT 0x02
|
|
#define QPNP_BSI_MODE_RX_FORMAT_11_BIT 0x00
|
|
#define QPNP_BSI_MODE_TX_FORMAT_MASK 0x01
|
|
#define QPNP_BSI_MODE_TX_FORMAT_17_BIT 0x01
|
|
#define QPNP_BSI_MODE_TX_FORMAT_11_BIT 0x00
|
|
|
|
#define QPNP_BSI_TX_ENABLE_MASK 0x80
|
|
#define QPNP_BSI_TX_ENABLE 0x80
|
|
#define QPNP_BSI_TX_DISABLE 0x00
|
|
#define QPNP_BSI_RX_ENABLE_MASK 0x40
|
|
#define QPNP_BSI_RX_ENABLE 0x40
|
|
#define QPNP_BSI_RX_DISABLE 0x00
|
|
|
|
#define QPNP_BSI_TX_DATA_HIGH_MASK 0x07
|
|
|
|
#define QPNP_BSI_TX_CTRL_GO 0x01
|
|
|
|
#define QPNP_BSI_RX_DATA_HIGH_MASK 0x07
|
|
|
|
#define QPNP_BSI_RX_SRC_LOOPBACK_FLAG 0x10
|
|
|
|
#define QPNP_BSI_BSI_ERROR_CLEAR 0x80
|
|
|
|
#define QPNP_SMBB_BAT_IF_BATT_PRES_MASK 0x80
|
|
#define QPNP_SMBB_BAT_IF_BATT_ID_MASK 0x01
|
|
|
|
#define QPNP_BSI_NUM_CLOCK_PERIODS 8
|
|
|
|
struct qpnp_bsi_tau {
|
|
int period_4x_ns[QPNP_BSI_NUM_CLOCK_PERIODS];
|
|
int period_8x_ns[QPNP_BSI_NUM_CLOCK_PERIODS];
|
|
int period_4x_us[QPNP_BSI_NUM_CLOCK_PERIODS];
|
|
int period_8x_us[QPNP_BSI_NUM_CLOCK_PERIODS];
|
|
};
|
|
|
|
/* Tau BIF clock periods in ns supported by BSI for either 4x or 8x sampling. */
|
|
static const struct qpnp_bsi_tau qpnp_bsi_tau_period = {
|
|
.period_4x_ns = {
|
|
150420, 122080, 61040, 31670, 15830, 7920, 3960, 2080
|
|
},
|
|
.period_8x_ns = {
|
|
150420, 122080, 63330, 31670, 15830, 7920, 4170, 2080
|
|
},
|
|
.period_4x_us = {
|
|
151, 122, 61, 32, 16, 8, 4, 2
|
|
},
|
|
.period_8x_us = {
|
|
151, 122, 64, 32, 16, 8, 4, 2
|
|
},
|
|
|
|
};
|
|
#define QPNP_BSI_MIN_CLOCK_SPEED_NS 2080
|
|
#define QPNP_BSI_MAX_CLOCK_SPEED_NS 150420
|
|
|
|
#define QPNP_BSI_MIN_PULLUP_OHM 1000
|
|
#define QPNP_BSI_MAX_PULLUP_OHM 500000
|
|
#define QPNP_BSI_DEFAULT_PULLUP_OHM 100000
|
|
#define QPNP_BSI_MIN_VID_REF_UV 500000
|
|
#define QPNP_BSI_MAX_VID_REF_UV 5000000
|
|
#define QPNP_BSI_DEFAULT_VID_REF_UV 1800000
|
|
|
|
/* These have units of tau_bif. */
|
|
#define QPNP_BSI_MAX_TRANSMIT_CYCLES 46
|
|
#define QPNP_BSI_MIN_RECEIVE_CYCLES 24
|
|
#define QPNP_BSI_MAX_BUS_QUERY_CYCLES 17
|
|
|
|
/*
|
|
* Maximum time in microseconds for a slave to transition from suspend to active
|
|
* state.
|
|
*/
|
|
#define QPNP_BSI_MAX_SLAVE_ACTIVIATION_DELAY_US 50
|
|
|
|
/*
|
|
* Maximum time in milliseconds for a slave to transition from power down to
|
|
* active state.
|
|
*/
|
|
#define QPNP_BSI_MAX_SLAVE_POWER_UP_DELAY_MS 10
|
|
|
|
#define QPNP_BSI_POWER_UP_LOW_DELAY_US 240
|
|
|
|
/*
|
|
* Latencies that are used when determining if polling or interrupts should be
|
|
* used for a given transaction.
|
|
*/
|
|
#define QPNP_BSI_MAX_IRQ_LATENCY_US 170
|
|
#define QPNP_BSI_MAX_BSI_DATA_READ_LATENCY_US 16
|
|
|
|
static int qpnp_bsi_set_bus_state(struct bif_ctrl_dev *bdev, int state);
|
|
|
|
static inline int qpnp_bsi_read(struct qpnp_bsi_chip *chip, u16 addr, u8 *buf,
|
|
int len)
|
|
{
|
|
int rc;
|
|
|
|
rc = spmi_ext_register_readl(chip->spmi_dev->ctrl,
|
|
chip->spmi_dev->sid, chip->base_addr + addr, buf, len);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: spmi_ext_register_readl() failed. sid=%d, addr=%04X, len=%d, rc=%d\n",
|
|
__func__, chip->spmi_dev->sid, chip->base_addr + addr,
|
|
len, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static inline int qpnp_bsi_write(struct qpnp_bsi_chip *chip, u16 addr, u8 *buf,
|
|
int len)
|
|
{
|
|
int rc;
|
|
|
|
rc = spmi_ext_register_writel(chip->spmi_dev->ctrl,
|
|
chip->spmi_dev->sid, chip->base_addr + addr, buf, len);
|
|
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: spmi_ext_register_writel() failed. sid=%d, addr=%04X, len=%d, rc=%d\n",
|
|
__func__, chip->spmi_dev->sid, chip->base_addr + addr,
|
|
len, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
enum qpnp_bsi_rx_tx_state {
|
|
QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF,
|
|
QPNP_BSI_RX_TX_STATE_RX_OFF_TX_DATA,
|
|
QPNP_BSI_RX_TX_STATE_RX_OFF_TX_INT,
|
|
QPNP_BSI_RX_TX_STATE_RX_INT_TX_DATA,
|
|
QPNP_BSI_RX_TX_STATE_RX_DATA_TX_DATA,
|
|
QPNP_BSI_RX_TX_STATE_RX_INT_TX_OFF,
|
|
};
|
|
|
|
static int qpnp_bsi_rx_tx_config(struct qpnp_bsi_chip *chip,
|
|
enum qpnp_bsi_rx_tx_state state)
|
|
{
|
|
u8 buf[2] = {0, 0};
|
|
int rc;
|
|
|
|
buf[0] = QPNP_BSI_MODE_TX_FORMAT_11_BIT
|
|
| QPNP_BSI_MODE_RX_FORMAT_11_BIT;
|
|
|
|
switch (state) {
|
|
case QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF:
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_DATA |
|
|
QPNP_BSI_MODE_RX_PULSE_DATA;
|
|
buf[1] = QPNP_BSI_TX_DISABLE | QPNP_BSI_RX_DISABLE;
|
|
break;
|
|
case QPNP_BSI_RX_TX_STATE_RX_OFF_TX_DATA:
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_DATA |
|
|
QPNP_BSI_MODE_RX_PULSE_DATA;
|
|
buf[1] = QPNP_BSI_TX_ENABLE | QPNP_BSI_RX_DISABLE;
|
|
break;
|
|
case QPNP_BSI_RX_TX_STATE_RX_OFF_TX_INT:
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_INT |
|
|
QPNP_BSI_MODE_RX_PULSE_DATA;
|
|
buf[1] = QPNP_BSI_TX_ENABLE | QPNP_BSI_RX_DISABLE;
|
|
break;
|
|
case QPNP_BSI_RX_TX_STATE_RX_INT_TX_DATA:
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_DATA |
|
|
QPNP_BSI_MODE_RX_PULSE_INT;
|
|
buf[1] = QPNP_BSI_TX_ENABLE | QPNP_BSI_RX_ENABLE;
|
|
break;
|
|
case QPNP_BSI_RX_TX_STATE_RX_DATA_TX_DATA:
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_DATA |
|
|
QPNP_BSI_MODE_RX_PULSE_DATA;
|
|
buf[1] = QPNP_BSI_TX_ENABLE | QPNP_BSI_RX_ENABLE;
|
|
break;
|
|
case QPNP_BSI_RX_TX_STATE_RX_INT_TX_OFF:
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_DATA |
|
|
QPNP_BSI_MODE_RX_PULSE_INT;
|
|
buf[1] = QPNP_BSI_TX_DISABLE | QPNP_BSI_RX_DISABLE;
|
|
break;
|
|
default:
|
|
dev_err(&chip->spmi_dev->dev, "%s: invalid state=%d\n",
|
|
__func__, state);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_MODE, buf, 2);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void qpnp_bsi_slave_irq_work(struct work_struct *work)
|
|
{
|
|
struct qpnp_bsi_chip *chip
|
|
= container_of(work, struct qpnp_bsi_chip, slave_irq_work);
|
|
int rc;
|
|
|
|
rc = bif_ctrl_notify_slave_irq(chip->bdev);
|
|
if (rc)
|
|
pr_err("Could not notify BIF core about slave interrupt, rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
static irqreturn_t qpnp_bsi_isr(int irq, void *data)
|
|
{
|
|
struct qpnp_bsi_chip *chip = data;
|
|
bool found = false;
|
|
int i;
|
|
|
|
for (i = 0; i < QPNP_BSI_IRQ_COUNT; i++) {
|
|
if (irq == chip->irq[i]) {
|
|
found = true;
|
|
atomic_cmpxchg(&chip->irq_flag[i], 0, 1);
|
|
|
|
/* Check if this is a slave interrupt. */
|
|
if (i == QPNP_BSI_IRQ_RX
|
|
&& chip->state == BIF_BUS_STATE_INTERRUPT) {
|
|
/* Slave IRQ makes the bus active. */
|
|
qpnp_bsi_rx_tx_config(chip,
|
|
QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
chip->state = BIF_BUS_STATE_ACTIVE;
|
|
schedule_work(&chip->slave_irq_work);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
pr_err("Unknown interrupt: %d\n", irq);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qpnp_bsi_batt_present_isr(int irq, void *data)
|
|
{
|
|
struct qpnp_bsi_chip *chip = data;
|
|
int rc;
|
|
|
|
if (!chip->bdev)
|
|
return IRQ_HANDLED;
|
|
|
|
rc = bif_ctrl_notify_battery_changed(chip->bdev);
|
|
if (rc)
|
|
pr_err("Could not notify about battery state change, rc=%d\n",
|
|
rc);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void qpnp_bsi_set_com_mode(struct qpnp_bsi_chip *chip,
|
|
enum qpnp_bsi_com_mode mode)
|
|
{
|
|
int i;
|
|
|
|
if (chip->com_mode == mode)
|
|
return;
|
|
|
|
if (mode == QPNP_BSI_COM_MODE_IRQ)
|
|
for (i = 0; i < QPNP_BSI_IRQ_COUNT; i++)
|
|
enable_irq(chip->irq[i]);
|
|
else
|
|
for (i = 0; i < QPNP_BSI_IRQ_COUNT; i++)
|
|
disable_irq(chip->irq[i]);
|
|
|
|
chip->com_mode = mode;
|
|
}
|
|
|
|
static inline bool qpnp_bsi_check_irq(struct qpnp_bsi_chip *chip, int irq)
|
|
{
|
|
return atomic_cmpxchg(&chip->irq_flag[irq], 1, 0);
|
|
}
|
|
|
|
static void qpnp_bsi_clear_irq_flags(struct qpnp_bsi_chip *chip)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < QPNP_BSI_IRQ_COUNT; i++)
|
|
atomic_set(&chip->irq_flag[i], 0);
|
|
}
|
|
|
|
static inline int qpnp_bsi_get_tau_ns(struct qpnp_bsi_chip *chip)
|
|
{
|
|
if (chip->tau_sampling_mask == QPNP_BSI_TAU_CONFIG_SAMPLE_4X)
|
|
return qpnp_bsi_tau_period.period_4x_ns[chip->tau_index];
|
|
else
|
|
return qpnp_bsi_tau_period.period_8x_ns[chip->tau_index];
|
|
}
|
|
|
|
static inline int qpnp_bsi_get_tau_us(struct qpnp_bsi_chip *chip)
|
|
{
|
|
if (chip->tau_sampling_mask == QPNP_BSI_TAU_CONFIG_SAMPLE_4X)
|
|
return qpnp_bsi_tau_period.period_4x_us[chip->tau_index];
|
|
else
|
|
return qpnp_bsi_tau_period.period_8x_us[chip->tau_index];
|
|
}
|
|
|
|
/* Checks if BSI is in an error state and clears the error if it is. */
|
|
static int qpnp_bsi_clear_bsi_error(struct qpnp_bsi_chip *chip)
|
|
{
|
|
int rc, delay_us;
|
|
u8 reg;
|
|
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_BSI_ERROR, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_read() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (reg > 0) {
|
|
/*
|
|
* Delay before clearing the BSI error in case a transaction is
|
|
* still in flight.
|
|
*/
|
|
delay_us = QPNP_BSI_MAX_TRANSMIT_CYCLES
|
|
* qpnp_bsi_get_tau_us(chip);
|
|
udelay(delay_us);
|
|
|
|
pr_info("PMIC BSI module in error state, error=%d\n", reg);
|
|
|
|
reg = QPNP_BSI_BSI_ERROR_CLEAR;
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_CLEAR_ERROR, ®, 1);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_get_bsi_error(struct qpnp_bsi_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_BSI_ERROR, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_read() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
return reg;
|
|
}
|
|
|
|
static int qpnp_bsi_wait_for_tx(struct qpnp_bsi_chip *chip, int timeout)
|
|
{
|
|
int rc = 0;
|
|
|
|
/* Wait for TX or ERR IRQ. */
|
|
while (timeout > 0) {
|
|
if (qpnp_bsi_check_irq(chip, QPNP_BSI_IRQ_ERR)) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction error occurred, BSI error=%d\n",
|
|
__func__, qpnp_bsi_get_bsi_error(chip));
|
|
return -EIO;
|
|
}
|
|
|
|
if (qpnp_bsi_check_irq(chip, QPNP_BSI_IRQ_TX))
|
|
break;
|
|
|
|
udelay(1);
|
|
timeout--;
|
|
}
|
|
|
|
if (timeout == 0) {
|
|
rc = -ETIMEDOUT;
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction timed out, no interrupts received, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_issue_transaction(struct qpnp_bsi_chip *chip,
|
|
int transaction, u8 data)
|
|
{
|
|
int rc;
|
|
u8 buf[4];
|
|
|
|
/* MIPI_BIF_DATA_TX_0 = BIF word bits 7 to 0 */
|
|
buf[0] = data;
|
|
/* MIPI_BIF_DATA_TX_1 = BIF word BCF, bits 9 to 8 */
|
|
buf[1] = transaction & QPNP_BSI_TX_DATA_HIGH_MASK;
|
|
/* MIPI_BIF_DATA_TX_2 ignored */
|
|
buf[2] = 0x00;
|
|
/* MIPI_BIF_TX_CTL bit 0 written to start the transaction. */
|
|
buf[3] = QPNP_BSI_TX_CTRL_GO;
|
|
|
|
/* Write the TX_DATA bytes and initiate the transaction. */
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_TX_DATA_LOW, buf, 4);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_issue_transaction_wait_for_tx(struct qpnp_bsi_chip *chip,
|
|
int transaction, u8 data)
|
|
{
|
|
int rc, timeout;
|
|
|
|
rc = qpnp_bsi_issue_transaction(chip, transaction, data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
timeout = QPNP_BSI_MAX_TRANSMIT_CYCLES * qpnp_bsi_get_tau_us(chip)
|
|
+ QPNP_BSI_MAX_IRQ_LATENCY_US;
|
|
|
|
rc = qpnp_bsi_wait_for_tx(chip, timeout);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_wait_for_rx(struct qpnp_bsi_chip *chip, int timeout)
|
|
{
|
|
int rc = 0;
|
|
|
|
/* Wait for RX IRQ to indicate that data is ready to read. */
|
|
while (timeout > 0) {
|
|
if (qpnp_bsi_check_irq(chip, QPNP_BSI_IRQ_ERR)) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction error occurred, BSI error=%d\n",
|
|
__func__, qpnp_bsi_get_bsi_error(chip));
|
|
return -EIO;
|
|
}
|
|
|
|
if (qpnp_bsi_check_irq(chip, QPNP_BSI_IRQ_RX))
|
|
break;
|
|
|
|
udelay(1);
|
|
timeout--;
|
|
}
|
|
|
|
if (timeout == 0)
|
|
rc = -ETIMEDOUT;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_bus_transaction(struct bif_ctrl_dev *bdev, int transaction,
|
|
u8 data)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
int rc;
|
|
|
|
qpnp_bsi_set_com_mode(chip, QPNP_BSI_COM_MODE_IRQ);
|
|
|
|
rc = qpnp_bsi_set_bus_state(bdev, BIF_BUS_STATE_ACTIVE);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to set bus state, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_DATA);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
rc = qpnp_bsi_issue_transaction_wait_for_tx(chip, transaction, data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_bus_transaction_query(struct bif_ctrl_dev *bdev,
|
|
int transaction, u8 data, bool *query_response)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
int rc, timeout;
|
|
|
|
qpnp_bsi_set_com_mode(chip, QPNP_BSI_COM_MODE_IRQ);
|
|
|
|
rc = qpnp_bsi_set_bus_state(bdev, BIF_BUS_STATE_ACTIVE);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to set bus state, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_INT_TX_DATA);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
rc = qpnp_bsi_issue_transaction_wait_for_tx(chip, transaction, data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
timeout = QPNP_BSI_MAX_BUS_QUERY_CYCLES * qpnp_bsi_get_tau_us(chip)
|
|
+ QPNP_BSI_MAX_IRQ_LATENCY_US;
|
|
|
|
rc = qpnp_bsi_wait_for_rx(chip, timeout);
|
|
if (rc == 0) {
|
|
*query_response = true;
|
|
} else if (rc == -ETIMEDOUT) {
|
|
*query_response = false;
|
|
rc = 0;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_bus_transaction_read(struct bif_ctrl_dev *bdev,
|
|
int transaction, u8 data, int *response)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
int rc, timeout;
|
|
u8 buf[3];
|
|
|
|
qpnp_bsi_set_com_mode(chip, QPNP_BSI_COM_MODE_IRQ);
|
|
|
|
rc = qpnp_bsi_set_bus_state(bdev, BIF_BUS_STATE_ACTIVE);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to set bus state, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_DATA_TX_DATA);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
rc = qpnp_bsi_issue_transaction_wait_for_tx(chip, transaction, data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
timeout = QPNP_BSI_MAX_TRANSMIT_CYCLES * qpnp_bsi_get_tau_us(chip)
|
|
+ QPNP_BSI_MAX_IRQ_LATENCY_US;
|
|
|
|
rc = qpnp_bsi_wait_for_rx(chip, timeout);
|
|
if (rc) {
|
|
if (rc == -ETIMEDOUT) {
|
|
/*
|
|
* No error message is printed in this case in order
|
|
* to provide silent operation when checking if a slave
|
|
* is selected using the transaction query bus command.
|
|
*/
|
|
dev_dbg(&chip->spmi_dev->dev, "%s: transaction timed out, no interrupts received, rc=%d\n",
|
|
__func__, rc);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* Read the RX_DATA bytes. */
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_RX_DATA_LOW, buf, 3);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_read() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (buf[2] & QPNP_BSI_RX_SRC_LOOPBACK_FLAG) {
|
|
rc = -EIO;
|
|
dev_err(&chip->spmi_dev->dev, "%s: unexpected loopback data read, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
*response = ((int)(buf[1] & QPNP_BSI_RX_DATA_HIGH_MASK) << 8) | buf[0];
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wait for RX_FLOW_STATUS to be set to 1 which indicates that another BIF word
|
|
* can be read from PMIC registers.
|
|
*/
|
|
static int qpnp_bsi_wait_for_rx_data(struct qpnp_bsi_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
int timeout;
|
|
u8 reg;
|
|
|
|
timeout = QPNP_BSI_MAX_TRANSMIT_CYCLES * qpnp_bsi_get_tau_us(chip);
|
|
|
|
/* Wait for RX_FLOW_STATUS == 1 or ERR_FLAG == 1. */
|
|
while (timeout > 0) {
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_STATUS, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (reg & QPNP_BSI_STATUS_ERROR) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction error occurred, BSI error=%d\n",
|
|
__func__, qpnp_bsi_get_bsi_error(chip));
|
|
return -EIO;
|
|
}
|
|
|
|
if (reg & QPNP_BSI_STATUS_RX_DATA_READY) {
|
|
/* BSI RX has data word latched. */
|
|
return 0;
|
|
}
|
|
|
|
udelay(1);
|
|
timeout--;
|
|
}
|
|
|
|
rc = -ETIMEDOUT;
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction timed out, RX_FLOW_STATUS never set to 1, rc=%d\n",
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Wait for TX_GO_STATUS to be set to 0 which indicates that another BIF word
|
|
* can be enqueued.
|
|
*/
|
|
static int qpnp_bsi_wait_for_tx_go(struct qpnp_bsi_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
int timeout;
|
|
u8 reg;
|
|
|
|
timeout = QPNP_BSI_MAX_TRANSMIT_CYCLES * qpnp_bsi_get_tau_us(chip);
|
|
|
|
/* Wait for TX_GO_STATUS == 0 or ERR_FLAG == 1. */
|
|
while (timeout > 0) {
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_STATUS, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (reg & QPNP_BSI_STATUS_ERROR) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction error occurred, BSI error=%d\n",
|
|
__func__, qpnp_bsi_get_bsi_error(chip));
|
|
return -EIO;
|
|
}
|
|
|
|
if (!(reg & QPNP_BSI_STATUS_TX_GO_BUSY)) {
|
|
/* BSI TX is ready to accept the next word. */
|
|
return 0;
|
|
}
|
|
|
|
udelay(1);
|
|
timeout--;
|
|
}
|
|
|
|
rc = -ETIMEDOUT;
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction timed out, TX_GO_STATUS never set to 0, rc=%d\n",
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Wait for TX_BUSY to be set to 0 which indicates that the TX data has been
|
|
* successfully transmitted.
|
|
*/
|
|
static int qpnp_bsi_wait_for_tx_idle(struct qpnp_bsi_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
int timeout;
|
|
u8 reg;
|
|
|
|
timeout = QPNP_BSI_MAX_TRANSMIT_CYCLES * qpnp_bsi_get_tau_us(chip);
|
|
|
|
/* Wait for TX_BUSY == 0 or ERR_FLAG == 1. */
|
|
while (timeout > 0) {
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_STATUS, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (reg & QPNP_BSI_STATUS_ERROR) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction error occurred, BSI error=%d\n",
|
|
__func__, qpnp_bsi_get_bsi_error(chip));
|
|
return -EIO;
|
|
}
|
|
|
|
if (!(reg & QPNP_BSI_STATUS_TX_BUSY)) {
|
|
/* BSI TX is idle. */
|
|
return 0;
|
|
}
|
|
|
|
udelay(1);
|
|
timeout--;
|
|
}
|
|
|
|
rc = -ETIMEDOUT;
|
|
dev_err(&chip->spmi_dev->dev, "%s: transaction timed out, TX_BUSY never set to 0, rc=%d\n",
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* For burst read length greater than 1, send necessary RBL and RBE BIF bus
|
|
* commands.
|
|
*/
|
|
static int qpnp_bsi_send_burst_length(struct qpnp_bsi_chip *chip, int burst_len)
|
|
{
|
|
int rc = 0;
|
|
|
|
/*
|
|
* Send burst read length bus commands according to the following:
|
|
*
|
|
* 1 --> No RBE or RBL
|
|
* 2 - 15 = x --> RBLx
|
|
* 16 - 255 = 16 * y + x --> RBEy and RBLx (RBL0 not sent)
|
|
* 256 --> RBL0
|
|
*/
|
|
if (burst_len == 256) {
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_BC,
|
|
BIF_CMD_RBL);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_wait_for_tx_go(chip);
|
|
if (rc)
|
|
return rc;
|
|
} else if (burst_len >= 16) {
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_BC,
|
|
BIF_CMD_RBE + (burst_len / 16));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_wait_for_tx_go(chip);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
if (burst_len % 16 && burst_len > 1) {
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_BC,
|
|
BIF_CMD_RBL + (burst_len % 16));
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_wait_for_tx_go(chip);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Perform validation steps on received BIF data. */
|
|
static int qpnp_bsi_validate_rx_data(struct qpnp_bsi_chip *chip, int response,
|
|
u8 rx2_data, bool last_word)
|
|
{
|
|
int err = -EIO;
|
|
|
|
if (rx2_data & QPNP_BSI_RX_SRC_LOOPBACK_FLAG) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: unexpected loopback data read, rc=%d\n",
|
|
__func__, err);
|
|
return err;
|
|
}
|
|
|
|
if (!(response & BIF_SLAVE_RD_ACK)) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: BIF register read error=0x%02X\n",
|
|
__func__, response & BIF_SLAVE_RD_ERR);
|
|
return err;
|
|
}
|
|
|
|
if (last_word && !(response & BIF_SLAVE_RD_EOT)) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: BIF register read error, last RD packet has EOT=0\n",
|
|
__func__);
|
|
return err;
|
|
} else if (!last_word && (response & BIF_SLAVE_RD_EOT)) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: BIF register read error, RD packet other than last has EOT=1\n",
|
|
__func__);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Performs all BIF transactions in order to utilize burst reads. */
|
|
static int qpnp_bsi_read_slave_registers(struct bif_ctrl_dev *bdev, u16 addr,
|
|
u8 *data, int len)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
int response = 0;
|
|
unsigned long flags;
|
|
int rc, rc2, i, burst_len;
|
|
u8 buf[3];
|
|
|
|
qpnp_bsi_set_com_mode(chip, QPNP_BSI_COM_MODE_POLL);
|
|
|
|
rc = qpnp_bsi_set_bus_state(bdev, BIF_BUS_STATE_ACTIVE);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to set bus state, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_DATA_TX_DATA);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
while (len > 0) {
|
|
burst_len = min(len, 256);
|
|
|
|
rc = qpnp_bsi_send_burst_length(chip, burst_len);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_ERA, addr >> 8);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_wait_for_tx_go(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Perform burst read in atomic context. */
|
|
local_irq_save(flags);
|
|
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_RRA,
|
|
addr & 0xFF);
|
|
if (rc)
|
|
goto burst_err;
|
|
|
|
for (i = 0; i < burst_len; i++) {
|
|
rc = qpnp_bsi_wait_for_rx_data(chip);
|
|
if (rc)
|
|
goto burst_err;
|
|
|
|
/* Read the RX_DATA bytes. */
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_RX_DATA_LOW, buf,
|
|
3);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_read() failed, rc=%d\n",
|
|
__func__, rc);
|
|
goto burst_err;
|
|
}
|
|
|
|
response = ((buf[1] & QPNP_BSI_RX_DATA_HIGH_MASK) << 8)
|
|
| buf[0];
|
|
|
|
rc = qpnp_bsi_validate_rx_data(chip, response, buf[2],
|
|
i == burst_len - 1);
|
|
if (rc)
|
|
goto burst_err;
|
|
|
|
data[i] = buf[0];
|
|
}
|
|
local_irq_restore(flags);
|
|
|
|
addr += burst_len;
|
|
data += burst_len;
|
|
len -= burst_len;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
|
|
return rc;
|
|
|
|
burst_err:
|
|
local_irq_restore(flags);
|
|
|
|
rc2 = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
if (rc2 < 0)
|
|
rc = rc2;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Performs all BIF transactions in order to utilize burst writes. */
|
|
static int qpnp_bsi_write_slave_registers(struct bif_ctrl_dev *bdev, u16 addr,
|
|
const u8 *data, int len)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
unsigned long flags;
|
|
int rc, rc2, i;
|
|
|
|
qpnp_bsi_set_com_mode(chip, QPNP_BSI_COM_MODE_POLL);
|
|
|
|
rc = qpnp_bsi_set_bus_state(bdev, BIF_BUS_STATE_ACTIVE);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to set bus state, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_DATA);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_ERA, addr >> 8);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_wait_for_tx_go(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_WRA, addr & 0xFF);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_wait_for_tx_go(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Perform burst write in atomic context. */
|
|
local_irq_save(flags);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
rc = qpnp_bsi_issue_transaction(chip, BIF_TRANS_WD, data[i]);
|
|
if (rc)
|
|
goto burst_err;
|
|
|
|
rc = qpnp_bsi_wait_for_tx_go(chip);
|
|
if (rc)
|
|
goto burst_err;
|
|
}
|
|
|
|
rc = qpnp_bsi_wait_for_tx_idle(chip);
|
|
if (rc)
|
|
goto burst_err;
|
|
|
|
local_irq_restore(flags);
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
|
|
return rc;
|
|
|
|
burst_err:
|
|
local_irq_restore(flags);
|
|
|
|
rc2 = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_OFF_TX_OFF);
|
|
if (rc2 < 0)
|
|
rc = rc2;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int qpnp_bsi_bus_set_interrupt_mode(struct bif_ctrl_dev *bdev)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
int rc;
|
|
|
|
qpnp_bsi_set_com_mode(chip, QPNP_BSI_COM_MODE_IRQ);
|
|
|
|
/*
|
|
* Temporarily change the bus to active state so that the EINT command
|
|
* can be issued.
|
|
*/
|
|
rc = qpnp_bsi_set_bus_state(bdev, BIF_BUS_STATE_ACTIVE);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to set bus state, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_INT_TX_DATA);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* Set the bus state to interrupt mode so that an RX interrupt which
|
|
* occurs immediately after issuing the EINT command is handled
|
|
* properly.
|
|
*/
|
|
chip->state = BIF_BUS_STATE_INTERRUPT;
|
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
/* Send EINT bus command. */
|
|
rc = qpnp_bsi_issue_transaction_wait_for_tx(chip, BIF_TRANS_BC,
|
|
BIF_CMD_EINT);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_bsi_rx_tx_config(chip, QPNP_BSI_RX_TX_STATE_RX_INT_TX_OFF);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_bus_set_active_mode(struct bif_ctrl_dev *bdev,
|
|
int prev_state)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
int rc;
|
|
u8 buf[2];
|
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
if (rc)
|
|
return rc;
|
|
|
|
buf[0] = QPNP_BSI_MODE_TX_PULSE_INT |
|
|
QPNP_BSI_MODE_RX_PULSE_DATA;
|
|
buf[1] = QPNP_BSI_TX_ENABLE | QPNP_BSI_RX_DISABLE;
|
|
|
|
if (prev_state == BIF_BUS_STATE_INTERRUPT)
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_T_1_TAU;
|
|
else
|
|
buf[0] |= QPNP_BSI_MODE_TX_PULSE_T_WAKE;
|
|
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_MODE, buf, 2);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
buf[0] = QPNP_BSI_TX_CTRL_GO;
|
|
/* Initiate BCL low pulse. */
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_TX_CTRL, buf, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
switch (prev_state) {
|
|
case BIF_BUS_STATE_INTERRUPT:
|
|
udelay(qpnp_bsi_get_tau_us(chip) * 4);
|
|
break;
|
|
case BIF_BUS_STATE_STANDBY:
|
|
udelay(qpnp_bsi_get_tau_us(chip)
|
|
+ QPNP_BSI_MAX_SLAVE_ACTIVIATION_DELAY_US
|
|
+ QPNP_BSI_POWER_UP_LOW_DELAY_US);
|
|
break;
|
|
case BIF_BUS_STATE_POWER_DOWN:
|
|
case BIF_BUS_STATE_MASTER_DISABLED:
|
|
msleep(QPNP_BSI_MAX_SLAVE_POWER_UP_DELAY_MS);
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_get_bus_state(struct bif_ctrl_dev *bdev)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
|
|
return chip->state;
|
|
}
|
|
|
|
static int qpnp_bsi_set_bus_state(struct bif_ctrl_dev *bdev, int state)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
int rc = 0;
|
|
u8 reg;
|
|
|
|
if (state == chip->state)
|
|
return 0;
|
|
|
|
if (chip->state == BIF_BUS_STATE_MASTER_DISABLED) {
|
|
/*
|
|
* Enable the BSI peripheral when transitioning from a disabled
|
|
* bus state to any of the active bus states so that BIF
|
|
* transactions can take place.
|
|
*/
|
|
reg = QPNP_BSI_ENABLE;
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_ENABLE, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
switch (state) {
|
|
case BIF_BUS_STATE_MASTER_DISABLED:
|
|
/* Disable the BSI peripheral. */
|
|
reg = QPNP_BSI_DISABLE;
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_ENABLE, ®, 1);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
break;
|
|
case BIF_BUS_STATE_POWER_DOWN:
|
|
rc = qpnp_bsi_bus_transaction(bdev, BIF_TRANS_BC, BIF_CMD_PDWN);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to enable power down mode, rc=%d\n",
|
|
__func__, rc);
|
|
break;
|
|
case BIF_BUS_STATE_STANDBY:
|
|
rc = qpnp_bsi_bus_transaction(bdev, BIF_TRANS_BC, BIF_CMD_STBY);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to enable standby mode, rc=%d\n",
|
|
__func__, rc);
|
|
break;
|
|
case BIF_BUS_STATE_ACTIVE:
|
|
rc = qpnp_bsi_bus_set_active_mode(bdev, chip->state);
|
|
if (rc)
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to enable active mode, rc=%d\n",
|
|
__func__, rc);
|
|
break;
|
|
case BIF_BUS_STATE_INTERRUPT:
|
|
/*
|
|
* qpnp_bsi_bus_set_interrupt_mode() internally sets
|
|
* chip->state = BIF_BUS_STATE_INTERRUPT immediately before
|
|
* issuing the EINT command.
|
|
*/
|
|
rc = qpnp_bsi_bus_set_interrupt_mode(bdev);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: failed to enable interrupt mode, rc=%d\n",
|
|
__func__, rc);
|
|
} else if (chip->state == BIF_BUS_STATE_ACTIVE) {
|
|
/*
|
|
* A slave interrupt was received immediately after
|
|
* issuing the EINT command. Therefore, stay in active
|
|
* communication mode.
|
|
*/
|
|
state = BIF_BUS_STATE_ACTIVE;
|
|
}
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
dev_err(&chip->spmi_dev->dev, "%s: invalid state=%d\n",
|
|
__func__, state);
|
|
}
|
|
|
|
if (!rc)
|
|
chip->state = state;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Returns the smallest tau_bif that is greater than or equal to period_ns. */
|
|
static int qpnp_bsi_tau_bif_higher(int period_ns, int sample_mask)
|
|
{
|
|
const int *supported_period_ns =
|
|
(sample_mask == QPNP_BSI_TAU_CONFIG_SAMPLE_4X ?
|
|
qpnp_bsi_tau_period.period_4x_ns :
|
|
qpnp_bsi_tau_period.period_8x_ns);
|
|
int smallest_tau_bif = INT_MAX;
|
|
int i;
|
|
|
|
for (i = QPNP_BSI_NUM_CLOCK_PERIODS - 1; i >= 0; i--) {
|
|
if (period_ns <= supported_period_ns[i]) {
|
|
smallest_tau_bif = supported_period_ns[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return smallest_tau_bif;
|
|
}
|
|
|
|
/* Returns the largest tau_bif that is less than or equal to period_ns. */
|
|
static int qpnp_bsi_tau_bif_lower(int period_ns, int sample_mask)
|
|
{
|
|
const int *supported_period_ns =
|
|
(sample_mask == QPNP_BSI_TAU_CONFIG_SAMPLE_4X ?
|
|
qpnp_bsi_tau_period.period_4x_ns :
|
|
qpnp_bsi_tau_period.period_8x_ns);
|
|
int largest_tau_bif = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < QPNP_BSI_NUM_CLOCK_PERIODS; i++) {
|
|
if (period_ns >= supported_period_ns[i]) {
|
|
largest_tau_bif = supported_period_ns[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return largest_tau_bif;
|
|
}
|
|
|
|
/*
|
|
* Moves period_ns into allowed range and then sets tau bif to the period that
|
|
* is greater than or equal to period_ns.
|
|
*/
|
|
static int qpnp_bsi_set_tau_bif(struct qpnp_bsi_chip *chip, int period_ns)
|
|
{
|
|
const int *supported_period_ns =
|
|
(chip->tau_sampling_mask == QPNP_BSI_TAU_CONFIG_SAMPLE_4X ?
|
|
qpnp_bsi_tau_period.period_4x_ns :
|
|
qpnp_bsi_tau_period.period_8x_ns);
|
|
int idx = 0;
|
|
int i, rc;
|
|
u8 reg;
|
|
|
|
if (period_ns < chip->bdesc.bus_clock_min_ns)
|
|
period_ns = chip->bdesc.bus_clock_min_ns;
|
|
else if (period_ns > chip->bdesc.bus_clock_max_ns)
|
|
period_ns = chip->bdesc.bus_clock_max_ns;
|
|
|
|
for (i = QPNP_BSI_NUM_CLOCK_PERIODS - 1; i >= 0; i--) {
|
|
if (period_ns <= supported_period_ns[i]) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set the tau BIF clock period and sampling rate. */
|
|
reg = chip->tau_sampling_mask | idx;
|
|
rc = qpnp_bsi_write(chip, QPNP_BSI_REG_TAU_CONFIG, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_bsi_write() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->tau_index = idx;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpnp_bsi_get_bus_period(struct bif_ctrl_dev *bdev)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
|
|
return qpnp_bsi_get_tau_ns(chip);
|
|
}
|
|
|
|
static int qpnp_bsi_set_bus_period(struct bif_ctrl_dev *bdev, int period_ns)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
|
|
return qpnp_bsi_set_tau_bif(chip, period_ns);
|
|
}
|
|
|
|
static int qpnp_bsi_get_battery_rid(struct bif_ctrl_dev *bdev)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
struct qpnp_vadc_result adc_result;
|
|
int rid_ohm, vid_uV, rc;
|
|
s64 temp;
|
|
|
|
if (chip->batt_id_adc_channel >= ADC_MAX_NUM) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: no ADC channel specified for Rid measurement\n",
|
|
__func__);
|
|
return -ENXIO;
|
|
}
|
|
|
|
rc = qpnp_vadc_read(chip->vadc_dev, chip->batt_id_adc_channel,
|
|
&adc_result);
|
|
if (!rc) {
|
|
vid_uV = adc_result.physical;
|
|
|
|
if (chip->vid_ref_uV - vid_uV <= 0) {
|
|
rid_ohm = INT_MAX;
|
|
} else {
|
|
temp = (s64)chip->r_pullup_ohm * (s64)vid_uV;
|
|
do_div(temp, chip->vid_ref_uV - vid_uV);
|
|
if (temp > INT_MAX)
|
|
rid_ohm = INT_MAX;
|
|
else
|
|
rid_ohm = temp;
|
|
}
|
|
} else {
|
|
dev_err(&chip->spmi_dev->dev, "%s: qpnp_vadc_read(%d) failed, rc=%d\n",
|
|
__func__, chip->batt_id_adc_channel, rc);
|
|
rid_ohm = rc;
|
|
}
|
|
|
|
return rid_ohm;
|
|
}
|
|
|
|
/*
|
|
* Returns 1 if a battery pack is present on the BIF bus, 0 if a battery pack
|
|
* is not present, or errno if detection fails.
|
|
*
|
|
* Battery detection is based upon the idle BCL voltage.
|
|
*/
|
|
static int qpnp_bsi_get_battery_presence(struct bif_ctrl_dev *bdev)
|
|
{
|
|
struct qpnp_bsi_chip *chip = bdev_get_drvdata(bdev);
|
|
u8 reg = 0x00;
|
|
int rc;
|
|
|
|
rc = spmi_ext_register_readl(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
|
|
chip->batt_id_stat_addr, ®, 1);
|
|
if (rc) {
|
|
dev_err(&chip->spmi_dev->dev, "%s: spmi_ext_register_readl() failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
return !!(reg & QPNP_SMBB_BAT_IF_BATT_PRES_MASK);
|
|
}
|
|
|
|
static struct bif_ctrl_ops qpnp_bsi_ops = {
|
|
.bus_transaction = qpnp_bsi_bus_transaction,
|
|
.bus_transaction_query = qpnp_bsi_bus_transaction_query,
|
|
.bus_transaction_read = qpnp_bsi_bus_transaction_read,
|
|
.get_bus_state = qpnp_bsi_get_bus_state,
|
|
.set_bus_state = qpnp_bsi_set_bus_state,
|
|
.get_bus_period = qpnp_bsi_get_bus_period,
|
|
.set_bus_period = qpnp_bsi_set_bus_period,
|
|
.read_slave_registers = qpnp_bsi_read_slave_registers,
|
|
.write_slave_registers = qpnp_bsi_write_slave_registers,
|
|
.get_battery_rid = qpnp_bsi_get_battery_rid,
|
|
.get_battery_presence = qpnp_bsi_get_battery_presence,
|
|
};
|
|
|
|
/* Load all BSI properties from device tree. */
|
|
static int qpnp_bsi_parse_dt(struct qpnp_bsi_chip *chip,
|
|
struct spmi_device *spmi)
|
|
{
|
|
struct device *dev = &spmi->dev;
|
|
struct device_node *node = spmi->dev.of_node;
|
|
struct resource *res;
|
|
int rc, temp;
|
|
|
|
chip->batt_id_adc_channel = ADC_MAX_NUM;
|
|
rc = of_property_read_u32(node, "qcom,channel-num",
|
|
&chip->batt_id_adc_channel);
|
|
if (!rc && (chip->batt_id_adc_channel < 0
|
|
|| chip->batt_id_adc_channel >= ADC_MAX_NUM)) {
|
|
dev_err(dev, "%s: invalid qcom,channel-num=%d specified\n",
|
|
__func__, chip->batt_id_adc_channel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->r_pullup_ohm = QPNP_BSI_DEFAULT_PULLUP_OHM;
|
|
rc = of_property_read_u32(node, "qcom,pullup-ohms",
|
|
&chip->r_pullup_ohm);
|
|
if (!rc && (chip->r_pullup_ohm < QPNP_BSI_MIN_PULLUP_OHM ||
|
|
chip->r_pullup_ohm > QPNP_BSI_MAX_PULLUP_OHM)) {
|
|
dev_err(dev, "%s: invalid qcom,pullup-ohms=%d property value\n",
|
|
__func__, chip->r_pullup_ohm);
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->vid_ref_uV = QPNP_BSI_DEFAULT_VID_REF_UV;
|
|
rc = of_property_read_u32(node, "qcom,vref-microvolts",
|
|
&chip->vid_ref_uV);
|
|
if (!rc && (chip->vid_ref_uV < QPNP_BSI_MIN_VID_REF_UV ||
|
|
chip->vid_ref_uV > QPNP_BSI_MAX_VID_REF_UV)) {
|
|
dev_err(dev, "%s: invalid qcom,vref-microvolts=%d property value\n",
|
|
__func__, chip->vid_ref_uV);
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = spmi_get_resource_byname(spmi, NULL, IORESOURCE_MEM, "bsi-base");
|
|
if (!res) {
|
|
dev_err(dev, "%s: node is missing BSI base address\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
chip->base_addr = res->start;
|
|
|
|
res = spmi_get_resource_byname(spmi, NULL, IORESOURCE_MEM,
|
|
"batt-id-status");
|
|
if (!res) {
|
|
dev_err(dev, "%s: node is missing BATT_ID status address\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
chip->batt_id_stat_addr = res->start;
|
|
|
|
chip->bdesc.name = spmi_get_primary_dev_name(spmi);
|
|
if (!chip->bdesc.name) {
|
|
dev_err(dev, "%s: label binding undefined for node %s\n",
|
|
__func__, spmi->dev.of_node->full_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Use maximum range by default. */
|
|
chip->bdesc.bus_clock_min_ns = QPNP_BSI_MIN_CLOCK_SPEED_NS;
|
|
chip->bdesc.bus_clock_max_ns = QPNP_BSI_MAX_CLOCK_SPEED_NS;
|
|
chip->tau_sampling_mask = QPNP_BSI_TAU_CONFIG_SAMPLE_4X;
|
|
|
|
rc = of_property_read_u32(node, "qcom,sample-rate", &temp);
|
|
if (rc == 0) {
|
|
if (temp == 4) {
|
|
chip->tau_sampling_mask = QPNP_BSI_TAU_CONFIG_SAMPLE_4X;
|
|
} else if (temp == 8) {
|
|
chip->tau_sampling_mask = QPNP_BSI_TAU_CONFIG_SAMPLE_8X;
|
|
} else {
|
|
dev_err(dev, "%s: invalid qcom,sample-rate=%d. Only values of 4 and 8 are supported.\n",
|
|
__func__, temp);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,min-clock-period", &temp);
|
|
if (rc == 0)
|
|
chip->bdesc.bus_clock_min_ns = qpnp_bsi_tau_bif_higher(temp,
|
|
chip->tau_sampling_mask);
|
|
|
|
rc = of_property_read_u32(node, "qcom,max-clock-period", &temp);
|
|
if (rc == 0)
|
|
chip->bdesc.bus_clock_max_ns = qpnp_bsi_tau_bif_lower(temp,
|
|
chip->tau_sampling_mask);
|
|
|
|
if (chip->bdesc.bus_clock_min_ns > chip->bdesc.bus_clock_max_ns) {
|
|
dev_err(dev, "%s: invalid qcom,min/max-clock-period.\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->irq[QPNP_BSI_IRQ_ERR] = spmi_get_irq_byname(spmi, NULL, "err");
|
|
if (chip->irq[QPNP_BSI_IRQ_ERR] < 0) {
|
|
dev_err(dev, "%s: node is missing err irq\n", __func__);
|
|
return chip->irq[QPNP_BSI_IRQ_ERR];
|
|
}
|
|
|
|
chip->irq[QPNP_BSI_IRQ_RX] = spmi_get_irq_byname(spmi, NULL, "rx");
|
|
if (chip->irq[QPNP_BSI_IRQ_RX] < 0) {
|
|
dev_err(dev, "%s: node is missing rx irq\n", __func__);
|
|
return chip->irq[QPNP_BSI_IRQ_RX];
|
|
}
|
|
|
|
chip->irq[QPNP_BSI_IRQ_TX] = spmi_get_irq_byname(spmi, NULL, "tx");
|
|
if (chip->irq[QPNP_BSI_IRQ_TX] < 0) {
|
|
dev_err(dev, "%s: node is missing tx irq\n", __func__);
|
|
return chip->irq[QPNP_BSI_IRQ_TX];
|
|
}
|
|
|
|
chip->batt_present_irq = spmi_get_irq_byname(spmi, NULL,
|
|
"batt-present");
|
|
if (chip->batt_present_irq < 0) {
|
|
dev_err(dev, "%s: node is missing batt-present irq\n",
|
|
__func__);
|
|
return chip->batt_present_irq;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Request all BSI and battery presence IRQs and set them as wakeable. */
|
|
static int qpnp_bsi_init_irqs(struct qpnp_bsi_chip *chip,
|
|
struct device *dev)
|
|
{
|
|
int rc;
|
|
|
|
rc = devm_request_irq(dev, chip->irq[QPNP_BSI_IRQ_ERR],
|
|
qpnp_bsi_isr, IRQF_TRIGGER_HIGH, "bsi-err", chip);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: request for bsi-err irq %d failed, rc=%d\n",
|
|
__func__, chip->irq[QPNP_BSI_IRQ_ERR], rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_ERR], 1);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: unable to set bsi-err irq %d as wakeable, rc=%d\n",
|
|
__func__, chip->irq[QPNP_BSI_IRQ_ERR], rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = devm_request_irq(dev, chip->irq[QPNP_BSI_IRQ_RX],
|
|
qpnp_bsi_isr, IRQF_TRIGGER_HIGH, "bsi-rx", chip);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: request for bsi-rx irq %d failed, rc=%d\n",
|
|
__func__, chip->irq[QPNP_BSI_IRQ_RX], rc);
|
|
goto set_unwakeable_irq_err;
|
|
}
|
|
|
|
rc = irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_RX], 1);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: unable to set bsi-rx irq %d as wakeable, rc=%d\n",
|
|
__func__, chip->irq[QPNP_BSI_IRQ_RX], rc);
|
|
goto set_unwakeable_irq_err;
|
|
}
|
|
|
|
rc = devm_request_irq(dev, chip->irq[QPNP_BSI_IRQ_TX],
|
|
qpnp_bsi_isr, IRQF_TRIGGER_HIGH, "bsi-tx", chip);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: request for bsi-tx irq %d failed, rc=%d\n",
|
|
__func__, chip->irq[QPNP_BSI_IRQ_TX], rc);
|
|
goto set_unwakeable_irq_rx;
|
|
}
|
|
|
|
rc = irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_TX], 1);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: unable to set bsi-tx irq %d as wakeable, rc=%d\n",
|
|
__func__, chip->irq[QPNP_BSI_IRQ_TX], rc);
|
|
goto set_unwakeable_irq_rx;
|
|
}
|
|
|
|
rc = devm_request_threaded_irq(dev, chip->batt_present_irq, NULL,
|
|
qpnp_bsi_batt_present_isr,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED
|
|
| IRQF_ONESHOT,
|
|
"bsi-batt-present", chip);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: request for bsi-batt-present irq %d failed, rc=%d\n",
|
|
__func__, chip->batt_present_irq, rc);
|
|
goto set_unwakeable_irq_tx;
|
|
}
|
|
|
|
rc = irq_set_irq_wake(chip->batt_present_irq, 1);
|
|
if (rc < 0) {
|
|
dev_err(dev, "%s: unable to set bsi-batt-present irq %d as wakeable, rc=%d\n",
|
|
__func__, chip->batt_present_irq, rc);
|
|
goto set_unwakeable_irq_tx;
|
|
}
|
|
|
|
return rc;
|
|
|
|
set_unwakeable_irq_tx:
|
|
irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_TX], 0);
|
|
set_unwakeable_irq_rx:
|
|
irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_RX], 0);
|
|
set_unwakeable_irq_err:
|
|
irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_ERR], 0);
|
|
return rc;
|
|
}
|
|
|
|
static void qpnp_bsi_cleanup_irqs(struct qpnp_bsi_chip *chip)
|
|
{
|
|
irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_ERR], 0);
|
|
irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_RX], 0);
|
|
irq_set_irq_wake(chip->irq[QPNP_BSI_IRQ_TX], 0);
|
|
irq_set_irq_wake(chip->batt_present_irq, 0);
|
|
}
|
|
|
|
static int qpnp_bsi_probe(struct spmi_device *spmi)
|
|
{
|
|
struct device *dev = &spmi->dev;
|
|
struct qpnp_bsi_chip *chip;
|
|
int rc;
|
|
u8 type[2];
|
|
|
|
if (!spmi->dev.of_node) {
|
|
dev_err(dev, "%s: device node missing\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
chip = devm_kzalloc(dev, sizeof(struct qpnp_bsi_chip), GFP_KERNEL);
|
|
if (!chip) {
|
|
dev_err(dev, "%s: Can't allocate qpnp_bsi\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rc = qpnp_bsi_parse_dt(chip, spmi);
|
|
if (rc) {
|
|
dev_err(dev, "%s: device tree parsing failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
INIT_WORK(&chip->slave_irq_work, qpnp_bsi_slave_irq_work);
|
|
|
|
rc = qpnp_bsi_init_irqs(chip, dev);
|
|
if (rc) {
|
|
dev_err(dev, "%s: IRQ initialization failed, rc=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->spmi_dev = spmi;
|
|
chip->bdesc.ops = &qpnp_bsi_ops;
|
|
chip->state = BIF_BUS_STATE_MASTER_DISABLED;
|
|
chip->com_mode = QPNP_BSI_COM_MODE_IRQ;
|
|
|
|
rc = qpnp_bsi_read(chip, QPNP_BSI_REG_TYPE, type, 2);
|
|
if (rc) {
|
|
dev_err(dev, "%s: could not read type register, rc=%d\n",
|
|
__func__, rc);
|
|
goto cleanup_irqs;
|
|
}
|
|
|
|
if (type[0] != QPNP_BSI_TYPE || type[1] != QPNP_BSI_SUBTYPE) {
|
|
dev_err(dev, "%s: BSI peripheral is not present; type=0x%02X, subtype=0x%02X\n",
|
|
__func__, type[0], type[1]);
|
|
rc = -ENODEV;
|
|
goto cleanup_irqs;
|
|
}
|
|
|
|
/* Ensure that ADC channel is available if it was specified. */
|
|
if (chip->batt_id_adc_channel < ADC_MAX_NUM) {
|
|
chip->vadc_dev = qpnp_get_vadc(dev, "bsi");
|
|
if (IS_ERR(chip->vadc_dev)) {
|
|
rc = PTR_ERR(chip->vadc_dev);
|
|
if (rc != -EPROBE_DEFER)
|
|
pr_err("missing vadc property, rc=%d\n", rc);
|
|
/* Probe retry, do not print an error message */
|
|
goto cleanup_irqs;
|
|
}
|
|
}
|
|
|
|
rc = qpnp_bsi_set_tau_bif(chip, chip->bdesc.bus_clock_min_ns);
|
|
if (rc) {
|
|
dev_err(dev, "%s: qpnp_bsi_set_tau_bif() failed, rc=%d\n",
|
|
__func__, rc);
|
|
goto cleanup_irqs;
|
|
}
|
|
|
|
chip->bdev = bif_ctrl_register(&chip->bdesc, dev, chip,
|
|
spmi->dev.of_node);
|
|
if (IS_ERR(chip->bdev)) {
|
|
rc = PTR_ERR(chip->bdev);
|
|
dev_err(dev, "%s: bif_ctrl_register failed, rc=%d\n",
|
|
__func__, rc);
|
|
goto cleanup_irqs;
|
|
}
|
|
|
|
dev_set_drvdata(dev, chip);
|
|
|
|
return rc;
|
|
|
|
cleanup_irqs:
|
|
qpnp_bsi_cleanup_irqs(chip);
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_bsi_remove(struct spmi_device *spmi)
|
|
{
|
|
struct qpnp_bsi_chip *chip = dev_get_drvdata(&spmi->dev);
|
|
dev_set_drvdata(&spmi->dev, NULL);
|
|
|
|
if (chip) {
|
|
bif_ctrl_unregister(chip->bdev);
|
|
qpnp_bsi_cleanup_irqs(chip);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct of_device_id spmi_match_table[] = {
|
|
{ .compatible = QPNP_BSI_DRIVER_NAME, },
|
|
{}
|
|
};
|
|
|
|
static const struct spmi_device_id qpnp_bsi_id[] = {
|
|
{ QPNP_BSI_DRIVER_NAME, 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(spmi, qpnp_bsi_id);
|
|
|
|
static struct spmi_driver qpnp_bsi_driver = {
|
|
.driver = {
|
|
.name = QPNP_BSI_DRIVER_NAME,
|
|
.of_match_table = spmi_match_table,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = qpnp_bsi_probe,
|
|
.remove = qpnp_bsi_remove,
|
|
.id_table = qpnp_bsi_id,
|
|
};
|
|
|
|
static int __init qpnp_bsi_init(void)
|
|
{
|
|
return spmi_driver_register(&qpnp_bsi_driver);
|
|
}
|
|
|
|
static void __exit qpnp_bsi_exit(void)
|
|
{
|
|
spmi_driver_unregister(&qpnp_bsi_driver);
|
|
}
|
|
|
|
MODULE_DESCRIPTION("QPNP PMIC BSI driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
arch_initcall(qpnp_bsi_init);
|
|
module_exit(qpnp_bsi_exit);
|