| /* 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 __devinit 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 __devinit 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 __devinit 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 __devexit 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 = __devexit_p(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); |