845 lines
22 KiB
C
845 lines
22 KiB
C
|
/* 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");
|