2013-01-23 22:10:28 +00:00
|
|
|
/* 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;
|
2013-06-21 19:07:05 +00:00
|
|
|
struct qpnp_vadc_chip *vadc_dev;
|
2013-01-23 22:10:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#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. */
|
2013-09-05 00:07:06 +00:00
|
|
|
#define QPNP_BSI_MAX_TRANSMIT_CYCLES 46
|
2013-01-23 22:10:28 +00:00
|
|
|
#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;
|
|
|
|
|
2013-05-04 00:08:30 +00:00
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
|
|
|
|
2013-05-04 00:08:30 +00:00
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
|
|
|
|
2013-05-04 00:08:30 +00:00
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
|
2013-01-23 22:10:28 +00:00
|
|
|
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:
|
|
|
|
*
|
2013-03-08 21:36:34 +00:00
|
|
|
* 1 --> No RBE or RBL
|
|
|
|
* 2 - 15 = x --> RBLx
|
|
|
|
* 16 - 255 = 16 * y + x --> RBEy and RBLx (RBL0 not sent)
|
|
|
|
* 256 --> RBL0
|
2013-01-23 22:10:28 +00:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-03-08 21:36:34 +00:00
|
|
|
if (burst_len % 16 && burst_len > 1) {
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
|
|
|
|
2013-05-04 00:08:30 +00:00
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
|
|
|
|
2013-05-04 00:08:30 +00:00
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
|
|
|
|
2013-05-04 00:08:30 +00:00
|
|
|
rc = qpnp_bsi_clear_bsi_error(chip);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
qpnp_bsi_clear_irq_flags(chip);
|
|
|
|
|
2013-01-23 22:10:28 +00:00
|
|
|
/* 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:
|
2013-09-12 20:13:22 +00:00
|
|
|
case BIF_BUS_STATE_MASTER_DISABLED:
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
2013-09-12 20:13:22 +00:00
|
|
|
u8 reg;
|
2013-01-23 22:10:28 +00:00
|
|
|
|
|
|
|
if (state == chip->state)
|
|
|
|
return 0;
|
|
|
|
|
2013-09-12 20:13:22 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-23 22:10:28 +00:00
|
|
|
switch (state) {
|
|
|
|
case BIF_BUS_STATE_MASTER_DISABLED:
|
2013-09-12 20:13:22 +00:00
|
|
|
/* 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);
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-06-21 19:07:05 +00:00
|
|
|
rc = qpnp_vadc_read(chip->vadc_dev, chip->batt_id_adc_channel,
|
|
|
|
&adc_result);
|
2013-01-23 22:10:28 +00:00
|
|
|
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],
|
2013-11-21 19:23:14 +00:00
|
|
|
qpnp_bsi_isr, IRQF_TRIGGER_HIGH, "bsi-err", chip);
|
2013-01-23 22:10:28 +00:00
|
|
|
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],
|
2013-11-21 19:23:14 +00:00
|
|
|
qpnp_bsi_isr, IRQF_TRIGGER_HIGH, "bsi-rx", chip);
|
2013-01-23 22:10:28 +00:00
|
|
|
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],
|
2013-11-21 19:23:14 +00:00
|
|
|
qpnp_bsi_isr, IRQF_TRIGGER_HIGH, "bsi-tx", chip);
|
2013-01-23 22:10:28 +00:00
|
|
|
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;
|
2013-09-12 20:13:22 +00:00
|
|
|
u8 type[2];
|
2013-01-23 22:10:28 +00:00
|
|
|
|
|
|
|
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;
|
2013-09-12 20:13:22 +00:00
|
|
|
chip->state = BIF_BUS_STATE_MASTER_DISABLED;
|
2013-01-23 22:10:28 +00:00
|
|
|
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) {
|
2013-06-21 19:07:05 +00:00
|
|
|
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);
|
2013-01-23 22:10:28 +00:00
|
|
|
/* 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);
|