M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
#
# SPMI driver configuration
#
menuconfig SPMI
bool "SPMI support"
help
SPMI (System Power Management Interface) is a two-wire
serial interface between baseband and application processors
and Power Management Integrated Circuits (PMIC).
if SPMI
config SPMI_MSM_PMIC_ARB
tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)"
help
If you say yes to this option, support will be included for the
built-in SPMI PMIC Arbiter interface on Qualcomm MSM family
processors.
This is required for communicating with Qualcomm PMICs and
other devices that have the SPMI interface.
config MSM_QPNP_INT
depends on SPARSE_IRQ
depends on SPMI
depends on OF_SPMI
bool "MSM QPNP INT"
help
Say 'y' here to include support for the Qualcomm QPNP interrupt
support. QPNP is a SPMI based PMIC implementation.
endif

View File

@@ -0,0 +1,10 @@
#
# Makefile for kernel SPMI framework.
#
obj-$(CONFIG_SPMI) += spmi.o spmi-resources.o
obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o
obj-$(CONFIG_MSM_QPNP_INT) += qpnp-int.o
ifdef CONFIG_DEBUG_FS
obj-$(CONFIG_SPMI) += spmi-dbgfs.o
endif

View File

@@ -0,0 +1,627 @@
/* Copyright (c) 2012-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/init.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/irqdomain.h>
#include <linux/interrupt.h>
#include <linux/spmi.h>
#include <linux/radix-tree.h>
#include <linux/slab.h>
#include <linux/printk.h>
#include <linux/ratelimit.h>
#include <asm/irq.h>
#include <asm/mach/irq.h>
#include <mach/qpnp-int.h>
/* 16 slave_ids, 256 per_ids per slave, and 8 ints per per_id */
#define QPNPINT_NR_IRQS (16 * 256 * 8)
/* This value is guaranteed not to be valid for private data */
#define QPNPINT_INVALID_DATA 0x80000000
enum qpnpint_regs {
QPNPINT_REG_RT_STS = 0x10,
QPNPINT_REG_SET_TYPE = 0x11,
QPNPINT_REG_POLARITY_HIGH = 0x12,
QPNPINT_REG_POLARITY_LOW = 0x13,
QPNPINT_REG_LATCHED_CLR = 0x14,
QPNPINT_REG_EN_SET = 0x15,
QPNPINT_REG_EN_CLR = 0x16,
QPNPINT_REG_LATCHED_STS = 0x18,
};
struct q_perip_data {
uint8_t type; /* bitmap */
uint8_t pol_high; /* bitmap */
uint8_t pol_low; /* bitmap */
uint8_t int_en; /* bitmap */
uint8_t use_count;
};
struct q_irq_data {
uint32_t priv_d; /* data to optimize arbiter interactions */
struct q_chip_data *chip_d;
struct q_perip_data *per_d;
uint8_t mask_shift;
uint8_t spmi_slave;
uint16_t spmi_offset;
};
struct q_chip_data {
int bus_nr;
struct irq_domain *domain;
struct qpnp_local_int *cb;
struct spmi_controller *spmi_ctrl;
struct radix_tree_root per_tree;
struct list_head list;
};
static LIST_HEAD(qpnpint_chips);
static DEFINE_MUTEX(qpnpint_chips_mutex);
#define QPNPINT_MAX_BUSSES 4
struct q_chip_data *chip_lookup[QPNPINT_MAX_BUSSES];
/**
* qpnpint_encode_hwirq - translate between qpnp_irq_spec and
* hwirq representation.
*
* slave_offset = (addr->slave * 256 * 8);
* perip_offset = slave_offset + (addr->perip * 8);
* return perip_offset + addr->irq;
*/
static inline int qpnpint_encode_hwirq(struct qpnp_irq_spec *spec)
{
uint32_t hwirq;
if (spec->slave > 15 || spec->irq > 7)
return -EINVAL;
hwirq = (spec->slave << 11);
hwirq |= (spec->per << 3);
hwirq |= spec->irq;
return hwirq;
}
/**
* qpnpint_decode_hwirq - translate between hwirq and
* qpnp_irq_spec representation.
*/
static inline int qpnpint_decode_hwirq(unsigned long hwirq,
struct qpnp_irq_spec *spec)
{
if (hwirq > 65535)
return -EINVAL;
spec->slave = (hwirq >> 11) & 0xF;
spec->per = (hwirq >> 3) & 0xFF;
spec->irq = hwirq & 0x7;
return 0;
}
static int qpnpint_spmi_read(struct q_irq_data *irq_d, uint8_t reg,
void *buf, uint32_t len)
{
struct q_chip_data *chip_d = irq_d->chip_d;
if (!chip_d->spmi_ctrl)
return -ENODEV;
return spmi_ext_register_readl(chip_d->spmi_ctrl, irq_d->spmi_slave,
irq_d->spmi_offset + reg, buf, len);
}
static int qpnpint_spmi_write(struct q_irq_data *irq_d, uint8_t reg,
void *buf, uint32_t len)
{
struct q_chip_data *chip_d = irq_d->chip_d;
int rc;
if (!chip_d->spmi_ctrl)
return -ENODEV;
rc = spmi_ext_register_writel(chip_d->spmi_ctrl, irq_d->spmi_slave,
irq_d->spmi_offset + reg, buf, len);
return rc;
}
static int qpnpint_arbiter_op(struct irq_data *d,
struct q_irq_data *irq_d,
int (*arb_op)(struct spmi_controller *,
struct qpnp_irq_spec *,
uint32_t))
{
struct q_chip_data *chip_d = irq_d->chip_d;
struct qpnp_irq_spec q_spec;
int rc;
if (!arb_op)
return 0;
if (!chip_d->cb->register_priv_data) {
pr_warn_ratelimited("No ability to register arbiter registration data\n");
return -ENODEV;
}
rc = qpnpint_decode_hwirq(d->hwirq, &q_spec);
if (rc) {
pr_err_ratelimited("%s: decode failed on hwirq %lu\n",
__func__, d->hwirq);
return rc;
} else {
if (irq_d->priv_d == QPNPINT_INVALID_DATA) {
rc = chip_d->cb->register_priv_data(chip_d->spmi_ctrl,
&q_spec, &irq_d->priv_d);
if (rc) {
pr_err_ratelimited(
"%s: decode failed on hwirq %lu\n",
__func__, d->hwirq);
return rc;
}
}
arb_op(chip_d->spmi_ctrl, &q_spec, irq_d->priv_d);
}
return 0;
}
static void qpnpint_irq_mask(struct irq_data *d)
{
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
struct q_chip_data *chip_d = irq_d->chip_d;
struct q_perip_data *per_d = irq_d->per_d;
int rc;
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
if (!chip_d->cb) {
pr_warn_ratelimited("No arbiter on bus=%u slave=%u offset=%u\n",
chip_d->bus_nr, irq_d->spmi_slave,
irq_d->spmi_offset);
return;
}
qpnpint_arbiter_op(d, irq_d, chip_d->cb->mask);
per_d->int_en &= ~irq_d->mask_shift;
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_EN_CLR,
(u8 *)&irq_d->mask_shift, 1);
if (rc) {
pr_err_ratelimited("spmi failure on irq %d\n", d->irq);
return;
}
pr_debug("done hwirq %lu irq: %d\n", d->hwirq, d->irq);
}
static void qpnpint_irq_mask_ack(struct irq_data *d)
{
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
struct q_chip_data *chip_d = irq_d->chip_d;
struct q_perip_data *per_d = irq_d->per_d;
int rc;
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
if (!chip_d->cb) {
pr_warn_ratelimited("No arbiter on bus=%u slave=%u offset=%u\n",
chip_d->bus_nr, irq_d->spmi_slave,
irq_d->spmi_offset);
return;
}
qpnpint_arbiter_op(d, irq_d, chip_d->cb->mask);
per_d->int_en &= ~irq_d->mask_shift;
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_EN_CLR,
&irq_d->mask_shift, 1);
if (rc) {
pr_err("spmi failure on irq %d\n", d->irq);
return;
}
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_LATCHED_CLR,
&irq_d->mask_shift, 1);
if (rc) {
pr_err("spmi failure on irq %d\n", d->irq);
return;
}
}
static void qpnpint_irq_unmask(struct irq_data *d)
{
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
struct q_chip_data *chip_d = irq_d->chip_d;
struct q_perip_data *per_d = irq_d->per_d;
int rc;
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
if (!chip_d->cb) {
pr_warn_ratelimited("No arbiter on bus=%u slave=%u offset=%u\n",
chip_d->bus_nr, irq_d->spmi_slave,
irq_d->spmi_offset);
return;
}
qpnpint_arbiter_op(d, irq_d, chip_d->cb->unmask);
per_d->int_en |= irq_d->mask_shift;
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_EN_SET,
&irq_d->mask_shift, 1);
if (rc) {
pr_err("spmi failure on irq %d\n", d->irq);
return;
}
}
static int qpnpint_irq_set_type(struct irq_data *d, unsigned int flow_type)
{
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
struct q_perip_data *per_d = irq_d->per_d;
int rc;
u8 buf[3];
pr_debug("hwirq %lu irq: %d flow: 0x%x\n", d->hwirq,
d->irq, flow_type);
per_d->pol_high &= ~irq_d->mask_shift;
per_d->pol_low &= ~irq_d->mask_shift;
if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) {
per_d->type |= irq_d->mask_shift; /* edge trig */
if (flow_type & IRQF_TRIGGER_RISING)
per_d->pol_high |= irq_d->mask_shift;
if (flow_type & IRQF_TRIGGER_FALLING)
per_d->pol_low |= irq_d->mask_shift;
} else {
if ((flow_type & IRQF_TRIGGER_HIGH) &&
(flow_type & IRQF_TRIGGER_LOW))
return -EINVAL;
per_d->type &= ~irq_d->mask_shift; /* level trig */
if (flow_type & IRQF_TRIGGER_HIGH)
per_d->pol_high |= irq_d->mask_shift;
else
per_d->pol_low |= irq_d->mask_shift;
}
buf[0] = per_d->type;
buf[1] = per_d->pol_high;
buf[2] = per_d->pol_low;
rc = qpnpint_spmi_write(irq_d, QPNPINT_REG_SET_TYPE, &buf, 3);
if (rc) {
pr_err("spmi failure on irq %d\n", d->irq);
return rc;
}
return 0;
}
static int qpnpint_irq_read_line(struct irq_data *d)
{
struct q_irq_data *irq_d = irq_data_get_irq_chip_data(d);
int rc;
u8 buf;
pr_debug("hwirq %lu irq: %d\n", d->hwirq, d->irq);
rc = qpnpint_spmi_read(irq_d, QPNPINT_REG_RT_STS, &buf, 1);
if (rc) {
pr_err("spmi failure on irq %d\n", d->irq);
return rc;
}
return (buf & irq_d->mask_shift) ? 1 : 0;
}
static int qpnpint_irq_set_wake(struct irq_data *d, unsigned int on)
{
return 0;
}
static struct irq_chip qpnpint_chip = {
.name = "qpnp-int",
.irq_mask = qpnpint_irq_mask,
.irq_mask_ack = qpnpint_irq_mask_ack,
.irq_unmask = qpnpint_irq_unmask,
.irq_set_type = qpnpint_irq_set_type,
.irq_read_line = qpnpint_irq_read_line,
.irq_set_wake = qpnpint_irq_set_wake,
.flags = IRQCHIP_MASK_ON_SUSPEND,
};
static int qpnpint_init_irq_data(struct q_chip_data *chip_d,
struct q_irq_data *irq_d,
unsigned long hwirq)
{
struct qpnp_irq_spec q_spec;
int rc;
irq_d->mask_shift = 1 << (hwirq & 0x7);
rc = qpnpint_decode_hwirq(hwirq, &q_spec);
if (rc < 0)
return rc;
irq_d->spmi_slave = q_spec.slave;
irq_d->spmi_offset = q_spec.per << 8;
irq_d->chip_d = chip_d;
irq_d->priv_d = QPNPINT_INVALID_DATA;
if (chip_d->cb && chip_d->cb->register_priv_data) {
rc = chip_d->cb->register_priv_data(chip_d->spmi_ctrl, &q_spec,
&irq_d->priv_d);
if (rc)
return rc;
}
irq_d->per_d->use_count++;
return 0;
}
static struct q_irq_data *qpnpint_alloc_irq_data(
struct q_chip_data *chip_d,
unsigned long hwirq)
{
struct q_irq_data *irq_d;
struct q_perip_data *per_d;
int rc;
irq_d = kzalloc(sizeof(struct q_irq_data), GFP_KERNEL);
if (!irq_d)
return ERR_PTR(-ENOMEM);
/**
* The Peripheral Tree is keyed from the slave + per_id. We're
* ignoring the irq bits here since this peripheral structure
* should be common for all irqs on the same peripheral.
*/
per_d = radix_tree_lookup(&chip_d->per_tree, (hwirq & ~0x7));
if (!per_d) {
per_d = kzalloc(sizeof(struct q_perip_data), GFP_KERNEL);
if (!per_d) {
rc = -ENOMEM;
goto alloc_fail;
}
rc = radix_tree_preload(GFP_KERNEL);
if (rc)
goto alloc_fail;
rc = radix_tree_insert(&chip_d->per_tree,
(hwirq & ~0x7), per_d);
if (rc)
goto alloc_fail;
radix_tree_preload_end();
}
irq_d->per_d = per_d;
return irq_d;
alloc_fail:
kfree(per_d);
kfree(irq_d);
return ERR_PTR(rc);
}
static int qpnpint_irq_domain_dt_translate(struct irq_domain *d,
struct device_node *controller,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq,
unsigned int *out_type)
{
struct qpnp_irq_spec addr;
int ret;
pr_debug("intspec[0] 0x%x intspec[1] 0x%x intspec[2] 0x%x\n",
intspec[0], intspec[1], intspec[2]);
if (d->of_node != controller)
return -EINVAL;
if (intsize != 3)
return -EINVAL;
addr.irq = intspec[2] & 0x7;
addr.per = intspec[1] & 0xFF;
addr.slave = intspec[0] & 0xF;
ret = qpnpint_encode_hwirq(&addr);
if (ret < 0) {
pr_err("invalid intspec\n");
return ret;
}
*out_hwirq = ret;
*out_type = IRQ_TYPE_NONE;
pr_debug("out_hwirq = %lu\n", *out_hwirq);
return 0;
}
static void qpnpint_free_irq_data(struct q_irq_data *irq_d)
{
if (irq_d->per_d->use_count == 1)
kfree(irq_d->per_d);
else
irq_d->per_d->use_count--;
kfree(irq_d);
}
static int qpnpint_irq_domain_map(struct irq_domain *d,
unsigned int virq, irq_hw_number_t hwirq)
{
struct q_chip_data *chip_d = d->host_data;
struct q_irq_data *irq_d;
int rc;
pr_debug("hwirq = %lu\n", hwirq);
if (hwirq < 0 || hwirq >= QPNPINT_NR_IRQS) {
pr_err("hwirq %lu out of bounds\n", hwirq);
return -EINVAL;
}
irq_radix_revmap_insert(d, virq, hwirq);
irq_d = qpnpint_alloc_irq_data(chip_d, hwirq);
if (IS_ERR(irq_d)) {
pr_err("failed to alloc irq data for hwirq %lu\n", hwirq);
return PTR_ERR(irq_d);
}
rc = qpnpint_init_irq_data(chip_d, irq_d, hwirq);
if (rc) {
pr_err("failed to init irq data for hwirq %lu\n", hwirq);
goto map_err;
}
irq_set_chip_and_handler(virq,
&qpnpint_chip,
handle_level_irq);
irq_set_chip_data(virq, irq_d);
#ifdef CONFIG_ARM
set_irq_flags(virq, IRQF_VALID);
#else
irq_set_noprobe(virq);
#endif
return 0;
map_err:
qpnpint_free_irq_data(irq_d);
return rc;
}
void qpnpint_irq_domain_unmap(struct irq_domain *d, unsigned int virq)
{
struct q_irq_data *irq_d = irq_get_chip_data(virq);
if (WARN_ON(!irq_d))
return;
qpnpint_free_irq_data(irq_d);
}
const struct irq_domain_ops qpnpint_irq_domain_ops = {
.map = qpnpint_irq_domain_map,
.unmap = qpnpint_irq_domain_unmap,
.xlate = qpnpint_irq_domain_dt_translate,
};
int qpnpint_register_controller(struct device_node *node,
struct spmi_controller *ctrl,
struct qpnp_local_int *li_cb)
{
struct q_chip_data *chip_d;
if (!node || !ctrl || ctrl->nr >= QPNPINT_MAX_BUSSES)
return -EINVAL;
list_for_each_entry(chip_d, &qpnpint_chips, list)
if (node == chip_d->domain->of_node) {
chip_d->cb = kmemdup(li_cb,
sizeof(*li_cb), GFP_ATOMIC);
if (!chip_d->cb)
return -ENOMEM;
chip_d->spmi_ctrl = ctrl;
chip_lookup[ctrl->nr] = chip_d;
return 0;
}
return -ENOENT;
}
EXPORT_SYMBOL(qpnpint_register_controller);
int qpnpint_unregister_controller(struct device_node *node)
{
struct q_chip_data *chip_d;
if (!node)
return -EINVAL;
list_for_each_entry(chip_d, &qpnpint_chips, list)
if (node == chip_d->domain->of_node) {
kfree(chip_d->cb);
chip_d->cb = NULL;
if (chip_d->spmi_ctrl)
chip_lookup[chip_d->spmi_ctrl->nr] = NULL;
chip_d->spmi_ctrl = NULL;
return 0;
}
return -ENOENT;
}
EXPORT_SYMBOL(qpnpint_unregister_controller);
int qpnpint_handle_irq(struct spmi_controller *spmi_ctrl,
struct qpnp_irq_spec *spec)
{
struct irq_domain *domain;
unsigned long hwirq, busno;
int irq;
if (!spec || !spmi_ctrl)
return -EINVAL;
pr_debug("spec slave = %u per = %u irq = %u\n",
spec->slave, spec->per, spec->irq);
busno = spmi_ctrl->nr;
if (busno >= QPNPINT_MAX_BUSSES)
return -EINVAL;
hwirq = qpnpint_encode_hwirq(spec);
if (hwirq < 0) {
pr_err("invalid irq spec passed\n");
return -EINVAL;
}
domain = chip_lookup[busno]->domain;
irq = irq_radix_revmap_lookup(domain, hwirq);
generic_handle_irq(irq);
return 0;
}
EXPORT_SYMBOL(qpnpint_handle_irq);
int __init qpnpint_of_init(struct device_node *node, struct device_node *parent)
{
struct q_chip_data *chip_d;
chip_d = kzalloc(sizeof(struct q_chip_data), GFP_KERNEL);
if (!chip_d)
return -ENOMEM;
chip_d->domain = irq_domain_add_tree(node,
&qpnpint_irq_domain_ops, chip_d);
if (!chip_d->domain) {
pr_err("Unable to allocate irq_domain\n");
kfree(chip_d);
return -ENOMEM;
}
INIT_RADIX_TREE(&chip_d->per_tree, GFP_ATOMIC);
list_add(&chip_d->list, &qpnpint_chips);
return 0;
}
EXPORT_SYMBOL(qpnpint_of_init);

View File

@@ -0,0 +1,781 @@
/* Copyright (c) 2012-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.
*/
/**
* SPMI Debug-fs support.
*
* Hierarchy schema:
* /sys/kernel/debug/spmi
* /help -- static help text
* /spmi-0
* /spmi-0/address -- Starting register address for reads or writes
* /spmi-0/count -- number of registers to read (only on read)
* /spmi-0/data -- Triggers the SPMI formatted read.
* /spmi-0/data_raw -- Triggers the SPMI raw read or write
* /spmi-#
*/
#define pr_fmt(fmt) "%s:%d: " fmt, __func__, __LINE__
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/debugfs.h>
#include <linux/spmi.h>
#include <linux/ctype.h>
#include "spmi-dbgfs.h"
#define ADDR_LEN 6 /* 5 byte address + 1 space character */
#define CHARS_PER_ITEM 3 /* Format is 'XX ' */
#define ITEMS_PER_LINE 16 /* 16 data items per line */
#define MAX_LINE_LENGTH (ADDR_LEN + (ITEMS_PER_LINE * CHARS_PER_ITEM) + 1)
#define MAX_REG_PER_TRANSACTION (8)
static const char *DFS_ROOT_NAME = "spmi";
static const mode_t DFS_MODE = S_IRUSR | S_IWUSR;
/* Log buffer */
struct spmi_log_buffer {
u32 rpos; /* Current 'read' position in buffer */
u32 wpos; /* Current 'write' position in buffer */
u32 len; /* Length of the buffer */
char data[0]; /* Log buffer */
};
/* SPMI controller specific data */
struct spmi_ctrl_data {
u32 cnt;
u32 addr;
struct dentry *dir;
struct list_head node;
struct spmi_controller *ctrl;
};
/* SPMI transaction parameters */
struct spmi_trans {
u32 cnt; /* Number of bytes to read */
u32 addr; /* 20-bit address: SID + PID + Register offset */
u32 offset; /* Offset of last read data */
bool raw_data; /* Set to true for raw data dump */
struct spmi_controller *ctrl;
struct spmi_log_buffer *log; /* log buffer */
};
struct spmi_dbgfs {
struct dentry *root;
struct mutex lock;
struct list_head ctrl; /* List of spmi_ctrl_data nodes */
struct debugfs_blob_wrapper help_msg;
};
static struct spmi_dbgfs dbgfs_data = {
.lock = __MUTEX_INITIALIZER(dbgfs_data.lock),
.ctrl = LIST_HEAD_INIT(dbgfs_data.ctrl),
.help_msg = {
.data =
"SPMI Debug-FS support\n"
"\n"
"Hierarchy schema:\n"
"/sys/kernel/debug/spmi\n"
" /help -- Static help text\n"
" /spmi-0 -- Directory for SPMI bus 0\n"
" /spmi-0/address -- Starting register address for reads or writes\n"
" /spmi-0/count -- Number of registers to read (only used for reads)\n"
" /spmi-0/data -- Initiates the SPMI read (formatted output)\n"
" /spmi-0/data_raw -- Initiates the SPMI raw read or write\n"
" /spmi-n -- Directory for SPMI bus n\n"
"\n"
"To perform SPMI read or write transactions, you need to first write the\n"
"address of the slave device register to the 'address' file. For read\n"
"transactions, the number of bytes to be read needs to be written to the\n"
"'count' file.\n"
"\n"
"The 'address' file specifies the 20-bit address of a slave device register.\n"
"The upper 4 bits 'address[19..16]' specify the slave identifier (SID) for\n"
"the slave device. The lower 16 bits specify the slave register address.\n"
"\n"
"Reading from the 'data' file will initiate a SPMI read transaction starting\n"
"from slave register 'address' for 'count' number of bytes.\n"
"\n"
"Writing to the 'data' file will initiate a SPMI write transaction starting\n"
"from slave register 'address'. The number of registers written to will\n"
"match the number of bytes written to the 'data' file.\n"
"\n"
"Example: Read 4 bytes starting at register address 0x1234 for SID 2\n"
"\n"
"echo 0x21234 > address\n"
"echo 4 > count\n"
"cat data\n"
"\n"
"Example: Write 3 bytes starting at register address 0x1008 for SID 1\n"
"\n"
"echo 0x11008 > address\n"
"echo 0x01 0x02 0x03 > data\n"
"\n"
"Note that the count file is not used for writes. Since 3 bytes are\n"
"written to the 'data' file, then 3 bytes will be written across the\n"
"SPMI bus.\n\n",
},
};
static int spmi_dfs_open(struct spmi_ctrl_data *ctrl_data, struct file *file)
{
struct spmi_log_buffer *log;
struct spmi_trans *trans;
size_t logbufsize = SZ_4K;
if (!ctrl_data) {
pr_err("No SPMI controller data\n");
return -EINVAL;
}
/* Per file "transaction" data */
trans = kzalloc(sizeof(*trans), GFP_KERNEL);
if (!trans) {
pr_err("Unable to allocate memory for transaction data\n");
return -ENOMEM;
}
/* Allocate log buffer */
log = kzalloc(logbufsize, GFP_KERNEL);
if (!log) {
kfree(trans);
pr_err("Unable to allocate memory for log buffer\n");
return -ENOMEM;
}
log->rpos = 0;
log->wpos = 0;
log->len = logbufsize - sizeof(*log);
trans->log = log;
trans->cnt = ctrl_data->cnt;
trans->addr = ctrl_data->addr;
trans->ctrl = ctrl_data->ctrl;
trans->offset = trans->addr;
file->private_data = trans;
return 0;
}
static int spmi_dfs_data_open(struct inode *inode, struct file *file)
{
struct spmi_ctrl_data *ctrl_data = inode->i_private;
return spmi_dfs_open(ctrl_data, file);
}
static int spmi_dfs_raw_data_open(struct inode *inode, struct file *file)
{
int rc;
struct spmi_trans *trans;
struct spmi_ctrl_data *ctrl_data = inode->i_private;
rc = spmi_dfs_open(ctrl_data, file);
trans = file->private_data;
trans->raw_data = true;
return rc;
}
static int spmi_dfs_close(struct inode *inode, struct file *file)
{
struct spmi_trans *trans = file->private_data;
if (trans && trans->log) {
file->private_data = NULL;
kfree(trans->log);
kfree(trans);
}
return 0;
}
/**
* spmi_read_data: reads data across the SPMI bus
* @ctrl: The SPMI controller
* @buf: buffer to store the data read.
* @offset: SPMI address offset to start reading from.
* @cnt: The number of bytes to read.
*
* Returns 0 on success, otherwise returns error code from SPMI driver.
*/
static int
spmi_read_data(struct spmi_controller *ctrl, uint8_t *buf, int offset, int cnt)
{
int ret = 0;
int len;
uint8_t sid;
uint16_t addr;
while (cnt > 0) {
sid = (offset >> 16) & 0xF;
addr = offset & 0xFFFF;
len = min(cnt, MAX_REG_PER_TRANSACTION);
ret = spmi_ext_register_readl(ctrl, sid, addr, buf, len);
if (ret < 0) {
pr_err("SPMI read failed, err = %d\n", ret);
goto done;
}
cnt -= len;
buf += len;
offset += len;
}
done:
return ret;
}
/**
* spmi_write_data: writes data across the SPMI bus
* @ctrl: The SPMI controller
* @buf: data to be written.
* @offset: SPMI address offset to start writing to.
* @cnt: The number of bytes to write.
*
* Returns 0 on success, otherwise returns error code from SPMI driver.
*/
static int
spmi_write_data(struct spmi_controller *ctrl, uint8_t *buf, int offset, int cnt)
{
int ret = 0;
int len;
uint8_t sid;
uint16_t addr;
while (cnt > 0) {
sid = (offset >> 16) & 0xF;
addr = offset & 0xFFFF;
len = min(cnt, MAX_REG_PER_TRANSACTION);
ret = spmi_ext_register_writel(ctrl, sid, addr, buf, len);
if (ret < 0) {
pr_err("SPMI write failed, err = %d\n", ret);
goto done;
}
cnt -= len;
buf += len;
offset += len;
}
done:
return ret;
}
/**
* print_to_log: format a string and place into the log buffer
* @log: The log buffer to place the result into.
* @fmt: The format string to use.
* @...: The arguments for the format string.
*
* The return value is the number of characters written to @log buffer
* not including the trailing '\0'.
*/
static int print_to_log(struct spmi_log_buffer *log, const char *fmt, ...)
{
va_list args;
int cnt;
char *buf = &log->data[log->wpos];
size_t size = log->len - log->wpos;
va_start(args, fmt);
cnt = vscnprintf(buf, size, fmt, args);
va_end(args);
log->wpos += cnt;
return cnt;
}
/**
* write_next_line_to_log: Writes a single "line" of data into the log buffer
* @trans: Pointer to SPMI transaction data.
* @offset: SPMI address offset to start reading from.
* @pcnt: Pointer to 'cnt' variable. Indicates the number of bytes to read.
*
* The 'offset' is a 20-bits SPMI address which includes a 4-bit slave id (SID),
* an 8-bit peripheral id (PID), and an 8-bit peripheral register address.
*
* On a successful read, the pcnt is decremented by the number of data
* bytes read across the SPMI bus. When the cnt reaches 0, all requested
* bytes have been read.
*/
static int
write_next_line_to_log(struct spmi_trans *trans, int offset, size_t *pcnt)
{
int i, j;
u8 data[ITEMS_PER_LINE];
struct spmi_log_buffer *log = trans->log;
int cnt = 0;
int padding = offset % ITEMS_PER_LINE;
int items_to_read = min(ARRAY_SIZE(data) - padding, *pcnt);
int items_to_log = min(ITEMS_PER_LINE, padding + items_to_read);
/* Buffer needs enough space for an entire line */
if ((log->len - log->wpos) < MAX_LINE_LENGTH)
goto done;
/* Read the desired number of "items" */
if (spmi_read_data(trans->ctrl, data, offset, items_to_read))
goto done;
*pcnt -= items_to_read;
/* Each line starts with the aligned offset (20-bit address) */
cnt = print_to_log(log, "%5.5X ", offset & 0xffff0);
if (cnt == 0)
goto done;
/* If the offset is unaligned, add padding to right justify items */
for (i = 0; i < padding; ++i) {
cnt = print_to_log(log, "-- ");
if (cnt == 0)
goto done;
}
/* Log the data items */
for (j = 0; i < items_to_log; ++i, ++j) {
cnt = print_to_log(log, "%2.2X ", data[j]);
if (cnt == 0)
goto done;
}
/* If the last character was a space, then replace it with a newline */
if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
log->data[log->wpos - 1] = '\n';
done:
return cnt;
}
/**
* write_raw_data_to_log: Writes a single "line" of data into the log buffer
* @trans: Pointer to SPMI transaction data.
* @offset: SPMI address offset to start reading from.
* @pcnt: Pointer to 'cnt' variable. Indicates the number of bytes to read.
*
* The 'offset' is a 20-bits SPMI address which includes a 4-bit slave id (SID),
* an 8-bit peripheral id (PID), and an 8-bit peripheral register address.
*
* On a successful read, the pcnt is decremented by the number of data
* bytes read across the SPMI bus. When the cnt reaches 0, all requested
* bytes have been read.
*/
static int
write_raw_data_to_log(struct spmi_trans *trans, int offset, size_t *pcnt)
{
u8 data[16];
struct spmi_log_buffer *log = trans->log;
int i;
int cnt = 0;
int items_to_read = min(ARRAY_SIZE(data), *pcnt);
/* Buffer needs enough space for an entire line */
if ((log->len - log->wpos) < 80)
goto done;
/* Read the desired number of "items" */
if (spmi_read_data(trans->ctrl, data, offset, items_to_read))
goto done;
*pcnt -= items_to_read;
/* Log the data items */
for (i = 0; i < items_to_read; ++i) {
cnt = print_to_log(log, "0x%2.2X ", data[i]);
if (cnt == 0)
goto done;
}
/* If the last character was a space, then replace it with a newline */
if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
log->data[log->wpos - 1] = '\n';
done:
return cnt;
}
/**
* get_log_data - reads data across the SPMI bus and saves to the log buffer
* @trans: Pointer to SPMI transaction data.
*
* Returns the number of "items" read or SPMI error code for read failures.
*/
static int get_log_data(struct spmi_trans *trans)
{
int cnt;
int last_cnt;
int items_read;
int total_items_read = 0;
u32 offset = trans->offset;
size_t item_cnt = trans->cnt;
struct spmi_log_buffer *log = trans->log;
int (*write_to_log)(struct spmi_trans *, int, size_t *);
if (item_cnt == 0)
return 0;
if (trans->raw_data)
write_to_log = write_raw_data_to_log;
else
write_to_log = write_next_line_to_log;
/* Reset the log buffer 'pointers' */
log->wpos = log->rpos = 0;
/* Keep reading data until the log is full */
do {
last_cnt = item_cnt;
cnt = write_to_log(trans, offset, &item_cnt);
items_read = last_cnt - item_cnt;
offset += items_read;
total_items_read += items_read;
} while (cnt && item_cnt > 0);
/* Adjust the transaction offset and count */
trans->cnt = item_cnt;
trans->offset += total_items_read;
return total_items_read;
}
/**
* spmi_dfs_reg_write: write user's byte array (coded as string) over SPMI.
* @file: file pointer
* @buf: user data to be written.
* @count: maximum space available in @buf
* @ppos: starting position
* @return number of user byte written, or negative error value
*/
static ssize_t spmi_dfs_reg_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int bytes_read;
int data;
int pos = 0;
int cnt = 0;
u8 *values;
size_t ret = 0;
struct spmi_trans *trans = file->private_data;
u32 offset = trans->offset;
/* Make a copy of the user data */
char *kbuf = kmalloc(count + 1, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
ret = copy_from_user(kbuf, buf, count);
if (ret == count) {
pr_err("failed to copy data from user\n");
ret = -EFAULT;
goto free_buf;
}
count -= ret;
*ppos += count;
kbuf[count] = '\0';
/* Override the text buffer with the raw data */
values = kbuf;
/* Parse the data in the buffer. It should be a string of numbers */
while (sscanf(kbuf + pos, "%i%n", &data, &bytes_read) == 1) {
pos += bytes_read;
values[cnt++] = data & 0xff;
}
if (!cnt)
goto free_buf;
/* Perform the SPMI write(s) */
ret = spmi_write_data(trans->ctrl, values, offset, cnt);
if (ret) {
pr_err("SPMI write failed, err = %zu\n", ret);
} else {
ret = count;
trans->offset += cnt;
}
free_buf:
kfree(kbuf);
return ret;
}
/**
* spmi_dfs_reg_read: reads value(s) over SPMI and fill user's buffer a
* byte array (coded as string)
* @file: file pointer
* @buf: where to put the result
* @count: maximum space available in @buf
* @ppos: starting position
* @return number of user bytes read, or negative error value
*/
static ssize_t spmi_dfs_reg_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct spmi_trans *trans = file->private_data;
struct spmi_log_buffer *log = trans->log;
size_t ret;
size_t len;
/* Is the the log buffer empty */
if (log->rpos >= log->wpos) {
if (get_log_data(trans) <= 0)
return 0;
}
len = min(count, log->wpos - log->rpos);
ret = copy_to_user(buf, &log->data[log->rpos], len);
if (ret == len) {
pr_err("error copy SPMI register values to user\n");
return -EFAULT;
}
/* 'ret' is the number of bytes not copied */
len -= ret;
*ppos += len;
log->rpos += len;
return len;
}
static const struct file_operations spmi_dfs_reg_fops = {
.open = spmi_dfs_data_open,
.release = spmi_dfs_close,
.read = spmi_dfs_reg_read,
.write = spmi_dfs_reg_write,
};
static const struct file_operations spmi_dfs_raw_data_fops = {
.open = spmi_dfs_raw_data_open,
.release = spmi_dfs_close,
.read = spmi_dfs_reg_read,
.write = spmi_dfs_reg_write,
};
/**
* spmi_dfs_create_fs: create debugfs file system.
* @return pointer to root directory or NULL if failed to create fs
*/
static struct dentry *spmi_dfs_create_fs(void)
{
struct dentry *root, *file;
pr_debug("Creating SPMI debugfs file-system\n");
root = debugfs_create_dir(DFS_ROOT_NAME, NULL);
if (IS_ERR(root)) {
pr_err("Error creating top level directory err:%ld",
(long)root);
if ((int)root == -ENODEV)
pr_err("debugfs is not enabled in the kernel");
return NULL;
}
dbgfs_data.help_msg.size = strlen(dbgfs_data.help_msg.data);
file = debugfs_create_blob("help", S_IRUGO, root, &dbgfs_data.help_msg);
if (!file) {
pr_err("error creating help entry\n");
goto err_remove_fs;
}
return root;
err_remove_fs:
debugfs_remove_recursive(root);
return NULL;
}
/**
* spmi_dfs_get_root: return a pointer to SPMI debugfs root directory.
* @brief return a pointer to the existing directory, or if no root
* directory exists then create one. Directory is created with file that
* configures SPMI transaction, namely: sid, address, and count.
* @returns valid pointer on success or NULL
*/
struct dentry *spmi_dfs_get_root(void)
{
if (dbgfs_data.root)
return dbgfs_data.root;
if (mutex_lock_interruptible(&dbgfs_data.lock) < 0)
return NULL;
/* critical section */
if (!dbgfs_data.root) { /* double checking idiom */
dbgfs_data.root = spmi_dfs_create_fs();
}
mutex_unlock(&dbgfs_data.lock);
return dbgfs_data.root;
}
/*
* spmi_dfs_add_controller: adds new spmi controller entry
* @return zero on success
*/
int spmi_dfs_add_controller(struct spmi_controller *ctrl)
{
struct dentry *dir;
struct dentry *root;
struct dentry *file;
struct spmi_ctrl_data *ctrl_data;
pr_debug("Adding controller %s\n", ctrl->dev.kobj.name);
root = spmi_dfs_get_root();
if (!root)
return -ENOENT;
/* Allocate transaction data for the controller */
ctrl_data = kzalloc(sizeof(*ctrl_data), GFP_KERNEL);
if (!ctrl_data)
return -ENOMEM;
dir = debugfs_create_dir(ctrl->dev.kobj.name, root);
if (!dir) {
pr_err("Error creating entry for spmi controller %s\n",
ctrl->dev.kobj.name);
goto err_create_dir_failed;
}
ctrl_data->cnt = 1;
ctrl_data->dir = dir;
ctrl_data->ctrl = ctrl;
file = debugfs_create_u32("count", DFS_MODE, dir, &ctrl_data->cnt);
if (!file) {
pr_err("error creating 'count' entry\n");
goto err_remove_fs;
}
file = debugfs_create_x32("address", DFS_MODE, dir, &ctrl_data->addr);
if (!file) {
pr_err("error creating 'address' entry\n");
goto err_remove_fs;
}
file = debugfs_create_file("data", DFS_MODE, dir, ctrl_data,
&spmi_dfs_reg_fops);
if (!file) {
pr_err("error creating 'data' entry\n");
goto err_remove_fs;
}
file = debugfs_create_file("data_raw", DFS_MODE, dir, ctrl_data,
&spmi_dfs_raw_data_fops);
if (!file) {
pr_err("error creating 'data' entry\n");
goto err_remove_fs;
}
list_add(&ctrl_data->node, &dbgfs_data.ctrl);
return 0;
err_remove_fs:
debugfs_remove_recursive(dir);
err_create_dir_failed:
kfree(ctrl_data);
return -ENOMEM;
}
/*
* spmi_dfs_del_controller: deletes spmi controller entry
* @return zero on success
*/
int spmi_dfs_del_controller(struct spmi_controller *ctrl)
{
int rc;
struct list_head *pos, *tmp;
struct spmi_ctrl_data *ctrl_data;
pr_debug("Deleting controller %s\n", ctrl->dev.kobj.name);
rc = mutex_lock_interruptible(&dbgfs_data.lock);
if (rc)
return rc;
list_for_each_safe(pos, tmp, &dbgfs_data.ctrl) {
ctrl_data = list_entry(pos, struct spmi_ctrl_data, node);
if (ctrl_data->ctrl == ctrl) {
debugfs_remove_recursive(ctrl_data->dir);
list_del(pos);
kfree(ctrl_data);
rc = 0;
goto done;
}
}
rc = -EINVAL;
pr_debug("Unknown controller %s\n", ctrl->dev.kobj.name);
done:
mutex_unlock(&dbgfs_data.lock);
return rc;
}
/*
* spmi_dfs_create_file: creates a new file in the SPMI debugfs
* @returns valid dentry pointer on success or NULL
*/
struct dentry *spmi_dfs_create_file(struct spmi_controller *ctrl,
const char *name, void *data,
const struct file_operations *fops)
{
struct spmi_ctrl_data *ctrl_data;
list_for_each_entry(ctrl_data, &dbgfs_data.ctrl, node) {
if (ctrl_data->ctrl == ctrl)
return debugfs_create_file(name,
DFS_MODE, ctrl_data->dir, data, fops);
}
return NULL;
}
static void __exit spmi_dfs_delete_all_ctrl(struct list_head *head)
{
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, head) {
struct spmi_ctrl_data *ctrl_data;
ctrl_data = list_entry(pos, struct spmi_ctrl_data, node);
list_del(pos);
kfree(ctrl_data);
}
}
static void __exit spmi_dfs_destroy(void)
{
pr_debug("de-initializing spmi debugfs ...");
if (mutex_lock_interruptible(&dbgfs_data.lock) < 0)
return;
if (dbgfs_data.root) {
debugfs_remove_recursive(dbgfs_data.root);
dbgfs_data.root = NULL;
spmi_dfs_delete_all_ctrl(&dbgfs_data.ctrl);
}
mutex_unlock(&dbgfs_data.lock);
}
module_exit(spmi_dfs_destroy);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:spmi_debug_fs");

