* [PATCH v2 2/9] clk: stm32f4: Add PLL_I2S & PLL_SAI for STM32F429/469 boards
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, kernel,
gabriel.fernandez, ludovic.barre, olivier.bideau, amelie.delaunay
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
This patch introduces PLL_I2S and PLL_SAI.
Vco clock of these PLLs can be modify by DT (only n multiplicator,
m divider is still fixed by the boot-loader).
Each PLL has 3 dividers. PLL should be off when we modify the rate.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
.../devicetree/bindings/clock/st,stm32-rcc.txt | 2 +
drivers/clk/clk-stm32f4.c | 342 ++++++++++++++++++++-
include/dt-bindings/clock/stm32f4-clock.h | 4 +-
3 files changed, 332 insertions(+), 16 deletions(-)
diff --git a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
index 18e05c2..eb6733c 100644
--- a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
+++ b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
@@ -74,6 +74,8 @@ The secondary index is bound with the following magic numbers:
crystal or ceramic resonator)
4 CLK_HSE_RTC (HSE division factor for RTC clock)
5 CLK_RTC (real-time clock)
+ 6 PLL_VCO_I2S (vco frequency of I2S pll)
+ 7 PLL_VCO_SAI (vco frequency of SAI pll)
Example:
diff --git a/drivers/clk/clk-stm32f4.c b/drivers/clk/clk-stm32f4.c
index 39965ab..c2b62cc 100644
--- a/drivers/clk/clk-stm32f4.c
+++ b/drivers/clk/clk-stm32f4.c
@@ -35,6 +35,7 @@
*/
#include <dt-bindings/clock/stm32f4-clock.h>
+#define STM32F4_RCC_CR 0x00
#define STM32F4_RCC_PLLCFGR 0x04
#define STM32F4_RCC_CFGR 0x08
#define STM32F4_RCC_AHB1ENR 0x30
@@ -44,6 +45,8 @@
#define STM32F4_RCC_APB2ENR 0x44
#define STM32F4_RCC_BDCR 0x70
#define STM32F4_RCC_CSR 0x74
+#define STM32F4_RCC_PLLI2SCFGR 0x84
+#define STM32F4_RCC_PLLSAICFGR 0x88
struct stm32f4_gate_data {
u8 offset;
@@ -329,23 +332,312 @@ static struct clk *clk_register_apb_mul(struct device *dev, const char *name,
return clk;
}
-/*
- * Decode current PLL state and (statically) model the state we inherit from
- * the bootloader.
- */
-static void stm32f4_rcc_register_pll(const char *hse_clk, const char *hsi_clk)
+enum {
+ PLL,
+ PLL_I2S,
+ PLL_SAI,
+};
+
+static const struct clk_div_table pll_divp_table[] = {
+ { 0, 2 }, { 1, 4 }, { 2, 6 }, { 3, 8 }, { 0 }
+};
+
+static const struct clk_div_table pll_divr_table[] = {
+ { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 }, { 6, 6 }, { 7, 7 }, { 0 }
+};
+
+struct stm32f4_pll {
+ spinlock_t *lock;
+ struct clk_gate gate;
+ u8 offset;
+ u8 bit_rdy_idx;
+ u8 status;
+ u8 n_start;
+};
+
+#define to_stm32f4_pll(_gate) container_of(_gate, struct stm32f4_pll, gate)
+
+struct stm32f4_vco_data {
+ const char *vco_name;
+ u8 offset;
+ u8 bit_idx;
+ u8 bit_rdy_idx;
+};
+
+static const struct stm32f4_vco_data vco_data[] = {
+ { "vco", STM32F4_RCC_PLLCFGR, 24, 25 },
+ { "vco-i2s", STM32F4_RCC_PLLI2SCFGR, 26, 27 },
+ { "vco-sai", STM32F4_RCC_PLLSAICFGR, 28, 29 },
+};
+
+struct stm32f4_div_data {
+ u8 shift;
+ u8 width;
+ u8 flag_div;
+ const struct clk_div_table *div_table;
+};
+
+#define MAX_PLL_DIV 3
+static const struct stm32f4_div_data div_data[MAX_PLL_DIV] = {
+ { 16, 2, 0, pll_divp_table },
+ { 24, 4, CLK_DIVIDER_ONE_BASED, NULL },
+ { 28, 3, 0, pll_divr_table },
+};
+
+struct stm32f4_pll_data {
+ u8 pll_num;
+ u8 n_start;
+ const char *div_name[MAX_PLL_DIV];
+};
+
+static const struct stm32f4_pll_data stm32f429_pll[MAX_PLL_DIV] = {
+ { PLL, 192, { "pll", "pll48", NULL } },
+ { PLL_I2S, 192, { NULL, "plli2s-q", "plli2s-r" } },
+ { PLL_SAI, 49, { NULL, "pllsai-q", "pllsai-r" } },
+};
+
+static const struct stm32f4_pll_data stm32f469_pll[MAX_PLL_DIV] = {
+ { PLL, 50, { "pll", "pll-q", NULL } },
+ { PLL_I2S, 50, { "plli2s-p", "plli2s-q", "plli2s-r" } },
+ { PLL_SAI, 50, { "pllsai-p", "pllsai-q", "pllsai-r" } },
+};
+
+static int stm32f4_pll_is_enabled(struct clk_hw *hw)
+{
+ return clk_gate_ops.is_enabled(hw);
+}
+
+static int stm32f4_pll_enable(struct clk_hw *hw)
+{
+ struct clk_gate *gate = to_clk_gate(hw);
+ struct stm32f4_pll *pll = to_stm32f4_pll(gate);
+ int ret = 0;
+ unsigned long reg;
+
+ ret = clk_gate_ops.enable(hw);
+
+ ret = readl_relaxed_poll_timeout_atomic(base + STM32F4_RCC_CR, reg,
+ reg & (1 << pll->bit_rdy_idx), 0, 10000);
+
+ return ret;
+}
+
+static void stm32f4_pll_disable(struct clk_hw *hw)
{
- unsigned long pllcfgr = readl(base + STM32F4_RCC_PLLCFGR);
+ clk_gate_ops.disable(hw);
+}
- unsigned long pllm = pllcfgr & 0x3f;
- unsigned long plln = (pllcfgr >> 6) & 0x1ff;
- unsigned long pllp = BIT(((pllcfgr >> 16) & 3) + 1);
- const char *pllsrc = pllcfgr & BIT(22) ? hse_clk : hsi_clk;
- unsigned long pllq = (pllcfgr >> 24) & 0xf;
+static unsigned long stm32f4_pll_recalc(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_gate *gate = to_clk_gate(hw);
+ struct stm32f4_pll *pll = to_stm32f4_pll(gate);
+ unsigned long n;
- clk_register_fixed_factor(NULL, "vco", pllsrc, 0, plln, pllm);
- clk_register_fixed_factor(NULL, "pll", "vco", 0, 1, pllp);
- clk_register_fixed_factor(NULL, "pll48", "vco", 0, 1, pllq);
+ n = (readl(base + pll->offset) >> 6) & 0x1ff;
+
+ return parent_rate * n;
+}
+
+static long stm32f4_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct clk_gate *gate = to_clk_gate(hw);
+ struct stm32f4_pll *pll = to_stm32f4_pll(gate);
+ unsigned long n;
+
+ n = rate / *prate;
+
+ if (n < pll->n_start)
+ n = pll->n_start;
+ else if (n > 432)
+ n = 432;
+
+ return *prate * n;
+}
+
+static int stm32f4_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_gate *gate = to_clk_gate(hw);
+ struct stm32f4_pll *pll = to_stm32f4_pll(gate);
+
+ unsigned long n;
+ unsigned long val;
+ int pll_state;
+
+ pll_state = stm32f4_pll_is_enabled(hw);
+
+ if (pll_state)
+ stm32f4_pll_disable(hw);
+
+ n = rate / parent_rate;
+
+ val = readl(base + pll->offset) & ~(0x1ff << 6);
+
+ writel(val | ((n & 0x1ff) << 6), base + pll->offset);
+
+ if (pll_state)
+ stm32f4_pll_enable(hw);
+
+ return 0;
+}
+
+static const struct clk_ops stm32f4_pll_gate_ops = {
+ .enable = stm32f4_pll_enable,
+ .disable = stm32f4_pll_disable,
+ .is_enabled = stm32f4_pll_is_enabled,
+ .recalc_rate = stm32f4_pll_recalc,
+ .round_rate = stm32f4_pll_round_rate,
+ .set_rate = stm32f4_pll_set_rate,
+};
+
+struct stm32f4_pll_div {
+ struct clk_divider div;
+ struct clk_hw *hw_pll;
+};
+
+#define to_pll_div_clk(_div) container_of(_div, struct stm32f4_pll_div, div)
+
+static unsigned long stm32f4_pll_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return clk_divider_ops.recalc_rate(hw, parent_rate);
+}
+
+static long stm32f4_pll_div_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ return clk_divider_ops.round_rate(hw, rate, prate);
+}
+
+static int stm32f4_pll_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ int pll_state, ret;
+
+ struct clk_divider *div = to_clk_divider(hw);
+ struct stm32f4_pll_div *pll_div = to_pll_div_clk(div);
+
+ pll_state = stm32f4_pll_is_enabled(pll_div->hw_pll);
+
+ if (pll_state)
+ stm32f4_pll_disable(pll_div->hw_pll);
+
+ ret = clk_divider_ops.set_rate(hw, rate, parent_rate);
+
+ if (pll_state)
+ stm32f4_pll_enable(pll_div->hw_pll);
+
+ return ret;
+}
+
+const struct clk_ops stm32f4_pll_div_ops = {
+ .recalc_rate = stm32f4_pll_div_recalc_rate,
+ .round_rate = stm32f4_pll_div_round_rate,
+ .set_rate = stm32f4_pll_div_set_rate,
+};
+
+static struct clk_hw *clk_register_pll_div(const char *name,
+ const char *parent_name, unsigned long flags,
+ void __iomem *reg, u8 shift, u8 width,
+ u8 clk_divider_flags, const struct clk_div_table *table,
+ struct clk_hw *pll_hw, spinlock_t *lock)
+{
+ struct stm32f4_pll_div *pll_div;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ /* allocate the divider */
+ pll_div = kzalloc(sizeof(*pll_div), GFP_KERNEL);
+ if (!pll_div)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &stm32f4_pll_div_ops;
+ init.flags = flags;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+
+ /* struct clk_divider assignments */
+ pll_div->div.reg = reg;
+ pll_div->div.shift = shift;
+ pll_div->div.width = width;
+ pll_div->div.flags = clk_divider_flags;
+ pll_div->div.lock = lock;
+ pll_div->div.table = table;
+ pll_div->div.hw.init = &init;
+
+ pll_div->hw_pll = pll_hw;
+
+ /* register the clock */
+ hw = &pll_div->div.hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(pll_div);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static struct clk_hw *stm32f4_rcc_register_pll(const char *pllsrc,
+ const struct stm32f4_pll_data *data, spinlock_t *lock)
+{
+ struct stm32f4_pll *pll;
+ struct clk_init_data init = { NULL };
+ void __iomem *reg;
+ struct clk_hw *pll_hw;
+ int ret;
+ int i;
+ const struct stm32f4_vco_data *vco;
+
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ vco = &vco_data[data->pll_num];
+
+ init.name = vco->vco_name;
+ init.ops = &stm32f4_pll_gate_ops;
+ init.flags = CLK_SET_RATE_GATE;
+ init.parent_names = &pllsrc;
+ init.num_parents = 1;
+
+ pll->gate.lock = lock;
+ pll->gate.reg = base + STM32F4_RCC_CR;
+ pll->gate.bit_idx = vco->bit_idx;
+ pll->gate.hw.init = &init;
+
+ pll->offset = vco->offset;
+ pll->n_start = data->n_start;
+ pll->bit_rdy_idx = vco->bit_rdy_idx;
+ pll->status = (readl(base + STM32F4_RCC_CR) >> vco->bit_idx) & 0x1;
+
+ reg = base + pll->offset;
+
+ pll_hw = &pll->gate.hw;
+ ret = clk_hw_register(NULL, pll_hw);
+ if (ret) {
+ kfree(pll);
+ return ERR_PTR(ret);
+ }
+
+ for (i = 0; i < MAX_PLL_DIV; i++)
+ if (data->div_name[i])
+ clk_register_pll_div(data->div_name[i],
+ vco->vco_name,
+ 0,
+ reg,
+ div_data[i].shift,
+ div_data[i].width,
+ div_data[i].flag_div,
+ div_data[i].div_table,
+ pll_hw,
+ lock);
+ return pll_hw;
}
/*
@@ -620,18 +912,21 @@ struct stm32f4_clk_data {
const struct stm32f4_gate_data *gates_data;
const u64 *gates_map;
int gates_num;
+ const struct stm32f4_pll_data *pll_data;
};
static const struct stm32f4_clk_data stm32f429_clk_data = {
.gates_data = stm32f429_gates,
.gates_map = stm32f42xx_gate_map,
.gates_num = ARRAY_SIZE(stm32f429_gates),
+ .pll_data = stm32f429_pll,
};
static const struct stm32f4_clk_data stm32f469_clk_data = {
.gates_data = stm32f469_gates,
.gates_map = stm32f46xx_gate_map,
.gates_num = ARRAY_SIZE(stm32f469_gates),
+ .pll_data = stm32f469_pll,
};
static const struct of_device_id stm32f4_of_match[] = {
@@ -652,6 +947,9 @@ static void __init stm32f4_rcc_init(struct device_node *np)
int n;
const struct of_device_id *match;
const struct stm32f4_clk_data *data;
+ unsigned long pllcfgr;
+ const char *pllsrc;
+ unsigned long pllm;
base = of_iomap(np, 0);
if (!base) {
@@ -682,7 +980,21 @@ static void __init stm32f4_rcc_init(struct device_node *np)
clk_register_fixed_rate_with_accuracy(NULL, "hsi", NULL, 0,
16000000, 160000);
- stm32f4_rcc_register_pll(hse_clk, "hsi");
+ pllcfgr = readl(base + STM32F4_RCC_PLLCFGR);
+ pllsrc = pllcfgr & BIT(22) ? hse_clk : "hsi";
+ pllm = pllcfgr & 0x3f;
+
+ clk_hw_register_fixed_factor(NULL, "vco_in", pllsrc,
+ 0, 1, pllm);
+
+ stm32f4_rcc_register_pll("vco_in", &data->pll_data[0],
+ &stm32f4_clk_lock);
+
+ clks[PLL_VCO_I2S] = stm32f4_rcc_register_pll("vco_in",
+ &data->pll_data[1], &stm32f4_clk_lock);
+
+ clks[PLL_VCO_SAI] = stm32f4_rcc_register_pll("vco_in",
+ &data->pll_data[2], &stm32f4_clk_lock);
sys_parents[1] = hse_clk;
clk_register_mux_table(
diff --git a/include/dt-bindings/clock/stm32f4-clock.h b/include/dt-bindings/clock/stm32f4-clock.h
index 3132b6a..56b8e10 100644
--- a/include/dt-bindings/clock/stm32f4-clock.h
+++ b/include/dt-bindings/clock/stm32f4-clock.h
@@ -25,7 +25,9 @@
#define CLK_LSE 3
#define CLK_HSE_RTC 4
#define CLK_RTC 5
+#define PLL_VCO_I2S 6
+#define PLL_VCO_SAI 7
-#define END_PRIMARY_CLK 6
+#define END_PRIMARY_CLK 8
#endif
--
1.9.1
^ permalink raw reply related
* [PATCH v2 3/9] clk: stm32f4: Add post divisor for I2S & SAI PLLs
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, amelie.delaunay, kernel, olivier.bideau, linux-kernel,
linux-clk, ludovic.barre, gabriel.fernandez, linux-arm-kernel
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
This patch adds post dividers of I2S & SAI PLLs.
These dividers are managed by a dedicated register (RCC_DCKCFGR).
The PLL should be off before a set rate.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
drivers/clk/clk-stm32f4.c | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/drivers/clk/clk-stm32f4.c b/drivers/clk/clk-stm32f4.c
index c2b62cc..3918305 100644
--- a/drivers/clk/clk-stm32f4.c
+++ b/drivers/clk/clk-stm32f4.c
@@ -357,6 +357,18 @@ struct stm32f4_pll {
#define to_stm32f4_pll(_gate) container_of(_gate, struct stm32f4_pll, gate)
+struct stm32f4_pll_post_div_data {
+ u8 pll_num;
+ const char *name;
+ const char *parent;
+ u8 flag;
+ u8 offset;
+ u8 shift;
+ u8 width;
+ u8 flag_div;
+ const struct clk_div_table *div_table;
+};
+
struct stm32f4_vco_data {
const char *vco_name;
u8 offset;
@@ -370,6 +382,18 @@ struct stm32f4_vco_data {
{ "vco-sai", STM32F4_RCC_PLLSAICFGR, 28, 29 },
};
+#define MAX_POST_DIV 3
+static const struct stm32f4_pll_post_div_data post_div_data[MAX_POST_DIV] = {
+ { PLL_I2S, "plli2s-q-div", "plli2s-q", CLK_SET_RATE_PARENT,
+ STM32F4_RCC_DCKCFGR, 0, 5, 0, NULL},
+
+ { PLL_SAI, "pllsai-q-div", "pllsai-q", CLK_SET_RATE_PARENT,
+ STM32F4_RCC_DCKCFGR, 8, 5, 0, NULL },
+
+ { PLL_SAI, "pllsai-r-div", "pllsai-r", CLK_SET_RATE_PARENT,
+ STM32F4_RCC_DCKCFGR, 16, 2, CLK_DIVIDER_POWER_OF_TWO},
+};
+
struct stm32f4_div_data {
u8 shift;
u8 width;
@@ -996,6 +1020,23 @@ static void __init stm32f4_rcc_init(struct device_node *np)
clks[PLL_VCO_SAI] = stm32f4_rcc_register_pll("vco_in",
&data->pll_data[2], &stm32f4_clk_lock);
+ for (n = 0; n < MAX_POST_DIV; n++) {
+ const struct stm32f4_pll_post_div_data *post_div;
+
+ post_div = &post_div_data[n];
+
+ clk_register_pll_div(post_div->name,
+ post_div->parent,
+ post_div->flag,
+ base + post_div->offset,
+ post_div->shift,
+ post_div->width,
+ post_div->flag_div,
+ post_div->div_table,
+ clks[post_div->pll_num],
+ &stm32f4_clk_lock);
+ }
+
sys_parents[1] = hse_clk;
clk_register_mux_table(
NULL, "sys", sys_parents, ARRAY_SIZE(sys_parents), 0,
--
1.9.1
^ permalink raw reply related
* [PATCH v2 4/9] clk: stm32f4: Add lcd-tft clock
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, kernel,
gabriel.fernandez, ludovic.barre, olivier.bideau, amelie.delaunay
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
This patch introduces lcd-tft clock for stm32f4 soc.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
.../devicetree/bindings/clock/st,stm32-rcc.txt | 1 +
drivers/clk/clk-stm32f4.c | 118 +++++++++++++++++++++
include/dt-bindings/clock/stm32f4-clock.h | 3 +-
3 files changed, 121 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
index eb6733c..4cd08da6 100644
--- a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
+++ b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
@@ -76,6 +76,7 @@ The secondary index is bound with the following magic numbers:
5 CLK_RTC (real-time clock)
6 PLL_VCO_I2S (vco frequency of I2S pll)
7 PLL_VCO_SAI (vco frequency of SAI pll)
+ 8 CLK_LCD (LCD-TFT)
Example:
diff --git a/drivers/clk/clk-stm32f4.c b/drivers/clk/clk-stm32f4.c
index 3918305..86244fc 100644
--- a/drivers/clk/clk-stm32f4.c
+++ b/drivers/clk/clk-stm32f4.c
@@ -47,6 +47,7 @@
#define STM32F4_RCC_CSR 0x74
#define STM32F4_RCC_PLLI2SCFGR 0x84
#define STM32F4_RCC_PLLSAICFGR 0x88
+#define STM32F4_RCC_DCKCFGR 0x8c
struct stm32f4_gate_data {
u8 offset;
@@ -932,11 +933,42 @@ static struct clk_hw *stm32_register_cclk(struct device *dev, const char *name,
"no-clock", "lse", "lsi", "hse-rtc"
};
+static const char *lcd_parent[1] = { "pllsai-r-div" };
+
+struct stm32_aux_clk {
+ int idx;
+ const char *name;
+ const char * const *parent_names;
+ int num_parents;
+ int offset_mux;
+ u8 shift;
+ u8 mask;
+ int offset_gate;
+ u8 bit_idx;
+ unsigned long flags;
+};
+
struct stm32f4_clk_data {
const struct stm32f4_gate_data *gates_data;
const u64 *gates_map;
int gates_num;
const struct stm32f4_pll_data *pll_data;
+ const struct stm32_aux_clk *aux_clk;
+ int aux_clk_num;
+};
+
+#define NONE -1
+#define NO_IDX NONE
+#define NO_MUX NONE
+#define NO_GATE NONE
+
+static const struct stm32_aux_clk stm32f429_aux_clk[] = {
+ {
+ CLK_LCD, "lcd-tft", lcd_parent, ARRAY_SIZE(lcd_parent),
+ NO_MUX, 0, 0,
+ STM32F4_RCC_APB2ENR, 26,
+ CLK_SET_RATE_PARENT
+ },
};
static const struct stm32f4_clk_data stm32f429_clk_data = {
@@ -944,6 +976,8 @@ struct stm32f4_clk_data {
.gates_map = stm32f42xx_gate_map,
.gates_num = ARRAY_SIZE(stm32f429_gates),
.pll_data = stm32f429_pll,
+ .aux_clk = stm32f429_aux_clk,
+ .aux_clk_num = ARRAY_SIZE(stm32f429_aux_clk),
};
static const struct stm32f4_clk_data stm32f469_clk_data = {
@@ -951,6 +985,8 @@ struct stm32f4_clk_data {
.gates_map = stm32f46xx_gate_map,
.gates_num = ARRAY_SIZE(stm32f469_gates),
.pll_data = stm32f469_pll,
+ .aux_clk = stm32f429_aux_clk,
+ .aux_clk_num = ARRAY_SIZE(stm32f429_aux_clk),
};
static const struct of_device_id stm32f4_of_match[] = {
@@ -965,6 +1001,66 @@ struct stm32f4_clk_data {
{}
};
+static struct clk_hw *stm32_register_aux_clk(const char *name,
+ const char * const *parent_names, int num_parents,
+ int offset_mux, u8 shift, u8 mask,
+ int offset_gate, u8 bit_idx,
+ unsigned long flags, spinlock_t *lock)
+{
+ struct clk_hw *hw;
+ struct clk_gate *gate;
+ struct clk_mux *mux = NULL;
+ struct clk_hw *mux_hw = NULL, *gate_hw = NULL;
+ const struct clk_ops *mux_ops = NULL, *gate_ops = NULL;
+
+ if (offset_gate != NO_GATE) {
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate) {
+ hw = ERR_PTR(-EINVAL);
+ goto fail;
+ }
+
+ gate->reg = base + offset_gate;
+ gate->bit_idx = bit_idx;
+ gate->flags = 0;
+ gate->lock = lock;
+ gate_hw = &gate->hw;
+ gate_ops = &clk_gate_ops;
+ }
+
+ if (offset_mux != NO_MUX) {
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux) {
+ kfree(gate);
+ hw = ERR_PTR(-EINVAL);
+ goto fail;
+ }
+
+ mux->reg = base + offset_mux;
+ mux->shift = shift;
+ mux->mask = mask;
+ mux->flags = 0;
+ mux_hw = &mux->hw;
+ mux_ops = &clk_mux_ops;
+ }
+
+ if (mux_hw == NULL && gate_hw == NULL)
+ return ERR_PTR(-EINVAL);
+
+ hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
+ mux_hw, mux_ops,
+ NULL, NULL,
+ gate_hw, gate_ops,
+ flags);
+
+ if (IS_ERR(hw)) {
+ kfree(gate);
+ kfree(mux);
+ }
+fail:
+ return hw;
+}
+
static void __init stm32f4_rcc_init(struct device_node *np)
{
const char *hse_clk;
@@ -1120,6 +1216,28 @@ static void __init stm32f4_rcc_init(struct device_node *np)
goto fail;
}
+ for (n = 0; n < data->aux_clk_num; n++) {
+ const struct stm32_aux_clk *aux_clk;
+ struct clk_hw *hw;
+
+ aux_clk = &data->aux_clk[n];
+
+ hw = stm32_register_aux_clk(aux_clk->name,
+ aux_clk->parent_names, aux_clk->num_parents,
+ aux_clk->offset_mux, aux_clk->shift,
+ aux_clk->mask, aux_clk->offset_gate,
+ aux_clk->bit_idx, aux_clk->flags,
+ &stm32f4_clk_lock);
+
+ if (IS_ERR(hw)) {
+ pr_warn("Unable to register %s clk\n", aux_clk->name);
+ continue;
+ }
+
+ if (aux_clk->idx != NO_IDX)
+ clks[aux_clk->idx] = hw;
+ }
+
of_clk_add_hw_provider(np, stm32f4_rcc_lookup_clk, NULL);
return;
fail:
diff --git a/include/dt-bindings/clock/stm32f4-clock.h b/include/dt-bindings/clock/stm32f4-clock.h
index 56b8e10..1be4a3a 100644
--- a/include/dt-bindings/clock/stm32f4-clock.h
+++ b/include/dt-bindings/clock/stm32f4-clock.h
@@ -27,7 +27,8 @@
#define CLK_RTC 5
#define PLL_VCO_I2S 6
#define PLL_VCO_SAI 7
+#define CLK_LCD 8
-#define END_PRIMARY_CLK 8
+#define END_PRIMARY_CLK 9
#endif
--
1.9.1
^ permalink raw reply related
* [PATCH v2 5/9] clk: stm32f4: Add I2S clock
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, kernel,
gabriel.fernandez, ludovic.barre, olivier.bideau, amelie.delaunay
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
This patch introduces I2S clock for stm32f4 soc.
The I2S clock could be derived from an external clock or from pll-i2s
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
Documentation/devicetree/bindings/clock/st,stm32-rcc.txt | 4 +++-
drivers/clk/clk-stm32f4.c | 14 +++++++++++++-
include/dt-bindings/clock/stm32f4-clock.h | 3 ++-
3 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
index 4cd08da6..8c1ca68 100644
--- a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
+++ b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
@@ -19,6 +19,7 @@ Required properties:
use.
- clocks: External oscillator clock phandle
- high speed external clock signal (HSE)
+ - external I2S clock (I2S_CKIN)
Example:
@@ -27,7 +28,7 @@ Example:
#clock-cells = <2>
compatible = "st,stm32f42xx-rcc", "st,stm32-rcc";
reg = <0x40023800 0x400>;
- clocks = <&clk_hse>;
+ clocks = <&clk_hse>, <&clk_i2s_ckin>;
};
Specifying gated clocks
@@ -77,6 +78,7 @@ The secondary index is bound with the following magic numbers:
6 PLL_VCO_I2S (vco frequency of I2S pll)
7 PLL_VCO_SAI (vco frequency of SAI pll)
8 CLK_LCD (LCD-TFT)
+ 9 CLK_I2S (I2S clocks)
Example:
diff --git a/drivers/clk/clk-stm32f4.c b/drivers/clk/clk-stm32f4.c
index 86244fc..3063b30 100644
--- a/drivers/clk/clk-stm32f4.c
+++ b/drivers/clk/clk-stm32f4.c
@@ -935,6 +935,8 @@ static struct clk_hw *stm32_register_cclk(struct device *dev, const char *name,
static const char *lcd_parent[1] = { "pllsai-r-div" };
+static const char *i2s_parents[2] = { "plli2s-r", NULL };
+
struct stm32_aux_clk {
int idx;
const char *name;
@@ -969,6 +971,12 @@ struct stm32f4_clk_data {
STM32F4_RCC_APB2ENR, 26,
CLK_SET_RATE_PARENT
},
+ {
+ CLK_I2S, "i2s", i2s_parents, ARRAY_SIZE(i2s_parents),
+ STM32F4_RCC_CFGR, 23, 1,
+ NO_GATE, 0,
+ CLK_SET_RATE_PARENT
+ },
};
static const struct stm32f4_clk_data stm32f429_clk_data = {
@@ -1063,7 +1071,7 @@ static struct clk_hw *stm32_register_aux_clk(const char *name,
static void __init stm32f4_rcc_init(struct device_node *np)
{
- const char *hse_clk;
+ const char *hse_clk, *i2s_in_clk;
int n;
const struct of_device_id *match;
const struct stm32f4_clk_data *data;
@@ -1098,6 +1106,10 @@ static void __init stm32f4_rcc_init(struct device_node *np)
hse_clk = of_clk_get_parent_name(np, 0);
+ i2s_in_clk = of_clk_get_parent_name(np, 1);
+
+ i2s_parents[1] = i2s_in_clk;
+
clk_register_fixed_rate_with_accuracy(NULL, "hsi", NULL, 0,
16000000, 160000);
pllcfgr = readl(base + STM32F4_RCC_PLLCFGR);
diff --git a/include/dt-bindings/clock/stm32f4-clock.h b/include/dt-bindings/clock/stm32f4-clock.h
index 1be4a3a..b129ab9 100644
--- a/include/dt-bindings/clock/stm32f4-clock.h
+++ b/include/dt-bindings/clock/stm32f4-clock.h
@@ -28,7 +28,8 @@
#define PLL_VCO_I2S 6
#define PLL_VCO_SAI 7
#define CLK_LCD 8
+#define CLK_I2S 9
-#define END_PRIMARY_CLK 9
+#define END_PRIMARY_CLK 10
#endif
--
1.9.1
^ permalink raw reply related
* [PATCH v2 6/9] clk: stm32f4: Add SAI clocks
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, kernel,
gabriel.fernandez, ludovic.barre, olivier.bideau, amelie.delaunay
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
This patch introduces SAI clocks for stm32f4 socs.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
Documentation/devicetree/bindings/clock/st,stm32-rcc.txt | 2 ++
drivers/clk/clk-stm32f4.c | 16 ++++++++++++++++
include/dt-bindings/clock/stm32f4-clock.h | 4 +++-
3 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
index 8c1ca68..8f93740 100644
--- a/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
+++ b/Documentation/devicetree/bindings/clock/st,stm32-rcc.txt
@@ -79,6 +79,8 @@ The secondary index is bound with the following magic numbers:
7 PLL_VCO_SAI (vco frequency of SAI pll)
8 CLK_LCD (LCD-TFT)
9 CLK_I2S (I2S clocks)
+ 10 CLK_SAI1 (audio clocks)
+ 11 CLK_SAI2
Example:
diff --git a/drivers/clk/clk-stm32f4.c b/drivers/clk/clk-stm32f4.c
index 3063b30..02339d1 100644
--- a/drivers/clk/clk-stm32f4.c
+++ b/drivers/clk/clk-stm32f4.c
@@ -937,6 +937,9 @@ static struct clk_hw *stm32_register_cclk(struct device *dev, const char *name,
static const char *i2s_parents[2] = { "plli2s-r", NULL };
+static const char *sai_parents[4] = { "pllsai-q-div", "plli2s-q-div", NULL,
+ "no-clock" };
+
struct stm32_aux_clk {
int idx;
const char *name;
@@ -977,6 +980,18 @@ struct stm32f4_clk_data {
NO_GATE, 0,
CLK_SET_RATE_PARENT
},
+ {
+ CLK_SAI1, "sai1-a", sai_parents, ARRAY_SIZE(sai_parents),
+ STM32F4_RCC_DCKCFGR, 20, 3,
+ STM32F4_RCC_APB2ENR, 22,
+ CLK_SET_RATE_PARENT
+ },
+ {
+ CLK_SAI2, "sai1-b", sai_parents, ARRAY_SIZE(sai_parents),
+ STM32F4_RCC_DCKCFGR, 22, 3,
+ STM32F4_RCC_APB2ENR, 22,
+ CLK_SET_RATE_PARENT
+ },
};
static const struct stm32f4_clk_data stm32f429_clk_data = {
@@ -1109,6 +1124,7 @@ static void __init stm32f4_rcc_init(struct device_node *np)
i2s_in_clk = of_clk_get_parent_name(np, 1);
i2s_parents[1] = i2s_in_clk;
+ sai_parents[2] = i2s_in_clk;
clk_register_fixed_rate_with_accuracy(NULL, "hsi", NULL, 0,
16000000, 160000);
diff --git a/include/dt-bindings/clock/stm32f4-clock.h b/include/dt-bindings/clock/stm32f4-clock.h
index b129ab9..5431f00 100644
--- a/include/dt-bindings/clock/stm32f4-clock.h
+++ b/include/dt-bindings/clock/stm32f4-clock.h
@@ -29,7 +29,9 @@
#define PLL_VCO_SAI 7
#define CLK_LCD 8
#define CLK_I2S 9
+#define CLK_SAI1 10
+#define CLK_SAI2 11
-#define END_PRIMARY_CLK 10
+#define END_PRIMARY_CLK 12
#endif
--
1.9.1
^ permalink raw reply related
* [PATCH v2 7/9] clk: stm32f4: SDIO & 48Mhz clock management for STM32F469 board
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, kernel,
gabriel.fernandez, ludovic.barre, olivier.bideau, amelie.delaunay
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
In the stm32f469 soc, the 48Mhz clock could be derived from pll-q or
from pll-sai-p.
The SDIO clock could be also derived from 48Mhz or from sys clock.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
drivers/clk/clk-stm32f4.c | 49 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 46 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/clk-stm32f4.c b/drivers/clk/clk-stm32f4.c
index 02339d1..161449d 100644
--- a/drivers/clk/clk-stm32f4.c
+++ b/drivers/clk/clk-stm32f4.c
@@ -206,7 +206,7 @@ struct stm32f4_gate_data {
{ STM32F4_RCC_APB2ENR, 8, "adc1", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 9, "adc2", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 10, "adc3", "apb2_div" },
- { STM32F4_RCC_APB2ENR, 11, "sdio", "pll48" },
+ { STM32F4_RCC_APB2ENR, 11, "sdio", "sdmux" },
{ STM32F4_RCC_APB2ENR, 12, "spi1", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 13, "spi4", "apb2_div" },
{ STM32F4_RCC_APB2ENR, 14, "syscfg", "apb2_div" },
@@ -940,6 +940,10 @@ static struct clk_hw *stm32_register_cclk(struct device *dev, const char *name,
static const char *sai_parents[4] = { "pllsai-q-div", "plli2s-q-div", NULL,
"no-clock" };
+static const char *pll48_parents[2] = { "pll-q", "pllsai-p" };
+
+static const char *sdmux_parents[2] = { "pll48", "sys" };
+
struct stm32_aux_clk {
int idx;
const char *name;
@@ -994,6 +998,45 @@ struct stm32f4_clk_data {
},
};
+static const struct stm32_aux_clk stm32f469_aux_clk[] = {
+ {
+ CLK_LCD, "lcd-tft", lcd_parent, ARRAY_SIZE(lcd_parent),
+ NO_MUX, 0, 0,
+ STM32F4_RCC_APB2ENR, 26,
+ CLK_SET_RATE_PARENT
+ },
+ {
+ CLK_I2S, "i2s", i2s_parents, ARRAY_SIZE(i2s_parents),
+ STM32F4_RCC_CFGR, 23, 1,
+ NO_GATE, 0,
+ CLK_SET_RATE_PARENT
+ },
+ {
+ CLK_SAI1, "sai1-a", sai_parents, ARRAY_SIZE(sai_parents),
+ STM32F4_RCC_DCKCFGR, 20, 3,
+ STM32F4_RCC_APB2ENR, 22,
+ CLK_SET_RATE_PARENT
+ },
+ {
+ CLK_SAI2, "sai1-b", sai_parents, ARRAY_SIZE(sai_parents),
+ STM32F4_RCC_DCKCFGR, 22, 3,
+ STM32F4_RCC_APB2ENR, 22,
+ CLK_SET_RATE_PARENT
+ },
+ {
+ NO_IDX, "pll48", pll48_parents, ARRAY_SIZE(pll48_parents),
+ STM32F4_RCC_DCKCFGR, 27, 1,
+ NO_GATE, 0,
+ 0
+ },
+ {
+ NO_IDX, "sdmux", sdmux_parents, ARRAY_SIZE(sdmux_parents),
+ STM32F4_RCC_DCKCFGR, 28, 1,
+ NO_GATE, 0,
+ 0
+ },
+};
+
static const struct stm32f4_clk_data stm32f429_clk_data = {
.gates_data = stm32f429_gates,
.gates_map = stm32f42xx_gate_map,
@@ -1008,8 +1051,8 @@ struct stm32f4_clk_data {
.gates_map = stm32f46xx_gate_map,
.gates_num = ARRAY_SIZE(stm32f469_gates),
.pll_data = stm32f469_pll,
- .aux_clk = stm32f429_aux_clk,
- .aux_clk_num = ARRAY_SIZE(stm32f429_aux_clk),
+ .aux_clk = stm32f469_aux_clk,
+ .aux_clk_num = ARRAY_SIZE(stm32f469_aux_clk),
};
static const struct of_device_id stm32f4_of_match[] = {
--
1.9.1
^ permalink raw reply related
* [PATCH v2 8/9] arm: dts: stm32f4: Add external I2S clock
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, kernel,
gabriel.fernandez, ludovic.barre, olivier.bideau, amelie.delaunay
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
This patch adds an external I2S clock in the DT.
The I2S clock could be derived from an external I2S clock or by I2S pll.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
arch/arm/boot/dts/stm32f429.dtsi | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/stm32f429.dtsi b/arch/arm/boot/dts/stm32f429.dtsi
index e4dae0e..7c7dfbd 100644
--- a/arch/arm/boot/dts/stm32f429.dtsi
+++ b/arch/arm/boot/dts/stm32f429.dtsi
@@ -68,6 +68,12 @@
compatible = "fixed-clock";
clock-frequency = <32000>;
};
+
+ clk_i2s_ckin: i2s-ckin {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <0>;
+ };
};
soc {
@@ -362,7 +368,7 @@
#clock-cells = <2>;
compatible = "st,stm32f42xx-rcc", "st,stm32-rcc";
reg = <0x40023800 0x400>;
- clocks = <&clk_hse>;
+ clocks = <&clk_hse>, <&clk_i2s_ckin>;
st,syscfg = <&pwrcfg>;
};
--
1.9.1
^ permalink raw reply related
* [PATCH v2 9/9] arm: dts: stm32f4: Include auxiliary stm32f4 clock definition
From: gabriel.fernandez @ 2016-11-24 14:45 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Russell King, Maxime Coquelin,
Alexandre Torgue, Michael Turquette, Stephen Boyd, Nicolas Pitre,
Arnd Bergmann, daniel.thompson, andrea.merello, radoslaw.pietrzyk
Cc: devicetree, linux-arm-kernel, linux-kernel, linux-clk, kernel,
gabriel.fernandez, ludovic.barre, olivier.bideau, amelie.delaunay
In-Reply-To: <1479998749-20358-1-git-send-email-gabriel.fernandez@st.com>
From: Gabriel Fernandez <gabriel.fernandez@st.com>
This patch include auxiliary clock definition (clocks which are not derived
from system clock.
Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
---
arch/arm/boot/dts/stm32f429.dtsi | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm/boot/dts/stm32f429.dtsi b/arch/arm/boot/dts/stm32f429.dtsi
index 7c7dfbd..223dc12 100644
--- a/arch/arm/boot/dts/stm32f429.dtsi
+++ b/arch/arm/boot/dts/stm32f429.dtsi
@@ -48,6 +48,7 @@
#include "skeleton.dtsi"
#include "armv7-m.dtsi"
#include <dt-bindings/pinctrl/stm32f429-pinfunc.h>
+#include <dt-bindings/clock/stm32f4-clock.h>
/ {
clocks {
--
1.9.1
^ permalink raw reply related
* Re: [PATCH v9 3/4] dtc: Plugin and fixup support
From: Phil Elwell @ 2016-11-24 14:54 UTC (permalink / raw)
To: Pantelis Antoniou
Cc: David Gibson, Jon Loeliger, Grant Likely, Frank Rowand,
Rob Herring, Jan Luebbe, Sascha Hauer, Simon Glass, Maxime Ripard,
Thomas Petazzoni, Boris Brezillon, Antoine Tenart, Stephen Boyd,
Devicetree Compiler, devicetree-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <6FE2D679-BD31-4366-B23C-2F95B0706CE0-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
On 24/11/2016 14:23, Pantelis Antoniou wrote:
> Hi Phil,
>> I would have thought that all DTBs already contain enough dependency
>> information in the form of the phandles themselves. One of the first
>> things the kernel does is to unflatten the DTB, and that is the obvious
>> point to resolve the phandles and generate the necessary dependencies.
>> Can you explain how both __fixups__ and __local_fixups__ aid this
>> process? Ideally they wouldn't duplicate any information already in the
>> tree, since then you have to cope with the possibility of malformed DTBs
>> where the two don't actually match.
> No, the DTBs by themselves do not contain enough information to build the
> probe dependency graph, because phandles are simply converted to 32 bit
> cell values on compile.
>
> For instance take a case of one node using the other:
>
> foo_label: foo { };
> bar { use = <&foo_label>; };
>
> A standard compile would generate a bar node as bar { use = <1>; };
>
> While using the -@ switch you’d get a local ref that says that there is
> a phandle cell value at offset 0 of bar/use property. Looking up the
> phandle value you can see that this property references the foo node.
>
> So when building the probe order graph the foo node should be probed
> before bar.
Yes, you are quite right. During compilation the phandleness of a
phandle reference gets lost - it's just an integer. All you really need
is one bit per word, and you could dream up clever coding schemes for
that sparse map, but extra information is needed.
I still think that __fixups__ is unnecessary for base DTBs, but since it
will be empty anyway it just looks a bit awkward rather than taking up
much space.
Seemingly random load order of device nodes has been a cause of
confusion and annoyance, so I'm happy that there is a plan to eliminate
them. If the DTBs have to grow to make that possible then so be it.
Phil
^ permalink raw reply
* Re: [PATCH 6/10] mmc: sdhci-xenon: Add Marvell Xenon SDHC core functionality
From: Ziji Hu @ 2016-11-24 15:00 UTC (permalink / raw)
To: Ulf Hansson, Adrian Hunter
Cc: Jimmy Xu, Andrew Lunn, Romain Perier, Hanna Hawa, Nadav Haklai,
Zhen Huang, Victor Gu, Doug Jones, Jisheng Zhang, Yehuda Yitschak,
Wei(SOCP) Liu, Kostya Porotchkin, Sebastian Hesselbarth,
devicetree@vger.kernel.org, Jason Cooper, Rob Herring, Ryan Gao,
Gregory CLEMENT, Marcin Wojtas,
linux-arm-kernel@lists.infradead.org, Thomas Petazzoni, linux-mmc
In-Reply-To: <CAPDyKFr9uEjVQmTNP0KK8Zj9mxCW3i564E=47vTK0RLvXCjw3Q@mail.gmail.com>
Hi Ulf,
On 2016/11/24 21:34, Ulf Hansson wrote:
> On 24 November 2016 at 13:41, Ziji Hu <huziji@marvell.com> wrote:
>> Hi Ulf,
>>
>> On 2016/11/24 18:43, Ulf Hansson wrote:
>>> On 31 October 2016 at 12:09, Gregory CLEMENT
>>> <gregory.clement@free-electrons.com> wrote:
>>>> From: Ziji Hu <huziji@marvell.com>
>>>>
>> <snip>
>>>> +static int xenon_emmc_signal_voltage_switch(struct mmc_host *mmc,
>>>> + struct mmc_ios *ios)
>>>> +{
>>>> + unsigned char voltage = ios->signal_voltage;
>>>> +
>>>> + if ((voltage == MMC_SIGNAL_VOLTAGE_330) ||
>>>> + (voltage == MMC_SIGNAL_VOLTAGE_180))
>>>> + return __emmc_signal_voltage_switch(mmc, voltage);
>>>> +
>>>> + dev_err(mmc_dev(mmc), "Unsupported signal voltage: %d\n",
>>>> + voltage);
>>>> + return -EINVAL;
>>>
>>> This wrapper function seems unnessarry. It only adds a dev_err(), so
>>> then might as well do that in __emmc_signal_voltage_switch().
>>>
>> Sure. Will merge it back to __emmc_signal_voltage_switch().
>>
>>>> +}
>>>> +
>>>> +static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>>>> + struct mmc_ios *ios)
>>>> +{
>>>> + struct sdhci_host *host = mmc_priv(mmc);
>>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> + /*
>>>> + * Before SD/SDIO set signal voltage, SD bus clock should be
>>>> + * disabled. However, sdhci_set_clock will also disable the Internal
>>>> + * clock in mmc_set_signal_voltage().
>>>
>>> If that's the case then that is wrong in the generic sdhci code.
>>> What's the reason why it can't be fixed there instead of having this
>>> workaround?
>>>
>> In my very own opinion, SD Spec doesn't specify whether SDCLK should be
>> enabled or not during power setting.
>> Enabling SDCLK might be a special condition only required by our SDHC.
>> I try to avoid breaking other vendors' SDHC functionality
>> if their SDHCs require SDCLK disabled.
>> Thus I prefer to keep it inside our SDHC driver.
>
> I let Adrian comment on this.
>
> For sure we should avoid breaking other sdhci variant, but on the
> other hand *if* the generic code is wrong we should fix it!
>
Of course.
>>
>>>> + * If Internal clock is disabled, the 3.3V/1.8V bit can not be updated.
>>>> + * Thus here manually enable internal clock.
>>>> + *
>>>> + * After switch completes, it is unnecessary to disable internal clock,
>>>> + * since keeping internal clock active obeys SD spec.
>>>> + */
>>>> + enable_xenon_internal_clk(host);
>>>> +
>>>> + if (priv->emmc_slot)
>>>> + return xenon_emmc_signal_voltage_switch(mmc, ios);
>>>> +
>>>> + return sdhci_start_signal_voltage_switch(mmc, ios);
>>>> +}
>>>> +
>>>> +/*
>>>> + * After determining which slot is used for SDIO,
>>>> + * some additional task is required.
>>>> + */
>>>> +static void xenon_init_card(struct mmc_host *mmc, struct mmc_card *card)
>>>> +{
>>>> + struct sdhci_host *host = mmc_priv(mmc);
>>>> + u32 reg;
>>>> + u8 slot_idx;
>>>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>>>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>>>> +
>>>> + /* Link the card for delay adjustment */
>>>> + priv->card_candidate = card;
>>>> + /* Set tuning functionality of this slot */
>>>> + xenon_slot_tuning_setup(host);
>>>
>>> This looks weird. I assume this can be done as a part of the regular
>>> tuning seqeunce!?
>>>
>> It is our SDHC specific preparation prior to tuning, rather than a
>> standard step in spec.
>> Thus I leave it inside our driver.
>
> My point is that this isn't the purpose of ->init_card(). thus you are
> abusing it.
>
> Try to make it work in another way, please. I think you can.
>
Got it.
I will move it to our host specific probe function.
>>
>>>> +
>>>> + slot_idx = priv->slot_idx;
>>>> + if (!mmc_card_sdio(card)) {
>>>> + /* Clear SDIO Card Inserted indication */
>>>
>>> Why do you need this?
>>>
>>> If you need to reset this, I think it's better to do it from
>>> ->set_ios() at MMC_POWER_OFF.
>>>
>> This field indicates SDIO card and controls async interrupt feature
>> of SDIO in our SDHC.
>> This async interrupt feature is enabled when SDIO card is inserted.
>> It should be disabled if SD card is inserted instead.
>
> What do you mean by SDIO async interupts? Are you talking about SDIO
> irqs on DAT1 line?
>
> Those is supposed to be enabled when someone explicitly requests them,
> not when the card is inserted.
> In other words when an SDIO func driver have called sdio_claim_irq().
>
> Moreover, we have ->enable_sdio_irq() ops that deals with this.
>
Yes. I mean the SDIO irqs on DAT1 line in async mode.
This field enables our host to recognize the async SDIO irq from SDIO device.
It controls our host side behavior, other than the SDIO device.
I think ->enable_sdio_irq() is a more reasonable place to put this workraound.
I will export sdhci_enable_sdio_irq() and implement out host own
enable_sdio_irq() calling sdhci_enable_sdio)irq() plus this workaround.
Does it sound reasonable to you?
> [...]
>
>>>> +
>>>> + /*
>>>> + * Xenon Specific property:
>>>> + * emmc: explicitly indicate whether this slot is for eMMC
>>>> + * slotno: the index of slot. Refer to SDHC_SYS_CFG_INFO register
>>>> + * tun-count: the interval between re-tuning
>>>> + * PHY type: "sdhc phy", "emmc phy 5.0" or "emmc phy 5.1"
>>>> + */
>>>> + if (of_property_read_bool(np, "marvell,xenon-emmc"))
>>>> + priv->emmc_slot = true;
>>>
>>> So, you need this because of the eMMC voltage switch behaviour, right?
>>>
>>> Then I would rather like to describe this a generic DT bindings for
>>> the eMMC voltage level support. There have acutally been some earlier
>>> discussions for this, but we haven't yet made some changes.
>>>
>>> I think what is missing is a mmc-ddr-3_3v DT binding, which when set,
>>> allows the host driver to accept I/O voltage switches to 3.3V. If not
>>> supported the ->start_signal_voltage_switch() ops may return -EINVAL.
>>> This would inform the mmc core to move on to the next supported
>>> voltage level. There might be some minor additional changes to the mmc
>>> card initialization sequence, but those should be simple.
>>>
>>> I can help out to look into this, unless you want to do it yourself of course!?
>>>
>> Yes. One of the reasons is to provide eMMC specific voltage setting.
>> But in my very own opinion, it should be irrelevant to voltage level.
>> The eMMC voltage setting on our SDHC is different from SD/SDIO voltage switch.
>> It will become more complex with different SOC implementation details.
>
> Got it. Although I think we can cope with that fine just by using the
> different SD/eMMC speed modes settings defined in DT (or from the
> SDHCI caps register)
>
In my very opinion, I'm not sure if there is any corner case that driver cannot
determine the eMMC card type from DT and SDHC caps.
>> Unfortunately, MMC driver cannot determine the card type yet when eMMC voltage
>> setting should be executed.
>> Thus an flag is required here to tell driver to execute eMMC voltage setting.
>>
>> Besides, additional eMMC specific settings might be implemented in future, besides
>> voltage setting. Most of them should be completed before MMC driver recognizes the
>> card type. Thus I have to keep this flag to indicate current SDHC is for eMMC.
>
> I doubt you will need a generic "eMMC" flag, but let's see when we go forward.
>
> Currently it's clear you don't need such a flag, so I will submit a
> change adding a DT binding for "mmc-ddr-3_3v" then we can take it from
> there, to see if it suits your needs.
>
Actually, our eMMC is usually fixed as 1.8V.
The pair "no-sd" + "no-sdio" can provide the similar information.
But I'm not sure if it is proper to use those two property in such a way.
Thank you.
Best regards
Hu Ziji
> [...]
>
> Kind regards
> Uffe
>
^ permalink raw reply
* [PATCH v5 0/2] DW DMAC: update device tree
From: Eugeniy Paltsev @ 2016-11-24 15:04 UTC (permalink / raw)
To: devicetree
Cc: robh+dt, mark.rutland, linux-kernel, andriy.shevchenko,
vinod.koul, dmaengine, arnd, linux-snps-arc, vireshk,
shiraz.linux.kernel, christian.ruppert, Eugeniy Paltsev
It wasn't possible to enable some features like
memory-to-memory transfers or multi block transfers via DT.
It is fixed by these patches.
Changes for v5:
* Update existing DTS. Pointed by Andy Shevchenko.
* Read multi-block per chanel instead of per master (Move
DW_DMA_MAX_NR_CHANNELS define from regs.h to dma-dw.h to
implement this) Pointed by Andy Shevchenko.
Changes for v4:
* Fix setting inverted value to "dwc->nollp". My fault - I
tested with wrong DTS, so DMAC was configured from autoconfig
instead of device tree. Pointed by Andy Shevchenko.
* Update "multi-block" diescription in documentation to be more
clear. Pointed by Arnd Bergmann.
Changes for v3:
* Update existing platform data.
We don't need to update existing DTS because default logic
wasn't change: we don't set "is_nollp" if we read
configuration from DT before. And we don't set it now if
"multi-block" property doesn't exist in DTS.
Changes for v2:
* I thought about is_memcpy DT property: all known devices, which
use DT for configuration, support memory-to-memory transfers.
So we don't need to read it from DT. So enable it by default,
if we read configuration from DT.
* Use "multi-block" instead of "hw-llp" name to be more clear.
* Move adding DT property and adding documentation for this
property to one patch.
Eugeniy Paltsev (2):
DW DMAC: enable memory-to-memory transfers support
DW DMAC: add multi-block property to device tree
Documentation/devicetree/bindings/dma/snps-dma.txt | 2 ++
arch/arc/boot/dts/abilis_tb10x.dtsi | 1 +
arch/arm/boot/dts/spear13xx.dtsi | 2 ++
drivers/dma/dw/core.c | 2 +-
drivers/dma/dw/platform.c | 15 ++++++++++++++-
drivers/dma/dw/regs.h | 3 ++-
drivers/tty/serial/8250/8250_lpss.c | 2 +-
include/linux/platform_data/dma-dw.h | 5 +++--
8 files changed, 26 insertions(+), 6 deletions(-)
--
2.5.5
^ permalink raw reply
* [PATCH v5 1/2] DW DMAC: enable memory-to-memory transfers support
From: Eugeniy Paltsev @ 2016-11-24 15:04 UTC (permalink / raw)
To: devicetree
Cc: mark.rutland, linux-snps-arc, christian.ruppert, arnd, vinod.koul,
vireshk, linux-kernel, Eugeniy Paltsev, robh+dt, dmaengine,
andriy.shevchenko, shiraz.linux.kernel
In-Reply-To: <1479999878-19120-1-git-send-email-Eugeniy.Paltsev@synopsys.com>
All known devices, which use DT for configuration, support
memory-to-memory transfers. So enable it by default, if we read
configuration from DT.
Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
---
drivers/dma/dw/platform.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/dma/dw/platform.c b/drivers/dma/dw/platform.c
index 5bda0eb..aa7a5c1 100644
--- a/drivers/dma/dw/platform.c
+++ b/drivers/dma/dw/platform.c
@@ -129,6 +129,12 @@ dw_dma_parse_dt(struct platform_device *pdev)
if (of_property_read_bool(np, "is_private"))
pdata->is_private = true;
+ /*
+ * All known devices, which use DT for configuration, support
+ * memory-to-memory transfers. So enable it by default.
+ */
+ pdata->is_memcpy = true;
+
if (!of_property_read_u32(np, "chan_allocation_order", &tmp))
pdata->chan_allocation_order = (unsigned char)tmp;
--
2.5.5
^ permalink raw reply related
* [PATCH v5 2/2] DW DMAC: add multi-block property to device tree
From: Eugeniy Paltsev @ 2016-11-24 15:04 UTC (permalink / raw)
To: devicetree
Cc: mark.rutland, linux-snps-arc, christian.ruppert, arnd, vinod.koul,
vireshk, linux-kernel, Eugeniy Paltsev, robh+dt, dmaengine,
andriy.shevchenko, shiraz.linux.kernel
In-Reply-To: <1479999878-19120-1-git-send-email-Eugeniy.Paltsev@synopsys.com>
Several versions of DW DMAC have multi block transfers hardware
support. Hardware support of multi block transfers is disabled
by default if we use DT to configure DMAC and software emulation
of multi block transfers used instead.
Add multi-block property, so it is possible to enable hardware
multi block transfers (if present) via DT.
Switch from per device is_nollp variable to multi_block array
to be able enable/disable multi block transfers separately per
channel.
Signed-off-by: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
---
Also:
Update DT documentation.
Update existing platform data and DTS.
Documentation/devicetree/bindings/dma/snps-dma.txt | 2 ++
arch/arc/boot/dts/abilis_tb10x.dtsi | 1 +
arch/arm/boot/dts/spear13xx.dtsi | 2 ++
drivers/dma/dw/core.c | 2 +-
drivers/dma/dw/platform.c | 9 ++++++++-
drivers/dma/dw/regs.h | 3 ++-
drivers/tty/serial/8250/8250_lpss.c | 2 +-
include/linux/platform_data/dma-dw.h | 5 +++--
8 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/Documentation/devicetree/bindings/dma/snps-dma.txt b/Documentation/devicetree/bindings/dma/snps-dma.txt
index 0f55832..aeb90d1 100644
--- a/Documentation/devicetree/bindings/dma/snps-dma.txt
+++ b/Documentation/devicetree/bindings/dma/snps-dma.txt
@@ -27,6 +27,8 @@ Optional properties:
that services interrupts for this device
- is_private: The device channels should be marked as private and not for by the
general purpose DMA channel allocator. False if not passed.
+- multi-block: Multi block transfers supported by hardware. Array property with
+ one cell per channel. 0 (default): not supported, 1: supported.
Example:
diff --git a/arch/arc/boot/dts/abilis_tb10x.dtsi b/arch/arc/boot/dts/abilis_tb10x.dtsi
index de53f5c..3121536 100644
--- a/arch/arc/boot/dts/abilis_tb10x.dtsi
+++ b/arch/arc/boot/dts/abilis_tb10x.dtsi
@@ -129,6 +129,7 @@
data-width = <4>;
clocks = <&ahb_clk>;
clock-names = "hclk";
+ multi-block = <1 1 1 1 1 1>;
};
i2c0: i2c@FF120000 {
diff --git a/arch/arm/boot/dts/spear13xx.dtsi b/arch/arm/boot/dts/spear13xx.dtsi
index 449acf0..17ea0ab 100644
--- a/arch/arm/boot/dts/spear13xx.dtsi
+++ b/arch/arm/boot/dts/spear13xx.dtsi
@@ -118,6 +118,7 @@
block_size = <0xfff>;
dma-masters = <2>;
data-width = <8 8>;
+ multi-block = <1 1 1 1 1 1 1 1>;
};
dma@eb000000 {
@@ -134,6 +135,7 @@
chan_priority = <1>;
block_size = <0xfff>;
data-width = <8 8>;
+ multi-block = <1 1 1 1 1 1 1 1>;
};
fsmc: flash@b0000000 {
diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c
index c2c0a61..e5adf5d 100644
--- a/drivers/dma/dw/core.c
+++ b/drivers/dma/dw/core.c
@@ -1569,7 +1569,7 @@ int dw_dma_probe(struct dw_dma_chip *chip)
(dwc_params >> DWC_PARAMS_MBLK_EN & 0x1) == 0;
} else {
dwc->block_size = pdata->block_size;
- dwc->nollp = pdata->is_nollp;
+ dwc->nollp = !pdata->multi_block[i];
}
}
diff --git a/drivers/dma/dw/platform.c b/drivers/dma/dw/platform.c
index aa7a5c1..80e161f 100644
--- a/drivers/dma/dw/platform.c
+++ b/drivers/dma/dw/platform.c
@@ -102,7 +102,7 @@ dw_dma_parse_dt(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct dw_dma_platform_data *pdata;
- u32 tmp, arr[DW_DMA_MAX_NR_MASTERS];
+ u32 tmp, arr[DW_DMA_MAX_NR_MASTERS], chan[DW_DMA_MAX_NR_CHANNELS];
u32 nr_masters;
u32 nr_channels;
@@ -118,6 +118,8 @@ dw_dma_parse_dt(struct platform_device *pdev)
if (of_property_read_u32(np, "dma-channels", &nr_channels))
return NULL;
+ if (nr_channels > DW_DMA_MAX_NR_CHANNELS)
+ return NULL;
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
@@ -152,6 +154,11 @@ dw_dma_parse_dt(struct platform_device *pdev)
pdata->data_width[tmp] = BIT(arr[tmp] & 0x07);
}
+ if (!of_property_read_u32_array(np, "multi-block", chan, nr_channels)) {
+ for (tmp = 0; tmp < nr_channels; tmp++)
+ pdata->multi_block[tmp] = chan[tmp];
+ }
+
return pdata;
}
#else
diff --git a/drivers/dma/dw/regs.h b/drivers/dma/dw/regs.h
index f65dd10..4e0128c 100644
--- a/drivers/dma/dw/regs.h
+++ b/drivers/dma/dw/regs.h
@@ -12,7 +12,8 @@
#include <linux/interrupt.h>
#include <linux/dmaengine.h>
-#define DW_DMA_MAX_NR_CHANNELS 8
+#include "internal.h"
+
#define DW_DMA_MAX_NR_REQUESTS 16
/* flow controller */
diff --git a/drivers/tty/serial/8250/8250_lpss.c b/drivers/tty/serial/8250/8250_lpss.c
index f607946..58cbb30 100644
--- a/drivers/tty/serial/8250/8250_lpss.c
+++ b/drivers/tty/serial/8250/8250_lpss.c
@@ -157,12 +157,12 @@ static int byt_serial_setup(struct lpss8250 *lpss, struct uart_port *port)
static const struct dw_dma_platform_data qrk_serial_dma_pdata = {
.nr_channels = 2,
.is_private = true,
- .is_nollp = true,
.chan_allocation_order = CHAN_ALLOCATION_ASCENDING,
.chan_priority = CHAN_PRIORITY_ASCENDING,
.block_size = 4095,
.nr_masters = 1,
.data_width = {4},
+ .multi_block = {0},
};
static void qrk_serial_setup_dma(struct lpss8250 *lpss, struct uart_port *port)
diff --git a/include/linux/platform_data/dma-dw.h b/include/linux/platform_data/dma-dw.h
index 5f0e11e..e69e415 100644
--- a/include/linux/platform_data/dma-dw.h
+++ b/include/linux/platform_data/dma-dw.h
@@ -14,6 +14,7 @@
#include <linux/device.h>
#define DW_DMA_MAX_NR_MASTERS 4
+#define DW_DMA_MAX_NR_CHANNELS 8
/**
* struct dw_dma_slave - Controller-specific information about a slave
@@ -40,19 +41,18 @@ struct dw_dma_slave {
* @is_private: The device channels should be marked as private and not for
* by the general purpose DMA channel allocator.
* @is_memcpy: The device channels do support memory-to-memory transfers.
- * @is_nollp: The device channels does not support multi block transfers.
* @chan_allocation_order: Allocate channels starting from 0 or 7
* @chan_priority: Set channel priority increasing from 0 to 7 or 7 to 0.
* @block_size: Maximum block size supported by the controller
* @nr_masters: Number of AHB masters supported by the controller
* @data_width: Maximum data width supported by hardware per AHB master
* (in bytes, power of 2)
+ * @multi_block: Multi block transfers supported by hardware per channel.
*/
struct dw_dma_platform_data {
unsigned int nr_channels;
bool is_private;
bool is_memcpy;
- bool is_nollp;
#define CHAN_ALLOCATION_ASCENDING 0 /* zero to seven */
#define CHAN_ALLOCATION_DESCENDING 1 /* seven to zero */
unsigned char chan_allocation_order;
@@ -62,6 +62,7 @@ struct dw_dma_platform_data {
unsigned int block_size;
unsigned char nr_masters;
unsigned char data_width[DW_DMA_MAX_NR_MASTERS];
+ unsigned char multi_block[DW_DMA_MAX_NR_CHANNELS];
};
#endif /* _PLATFORM_DATA_DMA_DW_H */
--
2.5.5
^ permalink raw reply related
* Re: [RFC PATCH] ARM: dts: Add support for Turris Omnia
From: Andrew Lunn @ 2016-11-24 15:07 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Tomas Hlavacek, Rob Herring, Mark Rutland, Russell King,
Jason Cooper, Gregory Clement, Sebastian Hesselbarth, devicetree,
linux-arm-kernel, linux-kernel
In-Reply-To: <4e3f9628-dbf8-27c1-abea-d0ef58a67e51@kleine-koenig.org>
> @Tomas: I think it doesn't make sense when we alternate sending patches
> without prior arrangement. Do you already work on a v5? If not I can do
> that to fix the last few comments. Not sure when a submission is too
> late to enter v4.10, but I think the window isn't that big any more.
It is getting a bit late. But maybe Linus will add in another -rc
week.
>
> > No leds? No buttons via gpio-keys?
>
> The leds are controlled by a Cortex-M0 and without intervention blink
> according to a hardware function (network, power, pci). IMHO that's ok
> for an initial setup.
Yes. That is fine. It is just unusual. Most boards have gpio-led and
gpio-keys, which are easy to add. That is why i asked. Adding an LED
driver which talks to this M0 can be added later.
Andrew
^ permalink raw reply
* [PATCH v2 0/7] Add pwm and IIO timer drivers for stm32
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones-QSEj5FYQhm4dnm+yROfE0A, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, alexandre.torgue-qxv4g6HH51o,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
linux-pwm-u79uwXL29TY76Z2rM5mHXA, jic23-DgEjT+Ai2ygdnm+yROfE0A,
knaack.h-Mmb7MZpHnFY, lars-Qo5EllUWu/uELgA04lAiVw,
pmeerw-jW+XmwGofnusTnJN9+BGXg, linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: fabrice.gasnier-qxv4g6HH51o, gerald.baeza-qxv4g6HH51o,
arnaud.pouliquen-qxv4g6HH51o,
linus.walleij-QSEj5FYQhm4dnm+yROfE0A,
linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
benjamin.gaignard-QSEj5FYQhm4dnm+yROfE0A, Benjamin Gaignard
version 2:
- keep only one compatible per driver
- use DT parameters to describe hardware block configuration:
- pwm channels, complementary output, counter size, break input
- triggers accepted and create by IIO timers
- change DT to limite use of reference to the node
- interrupt is now in IIO timer driver
- rename stm32-mfd-timer to stm32-gptimer (for general purpose timer)
The following patches enable pwm and IIO Timer features for stm32 platforms.
Those two features are mixed into the registers of the same hardware block
(named general purpose timer) which lead to introduce a multifunctions driver
on the top of them to be able to share the registers.
In stm32 14 instances of timer hardware block exist, even if they all have
the same register mapping they could have a different number of pwm channels
and/or different triggers capabilities. We use various parameters in DT to
describe the differences between hardware blocks
The MFD (stm32-gptimer.c) takes care of clock and register mapping
by using regmap. stm32_gptimer_dev structure is provided to its sub-node to
share those information.
PWM driver is implemented into pwm-stm32.c. Depending of the instance we may
have up to 4 channels, sometime with complementary outputs or 32 bits counter
instead of 16 bits. Some hardware blocks may also have a break input function
which allows to stop pwm depending of a level, defined in devicetree, on an
external pin.
IIO timer driver (stm32-iio-timer.c and stm32-iio-timers.h) define a list of
hardware triggers usable by hardware blocks like ADC, DAC or other timers.
The matrix of possible connections between blocks is quite complex so we use
trigger names and is_stm32_iio_timer_trigger() function to be sure that
triggers are valid and configure the IPs.
Possible triggers ar listed in include/dt-bindings/iio/timer/st,stm32-iio-timer.h
At run time IIO timer hardware blocks can configure (through "master_mode"
IIO device attribute) which internal signal (counter enable, reset,
comparison block, etc...) is used to generate the trigger.
By using "slave_mode" IIO device attribute timer can also configure on which
event (level, rising edge) of the block is enabled.
Since we can use trigger from one hardware to control an other block, we can
use a pwm to control an other one. The following example shows how to configure
pwm1 and pwm3 to make pwm3 generate pulse only when pwm1 pulse level is high.
/sys/bus/iio/devices # ls
iio:device0 iio:device1 trigger0 trigger1
configure timer1 to use pwm1 channel 0 as output trigger
/sys/bus/iio/devices # echo 4 > iio\:device0/master_mode
configure timer3 to enable only when input is high
/sys/bus/iio/devices # echo 5 > iio\:device1/slave_mode
/sys/bus/iio/devices # cat trigger0/name
tim1_trgo
configure timer2 to use timer1 trigger is input
/sys/bus/iio/devices # echo "tim1_trgo" > iio\:device1/trigger/current_trigger
configure pwm3 channel 0 to generate a signal with a period of 100ms and a
duty cycle of 50%
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4 # echo 0 > export
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4 # echo 100000000 > pwm0/period
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4 # echo 50000000 > pwm0/duty_cycle
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4# echo 1 > pwm0/enable
here pwm3 channel 0, as expected, doesn't start because has to be triggered by
pwm1 channel 0
configure pwm1 channel 0 to generate a signal with a period of 1s and a
duty cycle of 50%
/sys/devices/platform/soc/40010000.gptimer1/40010000.gptimer1:pwm1@0/pwm/pwmchip0 # echo 0 > export
/sys/devices/platform/soc/40010000.gptimer1/40010000.gptimer1:pwm1@0/pwm/pwmchip0 # echo 1000000000 > pwm0/period
/sys/devices/platform/soc/40010000.gptimer1/40010000.gptimer1:pwm1@0/pwm/pwmchip0 # echo 500000000 > pwm0/duty_cycle
/sys/devices/platform/soc/40010000.gptimer1/40010000.gptimer1:pwm1@0/pwm/pwmchip0 # echo 1 > pwm0/enable
finally pwm1 starts and pwm3 only generates pulse when pwm1 signal is high
An other example to use a timer as source of clock for another device.
Here timer1 is used a source clock for pwm3:
/sys/bus/iio/devices # echo 100000 > trigger0/sampling_frequency
/sys/bus/iio/devices # echo tim1_trgo > iio\:device1/trigger/current_trigger
/sys/bus/iio/devices # echo 7 > iio\:device1/slave_mode
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4 # echo 0 > export
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4 # echo 1000000 > pwm0/period
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4 # echo 500000 > pwm0/duty_cycle
/sys/devices/platform/soc/40000400.gptimer3/40000400.gptimer3:pwm3@0/pwm/pwmchip4 # echo 1 > pwm0/enable
Benjamin Gaignard (7):
MFD: add bindings for stm32 general purpose timer driver
MFD: add stm32 general purpose timer driver
PWM: add pwm-stm32 DT bindings
PWM: add pwm driver for stm32 plaftorm
IIO: add bindings for stm32 IIO timer driver
IIO: add STM32 IIO timer driver
ARM: dts: stm32: add stm32 general purpose timer driver in DT
.../bindings/iio/timer/stm32-iio-timer.txt | 41 ++
.../bindings/mfd/stm32-general-purpose-timer.txt | 43 ++
.../devicetree/bindings/pwm/pwm-stm32.txt | 37 ++
arch/arm/boot/dts/stm32f429.dtsi | 305 +++++++++++++-
arch/arm/boot/dts/stm32f469-disco.dts | 28 ++
drivers/iio/Kconfig | 2 +-
drivers/iio/Makefile | 1 +
drivers/iio/timer/Kconfig | 15 +
drivers/iio/timer/Makefile | 1 +
drivers/iio/timer/stm32-iio-timer.c | 448 +++++++++++++++++++++
drivers/iio/trigger/Kconfig | 1 -
drivers/mfd/Kconfig | 10 +
drivers/mfd/Makefile | 2 +
drivers/mfd/stm32-gptimer.c | 73 ++++
drivers/pwm/Kconfig | 8 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-stm32.c | 285 +++++++++++++
include/dt-bindings/iio/timer/st,stm32-iio-timer.h | 23 ++
include/linux/iio/timer/stm32-iio-timers.h | 16 +
include/linux/mfd/stm32-gptimer.h | 62 +++
20 files changed, 1399 insertions(+), 3 deletions(-)
create mode 100644 Documentation/devicetree/bindings/iio/timer/stm32-iio-timer.txt
create mode 100644 Documentation/devicetree/bindings/mfd/stm32-general-purpose-timer.txt
create mode 100644 Documentation/devicetree/bindings/pwm/pwm-stm32.txt
create mode 100644 drivers/iio/timer/Kconfig
create mode 100644 drivers/iio/timer/Makefile
create mode 100644 drivers/iio/timer/stm32-iio-timer.c
create mode 100644 drivers/mfd/stm32-gptimer.c
create mode 100644 drivers/pwm/pwm-stm32.c
create mode 100644 include/dt-bindings/iio/timer/st,stm32-iio-timer.h
create mode 100644 include/linux/iio/timer/stm32-iio-timers.h
create mode 100644 include/linux/mfd/stm32-gptimer.h
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH v2 1/7] MFD: add bindings for stm32 general purpose timer driver
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones, robh+dt, mark.rutland, alexandre.torgue, devicetree,
linux-kernel, thierry.reding, linux-pwm, jic23, knaack.h, lars,
pmeerw, linux-iio, linux-arm-kernel
Cc: linaro-kernel, Benjamin Gaignard, linus.walleij, arnaud.pouliquen,
benjamin.gaignard, gerald.baeza, fabrice.gasnier
In-Reply-To: <1480000463-9625-1-git-send-email-benjamin.gaignard@st.com>
Add bindings information for stm32 general purpose timer
version 2:
- rename stm32-mfd-timer to stm32-gptimer
- only keep one compatible string
Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
.../bindings/mfd/stm32-general-purpose-timer.txt | 43 ++++++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/stm32-general-purpose-timer.txt
diff --git a/Documentation/devicetree/bindings/mfd/stm32-general-purpose-timer.txt b/Documentation/devicetree/bindings/mfd/stm32-general-purpose-timer.txt
new file mode 100644
index 0000000..2f10e67
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/stm32-general-purpose-timer.txt
@@ -0,0 +1,43 @@
+STM32 general purpose timer driver
+
+Required parameters:
+- compatible: must be "st,stm32-gptimer"
+
+- reg: Physical base address and length of the controller's
+ registers.
+- clock-names: Set to "clk_int".
+- clocks: Phandle to the clock used by the timer module.
+ For Clk properties, please refer to ../clock/clock-bindings.txt
+
+Optional parameters:
+- resets: Phandle to the parent reset controller.
+ See ..reset/st,stm32-rcc.txt
+
+Optional subnodes:
+- pwm: See ../pwm/pwm-stm32.txt
+- iiotimer: See ../iio/timer/stm32-iio-timer.txt
+
+Example:
+ gptimer1: gptimer1@40010000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40010000 0x400>;
+ clocks = <&rcc 0 160>;
+ clock-names = "clk_int";
+
+ pwm1@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ st,breakinput;
+ st,complementary;
+ };
+
+ iiotimer1@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <27>;
+ st,input-triggers-names = TIM5_TRGO,
+ TIM2_TRGO,
+ TIM4_TRGO,
+ TIM3_TRGO;
+ st,output-triggers-names = TIM1_TRGO;
+ };
+ };
--
1.9.1
^ permalink raw reply related
* [PATCH v2 2/7] MFD: add stm32 general purpose timer driver
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones, robh+dt, mark.rutland, alexandre.torgue, devicetree,
linux-kernel, thierry.reding, linux-pwm, jic23, knaack.h, lars,
pmeerw, linux-iio, linux-arm-kernel
Cc: linaro-kernel, Benjamin Gaignard, linus.walleij, arnaud.pouliquen,
benjamin.gaignard, gerald.baeza, fabrice.gasnier
In-Reply-To: <1480000463-9625-1-git-send-email-benjamin.gaignard@st.com>
This hardware block could at used at same time for PWM generation
and IIO timer for other IPs like DAC, ADC or other timers.
PWM and IIO timer configuration are mixed in the same registers
so we need a multi fonction driver to be able to share those registers.
version 2:
- rename driver "stm32-gptimer" to be align with SoC documentation
- only keep one compatible
- use of_platform_populate() instead of devm_mfd_add_devices()
Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
drivers/mfd/Kconfig | 10 ++++++
drivers/mfd/Makefile | 2 ++
drivers/mfd/stm32-gptimer.c | 73 +++++++++++++++++++++++++++++++++++++++
include/linux/mfd/stm32-gptimer.h | 62 +++++++++++++++++++++++++++++++++
4 files changed, 147 insertions(+)
create mode 100644 drivers/mfd/stm32-gptimer.c
create mode 100644 include/linux/mfd/stm32-gptimer.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c6df644..e75abcb 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1607,6 +1607,15 @@ config MFD_STW481X
in various ST Microelectronics and ST-Ericsson embedded
Nomadik series.
+config MFD_STM32_GP_TIMER
+ tristate "Support for STM32 General Purpose Timer"
+ select MFD_CORE
+ select REGMAP
+ depends on ARCH_STM32
+ depends on OF
+ help
+ Select this option to enable stm32 general purpose timer
+
menu "Multimedia Capabilities Port drivers"
depends on ARCH_SA1100
@@ -1644,4 +1653,5 @@ config MFD_VEXPRESS_SYSREG
on the ARM Ltd. Versatile Express board.
endmenu
+
endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 9834e66..86353b9 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -211,3 +211,5 @@ obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o
obj-$(CONFIG_MFD_MT6397) += mt6397-core.o
obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o
+
+obj-$(CONFIG_MFD_STM32_GP_TIMER) += stm32-gptimer.o
diff --git a/drivers/mfd/stm32-gptimer.c b/drivers/mfd/stm32-gptimer.c
new file mode 100644
index 0000000..54fb95c
--- /dev/null
+++ b/drivers/mfd/stm32-gptimer.c
@@ -0,0 +1,73 @@
+/*
+ * stm32-gptimer.c
+ *
+ * Copyright (C) STMicroelectronics 2016
+ * Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/reset.h>
+
+#include <linux/mfd/stm32-gptimer.h>
+
+static const struct regmap_config stm32_gptimer_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = sizeof(u32),
+ .max_register = 0x400,
+ .fast_io = true,
+};
+
+static int stm32_gptimer_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct stm32_gptimer_dev *mfd;
+ struct resource *res;
+ void __iomem *mmio;
+
+ mfd = devm_kzalloc(dev, sizeof(*mfd), GFP_KERNEL);
+ if (!mfd)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOMEM;
+
+ mmio = devm_ioremap_resource(dev, res);
+ if (IS_ERR(mmio))
+ return PTR_ERR(mmio);
+
+ mfd->regmap = devm_regmap_init_mmio_clk(dev, "clk_int", mmio,
+ &stm32_gptimer_regmap_cfg);
+ if (IS_ERR(mfd->regmap))
+ return PTR_ERR(mfd->regmap);
+
+ mfd->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(mfd->clk))
+ return PTR_ERR(mfd->clk);
+
+ platform_set_drvdata(pdev, mfd);
+
+ return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+}
+
+static const struct of_device_id stm32_gptimer_of_match[] = {
+ {
+ .compatible = "st,stm32-gptimer",
+ },
+};
+MODULE_DEVICE_TABLE(of, stm32_gptimer_of_match);
+
+static struct platform_driver stm32_gptimer_driver = {
+ .probe = stm32_gptimer_probe,
+ .driver = {
+ .name = "stm32-gptimer",
+ .of_match_table = stm32_gptimer_of_match,
+ },
+};
+module_platform_driver(stm32_gptimer_driver);
+
+MODULE_DESCRIPTION("STMicroelectronics STM32 general purpose timer");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/stm32-gptimer.h b/include/linux/mfd/stm32-gptimer.h
new file mode 100644
index 0000000..f8c92de
--- /dev/null
+++ b/include/linux/mfd/stm32-gptimer.h
@@ -0,0 +1,62 @@
+/*
+ * stm32-gptimer.h
+ *
+ * Copyright (C) STMicroelectronics 2016
+ * Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef _LINUX_STM32_GPTIMER_H_
+#define _LINUX_STM32_GPTIMER_H_
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+#define TIM_CR1 0x00 /* Control Register 1 */
+#define TIM_CR2 0x04 /* Control Register 2 */
+#define TIM_SMCR 0x08 /* Slave mode control reg */
+#define TIM_DIER 0x0C /* DMA/interrupt register */
+#define TIM_SR 0x10 /* Status register */
+#define TIM_EGR 0x14 /* Event Generation Reg */
+#define TIM_CCMR1 0x18 /* Capt/Comp 1 Mode Reg */
+#define TIM_CCMR2 0x1C /* Capt/Comp 2 Mode Reg */
+#define TIM_CCER 0x20 /* Capt/Comp Enable Reg */
+#define TIM_PSC 0x28 /* Prescaler */
+#define TIM_ARR 0x2c /* Auto-Reload Register */
+#define TIM_CCR1 0x34 /* Capt/Comp Register 1 */
+#define TIM_CCR2 0x38 /* Capt/Comp Register 2 */
+#define TIM_CCR3 0x3C /* Capt/Comp Register 3 */
+#define TIM_CCR4 0x40 /* Capt/Comp Register 4 */
+#define TIM_BDTR 0x44 /* Break and Dead-Time Reg */
+
+#define TIM_CR1_CEN BIT(0) /* Counter Enable */
+#define TIM_CR1_ARPE BIT(7) /* Auto-reload Preload Ena */
+#define TIM_CR2_MMS (BIT(4) | BIT(5) | BIT(6)) /* Master mode selection */
+#define TIM_SMCR_SMS (BIT(0) | BIT(1) | BIT(2)) /* Slave mode selection */
+#define TIM_SMCR_TS (BIT(4) | BIT(5) | BIT(6)) /* Trigger selection */
+#define TIM_DIER_UIE BIT(0) /* Update interrupt */
+#define TIM_SR_UIF BIT(0) /* Update interrupt flag */
+#define TIM_EGR_UG BIT(0) /* Update Generation */
+#define TIM_CCMR_PE BIT(3) /* Channel Preload Enable */
+#define TIM_CCMR_M1 (BIT(6) | BIT(5)) /* Channel PWM Mode 1 */
+#define TIM_CCER_CC1E BIT(0) /* Capt/Comp 1 out Ena */
+#define TIM_CCER_CC1P BIT(1) /* Capt/Comp 1 Polarity */
+#define TIM_CCER_CC1NE BIT(2) /* Capt/Comp 1N out Ena */
+#define TIM_CCER_CC1NP BIT(3) /* Capt/Comp 1N Polarity */
+#define TIM_CCER_CCXE (BIT(0) | BIT(4) | BIT(8) | BIT(12))
+#define TIM_BDTR_BKE BIT(12) /* Break input enable */
+#define TIM_BDTR_BKP BIT(13) /* Break input polarity */
+#define TIM_BDTR_AOE BIT(14) /* Automatic Output Enable */
+#define TIM_BDTR_MOE BIT(15) /* Main Output Enable */
+
+#define MAX_TIM_PSC 0xFFFF
+
+struct stm32_gptimer_dev {
+ /* Device data */
+ struct clk *clk;
+
+ /* Registers mapping */
+ struct regmap *regmap;
+};
+
+#endif
--
1.9.1
^ permalink raw reply related
* [PATCH v2 3/7] PWM: add pwm-stm32 DT bindings
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones, robh+dt, mark.rutland, alexandre.torgue, devicetree,
linux-kernel, thierry.reding, linux-pwm, jic23, knaack.h, lars,
pmeerw, linux-iio, linux-arm-kernel
Cc: fabrice.gasnier, gerald.baeza, arnaud.pouliquen, linus.walleij,
linaro-kernel, benjamin.gaignard, Benjamin Gaignard
In-Reply-To: <1480000463-9625-1-git-send-email-benjamin.gaignard@st.com>
Define bindings for pwm-stm32
version 2:
- use parameters instead of compatible of handle the hardware configuration
Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
.../devicetree/bindings/pwm/pwm-stm32.txt | 37 ++++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/pwm-stm32.txt
diff --git a/Documentation/devicetree/bindings/pwm/pwm-stm32.txt b/Documentation/devicetree/bindings/pwm/pwm-stm32.txt
new file mode 100644
index 0000000..36263f0
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/pwm-stm32.txt
@@ -0,0 +1,37 @@
+STMicroelectronics PWM driver bindings for STM32
+
+Must be a sub-node of STM32 general purpose timer driver
+
+Required parameters:
+- compatible: Must be "st,stm32-pwm"
+- pinctrl-names: Set to "default".
+- pinctrl-0: List of phandles pointing to pin configuration nodes
+ for PWM module.
+ For Pinctrl properties, please refer to [1].
+
+Optional parameters:
+- st,breakinput: Set if the hardware have break input capabilities
+- st,breakinput-polarity: Set break input polarity. Default is 0
+ The value define the active polarity:
+ - 0 (active LOW)
+ - 1 (active HIGH)
+- st,pwm-num-chan: Number of available PWM channels. Default is 0.
+- st,32bits-counter: Set if the hardware have a 32 bits counter
+- st,complementary: Set if the hardware have complementary output channels
+
+[1] Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+
+Example:
+ gptimer1: gptimer1@40010000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40010000 0x400>;
+ clocks = <&rcc 0 160>;
+ clock-names = "clk_int";
+
+ pwm1@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ st,breakinput;
+ st,complementary;
+ };
+ };
--
1.9.1
^ permalink raw reply related
* [PATCH v2 4/7] PWM: add pwm driver for stm32 plaftorm
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones, robh+dt, mark.rutland, alexandre.torgue, devicetree,
linux-kernel, thierry.reding, linux-pwm, jic23, knaack.h, lars,
pmeerw, linux-iio, linux-arm-kernel
Cc: fabrice.gasnier, gerald.baeza, arnaud.pouliquen, linus.walleij,
linaro-kernel, benjamin.gaignard, Benjamin Gaignard
In-Reply-To: <1480000463-9625-1-git-send-email-benjamin.gaignard@st.com>
This driver add support for pwm driver on stm32 platform.
The SoC have multiple instances of the hardware IP and each
of them could have small differences: number of channels,
complementary output, counter register size...
Use DT parameters to handle those differentes configuration
version 2:
- only keep one comptatible
- use DT paramaters to discover hardware block configuration
Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
drivers/pwm/Kconfig | 8 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-stm32.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 294 insertions(+)
create mode 100644 drivers/pwm/pwm-stm32.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index bf01288..a89fdba 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -388,6 +388,14 @@ config PWM_STI
To compile this driver as a module, choose M here: the module
will be called pwm-sti.
+config PWM_STM32
+ bool "STMicroelectronics STM32 PWM"
+ depends on ARCH_STM32
+ depends on OF
+ select MFD_STM32_GP_TIMER
+ help
+ Generic PWM framework driver for STM32 SoCs.
+
config PWM_STMPE
bool "STMPE expander PWM export"
depends on MFD_STMPE
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 1194c54..5aa9308 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
obj-$(CONFIG_PWM_STI) += pwm-sti.o
+obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c
new file mode 100644
index 0000000..a362f63
--- /dev/null
+++ b/drivers/pwm/pwm-stm32.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) STMicroelectronics 2016
+ * Author: Gerald Baeza <gerald.baeza@st.com>
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Inspired by timer-stm32.c from Maxime Coquelin
+ * pwm-atmel.c from Bo Shen
+ */
+
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#include <linux/mfd/stm32-gptimer.h>
+
+#define DRIVER_NAME "stm32-pwm"
+
+#define CAP_COMPLEMENTARY BIT(0)
+#define CAP_32BITS_COUNTER BIT(1)
+#define CAP_BREAKINPUT BIT(2)
+#define CAP_BREAKINPUT_POLARITY BIT(3)
+
+struct stm32_pwm_dev {
+ struct device *dev;
+ struct clk *clk;
+ struct regmap *regmap;
+ struct pwm_chip chip;
+ int caps;
+ int npwm;
+ u32 polarity;
+};
+
+#define to_stm32_pwm_dev(x) container_of(chip, struct stm32_pwm_dev, chip)
+
+static u32 __active_channels(struct stm32_pwm_dev *pwm_dev)
+{
+ u32 ccer;
+
+ regmap_read(pwm_dev->regmap, TIM_CCER, &ccer);
+
+ return ccer & TIM_CCER_CCXE;
+}
+
+static int write_ccrx(struct stm32_pwm_dev *dev, struct pwm_device *pwm,
+ u32 ccr)
+{
+ switch (pwm->hwpwm) {
+ case 0:
+ return regmap_write(dev->regmap, TIM_CCR1, ccr);
+ case 1:
+ return regmap_write(dev->regmap, TIM_CCR2, ccr);
+ case 2:
+ return regmap_write(dev->regmap, TIM_CCR3, ccr);
+ case 3:
+ return regmap_write(dev->regmap, TIM_CCR4, ccr);
+ }
+ return -EINVAL;
+}
+
+static int stm32_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ int duty_ns, int period_ns)
+{
+ struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);
+ unsigned long long prd, div, dty;
+ int prescaler = 0;
+ u32 max_arr = 0xFFFF, ccmr, mask, shift, bdtr;
+
+ if (dev->caps & CAP_32BITS_COUNTER)
+ max_arr = 0xFFFFFFFF;
+
+ /* Period and prescaler values depends of clock rate */
+ div = (unsigned long long)clk_get_rate(dev->clk) * period_ns;
+
+ do_div(div, NSEC_PER_SEC);
+ prd = div;
+
+ while (div > max_arr) {
+ prescaler++;
+ div = prd;
+ do_div(div, (prescaler + 1));
+ }
+ prd = div;
+
+ if (prescaler > MAX_TIM_PSC) {
+ dev_err(chip->dev, "prescaler exceeds the maximum value\n");
+ return -EINVAL;
+ }
+
+ /* All channels share the same prescaler and counter so
+ * when two channels are active at the same we can't change them
+ */
+ if (__active_channels(dev) & ~(1 << pwm->hwpwm * 4)) {
+ u32 psc, arr;
+
+ regmap_read(dev->regmap, TIM_PSC, &psc);
+ regmap_read(dev->regmap, TIM_ARR, &arr);
+
+ if ((psc != prescaler) || (arr != prd - 1))
+ return -EINVAL;
+ }
+
+ regmap_write(dev->regmap, TIM_PSC, prescaler);
+ regmap_write(dev->regmap, TIM_ARR, prd - 1);
+ regmap_update_bits(dev->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE);
+
+ /* Calculate the duty cycles */
+ dty = prd * duty_ns;
+ do_div(dty, period_ns);
+
+ write_ccrx(dev, pwm, dty);
+
+ /* Configure output mode */
+ shift = (pwm->hwpwm & 0x1) * 8;
+ ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
+ mask = 0xFF << shift;
+
+ if (pwm->hwpwm & 0x2)
+ regmap_update_bits(dev->regmap, TIM_CCMR2, mask, ccmr);
+ else
+ regmap_update_bits(dev->regmap, TIM_CCMR1, mask, ccmr);
+
+ if (!(dev->caps & CAP_BREAKINPUT))
+ return 0;
+
+ bdtr = TIM_BDTR_MOE | TIM_BDTR_AOE;
+
+ if (dev->caps & CAP_BREAKINPUT_POLARITY)
+ bdtr |= TIM_BDTR_BKE;
+
+ if (dev->polarity)
+ bdtr |= TIM_BDTR_BKP;
+
+ regmap_update_bits(dev->regmap, TIM_BDTR,
+ TIM_BDTR_MOE | TIM_BDTR_AOE |
+ TIM_BDTR_BKP | TIM_BDTR_BKE,
+ bdtr);
+
+ return 0;
+}
+
+static int stm32_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
+ enum pwm_polarity polarity)
+{
+ u32 mask;
+ struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);
+
+ mask = TIM_CCER_CC1P << (pwm->hwpwm * 4);
+ if (dev->caps & CAP_COMPLEMENTARY)
+ mask |= TIM_CCER_CC1NP << (pwm->hwpwm * 4);
+
+ regmap_update_bits(dev->regmap, TIM_CCER, mask,
+ polarity == PWM_POLARITY_NORMAL ? 0 : mask);
+
+ return 0;
+}
+
+static int stm32_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ u32 mask;
+ struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);
+
+ clk_enable(dev->clk);
+
+ /* Enable channel */
+ mask = TIM_CCER_CC1E << (pwm->hwpwm * 4);
+ if (dev->caps & CAP_COMPLEMENTARY)
+ mask |= TIM_CCER_CC1NE << (pwm->hwpwm * 4);
+
+ regmap_update_bits(dev->regmap, TIM_CCER, mask, mask);
+
+ /* Make sure that registers are updated */
+ regmap_update_bits(dev->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG);
+
+ /* Enable controller */
+ regmap_update_bits(dev->regmap, TIM_CR1, TIM_CR1_CEN, TIM_CR1_CEN);
+
+ return 0;
+}
+
+static void stm32_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ u32 mask;
+ struct stm32_pwm_dev *dev = to_stm32_pwm_dev(chip);
+
+ /* Disable channel */
+ mask = TIM_CCER_CC1E << (pwm->hwpwm * 4);
+ if (dev->caps & CAP_COMPLEMENTARY)
+ mask |= TIM_CCER_CC1NE << (pwm->hwpwm * 4);
+
+ regmap_update_bits(dev->regmap, TIM_CCER, mask, 0);
+
+ /* When all channels are disabled, we can disable the controller */
+ if (!__active_channels(dev))
+ regmap_update_bits(dev->regmap, TIM_CR1, TIM_CR1_CEN, 0);
+
+ clk_disable(dev->clk);
+}
+
+static const struct pwm_ops stm32pwm_ops = {
+ .config = stm32_pwm_config,
+ .set_polarity = stm32_pwm_set_polarity,
+ .enable = stm32_pwm_enable,
+ .disable = stm32_pwm_disable,
+};
+
+static int stm32_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct stm32_gptimer_dev *mfd = dev_get_drvdata(pdev->dev.parent);
+ struct stm32_pwm_dev *pwm;
+ int ret;
+
+ pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+ if (!pwm)
+ return -ENOMEM;
+
+ pwm->regmap = mfd->regmap;
+ pwm->clk = mfd->clk;
+
+ if (!pwm->regmap || !pwm->clk)
+ return -EINVAL;
+
+ if (of_property_read_bool(np, "st,complementary"))
+ pwm->caps |= CAP_COMPLEMENTARY;
+
+ if (of_property_read_bool(np, "st,32bits-counter"))
+ pwm->caps |= CAP_32BITS_COUNTER;
+
+ if (of_property_read_bool(np, "st,breakinput"))
+ pwm->caps |= CAP_BREAKINPUT;
+
+ if (!of_property_read_u32(np, "st,breakinput-polarity", &pwm->polarity))
+ pwm->caps |= CAP_BREAKINPUT_POLARITY;
+
+ of_property_read_u32(np, "st,pwm-num-chan", &pwm->npwm);
+
+ pwm->chip.base = -1;
+ pwm->chip.dev = dev;
+ pwm->chip.ops = &stm32pwm_ops;
+ pwm->chip.npwm = pwm->npwm;
+
+ ret = pwmchip_add(&pwm->chip);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, pwm);
+
+ return 0;
+}
+
+static int stm32_pwm_remove(struct platform_device *pdev)
+{
+ struct stm32_pwm_dev *pwm = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < pwm->npwm; i++)
+ pwm_disable(&pwm->chip.pwms[i]);
+
+ pwmchip_remove(&pwm->chip);
+
+ return 0;
+}
+
+static const struct of_device_id stm32_pwm_of_match[] = {
+ {
+ .compatible = "st,stm32-pwm",
+ },
+};
+MODULE_DEVICE_TABLE(of, stm32_pwm_of_match);
+
+static struct platform_driver stm32_pwm_driver = {
+ .probe = stm32_pwm_probe,
+ .remove = stm32_pwm_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = stm32_pwm_of_match,
+ },
+};
+module_platform_driver(stm32_pwm_driver);
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_DESCRIPTION("STMicroelectronics STM32 PWM driver");
+MODULE_LICENSE("GPL");
--
1.9.1
^ permalink raw reply related
* [PATCH v2 5/7] IIO: add bindings for stm32 IIO timer driver
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones, robh+dt, mark.rutland, alexandre.torgue, devicetree,
linux-kernel, thierry.reding, linux-pwm, jic23, knaack.h, lars,
pmeerw, linux-iio, linux-arm-kernel
Cc: fabrice.gasnier, gerald.baeza, arnaud.pouliquen, linus.walleij,
linaro-kernel, benjamin.gaignard, Benjamin Gaignard
In-Reply-To: <1480000463-9625-1-git-send-email-benjamin.gaignard@st.com>
Define bindings for stm32 IIO timer
version 2:
- only keep one compatible
- add DT parameters to set lists of the triggers:
one list describe the triggers created by the device
another one give the triggers accepted by the device
Signed-off-by: Benjamin Gaignard <benjamin.gaignard@st.com>
---
.../bindings/iio/timer/stm32-iio-timer.txt | 41 ++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/timer/stm32-iio-timer.txt
diff --git a/Documentation/devicetree/bindings/iio/timer/stm32-iio-timer.txt b/Documentation/devicetree/bindings/iio/timer/stm32-iio-timer.txt
new file mode 100644
index 0000000..840b417
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/timer/stm32-iio-timer.txt
@@ -0,0 +1,41 @@
+timer IIO trigger bindings for STM32
+
+Must be a sub-node of STM32 general purpose timer driver
+
+Required parameters:
+- compatible: must be "st,stm32-iio-timer"
+- interrupts: Interrupt for this device
+ See ../interrupt-controller/st,stm32-exti.txt
+
+Optional parameters:
+- st,input-triggers-names: List of the possible input triggers for
+ the device
+- st,output-triggers-names: List of the possible output triggers for
+ the device
+
+Possible triggers are defined in include/dt-bindings/iio/timer/st,stm32-iio-timer.h
+
+Example:
+ gptimer1: gptimer1@40010000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40010000 0x400>;
+ clocks = <&rcc 0 160>;
+ clock-names = "clk_int";
+
+ pwm1@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ st,breakinput;
+ st,complementary;
+ };
+
+ iiotimer1@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <27>;
+ st,input-triggers-names = TIM5_TRGO,
+ TIM2_TRGO,
+ TIM4_TRGO,
+ TIM3_TRGO;
+ st,output-triggers-names = TIM1_TRGO;
+ };
+ };
--
1.9.1
^ permalink raw reply related
* [PATCH v2 6/7] IIO: add STM32 IIO timer driver
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones-QSEj5FYQhm4dnm+yROfE0A, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, alexandre.torgue-qxv4g6HH51o,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
linux-pwm-u79uwXL29TY76Z2rM5mHXA, jic23-DgEjT+Ai2ygdnm+yROfE0A,
knaack.h-Mmb7MZpHnFY, lars-Qo5EllUWu/uELgA04lAiVw,
pmeerw-jW+XmwGofnusTnJN9+BGXg, linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: fabrice.gasnier-qxv4g6HH51o, gerald.baeza-qxv4g6HH51o,
arnaud.pouliquen-qxv4g6HH51o,
linus.walleij-QSEj5FYQhm4dnm+yROfE0A,
linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
benjamin.gaignard-QSEj5FYQhm4dnm+yROfE0A, Benjamin Gaignard
In-Reply-To: <1480000463-9625-1-git-send-email-benjamin.gaignard-qxv4g6HH51o@public.gmane.org>
Timers IPs can be used to generate triggers for other IPs like
DAC, ADC or other timers.
Each trigger may result of timer internals signals like counter enable,
reset or edge, this configuration could be done through "master_mode"
device attribute.
A timer device could be triggered by other timers, we use the trigger
name and is_stm32_iio_timer_trigger() function to distinguish them
and configure IP input switch.
Timer may also decide on which event (edge, level) they could
be activated by a trigger, this configuration is done by writing in
"slave_mode" device attribute.
Since triggers could also be used by DAC or ADC their names are defined
in include/dt-bindings/iio/timer/st,stm32-iio-timer.h so those IPs will be able
to configure themselves in valid_trigger function
Trigger have a "sampling_frequency" attribute which allow to configure
timer sampling frequency without using pwm interface
version 2:
- keep only one compatible
- use st,input-triggers-names and st,output-triggers-names
to know which triggers are accepted and/or create by the device
Signed-off-by: Benjamin Gaignard <benjamin.gaignard-qxv4g6HH51o@public.gmane.org>
---
drivers/iio/Kconfig | 2 +-
drivers/iio/Makefile | 1 +
drivers/iio/timer/Kconfig | 15 +
drivers/iio/timer/Makefile | 1 +
drivers/iio/timer/stm32-iio-timer.c | 448 +++++++++++++++++++++
drivers/iio/trigger/Kconfig | 1 -
include/dt-bindings/iio/timer/st,stm32-iio-timer.h | 23 ++
include/linux/iio/timer/stm32-iio-timers.h | 16 +
8 files changed, 505 insertions(+), 2 deletions(-)
create mode 100644 drivers/iio/timer/Kconfig
create mode 100644 drivers/iio/timer/Makefile
create mode 100644 drivers/iio/timer/stm32-iio-timer.c
create mode 100644 include/dt-bindings/iio/timer/st,stm32-iio-timer.h
create mode 100644 include/linux/iio/timer/stm32-iio-timers.h
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 6743b18..2de2a80 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -90,5 +90,5 @@ source "drivers/iio/potentiometer/Kconfig"
source "drivers/iio/pressure/Kconfig"
source "drivers/iio/proximity/Kconfig"
source "drivers/iio/temperature/Kconfig"
-
+source "drivers/iio/timer/Kconfig"
endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 87e4c43..b797c08 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -32,4 +32,5 @@ obj-y += potentiometer/
obj-y += pressure/
obj-y += proximity/
obj-y += temperature/
+obj-y += timer/
obj-y += trigger/
diff --git a/drivers/iio/timer/Kconfig b/drivers/iio/timer/Kconfig
new file mode 100644
index 0000000..7a73bc6
--- /dev/null
+++ b/drivers/iio/timer/Kconfig
@@ -0,0 +1,15 @@
+#
+# Timers drivers
+
+menu "Timers"
+
+config IIO_STM32_TIMER
+ tristate "stm32 iio timer"
+ depends on ARCH_STM32
+ depends on OF
+ select IIO_TRIGGERED_EVENT
+ select MFD_STM32_GP_TIMER
+ help
+ Select this option to enable stm32 timers hardware IPs
+
+endmenu
diff --git a/drivers/iio/timer/Makefile b/drivers/iio/timer/Makefile
new file mode 100644
index 0000000..a360c9f
--- /dev/null
+++ b/drivers/iio/timer/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_IIO_STM32_TIMER) += stm32-iio-timer.o
diff --git a/drivers/iio/timer/stm32-iio-timer.c b/drivers/iio/timer/stm32-iio-timer.c
new file mode 100644
index 0000000..35f2687
--- /dev/null
+++ b/drivers/iio/timer/stm32-iio-timer.c
@@ -0,0 +1,448 @@
+/*
+ * stm32-iio-timer.c
+ *
+ * Copyright (C) STMicroelectronics 2016
+ * Author: Benjamin Gaignard <benjamin.gaignard-qxv4g6HH51o@public.gmane.org> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/timer/stm32-iio-timers.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_event.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stm32-gptimer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "stm32-iio-timer"
+
+struct stm32_iio_timer_dev {
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *clk;
+ int irq;
+ bool own_timer;
+ unsigned int sampling_frequency;
+ struct iio_trigger *active_trigger;
+};
+
+static ssize_t _store_frequency(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_trigger *trig = to_iio_trigger(dev);
+ struct stm32_iio_timer_dev *stm32 = iio_trigger_get_drvdata(trig);
+ unsigned int freq;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &freq);
+ if (ret)
+ return ret;
+
+ stm32->sampling_frequency = freq;
+
+ return len;
+}
+
+static ssize_t _read_frequency(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_trigger *trig = to_iio_trigger(dev);
+ struct stm32_iio_timer_dev *stm32 = iio_trigger_get_drvdata(trig);
+ unsigned long long freq = stm32->sampling_frequency;
+ u32 psc, arr, cr1;
+
+ regmap_read(stm32->regmap, TIM_CR1, &cr1);
+ regmap_read(stm32->regmap, TIM_PSC, &psc);
+ regmap_read(stm32->regmap, TIM_ARR, &arr);
+
+ if (psc && arr && (cr1 & TIM_CR1_CEN)) {
+ freq = (unsigned long long)clk_get_rate(stm32->clk);
+ do_div(freq, psc);
+ do_div(freq, arr);
+ }
+
+ return sprintf(buf, "%d\n", (unsigned int)freq);
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO,
+ _read_frequency,
+ _store_frequency);
+
+static struct attribute *stm32_trigger_attrs[] = {
+ &iio_dev_attr_sampling_frequency.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group stm32_trigger_attr_group = {
+ .attrs = stm32_trigger_attrs,
+};
+
+static const struct attribute_group *stm32_trigger_attr_groups[] = {
+ &stm32_trigger_attr_group,
+ NULL,
+};
+
+static
+ssize_t _show_master_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct stm32_iio_timer_dev *stm32 = iio_priv(indio_dev);
+ u32 cr2;
+
+ regmap_read(stm32->regmap, TIM_CR2, &cr2);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", (cr2 >> 4) & 0x7);
+}
+
+static
+ssize_t _store_master_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct stm32_iio_timer_dev *stm32 = iio_priv(indio_dev);
+ u8 mode;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &mode);
+ if (ret)
+ return ret;
+
+ if (mode > 0x7)
+ return -EINVAL;
+
+ regmap_update_bits(stm32->regmap, TIM_CR2, TIM_CR2_MMS, mode << 4);
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(master_mode, S_IRUGO | S_IWUSR,
+ _show_master_mode,
+ _store_master_mode,
+ 0);
+
+static
+ssize_t _show_slave_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct stm32_iio_timer_dev *stm32 = iio_priv(indio_dev);
+ u32 smcr;
+
+ regmap_read(stm32->regmap, TIM_SMCR, &smcr);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", smcr & 0x3);
+}
+
+static
+ssize_t _store_slave_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct stm32_iio_timer_dev *stm32 = iio_priv(indio_dev);
+ u8 mode;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &mode);
+ if (ret)
+ return ret;
+
+ if (mode > 0x7)
+ return -EINVAL;
+
+ regmap_update_bits(stm32->regmap, TIM_SMCR, TIM_SMCR_SMS, mode);
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(slave_mode, S_IRUGO | S_IWUSR,
+ _show_slave_mode,
+ _store_slave_mode,
+ 0);
+
+static struct attribute *stm32_timer_attrs[] = {
+ &iio_dev_attr_master_mode.dev_attr.attr,
+ &iio_dev_attr_slave_mode.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group stm32_timer_attr_group = {
+ .attrs = stm32_timer_attrs,
+};
+
+static int stm32_timer_start(struct stm32_iio_timer_dev *stm32)
+{
+ unsigned long long prd, div;
+ int prescaler = 0;
+ u32 max_arr = 0xFFFF, cr1;
+
+ if (stm32->sampling_frequency == 0)
+ return 0;
+
+ /* Period and prescaler values depends of clock rate */
+ div = (unsigned long long)clk_get_rate(stm32->clk);
+
+ do_div(div, stm32->sampling_frequency);
+
+ prd = div;
+
+ while (div > max_arr) {
+ prescaler++;
+ div = prd;
+ do_div(div, (prescaler + 1));
+ }
+ prd = div;
+
+ if (prescaler > MAX_TIM_PSC) {
+ dev_err(stm32->dev, "prescaler exceeds the maximum value\n");
+ return -EINVAL;
+ }
+
+ /* Check that we own the timer */
+ regmap_read(stm32->regmap, TIM_CR1, &cr1);
+ if ((cr1 & TIM_CR1_CEN) && !stm32->own_timer)
+ return -EBUSY;
+
+ if (!stm32->own_timer) {
+ stm32->own_timer = true;
+ clk_enable(stm32->clk);
+ }
+
+ regmap_write(stm32->regmap, TIM_PSC, prescaler);
+ regmap_write(stm32->regmap, TIM_ARR, prd - 1);
+ regmap_update_bits(stm32->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE);
+
+ /* Force master mode to update mode */
+ regmap_update_bits(stm32->regmap, TIM_CR2, TIM_CR2_MMS, 0x20);
+
+ /* Make sure that registers are updated */
+ regmap_update_bits(stm32->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG);
+
+ /* Enable interrupt */
+ regmap_write(stm32->regmap, TIM_SR, 0);
+ regmap_update_bits(stm32->regmap, TIM_DIER, TIM_DIER_UIE, TIM_DIER_UIE);
+
+ /* Enable controller */
+ regmap_update_bits(stm32->regmap, TIM_CR1, TIM_CR1_CEN, TIM_CR1_CEN);
+
+ return 0;
+}
+
+static int stm32_timer_stop(struct stm32_iio_timer_dev *stm32)
+{
+ if (!stm32->own_timer)
+ return 0;
+
+ /* Stop timer */
+ regmap_update_bits(stm32->regmap, TIM_DIER, TIM_DIER_UIE, 0);
+ regmap_update_bits(stm32->regmap, TIM_CR1, TIM_CR1_CEN, 0);
+ regmap_write(stm32->regmap, TIM_PSC, 0);
+ regmap_write(stm32->regmap, TIM_ARR, 0);
+
+ clk_disable(stm32->clk);
+
+ stm32->own_timer = false;
+ stm32->active_trigger = NULL;
+
+ return 0;
+}
+
+static int stm32_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+ struct stm32_iio_timer_dev *stm32 = iio_trigger_get_drvdata(trig);
+
+ stm32->active_trigger = trig;
+
+ if (state)
+ return stm32_timer_start(stm32);
+ else
+ return stm32_timer_stop(stm32);
+}
+
+static irqreturn_t stm32_timer_irq_handler(int irq, void *private)
+{
+ struct stm32_iio_timer_dev *stm32 = private;
+ u32 sr;
+
+ regmap_read(stm32->regmap, TIM_SR, &sr);
+ regmap_write(stm32->regmap, TIM_SR, 0);
+
+ if ((sr & TIM_SR_UIF) && stm32->active_trigger)
+ iio_trigger_poll(stm32->active_trigger);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_trigger_ops timer_trigger_ops = {
+ .owner = THIS_MODULE,
+ .set_trigger_state = stm32_set_trigger_state,
+};
+
+static int stm32_setup_iio_triggers(struct stm32_iio_timer_dev *stm32)
+{
+ int ret;
+ struct property *p;
+ const char *cur = NULL;
+
+ p = of_find_property(stm32->dev->of_node,
+ "st,output-triggers-names", NULL);
+
+ while ((cur = of_prop_next_string(p, cur)) != NULL) {
+ struct iio_trigger *trig;
+
+ trig = devm_iio_trigger_alloc(stm32->dev, "%s", cur);
+ if (!trig)
+ return -ENOMEM;
+
+ trig->dev.parent = stm32->dev->parent;
+ trig->ops = &timer_trigger_ops;
+ trig->dev.groups = stm32_trigger_attr_groups;
+ iio_trigger_set_drvdata(trig, stm32);
+
+ ret = devm_iio_trigger_register(stm32->dev, trig);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * is_stm32_iio_timer_trigger
+ * @trig: trigger to be checked
+ *
+ * return true if the trigger is a valid stm32 iio timer trigger
+ * either return false
+ */
+bool is_stm32_iio_timer_trigger(struct iio_trigger *trig)
+{
+ return (trig->ops == &timer_trigger_ops);
+}
+EXPORT_SYMBOL(is_stm32_iio_timer_trigger);
+
+static int stm32_validate_trigger(struct iio_dev *indio_dev,
+ struct iio_trigger *trig)
+{
+ struct stm32_iio_timer_dev *dev = iio_priv(indio_dev);
+ int ret;
+
+ if (!is_stm32_iio_timer_trigger(trig))
+ return -EINVAL;
+
+ ret = of_property_match_string(dev->dev->of_node,
+ "st,input-triggers-names",
+ trig->name);
+
+ if (ret < 0)
+ return ret;
+
+ regmap_update_bits(dev->regmap, TIM_SMCR, TIM_SMCR_TS, ret << 4);
+
+ return 0;
+}
+
+static const struct iio_info stm32_trigger_info = {
+ .driver_module = THIS_MODULE,
+ .validate_trigger = stm32_validate_trigger,
+ .attrs = &stm32_timer_attr_group,
+};
+
+static struct stm32_iio_timer_dev *stm32_setup_iio_device(struct device *dev)
+{
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(struct stm32_iio_timer_dev));
+ if (!indio_dev)
+ return NULL;
+
+ indio_dev->name = dev_name(dev);
+ indio_dev->dev.parent = dev;
+ indio_dev->info = &stm32_trigger_info;
+ indio_dev->modes = INDIO_EVENT_TRIGGERED;
+ indio_dev->num_channels = 0;
+ indio_dev->dev.of_node = dev->of_node;
+
+ ret = iio_triggered_event_setup(indio_dev,
+ NULL,
+ stm32_timer_irq_handler);
+ if (ret)
+ return NULL;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret) {
+ iio_triggered_event_cleanup(indio_dev);
+ return NULL;
+ }
+
+ return iio_priv(indio_dev);
+}
+
+static int stm32_iio_timer_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct stm32_iio_timer_dev *stm32;
+ struct stm32_gptimer_dev *mfd = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ stm32 = stm32_setup_iio_device(dev);
+ if (!stm32)
+ return -ENOMEM;
+
+ stm32->dev = dev;
+ stm32->regmap = mfd->regmap;
+ stm32->clk = mfd->clk;
+
+ stm32->irq = platform_get_irq(pdev, 0);
+ if (stm32->irq < 0)
+ return -EINVAL;
+
+ ret = devm_request_irq(stm32->dev, stm32->irq,
+ stm32_timer_irq_handler, IRQF_SHARED,
+ "iiotimer_event", stm32);
+ if (ret)
+ return ret;
+
+ ret = stm32_setup_iio_triggers(stm32);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, stm32);
+
+ return 0;
+}
+
+static int stm32_iio_timer_remove(struct platform_device *pdev)
+{
+ struct stm32_iio_timer_dev *stm32 = platform_get_drvdata(pdev);
+
+ iio_triggered_event_cleanup((struct iio_dev *)stm32);
+
+ return 0;
+}
+
+static const struct of_device_id stm32_trig_of_match[] = {
+ {
+ .compatible = "st,stm32-iio-timer",
+ },
+};
+MODULE_DEVICE_TABLE(of, stm32_trig_of_match);
+
+static struct platform_driver stm32_iio_timer_driver = {
+ .probe = stm32_iio_timer_probe,
+ .remove = stm32_iio_timer_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = stm32_trig_of_match,
+ },
+};
+module_platform_driver(stm32_iio_timer_driver);
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_DESCRIPTION("STMicroelectronics STM32 iio timer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig
index 809b2e7..f2af4fe 100644
--- a/drivers/iio/trigger/Kconfig
+++ b/drivers/iio/trigger/Kconfig
@@ -46,5 +46,4 @@ config IIO_SYSFS_TRIGGER
To compile this driver as a module, choose M here: the
module will be called iio-trig-sysfs.
-
endmenu
diff --git a/include/dt-bindings/iio/timer/st,stm32-iio-timer.h b/include/dt-bindings/iio/timer/st,stm32-iio-timer.h
new file mode 100644
index 0000000..d39bf16
--- /dev/null
+++ b/include/dt-bindings/iio/timer/st,stm32-iio-timer.h
@@ -0,0 +1,23 @@
+/*
+ * st,stm32-iio-timer.h
+ *
+ * Copyright (C) STMicroelectronics 2016
+ * Author: Benjamin Gaignard <benjamin.gaignard-qxv4g6HH51o@public.gmane.org> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef _DT_BINDINGS_IIO_TIMER_H_
+#define _DT_BINDINGS_IIO_TIMER_H_
+
+#define TIM1_TRGO "tim1_trgo"
+#define TIM2_TRGO "tim2_trgo"
+#define TIM3_TRGO "tim3_trgo"
+#define TIM4_TRGO "tim4_trgo"
+#define TIM5_TRGO "tim5_trgo"
+#define TIM6_TRGO "tim6_trgo"
+#define TIM7_TRGO "tim7_trgo"
+#define TIM8_TRGO "tim8_trgo"
+#define TIM9_TRGO "tim9_trgo"
+#define TIM12_TRGO "tim12_trgo"
+
+#endif
diff --git a/include/linux/iio/timer/stm32-iio-timers.h b/include/linux/iio/timer/stm32-iio-timers.h
new file mode 100644
index 0000000..5d1b86c
--- /dev/null
+++ b/include/linux/iio/timer/stm32-iio-timers.h
@@ -0,0 +1,16 @@
+/*
+ * stm32-iio-timers.h
+ *
+ * Copyright (C) STMicroelectronics 2016
+ * Author: Benjamin Gaignard <benjamin.gaignard-qxv4g6HH51o@public.gmane.org> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef _STM32_IIO_TIMERS_H_
+#define _STM32_IIO_TIMERS_H_
+
+#include <dt-bindings/iio/timer/st,stm32-iio-timer.h>
+
+bool is_stm32_iio_timer_trigger(struct iio_trigger *trig);
+
+#endif
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 7/7] ARM: dts: stm32: add stm32 general purpose timer driver in DT
From: Benjamin Gaignard @ 2016-11-24 15:14 UTC (permalink / raw)
To: lee.jones-QSEj5FYQhm4dnm+yROfE0A, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, alexandre.torgue-qxv4g6HH51o,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
thierry.reding-Re5JQEeQqe8AvxtiuMwx3w,
linux-pwm-u79uwXL29TY76Z2rM5mHXA, jic23-DgEjT+Ai2ygdnm+yROfE0A,
knaack.h-Mmb7MZpHnFY, lars-Qo5EllUWu/uELgA04lAiVw,
pmeerw-jW+XmwGofnusTnJN9+BGXg, linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: fabrice.gasnier-qxv4g6HH51o, gerald.baeza-qxv4g6HH51o,
arnaud.pouliquen-qxv4g6HH51o,
linus.walleij-QSEj5FYQhm4dnm+yROfE0A,
linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
benjamin.gaignard-QSEj5FYQhm4dnm+yROfE0A, Benjamin Gaignard
In-Reply-To: <1480000463-9625-1-git-send-email-benjamin.gaignard-qxv4g6HH51o@public.gmane.org>
Add general purpose timers and it sub-nodes into DT for stm32f4.
Define and enable pwm1 and pwm3 for stm32f469 discovery board
version 2:
- use parameters to describe hardware capabilities
- do not use references for pwm and iio timer subnodes
Signed-off-by: Benjamin Gaignard <benjamin.gaignard-qxv4g6HH51o@public.gmane.org>
---
arch/arm/boot/dts/stm32f429.dtsi | 305 +++++++++++++++++++++++++++++++++-
arch/arm/boot/dts/stm32f469-disco.dts | 28 ++++
2 files changed, 332 insertions(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/stm32f429.dtsi b/arch/arm/boot/dts/stm32f429.dtsi
index bca491d..8f217b1 100644
--- a/arch/arm/boot/dts/stm32f429.dtsi
+++ b/arch/arm/boot/dts/stm32f429.dtsi
@@ -48,7 +48,7 @@
#include "skeleton.dtsi"
#include "armv7-m.dtsi"
#include <dt-bindings/pinctrl/stm32f429-pinfunc.h>
-
+#include <dt-bindings/iio/timer/st,stm32-iio-timer.h>
/ {
clocks {
clk_hse: clk-hse {
@@ -355,6 +355,21 @@
slew-rate = <2>;
};
};
+
+ pwm1_pins: pwm@1 {
+ pins {
+ pinmux = <STM32F429_PA8_FUNC_TIM1_CH1>,
+ <STM32F429_PB13_FUNC_TIM1_CH1N>,
+ <STM32F429_PB12_FUNC_TIM1_BKIN>;
+ };
+ };
+
+ pwm3_pins: pwm@3 {
+ pins {
+ pinmux = <STM32F429_PB4_FUNC_TIM3_CH1>,
+ <STM32F429_PB5_FUNC_TIM3_CH2>;
+ };
+ };
};
rcc: rcc@40023810 {
@@ -426,6 +441,294 @@
interrupts = <80>;
clocks = <&rcc 0 38>;
};
+
+ gptimer1: gptimer1@40010000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40010000 0x400>;
+ clocks = <&rcc 0 160>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm1@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ st,breakinput;
+ st,complementary;
+ status = "disabled";
+ };
+
+ iiotimer1@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <27>;
+ st,input-triggers-names = TIM5_TRGO,
+ TIM2_TRGO,
+ TIM4_TRGO,
+ TIM3_TRGO;
+ st,output-triggers-names = TIM1_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer2: gptimer2@40000000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40000000 0x400>;
+ clocks = <&rcc 0 128>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm2@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ st,32bits-counter;
+ status = "disabled";
+ };
+
+ iiotimer2@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <28>;
+ st,input-triggers-names = TIM1_TRGO,
+ TIM8_TRGO,
+ TIM3_TRGO,
+ TIM4_TRGO;
+ st,output-triggers-names = TIM2_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer3: gptimer3@40000400 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40000400 0x400>;
+ clocks = <&rcc 0 129>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm3@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ status = "disabled";
+ };
+
+ iiotimer3@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <29>;
+ st,input-triggers-names = TIM1_TRGO,
+ TIM8_TRGO,
+ TIM5_TRGO,
+ TIM4_TRGO;
+ st,output-triggers-names = TIM3_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer4: gptimer4@40000800 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40000800 0x400>;
+ clocks = <&rcc 0 130>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm4@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ status = "disabled";
+ };
+
+ iiotimer4@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <30>;
+ st,input-triggers-names = TIM1_TRGO,
+ TIM2_TRGO,
+ TIM3_TRGO,
+ TIM8_TRGO;
+ st,output-triggers-names = TIM4_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer5: gptimer5@40000C00 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40000C00 0x400>;
+ clocks = <&rcc 0 131>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm5@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ st,32bits-counter;
+ status = "disabled";
+ };
+
+ iiotimer5@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <50>;
+ st,input-triggers-names = TIM2_TRGO,
+ TIM3_TRGO,
+ TIM4_TRGO,
+ TIM8_TRGO;
+ st,output-triggers-names = TIM5_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer6: gptimer6@40001000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40001000 0x400>;
+ clocks = <&rcc 0 132>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ iiotimer6@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <54>;
+ st,output-triggers-names = TIM6_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer7: gptimer7@40001400 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40001400 0x400>;
+ clocks = <&rcc 0 133>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ iiotimer7@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <55>;
+ st,output-triggers-names = TIM7_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer8: gptimer8@40010400 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40010400 0x400>;
+ clocks = <&rcc 0 161>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm8@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <4>;
+ st,complementary;
+ st,breakinput;
+ status = "disabled";
+ };
+
+ iiotimer8@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <46>;
+ st,input-triggers-names = TIM1_TRGO,
+ TIM2_TRGO,
+ TIM4_TRGO,
+ TIM5_TRGO;
+ st,output-triggers-names = TIM8_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer9: gptimer9@40014000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40014000 0x400>;
+ clocks = <&rcc 0 176>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm9@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <2>;
+ status = "disabled";
+ };
+
+ iiotimer9@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <24>;
+ st,input-triggers-names = TIM2_TRGO,
+ TIM3_TRGO;
+ st,output-triggers-names = TIM9_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer10: gptimer10@40014400 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40014400 0x400>;
+ clocks = <&rcc 0 177>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm10@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <1>;
+ status = "disabled";
+ };
+ };
+
+ gptimer11: gptimer11@40014800 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40014800 0x400>;
+ clocks = <&rcc 0 178>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm11@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <1>;
+ status = "disabled";
+ };
+ };
+
+ gptimer12: gptimer12@40001800 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40001800 0x400>;
+ clocks = <&rcc 0 134>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm12@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <2>;
+ status = "disabled";
+ };
+
+ iiotimer12@0 {
+ compatible = "st,stm32-iio-timer";
+ interrupts = <43>;
+ st,input-triggers-names = TIM4_TRGO,
+ TIM5_TRGO;
+ st,output-triggers-names = TIM12_TRGO;
+ status = "disabled";
+ };
+ };
+
+ gptimer13: gptimer13@40001C00 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40001C00 0x400>;
+ clocks = <&rcc 0 135>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm13@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <1>;
+ status = "disabled";
+ };
+ };
+
+ gptimer14: gptimer14@40002000 {
+ compatible = "st,stm32-gptimer";
+ reg = <0x40002000 0x400>;
+ clocks = <&rcc 0 136>;
+ clock-names = "clk_int";
+ status = "disabled";
+
+ pwm14@0 {
+ compatible = "st,stm32-pwm";
+ st,pwm-num-chan = <1>;
+ status = "disabled";
+ };
+ };
};
};
diff --git a/arch/arm/boot/dts/stm32f469-disco.dts b/arch/arm/boot/dts/stm32f469-disco.dts
index 8a163d7..0c93846 100644
--- a/arch/arm/boot/dts/stm32f469-disco.dts
+++ b/arch/arm/boot/dts/stm32f469-disco.dts
@@ -81,3 +81,31 @@
&usart3 {
status = "okay";
};
+
+&gptimer1 {
+ status = "okay";
+
+ pwm1@0 {
+ pinctrl-0 = <&pwm1_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ };
+
+ iiotimer1@0 {
+ status = "okay";
+ };
+};
+
+&gptimer3 {
+ status = "okay";
+
+ pwm3@0 {
+ pinctrl-0 = <&pwm3_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ };
+
+ iiotimer3@0 {
+ status = "okay";
+ };
+};
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v4 0/7] mux controller abstraction and iio/i2c muxes
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
Hi!
v3 -> v4 changes
- rebased onto next-20161122 (depends on recent _available iio changes).
- added support for having the mux-controller in a child node of a
mux-consumer if it is a sole consumer, to hopefully even further satisfy
the complaint from Rob (and later Lars-Peter) about dt complexity.
- the above came at the cost of some rather horrible refcounting code,
please review and suggest how it should be done...
- changed to register a device class instead of a bus.
- pass in the parent device into mux_control_alloc and require less
work from mux-control drivers.
- changed device names from mux:control%d to mux%d
- move kernel-doc from mux-core.c to mux.h (and add some bits).
- give the gpio driver a chance to update all mux pins at once.
- factor out iio ext_info lookup into new helper function. /Lars-Peter
- use an unsigned type for the iio ext_info count. /Lars-Peter
- unified "brag strings" in the file headers.
v2 -> v3 changes
- have the mux-controller in the parent node of any mux-controller consumer,
to hopefully satisfy complaint from Rob about dt complexity.
- improve commit message of the mux subsystem commit, making it more
general, as requested by Jonathan.
- remove priv member from struct mux_control and calculate it on the
fly. /Jonathan
- make the function comments in mux-core.c kernel doc. /Jonathan
- add devm_mux_control_* to Documentation/driver.model/devres.txt. /Jonathan
- add common dt bindings for mux-controllers, refer to them from the
mux-gpio bindings. /Rob
- clarify how the gpio pins map to the mux state. /Rob
- separate CONFIG_ variables for the mux core and the mux gpio driver.
- improve Kconfig help texts.
- make CONFIG_MUX_GPIO depend on CONFIG_GPIOLIB.
- keep track of the number of mux states in the mux core.
- since the iio channel number is used as mux state, it was possible
to drop the state member from the mux_child struct.
- cleanup dt bindings for i2c-mux-simple, it had some of copy-paste
problems from ots origin (i2c-mux-gpio).
- select the mux control subsystem in config for the i2c-mux-simple driver.
- add entries to MAINTAINERS and my sign-off, I'm now satisfied and know
nothing in this to be ashamed of.
v1 -> v2 changes
- fixup export of mux_control_put reported by kbuild
- drop devicetree iio-ext-info property as noted by Lars-Peter,
and replace the functionality by exposing all ext_info
attributes of the parent channel for each of the muxed
channels. A cache on top of that and each muxed channel
gets its own view of the ext_info of the parent channel.
- implement idle-state for muxes
- clear out the cache on failure in order to force a mux
update on the following use
- cleanup the probe of i2c-mux-simple driver
- fix a bug in the i2c-mux-simple driver, where failure in
the selection of the mux caused a deadlock when the mux
was later unconditionally deselected.
I have a piece of hardware that is using the same 3 GPIO pins
to control four 8-way muxes. Three of them control ADC lines
to an ADS1015 chip with an iio driver, and the last one
controls the SDA line of an i2c bus. We have some deployed
code to handle this, but you do not want to see it or ever
hear about it. I'm not sure why I even mention it. Anyway,
the situation has nagged me to no end for quite some time.
So, after first getting more intimate with the i2c muxing code
and later discovering the drivers/iio/inkern.c file and
writing a couple of drivers making use of it, I came up with
what I think is an acceptable solution; add a generic mux
controller driver (and subsystem) that is shared between all
instances, and combine that with an iio mux driver and a new
generic i2c mux driver. The new i2c mux I called "simple"
since it is only hooking the i2c muxing and the new mux
controller (much like the alsa simple card driver does for ASoC).
One thing that I would like to do, but don't see a solution
for, is to move the mux control code that is present in
various drivers in drivers/i2c/muxes to this new minimalistic
muxing subsystem, thus converting all present i2c muxes (but
perhaps not gates and arbitrators) to be i2c-mux-simple muxes.
I'm using an rwsem to lock a mux, but that isn't really a
perfect fit. Is there a better locking primitive that I don't
know about that fits better? I had a mutex at one point, but
that didn't allow any concurrent accesses at all. At least
the rwsem allows concurrent access as long as all users
agree on the mux state, but I suspect that the rwsem will
degrade to the mutex situation pretty quickly if there is
any contention.
Also, the "mux" name feels a bit ambitious, there are many muxes
in the world, and this tiny bit of code is probably not good
enough to be a nice fit for all...
Cheers,
Peter
Peter Rosin (7):
dt-bindings: document devicetree bindings for mux-controllers and
mux-gpio
misc: minimal mux subsystem and gpio-based mux controller
iio: inkern: api for manipulating ext_info of iio channels
dt-bindings: iio: iio-mux: document iio-mux bindings
iio: multiplexer: new iio category and iio-mux driver
dt-bindings: i2c: i2c-mux-simple: document i2c-mux-simple bindings
i2c: i2c-mux-simple: new driver
.../devicetree/bindings/i2c/i2c-mux-simple.txt | 76 ++++
.../bindings/iio/multiplexer/iio-mux.txt | 47 +++
.../devicetree/bindings/misc/mux-controller.txt | 106 +++++
.../devicetree/bindings/misc/mux-gpio.txt | 78 ++++
Documentation/driver-model/devres.txt | 6 +-
MAINTAINERS | 14 +
drivers/i2c/muxes/Kconfig | 13 +
drivers/i2c/muxes/Makefile | 1 +
drivers/i2c/muxes/i2c-mux-simple.c | 179 ++++++++
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/inkern.c | 60 +++
drivers/iio/multiplexer/Kconfig | 18 +
drivers/iio/multiplexer/Makefile | 6 +
drivers/iio/multiplexer/iio-mux.c | 457 +++++++++++++++++++++
drivers/misc/Kconfig | 23 ++
drivers/misc/Makefile | 2 +
drivers/misc/mux-core.c | 311 ++++++++++++++
drivers/misc/mux-gpio.c | 124 ++++++
include/linux/iio/consumer.h | 37 ++
include/linux/mux.h | 160 ++++++++
21 files changed, 1719 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-controller.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-gpio.txt
create mode 100644 drivers/i2c/muxes/i2c-mux-simple.c
create mode 100644 drivers/iio/multiplexer/Kconfig
create mode 100644 drivers/iio/multiplexer/Makefile
create mode 100644 drivers/iio/multiplexer/iio-mux.c
create mode 100644 drivers/misc/mux-core.c
create mode 100644 drivers/misc/mux-gpio.c
create mode 100644 include/linux/mux.h
--
2.1.4
^ permalink raw reply
* [PATCH v4 1/7] dt-bindings: document devicetree bindings for mux-controllers and mux-gpio
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
In-Reply-To: <1480000687-5630-1-git-send-email-peda@axentia.se>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
.../devicetree/bindings/misc/mux-controller.txt | 106 +++++++++++++++++++++
.../devicetree/bindings/misc/mux-gpio.txt | 78 +++++++++++++++
MAINTAINERS | 5 +
3 files changed, 189 insertions(+)
create mode 100644 Documentation/devicetree/bindings/misc/mux-controller.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-gpio.txt
diff --git a/Documentation/devicetree/bindings/misc/mux-controller.txt b/Documentation/devicetree/bindings/misc/mux-controller.txt
new file mode 100644
index 000000000000..8e0733f60a37
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/mux-controller.txt
@@ -0,0 +1,106 @@
+Common multiplexer controller bindings
+
+A mux controller will have one, or several, consumer devices that uses the
+mux controller. Thus, a mux controller can possibly control several
+parallel multiplexers. If there are several consumer devices, the node for
+a mux controller will have one child node for each consumer of the multiplexer
+controller. If there is only one consumer of the mux controller, the mux
+controller node can be the child (with node name 'mux-controller') of the
+consumer node.
+
+A mux controller provides a number of states to its consumers, and the
+state space is a simple zero-based enumeration. I.e. 0-1 for a 2-way
+multiplexer, 0-7 for an 8-way multiplexer, etc.
+
+Example:
+
+ /*
+ * One consumer of a 2-way multiplexer controller by one GPIO-line,
+ * with the multiplexer controller as a child node.
+ */
+ adc-mux {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ mux-controller {
+ compatible = "mux-gpio";
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>;
+ };
+
+ sync-1@0 {
+ reg = <0>;
+ };
+
+ in@1 {
+ reg = <1>;
+ };
+ };
+
+ /*
+ * Two consumers (one for an ADC line and one for an i2c bus) of
+ * parallel 4-way multiplexers controlled by the same two GPIO-lines.
+ */
+ mux {
+ compatible = "mux-gpio";
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+
+ adc-mux {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sync-1@0 {
+ reg = <0>;
+ };
+
+ in@1 {
+ reg = <1>;
+ };
+
+ out@2 {
+ reg = <2>;
+ };
+
+ sync-2@3 {
+ reg = <3>;
+ };
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux-simple,mux-locked";
+ i2c-parent = <&i2c1>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ /* ... */
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ /* ... */
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/misc/mux-gpio.txt b/Documentation/devicetree/bindings/misc/mux-gpio.txt
new file mode 100644
index 000000000000..23b87913f4b3
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/mux-gpio.txt
@@ -0,0 +1,78 @@
+GPIO-based multiplexer controller bindings
+
+Define what GPIO pins are used to control a multiplexer. Or several
+multiplexers, if the same pins control more than one multiplexer.
+
+Required properties:
+- compatible : "mux-gpio"
+- mux-gpios : list of gpios used to control the multiplexer, least
+ significant bit first.
+* Standard mux-controller bindings as decribed in mux-controller.txt
+
+Optional properties:
+- idle-state : if present, the state the mux will have when idle.
+
+The multiplexer state is defined as the number represented by the
+multiplexer GPIO pins, where the first pin is the least significant
+bit. And active pin is a binary 1, an inactive pin is a binary 0.
+
+Example:
+ mux {
+ compatible = "mux-gpio";
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+
+ adc {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sync-1@0 {
+ reg = <0>;
+ };
+
+ in@1 {
+ reg = <1>;
+ };
+
+ out@2 {
+ reg = <2>;
+ };
+
+ sync-2@3 {
+ reg = <3>;
+ };
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux-simple,mux-locked";
+ i2c-parent = <&i2c1>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ /* ... */
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ /* ... */
+ };
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 4ebcd8df29d2..35869ed0a50e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8402,6 +8402,11 @@ S: Orphan
F: drivers/mmc/host/mmc_spi.c
F: include/linux/spi/mmc_spi.h
+MULTIPLEXER SUBSYSTEM
+M: Peter Rosin <peda@axentia.se>
+S: Maintained
+F: Documentation/devicetree/bindings/misc/mux-*
+
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv@usa.net>
S: Maintained
--
2.1.4
^ permalink raw reply related
* [PATCH v4 2/7] misc: minimal mux subsystem and gpio-based mux controller
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
In-Reply-To: <1480000687-5630-1-git-send-email-peda@axentia.se>
Add a new minimalistic subsystem that handles multiplexer controllers.
When multiplexers are used in various places in the kernel, and the
same multiplexer controller can be used for several independent things,
there should be one place to implement support for said multiplexer
controller.
A single multiplexer controller can also be used to control several
parallel multiplexers, that are in turn used by different subsystems
in the kernel, leading to a need to coordinate multiplexer accesses.
The multiplexer subsystem handles this coordination.
This new mux controller subsystem comes with a single backend driver
that controls gpio based multiplexers.
Signed-off-by: Peter Rosin <peda@axentia.se>
---
Documentation/driver-model/devres.txt | 6 +-
MAINTAINERS | 2 +
drivers/misc/Kconfig | 23 +++
drivers/misc/Makefile | 2 +
drivers/misc/mux-core.c | 311 ++++++++++++++++++++++++++++++++++
drivers/misc/mux-gpio.c | 124 ++++++++++++++
include/linux/mux.h | 160 +++++++++++++++++
7 files changed, 627 insertions(+), 1 deletion(-)
create mode 100644 drivers/misc/mux-core.c
create mode 100644 drivers/misc/mux-gpio.c
create mode 100644 include/linux/mux.h
diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt
index ca9d1eb46bc0..d64ede85b61b 100644
--- a/Documentation/driver-model/devres.txt
+++ b/Documentation/driver-model/devres.txt
@@ -330,7 +330,11 @@ MEM
devm_kzalloc()
MFD
- devm_mfd_add_devices()
+ devm_mfd_add_devices()
+
+MUX
+ devm_mux_control_get()
+ devm_mux_control_put()
PER-CPU MEM
devm_alloc_percpu()
diff --git a/MAINTAINERS b/MAINTAINERS
index 35869ed0a50e..9635c9708a13 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8406,6 +8406,8 @@ MULTIPLEXER SUBSYSTEM
M: Peter Rosin <peda@axentia.se>
S: Maintained
F: Documentation/devicetree/bindings/misc/mux-*
+F: include/linux/mux.h
+F: drivers/misc/mux-*
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv@usa.net>
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 64971baf11fa..a3ca79e082c7 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -766,6 +766,29 @@ config PANEL_BOOT_MESSAGE
An empty message will only clear the display at driver init time. Any other
printf()-formatted message is valid with newline and escape codes.
+config MULTIPLEXER
+ tristate "Multiplexer subsystem"
+ help
+ Multiplexer controller subsystem. Multiplexers are used in a
+ variety of settings, and this subsystem abstracts their use
+ so that the rest of the kernel sees a common interface. When
+ multiple parallel multiplexers are controlled by one single
+ multiplexer controller, this subsystem also coordinates the
+ multiplexer accesses.
+
+if MULTIPLEXER
+
+config MUX_GPIO
+ tristate "GPIO-controlled MUX controller"
+ depends on OF && GPIOLIB
+ help
+ GPIO-controlled MUX controller.
+
+ To compile this driver as a module, choose M here: the module will
+ be called mux-gpio.
+
+endif
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 31983366090a..0befa2bba762 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -53,6 +53,8 @@ obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_PANEL) += panel.o
+obj-$(CONFIG_MULTIPLEXER) += mux-core.o
+obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
diff --git a/drivers/misc/mux-core.c b/drivers/misc/mux-core.c
new file mode 100644
index 000000000000..6617fc78c072
--- /dev/null
+++ b/drivers/misc/mux-core.c
@@ -0,0 +1,311 @@
+/*
+ * Multiplexer subsystem
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "mux-core: " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+static struct class mux_class = {
+ .name = "mux",
+ .owner = THIS_MODULE,
+};
+
+static int __init mux_init(void)
+{
+ return class_register(&mux_class);
+}
+
+static void __exit mux_exit(void)
+{
+ class_unregister(&mux_class);
+}
+
+static DEFINE_IDA(mux_ida);
+
+static void mux_control_release(struct device *dev)
+{
+ struct mux_control *mux = to_mux_control(dev);
+
+ ida_simple_remove(&mux_ida, mux->id);
+ kfree(mux);
+}
+
+static struct device_type mux_type = {
+ .name = "mux-control",
+ .release = mux_control_release,
+};
+
+struct mux_control *mux_control_alloc(struct device *dev, size_t sizeof_priv)
+{
+ struct mux_control *mux;
+
+ mux = kzalloc(sizeof(*mux) + sizeof_priv, GFP_KERNEL);
+ if (!mux)
+ return NULL;
+
+ mux->dev.class = &mux_class;
+ mux->dev.type = &mux_type;
+ mux->dev.parent = dev;
+ mux->dev.of_node = dev->of_node;
+ dev_set_drvdata(&mux->dev, mux);
+
+ mux->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
+ if (mux->id < 0) {
+ pr_err("muxX failed to get a device id\n");
+ kfree(mux);
+ return NULL;
+ }
+ dev_set_name(&mux->dev, "mux%d", mux->id);
+
+ init_rwsem(&mux->lock);
+ mux->cached_state = -1;
+ mux->idle_state = -1;
+
+ device_initialize(&mux->dev);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(mux_control_alloc);
+
+int mux_control_register(struct mux_control *mux)
+{
+ int ret;
+
+ ret = device_add(&mux->dev);
+ if (ret < 0)
+ return ret;
+
+ if (mux->drv_pdev)
+ return ret;
+
+ ret = of_platform_populate(mux->dev.of_node, NULL, NULL, &mux->dev);
+ if (ret < 0)
+ device_del(&mux->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mux_control_register);
+
+void mux_control_unregister(struct mux_control *mux)
+{
+ if (!mux->drv_pdev)
+ of_platform_depopulate(&mux->dev);
+
+ device_del(&mux->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_unregister);
+
+void mux_control_put(struct mux_control *mux)
+{
+ struct platform_device *drv_pdev;
+
+ if (!mux)
+ return;
+ put_device(&mux->dev);
+
+ if (!mux->drv_pdev)
+ return;
+
+ if (atomic_read(&mux->dev.kobj.kref.refcount) != 1)
+ return;
+
+ /*
+ * Only one ref left, and the mux core created the driver
+ * that presumably holds it. Time to release the driver so
+ * that it can let go of the final ref.
+ */
+ drv_pdev = mux->drv_pdev;
+ mux->drv_pdev = NULL;
+ platform_device_unregister(drv_pdev);
+}
+EXPORT_SYMBOL_GPL(mux_control_put);
+
+static int mux_control_set(struct mux_control *mux, int state)
+{
+ int ret = mux->ops->set(mux, state);
+
+ mux->cached_state = ret < 0 ? -1 : state;
+
+ return ret;
+}
+
+int mux_control_select(struct mux_control *mux, int state)
+{
+ int ret;
+
+ if (down_read_trylock(&mux->lock)) {
+ if (mux->cached_state == state)
+ return 0;
+
+ /* Sigh, the mux needs updating... */
+ up_read(&mux->lock);
+ }
+
+ /* ...or it's just contended. */
+ down_write(&mux->lock);
+
+ if (mux->cached_state == state) {
+ /*
+ * Hmmm, someone else changed the mux to my liking.
+ * That makes me wonder how long I waited for nothing?
+ */
+ downgrade_write(&mux->lock);
+ return 0;
+ }
+
+ ret = mux_control_set(mux, state);
+ if (ret < 0) {
+ if (mux->idle_state != -1)
+ mux_control_set(mux, mux->idle_state);
+
+ up_write(&mux->lock);
+ return ret;
+ }
+
+ downgrade_write(&mux->lock);
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(mux_control_select);
+
+int mux_control_deselect(struct mux_control *mux)
+{
+ int ret = 0;
+
+ if (mux->idle_state != -1 && mux->cached_state != mux->idle_state)
+ ret = mux_control_set(mux, mux->idle_state);
+
+ up_read(&mux->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mux_control_deselect);
+
+static int of_dev_node_match(struct device *dev, const void *data)
+{
+ return dev->of_node == data;
+}
+
+static struct mux_control *of_find_mux_by_node(struct device_node *np)
+{
+ struct device *dev;
+
+ dev = class_find_device(&mux_class, NULL, np, of_dev_node_match);
+
+ return dev ? to_mux_control(dev) : NULL;
+}
+
+struct mux_control *mux_control_get(struct device *dev)
+{
+ struct device_node *mux_np;
+ struct platform_device *drv_pdev;
+ struct mux_control *mux;
+
+ if (!dev->of_node)
+ return ERR_PTR(-ENODEV);
+
+ mux_np = of_get_child_by_name(dev->of_node, "mux-controller");
+ if (!mux_np) {
+ mux = of_find_mux_by_node(dev->of_node->parent);
+ if (!mux)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return mux;
+ }
+
+ drv_pdev = of_platform_device_create(mux_np, "mux-controller", dev);
+ of_node_put(mux_np);
+
+ if (!drv_pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ mux = of_find_mux_by_node(mux_np);
+ if (!mux) {
+ platform_device_unregister(drv_pdev);
+ return ERR_PTR(-ENODEV);
+ }
+
+ /*
+ * Aiee, holding a reference to the driver that holds a
+ * reference back. Circular deps, and refcounts never
+ * hit zero -> leak.
+ * So, watch for the mux-controller refcount to hit one
+ * and release the driver-ref then, knowing that the
+ * driver will (probably) not let go of its back-ref as
+ * long as the mux core holds a ref to it.
+ */
+
+ mux->drv_pdev = drv_pdev;
+ return mux;
+}
+EXPORT_SYMBOL_GPL(mux_control_get);
+
+static void devm_mux_control_free(struct device *dev, void *res)
+{
+ struct mux_control *mux = *(struct mux_control **)res;
+
+ mux_control_put(mux);
+}
+
+struct mux_control *devm_mux_control_get(struct device *dev)
+{
+ struct mux_control **ptr, *mux;
+
+ ptr = devres_alloc(devm_mux_control_free, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ mux = mux_control_get(dev);
+ if (IS_ERR(mux)) {
+ devres_free(ptr);
+ return mux;
+ }
+
+ *ptr = mux;
+ devres_add(dev, ptr);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_get);
+
+static int devm_mux_control_match(struct device *dev, void *res, void *data)
+{
+ struct mux_control **r = res;
+
+ if (!r || !*r) {
+ WARN_ON(!r || !*r);
+ return 0;
+ }
+
+ return *r == data;
+}
+
+void devm_mux_control_put(struct device *dev, struct mux_control *mux)
+{
+ WARN_ON(devres_release(dev, devm_mux_control_free,
+ devm_mux_control_match, mux));
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_put);
+
+subsys_initcall(mux_init);
+module_exit(mux_exit);
+
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se");
+MODULE_DESCRIPTION("MUX subsystem");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/mux-gpio.c b/drivers/misc/mux-gpio.c
new file mode 100644
index 000000000000..66798d920e17
--- /dev/null
+++ b/drivers/misc/mux-gpio.c
@@ -0,0 +1,124 @@
+/*
+ * GPIO-controlled multiplexer driver
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * 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/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+struct mux_gpio {
+ struct gpio_descs *gpios;
+};
+
+static int mux_gpio_set(struct mux_control *mux, int state)
+{
+ struct mux_gpio *mux_gpio = mux_control_priv(mux);
+ int values[mux_gpio->gpios->ndescs];
+ int i;
+
+ for (i = 0; i < mux_gpio->gpios->ndescs; i++)
+ values[i] = (state >> i) & 1;
+
+ gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
+ mux_gpio->gpios->desc,
+ values);
+
+ return 0;
+}
+
+static const struct mux_control_ops mux_gpio_ops = {
+ .set = mux_gpio_set,
+};
+
+static const struct of_device_id mux_gpio_dt_ids[] = {
+ { .compatible = "mux-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct mux_control *mux;
+ struct mux_gpio *mux_gpio;
+ u32 idle_state;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ mux = mux_control_alloc(dev, sizeof(*mux_gpio));
+ if (!mux)
+ return -ENOMEM;
+ mux_gpio = mux_control_priv(mux);
+ mux->ops = &mux_gpio_ops;
+
+ platform_set_drvdata(pdev, mux);
+
+ mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
+ if (IS_ERR(mux_gpio->gpios)) {
+ if (PTR_ERR(mux_gpio->gpios) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get gpios\n");
+ mux_control_put(mux);
+ return PTR_ERR(mux_gpio->gpios);
+ }
+ mux->states = 1 << mux_gpio->gpios->ndescs;
+
+ ret = of_property_read_u32(np, "idle-state", &idle_state);
+ if (ret >= 0) {
+ if (idle_state >= mux->states) {
+ dev_err(dev, "invalid idle-state %u\n", idle_state);
+ return -EINVAL;
+ }
+ mux->idle_state = idle_state;
+ }
+
+ ret = mux_control_register(mux);
+ if (ret < 0) {
+ dev_err(dev, "failed to register mux_control\n");
+ mux_control_put(mux);
+ return ret;
+ }
+
+ dev_info(dev, "%u-way mux-controller registered\n", mux->states);
+
+ return 0;
+}
+
+static int mux_gpio_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mux_control *mux = to_mux_control(dev);
+
+ mux_control_unregister(mux);
+ mux_control_put(mux);
+
+ return 0;
+}
+
+static struct platform_driver mux_gpio_driver = {
+ .driver = {
+ .name = "mux-gpio",
+ .of_match_table = of_match_ptr(mux_gpio_dt_ids),
+ },
+ .probe = mux_gpio_probe,
+ .remove = mux_gpio_remove,
+};
+module_platform_driver(mux_gpio_driver);
+
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se");
+MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mux.h b/include/linux/mux.h
new file mode 100644
index 000000000000..be7c138f9228
--- /dev/null
+++ b/include/linux/mux.h
@@ -0,0 +1,160 @@
+/*
+ * mux.h - definitions for the multiplexer interface
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * 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.
+ */
+
+#ifndef _LINUX_MUX_H
+#define _LINUX_MUX_H
+
+#include <linux/device.h>
+#include <linux/rwsem.h>
+
+struct mux_control;
+struct platform_device;
+
+struct mux_control_ops {
+ int (*set)(struct mux_control *mux, int state);
+};
+
+/**
+ * struct mux_control - Represents a mux controller.
+ * @lock: Protects the mux controller state.
+ * @dev: Device structure.
+ * @id: Used to identify the device internally.
+ * @states: The number of mux controller states.
+ * @cached_state: The current mux controller state, or -1 if none.
+ * @idle_state: The mux controller state to use when inactive, or -1
+ * for none.
+ * @ops: Mux controller operations.
+ */
+struct mux_control {
+ struct rw_semaphore lock; /* protects the state of the mux */
+
+ struct device dev;
+ int id;
+ struct platform_device *drv_pdev;
+
+ unsigned int states;
+ int cached_state;
+ int idle_state;
+
+ const struct mux_control_ops *ops;
+};
+
+#define to_mux_control(x) container_of((x), struct mux_control, dev)
+
+/**
+ * mux_control_priv() - Get the extra memory reserved by mux_control_alloc().
+ * @mux: The mux-control to get the extra memory from.
+ *
+ * Return: Pointer to the private memory requested by the allocator.
+ */
+static inline void *mux_control_priv(struct mux_control *mux)
+{
+ return mux + 1;
+}
+
+/**
+ * mux_control_alloc() - Allocate a mux-control.
+ * @dev: The device implementing the mux interface.
+ * @sizeof_priv: Size of extra memory area for private use by the caller.
+ *
+ * Return: A pointer to the new mux-control, NULL on failure.
+ */
+struct mux_control *mux_control_alloc(struct device *dev, size_t sizeof_priv);
+
+/**
+ * mux_control_register() - Register a mux-control, thus readying it for use.
+ * @mux: The mux-control to register.
+ *
+ * Do not retry registration of the same mux-control on failure. You should
+ * instead put it away with mux_control_put() and allocate a new one, if you
+ * for some reason would like to retry registration.
+ *
+ * Return: Zero on success or a negative errno on error.
+ */
+int mux_control_register(struct mux_control *mux);
+
+/**
+ * mux_control_unregister() - Take the mux-control off-line.
+ * @mux: The mux-control to unregister.
+ *
+ * mux_control_unregister() reverses the effects of mux_control_register().
+ * But not completely, you should not try to call mux_control_register()
+ * on a mux-control that has been registered before.
+ */
+void mux_control_unregister(struct mux_control *mux);
+
+/**
+ * mux_control_put() - Put away the mux-control for good.
+ * @mux: The mux-control to put away.
+ *
+ * mux_control_put() reverses the effects of either mux_control_alloc() or
+ * mux_control_get().
+ */
+void mux_control_put(struct mux_control *mux);
+
+/**
+ * mux_control_select() - Select the given multiplexer state.
+ * @mux: The mux-control to request a change of state from.
+ * @state: The new requested state.
+ *
+ * Make sure to call mux_control_deselect() when the operation is complete and
+ * the mux-control is free for others to use, but do not call
+ * mux_control_deselect() if mux_control_select() fails.
+ *
+ * Return: 0 if the requested state was already active, or 1 it the
+ * mux-control state was changed to the requested state. Or a negavive
+ * errno on error.
+ *
+ * Note that the difference in return value of zero or one is of
+ * questionable value; especially if the mux-control has several independent
+ * consumers, which is something the consumers should not be making
+ * assumptions about.
+ */
+int mux_control_select(struct mux_control *mux, int state);
+
+/**
+ * mux_control_deselect() - Deselect the previously selected multiplexer state.
+ * @mux: The mux-control to deselect.
+ *
+ * Return: 0 on success and a negative errno on error. An error can only
+ * occur if the mux has an idle state. Note that even if an error occurs, the
+ * mux-control is unlocked for others to access.
+ */
+int mux_control_deselect(struct mux_control *mux);
+
+/**
+ * mux_control_get() - Get the mux-control for a device.
+ * @dev: The device that needs a mux-control.
+ *
+ * Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *mux_control_get(struct device *dev);
+
+/**
+ * devm_mux_control_get() - Get the mux-control for a device, with resource
+ * management.
+ * @dev: The device that needs a mux-control.
+ *
+ * Return: Pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *devm_mux_control_get(struct device *dev);
+
+/**
+ * devm_mux_control_put() - Resource-managed version mux_control_put().
+ * @dev: The device that originally got the mux-control.
+ * @mux: The mux-control to put away.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_control_put(struct device *dev, struct mux_control *mux);
+
+#endif /* _LINUX_MUX_H */
--
2.1.4
^ permalink raw reply related
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