/*
- * drivers/i2c/busses/i2c-rcar.c
+ * Driver for the Renesas RCar I2C unit
*
- * Copyright (C) 2012 Renesas Solutions Corp.
+ * Copyright (C) 2014 Wolfram Sang <wsa@sang-engineering.com>
+ *
+ * Copyright (C) 2012-14 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This file is based on the drivers/i2c/busses/i2c-sh7760.c
*
* 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
+ * the Free Software Foundation; version 2 of the License.
*
* 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.
- *
- * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
/* register offsets */
#define ICSCR 0x00 /* slave ctrl */
#define RCAR_IRQ_RECV (MNR | MAL | MST | MAT | MDR)
#define RCAR_IRQ_STOP (MST)
-#define RCAR_IRQ_ACK_SEND (~(MAT | MDE))
-#define RCAR_IRQ_ACK_RECV (~(MAT | MDR))
+#define RCAR_IRQ_ACK_SEND (~(MAT | MDE) & 0xFF)
+#define RCAR_IRQ_ACK_RECV (~(MAT | MDR) & 0xFF)
-/*
- * flags
- */
#define ID_LAST_MSG (1 << 0)
#define ID_IOERROR (1 << 1)
#define ID_DONE (1 << 2)
struct i2c_msg *msg;
struct clk *clk;
+ spinlock_t lock;
wait_queue_head_t wait;
int pos;
u32 icccr;
u32 flags;
- enum rcar_i2c_type devtype;
+ enum rcar_i2c_type devtype;
};
#define rcar_i2c_priv_to_dev(p) ((p)->adap.dev.parent)
#define LOOP_TIMEOUT 1024
-/*
- * basic functions
- */
+
static void rcar_i2c_write(struct rcar_i2c_priv *priv, int reg, u32 val)
{
writel(val, priv->io + reg);
rcar_i2c_write(priv, ICMAR, 0);
}
-/*
- * bus control functions
- */
static int rcar_i2c_bus_barrier(struct rcar_i2c_priv *priv)
{
int i;
return -EBUSY;
}
-/*
- * clock function
- */
static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv,
u32 bus_speed,
struct device *dev)
*/
rate = clk_get_rate(priv->clk);
cdf = rate / 20000000;
- if (cdf >= 1 << cdf_width) {
+ if (cdf >= 1U << cdf_width) {
dev_err(dev, "Input clock %lu too high\n", rate);
return -EIO;
}
return 0;
}
-/*
- * status functions
- */
-
-static int rcar_i2c_prepare_msg(struct rcar_i2c_priv *priv)
+static void rcar_i2c_prepare_msg(struct rcar_i2c_priv *priv)
{
int read = !!rcar_i2c_is_recv(priv);
rcar_i2c_write(priv, ICMSR, 0);
rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START);
rcar_i2c_write(priv, ICMIER, read ? RCAR_IRQ_RECV : RCAR_IRQ_SEND);
-
- return 0;
}
/*
static irqreturn_t rcar_i2c_irq(int irq, void *ptr)
{
struct rcar_i2c_priv *priv = ptr;
- struct device *dev = rcar_i2c_priv_to_dev(priv);
+ irqreturn_t result = IRQ_HANDLED;
u32 msr;
+ /*-------------- spin lock -----------------*/
+ spin_lock(&priv->lock);
+
msr = rcar_i2c_read(priv, ICMSR);
- /*
- * Arbitration lost
- */
- if (msr & MAL) {
- /*
- * CAUTION
- *
- * When arbitration lost, device become _slave_ mode.
- */
- dev_dbg(dev, "Arbitration Lost\n");
- rcar_i2c_flags_set(priv, (ID_DONE | ID_ARBLOST));
- goto out;
+ /* Only handle interrupts that are currently enabled */
+ msr &= rcar_i2c_read(priv, ICMIER);
+ if (!msr) {
+ result = IRQ_NONE;
+ goto exit;
}
- /*
- * Stop
- */
- if (msr & MST) {
- dev_dbg(dev, "Stop\n");
- rcar_i2c_flags_set(priv, ID_DONE);
+ /* Arbitration lost */
+ if (msr & MAL) {
+ rcar_i2c_flags_set(priv, (ID_DONE | ID_ARBLOST));
goto out;
}
- /*
- * Nack
- */
+ /* Nack */
if (msr & MNR) {
- dev_dbg(dev, "Nack\n");
-
/* go to stop phase */
rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP);
rcar_i2c_write(priv, ICMIER, RCAR_IRQ_STOP);
goto out;
}
- /*
- * recv/send
- */
+ /* Stop */
+ if (msr & MST) {
+ rcar_i2c_flags_set(priv, ID_DONE);
+ goto out;
+ }
+
if (rcar_i2c_is_recv(priv))
rcar_i2c_flags_set(priv, rcar_i2c_irq_recv(priv, msr));
else
wake_up(&priv->wait);
}
- return IRQ_HANDLED;
+exit:
+ spin_unlock(&priv->lock);
+ /*-------------- spin unlock -----------------*/
+
+ return result;
}
static int rcar_i2c_master_xfer(struct i2c_adapter *adap,
{
struct rcar_i2c_priv *priv = i2c_get_adapdata(adap);
struct device *dev = rcar_i2c_priv_to_dev(priv);
+ unsigned long flags;
int i, ret, timeout;
pm_runtime_get_sync(dev);
+ /*-------------- spin lock -----------------*/
+ spin_lock_irqsave(&priv->lock, flags);
+
rcar_i2c_init(priv);
/* start clock */
rcar_i2c_write(priv, ICCCR, priv->icccr);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ /*-------------- spin unlock -----------------*/
+
ret = rcar_i2c_bus_barrier(priv);
if (ret < 0)
goto out;
break;
}
+ /*-------------- spin lock -----------------*/
+ spin_lock_irqsave(&priv->lock, flags);
+
/* init each data */
priv->msg = &msgs[i];
priv->pos = 0;
priv->flags = 0;
- if (priv->msg == &msgs[num - 1])
+ if (i == num - 1)
rcar_i2c_flags_set(priv, ID_LAST_MSG);
- ret = rcar_i2c_prepare_msg(priv);
+ rcar_i2c_prepare_msg(priv);
- if (ret < 0)
- break;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ /*-------------- spin unlock -----------------*/
- /*
- * wait result
- */
timeout = wait_event_timeout(priv->wait,
rcar_i2c_flags_has(priv, ID_DONE),
5 * HZ);
break;
}
- /*
- * error handling
- */
if (rcar_i2c_flags_has(priv, ID_NACK)) {
ret = -ENXIO;
break;
int irq, ret;
priv = devm_kzalloc(dev, sizeof(struct rcar_i2c_priv), GFP_KERNEL);
- if (!priv) {
- dev_err(dev, "no mem for private data\n");
+ if (!priv)
return -ENOMEM;
- }
priv->clk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->clk)) {
irq = platform_get_irq(pdev, 0);
init_waitqueue_head(&priv->wait);
-
- adap = &priv->adap;
- adap->nr = pdev->id;
- adap->algo = &rcar_i2c_algo;
- adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD | I2C_CLASS_DEPRECATED;
- adap->retries = 3;
- adap->dev.parent = dev;
- adap->dev.of_node = dev->of_node;
+ spin_lock_init(&priv->lock);
+
+ adap = &priv->adap;
+ adap->nr = pdev->id;
+ adap->algo = &rcar_i2c_algo;
+ adap->class = I2C_CLASS_DEPRECATED;
+ adap->retries = 3;
+ adap->dev.parent = dev;
+ adap->dev.of_node = dev->of_node;
i2c_set_adapdata(adap, priv);
strlcpy(adap->name, pdev->name, sizeof(adap->name));
module_platform_driver(rcar_i2c_driver);
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Renesas R-Car I2C bus driver");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");