View File

@@ -0,0 +1,29 @@
/* Copyright (c) 2012-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.
*/
#ifndef _SPMI_DBGFS_H
#define _SPMI_DBGFS_H
#include <linux/debugfs.h>
#ifdef CONFIG_DEBUG_FS
int spmi_dfs_add_controller(struct spmi_controller *ctrl);
int spmi_dfs_del_controller(struct spmi_controller *ctrl);
#else
static int spmi_dfs_add_controller(struct spmi_controller *ctrl) { return 0; }
static int spmi_dfs_del_controller(struct spmi_controller *ctrl) { return 0; }
#endif
struct dentry *spmi_dfs_create_file(struct spmi_controller *ctrl,
const char *name, void *data,
const struct file_operations *fops);
#endif /* _SPMI_DBGFS_H */

View File

@@ -0,0 +1,810 @@
/* Copyright (c) 2012-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/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spmi.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/of_spmi.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <mach/qpnp-int.h>
#include "spmi-dbgfs.h"
#define SPMI_PMIC_ARB_NAME "spmi_pmic_arb"
/* PMIC Arbiter configuration registers */
#define PMIC_ARB_VERSION 0x0000
#define PMIC_ARB_INT_EN 0x0004
/* PMIC Arbiter channel registers */
#define PMIC_ARB_CMD(N) (0x0800 + (0x80 * (N)))
#define PMIC_ARB_CONFIG(N) (0x0804 + (0x80 * (N)))
#define PMIC_ARB_STATUS(N) (0x0808 + (0x80 * (N)))
#define PMIC_ARB_WDATA0(N) (0x0810 + (0x80 * (N)))
#define PMIC_ARB_WDATA1(N) (0x0814 + (0x80 * (N)))
#define PMIC_ARB_RDATA0(N) (0x0818 + (0x80 * (N)))
#define PMIC_ARB_RDATA1(N) (0x081C + (0x80 * (N)))
/* Interrupt Controller */
#define SPMI_PIC_OWNER_ACC_STATUS(M, N) (0x0000 + ((32 * (M)) + (4 * (N))))
#define SPMI_PIC_ACC_ENABLE(N) (0x0200 + (4 * (N)))
#define SPMI_PIC_IRQ_STATUS(N) (0x0600 + (4 * (N)))
#define SPMI_PIC_IRQ_CLEAR(N) (0x0A00 + (4 * (N)))
/* Mapping Table */
#define SPMI_MAPPING_TABLE_REG(N) (0x0B00 + (4 * (N)))
#define SPMI_MAPPING_BIT_INDEX(X) (((X) >> 18) & 0xF)
#define SPMI_MAPPING_BIT_IS_0_FLAG(X) (((X) >> 17) & 0x1)
#define SPMI_MAPPING_BIT_IS_0_RESULT(X) (((X) >> 9) & 0xFF)
#define SPMI_MAPPING_BIT_IS_1_FLAG(X) (((X) >> 8) & 0x1)
#define SPMI_MAPPING_BIT_IS_1_RESULT(X) (((X) >> 0) & 0xFF)
#define SPMI_MAPPING_TABLE_LEN 255
#define SPMI_MAPPING_TABLE_TREE_DEPTH 16 /* Maximum of 16-bits */
/* Ownership Table */
#define SPMI_OWNERSHIP_TABLE_REG(N) (0x0700 + (4 * (N)))
#define SPMI_OWNERSHIP_PERIPH2OWNER(X) ((X) & 0x7)
/* Channel Status fields */
enum pmic_arb_chnl_status {
PMIC_ARB_STATUS_DONE = (1 << 0),
PMIC_ARB_STATUS_FAILURE = (1 << 1),
PMIC_ARB_STATUS_DENIED = (1 << 2),
PMIC_ARB_STATUS_DROPPED = (1 << 3),
};
/* Command register fields */
#define PMIC_ARB_CMD_MAX_BYTE_COUNT 8
/* Command Opcodes */
enum pmic_arb_cmd_op_code {
PMIC_ARB_OP_EXT_WRITEL = 0,
PMIC_ARB_OP_EXT_READL = 1,
PMIC_ARB_OP_EXT_WRITE = 2,
PMIC_ARB_OP_RESET = 3,
PMIC_ARB_OP_SLEEP = 4,
PMIC_ARB_OP_SHUTDOWN = 5,
PMIC_ARB_OP_WAKEUP = 6,
PMIC_ARB_OP_AUTHENTICATE = 7,
PMIC_ARB_OP_MSTR_READ = 8,
PMIC_ARB_OP_MSTR_WRITE = 9,
PMIC_ARB_OP_EXT_READ = 13,
PMIC_ARB_OP_WRITE = 14,
PMIC_ARB_OP_READ = 15,
PMIC_ARB_OP_ZERO_WRITE = 16,
};
/* Maximum number of support PMIC peripherals */
#define PMIC_ARB_MAX_PERIPHS 256
#define PMIC_ARB_PERIPH_ID_VALID (1 << 15)
#define PMIC_ARB_TIMEOUT_US 100
#define PMIC_ARB_MAX_TRANS_BYTES (8)
#define PMIC_ARB_APID_MASK 0xFF
#define PMIC_ARB_PPID_MASK 0xFFF
/**
* base - base address of the PMIC Arbiter core registers.
* intr - base address of the SPMI interrupt control registers
*/
struct spmi_pmic_arb_dev {
struct spmi_controller controller;
struct device *dev;
struct device *slave;
void __iomem *base;
void __iomem *intr;
void __iomem *cnfg;
int pic_irq;
bool allow_wakeup;
spinlock_t lock;
u8 owner;
u8 channel;
u8 min_apid;
u8 max_apid;
u16 periph_id_map[PMIC_ARB_MAX_PERIPHS];
u32 mapping_table[SPMI_MAPPING_TABLE_LEN];
};
static u32 pmic_arb_read(struct spmi_pmic_arb_dev *dev, u32 offset)
{
u32 val = readl_relaxed(dev->base + offset);
pr_debug("address 0x%p, val 0x%x\n", dev->base + offset, val);
return val;
}
static void pmic_arb_write(struct spmi_pmic_arb_dev *dev, u32 offset, u32 val)
{
pr_debug("address 0x%p, val 0x%x\n", dev->base + offset, val);
writel_relaxed(val, dev->base + offset);
}
static int pmic_arb_wait_for_done(struct spmi_pmic_arb_dev *dev)
{
u32 status = 0;
u32 timeout = PMIC_ARB_TIMEOUT_US;
u32 offset = PMIC_ARB_STATUS(dev->channel);
while (timeout--) {
status = pmic_arb_read(dev, offset);
if (status & PMIC_ARB_STATUS_DONE) {
if (status & PMIC_ARB_STATUS_DENIED) {
dev_err(dev->dev,
"%s: transaction denied (0x%x)\n",
__func__, status);
return -EPERM;
}
if (status & PMIC_ARB_STATUS_FAILURE) {
dev_err(dev->dev,
"%s: transaction failed (0x%x)\n",
__func__, status);
return -EIO;
}
if (status & PMIC_ARB_STATUS_DROPPED) {
dev_err(dev->dev,
"%s: transaction dropped (0x%x)\n",
__func__, status);
return -EIO;
}
return 0;
}
udelay(1);
}
dev_err(dev->dev, "%s: timeout, status 0x%x\n", __func__, status);
return -ETIMEDOUT;
}
/**
* pa_read_data: reads pmic-arb's register and copy 1..4 bytes to buf
* @bc byte count -1. range: 0..3
* @reg register's address
* @buf output parameter, length must be bc+1
*/
static void pa_read_data(struct spmi_pmic_arb_dev *dev, u8 *buf, u32 reg, u8 bc)
{
u32 data = pmic_arb_read(dev, reg);
memcpy(buf, &data, (bc & 3) + 1);
}
/**
* pa_write_data: write 1..4 bytes from buf to pmic-arb's register
* @bc byte-count -1. range: 0..3
* @reg register's address
* @buf buffer to write. length must be bc+1
*/
static void
pa_write_data(struct spmi_pmic_arb_dev *dev, u8 *buf, u32 reg, u8 bc)
{
u32 data = 0;
memcpy(&data, buf, (bc & 3) + 1);
pmic_arb_write(dev, reg, data);
}
/* Non-data command */
static int pmic_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid)
{
struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl);
unsigned long flags;
u32 cmd;
int rc;
pr_debug("op:0x%x sid:%d\n", opc, sid);
/* Check for valid non-data command */
if (opc < SPMI_CMD_RESET || opc > SPMI_CMD_WAKEUP)
return -EINVAL;
cmd = ((opc | 0x40) << 27) | ((sid & 0xf) << 20);
spin_lock_irqsave(&pmic_arb->lock, flags);
pmic_arb_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd);
rc = pmic_arb_wait_for_done(pmic_arb);
spin_unlock_irqrestore(&pmic_arb->lock, flags);
return rc;
}
static int pmic_arb_read_cmd(struct spmi_controller *ctrl,
u8 opc, u8 sid, u16 addr, u8 bc, u8 *buf)
{
struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl);
unsigned long flags;
u32 cmd;
int rc;
if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
dev_err(pmic_arb->dev
, "pmic-arb supports 1..%d bytes per trans, but:%d requested"
, PMIC_ARB_MAX_TRANS_BYTES, bc+1);
return -EINVAL;
}
pr_debug("op:0x%x sid:%d bc:%d addr:0x%x\n", opc, sid, bc, addr);
/* Check the opcode */
if (opc >= 0x60 && opc <= 0x7F)
opc = PMIC_ARB_OP_READ;
else if (opc >= 0x20 && opc <= 0x2F)
opc = PMIC_ARB_OP_EXT_READ;
else if (opc >= 0x38 && opc <= 0x3F)
opc = PMIC_ARB_OP_EXT_READL;
else
return -EINVAL;
cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7);
spin_lock_irqsave(&pmic_arb->lock, flags);
pmic_arb_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd);
rc = pmic_arb_wait_for_done(pmic_arb);
if (rc)
goto done;
/* Read from FIFO, note 'bc' is actually number of bytes minus 1 */
pa_read_data(pmic_arb, buf, PMIC_ARB_RDATA0(pmic_arb->channel)
, min_t(u8, bc, 3));
if (bc > 3)
pa_read_data(pmic_arb, buf + 4,
PMIC_ARB_RDATA1(pmic_arb->channel), bc - 4);
done:
spin_unlock_irqrestore(&pmic_arb->lock, flags);
return rc;
}
static int pmic_arb_write_cmd(struct spmi_controller *ctrl,
u8 opc, u8 sid, u16 addr, u8 bc, u8 *buf)
{
struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl);
unsigned long flags;
u32 cmd;
int rc;
if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
dev_err(pmic_arb->dev
, "pmic-arb supports 1..%d bytes per trans, but:%d requested"
, PMIC_ARB_MAX_TRANS_BYTES, bc+1);
return -EINVAL;
}
pr_debug("op:0x%x sid:%d bc:%d addr:0x%x\n", opc, sid, bc, addr);
/* Check the opcode */
if (opc >= 0x40 && opc <= 0x5F)
opc = PMIC_ARB_OP_WRITE;
else if (opc >= 0x00 && opc <= 0x0F)
opc = PMIC_ARB_OP_EXT_WRITE;
else if (opc >= 0x30 && opc <= 0x37)
opc = PMIC_ARB_OP_EXT_WRITEL;
else if (opc >= 0x80 && opc <= 0xFF)
opc = PMIC_ARB_OP_ZERO_WRITE;
else
return -EINVAL;
cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7);
/* Write data to FIFOs */
spin_lock_irqsave(&pmic_arb->lock, flags);
pa_write_data(pmic_arb, buf, PMIC_ARB_WDATA0(pmic_arb->channel)
, min_t(u8, bc, 3));
if (bc > 3)
pa_write_data(pmic_arb, buf + 4,
PMIC_ARB_WDATA1(pmic_arb->channel), bc - 4);
/* Start the transaction */
pmic_arb_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd);
rc = pmic_arb_wait_for_done(pmic_arb);
spin_unlock_irqrestore(&pmic_arb->lock, flags);
return rc;
}
/* APID to PPID */
static u16 get_peripheral_id(struct spmi_pmic_arb_dev *pmic_arb, u8 apid)
{
return pmic_arb->periph_id_map[apid] & PMIC_ARB_PPID_MASK;
}
/* APID to PPID, returns valid flag */
static int is_apid_valid(struct spmi_pmic_arb_dev *pmic_arb, u8 apid)
{
return pmic_arb->periph_id_map[apid] & PMIC_ARB_PERIPH_ID_VALID;
}
static u32 search_mapping_table(struct spmi_pmic_arb_dev *pmic_arb, u16 ppid)
{
u32 *mapping_table = pmic_arb->mapping_table;
u32 apid = PMIC_ARB_MAX_PERIPHS;
int index = 0;
u32 data;
int i;
for (i = 0; i < SPMI_MAPPING_TABLE_TREE_DEPTH; ++i) {
data = mapping_table[index];
if (ppid & (1 << SPMI_MAPPING_BIT_INDEX(data))) {
if (SPMI_MAPPING_BIT_IS_1_FLAG(data)) {
index = SPMI_MAPPING_BIT_IS_1_RESULT(data);
} else {
apid = SPMI_MAPPING_BIT_IS_1_RESULT(data);
break;
}
} else {
if (SPMI_MAPPING_BIT_IS_0_FLAG(data)) {
index = SPMI_MAPPING_BIT_IS_0_RESULT(data);
} else {
apid = SPMI_MAPPING_BIT_IS_0_RESULT(data);
break;
}
}
}
return apid;
}
/* PPID to APID */
static uint32_t map_peripheral_id(struct spmi_pmic_arb_dev *pmic_arb, u16 ppid)
{
u32 apid = search_mapping_table(pmic_arb, ppid);
u32 old_ppid;
u32 owner;
/* If the apid was found, add it to the lookup table */
if (apid < PMIC_ARB_MAX_PERIPHS) {
old_ppid = get_peripheral_id(pmic_arb, apid);
owner = SPMI_OWNERSHIP_PERIPH2OWNER(
readl_relaxed(pmic_arb->cnfg +
SPMI_OWNERSHIP_TABLE_REG(apid)));
/* Check ownership */
if (owner != pmic_arb->owner) {
dev_err(pmic_arb->dev, "PPID 0x%x incorrect owner %d\n",
ppid, owner);
return PMIC_ARB_MAX_PERIPHS;
}
/* Check if already mapped */
if (pmic_arb->periph_id_map[apid] & PMIC_ARB_PERIPH_ID_VALID) {
if (ppid != old_ppid) {
dev_err(pmic_arb->dev,
"PPID 0x%x: APID 0x%x already mapped\n",
ppid, apid);
return PMIC_ARB_MAX_PERIPHS;
}
return apid;
}
pmic_arb->periph_id_map[apid] = ppid | PMIC_ARB_PERIPH_ID_VALID;
if (apid > pmic_arb->max_apid)
pmic_arb->max_apid = apid;
if (apid < pmic_arb->min_apid)
pmic_arb->min_apid = apid;
return apid;
}
dev_err(pmic_arb->dev, "Unknown ppid 0x%x\n", ppid);
return PMIC_ARB_MAX_PERIPHS;
}
/* Enable interrupt at the PMIC Arbiter PIC */
static int pmic_arb_pic_enable(struct spmi_controller *ctrl,
struct qpnp_irq_spec *spec, uint32_t data)
{
struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl);
u8 apid = data & PMIC_ARB_APID_MASK;
unsigned long flags;
u32 status;
dev_dbg(pmic_arb->dev, "PIC enable, apid:0x%x, sid:0x%x, pid:0x%x\n",
apid, spec->slave, spec->per);
if (data < pmic_arb->min_apid || data > pmic_arb->max_apid) {
dev_err(pmic_arb->dev, "int enable: invalid APID %d\n", data);
return -EINVAL;
}
if (!is_apid_valid(pmic_arb, apid)) {
dev_err(pmic_arb->dev, "int enable: int not supported\n");
return -EINVAL;
}
spin_lock_irqsave(&pmic_arb->lock, flags);
status = readl_relaxed(pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid));
if (!status) {
writel_relaxed(0x1, pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid));
/* Interrupt needs to be enabled before returning to caller */
wmb();
}
spin_unlock_irqrestore(&pmic_arb->lock, flags);
return 0;
}
/* Disable interrupt at the PMIC Arbiter PIC */
static int pmic_arb_pic_disable(struct spmi_controller *ctrl,
struct qpnp_irq_spec *spec, uint32_t data)
{
struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl);
u8 apid = data & PMIC_ARB_APID_MASK;
unsigned long flags;
u32 status;
dev_dbg(pmic_arb->dev, "PIC disable, apid:0x%x, sid:0x%x, pid:0x%x\n",
apid, spec->slave, spec->per);
if (data < pmic_arb->min_apid || data > pmic_arb->max_apid) {
dev_err(pmic_arb->dev, "int disable: invalid APID %d\n", data);
return -EINVAL;
}
if (!is_apid_valid(pmic_arb, apid)) {
dev_err(pmic_arb->dev, "int disable: int not supported\n");
return -EINVAL;
}
spin_lock_irqsave(&pmic_arb->lock, flags);
status = readl_relaxed(pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid));
if (status) {
writel_relaxed(0x0, pmic_arb->intr + SPMI_PIC_ACC_ENABLE(apid));
/* Interrupt needs to be disabled before returning to caller */
wmb();
}
spin_unlock_irqrestore(&pmic_arb->lock, flags);
return 0;
}
static irqreturn_t
periph_interrupt(struct spmi_pmic_arb_dev *pmic_arb, u8 apid)
{
u16 ppid = get_peripheral_id(pmic_arb, apid);
void __iomem *base = pmic_arb->intr;
u8 sid = (ppid >> 8) & 0x0F;
u8 pid = ppid & 0xFF;
u32 status;
int i;
if (!is_apid_valid(pmic_arb, apid)) {
dev_err(pmic_arb->dev, "unknown peripheral id 0x%x\n", ppid);
/* return IRQ_NONE; */
}
/* Read the peripheral specific interrupt bits */
status = readl_relaxed(base + SPMI_PIC_IRQ_STATUS(apid));
/* Clear the peripheral interrupts */
writel_relaxed(status, base + SPMI_PIC_IRQ_CLEAR(apid));
/* Interrupt needs to be cleared/acknowledged before exiting ISR */
mb();
dev_dbg(pmic_arb->dev,
"interrupt, apid:0x%x, sid:0x%x, pid:0x%x, intr:0x%x\n",
apid, sid, pid, status);
/* Send interrupt notification */
for (i = 0; status && i < 8; ++i, status >>= 1) {
if (status & 0x1) {
struct qpnp_irq_spec irq_spec = {
.slave = sid,
.per = pid,
.irq = i,
};
qpnpint_handle_irq(&pmic_arb->controller, &irq_spec);
}
}
return IRQ_HANDLED;
}
/* Peripheral interrupt handler */
static irqreturn_t pmic_arb_periph_irq(int irq, void *dev_id)
{
struct spmi_pmic_arb_dev *pmic_arb = dev_id;
void __iomem *intr = pmic_arb->intr;
u8 ee = pmic_arb->owner;
u32 ret = IRQ_NONE;
u32 status;
int first = pmic_arb->min_apid >> 5;
int last = pmic_arb->max_apid >> 5;
int i, j;
dev_dbg(pmic_arb->dev, "Peripheral interrupt detected\n");
/* Check the accumulated interrupt status */
for (i = first; i <= last; ++i) {
status = readl_relaxed(intr + SPMI_PIC_OWNER_ACC_STATUS(ee, i));
for (j = 0; status && j < 32; ++j, status >>= 1) {
if (status & 0x1) {
u8 id = (i * 32) + j;
ret |= periph_interrupt(pmic_arb, id);
}
}
}
return ret;
}
/* Callback to register an APID for specific slave/peripheral */
static int pmic_arb_intr_priv_data(struct spmi_controller *ctrl,
struct qpnp_irq_spec *spec, uint32_t *data)
{
struct spmi_pmic_arb_dev *pmic_arb = spmi_get_ctrldata(ctrl);
u16 ppid = ((spec->slave & 0x0F) << 8) | (spec->per & 0xFF);
*data = map_peripheral_id(pmic_arb, ppid);
return 0;
}
static int pmic_arb_mapping_data_show(struct seq_file *file, void *unused)
{
struct spmi_pmic_arb_dev *pmic_arb = file->private;
int first = pmic_arb->min_apid;
int last = pmic_arb->max_apid;
int i;
for (i = first; i <= last; ++i) {
if (!is_apid_valid(pmic_arb, i))
continue;
seq_printf(file, "APID 0x%.2x = PPID 0x%.3x. Enabled:%d\n",
i, get_peripheral_id(pmic_arb, i),
readl_relaxed(pmic_arb->intr + SPMI_PIC_ACC_ENABLE(i)));
}
return 0;
}
static int pmic_arb_mapping_data_open(struct inode *inode, struct file *file)
{
return single_open(file, pmic_arb_mapping_data_show, inode->i_private);
}
static const struct file_operations pmic_arb_dfs_fops = {
.open = pmic_arb_mapping_data_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __devinit
spmi_pmic_arb_get_property(struct platform_device *pdev, char *pname, u32 *prop)
{
int ret = of_property_read_u32(pdev->dev.of_node, pname, prop);
if (ret)
dev_err(&pdev->dev, "missing property: %s\n", pname);
else
pr_debug("%s = 0x%x\n", pname, *prop);
return ret;
}
static struct qpnp_local_int spmi_pmic_arb_intr_cb = {
.mask = pmic_arb_pic_disable,
.unmask = pmic_arb_pic_enable,
.register_priv_data = pmic_arb_intr_priv_data,
};
static int __devinit spmi_pmic_arb_probe(struct platform_device *pdev)
{
struct spmi_pmic_arb_dev *pmic_arb;
struct resource *mem_res;
u32 cell_index;
u32 prop;
int ret = 0;
int i;
pr_debug("SPMI PMIC Arbiter\n");
pmic_arb = devm_kzalloc(&pdev->dev,
sizeof(struct spmi_pmic_arb_dev), GFP_KERNEL);
if (!pmic_arb) {
dev_err(&pdev->dev, "can not allocate pmic_arb data\n");
return -ENOMEM;
}
mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
if (!mem_res) {
dev_err(&pdev->dev, "missing base memory resource\n");
return -ENODEV;
}
pmic_arb->base = devm_ioremap(&pdev->dev,
mem_res->start, resource_size(mem_res));
if (!pmic_arb->base) {
dev_err(&pdev->dev, "ioremap of 'base' failed\n");
return -ENOMEM;
}
mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr");
if (!mem_res) {
dev_err(&pdev->dev, "missing mem resource (interrupts)\n");
return -ENODEV;
}
pmic_arb->intr = devm_ioremap(&pdev->dev,
mem_res->start, resource_size(mem_res));
if (!pmic_arb->intr) {
dev_err(&pdev->dev, "ioremap of 'intr' failed\n");
return -ENOMEM;
}
mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cnfg");
if (!mem_res) {
dev_err(&pdev->dev, "missing mem resource (configuration)\n");
return -ENODEV;
}
pmic_arb->cnfg = devm_ioremap(&pdev->dev,
mem_res->start, resource_size(mem_res));
if (!pmic_arb->cnfg) {
dev_err(&pdev->dev, "ioremap of 'cnfg' failed\n");
return -ENOMEM;
}
for (i = 0; i < ARRAY_SIZE(pmic_arb->mapping_table); ++i)
pmic_arb->mapping_table[i] = readl_relaxed(
pmic_arb->cnfg + SPMI_MAPPING_TABLE_REG(i));
pmic_arb->pic_irq = platform_get_irq(pdev, 0);
if (!pmic_arb->pic_irq) {
dev_err(&pdev->dev, "missing IRQ resource\n");
return -ENODEV;
}
ret = devm_request_irq(&pdev->dev, pmic_arb->pic_irq,
pmic_arb_periph_irq, IRQF_TRIGGER_HIGH, pdev->name, pmic_arb);
if (ret) {
dev_err(&pdev->dev, "request IRQ failed\n");
return ret;
}
/* Get properties from the device tree */
ret = spmi_pmic_arb_get_property(pdev, "cell-index", &cell_index);
if (ret)
return -ENODEV;
ret = spmi_pmic_arb_get_property(pdev, "qcom,pmic-arb-ee", &prop);
if (ret)
return -ENODEV;
pmic_arb->owner = (u8)prop;
ret = spmi_pmic_arb_get_property(pdev, "qcom,pmic-arb-channel", &prop);
if (ret)
return -ENODEV;
pmic_arb->channel = (u8)prop;
pmic_arb->allow_wakeup = !of_property_read_bool(pdev->dev.of_node,
"qcom,not-wakeup");
if (pmic_arb->allow_wakeup) {
ret = irq_set_irq_wake(pmic_arb->pic_irq, 1);
if (unlikely(ret)) {
pr_err("Unable to set wakeup irq, err=%d\n", ret);
return -ENODEV;
}
}
pmic_arb->max_apid = 0;
pmic_arb->min_apid = PMIC_ARB_MAX_PERIPHS - 1;
pmic_arb->dev = &pdev->dev;
platform_set_drvdata(pdev, pmic_arb);
spmi_set_ctrldata(&pmic_arb->controller, pmic_arb);
spin_lock_init(&pmic_arb->lock);
pmic_arb->controller.nr = cell_index;
pmic_arb->controller.dev.parent = pdev->dev.parent;
pmic_arb->controller.dev.of_node = of_node_get(pdev->dev.of_node);
/* Callbacks */
pmic_arb->controller.cmd = pmic_arb_cmd;
pmic_arb->controller.read_cmd = pmic_arb_read_cmd;
pmic_arb->controller.write_cmd = pmic_arb_write_cmd;
ret = spmi_add_controller(&pmic_arb->controller);
if (ret)
goto err_add_controller;
/* Register the interrupt enable/disable functions */
ret = qpnpint_register_controller(pmic_arb->controller.dev.of_node,
&pmic_arb->controller,
&spmi_pmic_arb_intr_cb);
if (ret) {
dev_err(&pdev->dev, "Unable to register controller %d\n",
cell_index);
goto err_reg_controller;
}
/* Register device(s) from the device tree */
of_spmi_register_devices(&pmic_arb->controller);
/* Add debugfs file for mapping data */
if (spmi_dfs_create_file(&pmic_arb->controller, "mapping",
pmic_arb, &pmic_arb_dfs_fops) == NULL)
dev_err(&pdev->dev, "error creating 'mapping' debugfs file\n");
pr_debug("PMIC Arb Version 0x%x\n",
pmic_arb_read(pmic_arb, PMIC_ARB_VERSION));
return 0;
err_reg_controller:
spmi_del_controller(&pmic_arb->controller);
err_add_controller:
platform_set_drvdata(pdev, NULL);
if (pmic_arb->allow_wakeup)
irq_set_irq_wake(pmic_arb->pic_irq, 0);
return ret;
}
static int __devexit spmi_pmic_arb_remove(struct platform_device *pdev)
{
struct spmi_pmic_arb_dev *pmic_arb = platform_get_drvdata(pdev);
int ret;
ret = qpnpint_unregister_controller(pmic_arb->controller.dev.of_node);
if (ret)
dev_err(&pdev->dev, "Unable to unregister controller %d\n",
pmic_arb->controller.nr);
if (pmic_arb->allow_wakeup)
irq_set_irq_wake(pmic_arb->pic_irq, 0);
platform_set_drvdata(pdev, NULL);
spmi_del_controller(&pmic_arb->controller);
return ret;
}
static struct of_device_id spmi_pmic_arb_match_table[] = {
{ .compatible = "qcom,spmi-pmic-arb",
},
{}
};
static struct platform_driver spmi_pmic_arb_driver = {
.probe = spmi_pmic_arb_probe,
.remove = __exit_p(spmi_pmic_arb_remove),
.driver = {
.name = SPMI_PMIC_ARB_NAME,
.owner = THIS_MODULE,
.of_match_table = spmi_pmic_arb_match_table,
},
};
static int __init spmi_pmic_arb_init(void)
{
return platform_driver_register(&spmi_pmic_arb_driver);
}
postcore_initcall(spmi_pmic_arb_init);
static void __exit spmi_pmic_arb_exit(void)
{
platform_driver_unregister(&spmi_pmic_arb_driver);
}
module_exit(spmi_pmic_arb_exit);
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");
MODULE_ALIAS("platform:spmi_pmic_arb");

View File

@@ -0,0 +1,151 @@
/* Copyright (c) 2002-3 Patrick Mochel
* Copyright (c) 2002-3 Open Source Development Labs
* Copyright (c) 2012, 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.
*
* Resource handling based on platform.c.
*/
#include <linux/export.h>
#include <linux/spmi.h>
#include <linux/string.h>
/**
* spmi_get_resource - get a resource for a device
* @dev: spmi device
* @node: device node resource
* @type: resource type
* @res_num: resource index
*
* If 'node' is specified as NULL, then the API treats this as a special
* case to assume the first devnode. For configurations that do not use
* spmi-dev-container, there is only one node to begin with, so NULL should
* be passed in this case.
*
* Returns
* NULL on failure.
*/
struct resource *spmi_get_resource(struct spmi_device *dev,
struct spmi_resource *node,
unsigned int type, unsigned int res_num)
{
int i;
/* if a node is not specified, default to the first node */
if (!node)
node = &dev->res;
for (i = 0; i < node->num_resources; i++) {
struct resource *r = &node->resource[i];
if (type == resource_type(r) && res_num-- == 0)
return r;
}
return NULL;
}
EXPORT_SYMBOL_GPL(spmi_get_resource);
#define SPMI_MAX_RES_NAME 256
/**
* spmi_get_resource_byname - get a resource for a device given a name
* @dev: spmi device handle
* @node: device node resource
* @type: resource type
* @name: resource name to lookup
*/
struct resource *spmi_get_resource_byname(struct spmi_device *dev,
struct spmi_resource *node,
unsigned int type,
const char *name)
{
int i;
/* if a node is not specified, default to the first node */
if (!node)
node = &dev->res;
for (i = 0; i < node->num_resources; i++) {
struct resource *r = &node->resource[i];
if (type == resource_type(r) && r->name &&
!strncmp(r->name, name, SPMI_MAX_RES_NAME))
return r;
}
return NULL;
}
EXPORT_SYMBOL_GPL(spmi_get_resource_byname);
/**
* spmi_get_irq - get an IRQ for a device
* @dev: spmi device
* @node: device node resource
* @res_num: IRQ number index
*
* Returns
* -ENXIO on failure.
*/
int spmi_get_irq(struct spmi_device *dev, struct spmi_resource *node,
unsigned int res_num)
{
struct resource *r = spmi_get_resource(dev, node,
IORESOURCE_IRQ, res_num);
return r ? r->start : -ENXIO;
}
EXPORT_SYMBOL_GPL(spmi_get_irq);
/**
* spmi_get_irq_byname - get an IRQ for a device given a name
* @dev: spmi device handle
* @node: device node resource
* @name: resource name to lookup
*
* Returns -ENXIO on failure
*/
int spmi_get_irq_byname(struct spmi_device *dev,
struct spmi_resource *node, const char *name)
{
struct resource *r = spmi_get_resource_byname(dev, node,
IORESOURCE_IRQ, name);
return r ? r->start : -ENXIO;
}
EXPORT_SYMBOL_GPL(spmi_get_irq_byname);
/*
* spmi_get_container_dev_byname - get a device node resource
* @dev: spmi device handle
* @label: device name to lookup
*
* Only useable in spmi-dev-container configurations. Given a name,
* find the associated spmi_resource that matches the name.
*
* Return NULL if the spmi_device is not a dev-container,
* or if the lookup fails.
*/
struct spmi_resource *spmi_get_dev_container_byname(struct spmi_device *dev,
const char *label)
{
int i;
if (!label)
return NULL;
for (i = 0; i < dev->num_dev_node; i++) {
struct spmi_resource *r = &dev->dev_node[i];
if (r && r->label && !strncmp(r->label,
label, SPMI_MAX_RES_NAME))
return r;
}
return NULL;
}
EXPORT_SYMBOL(spmi_get_dev_container_byname);

844
kernel/drivers/spmi/spmi.c Normal file
View File

@@ -0,0 +1,844 @@
/* Copyright (c) 2012-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/kernel.h>
#include <linux/errno.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/spmi.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include "spmi-dbgfs.h"
struct spmii_boardinfo {
struct list_head list;
struct spmi_boardinfo board_info;
};
static DEFINE_MUTEX(board_lock);
static LIST_HEAD(board_list);
static DEFINE_IDR(ctrl_idr);
static struct device_type spmi_dev_type;
static struct device_type spmi_ctrl_type;
/* Forward declarations */
struct bus_type spmi_bus_type;
static int spmi_register_controller(struct spmi_controller *ctrl);
/**
* spmi_busnum_to_ctrl: Map bus number to controller
* @busnum: bus number
* Returns controller representing this bus number
*/
struct spmi_controller *spmi_busnum_to_ctrl(u32 bus_num)
{
struct spmi_controller *ctrl;
mutex_lock(&board_lock);
ctrl = idr_find(&ctrl_idr, bus_num);
mutex_unlock(&board_lock);
return ctrl;
}
EXPORT_SYMBOL_GPL(spmi_busnum_to_ctrl);
/**
* spmi_add_controller: Controller bring-up.
* @ctrl: controller to be registered.
* A controller is registered with the framework using this API. ctrl->nr is the
* desired number with which SPMI framework registers the controller.
* Function will return -EBUSY if the number is in use.
*/
int spmi_add_controller(struct spmi_controller *ctrl)
{
int id;
int status;
if (!ctrl)
return -EINVAL;
pr_debug("adding controller for bus %d (0x%p)\n", ctrl->nr, ctrl);
if (ctrl->nr & ~MAX_ID_MASK) {
pr_err("invalid bus identifier %d\n", ctrl->nr);
return -EINVAL;
}
retry:
if (idr_pre_get(&ctrl_idr, GFP_KERNEL) == 0) {
pr_err("no free memory for idr\n");
return -ENOMEM;
}
mutex_lock(&board_lock);
status = idr_get_new_above(&ctrl_idr, ctrl, ctrl->nr, &id);
if (status == 0 && id != ctrl->nr) {
status = -EBUSY;
idr_remove(&ctrl_idr, id);
}
mutex_unlock(&board_lock);
if (status == -EAGAIN)
goto retry;
if (status == 0)
status = spmi_register_controller(ctrl);
return status;
}
EXPORT_SYMBOL_GPL(spmi_add_controller);
/* Remove a device associated with a controller */
static int spmi_ctrl_remove_device(struct device *dev, void *data)
{
struct spmi_device *spmidev = to_spmi_device(dev);
struct spmi_controller *ctrl = data;
if (dev->type == &spmi_dev_type && spmidev->ctrl == ctrl)
spmi_remove_device(spmidev);
return 0;
}
/**
* spmi_del_controller: Controller tear-down.
* @ctrl: controller to be removed.
*
* Controller added with the above API is torn down using this API.
*/
int spmi_del_controller(struct spmi_controller *ctrl)
{
struct spmi_controller *found;
if (!ctrl)
return -EINVAL;
/* Check that the ctrl has been added */
mutex_lock(&board_lock);
found = idr_find(&ctrl_idr, ctrl->nr);
mutex_unlock(&board_lock);
if (found != ctrl)
return -EINVAL;
/* Remove all the clients associated with this controller */
mutex_lock(&board_lock);
bus_for_each_dev(&spmi_bus_type, NULL, ctrl, spmi_ctrl_remove_device);
mutex_unlock(&board_lock);
spmi_dfs_del_controller(ctrl);
mutex_lock(&board_lock);
idr_remove(&ctrl_idr, ctrl->nr);
mutex_unlock(&board_lock);
init_completion(&ctrl->dev_released);
device_unregister(&ctrl->dev);
wait_for_completion(&ctrl->dev_released);
return 0;
}
EXPORT_SYMBOL_GPL(spmi_del_controller);
#define spmi_ctrl_attr_gr NULL
static void spmi_ctrl_release(struct device *dev)
{
struct spmi_controller *ctrl = to_spmi_controller(dev);
complete(&ctrl->dev_released);
}
static struct device_type spmi_ctrl_type = {
.groups = spmi_ctrl_attr_gr,
.release = spmi_ctrl_release,
};
#define spmi_device_attr_gr NULL
#define spmi_device_uevent NULL
static void spmi_dev_release(struct device *dev)
{
struct spmi_device *spmidev = to_spmi_device(dev);
kfree(spmidev);
}
static struct device_type spmi_dev_type = {
.groups = spmi_device_attr_gr,
.uevent = spmi_device_uevent,
.release = spmi_dev_release,
};
/**
* spmi_alloc_device: Allocate a new SPMI devices.
* @ctrl: controller to which this device is to be added to.
* Context: can sleep
*
* Allows a driver to allocate and initialize a SPMI device without
* registering it immediately. This allows a driver to directly fill
* the spmi_device structure before calling spmi_add_device().
*
* Caller is responsible to call spmi_add_device() on the returned
* spmi_device. If the caller needs to discard the spmi_device without
* adding it, then spmi_dev_put() should be called.
*/
struct spmi_device *spmi_alloc_device(struct spmi_controller *ctrl)
{
struct spmi_device *spmidev;
if (!ctrl || !spmi_busnum_to_ctrl(ctrl->nr)) {
pr_err("Missing SPMI controller\n");
return NULL;
}
spmidev = kzalloc(sizeof(*spmidev), GFP_KERNEL);
if (!spmidev) {
dev_err(&ctrl->dev, "unable to allocate spmi_device\n");
return NULL;
}
spmidev->ctrl = ctrl;
spmidev->dev.parent = ctrl->dev.parent;
spmidev->dev.bus = &spmi_bus_type;
spmidev->dev.type = &spmi_dev_type;
device_initialize(&spmidev->dev);
return spmidev;
}
EXPORT_SYMBOL_GPL(spmi_alloc_device);
/* Validate the SPMI device structure */
static struct device *get_valid_device(struct spmi_device *spmidev)
{
struct device *dev;
if (!spmidev)
return NULL;
dev = &spmidev->dev;
if (dev->bus != &spmi_bus_type || dev->type != &spmi_dev_type)
return NULL;
if (!spmidev->ctrl || !spmi_busnum_to_ctrl(spmidev->ctrl->nr))
return NULL;
return dev;
}
/**
* spmi_add_device: Add a new device without register board info.
* @spmi_dev: spmi_device to be added (registered).
*
* Called when device doesn't have an explicit client-driver to be probed, or
* the client-driver is a module installed dynamically.
*/
int spmi_add_device(struct spmi_device *spmidev)
{
int rc;
struct device *dev = get_valid_device(spmidev);
if (!dev) {
pr_err("invalid SPMI device\n");
return -EINVAL;
}
/* Set the device name */
dev_set_name(dev, "%s-%p", spmidev->name, spmidev);
/* Device may be bound to an active driver when this returns */
rc = device_add(dev);
if (rc < 0)
dev_err(dev, "Can't add %s, status %d\n", dev_name(dev), rc);
else
dev_dbg(dev, "device %s registered\n", dev_name(dev));
return rc;
}
EXPORT_SYMBOL_GPL(spmi_add_device);
/**
* spmi_new_device: Instantiates a new SPMI device
* @ctrl: controller to which this device is to be added to.
* @info: board information for this device.
*
* Returns the new device or NULL.
*/
struct spmi_device *spmi_new_device(struct spmi_controller *ctrl,
struct spmi_boardinfo const *info)
{
struct spmi_device *spmidev;
int rc;
if (!ctrl || !info)
return NULL;
spmidev = spmi_alloc_device(ctrl);
if (!spmidev)
return NULL;
spmidev->name = info->name;
spmidev->sid = info->slave_id;
spmidev->dev.of_node = info->of_node;
spmidev->dev.platform_data = (void *)info->platform_data;
spmidev->num_dev_node = info->num_dev_node;
spmidev->dev_node = info->dev_node;
spmidev->res = info->res;
rc = spmi_add_device(spmidev);
if (rc < 0) {
spmi_dev_put(spmidev);
return NULL;
}
return spmidev;
}
EXPORT_SYMBOL_GPL(spmi_new_device);
/* spmi_remove_device: Remove the effect of spmi_add_device() */
void spmi_remove_device(struct spmi_device *spmi_dev)
{
device_unregister(&spmi_dev->dev);
}
EXPORT_SYMBOL_GPL(spmi_remove_device);
static void spmi_match_ctrl_to_boardinfo(struct spmi_controller *ctrl,
struct spmi_boardinfo *bi)
{
struct spmi_device *spmidev;
spmidev = spmi_new_device(ctrl, bi);
if (!spmidev)
dev_err(ctrl->dev.parent, "can't create new device for %s\n",
bi->name);
}
/**
* spmi_register_board_info: Board-initialization routine.
* @bus_num: controller number (bus) on which this device will sit.
* @info: list of all devices on all controllers present on the board.
* @n: number of entries.
* API enumerates respective devices on corresponding controller.
* Called from board-init function.
* If controller is not present, only add to boards list
*/
int spmi_register_board_info(int busnum,
struct spmi_boardinfo const *info, unsigned n)
{
int i;
struct spmii_boardinfo *bi;
struct spmi_controller *ctrl;
bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
if (!bi)
return -ENOMEM;
ctrl = spmi_busnum_to_ctrl(busnum);
for (i = 0; i < n; i++, bi++, info++) {
memcpy(&bi->board_info, info, sizeof(*info));
mutex_lock(&board_lock);
list_add_tail(&bi->list, &board_list);
if (ctrl)
spmi_match_ctrl_to_boardinfo(ctrl, &bi->board_info);
mutex_unlock(&board_lock);
}
return 0;
}
EXPORT_SYMBOL_GPL(spmi_register_board_info);
/* ------------------------------------------------------------------------- */
static inline int
spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid)
{
BUG_ON(!ctrl || !ctrl->cmd);
return ctrl->cmd(ctrl, opcode, sid);
}
static inline int spmi_read_cmd(struct spmi_controller *ctrl,
u8 opcode, u8 sid, u16 addr, u8 bc, u8 *buf)
{
BUG_ON(!ctrl || !ctrl->read_cmd);
return ctrl->read_cmd(ctrl, opcode, sid, addr, bc, buf);
}
static inline int spmi_write_cmd(struct spmi_controller *ctrl,
u8 opcode, u8 sid, u16 addr, u8 bc, u8 *buf)
{
BUG_ON(!ctrl || !ctrl->write_cmd);
return ctrl->write_cmd(ctrl, opcode, sid, addr, bc, buf);
}
/*
* register read/write: 5-bit address, 1 byte of data
* extended register read/write: 8-bit address, up to 16 bytes of data
* extended register read/write long: 16-bit address, up to 8 bytes of data
*/
/**
* spmi_register_read() - register read
* @dev: SPMI device.
* @sid: slave identifier.
* @ad: slave register address (5-bit address).
* @buf: buffer to be populated with data from the Slave.
*
* Reads 1 byte of data from a Slave device register.
*/
int spmi_register_read(struct spmi_controller *ctrl, u8 sid, u8 addr, u8 *buf)
{
/* 4-bit Slave Identifier, 5-bit register address */
if (sid > SPMI_MAX_SLAVE_ID || addr > 0x1F)
return -EINVAL;
return spmi_read_cmd(ctrl, SPMI_CMD_READ, sid, addr, 0, buf);
}
EXPORT_SYMBOL_GPL(spmi_register_read);
/**
* spmi_ext_register_read() - extended register read
* @dev: SPMI device.
* @sid: slave identifier.
* @ad: slave register address (8-bit address).
* @len: the request number of bytes to read (up to 16 bytes).
* @buf: buffer to be populated with data from the Slave.
*
* Reads up to 16 bytes of data from the extended register space on a
* Slave device.
*/
int spmi_ext_register_read(struct spmi_controller *ctrl,
u8 sid, u8 addr, u8 *buf, int len)
{
/* 4-bit Slave Identifier, 8-bit register address, up to 16 bytes */
if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 16)
return -EINVAL;
return spmi_read_cmd(ctrl, SPMI_CMD_EXT_READ, sid, addr, len - 1, buf);
}
EXPORT_SYMBOL_GPL(spmi_ext_register_read);
/**
* spmi_ext_register_readl() - extended register read long
* @dev: SPMI device.
* @sid: slave identifier.
* @ad: slave register address (16-bit address).
* @len: the request number of bytes to read (up to 8 bytes).
* @buf: buffer to be populated with data from the Slave.
*
* Reads up to 8 bytes of data from the extended register space on a
* Slave device using 16-bit address.
*/
int spmi_ext_register_readl(struct spmi_controller *ctrl,
u8 sid, u16 addr, u8 *buf, int len)
{
/* 4-bit Slave Identifier, 16-bit register address, up to 8 bytes */
if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 8)
return -EINVAL;
return spmi_read_cmd(ctrl, SPMI_CMD_EXT_READL, sid, addr, len - 1, buf);
}
EXPORT_SYMBOL_GPL(spmi_ext_register_readl);
/**
* spmi_register_write() - register write
* @dev: SPMI device.
* @sid: slave identifier.
* @ad: slave register address (5-bit address).
* @buf: buffer containing the data to be transferred to the Slave.
*
* Writes 1 byte of data to a Slave device register.
*/
int spmi_register_write(struct spmi_controller *ctrl, u8 sid, u8 addr, u8 *buf)
{
u8 op = SPMI_CMD_WRITE;
/* 4-bit Slave Identifier, 5-bit register address */
if (sid > SPMI_MAX_SLAVE_ID || addr > 0x1F)
return -EINVAL;
return spmi_write_cmd(ctrl, op, sid, addr, 0, buf);
}
EXPORT_SYMBOL_GPL(spmi_register_write);
/**
* spmi_register_zero_write() - register zero write
* @dev: SPMI device.
* @sid: slave identifier.
* @data: the data to be written to register 0 (7-bits).
*
* Writes data to register 0 of the Slave device.
*/
int spmi_register_zero_write(struct spmi_controller *ctrl, u8 sid, u8 data)
{
u8 op = SPMI_CMD_ZERO_WRITE;
/* 4-bit Slave Identifier, 5-bit register address */
if (sid > SPMI_MAX_SLAVE_ID)
return -EINVAL;
return spmi_write_cmd(ctrl, op, sid, 0, 0, &data);
}
EXPORT_SYMBOL_GPL(spmi_register_zero_write);
/**
* spmi_ext_register_write() - extended register write
* @dev: SPMI device.
* @sid: slave identifier.
* @ad: slave register address (8-bit address).
* @buf: buffer containing the data to be transferred to the Slave.
* @len: the request number of bytes to read (up to 16 bytes).
*
* Writes up to 16 bytes of data to the extended register space of a
* Slave device.
*/
int spmi_ext_register_write(struct spmi_controller *ctrl,
u8 sid, u8 addr, u8 *buf, int len)
{
u8 op = SPMI_CMD_EXT_WRITE;
/* 4-bit Slave Identifier, 8-bit register address, up to 16 bytes */
if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 16)
return -EINVAL;
return spmi_write_cmd(ctrl, op, sid, addr, len - 1, buf);
}
EXPORT_SYMBOL_GPL(spmi_ext_register_write);
/**
* spmi_ext_register_writel() - extended register write long
* @dev: SPMI device.
* @sid: slave identifier.
* @ad: slave register address (16-bit address).
* @buf: buffer containing the data to be transferred to the Slave.
* @len: the request number of bytes to read (up to 8 bytes).
*
* Writes up to 8 bytes of data to the extended register space of a
* Slave device using 16-bit address.
*/
int spmi_ext_register_writel(struct spmi_controller *ctrl,
u8 sid, u16 addr, u8 *buf, int len)
{
u8 op = SPMI_CMD_EXT_WRITEL;
/* 4-bit Slave Identifier, 16-bit register address, up to 8 bytes */
if (sid > SPMI_MAX_SLAVE_ID || len <= 0 || len > 8)
return -EINVAL;
return spmi_write_cmd(ctrl, op, sid, addr, len - 1, buf);
}
EXPORT_SYMBOL_GPL(spmi_ext_register_writel);
/**
* spmi_command_reset() - sends RESET command to the specified slave
* @dev: SPMI device.
* @sid: slave identifier.
*
* The Reset command initializes the Slave and forces all registers to
* their reset values. The Slave shall enter the STARTUP state after
* receiving a Reset command.
*
* Returns
* -EINVAL for invalid Slave Identifier.
* -EPERM if the SPMI transaction is denied due to permission issues.
* -EIO if the SPMI transaction fails (parity errors, etc).
* -ETIMEDOUT if the SPMI transaction times out.
*/
int spmi_command_reset(struct spmi_controller *ctrl, u8 sid)
{
if (sid > SPMI_MAX_SLAVE_ID)
return -EINVAL;
return spmi_cmd(ctrl, SPMI_CMD_RESET, sid);
}
EXPORT_SYMBOL_GPL(spmi_command_reset);
/**
* spmi_command_sleep() - sends SLEEP command to the specified slave
* @dev: SPMI device.
* @sid: slave identifier.
*
* The Sleep command causes the Slave to enter the user defined SLEEP state.
*
* Returns
* -EINVAL for invalid Slave Identifier.
* -EPERM if the SPMI transaction is denied due to permission issues.
* -EIO if the SPMI transaction fails (parity errors, etc).
* -ETIMEDOUT if the SPMI transaction times out.
*/
int spmi_command_sleep(struct spmi_controller *ctrl, u8 sid)
{
if (sid > SPMI_MAX_SLAVE_ID)
return -EINVAL;
return spmi_cmd(ctrl, SPMI_CMD_SLEEP, sid);
}
EXPORT_SYMBOL_GPL(spmi_command_sleep);
/**
* spmi_command_wakeup() - sends WAKEUP command to the specified slave
* @dev: SPMI device.
* @sid: slave identifier.
*
* The Wakeup command causes the Slave to move from the SLEEP state to
* the ACTIVE state.
*
* Returns
* -EINVAL for invalid Slave Identifier.
* -EPERM if the SPMI transaction is denied due to permission issues.
* -EIO if the SPMI transaction fails (parity errors, etc).
* -ETIMEDOUT if the SPMI transaction times out.
*/
int spmi_command_wakeup(struct spmi_controller *ctrl, u8 sid)
{
if (sid > SPMI_MAX_SLAVE_ID)
return -EINVAL;
return spmi_cmd(ctrl, SPMI_CMD_WAKEUP, sid);
}
EXPORT_SYMBOL_GPL(spmi_command_wakeup);
/**
* spmi_command_shutdown() - sends SHUTDOWN command to the specified slave
* @dev: SPMI device.
* @sid: slave identifier.
*
* The Shutdown command causes the Slave to enter the SHUTDOWN state.
*
* Returns
* -EINVAL for invalid Slave Identifier.
* -EPERM if the SPMI transaction is denied due to permission issues.
* -EIO if the SPMI transaction fails (parity errors, etc).
* -ETIMEDOUT if the SPMI transaction times out.
*/
int spmi_command_shutdown(struct spmi_controller *ctrl, u8 sid)
{
if (sid > SPMI_MAX_SLAVE_ID)
return -EINVAL;
return spmi_cmd(ctrl, SPMI_CMD_SHUTDOWN, sid);
}
EXPORT_SYMBOL_GPL(spmi_command_shutdown);
/* ------------------------------------------------------------------------- */
static const struct spmi_device_id *spmi_match(const struct spmi_device_id *id,
const struct spmi_device *spmi_dev)
{
while (id->name[0]) {
if (strncmp(spmi_dev->name, id->name, SPMI_NAME_SIZE) == 0)
return id;
id++;
}
return NULL;
}
static int spmi_device_match(struct device *dev, struct device_driver *drv)
{
struct spmi_device *spmi_dev;
struct spmi_driver *sdrv = to_spmi_driver(drv);
if (dev->type == &spmi_dev_type)
spmi_dev = to_spmi_device(dev);
else
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return spmi_match(sdrv->id_table, spmi_dev) != NULL;
if (drv->name)
return strncmp(spmi_dev->name, drv->name, SPMI_NAME_SIZE) == 0;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int spmi_legacy_suspend(struct device *dev, pm_message_t mesg)
{
struct spmi_device *spmi_dev = NULL;
struct spmi_driver *driver;
if (dev->type == &spmi_dev_type)
spmi_dev = to_spmi_device(dev);
if (!spmi_dev || !dev->driver)
return 0;
driver = to_spmi_driver(dev->driver);
if (!driver->suspend)
return 0;
return driver->suspend(spmi_dev, mesg);
}
static int spmi_legacy_resume(struct device *dev)
{
struct spmi_device *spmi_dev = NULL;
struct spmi_driver *driver;
if (dev->type == &spmi_dev_type)
spmi_dev = to_spmi_device(dev);
if (!spmi_dev || !dev->driver)
return 0;
driver = to_spmi_driver(dev->driver);
if (!driver->resume)
return 0;
return driver->resume(spmi_dev);
}
static int spmi_pm_suspend(struct device *dev)
{
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
if (pm)
return pm_generic_suspend(dev);
else
return spmi_legacy_suspend(dev, PMSG_SUSPEND);
}
static int spmi_pm_resume(struct device *dev)
{
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
if (pm)
return pm_generic_resume(dev);
else
return spmi_legacy_resume(dev);
}
#else
#define spmi_pm_suspend NULL
#define spmi_pm_resume NULL
#endif
static const struct dev_pm_ops spmi_pm_ops = {
.suspend = spmi_pm_suspend,
.resume = spmi_pm_resume,
SET_RUNTIME_PM_OPS(
pm_generic_suspend,
pm_generic_resume,
pm_generic_runtime_idle
)
};
struct bus_type spmi_bus_type = {
.name = "spmi",
.match = spmi_device_match,
.pm = &spmi_pm_ops,
};
EXPORT_SYMBOL_GPL(spmi_bus_type);
struct device spmi_dev = {
.init_name = "spmi",
};
static int spmi_drv_probe(struct device *dev)
{
const struct spmi_driver *sdrv = to_spmi_driver(dev->driver);
return sdrv->probe(to_spmi_device(dev));
}
static int spmi_drv_remove(struct device *dev)
{
const struct spmi_driver *sdrv = to_spmi_driver(dev->driver);
return sdrv->remove(to_spmi_device(dev));
}
static void spmi_drv_shutdown(struct device *dev)
{
const struct spmi_driver *sdrv = to_spmi_driver(dev->driver);
sdrv->shutdown(to_spmi_device(dev));
}
/**
* spmi_driver_register: Client driver registration with SPMI framework.
* @drv: client driver to be associated with client-device.
*
* This API will register the client driver with the SPMI framework.
* It is called from the driver's module-init function.
*/
int spmi_driver_register(struct spmi_driver *drv)
{
drv->driver.bus = &spmi_bus_type;
if (drv->probe)
drv->driver.probe = spmi_drv_probe;
if (drv->remove)
drv->driver.remove = spmi_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = spmi_drv_shutdown;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(spmi_driver_register);
static int spmi_register_controller(struct spmi_controller *ctrl)
{
int ret = 0;
/* Can't register until after driver model init */
if (WARN_ON(!spmi_bus_type.p)) {
ret = -EAGAIN;
goto exit;
}
dev_set_name(&ctrl->dev, "spmi-%d", ctrl->nr);
ctrl->dev.bus = &spmi_bus_type;
ctrl->dev.type = &spmi_ctrl_type;
ret = device_register(&ctrl->dev);
if (ret)
goto exit;
dev_dbg(&ctrl->dev, "Bus spmi-%d registered: dev:%x\n",
ctrl->nr, (u32)&ctrl->dev);
spmi_dfs_add_controller(ctrl);
return 0;
exit:
mutex_lock(&board_lock);
idr_remove(&ctrl_idr, ctrl->nr);
mutex_unlock(&board_lock);
return ret;
}
static void __exit spmi_exit(void)
{
device_unregister(&spmi_dev);
bus_unregister(&spmi_bus_type);
}
static int __init spmi_init(void)
{
int retval;
retval = bus_register(&spmi_bus_type);
if (!retval)
retval = device_register(&spmi_dev);
if (retval)
bus_unregister(&spmi_bus_type);
return retval;
}
postcore_initcall(spmi_init);
module_exit(spmi_exit);
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("SPMI module");
MODULE_ALIAS("platform:spmi");