mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-28 07:04:00 +08:00
3401b2fff3
Let general purpose I2C/SMBus bus drivers add SPD to their class. Once this is done, we will be able to tell the eeprom driver to only probe for SPD EEPROMs and similar on these buses. Note that I took a conservative approach here, adding I2C_CLASS_SPD to many drivers that have no idea whether they can host SPD EEPROMs or not. This is to make sure that the eeprom driver doesn't stop probing buses where SPD EEPROMs or equivalent live. So, bus driver maintainers and users should feel free to remove the SPD class from drivers those buses never have SPD EEPROMs or they don't want the eeprom driver to bind to them. Likewise, feel free to add the SPD class to any bus driver I might have missed. Signed-off-by: Jean Delvare <khali@linux-fr.org>
657 lines
18 KiB
C
657 lines
18 KiB
C
/*
|
|
* Specific bus support for PMC-TWI compliant implementation on MSP71xx.
|
|
*
|
|
* Copyright 2005-2007 PMC-Sierra, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
|
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/delay.h>
|
|
#include <asm/io.h>
|
|
|
|
#define DRV_NAME "pmcmsptwi"
|
|
|
|
#define MSP_TWI_SF_CLK_REG_OFFSET 0x00
|
|
#define MSP_TWI_HS_CLK_REG_OFFSET 0x04
|
|
#define MSP_TWI_CFG_REG_OFFSET 0x08
|
|
#define MSP_TWI_CMD_REG_OFFSET 0x0c
|
|
#define MSP_TWI_ADD_REG_OFFSET 0x10
|
|
#define MSP_TWI_DAT_0_REG_OFFSET 0x14
|
|
#define MSP_TWI_DAT_1_REG_OFFSET 0x18
|
|
#define MSP_TWI_INT_STS_REG_OFFSET 0x1c
|
|
#define MSP_TWI_INT_MSK_REG_OFFSET 0x20
|
|
#define MSP_TWI_BUSY_REG_OFFSET 0x24
|
|
|
|
#define MSP_TWI_INT_STS_DONE (1 << 0)
|
|
#define MSP_TWI_INT_STS_LOST_ARBITRATION (1 << 1)
|
|
#define MSP_TWI_INT_STS_NO_RESPONSE (1 << 2)
|
|
#define MSP_TWI_INT_STS_DATA_COLLISION (1 << 3)
|
|
#define MSP_TWI_INT_STS_BUSY (1 << 4)
|
|
#define MSP_TWI_INT_STS_ALL 0x1f
|
|
|
|
#define MSP_MAX_BYTES_PER_RW 8
|
|
#define MSP_MAX_POLL 5
|
|
#define MSP_POLL_DELAY 10
|
|
#define MSP_IRQ_TIMEOUT (MSP_MAX_POLL * MSP_POLL_DELAY)
|
|
|
|
/* IO Operation macros */
|
|
#define pmcmsptwi_readl __raw_readl
|
|
#define pmcmsptwi_writel __raw_writel
|
|
|
|
/* TWI command type */
|
|
enum pmcmsptwi_cmd_type {
|
|
MSP_TWI_CMD_WRITE = 0, /* Write only */
|
|
MSP_TWI_CMD_READ = 1, /* Read only */
|
|
MSP_TWI_CMD_WRITE_READ = 2, /* Write then Read */
|
|
};
|
|
|
|
/* The possible results of the xferCmd */
|
|
enum pmcmsptwi_xfer_result {
|
|
MSP_TWI_XFER_OK = 0,
|
|
MSP_TWI_XFER_TIMEOUT,
|
|
MSP_TWI_XFER_BUSY,
|
|
MSP_TWI_XFER_DATA_COLLISION,
|
|
MSP_TWI_XFER_NO_RESPONSE,
|
|
MSP_TWI_XFER_LOST_ARBITRATION,
|
|
};
|
|
|
|
/* Corresponds to a PMCTWI clock configuration register */
|
|
struct pmcmsptwi_clock {
|
|
u8 filter; /* Bits 15:12, default = 0x03 */
|
|
u16 clock; /* Bits 9:0, default = 0x001f */
|
|
};
|
|
|
|
struct pmcmsptwi_clockcfg {
|
|
struct pmcmsptwi_clock standard; /* The standard/fast clock config */
|
|
struct pmcmsptwi_clock highspeed; /* The highspeed clock config */
|
|
};
|
|
|
|
/* Corresponds to the main TWI configuration register */
|
|
struct pmcmsptwi_cfg {
|
|
u8 arbf; /* Bits 15:12, default=0x03 */
|
|
u8 nak; /* Bits 11:8, default=0x03 */
|
|
u8 add10; /* Bit 7, default=0x00 */
|
|
u8 mst_code; /* Bits 6:4, default=0x00 */
|
|
u8 arb; /* Bit 1, default=0x01 */
|
|
u8 highspeed; /* Bit 0, default=0x00 */
|
|
};
|
|
|
|
/* A single pmctwi command to issue */
|
|
struct pmcmsptwi_cmd {
|
|
u16 addr; /* The slave address (7 or 10 bits) */
|
|
enum pmcmsptwi_cmd_type type; /* The command type */
|
|
u8 write_len; /* Number of bytes in the write buffer */
|
|
u8 read_len; /* Number of bytes in the read buffer */
|
|
u8 *write_data; /* Buffer of characters to send */
|
|
u8 *read_data; /* Buffer to fill with incoming data */
|
|
};
|
|
|
|
/* The private data */
|
|
struct pmcmsptwi_data {
|
|
void __iomem *iobase; /* iomapped base for IO */
|
|
int irq; /* IRQ to use (0 disables) */
|
|
struct completion wait; /* Completion for xfer */
|
|
struct mutex lock; /* Used for threadsafeness */
|
|
enum pmcmsptwi_xfer_result last_result; /* result of last xfer */
|
|
};
|
|
|
|
/* The default settings */
|
|
static const struct pmcmsptwi_clockcfg pmcmsptwi_defclockcfg = {
|
|
.standard = {
|
|
.filter = 0x3,
|
|
.clock = 0x1f,
|
|
},
|
|
.highspeed = {
|
|
.filter = 0x3,
|
|
.clock = 0x1f,
|
|
},
|
|
};
|
|
|
|
static const struct pmcmsptwi_cfg pmcmsptwi_defcfg = {
|
|
.arbf = 0x03,
|
|
.nak = 0x03,
|
|
.add10 = 0x00,
|
|
.mst_code = 0x00,
|
|
.arb = 0x01,
|
|
.highspeed = 0x00,
|
|
};
|
|
|
|
static struct pmcmsptwi_data pmcmsptwi_data;
|
|
|
|
static struct i2c_adapter pmcmsptwi_adapter;
|
|
|
|
/* inline helper functions */
|
|
static inline u32 pmcmsptwi_clock_to_reg(
|
|
const struct pmcmsptwi_clock *clock)
|
|
{
|
|
return ((clock->filter & 0xf) << 12) | (clock->clock & 0x03ff);
|
|
}
|
|
|
|
static inline void pmcmsptwi_reg_to_clock(
|
|
u32 reg, struct pmcmsptwi_clock *clock)
|
|
{
|
|
clock->filter = (reg >> 12) & 0xf;
|
|
clock->clock = reg & 0x03ff;
|
|
}
|
|
|
|
static inline u32 pmcmsptwi_cfg_to_reg(const struct pmcmsptwi_cfg *cfg)
|
|
{
|
|
return ((cfg->arbf & 0xf) << 12) |
|
|
((cfg->nak & 0xf) << 8) |
|
|
((cfg->add10 & 0x1) << 7) |
|
|
((cfg->mst_code & 0x7) << 4) |
|
|
((cfg->arb & 0x1) << 1) |
|
|
(cfg->highspeed & 0x1);
|
|
}
|
|
|
|
static inline void pmcmsptwi_reg_to_cfg(u32 reg, struct pmcmsptwi_cfg *cfg)
|
|
{
|
|
cfg->arbf = (reg >> 12) & 0xf;
|
|
cfg->nak = (reg >> 8) & 0xf;
|
|
cfg->add10 = (reg >> 7) & 0x1;
|
|
cfg->mst_code = (reg >> 4) & 0x7;
|
|
cfg->arb = (reg >> 1) & 0x1;
|
|
cfg->highspeed = reg & 0x1;
|
|
}
|
|
|
|
/*
|
|
* Sets the current clock configuration
|
|
*/
|
|
static void pmcmsptwi_set_clock_config(const struct pmcmsptwi_clockcfg *cfg,
|
|
struct pmcmsptwi_data *data)
|
|
{
|
|
mutex_lock(&data->lock);
|
|
pmcmsptwi_writel(pmcmsptwi_clock_to_reg(&cfg->standard),
|
|
data->iobase + MSP_TWI_SF_CLK_REG_OFFSET);
|
|
pmcmsptwi_writel(pmcmsptwi_clock_to_reg(&cfg->highspeed),
|
|
data->iobase + MSP_TWI_HS_CLK_REG_OFFSET);
|
|
mutex_unlock(&data->lock);
|
|
}
|
|
|
|
/*
|
|
* Gets the current TWI bus configuration
|
|
*/
|
|
static void pmcmsptwi_get_twi_config(struct pmcmsptwi_cfg *cfg,
|
|
struct pmcmsptwi_data *data)
|
|
{
|
|
mutex_lock(&data->lock);
|
|
pmcmsptwi_reg_to_cfg(pmcmsptwi_readl(
|
|
data->iobase + MSP_TWI_CFG_REG_OFFSET), cfg);
|
|
mutex_unlock(&data->lock);
|
|
}
|
|
|
|
/*
|
|
* Sets the current TWI bus configuration
|
|
*/
|
|
static void pmcmsptwi_set_twi_config(const struct pmcmsptwi_cfg *cfg,
|
|
struct pmcmsptwi_data *data)
|
|
{
|
|
mutex_lock(&data->lock);
|
|
pmcmsptwi_writel(pmcmsptwi_cfg_to_reg(cfg),
|
|
data->iobase + MSP_TWI_CFG_REG_OFFSET);
|
|
mutex_unlock(&data->lock);
|
|
}
|
|
|
|
/*
|
|
* Parses the 'int_sts' register and returns a well-defined error code
|
|
*/
|
|
static enum pmcmsptwi_xfer_result pmcmsptwi_get_result(u32 reg)
|
|
{
|
|
if (reg & MSP_TWI_INT_STS_LOST_ARBITRATION) {
|
|
dev_dbg(&pmcmsptwi_adapter.dev,
|
|
"Result: Lost arbitration\n");
|
|
return MSP_TWI_XFER_LOST_ARBITRATION;
|
|
} else if (reg & MSP_TWI_INT_STS_NO_RESPONSE) {
|
|
dev_dbg(&pmcmsptwi_adapter.dev,
|
|
"Result: No response\n");
|
|
return MSP_TWI_XFER_NO_RESPONSE;
|
|
} else if (reg & MSP_TWI_INT_STS_DATA_COLLISION) {
|
|
dev_dbg(&pmcmsptwi_adapter.dev,
|
|
"Result: Data collision\n");
|
|
return MSP_TWI_XFER_DATA_COLLISION;
|
|
} else if (reg & MSP_TWI_INT_STS_BUSY) {
|
|
dev_dbg(&pmcmsptwi_adapter.dev,
|
|
"Result: Bus busy\n");
|
|
return MSP_TWI_XFER_BUSY;
|
|
}
|
|
|
|
dev_dbg(&pmcmsptwi_adapter.dev, "Result: Operation succeeded\n");
|
|
return MSP_TWI_XFER_OK;
|
|
}
|
|
|
|
/*
|
|
* In interrupt mode, handle the interrupt.
|
|
* NOTE: Assumes data->lock is held.
|
|
*/
|
|
static irqreturn_t pmcmsptwi_interrupt(int irq, void *ptr)
|
|
{
|
|
struct pmcmsptwi_data *data = ptr;
|
|
|
|
u32 reason = pmcmsptwi_readl(data->iobase +
|
|
MSP_TWI_INT_STS_REG_OFFSET);
|
|
pmcmsptwi_writel(reason, data->iobase + MSP_TWI_INT_STS_REG_OFFSET);
|
|
|
|
dev_dbg(&pmcmsptwi_adapter.dev, "Got interrupt 0x%08x\n", reason);
|
|
if (!(reason & MSP_TWI_INT_STS_DONE))
|
|
return IRQ_NONE;
|
|
|
|
data->last_result = pmcmsptwi_get_result(reason);
|
|
complete(&data->wait);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* Probe for and register the device and return 0 if there is one.
|
|
*/
|
|
static int __devinit pmcmsptwi_probe(struct platform_device *pldev)
|
|
{
|
|
struct resource *res;
|
|
int rc = -ENODEV;
|
|
|
|
/* get the static platform resources */
|
|
res = platform_get_resource(pldev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(&pldev->dev, "IOMEM resource not found\n");
|
|
goto ret_err;
|
|
}
|
|
|
|
/* reserve the memory region */
|
|
if (!request_mem_region(res->start, res->end - res->start + 1,
|
|
pldev->name)) {
|
|
dev_err(&pldev->dev,
|
|
"Unable to get memory/io address region 0x%08x\n",
|
|
res->start);
|
|
rc = -EBUSY;
|
|
goto ret_err;
|
|
}
|
|
|
|
/* remap the memory */
|
|
pmcmsptwi_data.iobase = ioremap_nocache(res->start,
|
|
res->end - res->start + 1);
|
|
if (!pmcmsptwi_data.iobase) {
|
|
dev_err(&pldev->dev,
|
|
"Unable to ioremap address 0x%08x\n", res->start);
|
|
rc = -EIO;
|
|
goto ret_unreserve;
|
|
}
|
|
|
|
/* request the irq */
|
|
pmcmsptwi_data.irq = platform_get_irq(pldev, 0);
|
|
if (pmcmsptwi_data.irq) {
|
|
rc = request_irq(pmcmsptwi_data.irq, &pmcmsptwi_interrupt,
|
|
IRQF_SHARED | IRQF_DISABLED | IRQF_SAMPLE_RANDOM,
|
|
pldev->name, &pmcmsptwi_data);
|
|
if (rc == 0) {
|
|
/*
|
|
* Enable 'DONE' interrupt only.
|
|
*
|
|
* If you enable all interrupts, you will get one on
|
|
* error and another when the operation completes.
|
|
* This way you only have to handle one interrupt,
|
|
* but you can still check all result flags.
|
|
*/
|
|
pmcmsptwi_writel(MSP_TWI_INT_STS_DONE,
|
|
pmcmsptwi_data.iobase +
|
|
MSP_TWI_INT_MSK_REG_OFFSET);
|
|
} else {
|
|
dev_warn(&pldev->dev,
|
|
"Could not assign TWI IRQ handler "
|
|
"to irq %d (continuing with poll)\n",
|
|
pmcmsptwi_data.irq);
|
|
pmcmsptwi_data.irq = 0;
|
|
}
|
|
}
|
|
|
|
init_completion(&pmcmsptwi_data.wait);
|
|
mutex_init(&pmcmsptwi_data.lock);
|
|
|
|
pmcmsptwi_set_clock_config(&pmcmsptwi_defclockcfg, &pmcmsptwi_data);
|
|
pmcmsptwi_set_twi_config(&pmcmsptwi_defcfg, &pmcmsptwi_data);
|
|
|
|
printk(KERN_INFO DRV_NAME ": Registering MSP71xx I2C adapter\n");
|
|
|
|
pmcmsptwi_adapter.dev.parent = &pldev->dev;
|
|
platform_set_drvdata(pldev, &pmcmsptwi_adapter);
|
|
i2c_set_adapdata(&pmcmsptwi_adapter, &pmcmsptwi_data);
|
|
|
|
rc = i2c_add_adapter(&pmcmsptwi_adapter);
|
|
if (rc) {
|
|
dev_err(&pldev->dev, "Unable to register I2C adapter\n");
|
|
goto ret_unmap;
|
|
}
|
|
|
|
return 0;
|
|
|
|
ret_unmap:
|
|
platform_set_drvdata(pldev, NULL);
|
|
if (pmcmsptwi_data.irq) {
|
|
pmcmsptwi_writel(0,
|
|
pmcmsptwi_data.iobase + MSP_TWI_INT_MSK_REG_OFFSET);
|
|
free_irq(pmcmsptwi_data.irq, &pmcmsptwi_data);
|
|
}
|
|
|
|
iounmap(pmcmsptwi_data.iobase);
|
|
|
|
ret_unreserve:
|
|
release_mem_region(res->start, res->end - res->start + 1);
|
|
|
|
ret_err:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Release the device and return 0 if there is one.
|
|
*/
|
|
static int __devexit pmcmsptwi_remove(struct platform_device *pldev)
|
|
{
|
|
struct resource *res;
|
|
|
|
i2c_del_adapter(&pmcmsptwi_adapter);
|
|
|
|
platform_set_drvdata(pldev, NULL);
|
|
if (pmcmsptwi_data.irq) {
|
|
pmcmsptwi_writel(0,
|
|
pmcmsptwi_data.iobase + MSP_TWI_INT_MSK_REG_OFFSET);
|
|
free_irq(pmcmsptwi_data.irq, &pmcmsptwi_data);
|
|
}
|
|
|
|
iounmap(pmcmsptwi_data.iobase);
|
|
|
|
res = platform_get_resource(pldev, IORESOURCE_MEM, 0);
|
|
release_mem_region(res->start, res->end - res->start + 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Polls the 'busy' register until the command is complete.
|
|
* NOTE: Assumes data->lock is held.
|
|
*/
|
|
static void pmcmsptwi_poll_complete(struct pmcmsptwi_data *data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MSP_MAX_POLL; i++) {
|
|
u32 val = pmcmsptwi_readl(data->iobase +
|
|
MSP_TWI_BUSY_REG_OFFSET);
|
|
if (val == 0) {
|
|
u32 reason = pmcmsptwi_readl(data->iobase +
|
|
MSP_TWI_INT_STS_REG_OFFSET);
|
|
pmcmsptwi_writel(reason, data->iobase +
|
|
MSP_TWI_INT_STS_REG_OFFSET);
|
|
data->last_result = pmcmsptwi_get_result(reason);
|
|
return;
|
|
}
|
|
udelay(MSP_POLL_DELAY);
|
|
}
|
|
|
|
dev_dbg(&pmcmsptwi_adapter.dev, "Result: Poll timeout\n");
|
|
data->last_result = MSP_TWI_XFER_TIMEOUT;
|
|
}
|
|
|
|
/*
|
|
* Do the transfer (low level):
|
|
* May use interrupt-driven or polling, depending on if an IRQ is
|
|
* presently registered.
|
|
* NOTE: Assumes data->lock is held.
|
|
*/
|
|
static enum pmcmsptwi_xfer_result pmcmsptwi_do_xfer(
|
|
u32 reg, struct pmcmsptwi_data *data)
|
|
{
|
|
dev_dbg(&pmcmsptwi_adapter.dev, "Writing cmd reg 0x%08x\n", reg);
|
|
pmcmsptwi_writel(reg, data->iobase + MSP_TWI_CMD_REG_OFFSET);
|
|
if (data->irq) {
|
|
unsigned long timeleft = wait_for_completion_timeout(
|
|
&data->wait, MSP_IRQ_TIMEOUT);
|
|
if (timeleft == 0) {
|
|
dev_dbg(&pmcmsptwi_adapter.dev,
|
|
"Result: IRQ timeout\n");
|
|
complete(&data->wait);
|
|
data->last_result = MSP_TWI_XFER_TIMEOUT;
|
|
}
|
|
} else
|
|
pmcmsptwi_poll_complete(data);
|
|
|
|
return data->last_result;
|
|
}
|
|
|
|
/*
|
|
* Helper routine, converts 'pmctwi_cmd' struct to register format
|
|
*/
|
|
static inline u32 pmcmsptwi_cmd_to_reg(const struct pmcmsptwi_cmd *cmd)
|
|
{
|
|
return ((cmd->type & 0x3) << 8) |
|
|
(((cmd->write_len - 1) & 0x7) << 4) |
|
|
((cmd->read_len - 1) & 0x7);
|
|
}
|
|
|
|
/*
|
|
* Do the transfer (high level)
|
|
*/
|
|
static enum pmcmsptwi_xfer_result pmcmsptwi_xfer_cmd(
|
|
struct pmcmsptwi_cmd *cmd,
|
|
struct pmcmsptwi_data *data)
|
|
{
|
|
enum pmcmsptwi_xfer_result retval;
|
|
|
|
if ((cmd->type == MSP_TWI_CMD_WRITE && cmd->write_len == 0) ||
|
|
(cmd->type == MSP_TWI_CMD_READ && cmd->read_len == 0) ||
|
|
(cmd->type == MSP_TWI_CMD_WRITE_READ &&
|
|
(cmd->read_len == 0 || cmd->write_len == 0))) {
|
|
dev_err(&pmcmsptwi_adapter.dev,
|
|
"%s: Cannot transfer less than 1 byte\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cmd->read_len > MSP_MAX_BYTES_PER_RW ||
|
|
cmd->write_len > MSP_MAX_BYTES_PER_RW) {
|
|
dev_err(&pmcmsptwi_adapter.dev,
|
|
"%s: Cannot transfer more than %d bytes\n",
|
|
__func__, MSP_MAX_BYTES_PER_RW);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&data->lock);
|
|
dev_dbg(&pmcmsptwi_adapter.dev,
|
|
"Setting address to 0x%04x\n", cmd->addr);
|
|
pmcmsptwi_writel(cmd->addr, data->iobase + MSP_TWI_ADD_REG_OFFSET);
|
|
|
|
if (cmd->type == MSP_TWI_CMD_WRITE ||
|
|
cmd->type == MSP_TWI_CMD_WRITE_READ) {
|
|
__be64 tmp = cpu_to_be64p((u64 *)cmd->write_data);
|
|
tmp >>= (MSP_MAX_BYTES_PER_RW - cmd->write_len) * 8;
|
|
dev_dbg(&pmcmsptwi_adapter.dev, "Writing 0x%016llx\n", tmp);
|
|
pmcmsptwi_writel(tmp & 0x00000000ffffffffLL,
|
|
data->iobase + MSP_TWI_DAT_0_REG_OFFSET);
|
|
if (cmd->write_len > 4)
|
|
pmcmsptwi_writel(tmp >> 32,
|
|
data->iobase + MSP_TWI_DAT_1_REG_OFFSET);
|
|
}
|
|
|
|
retval = pmcmsptwi_do_xfer(pmcmsptwi_cmd_to_reg(cmd), data);
|
|
if (retval != MSP_TWI_XFER_OK)
|
|
goto xfer_err;
|
|
|
|
if (cmd->type == MSP_TWI_CMD_READ ||
|
|
cmd->type == MSP_TWI_CMD_WRITE_READ) {
|
|
int i;
|
|
u64 rmsk = ~(0xffffffffffffffffLL << (cmd->read_len * 8));
|
|
u64 tmp = (u64)pmcmsptwi_readl(data->iobase +
|
|
MSP_TWI_DAT_0_REG_OFFSET);
|
|
if (cmd->read_len > 4)
|
|
tmp |= (u64)pmcmsptwi_readl(data->iobase +
|
|
MSP_TWI_DAT_1_REG_OFFSET) << 32;
|
|
tmp &= rmsk;
|
|
dev_dbg(&pmcmsptwi_adapter.dev, "Read 0x%016llx\n", tmp);
|
|
|
|
for (i = 0; i < cmd->read_len; i++)
|
|
cmd->read_data[i] = tmp >> i;
|
|
}
|
|
|
|
xfer_err:
|
|
mutex_unlock(&data->lock);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* -- Algorithm functions -- */
|
|
|
|
/*
|
|
* Sends an i2c command out on the adapter
|
|
*/
|
|
static int pmcmsptwi_master_xfer(struct i2c_adapter *adap,
|
|
struct i2c_msg *msg, int num)
|
|
{
|
|
struct pmcmsptwi_data *data = i2c_get_adapdata(adap);
|
|
struct pmcmsptwi_cmd cmd;
|
|
struct pmcmsptwi_cfg oldcfg, newcfg;
|
|
int ret;
|
|
|
|
if (num > 2) {
|
|
dev_dbg(&adap->dev, "%d messages unsupported\n", num);
|
|
return -EINVAL;
|
|
} else if (num == 2) {
|
|
/* Check for a dual write-then-read command */
|
|
struct i2c_msg *nextmsg = msg + 1;
|
|
if (!(msg->flags & I2C_M_RD) &&
|
|
(nextmsg->flags & I2C_M_RD) &&
|
|
msg->addr == nextmsg->addr) {
|
|
cmd.type = MSP_TWI_CMD_WRITE_READ;
|
|
cmd.write_len = msg->len;
|
|
cmd.write_data = msg->buf;
|
|
cmd.read_len = nextmsg->len;
|
|
cmd.read_data = nextmsg->buf;
|
|
} else {
|
|
dev_dbg(&adap->dev,
|
|
"Non write-read dual messages unsupported\n");
|
|
return -EINVAL;
|
|
}
|
|
} else if (msg->flags & I2C_M_RD) {
|
|
cmd.type = MSP_TWI_CMD_READ;
|
|
cmd.read_len = msg->len;
|
|
cmd.read_data = msg->buf;
|
|
cmd.write_len = 0;
|
|
cmd.write_data = NULL;
|
|
} else {
|
|
cmd.type = MSP_TWI_CMD_WRITE;
|
|
cmd.read_len = 0;
|
|
cmd.read_data = NULL;
|
|
cmd.write_len = msg->len;
|
|
cmd.write_data = msg->buf;
|
|
}
|
|
|
|
if (msg->len == 0) {
|
|
dev_err(&adap->dev, "Zero-byte messages unsupported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
cmd.addr = msg->addr;
|
|
|
|
if (msg->flags & I2C_M_TEN) {
|
|
pmcmsptwi_get_twi_config(&newcfg, data);
|
|
memcpy(&oldcfg, &newcfg, sizeof(oldcfg));
|
|
|
|
/* Set the special 10-bit address flag */
|
|
newcfg.add10 = 1;
|
|
|
|
pmcmsptwi_set_twi_config(&newcfg, data);
|
|
}
|
|
|
|
/* Execute the command */
|
|
ret = pmcmsptwi_xfer_cmd(&cmd, data);
|
|
|
|
if (msg->flags & I2C_M_TEN)
|
|
pmcmsptwi_set_twi_config(&oldcfg, data);
|
|
|
|
dev_dbg(&adap->dev, "I2C %s of %d bytes %s\n",
|
|
(msg->flags & I2C_M_RD) ? "read" : "write", msg->len,
|
|
(ret == MSP_TWI_XFER_OK) ? "succeeded" : "failed");
|
|
|
|
if (ret != MSP_TWI_XFER_OK) {
|
|
/*
|
|
* TODO: We could potentially loop and retry in the case
|
|
* of MSP_TWI_XFER_TIMEOUT.
|
|
*/
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 pmcmsptwi_i2c_func(struct i2c_adapter *adapter)
|
|
{
|
|
return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
|
|
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
|
|
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL;
|
|
}
|
|
|
|
/* -- Initialization -- */
|
|
|
|
static struct i2c_algorithm pmcmsptwi_algo = {
|
|
.master_xfer = pmcmsptwi_master_xfer,
|
|
.functionality = pmcmsptwi_i2c_func,
|
|
};
|
|
|
|
static struct i2c_adapter pmcmsptwi_adapter = {
|
|
.owner = THIS_MODULE,
|
|
.class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
|
|
.algo = &pmcmsptwi_algo,
|
|
.name = DRV_NAME,
|
|
};
|
|
|
|
/* work with hotplug and coldplug */
|
|
MODULE_ALIAS("platform:" DRV_NAME);
|
|
|
|
static struct platform_driver pmcmsptwi_driver = {
|
|
.probe = pmcmsptwi_probe,
|
|
.remove = __devexit_p(pmcmsptwi_remove),
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init pmcmsptwi_init(void)
|
|
{
|
|
return platform_driver_register(&pmcmsptwi_driver);
|
|
}
|
|
|
|
static void __exit pmcmsptwi_exit(void)
|
|
{
|
|
platform_driver_unregister(&pmcmsptwi_driver);
|
|
}
|
|
|
|
MODULE_DESCRIPTION("PMC MSP TWI/SMBus/I2C driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(pmcmsptwi_init);
|
|
module_exit(pmcmsptwi_exit);
|