* [v13, 3/8] dt: bindings: move guts devicetree doc out of powerpc directory
From: Yangbo Lu @ 2016-10-28 3:32 UTC (permalink / raw)
To: linux-mmc-u79uwXL29TY76Z2rM5mHXA,
ulf.hansson-QSEj5FYQhm4dnm+yROfE0A, Scott Wood, Arnd Bergmann
Cc: Mark Rutland, Greg Kroah-Hartman, Xiaobo Xie, Minghuan Lian,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-clk-u79uwXL29TY76Z2rM5mHXA, Qiang Zhao, Russell King,
Bhupesh Sharma, Jochen Friedrich, Claudiu Manoil,
devicetree-u79uwXL29TY76Z2rM5mHXA, Yangbo Lu, Rob Herring,
Santosh Shilimkar,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Leo Li,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA, Kumar Gala,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ
In-Reply-To: <1477625554-46700-1-git-send-email-yangbo.lu-3arQi8VN3Tc@public.gmane.org>
Move guts devicetree doc to Documentation/devicetree/bindings/soc/fsl/
since it's used by not only PowerPC but also ARM. And add a specification
for 'little-endian' property.
Signed-off-by: Yangbo Lu <yangbo.lu-3arQi8VN3Tc@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Acked-by: Scott Wood <oss-fOR+EgIDQEHk1uMJSBkQmQ@public.gmane.org>
---
Changes for v4:
- Added this patch
Changes for v5:
- Modified the description for little-endian property
Changes for v6:
- None
Changes for v7:
- None
Changes for v8:
- Added 'Acked-by: Scott Wood'
- Added 'Acked-by: Rob Herring'
Changes for v9:
- None
Changes for v10:
- None
Changes for v11:
- None
Changes for v12:
- None
Changes for v13:
- None
---
Documentation/devicetree/bindings/{powerpc => soc}/fsl/guts.txt | 3 +++
1 file changed, 3 insertions(+)
rename Documentation/devicetree/bindings/{powerpc => soc}/fsl/guts.txt (91%)
diff --git a/Documentation/devicetree/bindings/powerpc/fsl/guts.txt b/Documentation/devicetree/bindings/soc/fsl/guts.txt
similarity index 91%
rename from Documentation/devicetree/bindings/powerpc/fsl/guts.txt
rename to Documentation/devicetree/bindings/soc/fsl/guts.txt
index b71b203..07adca9 100644
--- a/Documentation/devicetree/bindings/powerpc/fsl/guts.txt
+++ b/Documentation/devicetree/bindings/soc/fsl/guts.txt
@@ -25,6 +25,9 @@ Recommended properties:
- fsl,liodn-bits : Indicates the number of defined bits in the LIODN
registers, for those SOCs that have a PAMU device.
+ - little-endian : Indicates that the global utilities block is little
+ endian. The default is big endian.
+
Examples:
global-utilities@e0000 { /* global utilities block */
compatible = "fsl,mpc8548-guts";
--
2.1.0.27.g96db324
^ permalink raw reply related
* [v13, 2/8] ARM64: dts: ls2080a: add device configuration node
From: Yangbo Lu @ 2016-10-28 3:32 UTC (permalink / raw)
To: linux-mmc-u79uwXL29TY76Z2rM5mHXA,
ulf.hansson-QSEj5FYQhm4dnm+yROfE0A, Scott Wood, Arnd Bergmann
Cc: Mark Rutland, Greg Kroah-Hartman, Xiaobo Xie, Minghuan Lian,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-clk-u79uwXL29TY76Z2rM5mHXA, Qiang Zhao, Russell King,
Bhupesh Sharma, Jochen Friedrich, Claudiu Manoil,
devicetree-u79uwXL29TY76Z2rM5mHXA, Yangbo Lu, Rob Herring,
Santosh Shilimkar,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Leo Li,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA, Kumar Gala,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ
In-Reply-To: <1477625554-46700-1-git-send-email-yangbo.lu-3arQi8VN3Tc@public.gmane.org>
Add the dts node for device configuration unit that provides
general purpose configuration and status for the device.
Signed-off-by: Yangbo Lu <yangbo.lu-3arQi8VN3Tc@public.gmane.org>
Acked-by: Scott Wood <oss-fOR+EgIDQEHk1uMJSBkQmQ@public.gmane.org>
---
Changes for v5:
- Added this patch
Changes for v6:
- None
Changes for v7:
- None
Changes for v8:
- Added 'Acked-by: Scott Wood'
Changes for v9:
- None
Changes for v10:
- None
Changes for v11:
- None
Changes for v12:
- None
Changes for v13:
- None
---
arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi b/arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi
index 337da90..c03b099 100644
--- a/arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi
+++ b/arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi
@@ -215,6 +215,12 @@
clocks = <&sysclk>;
};
+ dcfg: dcfg@1e00000 {
+ compatible = "fsl,ls2080a-dcfg", "syscon";
+ reg = <0x0 0x1e00000 0x0 0x10000>;
+ little-endian;
+ };
+
serial0: serial@21c0500 {
compatible = "fsl,ns16550", "ns16550a";
reg = <0x0 0x21c0500 0x0 0x100>;
--
2.1.0.27.g96db324
^ permalink raw reply related
* [v13, 1/8] dt: bindings: update Freescale DCFG compatible
From: Yangbo Lu @ 2016-10-28 3:32 UTC (permalink / raw)
To: linux-mmc-u79uwXL29TY76Z2rM5mHXA,
ulf.hansson-QSEj5FYQhm4dnm+yROfE0A, Scott Wood, Arnd Bergmann
Cc: Mark Rutland, Greg Kroah-Hartman, Xiaobo Xie, Minghuan Lian,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-clk-u79uwXL29TY76Z2rM5mHXA, Qiang Zhao, Russell King,
Bhupesh Sharma, Jochen Friedrich, Claudiu Manoil,
devicetree-u79uwXL29TY76Z2rM5mHXA, Yangbo Lu, Rob Herring,
Santosh Shilimkar,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Leo Li,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA, Kumar Gala,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ
In-Reply-To: <1477625554-46700-1-git-send-email-yangbo.lu-3arQi8VN3Tc@public.gmane.org>
Update Freescale DCFG compatible with 'fsl,<chip>-dcfg' instead
of 'fsl,ls1021a-dcfg' to include more chips such as ls1021a,
ls1043a, and ls2080a.
Signed-off-by: Yangbo Lu <yangbo.lu-3arQi8VN3Tc@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Scott Wood <oss-fOR+EgIDQEHk1uMJSBkQmQ@public.gmane.org>
---
Changes for v8:
- Added this patch
Changes for v9:
- Added a list for the possible compatibles
Changes for v10:
- None
Changes for v11:
- Added 'Acked-by: Rob Herring'
- Updated commit message by Scott
Changes for v12:
- None
Changes for v13:
- None
---
Documentation/devicetree/bindings/arm/fsl.txt | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/arm/fsl.txt b/Documentation/devicetree/bindings/arm/fsl.txt
index dbbc095..713c1ae 100644
--- a/Documentation/devicetree/bindings/arm/fsl.txt
+++ b/Documentation/devicetree/bindings/arm/fsl.txt
@@ -119,7 +119,11 @@ Freescale DCFG
configuration and status for the device. Such as setting the secondary
core start address and release the secondary core from holdoff and startup.
Required properties:
- - compatible: should be "fsl,ls1021a-dcfg"
+ - compatible: should be "fsl,<chip>-dcfg"
+ Possible compatibles:
+ "fsl,ls1021a-dcfg"
+ "fsl,ls1043a-dcfg"
+ "fsl,ls2080a-dcfg"
- reg : should contain base address and length of DCFG memory-mapped registers
Example:
--
2.1.0.27.g96db324
^ permalink raw reply related
* [v13, 0/8] Fix eSDHC host version register bug
From: Yangbo Lu @ 2016-10-28 3:32 UTC (permalink / raw)
To: linux-mmc-u79uwXL29TY76Z2rM5mHXA,
ulf.hansson-QSEj5FYQhm4dnm+yROfE0A, Scott Wood, Arnd Bergmann
Cc: Mark Rutland, Greg Kroah-Hartman, Xiaobo Xie, Minghuan Lian,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-clk-u79uwXL29TY76Z2rM5mHXA, Qiang Zhao, Russell King,
Bhupesh Sharma, Jochen Friedrich, Claudiu Manoil,
devicetree-u79uwXL29TY76Z2rM5mHXA, Yangbo Lu, Rob Herring,
Santosh Shilimkar,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Leo Li,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA, Kumar Gala,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ
This patchset is used to fix a host version register bug in the T4240-R1.0-R2.0
eSDHC controller. To match the SoC version and revision, 10 previous version
patchsets had tried many methods but all of them were rejected by reviewers.
Such as
- dts compatible method
- syscon method
- ifdef PPC method
- GUTS driver getting SVR method
Anrd suggested a soc_device_match method in v10, and this is the only available
method left now. This v11 patchset introduces the soc_device_match interface in
soc driver.
The first six patches of Yangbo are to add the GUTS driver. This is used to
register a soc device which contain soc version and revision information.
The other two patches introduce the soc_device_match method in soc driver
and apply it on esdhc driver to fix this bug.
Arnd Bergmann (1):
base: soc: introduce soc_device_match() interface
Yangbo Lu (7):
dt: bindings: update Freescale DCFG compatible
ARM64: dts: ls2080a: add device configuration node
dt: bindings: move guts devicetree doc out of powerpc directory
powerpc/fsl: move mpc85xx.h to include/linux/fsl
soc: fsl: add GUTS driver for QorIQ platforms
MAINTAINERS: add entry for Freescale SoC drivers
mmc: sdhci-of-esdhc: fix host version for T4240-R1.0-R2.0
Documentation/devicetree/bindings/arm/fsl.txt | 6 +-
.../bindings/{powerpc => soc}/fsl/guts.txt | 3 +
MAINTAINERS | 11 +-
arch/arm64/boot/dts/freescale/fsl-ls2080a.dtsi | 6 +
arch/powerpc/kernel/cpu_setup_fsl_booke.S | 2 +-
arch/powerpc/sysdev/fsl_pci.c | 2 +-
drivers/base/Kconfig | 1 +
drivers/base/soc.c | 66 ++++++
drivers/clk/clk-qoriq.c | 3 +-
drivers/i2c/busses/i2c-mpc.c | 2 +-
drivers/iommu/fsl_pamu.c | 3 +-
drivers/mmc/host/Kconfig | 1 +
drivers/mmc/host/sdhci-of-esdhc.c | 20 ++
drivers/net/ethernet/freescale/gianfar.c | 2 +-
drivers/soc/Kconfig | 3 +-
drivers/soc/fsl/Kconfig | 18 ++
drivers/soc/fsl/Makefile | 1 +
drivers/soc/fsl/guts.c | 236 +++++++++++++++++++++
include/linux/fsl/guts.h | 125 ++++++-----
.../asm/mpc85xx.h => include/linux/fsl/svr.h | 4 +-
include/linux/sys_soc.h | 3 +
21 files changed, 456 insertions(+), 62 deletions(-)
rename Documentation/devicetree/bindings/{powerpc => soc}/fsl/guts.txt (91%)
create mode 100644 drivers/soc/fsl/Kconfig
create mode 100644 drivers/soc/fsl/guts.c
rename arch/powerpc/include/asm/mpc85xx.h => include/linux/fsl/svr.h (97%)
--
2.1.0.27.g96db324
^ permalink raw reply
* Re: [PATCH] i2c: imx: add slave support. v2 status
From: Maxim Syrchin @ 2016-10-27 19:38 UTC (permalink / raw)
To: Frkuska, Joshua, linux-i2c
Cc: wsa, peda, Jiada_Wang, linux-kernel, Zapolskiy, Vladimir,
Baxter, Jim
In-Reply-To: <1e9f10a4-6ea2-3dd9-159e-fd70f7c40224@mentor.com>
[-- Attachment #1: Type: text/plain, Size: 1592 bytes --]
Hi,
Sorry for huge delay in answering. Unfortunately we don't have enough
resources now to prepare clean enough patch to be accepted by community.
Please find the latest version attached. Driver has passed stress
tests, but looks like it need seriuos refactoring (it is unnecessarily
complicated).
We still have polling in slave code. Since imx doesn't generate
interrupt on "bus busy"/"bus free" events we have to test IBB bit in
polling loop.
Comparing to v2 version several race conditions were fixed (bus locking
by slave, breaking slave transaction by starting master xfer). v2 is
working pretty good in slave-only or master-only mode. It is reasonable
to add slave locking test - sometimes imx6 hw doesn't release data
line. As workaround we use dummy reading of IMX_I2C_I2DR if driver is
in slave mode and IBB bit is set for a long time.
Thanks,
Maxim Syrchin
27.10.2016 10:31, Frkuska, Joshua пишет:
> Hi Maxim, Dmitriy, Wolfram,
>
> If there is no immediate plan for a third release of the below patch
> set, would it be possible to continue with merging v2 after addressing
> the remaining concerns?
>
>
> Thank you and regards,
>
> Joshua
>> Hi Maxim,
>>
>> On 2016-03-04 11:06:10 in the thread "Re: [PATCH] i2c: imx: add slave
>> support. v2"
>> referenced here: https://patchwork.ozlabs.org/patch/573353/ you said:
>>> Hi Wolfram,
>>> I'm now working on creating new driver version. I think I'll be able to
>>> sent it soon.
>> Do you still plan to release a driver update for an i2c imx driver
>> slave support?
>>
>> Best regards,
>> Jim Baxter
>>
[-- Attachment #2: 0001-i2c-imx-add-slave-support.-v3.patch --]
[-- Type: text/plain, Size: 22747 bytes --]
From 61ae34268d78eb284bf8ee0cbe8f9f0c5e7df074 Mon Sep 17 00:00:00 2001
From: Maxim Syrchin <msyrchin@dev.rtsoft.ru>
Date: Thu, 27 Oct 2016 17:37:56 +0300
Subject: [PATCH] i2c: imx: add slave support. v3
Add I2C slave provider using the generic slave interface.
It also supports master transactions when the slave in the idle mode.
Signed-off-by: Maxim Syrchin <msyrchin@dev.rtsoft.ru>
---
drivers/i2c/busses/i2c-imx.c | 682 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 653 insertions(+), 29 deletions(-)
diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index 592a8f2..11a2292 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -53,6 +53,7 @@
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/slab.h>
+#include <linux/kthread.h>
/* This will be the driver name the kernel reports */
#define DRIVER_NAME "imx-i2c"
@@ -171,6 +172,82 @@ enum imx_i2c_type {
VF610_I2C,
};
+enum i2c_imx_state {
+ STATE_IDLE = 0,
+ STATE_SLAVE,
+ STATE_MASTER,
+ STATE_SP
+};
+
+#define MAX_EVENTS (1<<4)
+#define EUNDEFINED 4000
+
+enum i2c_imx_events {
+ EVT_AL = 0,
+ EVT_SI,
+ EVT_START,
+ EVT_STOP,
+ EVT_POLL,
+ EVT_INVALID,
+ EVT_ENTRY
+};
+
+typedef struct evt_queue {
+ atomic_t count;
+ atomic_t ir;
+ atomic_t iw;
+ unsigned int evt[MAX_EVENTS];
+} evt_queue;
+
+static inline int evt_find_next_idx(atomic_t *v)
+{
+ return atomic_inc_return(v) & (MAX_EVENTS - 1);
+}
+
+static unsigned int evt_put(evt_queue *stk, unsigned int evt)
+{
+ int count = atomic_inc_return(&stk->count);
+ int idx;
+ if (count < MAX_EVENTS)
+ {
+ idx = evt_find_next_idx(&stk->iw);
+ stk->evt[idx] = evt;
+
+ return 0;
+ } else {
+ atomic_dec(&stk->count);
+ return EVT_INVALID;
+ }
+}
+
+static unsigned int evt_get(evt_queue *stk)
+{
+ int count = atomic_dec_return(&stk->count);
+ int idx;
+
+ if (count >= 0)
+ {
+ idx = evt_find_next_idx(&stk->ir);
+ return stk->evt[idx];
+ } else {
+ atomic_inc(&stk->count);
+ return EVT_INVALID;
+ }
+}
+
+static unsigned int evt_is_empty(evt_queue *stk)
+{
+ int ret = atomic_read(&stk->count);
+ return (ret <= 0);
+}
+
+static void evt_init(evt_queue *stk)
+{
+ atomic_set(&stk->count,0);
+ atomic_set(&stk->iw,0);
+ atomic_set(&stk->ir,0);
+}
+
struct imx_i2c_hwdata {
enum imx_i2c_type devtype;
unsigned regshift;
@@ -193,6 +270,7 @@ struct imx_i2c_dma {
struct imx_i2c_struct {
struct i2c_adapter adapter;
+ struct i2c_client *slave;
struct clk *clk;
void __iomem *base;
wait_queue_head_t queue;
@@ -210,6 +288,18 @@ struct imx_i2c_struct {
struct pinctrl_state *pinctrl_pins_gpio;
struct imx_i2c_dma *dma;
+
+ unsigned int state;
+ evt_queue events;
+ atomic_t last_error;
+
+ int master_interrupt;
+ int start_retry_cnt;
+ int slave_poll_cnt;
+
+ struct task_struct *slave_task;
+ wait_queue_head_t state_queue;
+
};
static const struct imx_i2c_hwdata imx1_i2c_hwdata = {
@@ -414,17 +504,31 @@ static void i2c_imx_dma_free(struct imx_i2c_struct *i2c_imx)
static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy)
{
unsigned long orig_jiffies = jiffies;
- unsigned int temp;
+ unsigned int temp, ctl;
+
dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
while (1) {
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ ctl = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
- /* check for arbitration lost */
- if (temp & I2SR_IAL) {
- temp &= ~I2SR_IAL;
- imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
+ /*
+ Check for arbitration lost. Datasheet recommends to
+ clean IAL bit in interrupt handler before any other action.
+ So, we cannot handle IAL here like this:
+ if (temp & I2SR_IAL) {
+ tepm &= ~I2SR_IAL;
+ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
+ }
+
+ But we can detect if controller resets MSTA bit, because
+ hardware is switched to slave mode if arbitration was lost.
+ */
+
+ if (for_busy && !(ctl & I2CR_MSTA)) {
+ dev_dbg(&i2c_imx->adapter.dev,
+ "<%s> Lost arbitration (SR = %02x, CR = %02x)\n", __func__, temp, ctl);
return -EAGAIN;
}
@@ -445,14 +549,14 @@ static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy)
static int i2c_imx_trx_complete(struct imx_i2c_struct *i2c_imx)
{
- wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10);
+ wait_event_timeout(i2c_imx->queue, i2c_imx->master_interrupt, HZ / 10);
- if (unlikely(!(i2c_imx->i2csr & I2SR_IIF))) {
+ if (unlikely(!(i2c_imx->master_interrupt))) {
dev_dbg(&i2c_imx->adapter.dev, "<%s> Timeout\n", __func__);
return -ETIMEDOUT;
}
dev_dbg(&i2c_imx->adapter.dev, "<%s> TRX complete\n", __func__);
- i2c_imx->i2csr = 0;
+ i2c_imx->master_interrupt = 0;
return 0;
}
@@ -510,6 +614,44 @@ static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx)
#endif
}
+static int i2c_imx_configure_clock(struct imx_i2c_struct *i2c_imx)
+{
+ int result;
+
+ i2c_imx_set_clk(i2c_imx);
+
+ result = clk_prepare_enable(i2c_imx->clk);
+ if (result == 0)
+ imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR);
+
+ return result;
+}
+
+static void i2c_imx_enable_i2c_controller(struct imx_i2c_struct *i2c_imx)
+{
+ imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx,
+ IMX_I2C_I2SR);
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx,
+ IMX_I2C_I2CR);
+
+ /* Wait controller to be stable */
+ udelay(50);
+}
+
+static int i2c_imx_hw_start(struct imx_i2c_struct *i2c_imx)
+{
+ int result;
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ result = i2c_imx_configure_clock(i2c_imx);
+ if (result != 0)
+ return result;
+ i2c_imx_enable_i2c_controller(i2c_imx);
+ return 0;
+}
+
+
static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
{
unsigned int temp = 0;
@@ -517,20 +659,49 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
- i2c_imx_set_clk(i2c_imx);
+ if (i2c_imx->slave_task != NULL)
+ {
+ int cnt = 0;
+ atomic_set(&i2c_imx->last_error, EUNDEFINED);
+ if (evt_put(&i2c_imx->events, EVT_START) != 0)
+ {
+ dev_err(&i2c_imx->adapter.dev, "Event buffer overflow\n");
+ return -EBUSY;
+ }
- imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR);
- /* Enable I2C controller */
- imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
- imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx, IMX_I2C_I2CR);
+ wake_up(&i2c_imx->state_queue);
- /* Wait controller to be stable */
- usleep_range(50, 150);
+ while( (result = atomic_read(&i2c_imx->last_error)) == EUNDEFINED)
+ {
+ schedule();
+
+ /* TODO: debug workaround - start hung monitoring */
+ cnt++;
+ if (cnt == 500000)
+ {
+ dev_err(&i2c_imx->adapter.dev, "Too many start loops\n");
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
+ i2c_imx, IMX_I2C_I2CR);
+ i2c_imx_enable_i2c_controller(i2c_imx);
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode | I2CR_IIEN,
+ i2c_imx,IMX_I2C_I2CR);
+
+ return -ETIMEDOUT;
+ }
+
+ };
+ return result;
+ }
+
+ result = i2c_imx_hw_start(i2c_imx);
+ if (result != 0)
+ return result;
/* Start I2C transaction */
temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
temp |= I2CR_MSTA;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+
result = i2c_imx_bus_busy(i2c_imx, 1);
if (result)
return result;
@@ -542,10 +713,23 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
return result;
}
-static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
+static void i2c_imx_slave_init(struct imx_i2c_struct *i2c_imx)
{
- unsigned int temp = 0;
+ unsigned int temp;
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ i2c_imx_enable_i2c_controller(i2c_imx);
+
+ /* Set Slave mode with interrupt enable */
+ temp = i2c_imx->hwdata->i2cr_ien_opcode | I2CR_IIEN;
+ imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+}
+
+static void i2c_imx_hw_stop(struct imx_i2c_struct *i2c_imx)
+{
+ unsigned int temp = 0;
if (!i2c_imx->stopped) {
/* Stop I2C transaction */
dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
@@ -555,6 +739,7 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
temp &= ~I2CR_DMAEN;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
}
+
if (is_imx1_i2c(i2c_imx)) {
/*
* This delay caused by an i.MXL hardware bug.
@@ -568,24 +753,388 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
i2c_imx->stopped = 1;
}
- /* Disable I2C controller */
- temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
+ temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN;
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
+
+ clk_disable_unprepare(i2c_imx->clk);
+
+}
+
+static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
+{
+ if (i2c_imx->slave == NULL) {
+ i2c_imx_hw_stop(i2c_imx);
+ } else {
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ evt_put(&i2c_imx->events, EVT_STOP);
+ wake_up(&i2c_imx->state_queue);
+ }
+}
+
+static void i2c_imx_clear_isr_bit(struct imx_i2c_struct *i2c_imx,
+ unsigned int status)
+{
+ status &= ~I2SR_IIF;
+ status |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IIF);
+ imx_i2c_write_reg(status, i2c_imx, IMX_I2C_I2SR);
+}
+
+static void i2c_imx_clear_ial_bit(struct imx_i2c_struct *i2c_imx)
+{
+ unsigned int status;
+ status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ status &= ~I2SR_IAL;
+ imx_i2c_write_reg(status, i2c_imx, IMX_I2C_I2SR);
+}
+
+static void set_state (struct imx_i2c_struct *i2c_imx, unsigned int new);
+
+static void i2c_imx_slave_process_irq(struct imx_i2c_struct *i2c_imx)
+{
+ unsigned int status, ctl;
+ u8 data;
+
+ status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ ctl = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+
+ if (status & I2SR_IAAS) {
+ if (status & I2SR_SRW) {
+ /* master wants to read from us */
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_READ_REQUESTED, &data);
+ ctl |= I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+
+ /*send data */
+ imx_i2c_write_reg(data, i2c_imx, IMX_I2C_I2DR);
+ } else {
+ dev_dbg(&i2c_imx->adapter.dev, "write requested");
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_WRITE_REQUESTED, &data);
+ /*slave receive */
+ ctl &= ~I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+
+ /*dummy read */
+ data = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+ }
+ } else {
+ /* slave send */
+ if (ctl & I2CR_MTX) {
+ if (!(status & I2SR_RXAK)) { /*ACK received */
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_READ_PROCESSED, &data);
+ ctl |= I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+ /*send data */
+ imx_i2c_write_reg(data, i2c_imx, IMX_I2C_I2DR);
+ } else {
+ /*no ACK. */
+ /*dummy read */
+ dev_dbg(&i2c_imx->adapter.dev, "read requested");
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_READ_REQUESTED, &data);
+
+ ctl &= ~I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+ imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+ }
+ } else { /*read */
+ ctl &= ~I2CR_MTX;
+ imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR);
+
+ /*read */
+ data = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+ dev_dbg(&i2c_imx->adapter.dev, "received %x",
+ (unsigned int) data);
+ i2c_slave_event(i2c_imx->slave,
+ I2C_SLAVE_WRITE_RECEIVED, &data);
+ }
+ }
+}
+
+
+static void idle_evt_handler (struct imx_i2c_struct *i2c_imx, unsigned int event)
+{
+ u8 reg, data;
+ switch (event) {
+ case EVT_ENTRY:
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
+ i2c_imx, IMX_I2C_I2CR);
+ i2c_imx_enable_i2c_controller(i2c_imx);
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode | I2CR_IIEN,
+ i2c_imx,IMX_I2C_I2CR);
+ if (atomic_read(&i2c_imx->last_error) == EUNDEFINED)
+ {
+ dev_dbg(&i2c_imx->adapter.dev, "Reset lost START event\n");
+ atomic_set(&i2c_imx->last_error, -EBUSY);
+ }
+ i2c_imx->start_retry_cnt = 0 ;
+ return;
+ case EVT_AL:
+ i2c_imx_clear_ial_bit(i2c_imx);
+ break;
+ case EVT_SI:
+ set_state(i2c_imx, STATE_SLAVE);
+ i2c_imx_slave_process_irq(i2c_imx);
+ break;
+ case EVT_START:
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ if ((reg & I2SR_IBB) != 0) {
+ atomic_set(&i2c_imx->last_error, -EBUSY);
+ break;
+ }
+
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+ reg |= I2CR_MSTA;
+ imx_i2c_write_reg(reg, i2c_imx, IMX_I2C_I2CR);
+ set_state(i2c_imx,STATE_SP);
+ i2c_imx->start_retry_cnt = 0;
+ return;
+ break;
+ case EVT_STOP:
+ break;
+ case EVT_POLL:
+ break;
+ default:
+ break;
+ }
+
+ wait_event_interruptible_timeout(i2c_imx->state_queue,(evt_is_empty(&i2c_imx->events) == 0),HZ/10);
+
+}
+
+static void master_evt_handler (struct imx_i2c_struct *i2c_imx, unsigned int event)
+{
+ switch (event) {
+ case EVT_ENTRY:
+ i2c_imx->start_retry_cnt = 0 ;
+ return;
+ case EVT_AL:
+ set_state(i2c_imx, STATE_IDLE);
+ break;
+ case EVT_SI:
+ break;
+ case EVT_START:
+ atomic_set(&i2c_imx->last_error, -EBUSY);
+ break;
+ case EVT_STOP:
+ imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx,
+ IMX_I2C_I2SR);
+ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode | I2CR_IIEN,
+ i2c_imx,IMX_I2C_I2CR);
+
+ i2c_imx->stopped = 1;
+ udelay(50);
+ set_state(i2c_imx, STATE_IDLE);
+ return;
+ case EVT_POLL:
+ default:
+ break;
+ }
+
+ wait_event_interruptible_timeout(i2c_imx->state_queue,(evt_is_empty(&i2c_imx->events) == 0), HZ / 10);
+}
+
+static void slave_evt_handler (struct imx_i2c_struct *i2c_imx, unsigned int event)
+{
+ u8 reg,data;
+ switch (event) {
+ case EVT_ENTRY:
+ if (atomic_read(&i2c_imx->last_error) == EUNDEFINED)
+ {
+ dev_dbg(&i2c_imx->adapter.dev, "Reset lost START event\n");
+ atomic_set(&i2c_imx->last_error, -EBUSY);
+ }
+ i2c_imx->start_retry_cnt = 0 ;
+ i2c_imx->slave_poll_cnt = 0 ;
+ return;
+ case EVT_AL:
+ set_state(i2c_imx, STATE_IDLE);
+ break;
+ case EVT_START:
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+ atomic_set(&i2c_imx->last_error, -EBUSY);
+ break;
+ case EVT_STOP:
+ break;
+ case EVT_SI:
+ i2c_imx_slave_process_irq(i2c_imx);
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ if ((reg & I2SR_IBB) == 0) {
+ data = 0;
+ set_state(i2c_imx, STATE_IDLE);
+ dev_dbg(&i2c_imx->adapter.dev, "end of package");
+ i2c_slave_event(i2c_imx->slave, I2C_SLAVE_STOP, &data);
+ }
+ if (i2c_imx->slave_poll_cnt > 10 )
+ {
+ dev_err(&i2c_imx->adapter.dev,"Too much slave loops (%i)\n",i2c_imx->slave_poll_cnt);
+ }
+
+ i2c_imx->slave_poll_cnt = 0 ;
+ break;
+ case EVT_POLL:
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ if ((reg & I2SR_IBB) == 0) {
+ data = 0;
+ set_state(i2c_imx, STATE_IDLE);
+ dev_dbg(&i2c_imx->adapter.dev, "end of package");
+ i2c_slave_event(i2c_imx->slave, I2C_SLAVE_STOP, &data);
+ if (i2c_imx->slave_poll_cnt > 10 )
+ {
+ dev_err(&i2c_imx->adapter.dev,"Too much slave loops (%i)\n",i2c_imx->slave_poll_cnt);
+ }
+
+ i2c_imx->slave_poll_cnt = 0 ;
+ }
+
+ /*TODO: do "dummy read" if no interrupts or stop condition for more then 10 wait loops*/
+ i2c_imx->slave_poll_cnt += 1 ;
+ if (i2c_imx->slave_poll_cnt % 10 == 0)
+ {
+ imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ wait_event_interruptible_timeout(i2c_imx->state_queue,(evt_is_empty(&i2c_imx->events) == 0), 1);
+}
+
+static void sp_evt_handler (struct imx_i2c_struct *i2c_imx, unsigned int event)
+{
+ u8 reg;
+
+ switch (event) {
+ case EVT_AL:
+ dev_dbg(&i2c_imx->adapter.dev, "Lost arbitration on START");
+ atomic_set(&i2c_imx->last_error, -EAGAIN);
+ set_state(i2c_imx, STATE_IDLE);
+ return;
+ case EVT_SI:
+ set_state(i2c_imx, STATE_IDLE);
+ evt_put(&i2c_imx->events, EVT_SI );
+ case EVT_START:
+ atomic_set(&i2c_imx->last_error, -EBUSY);
+ case EVT_STOP:
+ break;
+
+ case EVT_ENTRY:
+ return;
+ case EVT_POLL:
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ if ((reg & I2SR_IBB) && !(reg & I2SR_IAL)) {
+
+ set_state(i2c_imx, STATE_MASTER);
+ reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+
+ i2c_imx->stopped = 0;
+ reg |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK;
+ reg &= ~I2CR_DMAEN;
+ imx_i2c_write_reg(reg, i2c_imx, IMX_I2C_I2CR);
+ atomic_set(&i2c_imx->last_error,0);
+ i2c_imx->start_retry_cnt = 0 ;
+ return;
+ }
+ default:
+ break;
+
+ }
+ if (i2c_imx->start_retry_cnt ++ < 100)
+ {
+ wait_event_interruptible_timeout(i2c_imx->state_queue,(evt_is_empty(&i2c_imx->events) == 0),1);
+ dev_dbg(&i2c_imx->adapter.dev, "wait for busy cnt = %i evt = %i", i2c_imx->start_retry_cnt,event);
+ } else {
+
+ dev_dbg(&i2c_imx->adapter.dev, "start timeout");
+ i2c_imx->start_retry_cnt = 0 ;
+ atomic_set(&i2c_imx->last_error,-ETIMEDOUT);
+ set_state(i2c_imx, STATE_IDLE);
+ wait_event_interruptible_timeout(i2c_imx->state_queue,(evt_is_empty(&i2c_imx->events) == 0), HZ / 10);
+ }
+}
+
+static void set_state (struct imx_i2c_struct *i2c_imx, unsigned int new)
+{
+ i2c_imx->state = new;
+ switch (new) {
+ case STATE_IDLE:
+ idle_evt_handler(i2c_imx, EVT_ENTRY);
+ break;
+ case STATE_SLAVE:
+ slave_evt_handler(i2c_imx, EVT_ENTRY);
+ break;
+ case STATE_SP:
+ sp_evt_handler(i2c_imx, EVT_ENTRY);
+ break;
+ case STATE_MASTER:
+ master_evt_handler(i2c_imx, EVT_ENTRY);
+ break;
+ }
+}
+
+
+static int i2c_imx_slave_threadfn(void *pdata)
+{
+ unsigned int event;
+ struct imx_i2c_struct *i2c_imx = (struct imx_i2c_struct *) pdata;
+
+ do {
+
+ event = evt_get(&i2c_imx->events);
+ if (event == EVT_INVALID)
+ event = EVT_POLL;
+
+ switch (i2c_imx->state) {
+ case STATE_IDLE:
+ idle_evt_handler(i2c_imx, event);
+ break;
+ case STATE_SLAVE:
+ slave_evt_handler(i2c_imx, event);
+ break;
+ case STATE_SP:
+ sp_evt_handler(i2c_imx, event);
+ break;
+ case STATE_MASTER:
+ master_evt_handler(i2c_imx, event);
+ break;
+ default:
+ break;
+
+ }
+
+ } while (kthread_should_stop() == 0);
+
+ return 0;
}
static irqreturn_t i2c_imx_isr(int irq, void *dev_id)
{
struct imx_i2c_struct *i2c_imx = dev_id;
- unsigned int temp;
+ unsigned int status, ctl;
+
+ status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
+ ctl = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
+ if (status & I2SR_IIF) {
+ i2c_imx_clear_isr_bit(i2c_imx, status);
+
+ if (ctl & I2CR_MSTA) {
+ dev_dbg(&i2c_imx->adapter.dev, "master interrupt");
+ i2c_imx->master_interrupt = 1;
+ wake_up(&i2c_imx->queue);
+ } else if (status & I2SR_IAL) {
+ evt_put(&i2c_imx->events, EVT_AL );
+ } else {
+ dev_dbg(&i2c_imx->adapter.dev, "slave interrupt");
+ evt_put(&i2c_imx->events, EVT_SI );
+ wake_up(&i2c_imx->state_queue);
+ }
- temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
- if (temp & I2SR_IIF) {
- /* save status register */
- i2c_imx->i2csr = temp;
- temp &= ~I2SR_IIF;
- temp |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IIF);
- imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
- wake_up(&i2c_imx->queue);
return IRQ_HANDLED;
}
@@ -895,7 +1444,13 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
/* Start I2C transfer */
result = i2c_imx_start(i2c_imx);
- if (result) {
+ if (result == -ETIMEDOUT) {
+ /*
+ Recovery is not started on arbitration lost, since it can break
+ slave transfer. But in case of "bus timeout" recovery it
+ could be useful to bring controller out of "strange state".
+ */
+ dev_dbg(&i2c_imx->adapter.dev, "call bus recovery");
if (i2c_imx->adapter.bus_recovery_info) {
i2c_recover_bus(&i2c_imx->adapter);
result = i2c_imx_start(i2c_imx);
@@ -1024,6 +1579,60 @@ static int i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx,
rinfo->unprepare_recovery = i2c_imx_unprepare_recovery;
rinfo->recover_bus = i2c_generic_gpio_recovery;
i2c_imx->adapter.bus_recovery_info = rinfo;
+}
+
+static int i2c_imx_reg_slave(struct i2c_client *slave)
+{
+ struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(slave->adapter);
+ int result;
+ struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+
+ if (i2c_imx->slave)
+ return -EBUSY;
+
+ if (slave->flags & I2C_CLIENT_TEN)
+ return -EAFNOSUPPORT;
+
+ i2c_imx->slave = slave;
+
+ /* Set the Slave address */
+ imx_i2c_write_reg((i2c_imx->slave->addr << 1), i2c_imx, IMX_I2C_IADR);
+
+ result = i2c_imx_hw_start(i2c_imx);
+ if (result != 0)
+ return result;
+
+ i2c_imx->slave_task = kthread_run(i2c_imx_slave_threadfn,
+ (void *) i2c_imx, "i2c-slave-%s", i2c_imx->adapter.name);
+
+ sched_setscheduler(i2c_imx->slave_task, SCHED_FIFO, ¶m);
+
+ if (IS_ERR(i2c_imx->slave_task))
+ return PTR_ERR(i2c_imx->slave_task);
+
+ i2c_imx_slave_init(i2c_imx);
+
+ return 0;
+
+}
+
+static int i2c_imx_unreg_slave(struct i2c_client *slave)
+{
+ struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(slave->adapter);
+
+ dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
+ if (i2c_imx->slave_task != NULL)
+ kthread_stop(i2c_imx->slave_task);
+
+ wake_up(&i2c_imx->state_queue);
+
+ i2c_imx->slave_task = NULL;
+
+ i2c_imx->slave = NULL;
+
+ i2c_imx_stop(i2c_imx);
return 0;
}
@@ -1037,6 +1646,8 @@ static u32 i2c_imx_func(struct i2c_adapter *adapter)
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
+ .reg_slave = i2c_imx_reg_slave,
+ .unreg_slave = i2c_imx_unreg_slave,
};
static int i2c_imx_probe(struct platform_device *pdev)
@@ -1096,6 +1707,12 @@ static int i2c_imx_probe(struct platform_device *pdev)
return ret;
}
+ i2c_imx->pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (IS_ERR(i2c_imx->pinctrl)) {
+ ret = PTR_ERR(i2c_imx->pinctrl);
+ goto clk_disable;
+ }
+
/* Request IRQ */
ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, 0,
pdev->name, i2c_imx);
@@ -1106,6 +1723,7 @@ static int i2c_imx_probe(struct platform_device *pdev)
/* Init queue */
init_waitqueue_head(&i2c_imx->queue);
+ init_waitqueue_head(&i2c_imx->state_queue);
/* Set up adapter data */
i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
@@ -1157,6 +1775,9 @@ static int i2c_imx_probe(struct platform_device *pdev)
/* Init DMA config if supported */
i2c_imx_dma_request(i2c_imx, phy_addr);
+ /* init slave_state to IDLE */
+ i2c_imx->state = STATE_IDLE;
+ evt_init(&i2c_imx->events);
return 0; /* Return OK */
rpm_disable:
@@ -1183,6 +1804,9 @@ static int i2c_imx_remove(struct platform_device *pdev)
dev_dbg(&i2c_imx->adapter.dev, "adapter removed\n");
i2c_del_adapter(&i2c_imx->adapter);
+ if (i2c_imx->slave_task != NULL)
+ kthread_stop(i2c_imx->slave_task);
+
if (i2c_imx->dma)
i2c_imx_dma_free(i2c_imx);
--
2.1.4
^ permalink raw reply related
* Re: [RFC PATCH 0/5] add support for DMO embedded controller
From: Zahari Doychev @ 2016-10-27 15:54 UTC (permalink / raw)
To: Greg KH
Cc: Zahari Doychev, linux-kernel, lee.jones, wsa, linus.walleij, wim,
linux, linux-i2c, linux-gpio, gnurou, linux-watchdog
In-Reply-To: <20161027150151.GA19519@kroah.com>
On Thu, Oct 27, 2016 at 05:01:51PM +0200, Greg KH wrote:
> On Thu, Oct 27, 2016 at 12:47:11PM +0200, Zahari Doychev wrote:
> > This patch series adds support for the Data Modul Embedded Controller(dmec)
> > which is implemented within an on board FPGA found on Data Modul embedded
> > CPU modules.
> >
> > The dmec is connected to the host through the LPC bus and its registers are
> > mapped into the host I/O space. The controller supports two addressing modes:
> > linear and indexed.
> >
> > The driver adds support for the following functionality:
> >
> > - i2c
> > - gpio
> > - watchdog
> > - running time meter (rtm)
>
> Why do you want these in staging? I need a TODO file listing what needs
> to be done to the code to get it out of staging, and for someone to
> agree to work toward that goal.
Actually there is no reason to have them in staging. I was not sure what was
the right way to go. I just wanted to get some feedback. The drivers have been
through a lot of testing so next time I will resend the patches putting
each driver in its subsystem.
Regards Zahari
>
> Please resend the series with that information.
>
> thanks,
>
> greg k-h
^ permalink raw reply
* Re: [RFC PATCH 0/5] add support for DMO embedded controller
From: Greg KH @ 2016-10-27 15:01 UTC (permalink / raw)
To: Zahari Doychev
Cc: linux-kernel, lee.jones, wsa, linus.walleij, wim, linux,
linux-i2c, linux-gpio, gnurou, linux-watchdog
In-Reply-To: <cover.42dd8126f6e33b5130d9afa4596d88a106c54188.1477564996.git-series.zahari.doychev@linux.com>
On Thu, Oct 27, 2016 at 12:47:11PM +0200, Zahari Doychev wrote:
> This patch series adds support for the Data Modul Embedded Controller(dmec)
> which is implemented within an on board FPGA found on Data Modul embedded
> CPU modules.
>
> The dmec is connected to the host through the LPC bus and its registers are
> mapped into the host I/O space. The controller supports two addressing modes:
> linear and indexed.
>
> The driver adds support for the following functionality:
>
> - i2c
> - gpio
> - watchdog
> - running time meter (rtm)
Why do you want these in staging? I need a TODO file listing what needs
to be done to the code to get it out of staging, and for someone to
agree to work toward that goal.
Please resend the series with that information.
thanks,
greg k-h
^ permalink raw reply
* [PULL REQUEST] i2c for 4.9
From: Wolfram Sang @ 2016-10-27 13:08 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-i2c, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 2138 bytes --]
Linus,
I2C has some driver bugfixes, module autoload fixes, and driver
enablement on some architectures for you.
Please pull.
Thanks,
Wolfram
The following changes since commit 07d9a380680d1c0eb51ef87ff2eab5c994949e69:
Linux 4.9-rc2 (2016-10-23 17:10:14 -0700)
are available in the git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux.git i2c/for-current
for you to fetch changes up to 533169d164c6b4c8571d0d48779f6ff6be593d72:
i2c: imx: defer probe if bus recovery GPIOs are not ready (2016-10-25 12:15:00 +0200)
----------------------------------------------------------------
David Wu (1):
i2c: rk3x: Give the tuning value 0 during rk3x_i2c_v0_calc_timings
Greg Ungerer (1):
i2c: allow configuration of imx driver for ColdFire architecture
Hoan Tran (1):
i2c: xgene: Avoid dma_buffer overrun
Jarkko Nikula (1):
i2c: designware: Avoid aborted transfers with fast reacting I2C slaves
Javier Martinez Canillas (4):
i2c: jz4780: Fix module autoload
i2c: xlp9xx: Fix module autoload
i2c: xlr: Fix module autoload for OF registration
i2c: digicolor: Fix module autoload
Jean Delvare (1):
i2c: i801: Fix I2C Block Read on 8-Series/C220 and later
Ralf Ramsauer (1):
i2c: mark device nodes only in case of successful instantiation
Ruqiang Ju (1):
i2c: hix5hd2: allow build with ARCH_HISI
Stefan Agner (1):
i2c: imx: defer probe if bus recovery GPIOs are not ready
drivers/i2c/busses/Kconfig | 12 ++++++------
drivers/i2c/busses/i2c-designware-core.c | 17 ++++++++++++++---
drivers/i2c/busses/i2c-digicolor.c | 1 +
drivers/i2c/busses/i2c-i801.c | 16 +++++++++++++---
drivers/i2c/busses/i2c-imx.c | 11 +++++++----
drivers/i2c/busses/i2c-jz4780.c | 1 +
drivers/i2c/busses/i2c-rk3x.c | 2 ++
drivers/i2c/busses/i2c-xgene-slimpro.c | 2 +-
drivers/i2c/busses/i2c-xlp9xx.c | 1 +
drivers/i2c/busses/i2c-xlr.c | 1 +
drivers/i2c/i2c-core.c | 11 ++++++++++-
11 files changed, 57 insertions(+), 18 deletions(-)
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* [RFC PATCH 5/5] rtm-dmec: running time meter support
From: Zahari Doychev @ 2016-10-27 10:47 UTC (permalink / raw)
To: linux-kernel, gregkh, lee.jones, wsa, linus.walleij, wim, linux
Cc: linux-i2c, linux-gpio, gnurou, linux-watchdog, Zahari Doychev
In-Reply-To: <cover.42dd8126f6e33b5130d9afa4596d88a106c54188.1477564996.git-series.zahari.doychev@linux.com>
This is support for the running time meter(RTM) found on the Data Modul
embedded boards.
Signed-off-by: Zahari Doychev <zahari.doychev@linux.com>
---
drivers/staging/dmec/Kconfig | 10 ++-
drivers/staging/dmec/Makefile | 1 +-
drivers/staging/dmec/rtm-dmec.c | 203 +++++++++++++++++++++++++++++++++-
3 files changed, 214 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/dmec/rtm-dmec.c
diff --git a/drivers/staging/dmec/Kconfig b/drivers/staging/dmec/Kconfig
index eddf0bb..f6f4146 100644
--- a/drivers/staging/dmec/Kconfig
+++ b/drivers/staging/dmec/Kconfig
@@ -38,3 +38,13 @@ config WDT_DMEC
To compile this driver as a module, say M here: the module will be
called wdt-dmec
+
+config RTM_DMEC
+ tristate "Data Modul RTM"
+ depends on MFD_DMEC
+ help
+ Say Y to enable support for a running time meter(RTM) on a Data Modul
+ embedded controllers.
+
+ To compile this driver as a module, say M here: the module will be
+ called wdt-dmec
diff --git a/drivers/staging/dmec/Makefile b/drivers/staging/dmec/Makefile
index 8b363cc..b55d56f 100644
--- a/drivers/staging/dmec/Makefile
+++ b/drivers/staging/dmec/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_MFD_DMEC) += dmec-core.o
obj-$(CONFIG_I2C_DMEC) += i2c-dmec.o
obj-$(CONFIG_GPIO_DMEC) += gpio-dmec.o
obj-$(CONFIG_WDT_DMEC) += wdt-dmec.o
+obj-$(CONFIG_RTM_DMEC) += rtm-dmec.o
diff --git a/drivers/staging/dmec/rtm-dmec.c b/drivers/staging/dmec/rtm-dmec.c
new file mode 100644
index 0000000..cd32536
--- /dev/null
+++ b/drivers/staging/dmec/rtm-dmec.c
@@ -0,0 +1,203 @@
+/*
+ * Running time meter for Data Modul AG Embedded Controller
+ *
+ * Copyright (C) 2016 Data Modul AG
+ *
+ * Author: Sebastian Wezel <sebastian@torvus-musica.de>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mutex.h>
+
+#include "dmec.h"
+
+#define DMEC_RTM_RTM_OFFSET 0x60
+#define DMEC_RTM_RTMREV_OFFSET 0x63
+#define DMEC_RTM_OOSRTM_OFFSET 0x64
+#define DMEC_RTM_BTCNT_OFFSET 0x68
+#define DMEC_RTM_BBCTNT_OFFSET 0x6c
+
+static DEFINE_MUTEX(rtm_lock);
+static struct regmap *regmap;
+static bool enable_reset;
+
+static unsigned int dmec_rtm_get_three_byte(unsigned int reg,
+ struct device *dev)
+{
+ unsigned int low = 0, mid = 0, high = 0, val = 0;
+
+ regmap_read(regmap, reg, &low);
+ regmap_read(regmap, reg + 1, &mid);
+ regmap_read(regmap, reg + 2, &high);
+
+ val = ((high << 16) | (mid << 8) | (low & 0x0000ff));
+ return val;
+}
+
+static ssize_t dmec_rtm_time_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int val = 0;
+
+ val = dmec_rtm_get_three_byte(DMEC_RTM_RTM_OFFSET, dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t dmec_rtm_time_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ unsigned int low = 0, mid = 0, high = 0, higher = 0;
+ long val = 0, ret = -EPERM;
+ int reg = DMEC_RTM_RTM_OFFSET;
+
+ mutex_lock(&rtm_lock);
+ if (enable_reset && !kstrtol(buf, 10, &val)) {
+ higher = (val >> 24) & 0xff;
+ high = (val >> 16) & 0xff;
+ mid = (val >> 8) & 0xff;
+ low = val & 0xff;
+
+ regmap_write(regmap, reg, low);
+ regmap_write(regmap, reg + 1, mid);
+ regmap_write(regmap, reg + 2, high);
+ regmap_write(regmap, reg + 3, higher);
+ enable_reset = false;
+ ret = count;
+ }
+ mutex_unlock(&rtm_lock);
+
+ return ret;
+}
+
+static ssize_t dmec_rtm_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int val = 0;
+
+ regmap_read(regmap, DMEC_RTM_RTMREV_OFFSET, &val);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t dmec_rtm_out_of_spec_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int val = 0;
+
+ val = dmec_rtm_get_three_byte(DMEC_RTM_OOSRTM_OFFSET, dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t dmec_rtm_boot_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int val = 0;
+
+ val = dmec_rtm_get_three_byte(DMEC_RTM_BTCNT_OFFSET, dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t dmec_rtm_bios_boot_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int val = 0;
+
+ val = dmec_rtm_get_three_byte(DMEC_RTM_BBCTNT_OFFSET, dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t dmec_rtm_enable_reset_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%d\n", enable_reset);
+}
+
+static ssize_t dmec_rtm_enable_reset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ long val = 0;
+
+ mutex_lock(&rtm_lock);
+ if (kstrtol(buf, 10, &val) != 0)
+ enable_reset = false;
+
+ /* The enable should only work if the value is 1 */
+ if (val == 1)
+ enable_reset = true;
+ mutex_unlock(&rtm_lock);
+ return count;
+}
+
+static DEVICE_ATTR(rtm_time, 0664, dmec_rtm_time_show, dmec_rtm_time_store);
+static DEVICE_ATTR(rtm_version, 0444, dmec_rtm_version_show, NULL);
+static DEVICE_ATTR(rtm_out_of_spec, 0444, dmec_rtm_out_of_spec_show, NULL);
+static DEVICE_ATTR(rtm_boot_count, 0444, dmec_rtm_boot_count_show, NULL);
+static DEVICE_ATTR(rtm_bios_boot_count, 0444, dmec_rtm_bios_boot_count_show,
+ NULL);
+static DEVICE_ATTR(rtm_enable_reset, 0664, dmec_rtm_enable_reset_show,
+ dmec_rtm_enable_reset_store);
+
+static struct attribute *rtm_attribute[] = {
+ &dev_attr_rtm_time.attr,
+ &dev_attr_rtm_version.attr,
+ &dev_attr_rtm_out_of_spec.attr,
+ &dev_attr_rtm_boot_count.attr,
+ &dev_attr_rtm_bios_boot_count.attr,
+ &dev_attr_rtm_enable_reset.attr,
+ NULL
+};
+
+static const struct attribute_group rtm_attr_group = {
+ .attrs = rtm_attribute,
+};
+
+static int dmec_rtm_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ regmap = dmec_get_regmap(pdev->dev.parent);
+ enable_reset = 0;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &rtm_attr_group);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int dmec_rtm_remove(struct platform_device *pdev)
+{
+ sysfs_remove_group(&pdev->dev.kobj, &rtm_attr_group);
+
+ return 0;
+}
+
+static struct platform_driver dmec_rtm_driver = {
+ .driver = {
+ .name = "dmec-rtm",
+ .owner = THIS_MODULE,
+ },
+ .probe = dmec_rtm_probe,
+ .remove = dmec_rtm_remove,
+};
+
+module_platform_driver(dmec_rtm_driver);
+
+MODULE_DESCRIPTION("dmec rtm driver");
+MODULE_AUTHOR("Sebastian Wezel <sebastian@torvus-musica.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-rtm");
--
git-series 0.8.10
^ permalink raw reply related
* Re: [PATCH] i2c: rk3x: Give the tuning value 0 during rk3x_i2c_v0_calc_timings
From: Wolfram Sang @ 2016-10-27 13:11 UTC (permalink / raw)
To: David Wu
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, heiko-4mtYJXux2i+zQB+pC5nmwQ,
dianders-F7+t8E8rja9g9hUCZPvPmw,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1477125822-30644-1-git-send-email-david.wu-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
[-- Attachment #1.1: Type: text/plain, Size: 600 bytes --]
On Sat, Oct 22, 2016 at 04:43:42PM +0800, David Wu wrote:
> We found a bug that i2c transfer sometimes failed on 3066a board with
> stabel-4.8, the con register would be updated by uninitialized tuning
> value, it made the i2c transfer failed.
>
> So give the tuning value to be zero during rk3x_i2c_v0_calc_timings.
>
> Signed-off-by: David Wu <david.wu-TNX95d0MmH7DzftRWevZcw@public.gmane.org>
What I missed to say in my review:
Please use a subject line that describes WHY the change is needed not so
much WHAT is done. Like: "fix missing initialization causing boot
problems"
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
[-- Attachment #2: Type: text/plain, Size: 200 bytes --]
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply
* [RFC PATCH 4/5] wdt-dmec: watchdog support for dmec
From: Zahari Doychev @ 2016-10-27 10:47 UTC (permalink / raw)
To: linux-kernel, gregkh, lee.jones, wsa, linus.walleij, wim, linux
Cc: linux-i2c, linux-gpio, gnurou, linux-watchdog, Zahari Doychev
In-Reply-To: <cover.42dd8126f6e33b5130d9afa4596d88a106c54188.1477564996.git-series.zahari.doychev@linux.com>
This is support for the watchdog found on the Data Modul embedded boards.
Signed-off-by: Zahari Doychev <zahari.doychev@linux.com>
---
drivers/staging/dmec/Kconfig | 11 +-
drivers/staging/dmec/Makefile | 1 +-
drivers/staging/dmec/wdt-dmec.c | 569 +++++++++++++++++++++++++++++++++-
3 files changed, 581 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/dmec/wdt-dmec.c
diff --git a/drivers/staging/dmec/Kconfig b/drivers/staging/dmec/Kconfig
index 9c4a8e5..eddf0bb 100644
--- a/drivers/staging/dmec/Kconfig
+++ b/drivers/staging/dmec/Kconfig
@@ -27,3 +27,14 @@ config GPIO_DMEC
To compile this driver as a module, say M here: the module will be
called gpio-dmec
+
+config WDT_DMEC
+ tristate "Data Modul Watchdog"
+ depends on MFD_DMEC
+ select WATCHDOG_CORE
+ help
+ Say Y to enable support for a watchdog on a Data Modul embedded
+ controllers.
+
+ To compile this driver as a module, say M here: the module will be
+ called wdt-dmec
diff --git a/drivers/staging/dmec/Makefile b/drivers/staging/dmec/Makefile
index b71b27b..8b363cc 100644
--- a/drivers/staging/dmec/Makefile
+++ b/drivers/staging/dmec/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_MFD_DMEC) += dmec-core.o
obj-$(CONFIG_I2C_DMEC) += i2c-dmec.o
obj-$(CONFIG_GPIO_DMEC) += gpio-dmec.o
+obj-$(CONFIG_WDT_DMEC) += wdt-dmec.o
diff --git a/drivers/staging/dmec/wdt-dmec.c b/drivers/staging/dmec/wdt-dmec.c
new file mode 100644
index 0000000..714ed11
--- /dev/null
+++ b/drivers/staging/dmec/wdt-dmec.c
@@ -0,0 +1,569 @@
+/*
+ * Watchdog driver for Data Modul AG Embedded Controller
+ *
+ * Copyright (C) 2016 Zahari Doychev, Data Modul AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/bitops.h>
+
+#include "dmec.h"
+
+#define DMEC_WDT_VER 0x30
+#define DMEC_WDT_SRV 0x30
+#define DMEC_WDT_CFG 0x31
+#define DMEC_WDT_S0CFG 0x32
+#define DMEC_WDT_S0MOD0 0x33
+#define DMEC_WDT_S0MOD1 0x34
+#define DMEC_WDT_S1CFG (DMEC_WDT_S0CFG + 3)
+#define DMEC_WDT_S1MOD0 (DMEC_WDT_S0MOD0 + 3)
+#define DMEC_WDT_S1MOD1 (DMEC_WDT_S0MOD1 + 3)
+#define DMEC_WDT_S2CFG (DMEC_WDT_S1CFG + 3)
+#define DMEC_WDT_S2MOD0 (DMEC_WDT_S1MOD0 + 3)
+#define DMEC_WDT_S2MOD1 (DMEC_WDT_S1MOD1 + 3)
+
+#define DMEC_WDT_EN BIT(0)
+#define DMEC_WDT_LOCK BIT(1)
+#define DMEC_WDT_WIN_MODE BIT(2)
+#define DMEC_WDT_AL BIT(3)
+
+#define DMEC_WDT_PRESCALER BIT(4)
+#define DMEC_WDT_WDTEN BIT(3)
+#define DMEC_WDT_WDSTS BIT(5)
+
+#define DMEC_WDT_TIMEOUT_MIN 1 /* s */
+#define DMEC_WDT_TIMEOUT_MAX (2 * 3600) /* s */
+
+#define DMEC_WDT_TIME_MAX (65 * 1000)
+
+/* S0 used only during boot */
+#define DEFAULT_S0_TIMEOUT 0
+#define DEFAULT_S1_TIMEOUT 3
+#define DEFAULT_S2_TIMEOUT 5
+
+enum wdt_actions {
+ WDT_DISABLE = 0,
+ WDT_DELAY,
+ WDT_RESET,
+ WDT_SYSIRQ0,
+ WDT_SYSIRQ1,
+ WDT_SYSIRQ2,
+ WDT_IRQ,
+ WDT_RESERVED
+};
+
+enum wdt_stages {
+ S0,
+ S1,
+ S2
+};
+
+static unsigned int s1_timeout = DEFAULT_S1_TIMEOUT;
+module_param(s1_timeout, uint, 0644);
+MODULE_PARM_DESC(s1_timeout,
+ "Watchdog stage 1 timeout in [s], default=3");
+
+static unsigned int s2_timeout = DEFAULT_S2_TIMEOUT;
+module_param(s2_timeout, uint, 0644);
+MODULE_PARM_DESC(s2_timeout,
+ "Watchdog stage 2 timeout in [s], default=5");
+
+static enum wdt_actions action = WDT_DELAY;
+module_param(action, uint, 0644);
+MODULE_PARM_DESC(action,
+ "Watchdog action for stage 1, default=1 (delay)");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0644);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static bool win_mode;
+module_param(win_mode, bool, 0644);
+MODULE_PARM_DESC(win_mode, "Use watchdog window mode, default=0");
+
+static bool stop_on_reboot;
+module_param(stop_on_reboot, bool, 0644);
+MODULE_PARM_DESC(stop_on_reboot, "stop watchdog on system reboot, default=0");
+
+struct dmec_wdt_data {
+ struct watchdog_device wdd;
+ struct regmap *regmap;
+ unsigned int s1_time;
+ unsigned int s2_time;
+ unsigned int status;
+ unsigned int boot_cfg;
+ bool boot_mode;
+};
+
+static int dmec_wdt_set_stage_action(struct dmec_wdt_data *wdat,
+ unsigned int stage,
+ enum wdt_actions action)
+{
+ unsigned int offset = DMEC_WDT_S0CFG + (3 * stage);
+ unsigned int val;
+
+ regmap_read(wdat->regmap, offset, &val);
+ val &= ~DMEC_WDT_WDSTS;
+ val |= action | DMEC_WDT_WDTEN;
+ regmap_write(wdat->regmap, offset, val);
+
+ return 0;
+}
+
+static int dmec_wdt_clear_action(struct dmec_wdt_data *wdat, int stage)
+{
+ unsigned int val;
+
+ regmap_read(wdat->regmap, DMEC_WDT_S0CFG + 3 * stage, &val);
+ val |= DMEC_WDT_WDSTS;
+ val &= ~0xf;
+ regmap_write(wdat->regmap, DMEC_WDT_S0CFG + 3 * stage, val);
+
+ return 0;
+}
+
+static unsigned int dmec_wdt_get_stage_timeout(struct dmec_wdt_data *wdat,
+ unsigned int stage)
+{
+ unsigned int val, timeout = 0, cfg;
+
+ regmap_read(wdat->regmap, DMEC_WDT_S0CFG + 3 * stage, &cfg);
+ regmap_read(wdat->regmap, DMEC_WDT_S0MOD0 + 3 * stage, &val);
+ timeout = val;
+ regmap_read(wdat->regmap, DMEC_WDT_S0MOD1 + 3 * stage, &val);
+ timeout |= (val << 8);
+
+ if (cfg & DMEC_WDT_PRESCALER)
+ timeout <<= 7;
+
+ return timeout / 1000;
+}
+
+static int dmec_wdt_set_stage_timeout(struct dmec_wdt_data *wdat,
+ unsigned int stage,
+ unsigned int timeout)
+{
+ unsigned int val;
+
+ timeout *= 1000;
+ if (timeout > DMEC_WDT_TIME_MAX) {
+ /* enable prescaler */
+ regmap_read(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), &val);
+ val |= DMEC_WDT_PRESCALER;
+ regmap_write(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), val);
+ timeout >>= 7;
+ } else {
+ regmap_read(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), &val);
+ val &= ~DMEC_WDT_PRESCALER;
+ regmap_write(wdat->regmap, DMEC_WDT_S0CFG + (3 * stage), val);
+ }
+
+ val = timeout & 0xff;
+ regmap_write(wdat->regmap, DMEC_WDT_S0MOD0 + (3 * stage), val);
+ val = (timeout >> 8) & 0xff;
+ regmap_write(wdat->regmap, DMEC_WDT_S0MOD1 + (3 * stage), val);
+
+ return 0;
+}
+
+static int dmec_wdt_set_timeouts(struct dmec_wdt_data *wdat)
+{
+ dmec_wdt_clear_action(wdat, S0);
+ dmec_wdt_clear_action(wdat, S1);
+ dmec_wdt_clear_action(wdat, S2);
+
+ wdat->s1_time = s1_timeout;
+ wdat->s2_time = s2_timeout;
+ dmec_wdt_set_stage_timeout(wdat, S1, s1_timeout);
+ dmec_wdt_set_stage_timeout(wdat, S2, s2_timeout);
+ dmec_wdt_set_stage_action(wdat, S2, WDT_RESET);
+
+ return 0;
+}
+
+static int dmec_wdt_stop(struct watchdog_device *wdd)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+ regmap_update_bits(wdat->regmap, DMEC_WDT_CFG, DMEC_WDT_EN, 0);
+
+ return 0;
+}
+
+static int dmec_wdt_start(struct watchdog_device *wdd)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+ regmap_update_bits(wdat->regmap, DMEC_WDT_CFG,
+ DMEC_WDT_EN, DMEC_WDT_EN);
+
+ wdat->boot_mode = false;
+
+ return 0;
+}
+
+static int dmec_wdt_ping(struct watchdog_device *wdd)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+ unsigned int val;
+
+ /* We should try avoiding resets in window mode until the watchdog
+ * daemon takes control
+ */
+ if (win_mode && wdat->boot_mode) {
+ regmap_read(wdat->regmap, DMEC_WDT_S1CFG, &val);
+ if (!(val & DMEC_WDT_WDSTS))
+ return 0;
+ val |= DMEC_WDT_WDSTS;
+ regmap_write(wdat->regmap, DMEC_WDT_S1CFG, val);
+ }
+
+ regmap_write(wdat->regmap, DMEC_WDT_SRV, 0xff);
+
+ return 0;
+}
+
+static int dmec_wdt_set_win_mode(struct watchdog_device *wdd)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+ regmap_update_bits(wdat->regmap, DMEC_WDT_CFG,
+ DMEC_WDT_WIN_MODE, DMEC_WDT_WIN_MODE);
+
+ return 0;
+}
+
+static int dmec_wdt_set_std_mode(struct watchdog_device *wdd)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+ regmap_update_bits(wdat->regmap, DMEC_WDT_CFG, DMEC_WDT_WIN_MODE, 0);
+
+ return 0;
+}
+
+static int dmec_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+ wdd->timeout = timeout;
+ wdat->boot_mode = false;
+ dmec_wdt_stop(wdd);
+ dmec_wdt_clear_action(wdat, S0);
+
+ if (win_mode) {
+ dmec_wdt_clear_action(wdat, S1);
+ dmec_wdt_set_stage_timeout(wdat, S1, wdat->s1_time);
+ dmec_wdt_set_stage_action(wdat, S1, WDT_DELAY);
+ dmec_wdt_set_win_mode(wdd);
+ } else {
+ dmec_wdt_set_std_mode(wdd);
+ }
+
+ wdat->s2_time = timeout;
+ dmec_wdt_clear_action(wdat, S2);
+ dmec_wdt_set_stage_timeout(wdat, S2, timeout);
+ dmec_wdt_set_stage_action(wdat, S2, WDT_RESET);
+ clear_bit(WDOG_HW_RUNNING, &wdd->status);
+ dmec_wdt_start(wdd);
+
+ return 0;
+}
+
+static long dmec_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
+ unsigned long arg)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+ void __user *argp = (void __user *)arg;
+ int ret = -ENOIOCTLCMD;
+ int __user *p = argp;
+ int val;
+
+ switch (cmd) {
+ case WDIOC_SETPRETIMEOUT:
+ if (get_user(val, p) && val < 0)
+ return -EFAULT;
+ dmec_wdt_clear_action(wdat, S1);
+ if (val > 0) {
+ dmec_wdt_set_stage_timeout(wdat, S1, val);
+ dmec_wdt_set_stage_action(wdat, S1, action);
+ }
+ wdat->s1_time = val;
+ wdat->boot_mode = false;
+ ret = 0;
+ if (!win_mode)
+ ret = dmec_wdt_ping(wdd);
+ break;
+ case WDIOC_GETPRETIMEOUT:
+ ret = put_user(wdat->s1_time, (int __user *)arg);
+ break;
+ }
+
+ return ret;
+}
+
+static unsigned int dmec_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+
+ return dmec_wdt_get_stage_timeout(wdat, 2);
+}
+
+static unsigned int dmec_wdt_status(struct watchdog_device *wdd)
+{
+ struct dmec_wdt_data *wdat = watchdog_get_drvdata(wdd);
+ unsigned int status;
+
+ regmap_read(wdat->regmap, DMEC_WDT_CFG, &status);
+
+ return status;
+}
+
+static struct watchdog_info dmec_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE | WDIOF_PRETIMEOUT,
+ .identity = "DMEC WDT",
+};
+
+static const struct watchdog_ops dmec_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = dmec_wdt_start,
+ .stop = dmec_wdt_stop,
+ .ping = dmec_wdt_ping,
+ .set_timeout = dmec_wdt_set_timeout,
+ .status = dmec_wdt_status,
+ .ioctl = dmec_wdt_ioctl,
+ .get_timeleft = dmec_wdt_get_timeleft,
+};
+
+static int dmec_wdt_get_hw_ping_time(struct dmec_wdt_data *wdat)
+{
+ struct watchdog_device *wdd = &wdat->wdd;
+ unsigned int val, t;
+
+ regmap_read(wdat->regmap, DMEC_WDT_S0CFG, &val);
+ t = dmec_wdt_get_stage_timeout(wdat, S0);
+ if (t && (val & 0x7) > 1)
+ dmec_wdt_clear_action(wdat, S0);
+
+ wdat->s1_time = dmec_wdt_get_stage_timeout(wdat, S1);
+ wdat->s2_time = dmec_wdt_get_stage_timeout(wdat, S2);
+
+ regmap_read(wdat->regmap, DMEC_WDT_S1CFG, &val);
+ if (wdat->s1_time && (val & 0x7) > 1 && !(val & DMEC_WDT_WDSTS)) {
+ wdd->timeout = wdat->s1_time;
+ return 1;
+ }
+
+ regmap_read(wdat->regmap, DMEC_WDT_S2CFG, &val);
+ if (wdat->s2_time && (val & 0x7) > 1 && !(val & DMEC_WDT_WDSTS)) {
+ wdd->timeout = wdat->s2_time;
+ return 2;
+ }
+
+ return -1;
+}
+
+static int dmec_wdt_config_win_mode(struct dmec_wdt_data *wdat)
+{
+ struct watchdog_device *wdd = &wdat->wdd;
+ unsigned int t;
+
+ /* The timeout should be ok until the watchdog daemon appears */
+ t = (min_t(unsigned int, wdat->s1_time, wdat->s2_time) / 2);
+ wdd->timeout = t;
+
+ return 0;
+}
+
+static int dmec_wdt_config_mode(struct dmec_wdt_data *wdat)
+{
+ struct watchdog_device *wdd = &wdat->wdd;
+
+ if (!(wdat->boot_cfg & DMEC_WDT_EN))
+ return -1;
+
+ if (dmec_wdt_get_hw_ping_time(wdat) < 0)
+ return -1;
+
+ wdd->max_hw_heartbeat_ms = DMEC_WDT_TIMEOUT_MAX * 1000;
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ if (wdat->boot_cfg & DMEC_WDT_WIN_MODE)
+ dmec_wdt_config_win_mode(wdat);
+
+ wdat->boot_mode = true;
+
+ return 0;
+}
+
+static int dmec_wdt_setup(struct dmec_wdt_data *wdat)
+{
+ struct watchdog_device *wdd = &wdat->wdd;
+ int ret = 0;
+
+ regmap_read(wdat->regmap, DMEC_WDT_CFG, &wdat->boot_cfg);
+
+ ret = dmec_wdt_config_mode(wdat);
+
+ if (ret < 0)
+ dmec_wdt_set_timeouts(wdat);
+
+ if (wdat->boot_cfg & DMEC_WDT_LOCK && !nowayout) {
+ dev_info(wdd->parent, "watchdog lock is enabled.\n");
+ nowayout = true;
+ return 0;
+ }
+
+ return 0;
+}
+
+static int dmec_wdt_probe(struct platform_device *pdev)
+{
+ struct dmec_wdt_data *wdat;
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ int ret = 0;
+
+ wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL);
+ if (!wdat)
+ return -ENOMEM;
+
+ wdat->regmap = dmec_get_regmap(pdev->dev.parent);
+ wdd = &wdat->wdd;
+ wdd->parent = dev;
+
+ wdd->info = &dmec_wdt_info;
+ wdd->ops = &dmec_wdt_ops;
+ wdd->min_timeout = DMEC_WDT_TIMEOUT_MIN;
+ wdd->max_timeout = DMEC_WDT_TIMEOUT_MAX;
+
+ wdat->s1_time = s1_timeout;
+ wdat->s2_time = s2_timeout;
+
+ watchdog_set_drvdata(wdd, wdat);
+ platform_set_drvdata(pdev, wdat);
+
+ dmec_wdt_setup(wdat);
+
+ watchdog_set_nowayout(wdd, nowayout);
+ if (stop_on_reboot)
+ watchdog_stop_on_reboot(wdd);
+
+ ret = watchdog_register_device(wdd);
+ if (ret)
+ return ret;
+
+ regmap_read(wdat->regmap, DMEC_WDT_VER,
+ &dmec_wdt_info.firmware_version);
+
+ dev_info(dev, "registered. v%u.%u sta: %#lx mode:%d\n",
+ (dmec_wdt_info.firmware_version >> 4) & 0xf,
+ dmec_wdt_info.firmware_version & 0xf,
+ wdd->status, win_mode);
+
+ return 0;
+}
+
+static int dmec_wdt_remove(struct platform_device *pdev)
+{
+ struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+
+ dmec_wdt_stop(&wdat->wdd);
+ watchdog_unregister_device(&wdat->wdd);
+
+ return 0;
+}
+
+static void dmec_wdt_shutdown(struct platform_device *pdev)
+{
+ struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+
+ dmec_wdt_stop(&wdat->wdd);
+}
+
+#ifdef CONFIG_PM
+/* Disable watchdog if it is active during suspend */
+static int dmec_wdt_suspend(struct platform_device *pdev,
+ pm_message_t message)
+{
+ struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+ struct watchdog_device *wdd = &wdat->wdd;
+
+ regmap_read(wdat->regmap, DMEC_WDT_CFG, &wdat->status);
+
+ if (wdat->status & DMEC_WDT_EN)
+ return dmec_wdt_stop(wdd);
+
+ return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int dmec_wdt_resume(struct platform_device *pdev)
+{
+ struct dmec_wdt_data *wdat = platform_get_drvdata(pdev);
+ struct watchdog_device *wdd = &wdat->wdd;
+
+ if (!win_mode && wdat->status & DMEC_WDT_EN) {
+ dmec_wdt_stop(wdd);
+ dmec_wdt_clear_action(wdat, S0);
+ dmec_wdt_clear_action(wdat, S1);
+ dmec_wdt_clear_action(wdat, S2);
+
+ if (wdat->s1_time) {
+ dmec_wdt_set_stage_timeout(wdat, S1, wdat->s1_time);
+ dmec_wdt_set_stage_action(wdat, S1, WDT_DELAY);
+ }
+ dmec_wdt_set_stage_timeout(wdat, S2, wdat->s2_time);
+ dmec_wdt_set_stage_action(wdat, S2, WDT_RESET);
+ dmec_wdt_get_timeleft(wdd);
+ return dmec_wdt_start(wdd);
+ }
+
+ return dmec_wdt_stop(wdd);
+}
+#else
+#define dmec_wdt_suspend NULL
+#define dmec_wdt_resume NULL
+#endif
+
+static struct platform_driver dmec_wdt_driver = {
+ .driver = {
+ .name = "dmec-wdt",
+ .owner = THIS_MODULE,
+ },
+ .probe = dmec_wdt_probe,
+ .remove = dmec_wdt_remove,
+ .shutdown = dmec_wdt_shutdown,
+ .suspend = dmec_wdt_suspend,
+ .resume = dmec_wdt_resume,
+};
+
+module_platform_driver(dmec_wdt_driver);
+
+MODULE_DESCRIPTION("dmec watchdog driver");
+MODULE_AUTHOR("Zahari Doychev <zahari.doychev@linux.com");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-wdt");
--
git-series 0.8.10
^ permalink raw reply related
* [RFC PATCH 3/5] gpio-dmec: gpio support for dmec
From: Zahari Doychev @ 2016-10-27 10:47 UTC (permalink / raw)
To: linux-kernel, gregkh, lee.jones, wsa, linus.walleij, wim, linux
Cc: linux-i2c, linux-gpio, gnurou, linux-watchdog, Zahari Doychev
In-Reply-To: <cover.42dd8126f6e33b5130d9afa4596d88a106c54188.1477564996.git-series.zahari.doychev@linux.com>
This is support for the gpio functionality found on the Data Modul embedded
controllers
Signed-off-by: Zahari Doychev <zahari.doychev@linux.com>
---
drivers/staging/dmec/Kconfig | 10 +-
drivers/staging/dmec/Makefile | 1 +-
drivers/staging/dmec/dmec.h | 5 +-
drivers/staging/dmec/gpio-dmec.c | 390 ++++++++++++++++++++++++++++++++-
4 files changed, 406 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/dmec/gpio-dmec.c
diff --git a/drivers/staging/dmec/Kconfig b/drivers/staging/dmec/Kconfig
index 0067b0b..9c4a8e5 100644
--- a/drivers/staging/dmec/Kconfig
+++ b/drivers/staging/dmec/Kconfig
@@ -17,3 +17,13 @@ config I2C_DMEC
To compile this driver as a module, say M here: the module will be
called i2c-dmec
+
+config GPIO_DMEC
+ tristate "Data Modul GPIO"
+ depends on MFD_DMEC && GPIOLIB
+ help
+ Say Y here to enable support for a GPIOs on a Data Module embedded
+ controller.
+
+ To compile this driver as a module, say M here: the module will be
+ called gpio-dmec
diff --git a/drivers/staging/dmec/Makefile b/drivers/staging/dmec/Makefile
index c51a37e..b71b27b 100644
--- a/drivers/staging/dmec/Makefile
+++ b/drivers/staging/dmec/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_MFD_DMEC) += dmec-core.o
obj-$(CONFIG_I2C_DMEC) += i2c-dmec.o
+obj-$(CONFIG_GPIO_DMEC) += gpio-dmec.o
diff --git a/drivers/staging/dmec/dmec.h b/drivers/staging/dmec/dmec.h
index 178937d..cc42926 100644
--- a/drivers/staging/dmec/dmec.h
+++ b/drivers/staging/dmec/dmec.h
@@ -1,6 +1,11 @@
#ifndef _LINUX_MFD_DMEC_H
#define _LINUX_MFD_DMEC_H
+struct dmec_gpio_platform_data {
+ int gpio_base;
+ int chip_num;
+};
+
struct dmec_i2c_platform_data {
u32 reg_shift; /* register offset shift value */
u32 reg_io_width; /* register io read/write width */
diff --git a/drivers/staging/dmec/gpio-dmec.c b/drivers/staging/dmec/gpio-dmec.c
new file mode 100644
index 0000000..4cefbbf
--- /dev/null
+++ b/drivers/staging/dmec/gpio-dmec.c
@@ -0,0 +1,390 @@
+/*
+ * GPIO driver for Data Modul AG Embedded Controller
+ *
+ * Copyright (C) 2016 Data Modul AG
+ *
+ * Authors: Zahari Doychev <zahari.doychev@linux.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/gpio.h>
+#include <linux/seq_file.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+
+#include "dmec.h"
+
+#define DMEC_GPIO_BANKS 2
+#define DMEC_GPIO_MAX_NUM 8
+#define DMEC_GPIO_BASE(x) (0x40 + 0x10 * ((x)->chip_num))
+#define DMEC_GPIO_SET_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x1)
+#define DMEC_GPIO_GET_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x1)
+#define DMEC_GPIO_CLR_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x2)
+#define DMEC_GPIO_VER_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x2)
+#define DMEC_GPIO_DIR_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x3)
+#define DMEC_GPIO_IRQTYPE_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x4)
+#define DMEC_GPIO_EVTSTA_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x6)
+#define DMEC_GPIO_IRQCFG_OFFSET(x) (DMEC_GPIO_BASE(x) + 0x8)
+#define DMEC_GPIO_NOPS_OFFSET(x) (DMEC_GPIO_BASE(x) + 0xa)
+#define DMEC_GPIO_IRQSTA_OFFSET(x) (DMEC_GPIO_BASE(x) + 0xb)
+
+#ifdef CONFIG_PM
+struct dmec_reg_ctx {
+ u32 dat;
+ u32 dir;
+ u32 imask;
+ u32 icfg[2];
+ u32 emask[2];
+};
+#endif
+
+struct dmec_gpio_priv {
+ struct regmap *regmap;
+ struct gpio_chip gpio_chip;
+ struct irq_chip irq_chip;
+ unsigned int chip_num;
+ unsigned int irq;
+ u8 ver;
+#ifdef CONFIG_PM
+ struct dmec_reg_ctx regs;
+#endif
+};
+
+static int dmec_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+ unsigned int val;
+
+ /* read get register */
+ regmap_read(regmap, DMEC_GPIO_GET_OFFSET(priv), &val);
+
+ return !!(val & BIT(offset));
+}
+
+static void dmec_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+
+ if (value)
+ regmap_write(regmap, DMEC_GPIO_SET_OFFSET(priv), BIT(offset));
+ else
+ regmap_write(regmap, DMEC_GPIO_CLR_OFFSET(priv), BIT(offset));
+}
+
+static int dmec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+
+ /* set pin as input */
+ regmap_update_bits(regmap, DMEC_GPIO_DIR_OFFSET(priv), BIT(offset), 0);
+
+ return 0;
+}
+
+static int dmec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+ unsigned int val = BIT(offset);
+
+ if (value)
+ regmap_write(regmap, DMEC_GPIO_SET_OFFSET(priv), val);
+ else
+ regmap_write(regmap, DMEC_GPIO_CLR_OFFSET(priv), val);
+
+ /* set pin as output */
+ regmap_update_bits(regmap, DMEC_GPIO_DIR_OFFSET(priv), val, val);
+
+ return 0;
+}
+
+static int dmec_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+ unsigned int val;
+
+ regmap_read(regmap, DMEC_GPIO_DIR_OFFSET(priv), &val);
+
+ return !(val & BIT(offset));
+}
+
+static int dmec_gpio_pincount(struct dmec_gpio_priv *priv)
+{
+ struct regmap *regmap = priv->regmap;
+ unsigned int val;
+
+ regmap_read(regmap, DMEC_GPIO_NOPS_OFFSET(priv), &val);
+
+ /* number of pins is val + 1 */
+ return val == 0xff ? 0 : (val & 7) + 1;
+}
+
+static int dmec_gpio_get_version(struct gpio_chip *gc)
+{
+ struct device *dev = gc->parent;
+ struct dmec_gpio_priv *p = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ unsigned int v;
+
+ regmap_read(p->regmap, DMEC_GPIO_VER_OFFSET(p), &v);
+ p->ver = v;
+ dev_info(dev, "chip%u v%u.%u\n", p->chip_num, (v >> 4) & 0xf, v & 0xf);
+
+ return 0;
+}
+
+static void dmec_gpio_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+ int offset, mask;
+
+ offset = DMEC_GPIO_IRQCFG_OFFSET(priv) + (d->hwirq >> 2);
+ mask = BIT((d->hwirq & 3) << 1);
+
+ regmap_update_bits(regmap, offset, mask, mask);
+}
+
+static void dmec_gpio_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+ int offset, mask;
+
+ offset = DMEC_GPIO_IRQCFG_OFFSET(priv) + (d->hwirq >> 2);
+ mask = 3 << ((d->hwirq & 3) << 1);
+
+ regmap_update_bits(regmap, offset, mask, 0);
+}
+
+static int dmec_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct dmec_gpio_priv *priv = container_of(gc, struct dmec_gpio_priv,
+ gpio_chip);
+ struct regmap *regmap = priv->regmap;
+ unsigned int offset, mask, val;
+
+ offset = DMEC_GPIO_IRQTYPE_OFFSET(priv) + (d->hwirq >> 2);
+ mask = ((d->hwirq & 3) << 1);
+
+ regmap_read(regmap, offset, &val);
+
+ val &= ~(3 << mask);
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_LEVEL_LOW:
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ val |= (1 << mask);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ val |= (2 << mask);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ val |= (3 << mask);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write(regmap, offset, val);
+
+ return 0;
+}
+
+static irqreturn_t dmec_gpio_irq_handler(int irq, void *dev_id)
+{
+ struct dmec_gpio_priv *p = dev_id;
+ struct irq_domain *d = p->gpio_chip.irqdomain;
+ unsigned int irqs_handled = 0;
+ unsigned int val = 0, stat = 0;
+
+ regmap_read(p->regmap, DMEC_GPIO_IRQSTA_OFFSET(p), &val);
+ stat = val;
+ while (stat) {
+ int line = __ffs(stat);
+ int child_irq = irq_find_mapping(d, line);
+
+ handle_nested_irq(child_irq);
+ stat &= ~(BIT(line));
+ irqs_handled++;
+ }
+ regmap_write(p->regmap, DMEC_GPIO_EVTSTA_OFFSET(p), val);
+
+ return irqs_handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int dmec_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dmec_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct dmec_gpio_priv *priv;
+ struct gpio_chip *gpio_chip;
+ struct irq_chip *irq_chip;
+ int ret = 0;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = dmec_get_regmap(pdev->dev.parent);
+ priv->chip_num = pdata->chip_num;
+
+ gpio_chip = &priv->gpio_chip;
+ gpio_chip->label = "gpio-dmec";
+ gpio_chip->owner = THIS_MODULE;
+ gpio_chip->parent = dev;
+ gpio_chip->label = dev_name(dev);
+ gpio_chip->can_sleep = true;
+
+ gpio_chip->base = pdata->gpio_base;
+
+ gpio_chip->direction_input = dmec_gpio_direction_input;
+ gpio_chip->direction_output = dmec_gpio_direction_output;
+ gpio_chip->get_direction = dmec_gpio_get_direction;
+ gpio_chip->get = dmec_gpio_get;
+ gpio_chip->set = dmec_gpio_set;
+ gpio_chip->ngpio = dmec_gpio_pincount(priv);
+ if (gpio_chip->ngpio == 0) {
+ dev_err(dev, "No GPIOs detected\n");
+ return -ENODEV;
+ }
+
+ dmec_gpio_get_version(gpio_chip);
+
+ irq_chip = &priv->irq_chip;
+ irq_chip->name = dev_name(dev);
+ irq_chip->irq_mask = dmec_gpio_irq_disable;
+ irq_chip->irq_unmask = dmec_gpio_irq_enable;
+ irq_chip->irq_set_type = dmec_gpio_irq_set_type;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, gpio_chip, priv);
+ if (ret) {
+ dev_err(dev, "Could not register GPIO chip\n");
+ return ret;
+ }
+
+ ret = gpiochip_irqchip_add(gpio_chip, irq_chip, 0,
+ handle_simple_irq, IRQ_TYPE_NONE);
+ if (ret) {
+ dev_err(dev, "cannot add irqchip\n");
+ return ret;
+ }
+
+ priv->irq = platform_get_irq(pdev, 0);
+ ret = devm_request_threaded_irq(dev, priv->irq,
+ NULL, dmec_gpio_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(dev), priv);
+ if (ret) {
+ dev_err(dev, "unable to get irq: %d\n", ret);
+ return ret;
+ }
+
+ gpiochip_set_chained_irqchip(gpio_chip, irq_chip, priv->irq, NULL);
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static int dmec_gpio_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int dmec_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct dmec_gpio_priv *p = platform_get_drvdata(pdev);
+ struct regmap *regmap = p->regmap;
+ struct dmec_reg_ctx *ctx = &p->regs;
+
+ regmap_read(regmap, DMEC_GPIO_BASE(p), &ctx->dat);
+ regmap_read(regmap, DMEC_GPIO_DIR_OFFSET(p), &ctx->dir);
+ regmap_read(regmap, DMEC_GPIO_IRQCFG_OFFSET(p), &ctx->imask);
+ regmap_read(regmap, DMEC_GPIO_IRQTYPE_OFFSET(p), &ctx->emask[0]);
+ regmap_read(regmap, DMEC_GPIO_IRQTYPE_OFFSET(p) + 1, &ctx->emask[1]);
+ regmap_read(regmap, DMEC_GPIO_IRQCFG_OFFSET(p), &ctx->icfg[0]);
+ regmap_read(regmap, DMEC_GPIO_IRQCFG_OFFSET(p) + 1, &ctx->icfg[1]);
+
+ devm_free_irq(&pdev->dev, p->irq, p);
+
+ return 0;
+}
+
+static int dmec_gpio_resume(struct platform_device *pdev)
+{
+ struct dmec_gpio_priv *p = platform_get_drvdata(pdev);
+ struct regmap *regmap = p->regmap;
+ struct dmec_reg_ctx *ctx = &p->regs;
+ int ret;
+
+ regmap_write(regmap, DMEC_GPIO_BASE(p), ctx->dat);
+ regmap_write(regmap, DMEC_GPIO_DIR_OFFSET(p), ctx->dir);
+ regmap_write(regmap, DMEC_GPIO_IRQCFG_OFFSET(p), ctx->icfg[0]);
+ regmap_write(regmap, DMEC_GPIO_IRQCFG_OFFSET(p) + 1, ctx->icfg[1]);
+ regmap_write(regmap, DMEC_GPIO_IRQTYPE_OFFSET(p), ctx->emask[0]);
+ regmap_write(regmap, DMEC_GPIO_IRQTYPE_OFFSET(p) + 1, ctx->emask[1]);
+ regmap_write(regmap, DMEC_GPIO_IRQCFG_OFFSET(p), ctx->imask);
+ regmap_write(regmap, DMEC_GPIO_EVTSTA_OFFSET(p), 0xff);
+
+ ret = devm_request_threaded_irq(&pdev->dev, p->irq,
+ NULL, dmec_gpio_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&pdev->dev), p);
+ if (ret)
+ dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+
+ return ret;
+}
+#else
+#define dmec_gpio_suspend NULL
+#define dmec_gpio_resume NULL
+#endif
+
+static struct platform_driver dmec_gpio_driver = {
+ .driver = {
+ .name = "dmec-gpio",
+ .owner = THIS_MODULE,
+ },
+ .probe = dmec_gpio_probe,
+ .remove = dmec_gpio_remove,
+ .suspend = dmec_gpio_suspend,
+ .resume = dmec_gpio_resume,
+};
+
+module_platform_driver(dmec_gpio_driver);
+
+MODULE_DESCRIPTION("dmec gpio driver");
+MODULE_AUTHOR("Zahari Doychev <zahari.doychev@linux.com");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-gpio");
--
git-series 0.8.10
^ permalink raw reply related
* [RFC PATCH 2/5] i2c-dmec: add i2c bus support for dmec
From: Zahari Doychev @ 2016-10-27 10:47 UTC (permalink / raw)
To: linux-kernel, gregkh, lee.jones, wsa, linus.walleij, wim, linux
Cc: linux-i2c, linux-gpio, gnurou, linux-watchdog, Zahari Doychev
In-Reply-To: <cover.42dd8126f6e33b5130d9afa4596d88a106c54188.1477564996.git-series.zahari.doychev@linux.com>
This is support for the i2c bus functionality of the Data Modul embedded
controllers.
Signed-off-by: Zahari Doychev <zahari.doychev@linux.com>
---
drivers/staging/dmec/Kconfig | 10 +-
drivers/staging/dmec/Makefile | 1 +-
drivers/staging/dmec/dmec.h | 9 +-
drivers/staging/dmec/i2c-dmec.c | 524 +++++++++++++++++++++++++++++++++-
4 files changed, 544 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/dmec/i2c-dmec.c
diff --git a/drivers/staging/dmec/Kconfig b/drivers/staging/dmec/Kconfig
index 3641907..0067b0b 100644
--- a/drivers/staging/dmec/Kconfig
+++ b/drivers/staging/dmec/Kconfig
@@ -7,3 +7,13 @@ config MFD_DMEC
To compile this driver as a module, say M here: the module will be
called dmec
+
+config I2C_DMEC
+ tristate "Data Modul I2C"
+ depends on MFD_DMEC && I2C
+ help
+ Say Y here to enable support for a i2c bus on the Data Modul
+ embedded controller.
+
+ To compile this driver as a module, say M here: the module will be
+ called i2c-dmec
diff --git a/drivers/staging/dmec/Makefile b/drivers/staging/dmec/Makefile
index 859163b..c51a37e 100644
--- a/drivers/staging/dmec/Makefile
+++ b/drivers/staging/dmec/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_MFD_DMEC) += dmec-core.o
+obj-$(CONFIG_I2C_DMEC) += i2c-dmec.o
diff --git a/drivers/staging/dmec/dmec.h b/drivers/staging/dmec/dmec.h
index 4d8712d..178937d 100644
--- a/drivers/staging/dmec/dmec.h
+++ b/drivers/staging/dmec/dmec.h
@@ -1,6 +1,15 @@
#ifndef _LINUX_MFD_DMEC_H
#define _LINUX_MFD_DMEC_H
+struct dmec_i2c_platform_data {
+ u32 reg_shift; /* register offset shift value */
+ u32 reg_io_width; /* register io read/write width */
+ u32 clock_khz; /* input clock in kHz */
+ bool big_endian; /* registers are big endian */
+ u8 num_devices; /* number of devices in the devices list */
+ struct i2c_board_info const *devices; /* devices connected to the bus */
+};
+
struct regmap *dmec_get_regmap(struct device *dev);
#endif
diff --git a/drivers/staging/dmec/i2c-dmec.c b/drivers/staging/dmec/i2c-dmec.c
new file mode 100644
index 0000000..456e61e
--- /dev/null
+++ b/drivers/staging/dmec/i2c-dmec.c
@@ -0,0 +1,524 @@
+/*
+ * i2c-dmec.c: I2C bus driver for OpenCores I2C controller
+ * (http://www.opencores.org/projects.cgi/web/i2c/overview).
+ *
+ * Peter Korsgaard <jacmet@sunsite.dk>
+ *
+ * Support for the GRLIB port of the controller by
+ * Andreas Larsson <andreas@gaisler.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+#include <linux/interrupt.h>
+#include "dmec.h"
+
+/* registers */
+#define DMECI2C_PRELOW 0
+#define DMECI2C_PREHIGH 1
+#define DMECI2C_CONTROL 2
+#define DMECI2C_DATA 3
+#define DMECI2C_CMD 4 /* write only */
+#define DMECI2C_STATUS 4 /* read only, same address as DMECI2C_CMD */
+#define DMECI2C_VER 5
+#define DMECI2C_MUX 6 /* i2c demultiplezer */
+
+#define DMECI2C_CTRL_EN BIT(7)
+#define DMECI2C_CTRL_IEN BIT(6)
+#define DMECI2C_CTRL_VSSCL BIT(5)
+#define DMECI2C_CTRL_MABCLR BIT(4)
+#define DMECI2C_CTRL_MMDIS BIT(3)
+#define DMECI2C_CTRL_VSSDA BIT(2)
+
+#define DMECI2C_CMD_START 0x91
+#define DMECI2C_CMD_STOP 0x41
+#define DMECI2C_CMD_READ 0x21
+#define DMECI2C_CMD_WRITE 0x11
+#define DMECI2C_CMD_READ_ACK 0x21
+#define DMECI2C_CMD_READ_NACK 0x29
+#define DMECI2C_CMD_IACK 0x01
+
+#define DMECI2C_STAT_IF 0x01
+#define DMECI2C_STAT_TIP 0x02
+#define DMECI2C_STAT_ARBLOST 0x20
+#define DMECI2C_STAT_BUSY 0x40
+#define DMECI2C_STAT_NACK 0x80
+
+#define STATE_DONE 0
+#define STATE_START 1
+#define STATE_WRITE 2
+#define STATE_READ 3
+#define STATE_ERROR 4
+
+#define TYPE_OCORES 0
+#define TYPE_GRLIB 1
+
+#define DMEC_I2C_OFFSET 0x20
+#define DMEC_I2C_MAX_BUS_NUM 3
+
+struct dmec_i2c {
+ struct device *dev;
+ void __iomem *base;
+ u32 reg_shift;
+ u32 reg_io_width;
+ wait_queue_head_t wait;
+ struct i2c_adapter adap;
+ struct i2c_mux_core *mux;
+ struct i2c_msg *msg;
+ int pos;
+ int nmsgs;
+ int state; /* see STATE_ */
+ struct clk *clk;
+ int ip_clock_khz;
+ int bus_clock_khz;
+
+ u8 irq;
+ struct regmap *regmap;
+};
+
+static int flags;
+module_param(flags, int, 0644);
+MODULE_PARM_DESC(flags, "additional flags for the i2c bus configuration");
+
+static inline void dmec_i2c_setreg(struct dmec_i2c *i2c, int reg, u8 value)
+{
+ struct regmap *regmap = i2c->regmap;
+
+ regmap_write(regmap, DMEC_I2C_OFFSET + reg, value);
+}
+
+static inline u8 dmec_i2c_getreg(struct dmec_i2c *i2c, int reg)
+{
+ struct regmap *regmap = i2c->regmap;
+ unsigned int val;
+
+ regmap_read(regmap, DMEC_I2C_OFFSET + reg, &val);
+
+ return val;
+}
+
+static int dmec_i2c_dmx_select(struct i2c_mux_core *mux, u32 chan)
+{
+ struct dmec_i2c *i2c = mux->priv;
+ u8 bus = chan & 0x3;
+
+ dmec_i2c_setreg(i2c, DMECI2C_MUX, bus);
+
+ return 0;
+}
+
+static void dmec_i2c_dmx_del(struct dmec_i2c *i2c)
+{
+ if (i2c->mux)
+ i2c_mux_del_adapters(i2c->mux);
+}
+
+static int dmec_i2c_dmx_add(struct dmec_i2c *i2c)
+{
+ u8 bus_mask;
+ int i, ret = 0;
+
+ bus_mask = dmec_i2c_getreg(i2c, DMECI2C_MUX);
+ bus_mask = (bus_mask & 0x70) >> 4;
+
+ i2c->mux = i2c_mux_alloc(&i2c->adap,
+ i2c->dev,
+ DMEC_I2C_MAX_BUS_NUM, 0, 0,
+ dmec_i2c_dmx_select,
+ NULL);
+ if (!i2c->mux)
+ return -ENOMEM;
+
+ i2c->mux->priv = i2c;
+
+ for (i = 0; i < DMEC_I2C_MAX_BUS_NUM; i++) {
+ if (!(bus_mask & (i + 1)))
+ /* bus is not present so skip */
+ continue;
+ ret = i2c_mux_add_adapter(i2c->mux, 0, i, 0);
+ if (ret) {
+ ret = -ENODEV;
+ dev_err(i2c->dev,
+ "i2c dmx failed to register adapter %d\n", i);
+ goto dmec_i2c_dmx_add_failed;
+ }
+ }
+
+ return 0;
+
+dmec_i2c_dmx_add_failed:
+ dmec_i2c_dmx_del(i2c);
+ return ret;
+}
+
+static void dmec_i2c_process(struct dmec_i2c *i2c)
+{
+ struct i2c_msg *msg = i2c->msg;
+ u8 stat = dmec_i2c_getreg(i2c, DMECI2C_STATUS);
+
+ if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) {
+ /* stop has been sent */
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_IACK);
+ wake_up(&i2c->wait);
+ return;
+ }
+
+ /* error? */
+ if (stat & DMECI2C_STAT_ARBLOST) {
+ i2c->state = STATE_ERROR;
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_STOP);
+ return;
+ }
+
+ if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) {
+ i2c->state =
+ (msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE;
+
+ if (stat & DMECI2C_STAT_NACK) {
+ i2c->state = STATE_ERROR;
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_STOP);
+ return;
+ }
+ } else {
+ msg->buf[i2c->pos++] = dmec_i2c_getreg(i2c, DMECI2C_DATA);
+ }
+
+ /* end of msg? */
+ if (i2c->pos == msg->len) {
+ i2c->nmsgs--;
+ i2c->msg++;
+ i2c->pos = 0;
+ msg = i2c->msg;
+
+ if (i2c->nmsgs) { /* end? */
+ /* send start? */
+ if (!(msg->flags & I2C_M_NOSTART)) {
+ u8 addr = (msg->addr << 1);
+
+ if (msg->flags & I2C_M_RD)
+ addr |= 1;
+
+ i2c->state = STATE_START;
+
+ dmec_i2c_setreg(i2c, DMECI2C_DATA, addr);
+ dmec_i2c_setreg(i2c, DMECI2C_CMD,
+ DMECI2C_CMD_START);
+ return;
+ }
+ i2c->state = (msg->flags & I2C_M_RD)
+ ? STATE_READ : STATE_WRITE;
+ } else {
+ i2c->state = STATE_DONE;
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_STOP);
+ return;
+ }
+ }
+
+ if (i2c->state == STATE_READ) {
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, i2c->pos == (msg->len - 1) ?
+ DMECI2C_CMD_READ_NACK : DMECI2C_CMD_READ_ACK);
+ } else {
+ dmec_i2c_setreg(i2c, DMECI2C_DATA, msg->buf[i2c->pos++]);
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_WRITE);
+ }
+}
+
+static irqreturn_t dmec_i2c_isr(int irq, void *dev_id)
+{
+ struct dmec_i2c *i2c = dev_id;
+
+ dmec_i2c_process(i2c);
+
+ return IRQ_HANDLED;
+}
+
+static int dmec_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct dmec_i2c *i2c = i2c_get_adapdata(adap);
+
+ i2c->msg = msgs;
+ i2c->pos = 0;
+ i2c->nmsgs = num;
+ i2c->state = STATE_START;
+
+ dmec_i2c_setreg(i2c, DMECI2C_DATA,
+ (i2c->msg->addr << 1) |
+ ((i2c->msg->flags & I2C_M_RD) ? 1 : 0));
+
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_START);
+
+ if (wait_event_timeout(i2c->wait, (i2c->state == STATE_ERROR) ||
+ (i2c->state == STATE_DONE), HZ))
+ return (i2c->state == STATE_DONE) ? num : -EIO;
+ else
+ return -ETIMEDOUT;
+}
+
+static int dmec_i2c_init(struct device *dev, struct dmec_i2c *i2c)
+{
+ int prescale;
+ int diff;
+ u8 stat;
+ u8 ctrl = dmec_i2c_getreg(i2c, DMECI2C_CONTROL);
+
+ /* make sure the device is disabled */
+ ctrl &= ~(DMECI2C_CTRL_EN | DMECI2C_CTRL_IEN |
+ DMECI2C_CTRL_VSSCL | DMECI2C_CTRL_VSSDA |
+ DMECI2C_CTRL_MABCLR | DMECI2C_CTRL_MMDIS);
+ dmec_i2c_setreg(i2c, DMECI2C_CONTROL, ctrl);
+
+ prescale = (i2c->ip_clock_khz / (8 * i2c->bus_clock_khz)) - 2;
+ prescale = clamp(prescale, 0, 0xffff);
+
+ diff = i2c->ip_clock_khz / (8 * (prescale + 2)) - i2c->bus_clock_khz;
+ if (abs(diff) > i2c->bus_clock_khz / 10) {
+ dev_err(dev,
+ "Unsupported clock: core: %d KHz, bus: %d KHz\n",
+ i2c->ip_clock_khz, i2c->bus_clock_khz);
+ return -EINVAL;
+ }
+
+ dmec_i2c_setreg(i2c, DMECI2C_PRELOW, prescale & 0xff);
+ dmec_i2c_setreg(i2c, DMECI2C_PREHIGH, prescale >> 8);
+
+ /* default to first bus */
+ dmec_i2c_setreg(i2c, DMECI2C_MUX, 0x0);
+
+ /* Init the device */
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_IACK);
+ ctrl |= DMECI2C_CTRL_EN;
+ if (!flags)
+ ctrl |= DMECI2C_CTRL_MABCLR | DMECI2C_CTRL_MMDIS;
+ else
+ ctrl |= (flags & 0x3c);
+ dmec_i2c_setreg(i2c, DMECI2C_CONTROL, ctrl);
+
+ stat = dmec_i2c_getreg(i2c, DMECI2C_STATUS);
+ if (stat & DMECI2C_STAT_BUSY) {
+ dev_warn(dev,
+ "I2C bus is busy - generating stop signal\n");
+ dmec_i2c_setreg(i2c, DMECI2C_CMD, DMECI2C_CMD_STOP);
+ }
+
+ stat = dmec_i2c_getreg(i2c, DMECI2C_VER);
+ dev_info(dev, "v%u.%u\n", (stat >> 4) & 0xf, stat & 0xf);
+
+ return 0;
+}
+
+static int dmec_i2c_irq_enable(struct dmec_i2c *i2c)
+{
+ u8 ctrl;
+ unsigned int irq;
+ int ret;
+
+ irq = i2c->irq;
+
+ /* Initialize interrupt handlers if not already done */
+ init_waitqueue_head(&i2c->wait);
+
+ ret = devm_request_threaded_irq(i2c->dev, irq, NULL, dmec_i2c_isr,
+ IRQF_ONESHOT | IRQF_SHARED,
+ i2c->adap.name, i2c);
+ if (ret) {
+ dev_err(i2c->dev,
+ "Unable to claim IRQ\n");
+ return ret;
+ }
+
+ /* Now enable interrupts in the controller */
+ ctrl = dmec_i2c_getreg(i2c, DMECI2C_CONTROL);
+ ctrl |= DMECI2C_CTRL_IEN;
+ dmec_i2c_setreg(i2c, DMECI2C_CONTROL, ctrl);
+
+ return 0;
+}
+
+static int dmec_i2c_irq_disable(struct dmec_i2c *i2c)
+{
+ u8 ctrl;
+
+ ctrl = dmec_i2c_getreg(i2c, DMECI2C_CONTROL);
+ ctrl &= ~DMECI2C_CTRL_IEN;
+ dmec_i2c_setreg(i2c, DMECI2C_CONTROL, ctrl);
+
+ devm_free_irq(i2c->dev, i2c->irq, i2c);
+
+ return 0;
+}
+
+static u32 dmec_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm dmec_algorithm = {
+ .master_xfer = dmec_i2c_xfer,
+ .functionality = dmec_i2c_func,
+};
+
+static struct i2c_adapter dmec_adapter = {
+ .owner = THIS_MODULE,
+ .name = "i2c-dmec",
+ .class = I2C_CLASS_DEPRECATED,
+ .algo = &dmec_algorithm,
+};
+
+#define dmec_i2c_of_probe(pdev, i2c) -ENODEV
+
+static int dmec_i2c_probe(struct platform_device *pdev)
+{
+ struct dmec_i2c *i2c;
+ struct dmec_i2c_platform_data *pdata;
+ int ret;
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ i2c->dev = &pdev->dev;
+ pdata = dev_get_platdata(&pdev->dev);
+ if (pdata) {
+ i2c->reg_shift = pdata->reg_shift;
+ i2c->reg_io_width = pdata->reg_io_width;
+ i2c->ip_clock_khz = pdata->clock_khz;
+ i2c->bus_clock_khz = 100;
+ i2c->regmap = dmec_get_regmap(pdev->dev.parent);
+ } else {
+ ret = dmec_i2c_of_probe(pdev, i2c);
+ if (ret)
+ return ret;
+ }
+
+ if (i2c->reg_io_width == 0)
+ i2c->reg_io_width = 1; /* Set to default value */
+
+ i2c->irq = platform_get_irq(pdev, 0);
+
+ ret = dmec_i2c_init(&pdev->dev, i2c);
+ if (ret)
+ return ret;
+
+ /* hook up driver to tree */
+ platform_set_drvdata(pdev, i2c);
+ i2c->adap = dmec_adapter;
+ i2c_set_adapdata(&i2c->adap, i2c);
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.dev.of_node = pdev->dev.of_node;
+
+ if (dmec_i2c_irq_enable(i2c)) {
+ dev_err(&pdev->dev, "Cannot claim IRQ\n");
+ return ret;
+ }
+
+ /* add i2c adapter to i2c tree */
+ ret = i2c_add_adapter(&i2c->adap);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add adapter\n");
+ return ret;
+ }
+
+ ret = dmec_i2c_dmx_add(i2c);
+
+ return ret;
+}
+
+static int dmec_i2c_remove(struct platform_device *pdev)
+{
+ struct dmec_i2c *i2c = platform_get_drvdata(pdev);
+
+ /* disable i2c logic */
+ dmec_i2c_setreg(i2c, DMECI2C_CONTROL,
+ dmec_i2c_getreg(i2c, DMECI2C_CONTROL)
+ & ~(DMECI2C_CTRL_EN | DMECI2C_CTRL_IEN));
+
+ /* remove adapter & data */
+ dmec_i2c_dmx_del(i2c);
+ i2c_del_adapter(&i2c->adap);
+
+ if (!IS_ERR(i2c->clk))
+ clk_disable_unprepare(i2c->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int dmec_i2c_suspend(struct device *dev)
+{
+ struct dmec_i2c *i2c = dev_get_drvdata(dev);
+ u8 ctrl = dmec_i2c_getreg(i2c, DMECI2C_CONTROL);
+
+ dmec_i2c_irq_disable(i2c);
+
+ /* make sure the device is disabled */
+ dmec_i2c_setreg(i2c, DMECI2C_CONTROL,
+ ctrl & ~DMECI2C_CTRL_EN);
+
+ if (!IS_ERR(i2c->clk))
+ clk_disable_unprepare(i2c->clk);
+ return 0;
+}
+
+static int dmec_i2c_resume(struct device *dev)
+{
+ struct dmec_i2c *i2c = dev_get_drvdata(dev);
+ int ret;
+
+ if (!IS_ERR(i2c->clk)) {
+ unsigned long rate;
+
+ ret = clk_prepare_enable(i2c->clk);
+
+ if (ret) {
+ dev_err(dev,
+ "clk_prepare_enable failed: %d\n", ret);
+ return ret;
+ }
+ rate = clk_get_rate(i2c->clk) / 1000;
+ if (rate)
+ i2c->ip_clock_khz = rate;
+ }
+
+ ret = dmec_i2c_init(dev, i2c);
+ if (ret)
+ return ret;
+ return dmec_i2c_irq_enable(i2c);
+}
+
+static SIMPLE_DEV_PM_OPS(dmec_i2c_pm, dmec_i2c_suspend, dmec_i2c_resume);
+#define DMEC_I2C_PM (&dmec_i2c_pm)
+#else
+#define DMEC_I2C_PM NULL
+#endif
+
+static struct platform_driver dmec_i2c_driver = {
+ .probe = dmec_i2c_probe,
+ .remove = dmec_i2c_remove,
+ .driver = {
+ .name = "dmec-i2c",
+ .pm = DMEC_I2C_PM,
+ },
+};
+
+module_platform_driver(dmec_i2c_driver);
+
+MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");
+MODULE_AUTHOR("Zahari Doychev <zahari.doychev@linux.com>");
+MODULE_DESCRIPTION("DMO OpenCores based I2C bus driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-i2c");
--
git-series 0.8.10
^ permalink raw reply related
* [RFC PATCH 1/5] dmec: add DMO mfd driver
From: Zahari Doychev @ 2016-10-27 10:47 UTC (permalink / raw)
To: linux-kernel, gregkh, lee.jones, wsa, linus.walleij, wim, linux
Cc: linux-i2c, linux-gpio, gnurou, linux-watchdog, Zahari Doychev
In-Reply-To: <cover.42dd8126f6e33b5130d9afa4596d88a106c54188.1477564996.git-series.zahari.doychev@linux.com>
This is a core mfd driver for the on board embedded controllers found on the
Data Modul embedded CPU modules. The embedded controller may provide the
following functions: i2c bus, gpio, watchdog, uart and rtm.
Signed-off-by: Zahari Doychev <zahari.doychev@linux.com>
---
drivers/staging/Kconfig | 2 +-
drivers/staging/Makefile | 1 +-
drivers/staging/dmec/Kconfig | 9 +-
drivers/staging/dmec/Makefile | 1 +-
drivers/staging/dmec/dmec-core.c | 500 ++++++++++++++++++++++++++++++++-
drivers/staging/dmec/dmec.h | 6 +-
6 files changed, 519 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/dmec/Kconfig
create mode 100644 drivers/staging/dmec/Makefile
create mode 100644 drivers/staging/dmec/dmec-core.c
create mode 100644 drivers/staging/dmec/dmec.h
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 58a7b35..d95bee2 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -106,4 +106,6 @@ source "drivers/staging/greybus/Kconfig"
source "drivers/staging/vc04_services/Kconfig"
+source "drivers/staging/dmec/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 2fa9745..73b4833 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_ISDN_I4L) += i4l/
obj-$(CONFIG_KS7010) += ks7010/
obj-$(CONFIG_GREYBUS) += greybus/
obj-$(CONFIG_BCM2708_VCHIQ) += vc04_services/
+obj-$(CONFIG_MFD_DMEC) += dmec/
diff --git a/drivers/staging/dmec/Kconfig b/drivers/staging/dmec/Kconfig
new file mode 100644
index 0000000..3641907
--- /dev/null
+++ b/drivers/staging/dmec/Kconfig
@@ -0,0 +1,9 @@
+config MFD_DMEC
+ tristate "Data Modul Embedded Controller MFD"
+ select MFD_CORE
+ help
+ Say Y here to enable support for a Data Module embedded controller
+ found on Data Module CPU Modules
+
+ To compile this driver as a module, say M here: the module will be
+ called dmec
diff --git a/drivers/staging/dmec/Makefile b/drivers/staging/dmec/Makefile
new file mode 100644
index 0000000..859163b
--- /dev/null
+++ b/drivers/staging/dmec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MFD_DMEC) += dmec-core.o
diff --git a/drivers/staging/dmec/dmec-core.c b/drivers/staging/dmec/dmec-core.c
new file mode 100644
index 0000000..40f5481
--- /dev/null
+++ b/drivers/staging/dmec/dmec-core.c
@@ -0,0 +1,500 @@
+/*
+ * dmec-core: Data Modul AG mfd embedded controller driver
+ *
+ * Zahari Doychev <zahari.doychev@linux.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/acpi.h>
+#include <linux/nls.h>
+#include "dmec.h"
+
+#define DMEC_LBAR 0x00 /* Index addressing 4 bytes */
+#define DMEC_ECVER0 0x04
+#define DMEC_ECVER1 0x05
+#define DMEC_ECFTR0 0x07
+#define DMEC_ECFTR1 0x08
+#define DMEC_FPGAVER0 0x0c
+#define DMEC_FPGAVER1 0x0d
+#define DMEC_FPGABLD0 0x0e
+#define DMEC_FPGABLD1 0x0f
+#define DMEC_IRQCFG0 0x10
+#define DMEC_IRQCFG1 0x11
+#define DMEC_RTM_START 0x60
+#define DMEC_RTM_END 0x6e
+
+#define DMEC_MAX_GPIO_CHIPS 2
+
+#define DMEC_VERSION_LEN 32
+
+#define DMEC_FEATURE_BIT_I2C BIT(0)
+#define DMEC_FEATURE_BIT_WDT BIT(4)
+#define DMEC_FEATURE_BIT_GPIO (3 << 6)
+
+#define DMEC_REG_MAX 0x7f
+#define DMEC_MAX_DEVS ARRAY_SIZE(dmec_devs)
+#define DMEC_MAX_IO_RES 2
+#define DMEC_STR_SZ 128
+
+static bool i_addr;
+module_param(i_addr, bool, 0644);
+MODULE_PARM_DESC(i_addr, "Enable register index addressing usage");
+
+static const char * const fw_types[] = {"release", "custom",
+ "debug", "reserved"};
+
+enum dmec_cells {
+ DMEC_I2C = 0,
+ DMEC_GPIOA,
+ DMEC_GPIOB,
+ DMEC_WDT,
+ DMEC_RTM
+};
+
+struct dmec_features {
+ unsigned int i2c_buses:2;
+ unsigned int uarts:2;
+ unsigned int wdt:1;
+ unsigned int rsvd1:1;
+ unsigned int gpio_chips:2;
+ unsigned int spi_buses:2;
+ unsigned int can_buses:2;
+ unsigned int rsvd2:1;
+ unsigned int nmi:1;
+ unsigned int sci:1;
+ unsigned int smi:1;
+};
+
+struct dmec_info {
+ unsigned int ec_ver:4;
+ unsigned int ec_rev:4;
+ unsigned int ec_num:2;
+ unsigned int ec_type:2;
+ unsigned int ec_dbg:4;
+ u16 fpga_ver;
+ u16 fpga_bld;
+ char version[DMEC_VERSION_LEN];
+};
+
+struct dmec_device_data {
+ void __iomem *io_base;
+ void __iomem *io_index;
+ void __iomem *io_data;
+ union {
+ u16 feature_mask;
+ struct dmec_features ftr;
+ } u;
+ struct device *dev;
+ struct dmec_info info;
+ struct regmap *regmap;
+ /* use index addressing for register access if set*/
+ bool i_addr;
+};
+
+struct dmec_platform_data {
+ int (*get_info)(struct dmec_device_data *);
+ int (*register_cells)(struct dmec_device_data *);
+};
+
+static struct dmec_i2c_platform_data dmec_i2c_data = {
+ .reg_shift = 0, /* two bytes between registers */
+ .reg_io_width = 1, /* register io read/write width */
+ .clock_khz = 50000, /* input clock of 50MHz */
+};
+
+static struct dmec_gpio_platform_data dmec_gpio_pdata[DMEC_MAX_GPIO_CHIPS] = {
+ {
+ .gpio_base = -1,
+ .chip_num = 0,
+ },
+ {
+ .gpio_base = -1,
+ .chip_num = 1,
+ },
+};
+
+/* The gpio block can use up to DMEC_GPIO_MAX_IRQS APIC irqs */
+static struct resource dmec_gpio_irq_resources[DMEC_MAX_GPIO_CHIPS];
+static struct resource dmec_wdt_irq_resource;
+static struct resource dmec_i2c_irq_resource;
+
+static struct mfd_cell dmec_devs[] = {
+ [DMEC_I2C] = {
+ .name = "dmec-i2c",
+ .platform_data = &dmec_i2c_data,
+ .pdata_size = sizeof(dmec_i2c_data),
+ .resources = &dmec_i2c_irq_resource,
+ .num_resources = 1,
+ },
+ [DMEC_GPIOA] = {
+ .name = "dmec-gpio",
+ .platform_data = &dmec_gpio_pdata[0],
+ .pdata_size = sizeof(struct dmec_gpio_platform_data),
+ .resources = &dmec_gpio_irq_resources[0],
+ .num_resources = 1,
+ },
+ [DMEC_GPIOB] = {
+ .name = "dmec-gpio",
+ .platform_data = &dmec_gpio_pdata[1],
+ .pdata_size = sizeof(struct dmec_gpio_platform_data),
+ .resources = &dmec_gpio_irq_resources[1],
+ .num_resources = 1,
+ },
+ [DMEC_WDT] = {
+ .name = "dmec-wdt",
+ .resources = &dmec_wdt_irq_resource,
+ .num_resources = 1,
+ },
+ [DMEC_RTM] = {
+ .name = "dmec-rtm",
+ },
+};
+
+static void dmec_get_gpio_irqs(struct dmec_device_data *ec)
+{
+ unsigned int irq, val;
+
+ regmap_read(ec->regmap, DMEC_IRQCFG1, &val);
+ irq = (val >> 4) & 0xf;
+ dmec_gpio_irq_resources[0].start = irq;
+ dmec_gpio_irq_resources[0].end = irq;
+ dmec_gpio_irq_resources[0].flags = IORESOURCE_IRQ;
+ irq = val & 0xf;
+ dmec_gpio_irq_resources[1].start = irq;
+ dmec_gpio_irq_resources[1].end = irq;
+ dmec_gpio_irq_resources[1].flags = IORESOURCE_IRQ;
+}
+
+static void dmec_get_wdt_irq(struct dmec_device_data *ec)
+{
+ unsigned int irq, val;
+
+ regmap_read(ec->regmap, DMEC_IRQCFG0, &val);
+ irq = val & 0xf;
+ dmec_wdt_irq_resource.start = irq;
+ dmec_wdt_irq_resource.end = irq;
+ dmec_wdt_irq_resource.flags = IORESOURCE_IRQ;
+}
+
+static void dmec_get_i2c_irq(struct dmec_device_data *ec)
+{
+ unsigned int irq, val;
+
+ regmap_read(ec->regmap, DMEC_IRQCFG0, &val);
+ irq = (val >> 4) & 0xf;
+ dmec_i2c_irq_resource.start = irq;
+ dmec_i2c_irq_resource.end = irq;
+ dmec_i2c_irq_resource.flags = IORESOURCE_IRQ;
+}
+
+static int dmec_rtm_detect(struct dmec_device_data *ec)
+{
+ unsigned int val, n;
+
+ for (n = DMEC_RTM_START; n <= DMEC_RTM_END; n++) {
+ regmap_read(ec->regmap, n, &val);
+ if (val != 0xff)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int dmec_register_cells(struct dmec_device_data *ec)
+{
+ struct mfd_cell cells[DMEC_MAX_DEVS];
+ u8 n_dev = 0;
+
+ if (ec->u.feature_mask & DMEC_FEATURE_BIT_I2C) {
+ dmec_get_i2c_irq(ec);
+ dmec_devs[DMEC_I2C].id = n_dev;
+ cells[n_dev++] = dmec_devs[DMEC_I2C];
+ }
+
+ if (ec->u.feature_mask & DMEC_FEATURE_BIT_GPIO) {
+ dmec_get_gpio_irqs(ec);
+ dmec_devs[DMEC_GPIOA].id = n_dev;
+ cells[n_dev++] = dmec_devs[DMEC_GPIOA];
+ if (ec->u.ftr.gpio_chips > 1) {
+ dmec_devs[DMEC_GPIOB].id = n_dev;
+ cells[n_dev++] = dmec_devs[DMEC_GPIOB];
+ }
+ }
+
+ if (ec->u.feature_mask & DMEC_FEATURE_BIT_WDT) {
+ dmec_get_wdt_irq(ec);
+ dmec_devs[DMEC_WDT].id = n_dev;
+ cells[n_dev++] = dmec_devs[DMEC_WDT];
+ }
+
+ if (dmec_rtm_detect(ec)) {
+ dmec_devs[DMEC_RTM].id = n_dev;
+ cells[n_dev++] = dmec_devs[DMEC_RTM];
+ }
+
+ return devm_mfd_add_devices(ec->dev, 0,
+ cells, n_dev, NULL, 0, NULL);
+}
+
+static int dmec_read16(struct dmec_device_data *ec, u8 reg)
+{
+ unsigned int lsb, msb;
+ int ret;
+
+ ret = regmap_read(ec->regmap, reg, &lsb);
+ ret = regmap_read(ec->regmap, reg + 0x1, &msb);
+
+ return (msb << 8) | lsb;
+}
+
+static int dmec_get_info(struct dmec_device_data *ec)
+{
+ unsigned int ver0, ver1;
+
+ regmap_read(ec->regmap, DMEC_ECVER0, &ver0);
+ regmap_read(ec->regmap, DMEC_ECVER1, &ver1);
+ if (ver0 == 0xff && ver1 == 0xff)
+ return -ENODEV;
+
+ ec->u.feature_mask = dmec_read16(ec, DMEC_ECFTR0);
+
+ ec->info.ec_ver = (ver0 >> 4) & 0xf;
+ ec->info.ec_rev = ver0 & 0xf;
+ ec->info.ec_num = ver1 & 0x3;
+ ec->info.ec_type = (ver1 >> 2) & 0x3;
+ ec->info.ec_dbg = (ver1 >> 4) & 0xf;
+
+ ec->info.fpga_ver = dmec_read16(ec, DMEC_FPGAVER0);
+ ec->info.fpga_bld = dmec_read16(ec, DMEC_FPGABLD0);
+
+ return 0;
+}
+
+static int dmec_regmap_reg_read(void *context,
+ unsigned int reg, unsigned int *val)
+{
+ struct dmec_device_data *ec = context;
+
+ if (ec->i_addr) {
+ iowrite8(reg, ec->io_index);
+ *val = ioread8(ec->io_data);
+ } else {
+ *val = ioread8(ec->io_base + reg);
+ }
+
+ return 0;
+}
+
+static int dmec_regmap_reg_write(void *context,
+ unsigned int reg, unsigned int val)
+{
+ struct dmec_device_data *ec = context;
+
+ if (ec->i_addr) {
+ iowrite8(reg, ec->io_index);
+ iowrite8(val, ec->io_data);
+ } else {
+ iowrite8(val, ec->io_base + reg);
+ }
+
+ return 0;
+}
+
+struct regmap *dmec_get_regmap(struct device *dev)
+{
+ struct dmec_device_data *ec = dev_get_drvdata(dev);
+
+ return ec->regmap;
+}
+EXPORT_SYMBOL(dmec_get_regmap);
+
+static ssize_t dmec_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dmec_device_data *ec = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", ec->info.version);
+}
+
+static ssize_t dmec_fw_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dmec_device_data *ec = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE,
+ "ver: %u.%u\n"
+ "ec: %u\n"
+ "type: %s\n"
+ "debug: %u\n"
+ "fpga ver: %x\n"
+ "fpga build: %x\n",
+ ec->info.ec_ver,
+ ec->info.ec_rev,
+ ec->info.ec_num,
+ fw_types[ec->info.ec_type % (ARRAY_SIZE(fw_types) - 1)],
+ ec->info.ec_dbg,
+ ec->info.fpga_ver,
+ ec->info.fpga_bld);
+}
+
+static ssize_t dmec_features_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct dmec_device_data *ec = dev_get_drvdata(dev);
+ struct dmec_features *ftr = &ec->u.ftr;
+
+ return scnprintf(buf, PAGE_SIZE,
+ "i2c buses: %2u\n"
+ "uarts: %2u\n"
+ "watchdog: %2u\n"
+ "gpio chips: %2u\n"
+ "spi buses: %2u\n"
+ "can buses: %2u\n"
+ "nmi: %2u\n"
+ "sci: %2u\n"
+ "smi: %2u\n",
+ ftr->i2c_buses, ftr->uarts, ftr->wdt,
+ ftr->gpio_chips, ftr->spi_buses, ftr->can_buses,
+ ftr->nmi, ftr->sci, ftr->smi);
+}
+
+static DEVICE_ATTR(version, 0444, dmec_version_show, NULL);
+static DEVICE_ATTR(fw_type, 0444, dmec_fw_type_show, NULL);
+static DEVICE_ATTR(features, 0444, dmec_features_show, NULL);
+
+static struct attribute *dmec_attributes[] = {
+ &dev_attr_version.attr,
+ &dev_attr_fw_type.attr,
+ &dev_attr_features.attr,
+ NULL
+};
+
+static const struct attribute_group ec_attr_group = {
+ .attrs = dmec_attributes,
+};
+
+static int dmec_detect_device(struct dmec_device_data *ec)
+{
+ int ret;
+
+ ret = dmec_get_info(ec);
+ if (ret)
+ return ret;
+
+ ret = scnprintf(ec->info.version, sizeof(ec->info.version),
+ "%u.%u",
+ ec->info.ec_ver, ec->info.ec_rev);
+ if (ret < 0)
+ return ret;
+
+ dev_info(ec->dev, "v%s (%s) features: %#x\n",
+ ec->info.version,
+ fw_types[ec->info.ec_type % (ARRAY_SIZE(fw_types) - 1)],
+ ec->u.feature_mask);
+
+ ret = dmec_register_cells(ec);
+ if (ret)
+ return ret;
+
+ return sysfs_create_group(&ec->dev->kobj, &ec_attr_group);
+}
+
+static const struct regmap_config dmec_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_stride = 1,
+ .max_register = DMEC_REG_MAX,
+ .reg_write = dmec_regmap_reg_write,
+ .reg_read = dmec_regmap_reg_read,
+ .cache_type = REGCACHE_NONE,
+ .fast_io = true,
+};
+
+static int dmec_mfd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dmec_device_data *ec;
+ struct resource *io_idx, *io;
+ int ret;
+
+ ret = platform_device_add_data(pdev, NULL, 0);
+ if (ret)
+ return ret;
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ io_idx = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!io_idx)
+ return -EINVAL;
+
+ io = platform_get_resource(pdev, IORESOURCE_IO, 1);
+ if (!io) {
+ dev_info(dev, "falling back to index addressing\n");
+ ec->io_base = devm_ioport_map(dev, io_idx->start,
+ io_idx->end - io_idx->start);
+ i_addr = true;
+ } else
+ ec->io_base = devm_ioport_map(dev, io->start,
+ io->end - io->start);
+
+ if (IS_ERR(ec->io_base))
+ return PTR_ERR(ec->io_base);
+
+ /* In index mode registers @ 0x0 and 0x2 are used by the BIOS */
+ ec->i_addr = i_addr;
+ ec->io_index = ec->io_base + 1;
+ ec->io_data = ec->io_base + 3;
+ ec->dev = dev;
+
+ ec->regmap = devm_regmap_init(dev, NULL, ec, &dmec_regmap_config);
+ if (IS_ERR(ec->regmap))
+ return PTR_ERR(ec->regmap);
+ regcache_cache_bypass(ec->regmap, true);
+
+ platform_set_drvdata(pdev, ec);
+
+ return dmec_detect_device(ec);
+}
+
+static int dmec_mfd_remove(struct platform_device *pdev)
+{
+ struct dmec_device_data *ec = platform_get_drvdata(pdev);
+
+ sysfs_remove_group(&ec->dev->kobj, &ec_attr_group);
+
+ return 0;
+}
+
+static const struct acpi_device_id dmec_acpi_ids[] = {
+ {"DMEC0001", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, dmec_acpi_ids);
+
+static struct platform_driver dmec_driver = {
+ .probe = dmec_mfd_probe,
+ .remove = dmec_mfd_remove,
+ .driver = {
+ .name = "dmec",
+ .acpi_match_table = ACPI_PTR(dmec_acpi_ids)
+ },
+};
+
+module_platform_driver(dmec_driver);
+
+MODULE_DESCRIPTION("DMO embedded controller core driver");
+MODULE_AUTHOR("Zahari Doychev <zahari.doychev@linux.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dmec-core");
diff --git a/drivers/staging/dmec/dmec.h b/drivers/staging/dmec/dmec.h
new file mode 100644
index 0000000..4d8712d
--- /dev/null
+++ b/drivers/staging/dmec/dmec.h
@@ -0,0 +1,6 @@
+#ifndef _LINUX_MFD_DMEC_H
+#define _LINUX_MFD_DMEC_H
+
+struct regmap *dmec_get_regmap(struct device *dev);
+
+#endif
--
git-series 0.8.10
^ permalink raw reply related
* [RFC PATCH 0/5] add support for DMO embedded controller
From: Zahari Doychev @ 2016-10-27 10:47 UTC (permalink / raw)
To: linux-kernel, gregkh, lee.jones, wsa, linus.walleij, wim, linux
Cc: linux-i2c, linux-gpio, gnurou, linux-watchdog, Zahari Doychev
This patch series adds support for the Data Modul Embedded Controller(dmec)
which is implemented within an on board FPGA found on Data Modul embedded
CPU modules.
The dmec is connected to the host through the LPC bus and its registers are
mapped into the host I/O space. The controller supports two addressing modes:
linear and indexed.
The driver adds support for the following functionality:
- i2c
- gpio
- watchdog
- running time meter (rtm)
Zahari Doychev (5):
dmec: add DMO mfd driver
i2c-dmec: add i2c bus support for dmec
gpio-dmec: gpio support for dmec
wdt-dmec: watchdog support for dmec
rtm-dmec: running time meter support
drivers/staging/Kconfig | 2 +-
drivers/staging/Makefile | 1 +-
drivers/staging/dmec/Kconfig | 50 +++-
drivers/staging/dmec/Makefile | 5 +-
drivers/staging/dmec/dmec-core.c | 500 ++++++++++++++++++++++++++++-
drivers/staging/dmec/dmec.h | 20 +-
drivers/staging/dmec/gpio-dmec.c | 390 ++++++++++++++++++++++-
drivers/staging/dmec/i2c-dmec.c | 524 +++++++++++++++++++++++++++++-
drivers/staging/dmec/rtm-dmec.c | 203 +++++++++++-
drivers/staging/dmec/wdt-dmec.c | 569 ++++++++++++++++++++++++++++++++-
10 files changed, 2264 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/dmec/Kconfig
create mode 100644 drivers/staging/dmec/Makefile
create mode 100644 drivers/staging/dmec/dmec-core.c
create mode 100644 drivers/staging/dmec/dmec.h
create mode 100644 drivers/staging/dmec/gpio-dmec.c
create mode 100644 drivers/staging/dmec/i2c-dmec.c
create mode 100644 drivers/staging/dmec/rtm-dmec.c
create mode 100644 drivers/staging/dmec/wdt-dmec.c
base-commit: 9fe68cad6e74967b88d0c6aeca7d9cd6b6e91942
--
git-series 0.8.10
^ permalink raw reply
* Re: Re: [PATCH] i2c: imx: add slave support. v2 status
From: Frkuska, Joshua @ 2016-10-27 7:31 UTC (permalink / raw)
To: linux-i2c
Cc: syrchin, dbaranov, wsa, peda, Jiada_Wang, linux-kernel,
Zapolskiy, Vladimir, Baxter, Jim
Hi Maxim, Dmitriy, Wolfram,
If there is no immediate plan for a third release of the below patch set, would it be possible to continue with merging v2 after addressing the remaining concerns?
Thank you and regards,
Joshua
> Hi Maxim,
>
> On 2016-03-04 11:06:10 in the thread "Re: [PATCH] i2c: imx: add slave support. v2"
> referenced here: https://patchwork.ozlabs.org/patch/573353/ you said:
>> Hi Wolfram,
>> I'm now working on creating new driver version. I think I'll be able to
>> sent it soon.
> Do you still plan to release a driver update for an i2c imx driver slave support?
>
> Best regards,
> Jim Baxter
>
^ permalink raw reply
* RE: [v12, 5/8] soc: fsl: add GUTS driver for QorIQ platforms
From: Y.B. Lu @ 2016-10-27 4:34 UTC (permalink / raw)
To: Scott Wood, linux-mmc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
ulf.hansson-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
Arnd Bergmann
Cc: Mark Rutland, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Russell King, Bhupesh Sharma,
netdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Santosh Shilimkar,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Jochen Friedrich, X.B. Xie, M.H. Lian,
iommu-cunTk1MwBs9QetFLy7KEm3xJsTq8ys+cHZ5vskTnxNA@public.gmane.org,
Rob Herring, linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Claudiu Manoil, Kumar Gala, Leo Li,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org,
linux-clk-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
In-Reply-To: <1477501566.6812.9.camel-fOR+EgIDQEHk1uMJSBkQmQ@public.gmane.org>
Hi Scott,
> -----Original Message-----
> From: Scott Wood [mailto:oss@buserror.net]
> Sent: Thursday, October 27, 2016 1:06 AM
> To: Y.B. Lu; linux-mmc@vger.kernel.org; ulf.hansson@linaro.org; Arnd
> Bergmann
> Cc: linuxppc-dev@lists.ozlabs.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; linux-kernel@vger.kernel.org; linux-
> clk@vger.kernel.org; linux-i2c@vger.kernel.org; iommu@lists.linux-
> foundation.org; netdev@vger.kernel.org; Mark Rutland; Rob Herring;
> Russell King; Jochen Friedrich; Joerg Roedel; Claudiu Manoil; Bhupesh
> Sharma; Qiang Zhao; Kumar Gala; Santosh Shilimkar; Leo Li; X.B. Xie; M.H.
> Lian
> Subject: Re: [v12, 5/8] soc: fsl: add GUTS driver for QorIQ platforms
>
> On Wed, 2016-09-21 at 14:57 +0800, Yangbo Lu wrote:
> > diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig new
> > file mode 100644 index 0000000..b99764c
> > --- /dev/null
> > +++ b/drivers/soc/fsl/Kconfig
> > @@ -0,0 +1,19 @@
> > +#
> > +# Freescale SOC drivers
> > +#
> > +
> > +source "drivers/soc/fsl/qe/Kconfig"
> > +
> > +config FSL_GUTS
> > + bool "Freescale QorIQ GUTS driver"
> > + select SOC_BUS
> > + help
> > + The global utilities block controls power management, I/O device
> > + enabling, power-onreset(POR) configuration monitoring, alternate
> > + function selection for multiplexed signals,and clock control.
> > + This driver is to manage and access global utilities block.
> > + Initially only reading SVR and registering soc device are
> > supported.
> > + Other guts accesses, such as reading RCW, should eventually be
> > moved
> > + into this driver as well.
> > +
> > + If you want GUTS driver support, you should say Y here.
>
> This is user-enablable without dependencies, which means it will break
> some randconfigs. If this is to be enabled via select then remove the
> text after "bool".
[Lu Yangbo-B47093] Will enable it via select and remove text after 'bool'.
>
> > +/* SoC die attribute definition for QorIQ platform */ static const
> > +struct fsl_soc_die_attr fsl_soc_die[] = { #ifdef CONFIG_PPC
> > + /*
> > + * Power Architecture-based SoCs T Series
> > + */
> > +
> > + /* Die: T4240, SoC: T4240/T4160/T4080 */
> > + { .die = "T4240",
> > + .svr = 0x82400000,
> > + .mask = 0xfff00000,
> > + },
> > + /* Die: T1040, SoC: T1040/T1020/T1042/T1022 */
> > + { .die = "T1040",
> > + .svr = 0x85200000,
> > + .mask = 0xfff00000,
> > + },
> > + /* Die: T2080, SoC: T2080/T2081 */
> > + { .die = "T2080",
> > + .svr = 0x85300000,
> > + .mask = 0xfff00000,
> > + },
> > + /* Die: T1024, SoC: T1024/T1014/T1023/T1013 */
> > + { .die = "T1024",
> > + .svr = 0x85400000,
> > + .mask = 0xfff00000,
> > + },
> > +#endif /* CONFIG_PPC */
> > +#if defined(CONFIG_ARCH_MXC) || defined(CONFIG_ARCH_LAYERSCAPE)
>
> Will this driver ever be probed on MXC? Why do we need these ifdefs at
> all?
[Lu Yangbo-B47093] Will remove them. In the previous version, we use too many members for soc definition, so I add #ifdef for ARCH.
CONFIG_ARCH_MXC was for ls1021a.
>
>
> > + /*
> > + * ARM-based SoCs LS Series
> > + */
> > +
> > + /* Die: LS1043A, SoC: LS1043A/LS1023A */
> > + { .die = "LS1043A",
> > + .svr = 0x87920000,
> > + .mask = 0xffff0000,
> > + },
> > + /* Die: LS2080A, SoC: LS2080A/LS2040A/LS2085A */
> > + { .die = "LS2080A",
> > + .svr = 0x87010000,
> > + .mask = 0xff3f0000,
> > + },
> > + /* Die: LS1088A, SoC: LS1088A/LS1048A/LS1084A/LS1044A */
> > + { .die = "LS1088A",
> > + .svr = 0x87030000,
> > + .mask = 0xff3f0000,
> > + },
> > + /* Die: LS1012A, SoC: LS1012A */
> > + { .die = "LS1012A",
> > + .svr = 0x87040000,
> > + .mask = 0xffff0000,
> > + },
> > + /* Die: LS1046A, SoC: LS1046A/LS1026A */
> > + { .die = "LS1046A",
> > + .svr = 0x87070000,
> > + .mask = 0xffff0000,
> > + },
> > + /* Die: LS2088A, SoC: LS2088A/LS2048A/LS2084A/LS2044A */
> > + { .die = "LS2088A",
> > + .svr = 0x87090000,
> > + .mask = 0xff3f0000,
> > + },
> > + /* Die: LS1021A, SoC: LS1021A/LS1020A/LS1022A
> > + * Note: Put this die at the end in cause of incorrect
> > identification
> > + */
> > + { .die = "LS1021A",
> > + .svr = 0x87000000,
> > + .mask = 0xfff00000,
> > + },
> > +#endif /* CONFIG_ARCH_MXC || CONFIG_ARCH_LAYERSCAPE */
>
> Instead of relying on ordering, add more bits to the mask so that there's
> no overlap. I think 0xfff70000 would work.
[Lu Yangbo-B47093] Ok, Will do that. Then we add 3 bits of 'Various Personalities' field for ls1021a die identification.
>
> > +out:
> > + kfree(soc_dev_attr.machine);
> > + kfree(soc_dev_attr.family);
> > + kfree(soc_dev_attr.soc_id);
> > + kfree(soc_dev_attr.revision);
> > + iounmap(guts->regs);
> > +out_free:
> > + kfree(guts);
> > + return ret;
> > +}
>
> Please use devm.
[Lu Yangbo-B47093] Sorry for forgetting this. Will do that and send out the new version soon.
Thanks for your comments.
>
> -Scott
_______________________________________________
iommu mailing list
iommu@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/iommu
^ permalink raw reply
* Re: [v12, 5/8] soc: fsl: add GUTS driver for QorIQ platforms
From: Scott Wood @ 2016-10-26 17:06 UTC (permalink / raw)
To: Yangbo Lu, linux-mmc, ulf.hansson, Arnd Bergmann
Cc: linuxppc-dev, devicetree, linux-arm-kernel, linux-kernel,
linux-clk, linux-i2c, iommu, netdev, Mark Rutland, Rob Herring,
Russell King, Jochen Friedrich, Joerg Roedel, Claudiu Manoil,
Bhupesh Sharma, Qiang Zhao, Kumar Gala, Santosh Shilimkar, Leo Li,
Xiaobo Xie, Minghuan Lian
In-Reply-To: <1474441040-11946-6-git-send-email-yangbo.lu@nxp.com>
On Wed, 2016-09-21 at 14:57 +0800, Yangbo Lu wrote:
> diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig
> new file mode 100644
> index 0000000..b99764c
> --- /dev/null
> +++ b/drivers/soc/fsl/Kconfig
> @@ -0,0 +1,19 @@
> +#
> +# Freescale SOC drivers
> +#
> +
> +source "drivers/soc/fsl/qe/Kconfig"
> +
> +config FSL_GUTS
> + bool "Freescale QorIQ GUTS driver"
> + select SOC_BUS
> + help
> + The global utilities block controls power management, I/O device
> + enabling, power-onreset(POR) configuration monitoring, alternate
> + function selection for multiplexed signals,and clock control.
> + This driver is to manage and access global utilities block.
> + Initially only reading SVR and registering soc device are
> supported.
> + Other guts accesses, such as reading RCW, should eventually be
> moved
> + into this driver as well.
> +
> + If you want GUTS driver support, you should say Y here.
This is user-enablable without dependencies, which means it will break some
randconfigs. If this is to be enabled via select then remove the text after
"bool".
> +/* SoC die attribute definition for QorIQ platform */
> +static const struct fsl_soc_die_attr fsl_soc_die[] = {
> +#ifdef CONFIG_PPC
> + /*
> + * Power Architecture-based SoCs T Series
> + */
> +
> + /* Die: T4240, SoC: T4240/T4160/T4080 */
> + { .die = "T4240",
> + .svr = 0x82400000,
> + .mask = 0xfff00000,
> + },
> + /* Die: T1040, SoC: T1040/T1020/T1042/T1022 */
> + { .die = "T1040",
> + .svr = 0x85200000,
> + .mask = 0xfff00000,
> + },
> + /* Die: T2080, SoC: T2080/T2081 */
> + { .die = "T2080",
> + .svr = 0x85300000,
> + .mask = 0xfff00000,
> + },
> + /* Die: T1024, SoC: T1024/T1014/T1023/T1013 */
> + { .die = "T1024",
> + .svr = 0x85400000,
> + .mask = 0xfff00000,
> + },
> +#endif /* CONFIG_PPC */
> +#if defined(CONFIG_ARCH_MXC) || defined(CONFIG_ARCH_LAYERSCAPE)
Will this driver ever be probed on MXC? Why do we need these ifdefs at all?
> + /*
> + * ARM-based SoCs LS Series
> + */
> +
> + /* Die: LS1043A, SoC: LS1043A/LS1023A */
> + { .die = "LS1043A",
> + .svr = 0x87920000,
> + .mask = 0xffff0000,
> + },
> + /* Die: LS2080A, SoC: LS2080A/LS2040A/LS2085A */
> + { .die = "LS2080A",
> + .svr = 0x87010000,
> + .mask = 0xff3f0000,
> + },
> + /* Die: LS1088A, SoC: LS1088A/LS1048A/LS1084A/LS1044A */
> + { .die = "LS1088A",
> + .svr = 0x87030000,
> + .mask = 0xff3f0000,
> + },
> + /* Die: LS1012A, SoC: LS1012A */
> + { .die = "LS1012A",
> + .svr = 0x87040000,
> + .mask = 0xffff0000,
> + },
> + /* Die: LS1046A, SoC: LS1046A/LS1026A */
> + { .die = "LS1046A",
> + .svr = 0x87070000,
> + .mask = 0xffff0000,
> + },
> + /* Die: LS2088A, SoC: LS2088A/LS2048A/LS2084A/LS2044A */
> + { .die = "LS2088A",
> + .svr = 0x87090000,
> + .mask = 0xff3f0000,
> + },
> + /* Die: LS1021A, SoC: LS1021A/LS1020A/LS1022A
> + * Note: Put this die at the end in cause of incorrect
> identification
> + */
> + { .die = "LS1021A",
> + .svr = 0x87000000,
> + .mask = 0xfff00000,
> + },
> +#endif /* CONFIG_ARCH_MXC || CONFIG_ARCH_LAYERSCAPE */
Instead of relying on ordering, add more bits to the mask so that there's no
overlap. I think 0xfff70000 would work.
> +out:
> + kfree(soc_dev_attr.machine);
> + kfree(soc_dev_attr.family);
> + kfree(soc_dev_attr.soc_id);
> + kfree(soc_dev_attr.revision);
> + iounmap(guts->regs);
> +out_free:
> + kfree(guts);
> + return ret;
> +}
Please use devm.
-Scott
^ permalink raw reply
* Re: [PATCH v2] I2C Designware Core Supports SMBUS BLOCK
From: Mika Westerberg @ 2016-10-26 14:52 UTC (permalink / raw)
To: tnhuynh
Cc: Jarkko Nikula, Andy Shevchenko, Wolfram Sang, linux-i2c,
linux-kernel, Loc Ho, Thang Nguyen, Phong Vo, patches
In-Reply-To: <1477475308-24293-1-git-send-email-tnhuynh@apm.com>
On Wed, Oct 26, 2016 at 04:48:28PM +0700, tnhuynh@apm.com wrote:
> From: Tin Huynh <tnhuynh@apm.com>
>
> Free and Open IPMI use SMBUS BLOCK Read/Write to support SSIF protocol.
> However, I2C Designwave Core Driver doesn't handle the case at the moment.
> The below patch supports this feature.
Looks better now but I have still couple of comments.
First, the subject line should say something like:
i2c: designware: Implement support for SMBus block read and write
> Change from V1:
> -Remove empty lines
> -Add flags variable to make clean code
> -Change DW_DEFAULT_FUNCTIONALITY in i2c-designware-pcidrv.c
This changelog should be below the '---' in the patch.
> Signed-off-by: Tin Huynh <tnhuynh@apm.com>
> ---
> drivers/i2c/busses/i2c-designware-core.c | 36 ++++++++++++++++++++++++--
> drivers/i2c/busses/i2c-designware-pcidrv.c | 1 +
> drivers/i2c/busses/i2c-designware-platdrv.c | 1 +
> 3 files changed, 35 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-designware-core.c b/drivers/i2c/busses/i2c-designware-core.c
> index 1fe93c4..c77a83f 100644
> --- a/drivers/i2c/busses/i2c-designware-core.c
> +++ b/drivers/i2c/busses/i2c-designware-core.c
> @@ -543,6 +543,7 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
> intr_mask = DW_IC_INTR_DEFAULT_MASK;
>
> for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) {
> + u32 flags = msgs[dev->msg_write_idx].flags;
> /*
> * if target address has changed, we need to
> * reprogram the target address in the i2c
> @@ -588,8 +589,15 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
> * detected from the registers so we set it always
> * when writing/reading the last byte.
> */
> +
> + /*
> + * i2c-core.c always set the buffer length of
> + * I2C_FUNC_SMBUS_BLOCK_DATA to 1. The length will
> + * be adjusted when receiving the first byte.
> + * Thus we can't stop the transaction here.
> + */
> if (dev->msg_write_idx == dev->msgs_num - 1 &&
> - buf_len == 1)
> + buf_len == 1 && !(flags & I2C_M_RECV_LEN))
> cmd |= BIT(9);
>
> if (need_restart) {
> @@ -614,7 +622,12 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
> dev->tx_buf = buf;
> dev->tx_buf_len = buf_len;
>
> - if (buf_len > 0) {
> + /*
> + * Because we don't know the buffer length in the
> + * I2C_FUNC_SMBUS_BLOCK_DATA case, we can't stop
> + * the transaction here.
> + */
> + if (buf_len > 0 || flags & I2C_M_RECV_LEN) {
> /* more bytes to be written */
> dev->status |= STATUS_WRITE_IN_PROGRESS;
> break;
> @@ -659,7 +672,24 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
> rx_valid = dw_readl(dev, DW_IC_RXFLR);
>
> for (; len > 0 && rx_valid > 0; len--, rx_valid--) {
> - *buf++ = dw_readl(dev, DW_IC_DATA_CMD);
->
> + u32 flags = msgs[dev->msg_read_idx].flags;
> + *buf = dw_readl(dev, DW_IC_DATA_CMD);
> + /* ensure length byte is a valid value */
> + if (flags & I2C_M_RECV_LEN &&
> + *buf <= I2C_SMBUS_BLOCK_MAX && *buf > 0) {
> + /*
> + * Adjust the buffer length and mask the flag
> + * after receiving the first byte
> + */
> + len = (flags & I2C_CLIENT_PEC) ?
> + *buf + 2 : *buf + 1;
> + dev->tx_buf_len = len > dev->rx_outstanding ?
> + len - dev->rx_outstanding : 0;
> + msgs[dev->msg_read_idx].len = len;
> + flags &= ~I2C_M_RECV_LEN;
> + msgs[dev->msg_read_idx].flags = flags;
> + }
<- how about moving this block to a helper function?
> + buf++;
> dev->rx_outstanding--;
> }
>
> diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c
> index 96f8230..8ffe2da 100644
> --- a/drivers/i2c/busses/i2c-designware-pcidrv.c
> +++ b/drivers/i2c/busses/i2c-designware-pcidrv.c
> @@ -75,6 +75,7 @@ struct dw_pci_controller {
> I2C_FUNC_SMBUS_BYTE | \
> I2C_FUNC_SMBUS_BYTE_DATA | \
> I2C_FUNC_SMBUS_WORD_DATA | \
> + I2C_FUNC_SMBUS_BLOCK_DATA | \
> I2C_FUNC_SMBUS_I2C_BLOCK)
>
> /* Merrifield HCNT/LCNT/SDA hold time */
> diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c
> index 0b42a12..886fb62 100644
> --- a/drivers/i2c/busses/i2c-designware-platdrv.c
> +++ b/drivers/i2c/busses/i2c-designware-platdrv.c
> @@ -220,6 +220,7 @@ static int dw_i2c_plat_probe(struct platform_device *pdev)
> I2C_FUNC_SMBUS_BYTE |
> I2C_FUNC_SMBUS_BYTE_DATA |
> I2C_FUNC_SMBUS_WORD_DATA |
> + I2C_FUNC_SMBUS_BLOCK_DATA |
> I2C_FUNC_SMBUS_I2C_BLOCK;
>
> dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |
> --
> 1.7.1
^ permalink raw reply
* Re: [PATCH] mfd: axp20x-i2c: Add i2c-ids to fix module auto-loading
From: Lee Jones @ 2016-10-26 14:00 UTC (permalink / raw)
To: Wolfram Sang
Cc: Hans de Goede, Chen-Yu Tsai, Sebastian Reichel, Dennis Gilmore,
Maxime Ripard, open list:THERMAL, linux-arm-kernel, linux-i2c
In-Reply-To: <20161024104747.GC1440@katana>
On Mon, 24 Oct 2016, Wolfram Sang wrote:
>
> > I was under the impression it was all but ready.
>
> Then, I would have applied it.
>
> > What are you waiting on?
>
> Lee, I don't want to explain it *again*. Please re-read Kieran's last
> attempt.
I met with Kieran. He's submitted a new version.
Here's hoping! ;)
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* Re: [PATCH 01/10] mfd: Juniper PTXPMB CPLD Multi-function core driver
From: Lee Jones @ 2016-10-26 13:50 UTC (permalink / raw)
To: Pantelis Antoniou
Cc: Rob Herring, Linus Walleij, Alexandre Courbot, Mark Rutland,
Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
Wim Van Sebroeck, Guenter Roeck, Peter Rosin, Debjit Ghosh,
Georgi Vlaev, Guenter Roeck, JawaharBalaji Thirumalaisamy,
Rajat Jain, devicetree, linux-kernel, linux-gpio, linux-i2c,
linux-mtd
In-Reply-To: <1475853451-22121-2-git-send-email-pantelis.antoniou@konsulko.com>
On Fri, 07 Oct 2016, Pantelis Antoniou wrote:
> From: Guenter Roeck <groeck@juniper.net>
>
> Add Juniper's PTXPMB FPGA CPLD driver. Those FPGAs
> are present in Juniper's PTX series of routers.
>
> There are two variants, the original which is found on the
> PTXPMB_P2020, PTXPMB_P2020_SPMB based on a Freescale P2020 SoC,
> and PTXPMB_P5040 based on a Freescale P5040 SoC.
>
> The new variant NGPMB is present on a new line of x86 based
> boards (currently only the Gladiator FPC).
>
> Both variants provide a hardware watchdog, i2c mux and a
> gpio block, with the i2c mux block being different.
>
> Signed-off-by: Debjit Ghosh <dghosh@juniper.net>
> Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
> Signed-off-by: Guenter Roeck <groeck@juniper.net>
> Signed-off-by: JawaharBalaji Thirumalaisamy <jawaharb@juniper.net>
> Signed-off-by: Rajat Jain <rajatjain@juniper.net>
> Signed-off-by: Tom Kavanagh <tkavanagh@juniper.net>
> [Ported from Juniper kernel]
> Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
> ---
> drivers/mfd/Kconfig | 15 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/ptxpmb-cpld-core.c | 406 ++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/ptxpmb_cpld.h | 140 ++++++++++++++
> 4 files changed, 562 insertions(+)
> create mode 100644 drivers/mfd/ptxpmb-cpld-core.c
> create mode 100644 include/linux/mfd/ptxpmb_cpld.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 2caf7e9..438666a 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1340,6 +1340,21 @@ config TWL4030_POWER
> and load scripts controlling which resources are switched off/on
> or reset when a sleep, wakeup or warm reset event occurs.
>
> +config MFD_JUNIPER_CPLD
> + tristate "Juniper PTX PMB CPLD"
> + depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
> + default y if (PTXPMB_COMMON || JNX_PTX_NGPMB)
Nit: I'd like to see the default line above 'depends|select'.
> + select MFD_CORE
> + select I2C_MUX_PTXPMB
> + select GPIO_PTXPMB_CPLD
> + select JNX_PTXPMB_WDT
I don't think these should be selected blindly from here.
> + help
> + Select this to enable the PTX PMB CPLD multi-function kernel driver
What is PTX PMB?
> + for the applicable Juniper platforms.
What applicable platforms? Might wish to consider removing this part.
It would also be nice to mention what functionality this provides and
little more info besides "this enables".
> + This driver can be built as a module. If built as a module it will be
> + called "ptxpmb-cpld"
> +
> config MFD_TWL4030_AUDIO
> bool "TI TWL4030 Audio"
> depends on TWL4030_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 2bf6a1a..62decc9 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -148,6 +148,7 @@ obj-$(CONFIG_AB3100_CORE) += ab3100-core.o
> obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o
> obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o
> obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o
> +obj-$(CONFIG_MFD_JUNIPER_CPLD) += ptxpmb-cpld-core.o
Is there only ever going to be one Juniper CPLD?
> obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o
> # ab8500-core need to come after db8500-prcmu (which provides the channel)
> obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o
> diff --git a/drivers/mfd/ptxpmb-cpld-core.c b/drivers/mfd/ptxpmb-cpld-core.c
> new file mode 100644
> index 0000000..18e60a4
> --- /dev/null
> +++ b/drivers/mfd/ptxpmb-cpld-core.c
> @@ -0,0 +1,406 @@
> +/*
> + * Juniper PTX PMB CPLD multi-function core driver
Please expand on what a PTX PMB is.
> + * Copyright (C) 2012 Juniper Networks
This needs updating.
Why is this required now and not in the past 4 years?
> + * 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 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.
> + *
Superfluous line.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/sched.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/dmi.h>
> +#include <linux/mfd/core.h>
> +#include <linux/of_device.h>
> +#include <linux/mfd/ptxpmb_cpld.h>
> +#include <linux/jnx/jnx-subsys.h>
> +#include <linux/jnx/board_ids.h>
Alphabetical.
> +struct pmb_cpld_core {
> + struct device *dev;
> + struct pmb_boot_cpld __iomem *cpld;
This isn't how we usually handle IO registers.
> + spinlock_t lock;
> + int irq;
> + wait_queue_head_t wqh;
This is not a very good variable name.
> +};
> +
> +static const struct of_device_id pmb_cpld_of_ids[] = {
> + { .compatible = "jnx,ptxpmb-cpld", .data = (void *)CPLD_TYPE_PTXPMB },
> + { .compatible = "jnx,ngpmb-bcpld", .data = (void *)CPLD_TYPE_NGPMB },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, pmb_cpld_of_ids);
Place this just before it's about to be used. Since you're matching
on this, I would expect it be be located just above .probe().
> +static struct dmi_system_id gld_2t_dmi_data[] = {
> + {
> + .ident = "Juniper Networks Gladiator 2T FPC",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Juniper Networks Inc."),
> + DMI_MATCH(DMI_PRODUCT_NAME, "0BF9"),
> + },
> + },
> + {},
> +};
> +MODULE_DEVICE_TABLE(dmi, gld_2t_dmi_data);
> +
> +static struct dmi_system_id gld_3t_dmi_data[] = {
> + {
> + .ident = "Juniper Networks Gladiator 3T FPC",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Juniper Networks Inc."),
> + DMI_MATCH(DMI_PRODUCT_NAME, "0BFA"),
> + },
> + },
> + {},
> +};
> +MODULE_DEVICE_TABLE(dmi, gld_3t_dmi_data);
> +
> +static int ptxpmb_cpld_get_master(void *data)
> +{
> + struct pmb_cpld_core *cpld = data;
> + u8 s1;
s1 is not a good variable name.
> + s1 = ioread8(&cpld->cpld->i2c_host_sel) & CPLD_I2C_HOST_MSTR_MASK;
Why aren't you using readb()?
> + if ((s1 & CPLD_I2C_HOST0_MSTR) == CPLD_I2C_HOST0_MSTR)
> + return 0;
Define this.
> + if ((s1 & CPLD_I2C_HOST1_MSTR) == CPLD_I2C_HOST1_MSTR)
> + return 1;
And this.
> + return -1;
Why aren't you using Linux error numbers.
> +}
> +
> +static int ngpmb_cpld_get_master(void *data)
> +{
> + struct pmb_cpld_core *cpld = data;
> +
> + if (ioread8(&cpld->cpld->baseboard_status1) & NGPMB_MASTER_SELECT)
> + return 1;
> + else
> + return 0;
> +}
> +
> +static irqreturn_t pmb_cpld_core_interrupt(int irq, void *dev_data)
> +{
> + struct pmb_cpld_core *cpld = dev_data;
> +
> + pr_info("pmb_cpld_core_interrupt %d\n", irq);
This should be dgb at most, but doesn't really have any place in
Mainline code. Please remove it.
> + spin_lock(&cpld->wqh.lock);
> +
> + /* clear interrupt, wake up any handlers */
> + wake_up_locked(&cpld->wqh);
> +
> + spin_unlock(&cpld->wqh.lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static struct resource pmb_cpld_resources[] = {
> + {
> + .start = 0,
> + .end = sizeof(struct pmb_boot_cpld) - 1,
Define these properly.
> + .flags = IORESOURCE_MEM,
> + },
> +};
Use the DEFINE_RES_*() helpers.
> +static struct mfd_cell pmb_cpld_cells[] = {
> + {
> + .name = "jnx-ptxpmb-wdt",
> + .num_resources = ARRAY_SIZE(pmb_cpld_resources),
> + .resources = pmb_cpld_resources,
> + .of_compatible = "jnx,ptxpmb-wdt",
> + },
> + {
> + .name = "i2c-mux-ptxpmb-cpld",
> + .num_resources = ARRAY_SIZE(pmb_cpld_resources),
> + .resources = pmb_cpld_resources,
> + .of_compatible = "jnx,i2c-mux-ptxpmb-cpld",
> + },
> + {
> + .name = "gpio-ptxpmb-cpld",
> + .num_resources = ARRAY_SIZE(pmb_cpld_resources),
> + .resources = pmb_cpld_resources,
> + .of_compatible = "jnx,gpio-ptxpmb-cpld",
> + },
> +};
> +
> +static struct mfd_cell ngpmb_cpld_cells[] = {
> + {
> + .name = "jnx-ptxpmb-wdt",
> + .num_resources = ARRAY_SIZE(pmb_cpld_resources),
> + .resources = pmb_cpld_resources,
> + .of_compatible = "jnx,ptxpmb-wdt",
> + },
> + {
> + .name = "i2c-mux-ngpmb-bcpld",
> + .num_resources = ARRAY_SIZE(pmb_cpld_resources),
> + .resources = pmb_cpld_resources,
> + .of_compatible = "jnx,i2c-mux-ngpmb-bcpld",
> + },
> + {
> + .name = "gpio-ptxpmb-cpld",
> + .num_resources = ARRAY_SIZE(pmb_cpld_resources),
> + .resources = pmb_cpld_resources,
> + .of_compatible = "jnx,gpio-ptxpmb-cpld",
> + },
> +};
> +
> +static void cpld_ngpmb_init(struct pmb_cpld_core *cpld,
> + struct jnx_chassis_info *chinfo,
> + struct jnx_card_info *cinfo)
> +{
> + u8 s1, s2, val, chassis;
s1 and s1 are not good variable names.
status1 and status2 would be better.
> + s1 = ioread8(&cpld->cpld->baseboard_status1);
> + s2 = ioread8(&cpld->cpld->baseboard_status2);
> + chassis = (ioread8(&cpld->cpld->board.ngpmb.chassis_type)
> + & NGPMB_CHASSIS_TYPE_MASK) >> NGPMB_CHASSIS_TYPE_LSB;
> +
> + dev_info(cpld->dev, "Revision 0x%02X chassis type %s (0x%02X)\n",
> + ioread8(&cpld->cpld->cpld_rev),
> + chassis == NGPMB_CHASSIS_TYPE_POLARIS ? "PTX-1000" :
> + chassis == NGPMB_CHASSIS_TYPE_HENDRICKS ? "PTX-3000" :
> + "Unknown", chassis);
> +
> + /* Only the Gladiator 2t/3t FPC */
> + if (dmi_check_system(gld_2t_dmi_data) ||
> + dmi_check_system(gld_3t_dmi_data)) {
> + /* Take SAM FPGA out of reset */
> + val = ioread8(&cpld->cpld->gpio_2);
> + iowrite8(val | NGPMB_GPIO2_TO_BASEBRD_LSB, &cpld->cpld->gpio_2);
> + mdelay(10);
> + } else {
> + /*
> + * Get the PAM FPGA out of reset,
> + * and wait for 100ms as per HW manual
> + */
> + val = ioread8(&cpld->cpld->reset);
> + iowrite8(val & ~NGPMB_PCIE_OTHER_RESET, &cpld->cpld->reset);
> + mdelay(100);
> + }
> +
> + /* No Card / Chassis info needed in stand alone mode */
> + if (!(s1 & NGPMB_PMB_STANDALONE) || !(s1 & NGPMB_BASEBRD_STANDALONE))
> + return;
> +
> + cinfo->type = JNX_BOARD_TYPE_FPC;
> + cinfo->slot = (s1 & NGPMB_BASEBRD_SLOT_MASK) >> NGPMB_BASEBRD_SLOT_LSB;
> +
> + if (((s2 & NGPMB_BASEBRD_TYPE_MASK) >> NGPMB_BASEBRD_TYPE_LSB) !=
> + NGPMB_BASEBRD_TYPE_MX) {
> + if (dmi_check_system(gld_2t_dmi_data))
> + cinfo->assembly_id = JNX_ID_GLD_2T_FPC;
> + else if (dmi_check_system(gld_3t_dmi_data))
> + cinfo->assembly_id = JNX_ID_GLD_3T_FPC;
> + else
> + cinfo->assembly_id = JNX_ID_POLARIS_MLC;
> + }
> +
> + /*
> + * Multi chassis configuration. These bits are not
> + * valid for Gladiator.
> + */
> + if (!(dmi_check_system(gld_2t_dmi_data) ||
> + dmi_check_system(gld_3t_dmi_data))) {
> + if (ioread8(&cpld->cpld->board.ngpmb.sys_config) &
> + NGPMB_SYS_CONFIG_MULTI_CHASSIS) {
> + chinfo->multichassis = 1;
"true"?
> + chinfo->chassis_no =
"no" is ambiguous.
chassis_id?
> + ioread8(&cpld->cpld->board.ngpmb.chassis_id);
> + }
> + }
> +
> + switch (chassis) {
> + case NGPMB_CHASSIS_TYPE_POLARIS:
> + chinfo->platform = JNX_PRODUCT_POLARIS;
> + break;
> + case NGPMB_CHASSIS_TYPE_HENDRICKS:
> + chinfo->platform = JNX_PRODUCT_HENDRICKS;
> + break;
> + default:
> + chinfo->platform = 0;
Define this.
> + break;
> + };
> + chinfo->get_master = ngpmb_cpld_get_master;
Where is get_master() called from?
I think you're probably better off exporting the call, rather than
using call-backs.
> +}
> +
> +static void cpld_ptxpmb_init(struct pmb_cpld_core *cpld,
> + struct jnx_chassis_info *chinfo,
> + struct jnx_card_info *cinfo)
> +{
> + u8 s1, s2;
> +
> + s1 = ioread8(&cpld->cpld->baseboard_status1);
> + s2 = ioread8(&cpld->cpld->baseboard_status2);
> +
> + dev_info(cpld->dev, "Revision 0x%02x carrier type 0x%x [%s]\n",
> + ioread8(&cpld->cpld->cpld_rev), s2 & 0x1f,
> + (s1 & 0X3F) == 0X1F ? "standalone"
> + : (s2 & 0x10) ? "FPC" : "SPMB");
> +
> + if ((s1 & 0x3f) != 0x1f) { /* not standalone */
Define these, and all the other magic numbers in this driver.
I won't say "define this" again in this review.
> + cinfo->slot = s1 & 0x0f;
> + if (s2 & 0x10) { /* fpc */
Pointless comment. What is an FPC?
> + cinfo->type = JNX_BOARD_TYPE_FPC;
> + switch (s2 & 0x0f) {
> + case 0x00: /* Sangria */
> + cinfo->assembly_id = JNX_ID_SNG_VDV_BASE_P2;
> + chinfo->platform = JNX_PRODUCT_SANGRIA;
> + break;
> + case 0x01: /* Tiny */
> + chinfo->platform = JNX_PRODUCT_TINY;
> + break;
> + case 0x02: /* Hercules */
> + chinfo->platform = JNX_PRODUCT_HERCULES;
> + break;
> + case 0x03: /* Hendricks */
> + cinfo->assembly_id = JNX_ID_HENDRICKS_FPC_P2;
> + chinfo->platform = JNX_PRODUCT_HENDRICKS;
> + break;
> + default: /* unknown */
> + break;
> + }
> + } else { /* spmb */
> + cinfo->type = JNX_BOARD_TYPE_SPMB;
> + switch (s2 & 0x0f) {
> + case 0x00: /* Sangria */
> + cinfo->assembly_id = JNX_ID_SNG_PMB;
> + chinfo->platform = JNX_PRODUCT_SANGRIA;
> + break;
> + default: /* unknown */
> + break;
> + }
> + }
> + }
> + chinfo->get_master = ptxpmb_cpld_get_master;
> +}
> +
> +static int pmb_cpld_core_probe(struct platform_device *pdev)
> +{
> + static struct pmb_cpld_core *cpld;
> + struct device *dev = &pdev->dev;
> + struct resource *res;
> + struct ptxpmb_mux_data *pdata = dev->platform_data;
> + int i, error, mfd_size;
> + int cpld_type = CPLD_TYPE_PTXPMB;
> + const struct of_device_id *match;
> + struct mfd_cell *mfd_cells;
> +
> + struct jnx_chassis_info chinfo = {
> + .chassis_no = 0,
> + .multichassis = 0,
> + .master_data = NULL,
> + .platform = -1,
> + .get_master = NULL,
> + };
> + struct jnx_card_info cinfo = {
> + .type = JNX_BOARD_TYPE_UNKNOWN,
> + .slot = -1,
> + .assembly_id = -1,
> + };
> +
> + cpld = devm_kzalloc(dev, sizeof(*cpld), GFP_KERNEL);
> + if (!cpld)
> + return -ENOMEM;
> +
> + cpld->dev = dev;
> + dev_set_drvdata(dev, cpld);
> +
> + if (pdata) {
> + cpld_type = pdata->cpld_type;
> + } else {
> + match = of_match_device(pmb_cpld_of_ids, dev);
> + if (match)
> + cpld_type = (int)(unsigned long)match->data;
> + }
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + cpld->cpld = devm_ioremap_resource(dev, res);
> + if (IS_ERR(cpld->cpld))
> + return PTR_ERR(cpld->cpld);
cpld->cpld is confusing. Please rename.
> + chinfo.master_data = cpld;
> +
> + cpld->irq = platform_get_irq(pdev, 0);
> + if (cpld->irq >= 0) {
> + error = devm_request_threaded_irq(dev, cpld->irq, NULL,
> + pmb_cpld_core_interrupt,
> + IRQF_TRIGGER_RISING |
> + IRQF_ONESHOT,
> + dev_name(dev), cpld);
> + if (error < 0)
> + return error;
> + }
> +
> + spin_lock_init(&cpld->lock);
> + init_waitqueue_head(&cpld->wqh);
> +
> + mfd_cells = pmb_cpld_cells;
> + mfd_size = ARRAY_SIZE(pmb_cpld_cells);
> +
> + switch (cpld_type) {
> + case CPLD_TYPE_PTXPMB:
> + cpld_ptxpmb_init(cpld, &chinfo, &cinfo);
> + break;
> + case CPLD_TYPE_NGPMB:
> + cpld_ngpmb_init(cpld, &chinfo, &cinfo);
> + mfd_cells = ngpmb_cpld_cells;
> + mfd_size = ARRAY_SIZE(ngpmb_cpld_cells);
> + break;
Are other types supported?
> + }
> +
> + if (pdata) {
> + for (i = 0; i < mfd_size; i++) {
> + mfd_cells[i].platform_data = pdata;
> + mfd_cells[i].pdata_size = sizeof(*pdata);
> + }
> + }
> +
> + error = mfd_add_devices(dev, pdev->id, mfd_cells,
The use of pdev->id here is unusual.
Are you sure this is what you want?
> + mfd_size, res, 0, NULL);
> + if (error < 0)
> + return error;
> +
> + jnx_register_chassis(&chinfo);
> + jnx_register_local_card(&cinfo);
> +
> + return 0;
> +}
> +
> +static int pmb_cpld_core_remove(struct platform_device *pdev)
> +{
> + jnx_unregister_local_card();
> + jnx_unregister_chassis();
> + mfd_remove_devices(&pdev->dev);
'\n' here.
> + return 0;
> +}
> +
> +static struct platform_driver pmb_cpld_core_driver = {
> + .probe = pmb_cpld_core_probe,
> + .remove = pmb_cpld_core_remove,
> + .driver = {
> + .name = "ptxpmb-cpld",
> + .of_match_table = pmb_cpld_of_ids,
> + .owner = THIS_MODULE,
Remove this line.
> + }
> +};
> +
Remove this line.
> +module_platform_driver(pmb_cpld_core_driver);
> +
> +MODULE_DESCRIPTION("Juniper PTX PMB CPLD Core Driver");
> +MODULE_AUTHOR("Guenter Roeck <groeck@juniper.net>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:ptxpmb-cpld");
> diff --git a/include/linux/mfd/ptxpmb_cpld.h b/include/linux/mfd/ptxpmb_cpld.h
> new file mode 100644
> index 0000000..e44afb4
> --- /dev/null
> +++ b/include/linux/mfd/ptxpmb_cpld.h
> @@ -0,0 +1,140 @@
> +/*---------------------------------------------------------------------------
> + *
> + * ptxpmb_cpld_core.h
> + * Copyright (c) 2012 Juniper Networks
> + *
> + *---------------------------------------------------------------------------
> + */
This it not how we write header comments.
> +#ifndef PTXPMB_CPLD_CORE_H
> +#define PTXPMB_CPLD_CORE_H
__MFD_*
> +struct pmb_boot_cpld {
> + u8 cpld_rev; /* 0x00 */
> + u8 reset;
> +#define CPLD_MAIN_RESET (1 << 0)
Use BIT() for all these "1 <<"s
> +#define CPLD_PHYCB_RESET (1 << 1)
> +#define CPLD_PHYSW_RESET (1 << 2) /* P2020 only */
> +#define NGPMB_PCIE_OTHER_RESET (1 << 3) /* PAM reset on MLC */
> + u8 reset_reason;
> +#define NGPMB_REASON_MON_A_FAIL (1 << 0)
> +#define NGPMB_REASON_WDT1 (1 << 1)
> +#define NGPMB_REASON_WDT2 (1 << 2)
> +#define NGPMB_REASON_WDT3 (1 << 3)
> +#define NGPMB_REASON_WDT4 (1 << 4)
> +#define NGPMB_REASON_RE_HRST (1 << 5)
> +#define NGPMB_REASON_PWR_ON (1 << 6)
> +#define NGPMB_REASON_RE_SRST (1 << 7)
> + u8 control;
> +#define CPLD_CONTROL_BOOTED_LED (1 << 0)
> +#define CPLD_CONTROL_WATCHDOG (1 << 6)
> +#define CPLD_CONTROL_RTC (1 << 7)
> +#define NGPMB_FLASH_SELECT (1 << 4)
> +#define NGPMB_FLASH_SWIZZ_ENA (1 << 5)
> + u8 sys_timer_cnt;
> + u8 watchdog_hbyte;
> + u8 watchdog_lbyte;
> + u8 unused1[1];
> + u8 baseboard_status1; /* 0x08 */
> +#define NGPMB_PMB_STANDALONE (1 << 0)
> +#define NGPMB_MASTER_SELECT (1 << 1)
> +#define NGPMB_BASEBRD_STANDALONE (1 << 2)
> +#define NGPMB_BASEBRD_SLOT_LSB 3
> +#define NGPMB_BASEBRD_SLOT_MASK 0xF8
> + u8 baseboard_status2;
> +#define NGPMB_BASEBRD_TYPE_LSB 5
> +#define NGPMB_BASEBRD_TYPE_MASK 0xE0
> +#define NGPMB_BASEBRD_TYPE_MX 0
> + u8 chassis_number;
> + u8 sys_config;
> + u8 i2c_group_sel; /* 0x0c */
> + u8 i2c_group_en;
> + u8 unused2[4];
> + u8 timer_irq_st; /* 0x12 */
> + u8 timer_irq_en;
> + u8 unused3[12];
> + u8 prog_jtag_control; /* 0x20 */
> + u8 gp_reset1; /* 0x21 */
> +#define CPLD_GP_RST1_PCISW (1 << 0)
> +#define CPLD_GP_RST1_SAM (1 << 1)
> +#define CPLD_GP_RST1_BRCM (1 << 2)
> + u8 gp_reset2; /* 0x22 */
> + u8 phy_control;
> + u8 gpio_1;
> + u8 gpio_2;
> +#define NGPMB_GPIO2_TO_BASEBRD_LSB (1 << 3)
> +#define NGPMB_I2C_GRP_SEL_LSB 0
> +#define NGPMB_I2C_GRP_SEL_MASK 0x03
> + u8 thermal_status;
> + u8 i2c_host_sel;
> +#define CPLD_I2C_HOST0_MSTR 0x09
> +#define CPLD_I2C_HOST1_MSTR 0x06
> +#define CPLD_I2C_HOST_MSTR_MASK 0x0f
> + u8 scratch[3];
> + u8 misc_status;
> + u8 i2c_bus_control; /* 0x2c */
> + union {
> + struct {
> + u8 mezz_present;
> + u8 unused1[4];
> + u8 i2c_group_sel_dbg; /* 0x31 */
> + u8 i2c_group_en_dbg; /* 0x32 */
> + u8 i2c_group_sel_force; /* 0x33 */
> + u8 i2c_group_en_force; /* 0x34 */
> + u8 unused2[0x4b];
> + } p2020;
> + struct {
> + u8 hdk_minor_version; /* 0x2d */
> + u8 hdk_feature_ind;
> + u8 hdk_pmb_srds_mode;
> + u8 hdk_pwr_fail_status;
> + u8 hdk_pmb_pwr_status;
> + u8 hdk_pmb_mezz_status;
> + u8 cpld_self_reset; /* 0x33 */
> + u8 unused[0x4c];
> + u8 hdk_bcpld_rcw[80];
> + } p5020;
> + struct {
> + u8 unused[3];
> + u8 chassis_id; /* 0x30 */
> + u8 chassis_type; /* 0x31 */
> +#define NGPMB_CHASSIS_TYPE_LSB 0
> +#define NGPMB_CHASSIS_TYPE_MASK 0x0F
> +#define NGPMB_CHASSIS_TYPE_POLARIS 0x0B
> +#define NGPMB_CHASSIS_TYPE_HENDRICKS 0x09
> + u8 sys_config; /* 0x32 */
> +#define NGPMB_SYS_CONFIG_MULTI_CHASSIS 0x01
> + } ngpmb;
> + struct {
> + u8 nv_win; /* 0x2d */
> + u8 nv_addr1;
> + u8 nv_addr2;
> + u8 nv_wr_data;
> + u8 nv_rd_data;
> + u8 nv_cmd;
> + u8 nv_done_bit;
> + } nvram;
> + } board;
> +};
Please use the standard conventions for detailing registers and bits.
> +#ifdef CONFIG_P2020_PTXPMB
> +#define CPLD_PHY_RESET (CPLD_PHYCB_RESET | CPLD_PHYSW_RESET)
> +#else
> +#define CPLD_PHY_RESET CPLD_PHYCB_RESET
> +#endif
> +
> +#define i2c_group_sel_force board.p2020.i2c_group_sel_force
> +#define i2c_group_en_force board.p2020.i2c_group_en_force
I've never seen this before. Please don't do it.
> +struct ptxpmb_mux_data {
> + int cpld_type;
> +#define CPLD_TYPE_PTXPMB 0 /* SPMB / Sangria FPC / Hendricks FPC */
> +#define CPLD_TYPE_NGPMB 1 /* MLC / Stout / Gladiator... */
> + int num_enable; /* Number of I2C enable pins */
> + int num_channels; /* Number of I2C channels used in a mux chip */
> + int parent_bus_num; /* parent i2c bus number */
> + int base_bus_num; /* 1st bus number, 0 if undefined */
> + bool use_force; /* Use i2c force registers if true */
> +};
Rename to *_mux_pdata for clarity.
Use kdoc throughout.
> +#endif /* PTXPMB_CPLD_CORE_H */
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* Re: [PATCH v2] i2c: i801: Fix I2C Block Read on 8-Series/C220 and later
From: Wolfram Sang @ 2016-10-26 12:51 UTC (permalink / raw)
To: Jean Delvare; +Cc: Linux I2C, Jarkko Nikula, Mika Westerberg
In-Reply-To: <20161026141532.5bd82382@endymion>
[-- Attachment #1: Type: text/plain, Size: 250 bytes --]
> Your changes are obviously correct, but you'll have to solve the merge
> conflict again when applying Benjamin's patch, sorry.
I know and I am OK. Still, I wanted your patch to be in v4.9 while
Benjamin's patches look like v4.10 material to me.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: [PATCH v2] i2c: i801: Fix I2C Block Read on 8-Series/C220 and later
From: Jean Delvare @ 2016-10-26 12:15 UTC (permalink / raw)
To: Wolfram Sang; +Cc: Linux I2C, Jarkko Nikula, Mika Westerberg
In-Reply-To: <20161025100241.GH1597@katana>
Hi Wolfram,
On Tue, 25 Oct 2016 12:02:42 +0200, Wolfram Sang wrote:
> On Tue, Oct 11, 2016 at 01:13:27PM +0200, Jean Delvare wrote:
> > Starting with the 8-Series/C220 PCH (Lynx Point), the SMBus
> > controller includes a SPD EEPROM protection mechanism. Once the SPD
> > Write Disable bit is set, only reads are allowed to slave addresses
> > 0x50-0x57.
> >
> > However the legacy implementation of I2C Block Read since the ICH5
> > looks like a write, and is therefore blocked by the SPD protection
> > mechanism. This causes the eeprom and at24 drivers to fail.
> >
> > So assume that I2C Block Read is implemented as an actual read on
> > these chipsets. I tested it on my Q87 chipset and it seems to work
> > just fine.
> >
> > Signed-off-by: Jean Delvare <jdelvare@suse.de>
> > Cc: Jarkko Nikula <jarkko.nikula@linux.intel.com>
> > Cc: Mika Westerberg <mika.westerberg@linux.intel.com>
> > Cc: Wolfram Sang <wsa@the-dreams.de>
>
> Fixed the BIT() issue mentioned by Jarkko and applied to for-current,
> thanks! But please double check my commit once I pushed out.
The BIT() cleanup is a patch by Benjamin Tissoires ("i2c: i801: use
BIT() macro for bits definition"), I thought you had applied it already
so I rebased my patch on it, but it turns out I was wrong. You could
just have used v1 of the patch ;-)
Your changes are obviously correct, but you'll have to solve the merge
conflict again when applying Benjamin's patch, sorry.
Thanks,
--
Jean Delvare
SUSE L3 Support
^ permalink raw reply
* [PATCH v2] I2C Designware Core Supports SMBUS BLOCK
From: tnhuynh @ 2016-10-26 9:48 UTC (permalink / raw)
To: Jarkko Nikula, Andy Shevchenko, Mika Westerberg, Wolfram Sang,
linux-i2c, linux-kernel
Cc: Loc Ho, Thang Nguyen, Phong Vo, patches, Tin Huynh
From: Tin Huynh <tnhuynh@apm.com>
Free and Open IPMI use SMBUS BLOCK Read/Write to support SSIF protocol.
However, I2C Designwave Core Driver doesn't handle the case at the moment.
The below patch supports this feature.
Change from V1:
-Remove empty lines
-Add flags variable to make clean code
-Change DW_DEFAULT_FUNCTIONALITY in i2c-designware-pcidrv.c
Signed-off-by: Tin Huynh <tnhuynh@apm.com>
---
drivers/i2c/busses/i2c-designware-core.c | 36 ++++++++++++++++++++++++--
drivers/i2c/busses/i2c-designware-pcidrv.c | 1 +
drivers/i2c/busses/i2c-designware-platdrv.c | 1 +
3 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/drivers/i2c/busses/i2c-designware-core.c b/drivers/i2c/busses/i2c-designware-core.c
index 1fe93c4..c77a83f 100644
--- a/drivers/i2c/busses/i2c-designware-core.c
+++ b/drivers/i2c/busses/i2c-designware-core.c
@@ -543,6 +543,7 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
intr_mask = DW_IC_INTR_DEFAULT_MASK;
for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) {
+ u32 flags = msgs[dev->msg_write_idx].flags;
/*
* if target address has changed, we need to
* reprogram the target address in the i2c
@@ -588,8 +589,15 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
* detected from the registers so we set it always
* when writing/reading the last byte.
*/
+
+ /*
+ * i2c-core.c always set the buffer length of
+ * I2C_FUNC_SMBUS_BLOCK_DATA to 1. The length will
+ * be adjusted when receiving the first byte.
+ * Thus we can't stop the transaction here.
+ */
if (dev->msg_write_idx == dev->msgs_num - 1 &&
- buf_len == 1)
+ buf_len == 1 && !(flags & I2C_M_RECV_LEN))
cmd |= BIT(9);
if (need_restart) {
@@ -614,7 +622,12 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
dev->tx_buf = buf;
dev->tx_buf_len = buf_len;
- if (buf_len > 0) {
+ /*
+ * Because we don't know the buffer length in the
+ * I2C_FUNC_SMBUS_BLOCK_DATA case, we can't stop
+ * the transaction here.
+ */
+ if (buf_len > 0 || flags & I2C_M_RECV_LEN) {
/* more bytes to be written */
dev->status |= STATUS_WRITE_IN_PROGRESS;
break;
@@ -659,7 +672,24 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
rx_valid = dw_readl(dev, DW_IC_RXFLR);
for (; len > 0 && rx_valid > 0; len--, rx_valid--) {
- *buf++ = dw_readl(dev, DW_IC_DATA_CMD);
+ u32 flags = msgs[dev->msg_read_idx].flags;
+ *buf = dw_readl(dev, DW_IC_DATA_CMD);
+ /* ensure length byte is a valid value */
+ if (flags & I2C_M_RECV_LEN &&
+ *buf <= I2C_SMBUS_BLOCK_MAX && *buf > 0) {
+ /*
+ * Adjust the buffer length and mask the flag
+ * after receiving the first byte
+ */
+ len = (flags & I2C_CLIENT_PEC) ?
+ *buf + 2 : *buf + 1;
+ dev->tx_buf_len = len > dev->rx_outstanding ?
+ len - dev->rx_outstanding : 0;
+ msgs[dev->msg_read_idx].len = len;
+ flags &= ~I2C_M_RECV_LEN;
+ msgs[dev->msg_read_idx].flags = flags;
+ }
+ buf++;
dev->rx_outstanding--;
}
diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c
index 96f8230..8ffe2da 100644
--- a/drivers/i2c/busses/i2c-designware-pcidrv.c
+++ b/drivers/i2c/busses/i2c-designware-pcidrv.c
@@ -75,6 +75,7 @@ struct dw_pci_controller {
I2C_FUNC_SMBUS_BYTE | \
I2C_FUNC_SMBUS_BYTE_DATA | \
I2C_FUNC_SMBUS_WORD_DATA | \
+ I2C_FUNC_SMBUS_BLOCK_DATA | \
I2C_FUNC_SMBUS_I2C_BLOCK)
/* Merrifield HCNT/LCNT/SDA hold time */
diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c
index 0b42a12..886fb62 100644
--- a/drivers/i2c/busses/i2c-designware-platdrv.c
+++ b/drivers/i2c/busses/i2c-designware-platdrv.c
@@ -220,6 +220,7 @@ static int dw_i2c_plat_probe(struct platform_device *pdev)
I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA |
I2C_FUNC_SMBUS_I2C_BLOCK;
dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |
--
1.7.1
^ permalink raw reply related
* Re: [PATCH v1] I2C Designware Core Supports SMBUS BLOCK
From: Mika Westerberg @ 2016-10-26 9:30 UTC (permalink / raw)
To: Tin Huynh
Cc: Jarkko Nikula, Andy Shevchenko, Wolfram Sang, linux-i2c,
linux-kernel, Loc Ho, Thang Nguyen, Phong Vo, patches
In-Reply-To: <CANQ9TuAgW7ubyKq1RAiw+s=ooF4G99_MMWnG1a0bV3DxKa+UPw@mail.gmail.com>
On Wed, Oct 26, 2016 at 04:20:57PM +0700, Tin Huynh wrote:
> We need to set stop bit if three conditions are true : The latest
> bytes , the latest msg_write_ids and after receiving the buffer length.
> If we use i2c_dw_xfer_need_stop , arguments of function are complex.
OK, so you would need to pass dev and buf_len to that.
> Can i use i2c_dw_xfer_receive_length() function ?
> Thank you and best regards
> Tin
> if (dev->msg_write_idx == dev->msgs_num - 1 &&
> - buf_len == 1)
> + buf_len == 1 && !(flags & I2C_M_RECV_LEN))
> cmd |= BIT(9);
Maybe it is better if you just do it like the above then.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox