* [PATCH v3 8/8] net: stmmac: dwmac-meson8b: add support for the RX delay configuration
From: Martin Blumenstingl @ 2020-05-12 21:11 UTC (permalink / raw)
To: robh+dt, andrew, f.fainelli, linux-amlogic, devicetree
Cc: jianxin.pan, davem, netdev, linux-kernel, linux-arm-kernel,
Martin Blumenstingl
In-Reply-To: <20200512211103.530674-1-martin.blumenstingl@googlemail.com>
Configure the PRG_ETH0_ADJ_* bits to enable or disable the RX delay
based on the various RGMII PHY modes. For now the only supported RX
delay settings are:
- disabled, use for example for phy-mode "rgmii-id"
- 0ns - this is treated identical to "disabled", used for example on
boards where the PHY provides 2ns TX delay and the PCB trace length
already adds 2ns RX delay
- 2ns - for whenever the PHY cannot add the RX delay and the traces on
the PCB don't add any RX delay
Disabling the RX delay (in case u-boot enables it, which is the case
for example on Meson8b Odroid-C1) simply means that PRG_ETH0_ADJ_ENABLE,
PRG_ETH0_ADJ_SETUP, PRG_ETH0_ADJ_DELAY and PRG_ETH0_ADJ_SKEW should be
disabled (just disabling PRG_ETH0_ADJ_ENABLE may be enough, since that
disables the whole re-timing logic - but I find it makes more sense to
clear the other bits as well since they depend on that setting).
u-boot on Odroid-C1 uses the following steps to enable a 2ns RX delay:
- enabling enabling the timing adjustment clock
- enabling the timing adjustment logic by setting PRG_ETH0_ADJ_ENABLE
- setting the PRG_ETH0_ADJ_SETUP bit
The documentation for the PRG_ETH0_ADJ_DELAY and PRG_ETH0_ADJ_SKEW
registers indicates that we can even set different RX delays. However,
I could not find out how this works exactly, so for now we only support
a 2ns RX delay using the exact same way that Odroid-C1's u-boot does.
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
.../ethernet/stmicro/stmmac/dwmac-meson8b.c | 85 ++++++++++++++-----
1 file changed, 62 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
index d31f79c455de..234e8b6816ce 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
@@ -85,6 +85,7 @@ struct meson8b_dwmac {
phy_interface_t phy_mode;
struct clk *rgmii_tx_clk;
u32 tx_delay_ns;
+ u32 rx_delay_ns;
struct clk *timing_adj_clk;
};
@@ -284,25 +285,64 @@ static int meson8b_devm_clk_prepare_enable(struct meson8b_dwmac *dwmac,
static int meson8b_init_prg_eth(struct meson8b_dwmac *dwmac)
{
+ u32 tx_dly_config, rx_dly_config, delay_config;
int ret;
- u8 tx_dly_val = 0;
+
+ tx_dly_config = FIELD_PREP(PRG_ETH0_TXDLY_MASK,
+ dwmac->tx_delay_ns >> 1);
+
+ if (dwmac->rx_delay_ns == 2)
+ rx_dly_config = PRG_ETH0_ADJ_ENABLE | PRG_ETH0_ADJ_SETUP;
+ else
+ rx_dly_config = 0;
switch (dwmac->phy_mode) {
case PHY_INTERFACE_MODE_RGMII:
+ delay_config = tx_dly_config | rx_dly_config;
+ break;
case PHY_INTERFACE_MODE_RGMII_RXID:
- tx_dly_val = dwmac->tx_delay_ns >> 1;
- /* fall through */
-
- case PHY_INTERFACE_MODE_RGMII_ID:
+ delay_config = tx_dly_config;
+ break;
case PHY_INTERFACE_MODE_RGMII_TXID:
+ delay_config = rx_dly_config;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RMII:
+ delay_config = 0;
+ break;
+ default:
+ dev_err(dwmac->dev, "unsupported phy-mode %s\n",
+ phy_modes(dwmac->phy_mode));
+ return -EINVAL;
+ };
+
+ if (rx_dly_config & PRG_ETH0_ADJ_ENABLE) {
+ if (!dwmac->timing_adj_clk) {
+ dev_err(dwmac->dev,
+ "The timing-adjustment clock is mandatory for the RX delay re-timing\n");
+ return -EINVAL;
+ }
+
+ /* The timing adjustment logic is driven by a separate clock */
+ ret = meson8b_devm_clk_prepare_enable(dwmac,
+ dwmac->timing_adj_clk);
+ if (ret) {
+ dev_err(dwmac->dev,
+ "Failed to enable the timing-adjustment clock\n");
+ return ret;
+ }
+ }
+
+ meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TXDLY_MASK |
+ PRG_ETH0_ADJ_ENABLE | PRG_ETH0_ADJ_SETUP |
+ PRG_ETH0_ADJ_DELAY | PRG_ETH0_ADJ_SKEW,
+ delay_config);
+
+ if (phy_interface_mode_is_rgmii(dwmac->phy_mode)) {
/* only relevant for RMII mode -> disable in RGMII mode */
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
PRG_ETH0_INVERTED_RMII_CLK, 0);
- meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TXDLY_MASK,
- FIELD_PREP(PRG_ETH0_TXDLY_MASK,
- tx_dly_val));
-
/* Configure the 125MHz RGMII TX clock, the IP block changes
* the output automatically (= without us having to configure
* a register) based on the line-speed (125MHz for Gbit speeds,
@@ -322,24 +362,11 @@ static int meson8b_init_prg_eth(struct meson8b_dwmac *dwmac)
"failed to enable the RGMII TX clock\n");
return ret;
}
- break;
-
- case PHY_INTERFACE_MODE_RMII:
+ } else {
/* invert internal clk_rmii_i to generate 25/2.5 tx_rx_clk */
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0,
PRG_ETH0_INVERTED_RMII_CLK,
PRG_ETH0_INVERTED_RMII_CLK);
-
- /* TX clock delay cannot be configured in RMII mode */
- meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TXDLY_MASK,
- 0);
-
- break;
-
- default:
- dev_err(dwmac->dev, "unsupported phy-mode %s\n",
- phy_modes(dwmac->phy_mode));
- return -EINVAL;
}
/* enable TX_CLK and PHY_REF_CLK generator */
@@ -394,6 +421,18 @@ static int meson8b_dwmac_probe(struct platform_device *pdev)
&dwmac->tx_delay_ns))
dwmac->tx_delay_ns = 2;
+ /* use 0ns as fallback since this is what most boards actually use */
+ if (of_property_read_u32(pdev->dev.of_node, "amlogic,rx-delay-ns",
+ &dwmac->rx_delay_ns))
+ dwmac->rx_delay_ns = 0;
+
+ if (dwmac->rx_delay_ns != 0 && dwmac->rx_delay_ns != 2) {
+ dev_err(&pdev->dev,
+ "The only allowed RX delays values are: 0ns, 2ns");
+ ret = -EINVAL;
+ goto err_remove_config_dt;
+ }
+
dwmac->timing_adj_clk = devm_clk_get_optional(dwmac->dev,
"timing-adjustment");
if (IS_ERR(dwmac->timing_adj_clk)) {
--
2.26.2
^ permalink raw reply related
* [PATCH v3 6/8] net: stmmac: dwmac-meson8b: Fetch the "timing-adjustment" clock
From: Martin Blumenstingl @ 2020-05-12 21:11 UTC (permalink / raw)
To: robh+dt, andrew, f.fainelli, linux-amlogic, devicetree
Cc: jianxin.pan, davem, netdev, linux-kernel, linux-arm-kernel,
Martin Blumenstingl
In-Reply-To: <20200512211103.530674-1-martin.blumenstingl@googlemail.com>
The PRG_ETHERNET registers have a built-in timing adjustment circuit
which can provide the RX delay in RGMII mode. This is driven by an
external (to this IP, but internal to the SoC) clock input. Fetch this
clock as optional (even though it's there on all supported SoCs) since
we just learned about it and existing .dtbs don't specify it.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
index 70075628c58e..41f3ef6bea66 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
@@ -85,6 +85,7 @@ struct meson8b_dwmac {
phy_interface_t phy_mode;
struct clk *rgmii_tx_clk;
u32 tx_delay_ns;
+ struct clk *timing_adj_clk;
};
struct meson8b_dwmac_clk_configs {
@@ -380,6 +381,13 @@ static int meson8b_dwmac_probe(struct platform_device *pdev)
&dwmac->tx_delay_ns))
dwmac->tx_delay_ns = 2;
+ dwmac->timing_adj_clk = devm_clk_get_optional(dwmac->dev,
+ "timing-adjustment");
+ if (IS_ERR(dwmac->timing_adj_clk)) {
+ ret = PTR_ERR(dwmac->timing_adj_clk);
+ goto err_remove_config_dt;
+ }
+
ret = meson8b_init_rgmii_tx_clk(dwmac);
if (ret)
goto err_remove_config_dt;
--
2.26.2
^ permalink raw reply related
* [PATCH v3 3/8] net: stmmac: dwmac-meson8b: use FIELD_PREP instead of open-coding it
From: Martin Blumenstingl @ 2020-05-12 21:10 UTC (permalink / raw)
To: robh+dt, andrew, f.fainelli, linux-amlogic, devicetree
Cc: jianxin.pan, davem, netdev, linux-kernel, linux-arm-kernel,
Martin Blumenstingl
In-Reply-To: <20200512211103.530674-1-martin.blumenstingl@googlemail.com>
Use FIELD_PREP() to shift a value to the correct offset based on a
bitmask instead of open-coding the logic.
No functional changes.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
index a3934ca6a043..c9ec0cb68082 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
@@ -5,6 +5,7 @@
* Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
*/
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
@@ -32,7 +33,6 @@
#define PRG_ETH0_CLK_M250_SEL_SHIFT 4
#define PRG_ETH0_CLK_M250_SEL_MASK GENMASK(4, 4)
-#define PRG_ETH0_TXDLY_SHIFT 5
#define PRG_ETH0_TXDLY_MASK GENMASK(6, 5)
/* divider for the result of m250_sel */
@@ -262,7 +262,8 @@ static int meson8b_init_prg_eth(struct meson8b_dwmac *dwmac)
PRG_ETH0_INVERTED_RMII_CLK, 0);
meson8b_dwmac_mask_bits(dwmac, PRG_ETH0, PRG_ETH0_TXDLY_MASK,
- tx_dly_val << PRG_ETH0_TXDLY_SHIFT);
+ FIELD_PREP(PRG_ETH0_TXDLY_MASK,
+ tx_dly_val));
/* Configure the 125MHz RGMII TX clock, the IP block changes
* the output automatically (= without us having to configure
--
2.26.2
^ permalink raw reply related
* [PATCH v3 4/8] net: stmmac: dwmac-meson8b: Move the documentation for the TX delay
From: Martin Blumenstingl @ 2020-05-12 21:10 UTC (permalink / raw)
To: robh+dt, andrew, f.fainelli, linux-amlogic, devicetree
Cc: jianxin.pan, davem, netdev, linux-kernel, linux-arm-kernel,
Martin Blumenstingl
In-Reply-To: <20200512211103.530674-1-martin.blumenstingl@googlemail.com>
Move the documentation for the TX delay above the PRG_ETH0_TXDLY_MASK
definition. Future commits will add more registers also with
documentation above their register bit definitions. Move the existing
comment so it will be consistent with the upcoming changes.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
index c9ec0cb68082..1d7526ee09dd 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
@@ -33,6 +33,10 @@
#define PRG_ETH0_CLK_M250_SEL_SHIFT 4
#define PRG_ETH0_CLK_M250_SEL_MASK GENMASK(4, 4)
+/* TX clock delay in ns = "8ns / 4 * tx_dly_val" (where 8ns are exactly one
+ * cycle of the 125MHz RGMII TX clock):
+ * 0ns = 0x0, 2ns = 0x1, 4ns = 0x2, 6ns = 0x3
+ */
#define PRG_ETH0_TXDLY_MASK GENMASK(6, 5)
/* divider for the result of m250_sel */
@@ -248,10 +252,6 @@ static int meson8b_init_prg_eth(struct meson8b_dwmac *dwmac)
switch (dwmac->phy_mode) {
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_RXID:
- /* TX clock delay in ns = "8ns / 4 * tx_dly_val" (where
- * 8ns are exactly one cycle of the 125MHz RGMII TX clock):
- * 0ns = 0x0, 2ns = 0x1, 4ns = 0x2, 6ns = 0x3
- */
tx_dly_val = dwmac->tx_delay_ns >> 1;
/* fall through */
--
2.26.2
^ permalink raw reply related
* [PATCH v3 5/8] net: stmmac: dwmac-meson8b: Add the PRG_ETH0_ADJ_* bits
From: Martin Blumenstingl @ 2020-05-12 21:11 UTC (permalink / raw)
To: robh+dt, andrew, f.fainelli, linux-amlogic, devicetree
Cc: jianxin.pan, davem, netdev, linux-kernel, linux-arm-kernel,
Martin Blumenstingl
In-Reply-To: <20200512211103.530674-1-martin.blumenstingl@googlemail.com>
The PRG_ETH0_ADJ_* are used for applying the RGMII RX delay. The public
datasheets only have very limited description for these registers, but
Jianxin Pan provided more detailed documentation from an (unnamed)
Amlogic engineer. Add the PRG_ETH0_ADJ_* bits along with the improved
description.
Suggested-by: Jianxin Pan <jianxin.pan@amlogic.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
.../ethernet/stmicro/stmmac/dwmac-meson8b.c | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
index 1d7526ee09dd..70075628c58e 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c
@@ -48,6 +48,27 @@
#define PRG_ETH0_INVERTED_RMII_CLK BIT(11)
#define PRG_ETH0_TX_AND_PHY_REF_CLK BIT(12)
+/* Bypass (= 0, the signal from the GPIO input directly connects to the
+ * internal sampling) or enable (= 1) the internal logic for RXEN and RXD[3:0]
+ * timing tuning.
+ */
+#define PRG_ETH0_ADJ_ENABLE BIT(13)
+/* Controls whether the RXEN and RXD[3:0] signals should be aligned with the
+ * input RX rising/falling edge and sent to the Ethernet internals. This sets
+ * the automatically delay and skew automatically (internally).
+ */
+#define PRG_ETH0_ADJ_SETUP BIT(14)
+/* An internal counter based on the "timing-adjustment" clock. The counter is
+ * cleared on both, the falling and rising edge of the RX_CLK. This selects the
+ * delay (= the counter value) when to start sampling RXEN and RXD[3:0].
+ */
+#define PRG_ETH0_ADJ_DELAY GENMASK(19, 15)
+/* Adjusts the skew between each bit of RXEN and RXD[3:0]. If a signal has a
+ * large input delay, the bit for that signal (RXEN = bit 0, RXD[3] = bit 1,
+ * ...) can be configured to be 1 to compensate for a delay of about 1ns.
+ */
+#define PRG_ETH0_ADJ_SKEW GENMASK(24, 20)
+
#define MUX_CLK_NUM_PARENTS 2
struct meson8b_dwmac;
--
2.26.2
^ permalink raw reply related
* [PATCH v3 1/8] dt-bindings: net: meson-dwmac: Add the amlogic,rx-delay-ns property
From: Martin Blumenstingl @ 2020-05-12 21:10 UTC (permalink / raw)
To: robh+dt, andrew, f.fainelli, linux-amlogic, devicetree
Cc: jianxin.pan, davem, netdev, linux-kernel, linux-arm-kernel,
Martin Blumenstingl
In-Reply-To: <20200512211103.530674-1-martin.blumenstingl@googlemail.com>
The PRG_ETHERNET registers on Meson8b and newer SoCs can add an RX
delay. Add a property with the known supported values so it can be
configured according to the board layout.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
.../bindings/net/amlogic,meson-dwmac.yaml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/amlogic,meson-dwmac.yaml b/Documentation/devicetree/bindings/net/amlogic,meson-dwmac.yaml
index ae91aa9d8616..66074314e57a 100644
--- a/Documentation/devicetree/bindings/net/amlogic,meson-dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/amlogic,meson-dwmac.yaml
@@ -67,6 +67,19 @@ allOf:
PHY and MAC are adding a delay).
Any configuration is ignored when the phy-mode is set to "rmii".
+ amlogic,rx-delay-ns:
+ enum:
+ - 0
+ - 2
+ default: 0
+ description:
+ The internal RGMII RX clock delay (provided by this IP block) in
+ nanoseconds. When phy-mode is set to "rgmii" then the RX delay
+ should be explicitly configured. When the phy-mode is set to
+ either "rgmii-id" or "rgmii-rxid" the RX clock delay is already
+ provided by the PHY. Any configuration is ignored when the
+ phy-mode is set to "rmii".
+
properties:
compatible:
additionalItems: true
--
2.26.2
^ permalink raw reply related
* [PATCH v3 0/8] dwmac-meson8b Ethernet RX delay configuration
From: Martin Blumenstingl @ 2020-05-12 21:10 UTC (permalink / raw)
To: robh+dt, andrew, f.fainelli, linux-amlogic, devicetree
Cc: jianxin.pan, davem, netdev, linux-kernel, linux-arm-kernel,
Martin Blumenstingl
The Ethernet TX performance has been historically bad on Meson8b and
Meson8m2 SoCs because high packet loss was seen. I found out that this
was related (yet again) to the RGMII TX delay configuration.
In the process of discussing the big picture (and not just a single
patch) [0] with Andrew I discovered that the IP block behind the
dwmac-meson8b driver actually seems to support the configuration of the
RGMII RX delay (at least on the Meson8b SoC generation).
Since I sent the first RFC I got additional documentation from Jianxin
(many thanks!). Also I have discovered some more interesting details:
- Meson8b Odroid-C1 requires an RX delay (by either the PHY or the MAC)
Based on the vendor u-boot code (not upstream) I assume that it will
be the same for all Meson8b and Meson8m2 boards
- Khadas VIM2 seems to have the RX delay built into the PCB trace
length. When I enable the RX delay on the PHY or MAC I can't get any
data through. I expect that we will have the same situation on all
GXBB, GXM, AXG, G12A, G12B and SM1 boards. Further clarification is
needed here though (since I can't visually see these lengthened
traces on the PCB). This will be done before sending patches for
these boards.
Dependencies for this series:
There is a soft dependency for patch #2 on commit f22531438ff42c
"dt-bindings: net: dwmac: increase 'maxItems' for 'clocks',
'clock-names' properties" which is currently in Rob's -next tree.
That commit is needed to make the dt-bindings schema validation
pass for patch #2. That patch has been for ~4 weeks in Robs tree,
so I assume that is not going to be dropped.
Changes since RFC v2 at [2]:
- dropped $ref: /schemas/types.yaml#definitions/uint32 from the
"amlogic,rx-delay-ns" in patch #1 ("Don't need to define the
type when in standard units." says Rob - thanks, I learned
something new). Also use "default: 0" for for this property
instead of explaining it in the description text.
- added a note to the cover-letter about a hidden dependency for
dt-binding schema validation in patch #2
- Added Andrew's Reviewed-by to patches 1-7. Thank you again for
the quick and detailed reviews, I appreciate this!
- error out if the (optional) timing-adjustment clock is missing
but we're asked to enable the RGMII RX delay. The MAC won't
work in this specific case and either the RX delay has to be
provided by the PHY or the timing-adjustment clock has to be
added.
- dropped the dts patches (#9-11) which were only added to give
an overview how this is going to be used. those will be sent
separately
- dropped the RFC prefix
Changes since RFC v1 at [1]:
- add support for the timing adjustment clock input (dt-bindings and
in the driver) thanks to the input from the unnamed Ethernet engineer
at Amlogic. This is the missing link between the fclk_div2 clock and
the Ethernet controller on Meson8b (no traffic would flow if that
clock was disabled)
- add support fot the amlogic,rx-delay-ns property. The only supported
values so far are 0ns and 2ns. The registers seem to allow more
precise timing adjustments, but I could not make that work so far.
- add more register documentation (for the new RX delay bits) and
unified the placement of existing register documentation. Again,
thanks to Jianxin and the unnamed Ethernet engineer at Amlogic
- DO NOT MERGE: .dts patches to show the conversion of the Meson8b
and Meson8m2 boards to "rgmii-id". I didn't have time for all arm64
patches yet, but these will switch to phy-mode = "rgmii-txid" with
amlogic,rx-delay-ns = <0> (because the delay seems to be provided by
the PCB trace length).
[0] https://patchwork.kernel.org/patch/11309891/
[1] https://patchwork.kernel.org/cover/11310719/
[2] https://patchwork.kernel.org/cover/11518257/
Martin Blumenstingl (8):
dt-bindings: net: meson-dwmac: Add the amlogic,rx-delay-ns property
dt-bindings: net: dwmac-meson: Document the "timing-adjustment" clock
net: stmmac: dwmac-meson8b: use FIELD_PREP instead of open-coding it
net: stmmac: dwmac-meson8b: Move the documentation for the TX delay
net: stmmac: dwmac-meson8b: Add the PRG_ETH0_ADJ_* bits
net: stmmac: dwmac-meson8b: Fetch the "timing-adjustment" clock
net: stmmac: dwmac-meson8b: Make the clock enabling code re-usable
net: stmmac: dwmac-meson8b: add support for the RX delay configuration
.../bindings/net/amlogic,meson-dwmac.yaml | 23 ++-
.../ethernet/stmicro/stmmac/dwmac-meson8b.c | 146 ++++++++++++++----
2 files changed, 134 insertions(+), 35 deletions(-)
--
2.26.2
^ permalink raw reply
* Re: [PATCH 1/2] of: Make <linux/of_reserved_mem.h> self-contained
From: Stephen Boyd @ 2020-05-12 21:15 UTC (permalink / raw)
To: Frank Rowand, Geert Uytterhoeven, Jonathan Hunter, Joseph Lo,
Rob Herring, Thierry Reding
Cc: linux-tegra, devicetree, linux-clk, linux-kernel,
Geert Uytterhoeven
In-Reply-To: <20200506123236.7463-2-geert+renesas@glider.be>
Quoting Geert Uytterhoeven (2020-05-06 05:32:35)
> <linux/of_reserved_mem.h> is not self-contained, as it uses
> _OF_DECLARE() to define RESERVEDMEM_OF_DECLARE(), but does not include
> <linux/of.h>.
>
> Fix this by adding the missing include.
>
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> ---
Reviewed-by: Stephen Boyd <sboyd@kernel.org>
^ permalink raw reply
* Re: [PATCH 2/2] memory: tegra: Drop <linux/clk-provider.h>
From: Stephen Boyd @ 2020-05-12 21:15 UTC (permalink / raw)
To: Frank Rowand, Geert Uytterhoeven, Jonathan Hunter, Joseph Lo,
Rob Herring, Thierry Reding
Cc: linux-tegra, devicetree, linux-clk, linux-kernel,
Geert Uytterhoeven
In-Reply-To: <20200506123236.7463-3-geert+renesas@glider.be>
Quoting Geert Uytterhoeven (2020-05-06 05:32:36)
> The Tegra EMC scaling support code is not a clock provider, but merely a
> clock consumer, and thus does not need to include
> <linux/clk-provider.h>.
>
> Fixes: ec37a9a17afbfad5 ("memory: tegra: Add EMC scaling support code for Tegra210")
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> ---
Reviewed-by: Stephen Boyd <sboyd@kernel.org>
^ permalink raw reply
* Re: [PATCH 04/17] spi: dw: Cleanup generic DW DMA code namings
From: Serge Semin @ 2020-05-12 21:26 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Serge Semin, Mark Brown, Georgy Vlasov, Ramil Zaripov,
Alexey Malahov, Thomas Bogendoerfer, Paul Burton, Ralf Baechle,
Arnd Bergmann, Allison Randal, Gareth Williams, Rob Herring,
linux-mips, devicetree, Thomas Gleixner, linux-spi, linux-kernel
In-Reply-To: <20200508194324.GA185537@smile.fi.intel.com>
On Fri, May 08, 2020 at 10:43:24PM +0300, Andy Shevchenko wrote:
> On Fri, May 08, 2020 at 04:29:29PM +0300, Serge Semin wrote:
> > Since from now the former Intel MID platform layer will be used
> > as a generic DW SPI DMA module, lets alter the internal methods
> > naming to be DMA-related instead of having the "mid_" prefix. In
> > addition the DW PCI-DMA specific methods and structures should
> > be named with pci-suffexes, since we'll implement the DW MMIO DMA
> > specific functions and objects soon.
>
> I think we rather leave Intel Medfield things alone. And I think I did this already in spi/for-next in less invasive way.
The naming is horrible in that module. Since it's going to be a generic DMA
driver the prefixes should be correspondingly fixed. Currently they are a bit
random.
-Sergey
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply
* Re: [PATCH v8 04/10] OPP: Add support for parsing interconnect bandwidth
From: Matthias Kaehlcke @ 2020-05-12 21:35 UTC (permalink / raw)
To: Georgi Djakov
Cc: vireshk, nm, sboyd, rjw, saravanak, sibis, robh+dt, rnayak,
bjorn.andersson, vincent.guittot, jcrouse, evgreen, linux-pm,
devicetree, linux-kernel
In-Reply-To: <20200512125327.1868-5-georgi.djakov@linaro.org>
On Tue, May 12, 2020 at 03:53:21PM +0300, Georgi Djakov wrote:
> The OPP bindings now support bandwidth values, so add support to parse it
> from device tree and store it into the new dev_pm_opp_icc_bw struct, which
> is part of the dev_pm_opp.
>
> Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
> ---
> v8:
> * Drop bandwidth requests and free memory in _opp_table_kref_release.
> * Take into account the supply_count in struct size calculations.
> * Free the temporary arrays for peak and average bandwidth.
> * Fix the check for opp-level.
> * Use dev_warn insted of dev_dbg.
> * Rename _of_find_icc_paths to _of_find_icc_paths.
> * Rename the variable count to supply_count.
>
> drivers/opp/Kconfig | 1 +
> drivers/opp/core.c | 27 +++++++--
> drivers/opp/of.c | 121 ++++++++++++++++++++++++++++++++++++++++-
> drivers/opp/opp.h | 9 +++
> include/linux/pm_opp.h | 12 ++++
> 5 files changed, 164 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/opp/Kconfig b/drivers/opp/Kconfig
> index 35dfc7e80f92..230d2b84436c 100644
> --- a/drivers/opp/Kconfig
> +++ b/drivers/opp/Kconfig
> @@ -2,6 +2,7 @@
> config PM_OPP
> bool
> select SRCU
> + depends on INTERCONNECT || !INTERCONNECT
> ---help---
> SOCs have a standard set of tuples consisting of frequency and
> voltage pairs that the device will support per voltage domain. This
> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
> index ce7e4103ec09..a3dd0bc9b9f6 100644
> --- a/drivers/opp/core.c
> +++ b/drivers/opp/core.c
> @@ -999,6 +999,12 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index)
> ret);
> }
>
> + /* Find interconnect path(s) for the device */
> + ret = _of_find_icc_paths(opp_table, dev);
> + if (ret)
> + dev_warn(dev, "%s: Error finding interconnect paths: %d\n",
> + __func__, ret);
> +
> BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head);
> INIT_LIST_HEAD(&opp_table->opp_list);
> kref_init(&opp_table->kref);
> @@ -1057,6 +1063,7 @@ static void _opp_table_kref_release(struct kref *kref)
> {
> struct opp_table *opp_table = container_of(kref, struct opp_table, kref);
> struct opp_device *opp_dev, *temp;
> + int i;
>
> _of_clear_opp_table(opp_table);
>
> @@ -1064,6 +1071,12 @@ static void _opp_table_kref_release(struct kref *kref)
> if (!IS_ERR(opp_table->clk))
> clk_put(opp_table->clk);
>
> + if (opp_table->paths) {
> + for (i = 0; i < opp_table->path_count; i++)
> + icc_put(opp_table->paths[i]);
> + kfree(opp_table->paths);
> + }
> +
> WARN_ON(!list_empty(&opp_table->opp_list));
>
> list_for_each_entry_safe(opp_dev, temp, &opp_table->dev_list, node) {
> @@ -1243,19 +1256,22 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_remove_all_dynamic);
> struct dev_pm_opp *_opp_allocate(struct opp_table *table)
> {
> struct dev_pm_opp *opp;
> - int count, supply_size;
> + int supply_count, supply_size, icc_size;
>
> /* Allocate space for at least one supply */
> - count = table->regulator_count > 0 ? table->regulator_count : 1;
> - supply_size = sizeof(*opp->supplies) * count;
> + supply_count = table->regulator_count > 0 ? table->regulator_count : 1;
> + supply_size = sizeof(*opp->supplies) * supply_count;
> + icc_size = sizeof(*opp->bandwidth) * table->path_count;
>
> /* allocate new OPP node and supplies structures */
> - opp = kzalloc(sizeof(*opp) + supply_size, GFP_KERNEL);
> + opp = kzalloc(sizeof(*opp) + supply_size + icc_size, GFP_KERNEL);
> +
> if (!opp)
> return NULL;
>
> /* Put the supplies at the end of the OPP structure as an empty array */
> opp->supplies = (struct dev_pm_opp_supply *)(opp + 1);
> + opp->bandwidth = (struct dev_pm_opp_icc_bw *)(opp->supplies + supply_count);
> INIT_LIST_HEAD(&opp->node);
>
> return opp;
> @@ -1290,6 +1306,9 @@ int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
> {
> if (opp1->rate != opp2->rate)
> return opp1->rate < opp2->rate ? -1 : 1;
> + if (opp1->bandwidth && opp2->bandwidth &&
> + opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
> + return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1;
> if (opp1->level != opp2->level)
> return opp1->level < opp2->level ? -1 : 1;
> return 0;
> diff --git a/drivers/opp/of.c b/drivers/opp/of.c
> index 5179f034751a..d139ad8c8f4f 100644
> --- a/drivers/opp/of.c
> +++ b/drivers/opp/of.c
> @@ -332,6 +332,58 @@ static int _of_opp_alloc_required_opps(struct opp_table *opp_table,
> return ret;
> }
>
> +int _of_find_icc_paths(struct opp_table *opp_table, struct device *dev)
> +{
> + struct device_node *np;
> + int ret, i, count, num_paths;
> +
> + np = of_node_get(dev->of_node);
> + if (!np)
> + return 0;
> +
> + count = of_count_phandle_with_args(np, "interconnects",
> + "#interconnect-cells");
> + of_node_put(np);
> + if (count < 0)
> + return 0;
> +
> + /* two phandles when #interconnect-cells = <1> */
> + if (count % 2) {
> + dev_err(dev, "%s: Invalid interconnects values\n", __func__);
> + return -EINVAL;
> + }
> +
> + num_paths = count / 2;
> + opp_table->paths = kcalloc(num_paths, sizeof(*opp_table->paths),
> + GFP_KERNEL);
> + if (!opp_table->paths)
> + return -ENOMEM;
> +
> + for (i = 0; i < num_paths; i++) {
> + opp_table->paths[i] = of_icc_get_by_index(dev, i);
> + if (IS_ERR(opp_table->paths[i])) {
> + ret = PTR_ERR(opp_table->paths[i]);
> + if (ret != -EPROBE_DEFER) {
> + dev_err(dev, "%s: Unable to get path%d: %d\n",
> + __func__, i, ret);
> + }
> + goto err;
> + }
> + }
> + opp_table->path_count = num_paths;
> +
> + return 0;
> +
> +err:
> + while (i--)
> + icc_put(opp_table->paths[i]);
> +
> + kfree(opp_table->paths);
> + opp_table->paths = NULL;
> +
> + return ret;
> +}
> +
> static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
> struct device_node *np)
> {
> @@ -524,8 +576,11 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table);
> static int _read_opp_key(struct dev_pm_opp *new_opp, struct device_node *np,
> bool *rate_not_available)
> {
> + struct property *peak, *avg;
> + u32 *peak_bw, *avg_bw;
> u64 rate;
> - int ret;
> + int ret, i, count;
> + bool found = false;
>
> ret = of_property_read_u64(np, "opp-hz", &rate);
> if (!ret) {
> @@ -535,10 +590,72 @@ static int _read_opp_key(struct dev_pm_opp *new_opp, struct device_node *np,
> * bit guaranteed in clk API.
> */
> new_opp->rate = (unsigned long)rate;
> + found = true;
> }
> *rate_not_available = !!ret;
>
> - of_property_read_u32(np, "opp-level", &new_opp->level);
> + peak = of_find_property(np, "opp-peak-kBps", NULL);
> + if (peak) {
> + /*
> + * Bandwidth consists of peak and average (optional) values:
> + * opp-peak-kBps = <path1_value path2_value>;
> + * opp-avg-kBps = <path1_value path2_value>;
> + */
> + count = peak->length / sizeof(u32);
> + peak_bw = kmalloc_array(count, sizeof(*peak_bw), GFP_KERNEL);
> + if (!peak_bw)
> + return -ENOMEM;
> +
> + ret = of_property_read_u32_array(np, "opp-peak-kBps", peak_bw,
> + count);
> + if (ret) {
> + pr_err("%s: Error parsing opp-peak-kBps: %d\n",
> + __func__, ret);
> + goto free_peak_bw;
> + }
> +
> + for (i = 0; i < count; i++)
> + new_opp->bandwidth[i].peak = kBps_to_icc(peak_bw[i]);
> +
> + found = true;
> + kfree(peak_bw);
> + }
> +
> + avg = of_find_property(np, "opp-avg-kBps", NULL);
> + if (peak && avg) {
> + count = avg->length / sizeof(u32);
> + avg_bw = kmalloc_array(count, sizeof(*avg_bw), GFP_KERNEL);
> + if (!avg_bw) {
> + ret = -ENOMEM;
> + goto free_peak_bw;
> + }
> +
> + ret = of_property_read_u32_array(np, "opp-avg-kBps", avg_bw,
> + count);
> + if (ret) {
> + pr_err("%s: Error parsing opp-avg-kBps: %d\n",
> + __func__, ret);
> + goto free_avg_bw;
> + }
> +
> + for (i = 0; i < count; i++)
> + new_opp->bandwidth[i].avg = kBps_to_icc(avg_bw[i]);
> +
> + kfree(avg_bw);
> + }
> +
> + if (!of_property_read_u32(np, "opp-level", &new_opp->level))
> + found = true;
> +
> + if (found)
> + return 0;
> +
> + return ret;
> +
> +free_avg_bw:
> + kfree(avg_bw);
> +free_peak_bw:
> + kfree(peak_bw);
>
> return ret;
> }
> diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
> index bcadb1e328a4..17d45119d9bc 100644
> --- a/drivers/opp/opp.h
> +++ b/drivers/opp/opp.h
> @@ -12,6 +12,7 @@
> #define __DRIVER_OPP_H__
>
> #include <linux/device.h>
> +#include <linux/interconnect.h>
> #include <linux/kernel.h>
> #include <linux/kref.h>
> #include <linux/list.h>
> @@ -59,6 +60,7 @@ extern struct list_head opp_tables;
> * @rate: Frequency in hertz
> * @level: Performance level
> * @supplies: Power supplies voltage/current values
> + * @bandwidth: Interconnect bandwidth values
> * @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
> * frequency from any other OPP's frequency.
> * @required_opps: List of OPPs that are required by this OPP.
> @@ -81,6 +83,7 @@ struct dev_pm_opp {
> unsigned int level;
>
> struct dev_pm_opp_supply *supplies;
> + struct dev_pm_opp_icc_bw *bandwidth;
>
> unsigned long clock_latency_ns;
>
> @@ -146,6 +149,8 @@ enum opp_table_access {
> * @regulator_count: Number of power supply regulators. Its value can be -1
> * (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt
> * property).
> + * @paths: Interconnect path handles
> + * @path_count: Number of interconnect paths
> * @genpd_performance_state: Device's power domain support performance state.
> * @is_genpd: Marks if the OPP table belongs to a genpd.
> * @set_opp: Platform specific set_opp callback
> @@ -189,6 +194,8 @@ struct opp_table {
> struct clk *clk;
> struct regulator **regulators;
> int regulator_count;
> + struct icc_path **paths;
> + unsigned int path_count;
> bool genpd_performance_state;
> bool is_genpd;
>
> @@ -224,12 +231,14 @@ void _of_clear_opp_table(struct opp_table *opp_table);
> struct opp_table *_managed_opp(struct device *dev, int index);
> void _of_opp_free_required_opps(struct opp_table *opp_table,
> struct dev_pm_opp *opp);
> +int _of_find_icc_paths(struct opp_table *opp_table, struct device *dev);
> #else
> static inline void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index) {}
> static inline void _of_clear_opp_table(struct opp_table *opp_table) {}
> static inline struct opp_table *_managed_opp(struct device *dev, int index) { return NULL; }
> static inline void _of_opp_free_required_opps(struct opp_table *opp_table,
> struct dev_pm_opp *opp) {}
> +static inline int _of_find_icc_paths(struct opp_table *opp_table, struct device *dev) { return 0; }
> #endif
>
> #ifdef CONFIG_DEBUG_FS
> diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
> index 747861816f4f..cfceb0290401 100644
> --- a/include/linux/pm_opp.h
> +++ b/include/linux/pm_opp.h
> @@ -41,6 +41,18 @@ struct dev_pm_opp_supply {
> unsigned long u_amp;
> };
>
> +/**
> + * struct dev_pm_opp_icc_bw - Interconnect bandwidth values
> + * @avg: Average bandwidth corresponding to this OPP (in icc units)
> + * @peak: Peak bandwidth corresponding to this OPP (in icc units)
> + *
> + * This structure stores the bandwidth values for a single interconnect path.
> + */
> +struct dev_pm_opp_icc_bw {
> + u32 avg;
> + u32 peak;
> +};
> +
> /**
> * struct dev_pm_opp_info - OPP freq/voltage/current values
> * @rate: Target clk rate in hz
Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
^ permalink raw reply
* Re: [PATCH 04/17] spi: dw: Cleanup generic DW DMA code namings
From: Andy Shevchenko @ 2020-05-12 21:37 UTC (permalink / raw)
To: Serge Semin
Cc: Andy Shevchenko, Serge Semin, Mark Brown, Georgy Vlasov,
Ramil Zaripov, Alexey Malahov, Thomas Bogendoerfer, Paul Burton,
Ralf Baechle, Arnd Bergmann, Allison Randal, Gareth Williams,
Rob Herring, linux-mips, devicetree, Thomas Gleixner, linux-spi,
Linux Kernel Mailing List
In-Reply-To: <20200512212650.bmq5i3mrl5mydcvz@mobilestation>
On Wed, May 13, 2020 at 12:28 AM Serge Semin
<Sergey.Semin@baikalelectronics.ru> wrote:
>
> On Fri, May 08, 2020 at 10:43:24PM +0300, Andy Shevchenko wrote:
> > On Fri, May 08, 2020 at 04:29:29PM +0300, Serge Semin wrote:
> > > Since from now the former Intel MID platform layer will be used
> > > as a generic DW SPI DMA module, lets alter the internal methods
> > > naming to be DMA-related instead of having the "mid_" prefix. In
> > > addition the DW PCI-DMA specific methods and structures should
> > > be named with pci-suffexes, since we'll implement the DW MMIO DMA
> > > specific functions and objects soon.
> >
> > I think we rather leave Intel Medfield things alone. And I think I did this already in spi/for-next in less invasive way.
>
> The naming is horrible in that module.
I agree!
> Since it's going to be a generic DMA
> driver the prefixes should be correspondingly fixed. Currently they are a bit
> random.
Unfortunately, proposed '_pci' not anyhow better. There is Medfield
stuff and non-Medfield.
Luckily Elkhart Lake uses generic approach. That's why better to have
suffix based on platform, rather than on bus.
I think what should be done here are (in a patch per item):
1) moving to new named module (w/o mid)
2) removing mid_ prefix
3) something else I forgot?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 5/5] dt-bindings: Fix incorrect 'reg' property sizes
From: Stephen Boyd @ 2020-05-12 21:46 UTC (permalink / raw)
To: Rob Herring, devicetree
Cc: linux-clk, linux-usb, linux-spi, linux-kernel, Greg Kroah-Hartman,
Mark Brown
In-Reply-To: <20200512204543.22090-5-robh@kernel.org>
Quoting Rob Herring (2020-05-12 13:45:43)
> The examples template is a 'simple-bus' with a size of 1 cell for
> #address-cells and #size-cells. The schema was only checking the entries
> had between 2 and 4 cells which really only errors on I2C or SPI type
> devices with a single cell.
>
> The easiest fix in most cases is to change the 'reg' property to for 1 cell
> address and size. In some cases with child devices having 2 cells, that
> doesn't make sense so a bus node is needed.
>
> Signed-off-by: Rob Herring <robh@kernel.org>
> ---
Acked-by: Stephen Boyd <sboyd@kernel.org> # clk
^ permalink raw reply
* [PATCHv8 0/6] n_gsm serdev support and GNSS driver for droid4
From: Tony Lindgren @ 2020-05-12 21:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Johan Hovold, Rob Herring
Cc: Alan Cox, Lee Jones, Jiri Slaby, Merlijn Wajer, Pavel Machek,
Peter Hurley, Sebastian Reichel, linux-serial, devicetree,
linux-kernel, linux-omap
Hi all,
Here's the updated set of these patches fixed up for Johan's and
Pavel's earlier comments.
This series does the following:
1. Adds functions to n_gsm.c for serdev-ngsm.c driver to use
2. Adds a generic serdev-ngsm.c driver that brings up the TS 27.010
TTY ports configured in devicetree with help of n_gsm.c
3. Allows the use of standard Linux device drivers for dedicated
TS 27.010 channels for devices like GNSS and ALSA found on some
modems for example
4. Adds a gnss-motmdm consumer driver for the GNSS device found on
the Motorola Mapphone MDM6600 modem on devices like droid4
I've placed the serdev-ngsm.c under drivers/tty/serdev as it still
seems to make most sense with no better places available. It's no
longer an MFD driver as it really does not need to care what channel
specific consumer drivers might be configured for the generic driver.
Now serdev-ngsm just uses of_platform_populate() to probe whatever
child nodes it might find.
I'm not attached having the driver in drivers/tty/serdev. I just
don't have any better locations in mind. So using Johan's earlier
i2c example, the drivers/tty/serdev/serdev-ngsm.c driver is now a
generic protocol and bus driver, so it's getting closer to the
the drivers/i2c/busses analogy maybe :) Please do suggest better
locations other than MFD and misc if you have better ideas.
Now without the chardev support, the /dev/gsmtty* using apps need
to use "U1234AT+CFUN?" format for the packets. The advantage is
less kernel code, and we keep the existing /dev/gsmtty* interface.
If we still really need the custom chardev support, that can now
be added as needed with the channel specific consumer driver(s),
but looks like this won't be needed based on Pavel's ofono work.
Regards,
Tony
Changes since v7 (was accidentally posted as v6 again):
- Updated gsm_serdev_register_tty_port() and gsd_dlci_data() to use
receive_buf() to have the dlci handling follow the same path as
for gsm_serdev_register_dlci()
- Updated for Pavel's comments and acks, did not keep the ack for
n_gsm.c as that has changed
- Moved the GNSS driver binding to serdev-ngsm.yaml as suggested
by Rob
- Folded in a a fix from kbuild test robot <lkp@intel.com>
to make motmdm_gnss_send_command() static
Changes since v6:
- Based on comments from Johan, moved back to using the existing
TS 27.010 TTYs created by n_gsm.c instaed of adding custom chardev
support to deal with the Motorola custom protocol
- Based on comments from Johan, made the serdev-ngsm driver generic
with just minimal quirk handling for the Motorola modem
- Dropped the Motorola custom protocol on top of TS 27.010 handling
from serdev-ngsm.c as this can now be easily handled by the channel
specific drivers as needed
- Added few more helpers to n_gsm.c for serdev-ngsm.c to use
- Added the channel specific GNSS driver for the Motorola modem
Changes since v5:
- Cosmetic fixes for issues noted by Pavel
Changes since v4:
- Use drivers/tty/serdev/protocol directory for the driver instead of
drivers/mfd as discussed on the lists for v3 set of patches
- Fix remove to call kfree only after removing device from the list
Changes since v3:
- Update list of folks in Cc, looks like I sent v3 only to Lee and lkml
- Init privdata before motmdm_register_dlci calls gsm_serdev_register_dlci
- Update binding based on Rob's comments for license and "allOf"
Changes since v2:
- Drop useless send_command indirection, use static motmdm_send_command
Changes since v1:
- Simplified usage and got rid of few pointless inline functions
- Added consumer MFD driver, devicetree binding, and dts changes
Tony Lindgren (6):
tty: n_gsm: Add support for serdev drivers
dt-bindings: serdev: ngsm: Add binding for serdev-ngsm
dt-bindings: serdev: ngsm: Add binding for GNSS child node
serdev: ngsm: Add generic serdev-ngsm driver
gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
ARM: dts: omap4-droid4: Configure modem for serdev-ngsm
.../bindings/serdev/serdev-ngsm.yaml | 73 +++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
.../boot/dts/motorola-mapphone-common.dtsi | 14 +
drivers/gnss/Kconfig | 8 +
drivers/gnss/Makefile | 3 +
drivers/gnss/motmdm.c | 419 ++++++++++++++++
drivers/tty/n_gsm.c | 435 +++++++++++++++++
drivers/tty/serdev/Kconfig | 10 +
drivers/tty/serdev/Makefile | 1 +
drivers/tty/serdev/serdev-ngsm.c | 449 ++++++++++++++++++
include/linux/serdev-gsm.h | 165 +++++++
11 files changed, 1579 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
create mode 100644 drivers/gnss/motmdm.c
create mode 100644 drivers/tty/serdev/serdev-ngsm.c
create mode 100644 include/linux/serdev-gsm.h
--
2.26.2
^ permalink raw reply
* [PATCH 3/6] dt-bindings: serdev: ngsm: Add binding for GNSS child node
From: Tony Lindgren @ 2020-05-12 21:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Johan Hovold, Rob Herring
Cc: Alan Cox, Lee Jones, Jiri Slaby, Merlijn Wajer, Pavel Machek,
Peter Hurley, Sebastian Reichel, linux-serial, devicetree,
linux-kernel, linux-omap
In-Reply-To: <20200512214713.40501-1-tony@atomide.com>
For motorola modem case, we may have a GNSS device on channel 4.
Let's add that to the binding and example.
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
.../devicetree/bindings/serdev/serdev-ngsm.yaml | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
--- a/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
+++ b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
@@ -42,6 +42,10 @@ allOf:
description: Name of the USB PHY
const: usb
+ compatible:
+ description: GNSS receiver
+ const: motorola,mapphone-mdm6600-gnss
+
required:
- phys
- phy-names
@@ -61,4 +65,9 @@ examples:
phy-names = "usb";
#address-cells = <1>;
#size-cells = <0>;
+
+ gnss@4 {
+ compatible = "motorola,mapphone-mdm6600-gnss";
+ reg = <4>;
+ };
};
--
2.26.2
^ permalink raw reply
* [PATCH 6/6] ARM: dts: omap4-droid4: Configure modem for serdev-ngsm
From: Tony Lindgren @ 2020-05-12 21:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Johan Hovold, Rob Herring
Cc: Alan Cox, Lee Jones, Jiri Slaby, Merlijn Wajer, Pavel Machek,
Peter Hurley, Sebastian Reichel, linux-serial, devicetree,
linux-kernel, linux-omap
In-Reply-To: <20200512214713.40501-1-tony@atomide.com>
Let's enable the TS 27.010 /dev/gsmmux* interfaces via Linux n_gsm that
can be used for voice calls and SMS with commands using a custom Motorola
format.
And let's also enable the kernel GNSS driver via serdev-ngsm that uses a
dedicated TS 27.010 channel.
Note that voice call audio mixer is not supported yet.
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
arch/arm/boot/dts/motorola-mapphone-common.dtsi | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/arch/arm/boot/dts/motorola-mapphone-common.dtsi b/arch/arm/boot/dts/motorola-mapphone-common.dtsi
--- a/arch/arm/boot/dts/motorola-mapphone-common.dtsi
+++ b/arch/arm/boot/dts/motorola-mapphone-common.dtsi
@@ -698,6 +698,20 @@ &uart1 {
pinctrl-0 = <&uart1_pins>;
interrupts-extended = <&wakeupgen GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH
&omap4_pmx_core 0xfc>;
+
+ modem {
+ compatible = "motorola,mapphone-mdm6600-serial";
+ ttymask = <0 0x00001fee>;
+ phys = <&fsusb1_phy>;
+ phy-names = "usb";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ gnss@4 {
+ compatible = "motorola,mapphone-mdm6600-gnss";
+ reg = <4>;
+ };
+ };
};
&uart3 {
--
2.26.2
^ permalink raw reply
* [PATCH 5/6] gnss: motmdm: Add support for Motorola Mapphone MDM6600 modem
From: Tony Lindgren @ 2020-05-12 21:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Johan Hovold, Rob Herring
Cc: Alan Cox, Lee Jones, Jiri Slaby, Merlijn Wajer, Pavel Machek,
Peter Hurley, Sebastian Reichel, linux-serial, devicetree,
linux-kernel, linux-omap
In-Reply-To: <20200512214713.40501-1-tony@atomide.com>
Motorola is using a custom TS 27.010 based serial port line discipline
for various devices on the modem. These devices can be accessed on
dedicated channels using Linux kernel serdev-ngsm driver.
For the GNSS on these devices, we need to kick the GNSS device at a
desired rate. Otherwise the GNSS device stops sending data after a
few minutes. The rate we poll data defaults to 1000 ms, and can be
specified with a module option rate_ms between 1 to 16 seconds.
Note that AGPS with xtra2.bin is not yet supported, so getting a fix
can take quite a while. And a recent gpsd is needed to parse the
$GNGNS output, and to properly handle the /dev/gnss0 character device.
I've confirmed it works properly with gpsd-3.20.
Tested-by: Pavel Machek <pavel@ucw.cz>
Reviewed-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
drivers/gnss/Kconfig | 8 +
drivers/gnss/Makefile | 3 +
drivers/gnss/motmdm.c | 419 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 430 insertions(+)
create mode 100644 drivers/gnss/motmdm.c
diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
--- a/drivers/gnss/Kconfig
+++ b/drivers/gnss/Kconfig
@@ -13,6 +13,14 @@ menuconfig GNSS
if GNSS
+config GNSS_MOTMDM
+ tristate "Motorola Modem TS 27.010 serdev GNSS receiver support"
+ depends on SERIAL_DEV_N_GSM
+ ---help---
+ Say Y here if you have a Motorola modem using TS 27.010 line
+ discipline for GNSS such as a Motorola Mapphone series device
+ like Droid 4.
+
config GNSS_SERIAL
tristate
diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile
--- a/drivers/gnss/Makefile
+++ b/drivers/gnss/Makefile
@@ -6,6 +6,9 @@
obj-$(CONFIG_GNSS) += gnss.o
gnss-y := core.o
+obj-$(CONFIG_GNSS_MOTMDM) += gnss-motmdm.o
+gnss-motmdm-y := motmdm.o
+
obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o
gnss-serial-y := serial.o
diff --git a/drivers/gnss/motmdm.c b/drivers/gnss/motmdm.c
new file mode 100644
--- /dev/null
+++ b/drivers/gnss/motmdm.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Motorola Modem TS 27.010 serdev GNSS driver
+ *
+ * Copyright (C) 2018 - 2020 Tony Lindgren <tony@atomide.com>
+ *
+ * Based on drivers/gnss/sirf.c driver example:
+ * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
+ */
+
+#include <linux/errno.h>
+#include <linux/gnss.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/serdev-gsm.h>
+#include <linux/slab.h>
+
+#define MOTMDM_GNSS_TIMEOUT 1000
+#define MOTMDM_GNSS_RATE 1000
+
+/*
+ * Motorola MDM GNSS device communicates over a dedicated TS 27.010 channel
+ * using custom data packets. The packets look like AT commands embedded into
+ * a Motorola invented packet using format like "U1234AT+MPDSTART=0,1,100,0".
+ * But it's not an AT compatible serial interface, it's a packet interface
+ * using AT like commands.
+ */
+#define MOTMDM_GNSS_HEADER_LEN 5 /* U1234 */
+#define MOTMDM_GNSS_RESP_LEN (MOTMDM_GNSS_HEADER_LEN + 4) /* U1234+MPD */
+#define MOTMDM_GNSS_DATA_LEN (MOTMDM_GNSS_RESP_LEN + 1) /* U1234~+MPD */
+#define MOTMDM_GNSS_STATUS_LEN (MOTMDM_GNSS_DATA_LEN + 7) /* STATUS= */
+#define MOTMDM_GNSS_NMEA_LEN (MOTMDM_GNSS_DATA_LEN + 8) /* NMEA=NN, */
+
+enum motmdm_gnss_status {
+ MOTMDM_GNSS_UNKNOWN,
+ MOTMDM_GNSS_INITIALIZED,
+ MOTMDM_GNSS_DATA_OR_TIMEOUT,
+ MOTMDM_GNSS_STARTED,
+ MOTMDM_GNSS_STOPPED,
+};
+
+struct motmdm_gnss_data {
+ struct gnss_device *gdev;
+ struct device *modem;
+ struct gsm_serdev_dlci dlci;
+ struct delayed_work restart_work;
+ struct mutex mutex; /* For modem commands */
+ ktime_t last_update;
+ int status;
+ unsigned char *buf;
+ size_t len;
+ wait_queue_head_t read_queue;
+ unsigned int parsed:1;
+};
+
+static unsigned int rate_ms = MOTMDM_GNSS_RATE;
+module_param(rate_ms, uint, 0644);
+MODULE_PARM_DESC(rate_ms, "GNSS refresh rate between 1000 and 16000 ms (default 1000 ms)");
+
+/*
+ * Note that multiple commands can be sent in series with responses coming
+ * out-of-order. For GNSS, we don't need to care about the out-of-order
+ * responses, and can assume we have at most one command active at a time.
+ * For the commands, can use just a jiffies base packet ID and let the modem
+ * sort out the ID conflicts with the modem's unsolicited message ID
+ * numbering.
+ */
+static int motmdm_gnss_send_command(struct motmdm_gnss_data *ddata,
+ const u8 *buf, int len)
+{
+ struct gnss_device *gdev = ddata->gdev;
+ const int timeout_ms = 1000;
+ unsigned char cmd[128];
+ int ret, cmdlen;
+
+ cmdlen = len + 5 + 1;
+ if (cmdlen > 128)
+ return -EINVAL;
+
+ mutex_lock(&ddata->mutex);
+ memset(ddata->buf, 0, ddata->len);
+ ddata->parsed = false;
+ snprintf(cmd, cmdlen, "U%04li%s", jiffies % 10000, buf);
+ ret = serdev_ngsm_write(ddata->modem, &ddata->dlci, cmd, cmdlen);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = wait_event_timeout(ddata->read_queue, ddata->parsed,
+ msecs_to_jiffies(timeout_ms));
+ if (ret == 0) {
+ ret = -ETIMEDOUT;
+ goto out_unlock;
+ } else if (ret < 0) {
+ goto out_unlock;
+ }
+
+ if (!strstr(ddata->buf, ":OK")) {
+ dev_err(&gdev->dev, "command %s error %s\n",
+ cmd, ddata->buf);
+ ret = -EPIPE;
+ }
+
+ ret = len;
+
+out_unlock:
+ mutex_unlock(&ddata->mutex);
+
+ return ret;
+}
+
+/*
+ * Android uses AT+MPDSTART=0,1,100,0 which starts GNSS for a while,
+ * and then GNSS needs to be kicked with an AT command based on a
+ * status message.
+ */
+static void motmdm_gnss_restart(struct work_struct *work)
+{
+ struct motmdm_gnss_data *ddata =
+ container_of(work, struct motmdm_gnss_data,
+ restart_work.work);
+ struct gnss_device *gdev = ddata->gdev;
+ const unsigned char *cmd = "AT+MPDSTART=0,1,100,0";
+ int error;
+
+ ddata->last_update = ktime_get();
+
+ error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+ if (error < 0) {
+ /* Timeouts can happen, don't warn and try again */
+ if (error != -ETIMEDOUT)
+ dev_warn(&gdev->dev, "%s: could not start: %i\n",
+ __func__, error);
+
+ schedule_delayed_work(&ddata->restart_work,
+ msecs_to_jiffies(MOTMDM_GNSS_RATE));
+
+ return;
+ }
+}
+
+static void motmdm_gnss_start(struct gnss_device *gdev, int delay_ms)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ ktime_t now, next, delta;
+ int next_ms;
+
+ now = ktime_get();
+ next = ktime_add_ms(ddata->last_update, delay_ms);
+ delta = ktime_sub(next, now);
+ next_ms = ktime_to_ms(delta);
+
+ if (next_ms < 0)
+ next_ms = 0;
+ if (next_ms > delay_ms)
+ next_ms = delay_ms;
+
+ schedule_delayed_work(&ddata->restart_work, msecs_to_jiffies(next_ms));
+}
+
+static int motmdm_gnss_stop(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ const unsigned char *cmd = "AT+MPDSTOP";
+
+ cancel_delayed_work_sync(&ddata->restart_work);
+
+ return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+}
+
+static int motmdm_gnss_init(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ const unsigned char *cmd = "AT+MPDINIT=1";
+ int error;
+
+ error = motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+ if (error < 0)
+ return error;
+
+ motmdm_gnss_start(gdev, 0);
+
+ return 0;
+}
+
+static int motmdm_gnss_finish(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ const unsigned char *cmd = "AT+MPDINIT=0";
+ int error;
+
+ error = motmdm_gnss_stop(gdev);
+ if (error < 0)
+ return error;
+
+ return motmdm_gnss_send_command(ddata, cmd, strlen(cmd));
+}
+
+static int motmdm_gnss_receive_data(struct gsm_serdev_dlci *dlci,
+ const unsigned char *buf,
+ size_t len)
+{
+ struct gnss_device *gdev = dlci->drvdata;
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ const unsigned char *msg;
+ size_t msglen;
+ int error = 0;
+
+ if (len <= MOTMDM_GNSS_RESP_LEN)
+ return 0;
+
+ /* Handle U1234+MPD style command response */
+ if (buf[MOTMDM_GNSS_HEADER_LEN] != '~') {
+ msg = buf + MOTMDM_GNSS_RESP_LEN;
+ strncpy(ddata->buf, msg, len - MOTMDM_GNSS_RESP_LEN);
+ ddata->parsed = true;
+ wake_up(&ddata->read_queue);
+
+ return len;
+ }
+
+ if (len <= MOTMDM_GNSS_DATA_LEN)
+ return 0;
+
+ /* Handle U1234~+MPD style unsolicted message */
+ switch (buf[MOTMDM_GNSS_DATA_LEN]) {
+ case 'N': /* UNNNN~+MPDNMEA=NN, */
+ msg = buf + MOTMDM_GNSS_NMEA_LEN;
+ msglen = len - MOTMDM_GNSS_NMEA_LEN;
+
+ /*
+ * Firmware bug: Strip out extra duplicate line break always
+ * in the data
+ */
+ msglen--;
+
+ /*
+ * Firmware bug: Strip out extra data based on an
+ * earlier line break in the data
+ */
+ if (msg[msglen - 5 - 1] == 0x0a)
+ msglen -= 5;
+
+ error = gnss_insert_raw(gdev, msg, msglen);
+ break;
+ case 'S': /* UNNNN~+MPDSTATUS=N,NN */
+ msg = buf + MOTMDM_GNSS_STATUS_LEN;
+ msglen = len - MOTMDM_GNSS_STATUS_LEN;
+
+ switch (msg[0]) {
+ case '1':
+ ddata->status = MOTMDM_GNSS_INITIALIZED;
+ break;
+ case '2':
+ ddata->status = MOTMDM_GNSS_DATA_OR_TIMEOUT;
+ if (rate_ms < MOTMDM_GNSS_RATE)
+ rate_ms = MOTMDM_GNSS_RATE;
+ if (rate_ms > 16 * MOTMDM_GNSS_RATE)
+ rate_ms = 16 * MOTMDM_GNSS_RATE;
+ motmdm_gnss_start(gdev, rate_ms);
+ break;
+ case '3':
+ ddata->status = MOTMDM_GNSS_STARTED;
+ break;
+ case '4':
+ ddata->status = MOTMDM_GNSS_STOPPED;
+ break;
+ default:
+ ddata->status = MOTMDM_GNSS_UNKNOWN;
+ break;
+ }
+ break;
+ case 'X': /* UNNNN~+MPDXREQ=N for updated xtra2.bin needed */
+ default:
+ break;
+ }
+
+ return len;
+}
+
+static int motmdm_gnss_open(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct gsm_serdev_dlci *dlci = &ddata->dlci;
+ int error;
+
+ dlci->drvdata = gdev;
+ dlci->receive_buf = motmdm_gnss_receive_data;
+
+ error = serdev_ngsm_register_dlci(ddata->modem, dlci);
+ if (error)
+ return error;
+
+ error = motmdm_gnss_init(gdev);
+ if (error) {
+ serdev_ngsm_unregister_dlci(ddata->modem, dlci);
+
+ return error;
+ }
+
+ return 0;
+}
+
+static void motmdm_gnss_close(struct gnss_device *gdev)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+ struct gsm_serdev_dlci *dlci = &ddata->dlci;
+ int error;
+
+ dlci->receive_buf = NULL;
+ error = motmdm_gnss_finish(gdev);
+ if (error < 0)
+ dev_warn(&gdev->dev, "%s: close failed: %i\n",
+ __func__, error);
+
+ serdev_ngsm_unregister_dlci(ddata->modem, dlci);
+}
+
+static int motmdm_gnss_write_raw(struct gnss_device *gdev,
+ const unsigned char *buf,
+ size_t count)
+{
+ struct motmdm_gnss_data *ddata = gnss_get_drvdata(gdev);
+
+ return serdev_ngsm_write(ddata->modem, &ddata->dlci, buf, count);
+}
+
+static const struct gnss_operations motmdm_gnss_ops = {
+ .open = motmdm_gnss_open,
+ .close = motmdm_gnss_close,
+ .write_raw = motmdm_gnss_write_raw,
+};
+
+static int motmdm_gnss_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct motmdm_gnss_data *ddata;
+ struct gnss_device *gdev;
+ u32 line;
+ int ret;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(dev->of_node, "reg", &line);
+ if (ret)
+ return ret;
+
+ if (!line)
+ return -EINVAL;
+
+ ddata->dlci.line = line;
+ ddata->modem = dev->parent;
+ ddata->len = PAGE_SIZE;
+ mutex_init(&ddata->mutex);
+ INIT_DELAYED_WORK(&ddata->restart_work, motmdm_gnss_restart);
+ init_waitqueue_head(&ddata->read_queue);
+
+ ddata->buf = devm_kzalloc(dev, ddata->len, GFP_KERNEL);
+ if (!ddata->buf)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ddata);
+
+ gdev = gnss_allocate_device(dev);
+ if (!gdev)
+ return -ENOMEM;
+
+ gdev->type = GNSS_TYPE_NMEA;
+ gdev->ops = &motmdm_gnss_ops;
+ gnss_set_drvdata(gdev, ddata);
+ ddata->gdev = gdev;
+
+ ret = gnss_register_device(gdev);
+ if (ret)
+ goto err_put_device;
+
+ return 0;
+
+err_put_device:
+ gnss_put_device(ddata->gdev);
+
+ return ret;
+}
+
+static int motmdm_gnss_remove(struct platform_device *pdev)
+{
+ struct motmdm_gnss_data *data = platform_get_drvdata(pdev);
+
+ gnss_deregister_device(data->gdev);
+ gnss_put_device(data->gdev);
+
+ return 0;
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id motmdm_gnss_of_match[] = {
+ { .compatible = "motorola,mapphone-mdm6600-gnss" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, motmdm_gnss_of_match);
+#endif
+
+static struct platform_driver motmdm_gnss_driver = {
+ .driver = {
+ .name = "gnss-mot-mdm6600",
+ .of_match_table = of_match_ptr(motmdm_gnss_of_match),
+ },
+ .probe = motmdm_gnss_probe,
+ .remove = motmdm_gnss_remove,
+};
+module_platform_driver(motmdm_gnss_driver);
+
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_DESCRIPTION("Motorola Mapphone MDM6600 GNSS receiver driver");
+MODULE_LICENSE("GPL v2");
--
2.26.2
^ permalink raw reply
* [PATCH 4/6] serdev: ngsm: Add generic serdev-ngsm driver
From: Tony Lindgren @ 2020-05-12 21:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Johan Hovold, Rob Herring
Cc: Alan Cox, Lee Jones, Jiri Slaby, Merlijn Wajer, Pavel Machek,
Peter Hurley, Sebastian Reichel, linux-serial, devicetree,
linux-kernel, linux-omap
In-Reply-To: <20200512214713.40501-1-tony@atomide.com>
We can have a generic serdev-ngsm driver bring up the TS 27.010 line
discipline on the selected serial ports based on device tree data.
And we can now do standard Linux device driver for the dedicated
TS 27.010 channels for devices like GNSS and ALSA found on modems.
Tested-by: Pavel Machek <pavel@ucw.cz>
Reviewed-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
drivers/tty/serdev/Kconfig | 10 +
drivers/tty/serdev/Makefile | 1 +
drivers/tty/serdev/serdev-ngsm.c | 449 +++++++++++++++++++++++++++++++
include/linux/serdev-gsm.h | 11 +
4 files changed, 471 insertions(+)
create mode 100644 drivers/tty/serdev/serdev-ngsm.c
diff --git a/drivers/tty/serdev/Kconfig b/drivers/tty/serdev/Kconfig
--- a/drivers/tty/serdev/Kconfig
+++ b/drivers/tty/serdev/Kconfig
@@ -22,4 +22,14 @@ config SERIAL_DEV_CTRL_TTYPORT
depends on SERIAL_DEV_BUS != m
default y
+config SERIAL_DEV_N_GSM
+ tristate "Serial device TS 27.010 support"
+ depends on N_GSM
+ depends on SERIAL_DEV_CTRL_TTYPORT
+ help
+ Select this if you want to use the TS 27.010 with a serial port with
+ devices such as modems and GNSS devices.
+
+ If unsure, say N.
+
endif
diff --git a/drivers/tty/serdev/Makefile b/drivers/tty/serdev/Makefile
--- a/drivers/tty/serdev/Makefile
+++ b/drivers/tty/serdev/Makefile
@@ -4,3 +4,4 @@ serdev-objs := core.o
obj-$(CONFIG_SERIAL_DEV_BUS) += serdev.o
obj-$(CONFIG_SERIAL_DEV_CTRL_TTYPORT) += serdev-ttyport.o
+obj-$(CONFIG_SERIAL_DEV_N_GSM) += serdev-ngsm.o
diff --git a/drivers/tty/serdev/serdev-ngsm.c b/drivers/tty/serdev/serdev-ngsm.c
new file mode 100644
--- /dev/null
+++ b/drivers/tty/serdev/serdev-ngsm.c
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic TS 27.010 serial line discipline serdev driver
+ * Copyright (C) 2020 Tony Lindgren <tony@atomide.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/serdev.h>
+#include <linux/serdev-gsm.h>
+
+#include <linux/phy/phy.h>
+
+#include <uapi/linux/gsmmux.h>
+
+#define TS27010_C_N2 3 /* TS 27.010 default value */
+#define TS27010_RESERVED_DLCI (BIT_ULL(63) | BIT_ULL(62) | BIT_ULL(0))
+
+struct serdev_ngsm_cfg {
+ const struct gsm_config *gsm;
+ unsigned int init_retry_quirk:1;
+ unsigned int needs_usb_phy:1;
+ unsigned int aggressive_pm:1;
+ int (*init)(struct serdev_device *serdev); /* for device quirks */
+};
+
+struct serdev_ngsm {
+ struct device *dev;
+ struct gsm_serdev gsd;
+ struct phy *phy;
+ u32 baudrate;
+ DECLARE_BITMAP(ttymask, 64);
+ const struct serdev_ngsm_cfg *cfg;
+};
+
+static int serdev_ngsm_tty_init(struct serdev_ngsm *ddata)
+{
+ struct gsm_serdev *gsd = &ddata->gsd;
+ struct device *dev = ddata->dev;
+ int bit, err;
+
+ for_each_set_bit(bit, ddata->ttymask, 64) {
+ if (BIT_ULL(bit) & TS27010_RESERVED_DLCI)
+ continue;
+
+ err = gsm_serdev_register_tty_port(gsd, bit);
+ if (err) {
+ dev_err(dev, "ngsm tty init failed for dlci%i: %i\n",
+ bit, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void serdev_ngsm_tty_exit(struct serdev_ngsm *ddata)
+{
+ struct gsm_serdev *gsd = &ddata->gsd;
+ int bit;
+
+ for_each_set_bit(bit, ddata->ttymask, 64) {
+ if (BIT_ULL(bit) & TS27010_RESERVED_DLCI)
+ continue;
+
+ gsm_serdev_unregister_tty_port(gsd, bit);
+ }
+}
+
+/*
+ * Note that we rely on gsm_serdev_register_dlci() locking for
+ * reserved channels that serdev_ngsm_tty_init() and consumer
+ * drivers may have already reserved.
+ */
+int serdev_ngsm_register_dlci(struct device *dev,
+ struct gsm_serdev_dlci *dlci)
+{
+ struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+ struct gsm_serdev *gsd = &ddata->gsd;
+ int err;
+
+ err = gsm_serdev_register_dlci(gsd, dlci);
+ if (err)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(serdev_ngsm_register_dlci);
+
+void serdev_ngsm_unregister_dlci(struct device *dev,
+ struct gsm_serdev_dlci *dlci)
+{
+ struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+ struct gsm_serdev *gsd = &ddata->gsd;
+
+ gsm_serdev_unregister_dlci(gsd, dlci);
+}
+EXPORT_SYMBOL_GPL(serdev_ngsm_unregister_dlci);
+
+int serdev_ngsm_write(struct device *dev, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len)
+{
+ struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+ struct gsm_serdev *gsd = &ddata->gsd;
+ int ret;
+
+ ret = pm_runtime_get_sync(dev);
+ if ((ret != -EINPROGRESS) && ret < 0) {
+ pm_runtime_put_noidle(dev);
+
+ return ret;
+ }
+
+ ret = gsm_serdev_write(gsd, ops, buf, len);
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(serdev_ngsm_write);
+
+static int serdev_ngsm_set_config(struct device *dev)
+{
+ struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+ struct gsm_serdev *gsd = &ddata->gsd;
+ struct gsm_config c;
+ int err, n2;
+
+ memcpy(&c, ddata->cfg->gsm, sizeof(c));
+
+ if (ddata->cfg->init_retry_quirk) {
+ n2 = c.n2;
+ c.n2 *= 10;
+ err = gsm_serdev_set_config(gsd, &c);
+ if (err)
+ return err;
+
+ msleep(5000);
+ c.n2 = n2;
+ }
+
+ err = gsm_serdev_set_config(gsd, &c);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int serdev_ngsm_output(struct gsm_serdev *gsd, u8 *data, int len)
+{
+ struct serdev_device *serdev = gsd->serdev;
+ struct device *dev = &serdev->dev;
+ int err;
+
+ err = pm_runtime_get(dev);
+ if ((err != -EINPROGRESS) && err < 0) {
+ pm_runtime_put_noidle(dev);
+
+ return err;
+ }
+
+ serdev_device_write_buf(serdev, data, len);
+
+ pm_runtime_put(dev);
+
+ return len;
+}
+
+static int serdev_ngsm_runtime_suspend(struct device *dev)
+{
+ struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+ int err;
+
+ if (ddata->cfg->needs_usb_phy) {
+ err = phy_pm_runtime_put(ddata->phy);
+ if (err < 0) {
+ dev_warn(dev, "%s: phy_pm_runtime_put: %i\n",
+ __func__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int serdev_ngsm_runtime_resume(struct device *dev)
+{
+ struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+ int err;
+
+ if (ddata->cfg->needs_usb_phy) {
+ err = phy_pm_runtime_get_sync(ddata->phy);
+ if (err < 0) {
+ dev_warn(dev, "%s: phy_pm_runtime_get: %i\n",
+ __func__, err);
+
+ return err;
+ }
+ }
+
+ gsm_serdev_data_kick(&ddata->gsd);
+
+ return 0;
+}
+
+static const struct dev_pm_ops serdev_ngsm_pm_ops = {
+ SET_RUNTIME_PM_OPS(serdev_ngsm_runtime_suspend,
+ serdev_ngsm_runtime_resume,
+ NULL)
+};
+
+/*
+ * At least Motorola MDM6600 devices have GPIO wake pins shared between the
+ * USB PHY and the TS 27.010 interface. So for PM, we need to use the calls
+ * for phy_pm_runtime. Otherwise the modem won't respond to anything on the
+ * UART and will never idle either.
+ */
+static int serdev_ngsm_phy_init(struct device *dev)
+{
+ struct serdev_ngsm *ddata = gsm_serdev_get_drvdata(dev);
+ int err;
+
+ if (!ddata->cfg->needs_usb_phy)
+ return 0;
+
+ ddata->phy = devm_of_phy_get(dev, dev->of_node, NULL);
+ if (IS_ERR(ddata->phy)) {
+ err = PTR_ERR(ddata->phy);
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "%s: phy error: %i\n", __func__, err);
+
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Configure SoC 8250 device for 700 ms autosuspend delay, Values around 600 ms
+ * and shorter cause spurious wake-up events at least on Droid 4. Also keep the
+ * SoC 8250 device active during use because of the OOB GPIO wake-up signaling
+ * shared with USB PHY.
+ */
+static int motmdm_init(struct serdev_device *serdev)
+{
+ pm_runtime_set_autosuspend_delay(serdev->ctrl->dev.parent, 700);
+ pm_suspend_ignore_children(&serdev->ctrl->dev, false);
+
+ return 0;
+}
+
+static const struct gsm_config adaption1 = {
+ .i = 1, /* 1 = UIH, 2 = UI */
+ .initiator = 1,
+ .encapsulation = 0, /* basic mode */
+ .adaption = 1,
+ .mru = 1024, /* from android TS 27010 driver */
+ .mtu = 1024, /* from android TS 27010 driver */
+ .t1 = 10, /* ack timer, default 10ms */
+ .t2 = 34, /* response timer, default 34 */
+ .n2 = 3, /* retransmissions, default 3 */
+};
+
+static const struct serdev_ngsm_cfg adaption1_cfg = {
+ .gsm = &adaption1,
+};
+
+static const struct serdev_ngsm_cfg motmdm_cfg = {
+ .gsm = &adaption1,
+ .init_retry_quirk = 1,
+ .needs_usb_phy = 1,
+ .aggressive_pm = 1,
+ .init = motmdm_init,
+};
+
+static const struct of_device_id serdev_ngsm_id_table[] = {
+ {
+ .compatible = "etsi,3gpp-ts27010-adaption1",
+ .data = &adaption1_cfg,
+ },
+ {
+ .compatible = "motorola,mapphone-mdm6600-serial",
+ .data = &motmdm_cfg,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, serdev_ngsm_id_table);
+
+static int serdev_ngsm_probe(struct serdev_device *serdev)
+{
+ struct device *dev = &serdev->dev;
+ const struct of_device_id *match;
+ struct gsm_serdev *gsd;
+ struct serdev_ngsm *ddata;
+ u64 ttymask;
+ int err;
+
+ match = of_match_device(of_match_ptr(serdev_ngsm_id_table), dev);
+ if (!match)
+ return -ENODEV;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->dev = dev;
+ ddata->cfg = match->data;
+
+ gsd = &ddata->gsd;
+ gsd->serdev = serdev;
+ gsd->output = serdev_ngsm_output;
+ serdev_device_set_drvdata(serdev, gsd);
+ gsm_serdev_set_drvdata(dev, ddata);
+
+ err = serdev_ngsm_phy_init(dev);
+ if (err)
+ return err;
+
+ err = of_property_read_u64(dev->of_node, "ttymask", &ttymask);
+ if (err) {
+ dev_err(dev, "invalid or missing ttymask: %i\n", err);
+
+ return err;
+ }
+
+ bitmap_from_u64(ddata->ttymask, ttymask);
+
+ pm_runtime_set_autosuspend_delay(dev, 200);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_enable(dev);
+ err = pm_runtime_get_sync(dev);
+ if (err < 0) {
+ pm_runtime_put_noidle(dev);
+
+ return err;
+ }
+
+ err = gsm_serdev_register_device(gsd);
+ if (err)
+ goto err_disable;
+
+ err = serdev_device_open(gsd->serdev);
+ if (err)
+ goto err_disable;
+
+ /* Optional serial port configuration */
+ of_property_read_u32(dev->of_node->parent, "current-speed",
+ &ddata->baudrate);
+ if (ddata->baudrate)
+ serdev_device_set_baudrate(gsd->serdev, ddata->baudrate);
+
+ if (of_get_property(dev->of_node->parent, "uart-has-rtscts", NULL)) {
+ serdev_device_set_rts(gsd->serdev, true);
+ serdev_device_set_flow_control(gsd->serdev, true);
+ }
+
+ err = serdev_ngsm_set_config(dev);
+ if (err)
+ goto err_close;
+
+ err = serdev_ngsm_tty_init(ddata);
+ if (err)
+ goto err_tty;
+
+ if (ddata->cfg->init) {
+ err = ddata->cfg->init(serdev);
+ if (err)
+ goto err_tty;
+ }
+
+ err = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (err)
+ goto err_tty;
+
+ /* Allow parent serdev device to idle when open, balanced in remove */
+ if (ddata->cfg->aggressive_pm)
+ pm_runtime_put(&serdev->ctrl->dev);
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+
+err_tty:
+ serdev_ngsm_tty_exit(ddata);
+
+err_close:
+ serdev_device_close(serdev);
+
+err_disable:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+ gsm_serdev_unregister_device(gsd);
+
+ return err;
+}
+
+static void serdev_ngsm_remove(struct serdev_device *serdev)
+{
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+ struct device *dev = &serdev->dev;
+ struct serdev_ngsm *ddata;
+ int err;
+
+ ddata = gsm_serdev_get_drvdata(dev);
+
+ /* Balance the put done in probe for UART */
+ if (ddata->cfg->aggressive_pm)
+ pm_runtime_get(&serdev->ctrl->dev);
+
+ err = pm_runtime_get_sync(dev);
+ if (err < 0)
+ dev_warn(dev, "%s: PM runtime: %i\n", __func__, err);
+
+ of_platform_depopulate(dev);
+ serdev_ngsm_tty_exit(ddata);
+ serdev_device_close(serdev);
+ gsm_serdev_unregister_device(gsd);
+
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+}
+
+static struct serdev_device_driver serdev_ngsm_driver = {
+ .driver = {
+ .name = "serdev_ngsm",
+ .of_match_table = of_match_ptr(serdev_ngsm_id_table),
+ .pm = &serdev_ngsm_pm_ops,
+ },
+ .probe = serdev_ngsm_probe,
+ .remove = serdev_ngsm_remove,
+};
+
+module_serdev_device_driver(serdev_ngsm_driver);
+
+MODULE_DESCRIPTION("serdev n_gsm driver");
+MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
--- a/include/linux/serdev-gsm.h
+++ b/include/linux/serdev-gsm.h
@@ -45,6 +45,17 @@ struct gsm_serdev_dlci {
#if IS_ENABLED(CONFIG_N_GSM) && IS_ENABLED(CONFIG_SERIAL_DEV_BUS)
+/* TS 27.010 channel specific functions for consumer drivers */
+#if IS_ENABLED(CONFIG_SERIAL_DEV_N_GSM)
+extern int
+serdev_ngsm_register_dlci(struct device *dev, struct gsm_serdev_dlci *dlci);
+extern void serdev_ngsm_unregister_dlci(struct device *dev,
+ struct gsm_serdev_dlci *dlci);
+extern int serdev_ngsm_write(struct device *dev, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len);
+#endif
+
+/* Interface for_gsm serdev support */
extern int gsm_serdev_register_device(struct gsm_serdev *gsd);
extern void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
extern int gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line);
--
2.26.2
^ permalink raw reply
* [PATCH 2/6] dt-bindings: serdev: ngsm: Add binding for serdev-ngsm
From: Tony Lindgren @ 2020-05-12 21:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Johan Hovold, Rob Herring
Cc: Alan Cox, Lee Jones, Jiri Slaby, Merlijn Wajer, Pavel Machek,
Peter Hurley, Sebastian Reichel, linux-serial, devicetree,
linux-kernel, linux-omap
In-Reply-To: <20200512214713.40501-1-tony@atomide.com>
Add a binding document for a generic serdev-ngsm driver that can be
used to bring up TS 27.010 line discipline with Linux n_gsm support
on a serial port.
As the Motorola Mapphone modems require some custom handling, they
are handled with a separate compatible.
Let's also add vendor string for ETSI as we're using a ETSI 3GPP
TS 27.010 standard.
Reviewed-by: Pavel Machek <pavel@ucw.cz>
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
.../bindings/serdev/serdev-ngsm.yaml | 64 +++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
2 files changed, 66 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
diff --git a/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
new file mode 100644
--- /dev/null
+++ b/Documentation/devicetree/bindings/serdev/serdev-ngsm.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serdev/serdev-ngsm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic serdev-ngsm TS 27.010 driver
+
+maintainers:
+ - Tony Lindgren <tony@atomide.com>
+
+properties:
+ compatible:
+ enum:
+ - etsi,3gpp-ts27010-adaption1
+ - motorola,mapphone-mdm6600-serial
+
+ ttymask:
+ $ref: /schemas/types.yaml#/definitions/uint64
+ description: Mask of the TS 27.010 channel TTY interfaces to start (64 bit)
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: motorola,mapphone-mdm6600-serial
+ then:
+ properties:
+ phys:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ description: USB PHY needed for shared GPIO PM wake-up pins
+ maxItems: 1
+
+ phy-names:
+ description: Name of the USB PHY
+ const: usb
+
+ required:
+ - phys
+ - phy-names
+
+required:
+ - compatible
+ - ttymask
+ - "#address-cells"
+ - "#size-cells"
+
+examples:
+ - |
+ modem {
+ compatible = "motorola,mapphone-mdm6600-serial";
+ ttymask = <0 0x00001fee>;
+ phys = <&fsusb1_phy>;
+ phy-names = "usb";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -323,6 +323,8 @@ patternProperties:
description: Espressif Systems Co. Ltd.
"^est,.*":
description: ESTeem Wireless Modems
+ "^etsi,.*":
+ description: ETSI
"^ettus,.*":
description: NI Ettus Research
"^eukrea,.*":
--
2.26.2
^ permalink raw reply
* [PATCH 1/6] tty: n_gsm: Add support for serdev drivers
From: Tony Lindgren @ 2020-05-12 21:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Johan Hovold, Rob Herring
Cc: Alan Cox, Lee Jones, Jiri Slaby, Merlijn Wajer, Pavel Machek,
Peter Hurley, Sebastian Reichel, linux-serial, devicetree,
linux-kernel, linux-omap
In-Reply-To: <20200512214713.40501-1-tony@atomide.com>
We can make use of serdev drivers to do simple device drivers for
TS 27.010 chanels, and we can handle vendor specific protocols on top
of TS 27.010 with serdev drivers.
So far this has been tested with Motorola droid4 where there is a custom
packet numbering protocol on top of TS 27.010 for the MDM6600 modem.
I initially though about adding the serdev support into a separate file,
but that will take some refactoring of n_gsm.c. And I'd like to have
things working first. Then later on we might want to consider splitting
n_gsm.c into three pieces for core, tty and serdev parts. And then maybe
the serdev related parts can be just moved to live under something like
drivers/tty/serdev/protocol/ngsm.c.
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
drivers/tty/n_gsm.c | 435 +++++++++++++++++++++++++++++++++++++
include/linux/serdev-gsm.h | 154 +++++++++++++
2 files changed, 589 insertions(+)
create mode 100644 include/linux/serdev-gsm.h
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -39,6 +39,7 @@
#include <linux/file.h>
#include <linux/uaccess.h>
#include <linux/module.h>
+#include <linux/serdev.h>
#include <linux/timer.h>
#include <linux/tty_flip.h>
#include <linux/tty_driver.h>
@@ -50,6 +51,7 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/gsmmux.h>
+#include <linux/serdev-gsm.h>
static int debug;
module_param(debug, int, 0600);
@@ -150,6 +152,7 @@ struct gsm_dlci {
/* Data handling callback */
void (*data)(struct gsm_dlci *dlci, const u8 *data, int len);
void (*prev_data)(struct gsm_dlci *dlci, const u8 *data, int len);
+ struct gsm_serdev_dlci *ops; /* serdev dlci ops, if used */
struct net_device *net; /* network interface, if created */
};
@@ -198,6 +201,7 @@ enum gsm_mux_state {
*/
struct gsm_mux {
+ struct gsm_serdev *gsd; /* Serial device bus data */
struct tty_struct *tty; /* The tty our ldisc is bound to */
spinlock_t lock;
struct mutex mutex;
@@ -2346,6 +2350,437 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
return 0;
}
+#ifdef CONFIG_SERIAL_DEV_BUS
+
+/**
+ * gsm_serdev_get_config - read ts 27.010 config
+ * @gsd: serdev-gsm instance
+ * @c: ts 27.010 config data
+ *
+ * See gsm_copy_config_values() for more information.
+ */
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ struct gsm_mux *gsm;
+
+ if (!gsd || !gsd->gsm)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ if (!c)
+ return -EINVAL;
+
+ gsm_copy_config_values(gsm, c);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_get_config);
+
+/**
+ * gsm_serdev_set_config - set ts 27.010 config
+ * @gsd: serdev-gsm instance
+ * @c: ts 27.010 config data
+ *
+ * See gsm_config() for more information.
+ */
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ struct gsm_mux *gsm;
+
+ if (!gsd || !gsd->serdev || !gsd->gsm)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ if (!c)
+ return -EINVAL;
+
+ return gsm_config(gsm, c);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_set_config);
+
+static struct gsm_dlci *gsd_dlci_get(struct gsm_serdev *gsd, int line,
+ bool allocate)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+
+ if (!gsd || !gsd->gsm)
+ return ERR_PTR(-ENODEV);
+
+ gsm = gsd->gsm;
+
+ if (line < 1 || line >= 63)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&gsm->mutex);
+
+ if (gsm->dlci[line]) {
+ dlci = gsm->dlci[line];
+ goto unlock;
+ } else if (!allocate) {
+ dlci = ERR_PTR(-ENODEV);
+ goto unlock;
+ }
+
+ dlci = gsm_dlci_alloc(gsm, line);
+ if (!dlci) {
+ dlci = ERR_PTR(-ENOMEM);
+ goto unlock;
+ }
+
+ gsm->dlci[line] = dlci;
+
+unlock:
+ mutex_unlock(&gsm->mutex);
+
+ return dlci;
+}
+
+static int gsd_dlci_receive_buf(struct gsm_serdev_dlci *ops,
+ const unsigned char *buf,
+ size_t len)
+{
+ struct gsm_serdev *gsd = ops->gsd;
+ struct gsm_dlci *dlci;
+ struct tty_port *port;
+
+ dlci = gsd_dlci_get(gsd, ops->line, false);
+ if (IS_ERR(dlci))
+ return PTR_ERR(dlci);
+
+ port = &dlci->port;
+ tty_insert_flip_string(port, buf, len);
+ tty_flip_buffer_push(port);
+
+ return len;
+}
+
+static void gsd_dlci_data(struct gsm_dlci *dlci, const u8 *buf, int len)
+{
+ struct gsm_mux *gsm = dlci->gsm;
+ struct gsm_serdev *gsd = gsm->gsd;
+
+ if (!gsd || !dlci->ops)
+ return;
+
+ switch (dlci->adaption) {
+ case 0:
+ case 1:
+ if (dlci->ops->receive_buf)
+ dlci->ops->receive_buf(dlci->ops, buf, len);
+ break;
+ default:
+ pr_warn("dlci%i adaption %i not yet implemented\n",
+ dlci->addr, dlci->adaption);
+ break;
+ }
+}
+
+/**
+ * gsm_serdev_write - write data to a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ * @buf: write buffer
+ * @len: buffer length
+ */
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+ struct gsm_msg *msg;
+ int h, size, total_size = 0;
+ u8 *dp;
+
+ if (!gsd || !gsd->gsm)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ dlci = gsd_dlci_get(gsd, ops->line, false);
+ if (IS_ERR(dlci))
+ return PTR_ERR(dlci);
+
+ h = dlci->adaption - 1;
+
+ if (len > gsm->mtu)
+ len = gsm->mtu;
+
+ size = len + h;
+
+ msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+ if (!msg)
+ return -ENOMEM;
+
+ dp = msg->data;
+ switch (dlci->adaption) {
+ case 1:
+ break;
+ case 2:
+ *dp++ = gsm_encode_modem(dlci);
+ break;
+ }
+ memcpy(dp, buf, len);
+ gsm_data_queue(dlci, msg);
+ total_size += size;
+
+ return total_size;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_write);
+
+/**
+ * gsm_serdev_data_kick - indicate more data can be transmitted
+ * @gsd: serdev-gsm instance
+ *
+ * See gsm_data_kick() for more information.
+ */
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+ struct gsm_mux *gsm;
+ unsigned long flags;
+
+ if (!gsd || !gsd->gsm)
+ return;
+
+ gsm = gsd->gsm;
+
+ spin_lock_irqsave(&gsm->tx_lock, flags);
+ gsm_data_kick(gsm);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_data_kick);
+
+/**
+ * gsm_serdev_register_dlci - register a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ */
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ struct gsm_dlci *dlci;
+ struct gsm_mux *gsm;
+ int retries;
+
+ if (!gsd || !gsd->gsm || !gsd->serdev)
+ return -ENODEV;
+
+ gsm = gsd->gsm;
+
+ if (!ops || !ops->line)
+ return -EINVAL;
+
+ dlci = gsd_dlci_get(gsd, ops->line, true);
+ if (IS_ERR(dlci))
+ return PTR_ERR(dlci);
+
+ if (dlci->state == DLCI_OPENING || dlci->state == DLCI_OPEN ||
+ dlci->state == DLCI_CLOSING)
+ return -EBUSY;
+
+ mutex_lock(&dlci->mutex);
+ ops->gsd = gsd;
+ dlci->ops = ops;
+ dlci->modem_rx = 0;
+ dlci->prev_data = dlci->data;
+ dlci->data = gsd_dlci_data;
+ mutex_unlock(&dlci->mutex);
+
+ gsm_dlci_begin_open(dlci);
+
+ /*
+ * Allow some time for dlci to move to DLCI_OPEN state. Otherwise
+ * the serdev consumer driver can start sending data too early during
+ * the setup, and the response will be missed by gms_queue() if we
+ * still have DLCI_CLOSED state.
+ */
+ for (retries = 10; retries > 0; retries--) {
+ if (dlci->state == DLCI_OPEN)
+ break;
+ msleep(100);
+ }
+
+ if (!retries)
+ dev_dbg(&gsd->serdev->dev, "dlci%i not currently active\n",
+ dlci->addr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_register_dlci);
+
+/**
+ * gsm_serdev_unregister_dlci - unregister a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ */
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+
+ if (!gsd || !gsd->gsm || !gsd->serdev)
+ return;
+
+ gsm = gsd->gsm;
+
+ if (!ops || !ops->line)
+ return;
+
+ dlci = gsd_dlci_get(gsd, ops->line, false);
+ if (IS_ERR(dlci))
+ return;
+
+ mutex_lock(&dlci->mutex);
+ gsm_destroy_network(dlci);
+ dlci->data = dlci->prev_data;
+ dlci->ops->gsd = NULL;
+ dlci->ops = NULL;
+ mutex_unlock(&dlci->mutex);
+
+ gsm_dlci_begin_close(dlci);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_unregister_dlci);
+
+static int gsm_serdev_output(struct gsm_mux *gsm, u8 *data, int len)
+{
+ struct serdev_device *serdev = gsm->gsd->serdev;
+
+ if (gsm->gsd->output)
+ return gsm->gsd->output(gsm->gsd, data, len);
+ else
+ return serdev_device_write_buf(serdev, data, len);
+}
+
+static int gsd_receive_buf(struct serdev_device *serdev, const u8 *data,
+ size_t count)
+{
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+ struct gsm_mux *gsm;
+ const unsigned char *dp;
+ int i;
+
+ if (WARN_ON(!gsd))
+ return 0;
+
+ gsm = gsd->gsm;
+
+ if (debug & 4)
+ print_hex_dump_bytes("gsd_receive_buf: ",
+ DUMP_PREFIX_OFFSET,
+ data, count);
+
+ for (i = count, dp = data; i; i--, dp++)
+ gsm->receive(gsm, *dp);
+
+ return count;
+}
+
+static void gsd_write_wakeup(struct serdev_device *serdev)
+{
+ serdev_device_write_wakeup(serdev);
+}
+
+static struct serdev_device_ops gsd_client_ops = {
+ .receive_buf = gsd_receive_buf,
+ .write_wakeup = gsd_write_wakeup,
+};
+
+int gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line)
+{
+ struct gsm_serdev_dlci *ops;
+ unsigned int base;
+ int error;
+
+ if (line < 1)
+ return -EINVAL;
+
+ ops = kzalloc(sizeof(*ops), GFP_KERNEL);
+ if (!ops)
+ return -ENOMEM;
+
+ ops->line = line;
+ ops->receive_buf = gsd_dlci_receive_buf;
+
+ error = gsm_serdev_register_dlci(gsd, ops);
+ if (error) {
+ kfree(ops);
+
+ return error;
+ }
+
+ base = mux_num_to_base(gsd->gsm);
+ tty_register_device(gsm_tty_driver, base + ops->line, NULL);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_register_tty_port);
+
+void gsm_serdev_unregister_tty_port(struct gsm_serdev *gsd, int line)
+{
+ struct gsm_dlci *dlci;
+ unsigned int base;
+
+ if (line < 1)
+ return;
+
+ dlci = gsd_dlci_get(gsd, line, false);
+ if (IS_ERR(dlci))
+ return;
+
+ base = mux_num_to_base(gsd->gsm);
+ tty_unregister_device(gsm_tty_driver, base + line);
+ gsm_serdev_unregister_dlci(gsd, dlci->ops);
+ kfree(dlci->ops);
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_unregister_tty_port);
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+ struct gsm_mux *gsm;
+ int error;
+
+ if (WARN(!gsd || !gsd->serdev || !gsd->output,
+ "serdev and output must be initialized\n"))
+ return -EINVAL;
+
+ serdev_device_set_client_ops(gsd->serdev, &gsd_client_ops);
+
+ gsm = gsm_alloc_mux();
+ if (!gsm)
+ return -ENOMEM;
+
+ gsm->encoding = 1;
+ gsm->tty = NULL;
+ gsm->gsd = gsd;
+ gsm->output = gsm_serdev_output;
+ gsd->gsm = gsm;
+ mux_get(gsd->gsm);
+
+ error = gsm_activate_mux(gsd->gsm);
+ if (error) {
+ gsm_cleanup_mux(gsd->gsm);
+ mux_put(gsd->gsm);
+
+ return error;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_register_device);
+
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd)
+{
+ gsm_cleanup_mux(gsd->gsm);
+ mux_put(gsd->gsm);
+ gsd->gsm = NULL;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_unregister_device);
+
+#endif /* CONFIG_SERIAL_DEV_BUS */
+
/**
* gsmld_output - write to link
* @gsm: our mux
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
new file mode 100644
--- /dev/null
+++ b/include/linux/serdev-gsm.h
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_SERDEV_GSM_H
+#define _LINUX_SERDEV_GSM_H
+
+#include <linux/device.h>
+#include <linux/serdev.h>
+#include <linux/types.h>
+
+struct gsm_serdev_dlci;
+struct gsm_config;
+
+/**
+ * struct gsm_serdev - serdev-gsm instance
+ * @serdev: serdev instance
+ * @gsm: ts 27.010 n_gsm instance
+ * @drvdata: serdev-gsm consumer driver data
+ * @output: read data from ts 27.010 channel
+ *
+ * Currently only serdev and output must be initialized, the rest are
+ * are initialized by gsm_serdev_register_dlci().
+ */
+struct gsm_serdev {
+ struct serdev_device *serdev;
+ struct gsm_mux *gsm;
+ void *drvdata;
+ int (*output)(struct gsm_serdev *gsd, u8 *data, int len);
+};
+
+/**
+ * struct gsm_serdev_dlci - serdev-gsm ts 27.010 channel data
+ * @gsd: serdev-gsm instance
+ * @line: ts 27.010 channel, control channel 0 is not available
+ * @receive_buf: function to handle data received for the channel
+ * @drvdata: dlci specific consumer driver data
+ */
+struct gsm_serdev_dlci {
+ struct gsm_serdev *gsd;
+ int line;
+ int (*receive_buf)(struct gsm_serdev_dlci *ops,
+ const unsigned char *buf,
+ size_t len);
+ void *drvdata;
+};
+
+#if IS_ENABLED(CONFIG_N_GSM) && IS_ENABLED(CONFIG_SERIAL_DEV_BUS)
+
+extern int gsm_serdev_register_device(struct gsm_serdev *gsd);
+extern void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
+extern int gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line);
+extern void gsm_serdev_unregister_tty_port(struct gsm_serdev *gsd, int line);
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+ struct serdev_device *serdev = to_serdev_device(dev);
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+ if (gsd)
+ return gsd->drvdata;
+
+ return NULL;
+}
+
+static inline void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+ struct serdev_device *serdev = to_serdev_device(dev);
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+ if (gsd)
+ gsd->drvdata = drvdata;
+}
+
+extern int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c);
+extern int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c);
+extern int
+gsm_serdev_register_dlci(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops);
+extern void
+gsm_serdev_unregister_dlci(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops);
+extern int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len);
+extern void gsm_serdev_data_kick(struct gsm_serdev *gsd);
+
+#else /* CONFIG_SERIAL_DEV_BUS */
+
+static inline
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+ return -ENODEV;
+}
+
+static inline void gsm_serdev_unregister_device(struct gsm_serdev *gsd)
+{
+}
+
+static inline int
+gsm_serdev_register_tty_port(struct gsm_serdev *gsd, int line)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_tty_port(struct gsm_serdev *gsd, int line)
+{
+}
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+ return NULL;
+}
+
+static inline
+void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+}
+
+static inline
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ return -ENODEV;
+}
+
+static inline
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ return -ENODEV;
+}
+
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+}
+
+static inline
+int gsm_serdev_write(struct gsm_serdev *gsd, struct gsm_serdev_dlci *ops,
+ const u8 *buf, int len)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+}
+
+#endif /* CONFIG_N_GSM && CONFIG_SERIAL_DEV_BUS */
+#endif /* _LINUX_SERDEV_GSM_H */
--
2.26.2
^ permalink raw reply
* Re: [PATCH v8 08/10] cpufreq: dt: Validate all interconnect paths
From: Matthias Kaehlcke @ 2020-05-12 21:47 UTC (permalink / raw)
To: Georgi Djakov
Cc: vireshk, nm, sboyd, rjw, saravanak, sibis, robh+dt, rnayak,
bjorn.andersson, vincent.guittot, jcrouse, evgreen, linux-pm,
devicetree, linux-kernel
In-Reply-To: <20200512125327.1868-9-georgi.djakov@linaro.org>
Hi Georgi,
On Tue, May 12, 2020 at 03:53:25PM +0300, Georgi Djakov wrote:
> Currently when we check for the available resources, we assume that there
> is only one interconnect path, but in fact it could be more than one. Do
> some validation to determine the number of paths and verify if each one
> of them is available.
>
> Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
> ---
> v8:
> * New patch.
>
> drivers/cpufreq/cpufreq-dt.c | 49 ++++++++++++++++++++++++++++++++----
> 1 file changed, 44 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/cpufreq/cpufreq-dt.c b/drivers/cpufreq/cpufreq-dt.c
> index 4ecef3257532..3dd28c2c1633 100644
> --- a/drivers/cpufreq/cpufreq-dt.c
> +++ b/drivers/cpufreq/cpufreq-dt.c
> @@ -91,12 +91,54 @@ static const char *find_supply_name(struct device *dev)
> return name;
> }
>
> +static int find_icc_paths(struct device *dev)
> +{
> + struct device_node *np;
> + struct icc_path **paths;
> + int i, count, num_paths;
> + int ret = 0;
> +
> + np = of_node_get(dev->of_node);
> + if (!np)
> + return 0;
> +
> + count = of_count_phandle_with_args(np, "interconnects",
> + "#interconnect-cells");
> + of_node_put(np);
> + if (count < 0)
> + return 0;
> +
> + /* two phandles when #interconnect-cells = <1> */
> + if (count % 2) {
> + dev_err(dev, "%s: Invalid interconnects values\n", __func__);
> + return -EINVAL;
> + }
> +
> + num_paths = count / 2;
> + paths = kcalloc(num_paths, sizeof(*paths), GFP_KERNEL);
> + if (!paths)
> + return -ENOMEM;
> +
> + for (i = 0; i < num_paths; i++) {
> + paths[i] = of_icc_get_by_index(dev, i);
> + ret = PTR_ERR_OR_ZERO(paths[i]);
> + if (ret)
> + break;
> + }
> +
> + while (i--)
> + icc_put(paths[i]);
Since the function only does a validation and throws the paths away
afterwards you don't really need the dynamic allocation and 'icc_put'
loop. Just have a single 'struct icc_path' pointer and call icc_put()
inside the for loop.
^ permalink raw reply
* [PATCH] ARM: dts: imx6qdl-gw5910: add support for bcm4330-bt
From: Tim Harvey @ 2020-05-12 21:54 UTC (permalink / raw)
To: Rob Herring, Shawn Guo, Sascha Hauer
Cc: Pengutronix Kernel Team, Fabio Estevam, NXP Linux Team,
devicetree, linux-arm-kernel, linux-kernel, Tim Harvey
The Sterling-LWB has a BCM4330 which has a UART based bluetooth
HCI. Add support for binding to the bcm_hci driver to take care
of handling the shutdown gpio and loading firmware.
Because the shutdown gpio is more of an enable than a regulator
go ahead and replace the regulator with a shutdown-gpio.
Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
arch/arm/boot/dts/imx6qdl-gw5910.dtsi | 32 ++++++++++++--------------------
1 file changed, 12 insertions(+), 20 deletions(-)
diff --git a/arch/arm/boot/dts/imx6qdl-gw5910.dtsi b/arch/arm/boot/dts/imx6qdl-gw5910.dtsi
index 30fe47f..b850f8f 100644
--- a/arch/arm/boot/dts/imx6qdl-gw5910.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-gw5910.dtsi
@@ -83,19 +83,6 @@
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
-
- reg_bt: regulator-bt {
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_reg_bt>;
- compatible = "regulator-fixed";
- regulator-name = "bt";
- gpio = <&gpio1 2 GPIO_ACTIVE_HIGH>;
- startup-delay-us = <100>;
- enable-active-high;
- regulator-min-microvolt = <3300000>;
- regulator-max-microvolt = <3300000>;
- regulator-always-on;
- };
};
@@ -231,9 +218,14 @@
/* Sterling-LWB Bluetooth */
&uart4 {
pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_uart4>;
+ pinctrl-0 = <&pinctrl_uart4>,<&pinctrl_bten>;
uart-has-rtscts;
status = "okay";
+
+ bluetooth {
+ compatible = "brcm,bcm4330-bt";
+ shutdown-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
+ };
};
/* GPS */
@@ -288,6 +280,12 @@
>;
};
+ pinctrl_bten: btengrp {
+ fsl,pins = <
+ MX6QDL_PAD_GPIO_2__GPIO1_IO02 0x1b0b1
+ >;
+ };
+
pinctrl_ecspi3: escpi3grp {
fsl,pins = <
MX6QDL_PAD_DISP0_DAT0__ECSPI3_SCLK 0x100b1
@@ -393,12 +391,6 @@
>;
};
- pinctrl_reg_bt: regbtgrp {
- fsl,pins = <
- MX6QDL_PAD_GPIO_2__GPIO1_IO02 0x1b0b1
- >;
- };
-
pinctrl_reg_wl: regwlgrp {
fsl,pins = <
MX6QDL_PAD_GPIO_5__GPIO1_IO05 0x1b0b1
--
2.7.4
^ permalink raw reply related
* Re: [PATCH v3 05/16] mfd: Add support for Kontron sl28cpld management controller
From: Rob Herring @ 2020-05-12 21:59 UTC (permalink / raw)
To: Michael Walle
Cc: Andy Shevchenko, open list:GPIO SUBSYSTEM, devicetree,
linux-kernel@vger.kernel.org, Linux HWMON List, Linux PWM List,
LINUX-WATCHDOG,
moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
Linus Walleij, Bartosz Golaszewski, Jean Delvare, Guenter Roeck,
Lee Jones, Thierry Reding, Uwe Kleine-König,
Wim Van Sebroeck, Shawn Guo, Li Yang, Thomas Gleixner,
Jason Cooper, Marc Zyngier, Mark Brown, Greg Kroah-Hartman
In-Reply-To: <f0fafa63047f00e912013b137e4db15c@walle.cc>
On Mon, May 11, 2020 at 4:45 PM Michael Walle <michael@walle.cc> wrote:
>
> Am 2020-05-11 23:13, schrieb Rob Herring:
> > On Thu, Apr 23, 2020 at 07:45:32PM +0200, Michael Walle wrote:
> >> This patch adds core support for the board management controller found
> >> on the SMARC-sAL28 board. It consists of the following functions:
> >> - watchdog
> >> - GPIO controller
> >> - PWM controller
> >> - fan sensor
> >> - interrupt controller
> >>
> >> At the moment, this controller is used on the Kontron SMARC-sAL28
> >> board.
> >>
> >> Please note that the MFD driver is defined as bool in the Kconfig
> >> because the next patch will add interrupt support.
> >>
> >> Signed-off-by: Michael Walle <michael@walle.cc>
> >> ---
> >> drivers/mfd/Kconfig | 19 +++++
> >> drivers/mfd/Makefile | 2 +
> >> drivers/mfd/sl28cpld.c | 153
> >> +++++++++++++++++++++++++++++++++++++++++
> >> 3 files changed, 174 insertions(+)
> >> create mode 100644 drivers/mfd/sl28cpld.c
> >>
> >> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> >> index 0a59249198d3..be0c8d93c526 100644
> >> --- a/drivers/mfd/Kconfig
> >> +++ b/drivers/mfd/Kconfig
> >> @@ -2060,5 +2060,24 @@ config SGI_MFD_IOC3
> >> If you have an SGI Origin, Octane, or a PCI IOC3 card,
> >> then say Y. Otherwise say N.
> >>
> >> +config MFD_SL28CPLD
> >> + bool "Kontron sl28 core driver"
> >> + depends on I2C=y
> >> + depends on OF
> >> + select REGMAP_I2C
> >> + select MFD_CORE
> >> + help
> >> + This option enables support for the board management controller
> >> + found on the Kontron sl28 CPLD. You have to select individual
> >> + functions, such as watchdog, GPIO, etc, under the corresponding
> >> menus
> >> + in order to enable them.
> >> +
> >> + Currently supported boards are:
> >> +
> >> + Kontron SMARC-sAL28
> >> +
> >> + To compile this driver as a module, choose M here: the module will
> >> be
> >> + called sl28cpld.
> >> +
> >> endmenu
> >> endif
> >> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> >> index f935d10cbf0f..9bc38863b9c7 100644
> >> --- a/drivers/mfd/Makefile
> >> +++ b/drivers/mfd/Makefile
> >> @@ -259,3 +259,5 @@ obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
> >> obj-$(CONFIG_MFD_STMFX) += stmfx.o
> >>
> >> obj-$(CONFIG_SGI_MFD_IOC3) += ioc3.o
> >> +
> >> +obj-$(CONFIG_MFD_SL28CPLD) += sl28cpld.o
> >> diff --git a/drivers/mfd/sl28cpld.c b/drivers/mfd/sl28cpld.c
> >> new file mode 100644
> >> index 000000000000..1e5860cc7ffc
> >> --- /dev/null
> >> +++ b/drivers/mfd/sl28cpld.c
> >> @@ -0,0 +1,153 @@
> >> +// SPDX-License-Identifier: GPL-2.0-only
> >> +/*
> >> + * MFD core for the sl28cpld.
> >> + *
> >> + * Copyright 2019 Kontron Europe GmbH
> >> + */
> >> +
> >> +#include <linux/i2c.h>
> >> +#include <linux/interrupt.h>
> >> +#include <linux/kernel.h>
> >> +#include <linux/mfd/core.h>
> >> +#include <linux/module.h>
> >> +#include <linux/of_platform.h>
> >> +#include <linux/regmap.h>
> >> +
> >> +#define SL28CPLD_VERSION 0x03
> >> +#define SL28CPLD_WATCHDOG_BASE 0x04
> >> +#define SL28CPLD_HWMON_FAN_BASE 0x0b
> >> +#define SL28CPLD_PWM0_BASE 0x0c
> >> +#define SL28CPLD_PWM1_BASE 0x0e
> >> +#define SL28CPLD_GPIO0_BASE 0x10
> >> +#define SL28CPLD_GPIO1_BASE 0x15
> >> +#define SL28CPLD_GPO_BASE 0x1a
> >> +#define SL28CPLD_GPI_BASE 0x1b
> >> +#define SL28CPLD_INTC_BASE 0x1c
> >
> > If you want to use 'reg' in the binding, these are the numbers you
> > should be using rather than making up numbering!
>
> My motivation is that I don't want to hardcode the internal addresses
> of the management controller in the device tree. For example if they
> will move around with a later update of the controller, so a driver can
> be compatible with both the old and the new version. If they are in the
> device tree, only one register layout is possible.
I don't understand, if the addresses change, then the above defines
have to change. So your driver is only compatible with 1 version. If
you change the CPLD, then that's a h/w change and your h/w description
(DT) should change. That can either be the compatible string changing
and updating the driver with new match data such as register offsets
or all the differences are in DT and there's no kernel change.
> > However, I still don't think you need any child nodes. All the data in
> > the DT binding is right here in the driver already. There's no
> > advantage
> > to putting child nodes in DT, because this driver still has to be
> > updated if you add more nodes.
>
> But then any phandle will reference the mfd device. And for example
> there
> are two different interrupt controllers, that is the INTC and the
> GPIO[01],
> which will then be combined into one device tree node, right?
You either have to add a cell for 'bank' or divide the 1st cell into a
bank and index. Both have been done before.
To go the other direction, AIUI you shouldn't need OF_MFD_CELL_REG
entries if you have the child devices in DT. Pick one way or the
other. It's ultimately a judgement call. For a one-off device, sub
devices in DT doesn't really buy you anything. If you have sub-blocks
showing up multiple devices, then sub devices makes sense. If there's
only 2-3 combinations, then it's a toss up.
Rob
^ permalink raw reply
* [PATCH] ARM: dts: imx6qdl-gw5910: fix wlan regulator
From: Tim Harvey @ 2020-05-12 22:02 UTC (permalink / raw)
To: Rob Herring, Shawn Guo, Sascha Hauer
Cc: Pengutronix Kernel Team, Fabio Estevam, NXP Linux Team,
devicetree, linux-arm-kernel, linux-kernel, Tim Harvey
Connect the wl_reg regulator to usdhc2 such that it can be enabled
and disabled as needed. There is no need for this to be always-on.
Signed-off-by: Tim Harvey <tharvey@gateworks.com>
---
arch/arm/boot/dts/imx6qdl-gw5910.dtsi | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/arch/arm/boot/dts/imx6qdl-gw5910.dtsi b/arch/arm/boot/dts/imx6qdl-gw5910.dtsi
index b850f8f..0857de5 100644
--- a/arch/arm/boot/dts/imx6qdl-gw5910.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-gw5910.dtsi
@@ -81,7 +81,6 @@
enable-active-high;
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
- regulator-always-on;
};
};
@@ -251,7 +250,7 @@
&usdhc2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc2>;
- vmmc-supply = <®_3p3v>;
+ vmmc-supply = <®_wl>;
non-removable;
bus-width = <4>;
status = "okay";
--
2.7.4
^ permalink raw reply related
* Re: [PATCH 1/6] dt-bindings: phy: meson8b-usb2: Convert to json-schema
From: Rob Herring @ 2020-05-12 22:03 UTC (permalink / raw)
To: Martin Blumenstingl
Cc: kishon, vkoul, devicetree, linux-amlogic, narmstrong,
linux-arm-kernel, linux-kernel, hexdump0815
In-Reply-To: <20200502114752.1048500-2-martin.blumenstingl@googlemail.com>
On Sat, May 02, 2020 at 01:47:47PM +0200, Martin Blumenstingl wrote:
> Now that we have the DT validation in place, let's convert the device
> tree bindings for the Amlogic Meson8, Meson8b, Meson8m2 and GXBB USB2
> PHY over to a YAML schema.
>
> While here, also add the fallback compatible string
> "amlogic,meson-gxbb-usb2-phy" which is already used in
> arch/arm/boot/dts/meson{,8,8b}.dtsi.
>
> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> ---
> .../phy/amlogic,meson8b-usb2-phy.yaml | 61 +++++++++++++++++++
> .../bindings/phy/meson8b-usb2-phy.txt | 28 ---------
> 2 files changed, 61 insertions(+), 28 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/phy/amlogic,meson8b-usb2-phy.yaml
> delete mode 100644 Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt
>
> diff --git a/Documentation/devicetree/bindings/phy/amlogic,meson8b-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/amlogic,meson8b-usb2-phy.yaml
> new file mode 100644
> index 000000000000..c2fe8c08d99e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/amlogic,meson8b-usb2-phy.yaml
> @@ -0,0 +1,61 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/phy/amlogic,meson8b-usb2-phy.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Amlogic Meson8, Meson8b, Meson8m2 and GXBB USB2 PHY
> +
> +maintainers:
> + - Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> +
> +properties:
> + compatible:
> + oneOf:
> + - items:
> + - enum:
> + - amlogic,meson8-usb2-phy
> + - amlogic,meson8b-usb2-phy
Needs 2 more spaces indent.
> + - const: amlogic,meson-mx-usb2-phy
> + - const: amlogic,meson-gxbb-usb2-phy
> +
> + reg:
> + maxItems: 1
> +
> + clocks:
> + minItems: 2
> +
> + clock-names:
> + items:
> + - const: usb_general
> + - const: usb
> +
> + resets:
> + minItems: 1
> +
> + "#phy-cells":
> + const: 0
> +
> + phy-supply:
> + description:
Wrong indentation.
> + Phandle to a regulator that provides power to the PHY. This
> + regulator will be managed during the PHY power on/off sequence.
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - "#phy-cells"
Add:
additionalProperties: false
> +
> +examples:
> + - |
> + usb-phy@c0000000 {
> + compatible = "amlogic,meson-gxbb-usb2-phy";
> + reg = <0xc0000000 0x20>;
> + resets = <&reset_usb_phy>;
> + clocks = <&clk_usb_general>, <&reset_usb>;
> + clock-names = "usb_general", "usb";
> + phy-supply = <&usb_vbus>;
> + #phy-cells = <0>;
> + };
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox