* Re: [Patch] mtd: fix writing incorrect ECC parity data in OOB region.
From: Boris Brezillon @ 2016-08-29 8:10 UTC (permalink / raw)
To: mtk04561
Cc: robh-DgEjT+Ai2ygdnm+yROfE0A,
daniel.thompson-QSEj5FYQhm4dnm+yROfE0A,
steven.liu-NuS5LvNUpcJWk0Htik3J/w,
linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Jorge Ramirez-Ortiz,
matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
xiaolei.li-NuS5LvNUpcJWk0Htik3J/w,
computersforpeace-Re5JQEeQqe8AvxtiuMwx3w,
dwmw2-wEGCiKHe2LqWVfeAwA7xHQ, blogic-p3rKhJxN3npAfugRpC6u6w
In-Reply-To: <1472443093.27061.4.camel@mtkswgap22>
+Jorge
Hi Roger,
On Mon, 29 Aug 2016 11:58:13 +0800
mtk04561 <rogercc.lin-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org> wrote:
> Hi Boris,
>
> I found a problem in current Mediatek upstream NAND driver (of nand/next
> branch). When testing the upstream driver with UBIFS file system, it has
> some chance to create incorrect ECC data in the second subpage, this
> makes UBIFS failed to mount. Below patch fixes this problem. Please help
> to review the patch and upstream. Thank you.
>
> Best regards,
> Roger
Can you put this message after the '---' (below your SoB), so that it
does not appear when applying the patch.
>
>
>
> Description:
Drop the 'Description:' line.
> When mtk_ecc_encode() is writing the ECC parity data to the OOB region,
> because each register is 4 bytes in length, but the len's unit is in
> bytes, the operation in the for loop will cross the ECC's boundary.
>
> And when mtk_nfc_do_write_page() comparing the sector number, because
> the sector number field is at the 12th-bit position of NFI_BYTELEN
> register, the masked register should be shifted 12 bits before being
> compared. The result of this bug may cause the second subpage has
> incomplete ECC parity bytes.
Maybe you could split those changes in 2 patches (not a strong
requirement).
>
> Test:
Drop the 'Test:' line.
> The patch passed the test of UBIFS file-system read/write on Mediatek's
> RFB. The tested driver is checked-out from LEDE OpenWRT project's
> upstream driver, which is pretty much same as nand/next branch upstream
> driver(git clone https://git.lede-project.org/source.git).
>
>
> Signed-off-by: RogerCC Lin <rogercc.lin-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> ---
> diff --git a/drivers/mtd/nand/mtk_ecc.c b/drivers/mtd/nand/mtk_ecc.c
> index 25a4fbd..0a7ce8d 100644
> --- a/drivers/mtd/nand/mtk_ecc.c
> +++ b/drivers/mtd/nand/mtk_ecc.c
> @@ -366,7 +366,8 @@ int mtk_ecc_encode(struct mtk_ecc *ecc, struct
> mtk_ecc_config *config,
> u8 *data, u32 bytes)
> {
> dma_addr_t addr;
> - u32 *p, len, i;
> + u8 *p;
> + u32 len, i, val;
> int ret = 0;
>
> addr = dma_map_single(ecc->dev, data, bytes, DMA_TO_DEVICE);
> @@ -395,8 +396,11 @@ int mtk_ecc_encode(struct mtk_ecc *ecc, struct
> mtk_ecc_config *config,
> p = (u32 *)(data + bytes);
Hm, you should now do:
p = data + bytes;
>
> /* write the parity bytes generated by the ECC back to the OOB
> region */
> - for (i = 0; i < len; i++)
> - p[i] = readl(ecc->regs + ECC_ENCPAR(i));
> + for (i = 0; i < len; i++) {
> + if ((i % 4) == 0)
> + val = readl(ecc->regs + ECC_ENCPAR(i >> 2));
> + p[i] = *((u8 *)&val + (i % 4));
Please, use a shift operator here:
p[i] = (val >> ((i % 4) * 8)) & 0xff;
Otherwise you're exposed to endianness conversion problems (this works
fine in your case because you're using a little-endian kernel).
> + }
> timeout:
>
> dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE);
> diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
> index ddaa2ac..9c49121 100644
> --- a/drivers/mtd/nand/mtk_nand.c
> +++ b/drivers/mtd/nand/mtk_nand.c
> @@ -699,8 +699,8 @@ static int mtk_nfc_do_write_page(struct mtd_info
> *mtd, struct nand_chip *chip,
> }
>
> ret = readl_poll_timeout_atomic(nfc->regs + NFI_ADDRCNTR, reg,
> - (reg & CNTR_MASK) >=
> chip->ecc.steps,
> - 10, MTK_TIMEOUT);
> + ((reg & CNTR_MASK) >> 12) >=
> + chip->ecc.steps, 10,
Please keep '((reg & CNTR_MASK) >> 12) >= chip->ecc.steps' on the same
line.
If you want to comply with the 80 char lines, you can do:
ret = readl_poll_timeout_atomic(nfc->regs + NFI_ADDRCNTR, reg,
((reg & CNTR_MASK) >> 12) >= chip->ecc.steps,
10, MTK_TIMEOUT);
> if (ret)
> dev_err(dev, "hwecc write timeout\n");
>
> @@ -902,8 +902,8 @@ static int mtk_nfc_read_subpage(struct mtd_info
> *mtd, struct nand_chip *chip,
> dev_warn(nfc->dev, "read ahb/dma done timeout\n");
>
> rc = readl_poll_timeout_atomic(nfc->regs + NFI_BYTELEN, reg,
> - (reg & CNTR_MASK) >= sectors, 10,
> - MTK_TIMEOUT);
> + ((reg & CNTR_MASK) >> 12) >=
> sectors,
> + 10, MTK_TIMEOUT);
> if (rc < 0) {
> dev_err(nfc->dev, "subpage done timeout\n");
> bitflips = -EIO;
>
>
^ permalink raw reply
* Re: [RESEND PATCH, v5 3/5] usb: xhci-mtk: make IPPC register optional
From: Felipe Balbi @ 2016-08-29 8:01 UTC (permalink / raw)
To: Greg Kroah-Hartman, Mathias Nyman, Oliver Neukum,
Matthias Brugger
Cc: Alan Stern, Rob Herring, Mark Rutland, Ian Campbell,
Sergei Shtylyov, Pawel Moll, Kumar Gala, Sascha Hauer,
Alan Cooper, Chunfeng Yun, linux-usb-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1472094329-18466-4-git-send-email-chunfeng.yun-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
[-- Attachment #1: Type: text/plain, Size: 431 bytes --]
Chunfeng Yun <chunfeng.yun-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org> writes:
> Make IPPC register optional to support host side of dual-role mode,
> due to it is moved into common glue layer for simplification.
>
> Signed-off-by: Chunfeng Yun <chunfeng.yun-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Mathias? Are you taking these patches?? I don't wanna take peripheral
side driver if you won't take xhci side.
--
balbi
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 800 bytes --]
^ permalink raw reply
* [PATCH net v2 9/9] net: ethernet: mediatek: fix error handling inside mtk_mdio_init
From: sean.wang @ 2016-08-29 5:03 UTC (permalink / raw)
To: john, davem; +Cc: nbd, netdev, linux-mediatek, keyhaede, Sean Wang
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang@mediatek.com>
From: Sean Wang <sean.wang@mediatek.com>
return -ENODEV if no child is found in MDIO bus.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Acked-by: John Crispin <john@phrozen.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index f741c6a..e48b2a4 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -304,7 +304,7 @@ static int mtk_mdio_init(struct mtk_eth *eth)
}
if (!of_device_is_available(mii_np)) {
- ret = 0;
+ ret = -ENODEV;
goto err_put_node;
}
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 8/9] net: ethernet: mediatek: use devm_mdiobus_alloc instead of mdiobus_alloc inside mtk_mdio_init
From: sean.wang @ 2016-08-29 5:03 UTC (permalink / raw)
To: john, davem; +Cc: nbd, netdev, linux-mediatek, keyhaede, Sean Wang
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang@mediatek.com>
From: Sean Wang <sean.wang@mediatek.com>
a lot of parts in the driver uses devm_* APIs to gain benefits from the
device resource management, so devm_mdiobus_alloc is also used instead
of mdiobus_alloc to have more elegant code flow.
Using common code provided by the devm_* helps to
1) have simplified the code flow as [1] says
2) decrease the risk of incorrect error handling by human
3) only a few drivers used it since it ware proposed on linux 3.16,
so just hope to promote for this.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
ref.
[1] https://patchwork.ozlabs.org/patch/344093/
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 23 ++++++-----------------
1 file changed, 6 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 85a527a..f741c6a 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -295,7 +295,7 @@ err_phy:
static int mtk_mdio_init(struct mtk_eth *eth)
{
struct device_node *mii_np;
- int err;
+ int ret;
mii_np = of_get_child_by_name(eth->dev->of_node, "mdio-bus");
if (!mii_np) {
@@ -304,13 +304,13 @@ static int mtk_mdio_init(struct mtk_eth *eth)
}
if (!of_device_is_available(mii_np)) {
- err = 0;
+ ret = 0;
goto err_put_node;
}
- eth->mii_bus = mdiobus_alloc();
+ eth->mii_bus = devm_mdiobus_alloc(eth->dev);
if (!eth->mii_bus) {
- err = -ENOMEM;
+ ret = -ENOMEM;
goto err_put_node;
}
@@ -321,20 +321,11 @@ static int mtk_mdio_init(struct mtk_eth *eth)
eth->mii_bus->parent = eth->dev;
snprintf(eth->mii_bus->id, MII_BUS_ID_SIZE, "%s", mii_np->name);
- err = of_mdiobus_register(eth->mii_bus, mii_np);
- if (err)
- goto err_free_bus;
- of_node_put(mii_np);
-
- return 0;
-
-err_free_bus:
- mdiobus_free(eth->mii_bus);
+ ret = of_mdiobus_register(eth->mii_bus, mii_np);
err_put_node:
of_node_put(mii_np);
- eth->mii_bus = NULL;
- return err;
+ return ret;
}
static void mtk_mdio_cleanup(struct mtk_eth *eth)
@@ -343,8 +334,6 @@ static void mtk_mdio_cleanup(struct mtk_eth *eth)
return;
mdiobus_unregister(eth->mii_bus);
- of_node_put(eth->mii_bus->dev.of_node);
- mdiobus_free(eth->mii_bus);
}
static inline void mtk_irq_disable(struct mtk_eth *eth, u32 mask)
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 7/9] net: ethernet: mediatek: fix the missing of_node_put() after node is used done inside mtk_mdio_init
From: sean.wang @ 2016-08-29 5:03 UTC (permalink / raw)
To: john, davem; +Cc: nbd, netdev, linux-mediatek, keyhaede, Sean Wang
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang@mediatek.com>
From: Sean Wang <sean.wang@mediatek.com>
This patch adds the missing of_node_put() after finishing the usage
of of_get_child_by_name.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Acked-by: John Crispin <john@phrozen.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 1001317..85a527a 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -324,6 +324,7 @@ static int mtk_mdio_init(struct mtk_eth *eth)
err = of_mdiobus_register(eth->mii_bus, mii_np);
if (err)
goto err_free_bus;
+ of_node_put(mii_np);
return 0;
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 6/9] net: ethernet: mediatek: fix issue of driver removal with interface is up
From: sean.wang-NuS5LvNUpcJWk0Htik3J/w @ 2016-08-29 5:03 UTC (permalink / raw)
To: john-Pj+rj9U5foFAfugRpC6u6w, davem-fT/PcQaiUtIeIZ0/mPfg9Q
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
Sean Wang, linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
mtk_stop() must be called to stop for freeing DMA
resources acquired and restoring state changed by mtk_open()
firstly when module removal.
Signed-off-by: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 17dd2f8..1001317 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -1903,6 +1903,14 @@ err_free_dev:
static int mtk_remove(struct platform_device *pdev)
{
struct mtk_eth *eth = platform_get_drvdata(pdev);
+ int i;
+
+ /* stop all devices to make sure that dma is properly shut down */
+ for (i = 0; i < MTK_MAC_COUNT; i++) {
+ if (!eth->netdev[i])
+ continue;
+ mtk_stop(eth->netdev[i]);
+ }
clk_disable_unprepare(eth->clks[MTK_CLK_GP1]);
clk_disable_unprepare(eth->clks[MTK_CLK_GP2]);
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 5/9] net: ethernet: mediatek: fix logic unbalance between probe and remove
From: sean.wang-NuS5LvNUpcJWk0Htik3J/w @ 2016-08-29 5:03 UTC (permalink / raw)
To: john-Pj+rj9U5foFAfugRpC6u6w, davem-fT/PcQaiUtIeIZ0/mPfg9Q
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
Sean Wang, linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
original mdio_cleanup is not in the symmetric place against where
mdio_init is, so relocate mdio_cleanup to the right one.
Signed-off-by: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Acked-by: John Crispin <john-Pj+rj9U5foFAfugRpC6u6w@public.gmane.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 2c5754e..17dd2f8 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -1508,7 +1508,6 @@ static void mtk_uninit(struct net_device *dev)
struct mtk_eth *eth = mac->hw;
phy_disconnect(mac->phy_dev);
- mtk_mdio_cleanup(eth);
mtk_irq_disable(eth, ~0);
}
@@ -1913,6 +1912,7 @@ static int mtk_remove(struct platform_device *pdev)
netif_napi_del(ð->tx_napi);
netif_napi_del(ð->rx_napi);
mtk_cleanup(eth);
+ mtk_mdio_cleanup(eth);
return 0;
}
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 4/9] net: ethernet: mediatek: remove redundant free_irq for devm_request_irq allocated irq
From: sean.wang-NuS5LvNUpcJWk0Htik3J/w @ 2016-08-29 5:03 UTC (permalink / raw)
To: john-Pj+rj9U5foFAfugRpC6u6w, davem-fT/PcQaiUtIeIZ0/mPfg9Q
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
Sean Wang, linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
these irqs are not used for shared irq and disabled during ethernet stops.
irq requested by devm_request_irq is safe to be freed automatically on
driver detach.
Signed-off-by: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Acked-by: John Crispin <john-Pj+rj9U5foFAfugRpC6u6w@public.gmane.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 518d987..2c5754e 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -1510,8 +1510,6 @@ static void mtk_uninit(struct net_device *dev)
phy_disconnect(mac->phy_dev);
mtk_mdio_cleanup(eth);
mtk_irq_disable(eth, ~0);
- free_irq(eth->irq[1], dev);
- free_irq(eth->irq[2], dev);
}
static int mtk_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 3/9] net: ethernet: mediatek: fix API usage with skb_free_frag
From: sean.wang @ 2016-08-29 5:03 UTC (permalink / raw)
To: john, davem; +Cc: nbd, netdev, linux-mediatek, keyhaede, Sean Wang
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang@mediatek.com>
From: Sean Wang <sean.wang@mediatek.com>
use skb_free_frag() instead of legacy put_page()
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Acked-by: John Crispin <john@phrozen.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index ad4865c..518d987 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -868,7 +868,7 @@ static int mtk_poll_rx(struct napi_struct *napi, int budget,
/* receive data */
skb = build_skb(data, ring->frag_size);
if (unlikely(!skb)) {
- put_page(virt_to_head_page(new_data));
+ skb_free_frag(new_data);
netdev->stats.rx_dropped++;
goto release_desc;
}
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 2/9] net: ethernet: mediatek: fix incorrect return value of devm_clk_get with EPROBE_DEFER
From: sean.wang-NuS5LvNUpcJWk0Htik3J/w @ 2016-08-29 5:03 UTC (permalink / raw)
To: john-Pj+rj9U5foFAfugRpC6u6w, davem-fT/PcQaiUtIeIZ0/mPfg9Q
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
Sean Wang, linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
1) If the return value of devm_clk_get is EPROBE_DEFER, we should
defer probing the driver. The change is verified and works based
on 4.8-rc1 staying with the latest clk-next code for MT7623.
2) Changing with the usage of loops to work out if all clocks are
fine
Signed-off-by: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 39 ++++++++++++++++-------------
drivers/net/ethernet/mediatek/mtk_eth_soc.h | 22 ++++++++++------
2 files changed, 36 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 6e4a6ca..ad4865c 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -50,6 +50,10 @@ static const struct mtk_ethtool_stats {
MTK_ETHTOOL_STAT(rx_flow_control_packets),
};
+static const char * const mtk_clks_source_name[] = {
+ "ethif", "esw", "gp1", "gp2"
+};
+
void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg)
{
__raw_writel(val, eth->base + reg);
@@ -1811,6 +1815,7 @@ static int mtk_probe(struct platform_device *pdev)
if (!eth)
return -ENOMEM;
+ eth->dev = &pdev->dev;
eth->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(eth->base))
return PTR_ERR(eth->base);
@@ -1845,21 +1850,21 @@ static int mtk_probe(struct platform_device *pdev)
return -ENXIO;
}
}
+ for (i = 0; i < ARRAY_SIZE(eth->clks); i++) {
+ eth->clks[i] = devm_clk_get(eth->dev,
+ mtk_clks_source_name[i]);
+ if (IS_ERR(eth->clks[i])) {
+ if (PTR_ERR(eth->clks[i]) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ return -ENODEV;
+ }
+ }
- eth->clk_ethif = devm_clk_get(&pdev->dev, "ethif");
- eth->clk_esw = devm_clk_get(&pdev->dev, "esw");
- eth->clk_gp1 = devm_clk_get(&pdev->dev, "gp1");
- eth->clk_gp2 = devm_clk_get(&pdev->dev, "gp2");
- if (IS_ERR(eth->clk_esw) || IS_ERR(eth->clk_gp1) ||
- IS_ERR(eth->clk_gp2) || IS_ERR(eth->clk_ethif))
- return -ENODEV;
-
- clk_prepare_enable(eth->clk_ethif);
- clk_prepare_enable(eth->clk_esw);
- clk_prepare_enable(eth->clk_gp1);
- clk_prepare_enable(eth->clk_gp2);
+ clk_prepare_enable(eth->clks[MTK_CLK_ETHIF]);
+ clk_prepare_enable(eth->clks[MTK_CLK_ESW]);
+ clk_prepare_enable(eth->clks[MTK_CLK_GP1]);
+ clk_prepare_enable(eth->clks[MTK_CLK_GP2]);
- eth->dev = &pdev->dev;
eth->msg_enable = netif_msg_init(mtk_msg_level, MTK_DEFAULT_MSG_ENABLE);
INIT_WORK(ð->pending_work, mtk_pending_work);
@@ -1902,10 +1907,10 @@ static int mtk_remove(struct platform_device *pdev)
{
struct mtk_eth *eth = platform_get_drvdata(pdev);
- clk_disable_unprepare(eth->clk_ethif);
- clk_disable_unprepare(eth->clk_esw);
- clk_disable_unprepare(eth->clk_gp1);
- clk_disable_unprepare(eth->clk_gp2);
+ clk_disable_unprepare(eth->clks[MTK_CLK_GP1]);
+ clk_disable_unprepare(eth->clks[MTK_CLK_GP2]);
+ clk_disable_unprepare(eth->clks[MTK_CLK_ESW]);
+ clk_disable_unprepare(eth->clks[MTK_CLK_ETHIF]);
netif_napi_del(ð->tx_napi);
netif_napi_del(ð->rx_napi);
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
index f82e3ac..6e1ade7 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -290,6 +290,17 @@ enum mtk_tx_flags {
MTK_TX_FLAGS_PAGE0 = 0x02,
};
+/* This enum allows us to identify how the clock is defined on the array of the
+ * clock in the order
+ */
+enum mtk_clks_map {
+ MTK_CLK_ETHIF,
+ MTK_CLK_ESW,
+ MTK_CLK_GP1,
+ MTK_CLK_GP2,
+ MTK_CLK_MAX
+};
+
/* struct mtk_tx_buf - This struct holds the pointers to the memory pointed at
* by the TX descriptor s
* @skb: The SKB pointer of the packet being sent
@@ -370,10 +381,7 @@ struct mtk_rx_ring {
* @scratch_ring: Newer SoCs need memory for a second HW managed TX ring
* @phy_scratch_ring: physical address of scratch_ring
* @scratch_head: The scratch memory that scratch_ring points to.
- * @clk_ethif: The ethif clock
- * @clk_esw: The switch clock
- * @clk_gp1: The gmac1 clock
- * @clk_gp2: The gmac2 clock
+ * @clks: clock array for all clocks required
* @mii_bus: If there is a bus we need to create an instance for it
* @pending_work: The workqueue used to reset the dma ring
*/
@@ -400,10 +408,8 @@ struct mtk_eth {
struct mtk_tx_dma *scratch_ring;
dma_addr_t phy_scratch_ring;
void *scratch_head;
- struct clk *clk_ethif;
- struct clk *clk_esw;
- struct clk *clk_gp1;
- struct clk *clk_gp2;
+ struct clk *clks[MTK_CLK_MAX];
+
struct mii_bus *mii_bus;
struct work_struct pending_work;
};
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 1/9] net: ethernet: mediatek: fix fails from TX housekeeping due to incorrect port setup
From: sean.wang-NuS5LvNUpcJWk0Htik3J/w @ 2016-08-29 5:03 UTC (permalink / raw)
To: john-Pj+rj9U5foFAfugRpC6u6w, davem-fT/PcQaiUtIeIZ0/mPfg9Q
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
Sean Wang, linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1472447003-30726-1-git-send-email-sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
which net device the SKB is complete for depends on the forward port
on txd4 on the corresponding TX descriptor, but the information isn't
set up well in case of SKB fragments that would lead to watchdog timeout
from the upper layer, so fix it up.
Signed-off-by: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Acked-by: John Crispin <john-Pj+rj9U5foFAfugRpC6u6w@public.gmane.org>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 1801fd8..6e4a6ca 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -587,14 +587,15 @@ static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
dma_addr_t mapped_addr;
unsigned int nr_frags;
int i, n_desc = 1;
- u32 txd4 = 0;
+ u32 txd4 = 0, fport;
itxd = ring->next_free;
if (itxd == ring->last_free)
return -ENOMEM;
/* set the forward port */
- txd4 |= (mac->id + 1) << TX_DMA_FPORT_SHIFT;
+ fport = (mac->id + 1) << TX_DMA_FPORT_SHIFT;
+ txd4 |= fport;
tx_buf = mtk_desc_to_tx_buf(ring, itxd);
memset(tx_buf, 0, sizeof(*tx_buf));
@@ -652,7 +653,7 @@ static int mtk_tx_map(struct sk_buff *skb, struct net_device *dev,
WRITE_ONCE(txd->txd3, (TX_DMA_SWC |
TX_DMA_PLEN0(frag_map_size) |
last_frag * TX_DMA_LS0));
- WRITE_ONCE(txd->txd4, 0);
+ WRITE_ONCE(txd->txd4, fport);
tx_buf->skb = (struct sk_buff *)MTK_DMA_DUMMY_DESC;
tx_buf = mtk_desc_to_tx_buf(ring, txd);
--
1.9.1
^ permalink raw reply related
* [PATCH net v2 0/9] net: ethernet: mediatek: a couple of fixes
From: sean.wang-NuS5LvNUpcJWk0Htik3J/w @ 2016-08-29 5:03 UTC (permalink / raw)
To: john-Pj+rj9U5foFAfugRpC6u6w, davem-fT/PcQaiUtIeIZ0/mPfg9Q
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
Sean Wang, linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w
From: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
a couple of fixes come out from integrating with linux-4.8 rc1
they all are verified and workable on linux-4.8 rc1
changes since v1:
- usage of loops to work out if all required clock are ready instead
of tedious coding
- remove redundant pinctrl setup that is already done by core driver
thanks for careful and patient reviewing by Andrew Lunn
- splitting distinct changes into the separate patches
- change variable naming from err to ret for readable coding
Sean Wang (9):
net: ethernet: mediatek: fix fails from TX housekeeping due to
incorrect port setup
net: ethernet: mediatek: fix incorrect return value of devm_clk_get
with EPROBE_DEFER
net: ethernet: mediatek: fix API usage with skb_free_frag
net: ethernet: mediatek: remove redundant free_irq for
devm_request_irq allocated irq
net: ethernet: mediatek: fix logic unbalance between probe and remove
net: ethernet: mediatek: fix issue of driver removal with interface is
up
net: ethernet: mediatek: fix the missing of_node_put() after node is
used done inside mtk_mdio_init
net: ethernet: mediatek: use devm_mdiobus_alloc instead of
mdiobus_alloc inside mtk_mdio_init
net: ethernet: mediatek: fix error handling inside mtk_mdio_init
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 82 +++++++++++++++--------------
drivers/net/ethernet/mediatek/mtk_eth_soc.h | 22 +++++---
2 files changed, 56 insertions(+), 48 deletions(-)
--
1.9.1
^ permalink raw reply
* Re: [RESEND PATCH net 06/10] net: ethernet: mediatek: fix the loss
From: Sean Wang @ 2016-08-29 4:27 UTC (permalink / raw)
To: andrew; +Cc: john, davem, nbd, netdev, linux-mediatek, keyhaede
In-Reply-To: <20160826141759.GG25046@lunn.ch>
Date: Fri, 26 Aug 2016 16:17:59 +0200, Andrew Lunn wrote:
>> Hi Andrew,
>>
>> Here pinctrl is used to setup what function the group of the pins is
>> for.
>
>Agreed.
>
>> The group of the pins could be configured for the function provided
>> by the SoC, such as general purpose I/O or specific function such as
>> ethernet depending on what products or boards you design for various
>> customers or vendors. Thanks for device tree introducing, it is easy
>> to find what resources the board needs including the pins usage is
>> also defined here.
>
>All clear. However, if the ethernet driver has loaded, it means the
>device tree says the ethernet should be loaded, unless it happens to
>be on some discoverable bus. And so the device tree node for the
>ethernet should also contain the needed pinctrl properties. The core
>driver code should of seen these properties and already enabled the
>correct pinctrl state before the driver probes.
>
>This is how every other driver works. Like i said, i don't think i've
>seen any other driver do its own pinctrl. So i just need a simple
>description, what is different here, why does this driver need to do
>it, when no other does?
>
> Andrew
>
You are right
all that I need about pinctrl are all being done with core driver
as you said, so the patch I did seems the redundant work and i will remove
it from the patch set.
thanks for your patient and careful reviewing and that also helps me getting
familiar with based driver with pinctrl more :)
Sean
^ permalink raw reply
* [Patch] mtd: fix writing incorrect ECC parity data in OOB region.
From: mtk04561 @ 2016-08-29 3:58 UTC (permalink / raw)
To: boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
Cc: robh-DgEjT+Ai2ygdnm+yROfE0A,
daniel.thompson-QSEj5FYQhm4dnm+yROfE0A,
steven.liu-NuS5LvNUpcJWk0Htik3J/w,
linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
xiaolei.li-NuS5LvNUpcJWk0Htik3J/w,
computersforpeace-Re5JQEeQqe8AvxtiuMwx3w,
dwmw2-wEGCiKHe2LqWVfeAwA7xHQ, blogic-p3rKhJxN3npAfugRpC6u6w
Hi Boris,
I found a problem in current Mediatek upstream NAND driver (of nand/next
branch). When testing the upstream driver with UBIFS file system, it has
some chance to create incorrect ECC data in the second subpage, this
makes UBIFS failed to mount. Below patch fixes this problem. Please help
to review the patch and upstream. Thank you.
Best regards,
Roger
Description:
When mtk_ecc_encode() is writing the ECC parity data to the OOB region,
because each register is 4 bytes in length, but the len's unit is in
bytes, the operation in the for loop will cross the ECC's boundary.
And when mtk_nfc_do_write_page() comparing the sector number, because
the sector number field is at the 12th-bit position of NFI_BYTELEN
register, the masked register should be shifted 12 bits before being
compared. The result of this bug may cause the second subpage has
incomplete ECC parity bytes.
Test:
The patch passed the test of UBIFS file-system read/write on Mediatek's
RFB. The tested driver is checked-out from LEDE OpenWRT project's
upstream driver, which is pretty much same as nand/next branch upstream
driver(git clone https://git.lede-project.org/source.git).
Signed-off-by: RogerCC Lin <rogercc.lin-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
---
diff --git a/drivers/mtd/nand/mtk_ecc.c b/drivers/mtd/nand/mtk_ecc.c
index 25a4fbd..0a7ce8d 100644
--- a/drivers/mtd/nand/mtk_ecc.c
+++ b/drivers/mtd/nand/mtk_ecc.c
@@ -366,7 +366,8 @@ int mtk_ecc_encode(struct mtk_ecc *ecc, struct
mtk_ecc_config *config,
u8 *data, u32 bytes)
{
dma_addr_t addr;
- u32 *p, len, i;
+ u8 *p;
+ u32 len, i, val;
int ret = 0;
addr = dma_map_single(ecc->dev, data, bytes, DMA_TO_DEVICE);
@@ -395,8 +396,11 @@ int mtk_ecc_encode(struct mtk_ecc *ecc, struct
mtk_ecc_config *config,
p = (u32 *)(data + bytes);
/* write the parity bytes generated by the ECC back to the OOB
region */
- for (i = 0; i < len; i++)
- p[i] = readl(ecc->regs + ECC_ENCPAR(i));
+ for (i = 0; i < len; i++) {
+ if ((i % 4) == 0)
+ val = readl(ecc->regs + ECC_ENCPAR(i >> 2));
+ p[i] = *((u8 *)&val + (i % 4));
+ }
timeout:
dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE);
diff --git a/drivers/mtd/nand/mtk_nand.c b/drivers/mtd/nand/mtk_nand.c
index ddaa2ac..9c49121 100644
--- a/drivers/mtd/nand/mtk_nand.c
+++ b/drivers/mtd/nand/mtk_nand.c
@@ -699,8 +699,8 @@ static int mtk_nfc_do_write_page(struct mtd_info
*mtd, struct nand_chip *chip,
}
ret = readl_poll_timeout_atomic(nfc->regs + NFI_ADDRCNTR, reg,
- (reg & CNTR_MASK) >=
chip->ecc.steps,
- 10, MTK_TIMEOUT);
+ ((reg & CNTR_MASK) >> 12) >=
+ chip->ecc.steps, 10,
MTK_TIMEOUT);
if (ret)
dev_err(dev, "hwecc write timeout\n");
@@ -902,8 +902,8 @@ static int mtk_nfc_read_subpage(struct mtd_info
*mtd, struct nand_chip *chip,
dev_warn(nfc->dev, "read ahb/dma done timeout\n");
rc = readl_poll_timeout_atomic(nfc->regs + NFI_BYTELEN, reg,
- (reg & CNTR_MASK) >= sectors, 10,
- MTK_TIMEOUT);
+ ((reg & CNTR_MASK) >> 12) >=
sectors,
+ 10, MTK_TIMEOUT);
if (rc < 0) {
dev_err(nfc->dev, "subpage done timeout\n");
bitflips = -EIO;
^ permalink raw reply related
* [v17 2/2] drm/bridge: Add I2C based driver for ps8640 bridge
From: Jitao Shi @ 2016-08-27 6:44 UTC (permalink / raw)
To: David Airlie, Thierry Reding, Matthias Brugger
Cc: Mark Rutland, stonea168-9Onoh4P/yGk,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Andy Yan, Ajay Kumar,
Vincent Palatin, cawa.cheng-NuS5LvNUpcJWk0Htik3J/w,
bibby.hsieh-NuS5LvNUpcJWk0Htik3J/w, ck.hu-NuS5LvNUpcJWk0Htik3J/w,
Russell King, devicetree-u79uwXL29TY76Z2rM5mHXA, Jitao Shi,
Sascha Hauer, Pawel Moll, Ian Campbell, Inki Dae, Rob Herring,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
yingjoe.chen-NuS5LvNUpcJWk0Htik3J/w,
eddie.huang-NuS5LvNUpcJWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Rahul Sharma,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Philipp Zabel, Kumar Gala,
Sean Paul <sea>
In-Reply-To: <1472280263-18177-1-git-send-email-jitao.shi-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
This patch adds drm_bridge driver for parade DSI to eDP bridge chip.
Signed-off-by: Jitao Shi <jitao.shi-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Reviewed-by: Daniel Kurtz <djkurtz-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
---
Changes since v16:
- Disable ps8640 DSI MCS Function.
- Rename gpios name more clearly.
- Tune the ps8640 power on sequence.
Changes since v15:
- Drop drm_connector_(un)register calls from parade ps8640.
The main DRM driver mtk_drm_drv now calls
drm_connector_register_all() after drm_dev_register() in the
mtk_drm_bind() function. That function should iterate over all
connectors and call drm_connector_register() for each of them.
So, remove drm_connector_(un)register calls from parade ps8640.
Changes since v14:
- update copyright info.
- change bridge_to_ps8640 and connector_to_ps8640 to inline function.
- fix some coding style.
- use sizeof as array counter.
- use drm_get_edid when read edid.
- add mutex when firmware updating.
Changes since v13:
- add const on data, ps8640_write_bytes(struct i2c_client *client, const u8 *data, u16 data_len)
- fix PAGE2_SW_REST tyro.
- move the buf[3] init to entrance of the function.
Changes since v12:
- fix hw_chip_id build warning
Changes since v11:
- Remove depends on I2C, add DRM depends
- Reuse ps8640_write_bytes() in ps8640_write_byte()
- Use timer check for polling like the routines in <linux/iopoll.h>
- Fix no drm_connector_unregister/drm_connector_cleanup when ps8640_bridge_attach fail
- Check the ps8640 hardware id in ps8640_validate_firmware
- Remove fw_version check
- Move ps8640_validate_firmware before ps8640_enter_bl
- Add ddc_i2c unregister when probe fail and ps8640_remove
---
drivers/gpu/drm/bridge/Kconfig | 12 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/parade-ps8640.c | 1077 ++++++++++++++++++++++++++++++++
3 files changed, 1090 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/parade-ps8640.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index b590e67..c59d043 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -50,6 +50,18 @@ config DRM_PARADE_PS8622
---help---
Parade eDP-LVDS bridge chip driver.
+config DRM_PARADE_PS8640
+ tristate "Parade PS8640 MIPI DSI to eDP Converter"
+ depends on DRM
+ depends on OF
+ select DRM_KMS_HELPER
+ select DRM_MIPI_DSI
+ select DRM_PANEL
+ ---help---
+ Choose this option if you have PS8640 for display
+ The PS8640 is a high-performance and low-power
+ MIPI DSI to eDP converter
+
config DRM_SII902X
tristate "Silicon Image sii902x RGB/HDMI bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index efdb07e..3360537 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
+obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c
new file mode 100644
index 0000000..7d67431
--- /dev/null
+++ b/drivers/gpu/drm/bridge/parade-ps8640.c
@@ -0,0 +1,1077 @@
+/*
+ * Copyright (c) 2016 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/regulator/consumer.h>
+#include <asm/unaligned.h>
+#include <drm/drm_panel.h>
+
+#include <drmP.h>
+#include <drm_atomic_helper.h>
+#include <drm_crtc_helper.h>
+#include <drm_crtc.h>
+#include <drm_edid.h>
+#include <drm_mipi_dsi.h>
+
+#define PAGE1_VSTART 0x6b
+#define PAGE2_SPI_CFG3 0x82
+#define I2C_TO_SPI_RESET 0x20
+#define PAGE2_ROMADD_BYTE1 0x8e
+#define PAGE2_ROMADD_BYTE2 0x8f
+#define PAGE2_SWSPI_WDATA 0x90
+#define PAGE2_SWSPI_RDATA 0x91
+#define PAGE2_SWSPI_LEN 0x92
+#define PAGE2_SWSPI_CTL 0x93
+#define TRIGGER_NO_READBACK 0x05
+#define TRIGGER_READBACK 0x01
+#define PAGE2_SPI_STATUS 0x9e
+#define SPI_READY 0x0c
+#define PAGE2_GPIO_L 0xa6
+#define PAGE2_GPIO_H 0xa7
+#define PS_GPIO9 BIT(1)
+#define PAGE2_IROM_CTRL 0xb0
+#define IROM_ENABLE 0xc0
+#define IROM_DISABLE 0x80
+#define PAGE2_SW_RESET 0xbc
+#define SPI_SW_RESET BIT(7)
+#define MPU_SW_RESET BIT(6)
+#define PAGE2_ENCTLSPI_WR 0xda
+#define PAGE2_I2C_BYPASS 0xea
+#define I2C_BYPASS_EN 0xd0
+#define PAGE2_MCS_EN 0xf3
+#define MCS_EN BIT(0)
+#define PAGE3_SET_ADD 0xfe
+#define PAGE3_SET_VAL 0xff
+#define VDO_CTL_ADD 0x13
+#define VDO_DIS 0x18
+#define VDO_EN 0x1c
+#define PAGE4_REV_L 0xf0
+#define PAGE4_REV_H 0xf1
+#define PAGE4_CHIP_L 0xf2
+#define PAGE4_CHIP_H 0xf3
+
+/* Firmware */
+#define PS_FW_NAME "ps864x_fw.bin"
+
+#define FW_CHIP_ID_OFFSET 0
+#define FW_VERSION_OFFSET 2
+#define EDID_I2C_ADDR 0x50
+
+#define WRITE_STATUS_REG_CMD 0x01
+#define READ_STATUS_REG_CMD 0x05
+#define BUSY BIT(0)
+#define CLEAR_ALL_PROTECT 0x00
+#define BLK_PROTECT_BITS 0x0c
+#define STATUS_REG_PROTECT BIT(7)
+#define WRITE_ENABLE_CMD 0x06
+#define CHIP_ERASE_CMD 0xc7
+#define MAX_DEVS 0x8
+
+struct ps8640_info {
+ u8 family_id;
+ u8 variant_id;
+ u16 version;
+};
+
+struct ps8640 {
+ struct drm_connector connector;
+ struct drm_bridge bridge;
+ struct edid *edid;
+ struct mipi_dsi_device dsi;
+ struct i2c_client *page[MAX_DEVS];
+ struct i2c_client *ddc_i2c;
+ struct regulator_bulk_data supplies[2];
+ struct drm_panel *panel;
+ struct gpio_desc *gpio_reset;
+ struct gpio_desc *gpio_power_down;
+ struct gpio_desc *gpio_mode_sel;
+ bool enabled;
+
+ /* firmware file info */
+ struct ps8640_info info;
+ bool in_fw_update;
+ /* for firmware update protect */
+ struct mutex fw_mutex;
+};
+
+static const u8 enc_ctrl_code[6] = { 0xaa, 0x55, 0x50, 0x41, 0x52, 0x44 };
+static const u8 hw_chip_id[4] = { 0x00, 0x0a, 0x00, 0x30 };
+
+static inline struct ps8640 *bridge_to_ps8640(struct drm_bridge *e)
+{
+ return container_of(e, struct ps8640, bridge);
+}
+
+static inline struct ps8640 *connector_to_ps8640(struct drm_connector *e)
+{
+ return container_of(e, struct ps8640, connector);
+}
+
+static int ps8640_read(struct i2c_client *client, u8 reg, u8 *data,
+ u16 data_len)
+{
+ int ret;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = ®,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = data_len,
+ .buf = data,
+ }
+ };
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+
+ if (ret == 2)
+ return 0;
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+static int ps8640_write_bytes(struct i2c_client *client, const u8 *data,
+ u16 data_len)
+{
+ int ret;
+ struct i2c_msg msg;
+
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.len = data_len;
+ msg.buf = (u8 *)data;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret == 1)
+ return 0;
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+static int ps8640_write_byte(struct i2c_client *client, u8 reg, u8 data)
+{
+ u8 buf[] = { reg, data };
+
+ return ps8640_write_bytes(client, buf, sizeof(buf));
+}
+
+static void ps8640_get_mcu_fw_version(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[5];
+ u8 fw_ver[2];
+
+ ps8640_read(client, 0x4, fw_ver, sizeof(fw_ver));
+ ps_bridge->info.version = (fw_ver[0] << 8) | fw_ver[1];
+
+ DRM_INFO_ONCE("ps8640 rom fw version %d.%d\n", fw_ver[0], fw_ver[1]);
+}
+
+static int ps8640_bridge_unmute(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[3];
+ u8 vdo_ctrl_buf[3] = { PAGE3_SET_ADD, VDO_CTL_ADD, VDO_EN };
+
+ return ps8640_write_bytes(client, vdo_ctrl_buf, sizeof(vdo_ctrl_buf));
+}
+
+static int ps8640_bridge_mute(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[3];
+ u8 vdo_ctrl_buf[3] = { PAGE3_SET_ADD, VDO_CTL_ADD, VDO_DIS };
+
+ return ps8640_write_bytes(client, vdo_ctrl_buf, sizeof(vdo_ctrl_buf));
+}
+
+static void ps8640_pre_enable(struct drm_bridge *bridge)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ struct i2c_client *client = ps_bridge->page[2];
+ struct i2c_client *page1 = ps_bridge->page[1];
+ int err;
+ u8 set_vdo_done, mcs_en, vstart;
+ ktime_t timeout;
+
+ if (ps_bridge->in_fw_update)
+ return;
+
+ if (ps_bridge->enabled)
+ return;
+
+ err = drm_panel_prepare(ps_bridge->panel);
+ if (err < 0) {
+ DRM_ERROR("failed to prepare panel: %d\n", err);
+ return;
+ }
+
+ err = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+ if (err < 0) {
+ DRM_ERROR("cannot enable regulators %d\n", err);
+ goto err_panel_unprepare;
+ }
+
+ gpiod_set_value(ps_bridge->gpio_power_down, 1);
+ gpiod_set_value(ps_bridge->gpio_reset, 0);
+ usleep_range(2000, 2500);
+ gpiod_set_value(ps_bridge->gpio_reset, 1);
+
+ /*
+ * Wait for the ps8640 embed mcu ready
+ * First wait 200ms and then check the mcu ready flag every 20ms
+ */
+ msleep(200);
+
+ timeout = ktime_add_ms(ktime_get(), 200);
+ for (;;) {
+ err = ps8640_read(client, PAGE2_GPIO_H, &set_vdo_done, 1);
+ if (err < 0) {
+ DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", err);
+ goto err_regulators_disable;
+ }
+ if ((set_vdo_done & PS_GPIO9) == PS_GPIO9)
+ break;
+ if (ktime_compare(ktime_get(), timeout) > 0)
+ break;
+ msleep(20);
+ }
+
+ msleep(50);
+
+ ps8640_read(page1, PAGE1_VSTART, &vstart, 1);
+ DRM_INFO("PS8640 PAGE1.0x6B = 0x%x\n", vstart);
+
+ /**
+ * The Manufacturer Command Set (MCS) is a device dependent interface
+ * intended for factory programming of the display module default
+ * parameters. Once the display module is configured, the MCS shall be
+ * disabled by the manufacturer. Once disabled, all MCS commands are
+ * ignored by the display interface.
+ */
+ ps8640_read(client, PAGE2_MCS_EN, &mcs_en, 1);
+ ps8640_write_byte(client, PAGE2_MCS_EN, mcs_en & ~MCS_EN);
+
+ if (ps_bridge->info.version == 0)
+ ps8640_get_mcu_fw_version(ps_bridge);
+
+ err = ps8640_bridge_unmute(ps_bridge);
+ if (err)
+ DRM_ERROR("failed to enable unmutevideo: %d\n", err);
+ /* Switch access edp panel's edid through i2c */
+ ps8640_write_byte(client, PAGE2_I2C_BYPASS, I2C_BYPASS_EN);
+ ps_bridge->enabled = true;
+
+ return;
+
+err_regulators_disable:
+ regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+err_panel_unprepare:
+ drm_panel_unprepare(ps_bridge->panel);
+}
+
+static void ps8640_enable(struct drm_bridge *bridge)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ int err;
+
+ err = drm_panel_enable(ps_bridge->panel);
+ if (err < 0)
+ DRM_ERROR("failed to enable panel: %d\n", err);
+}
+
+static void ps8640_disable(struct drm_bridge *bridge)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ int err;
+
+ err = drm_panel_disable(ps_bridge->panel);
+ if (err < 0)
+ DRM_ERROR("failed to disable panel: %d\n", err);
+}
+
+static void ps8640_post_disable(struct drm_bridge *bridge)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ int err;
+
+ if (ps_bridge->in_fw_update)
+ return;
+
+ if (!ps_bridge->enabled)
+ return;
+
+ ps_bridge->enabled = false;
+
+ err = ps8640_bridge_mute(ps_bridge);
+ if (err < 0)
+ DRM_ERROR("failed to unmutevideo: %d\n", err);
+
+ gpiod_set_value(ps_bridge->gpio_reset, 0);
+ gpiod_set_value(ps_bridge->gpio_power_down, 0);
+ err = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+ if (err < 0)
+ DRM_ERROR("cannot disable regulators %d\n", err);
+
+ err = drm_panel_unprepare(ps_bridge->panel);
+ if (err)
+ DRM_ERROR("failed to unprepare panel: %d\n", err);
+}
+
+static int ps8640_get_modes(struct drm_connector *connector)
+{
+ struct ps8640 *ps_bridge = connector_to_ps8640(connector);
+ struct edid *edid;
+ int num_modes = 0;
+ bool power_off;
+
+ if (ps_bridge->edid)
+ return drm_add_edid_modes(connector, ps_bridge->edid);
+
+ power_off = !ps_bridge->enabled;
+ ps8640_pre_enable(&ps_bridge->bridge);
+
+ edid = drm_get_edid(connector, ps_bridge->ddc_i2c->adapter);
+ if (!edid)
+ goto out;
+
+ ps_bridge->edid = edid;
+ drm_mode_connector_update_edid_property(connector, ps_bridge->edid);
+ num_modes = drm_add_edid_modes(connector, ps_bridge->edid);
+
+out:
+ if (power_off)
+ ps8640_post_disable(&ps_bridge->bridge);
+
+ return num_modes;
+}
+
+static struct drm_encoder *ps8640_best_encoder(struct drm_connector *connector)
+{
+ struct ps8640 *ps_bridge = connector_to_ps8640(connector);
+
+ return ps_bridge->bridge.encoder;
+}
+
+static const struct drm_connector_helper_funcs ps8640_connector_helper_funcs = {
+ .get_modes = ps8640_get_modes,
+ .best_encoder = ps8640_best_encoder,
+};
+
+static enum drm_connector_status ps8640_detect(struct drm_connector *connector,
+ bool force)
+{
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs ps8640_connector_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = ps8640_detect,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+int ps8640_bridge_attach(struct drm_bridge *bridge)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ struct device *dev = &ps_bridge->page[0]->dev;
+ struct device_node *port, *in_ep;
+ struct device_node *dsi_node = NULL;
+ struct mipi_dsi_host *host = NULL;
+ int ret;
+
+ ret = drm_connector_init(bridge->dev, &ps_bridge->connector,
+ &ps8640_connector_funcs,
+ DRM_MODE_CONNECTOR_eDP);
+
+ if (ret) {
+ DRM_ERROR("Failed to initialize connector with drm: %d\n", ret);
+ return ret;
+ }
+
+ drm_connector_helper_add(&ps_bridge->connector,
+ &ps8640_connector_helper_funcs);
+
+ ps_bridge->connector.dpms = DRM_MODE_DPMS_ON;
+ drm_mode_connector_attach_encoder(&ps_bridge->connector,
+ bridge->encoder);
+
+ if (ps_bridge->panel)
+ drm_panel_attach(ps_bridge->panel, &ps_bridge->connector);
+
+ /* port@0 is ps8640 dsi input port */
+ port = of_graph_get_port_by_id(dev->of_node, 0);
+ if (port) {
+ in_ep = of_get_child_by_name(port, "endpoint");
+ of_node_put(port);
+ if (in_ep) {
+ dsi_node = of_graph_get_remote_port_parent(in_ep);
+ of_node_put(in_ep);
+ }
+ }
+ if (dsi_node) {
+ host = of_find_mipi_dsi_host_by_node(dsi_node);
+ of_node_put(dsi_node);
+ if (!host) {
+ ret = -ENODEV;
+ goto err;
+ }
+ }
+
+ ps_bridge->dsi.host = host;
+ ps_bridge->dsi.mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ ps_bridge->dsi.format = MIPI_DSI_FMT_RGB888;
+ ps_bridge->dsi.lanes = 4;
+ ret = mipi_dsi_attach(&ps_bridge->dsi);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ if (ps_bridge->panel)
+ drm_panel_detach(ps_bridge->panel);
+ drm_connector_cleanup(&ps_bridge->connector);
+ return ret;
+}
+
+static const struct drm_bridge_funcs ps8640_bridge_funcs = {
+ .attach = ps8640_bridge_attach,
+ .disable = ps8640_disable,
+ .post_disable = ps8640_post_disable,
+ .pre_enable = ps8640_pre_enable,
+ .enable = ps8640_enable,
+};
+
+/* Firmware Version is returned as Major.Minor */
+static ssize_t ps8640_fw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ps8640 *ps_bridge = dev_get_drvdata(dev);
+ struct ps8640_info *info = &ps_bridge->info;
+
+ return scnprintf(buf, PAGE_SIZE, "%u.%u\n", info->version >> 8,
+ info->version & 0xff);
+}
+
+/* Hardware Version is returned as FamilyID.VariantID */
+static ssize_t ps8640_hw_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ps8640 *ps_bridge = dev_get_drvdata(dev);
+ struct ps8640_info *info = &ps_bridge->info;
+
+ return scnprintf(buf, PAGE_SIZE, "ps%u.%u\n", info->family_id,
+ info->variant_id);
+}
+
+static int ps8640_spi_send_cmd(struct ps8640 *ps_bridge, u8 *cmd, u8 cmd_len)
+{
+ struct i2c_client *client = ps_bridge->page[2];
+ u8 i, buf[3] = { PAGE2_SWSPI_LEN, cmd_len - 1, TRIGGER_NO_READBACK };
+ int ret;
+
+ ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_ENABLE);
+ if (ret)
+ goto err;
+
+ /* write command in write port */
+ for (i = 0; i < cmd_len; i++) {
+ ret = ps8640_write_byte(client, PAGE2_SWSPI_WDATA, cmd[i]);
+ if (ret)
+ goto err_irom_disable;
+ }
+
+ ret = ps8640_write_bytes(client, buf, sizeof(buf));
+ if (ret)
+ goto err_irom_disable;
+
+ ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
+ if (ret)
+ goto err;
+
+ return 0;
+err_irom_disable:
+ ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
+err:
+ dev_err(&client->dev, "send command err: %d\n", ret);
+ return ret;
+}
+
+static int ps8640_wait_spi_ready(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[2];
+ u8 spi_rdy_st;
+ ktime_t timeout;
+
+ timeout = ktime_add_ms(ktime_get(), 200);
+ for (;;) {
+ ps8640_read(client, PAGE2_SPI_STATUS, &spi_rdy_st, 1);
+ if ((spi_rdy_st & SPI_READY) != SPI_READY)
+ break;
+
+ if (ktime_compare(ktime_get(), timeout) > 0) {
+ dev_err(&client->dev, "wait spi ready timeout\n");
+ return -EBUSY;
+ }
+
+ msleep(20);
+ }
+
+ return 0;
+}
+
+static int ps8640_wait_spi_nobusy(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[2];
+ u8 spi_status, buf[3] = { PAGE2_SWSPI_LEN, 0, TRIGGER_READBACK };
+ int ret;
+ ktime_t timeout;
+
+ timeout = ktime_add_ms(ktime_get(), 500);
+ for (;;) {
+ /* 0x05 RDSR; Read-Status-Register */
+ ret = ps8640_write_byte(client, PAGE2_SWSPI_WDATA,
+ READ_STATUS_REG_CMD);
+ if (ret)
+ goto err_send_cmd_exit;
+
+ ret = ps8640_write_bytes(client, buf, 3);
+ if (ret)
+ goto err_send_cmd_exit;
+
+ /* delay for cmd send */
+ usleep_range(300, 500);
+ /* wait for SPI ROM until not busy */
+ ret = ps8640_read(client, PAGE2_SWSPI_RDATA, &spi_status, 1);
+ if (ret)
+ goto err_send_cmd_exit;
+
+ if (!(spi_status & BUSY))
+ break;
+
+ if (ktime_compare(ktime_get(), timeout) > 0) {
+ dev_err(&client->dev, "wait spi no busy timeout: %d\n",
+ ret);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+
+err_send_cmd_exit:
+ dev_err(&client->dev, "send command err: %d\n", ret);
+ return ret;
+}
+
+static int ps8640_wait_rom_idle(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[0];
+ int ret;
+
+ ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_ENABLE);
+ if (ret)
+ goto exit;
+
+ ret = ps8640_wait_spi_ready(ps_bridge);
+ if (ret)
+ goto err_spi;
+
+ ret = ps8640_wait_spi_nobusy(ps_bridge);
+ if (ret)
+ goto err_spi;
+
+ ret = ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
+ if (ret)
+ goto exit;
+
+ return 0;
+
+err_spi:
+ ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
+exit:
+ dev_err(&client->dev, "wait ps8640 rom idle fail: %d\n", ret);
+
+ return ret;
+}
+
+static int ps8640_spi_dl_mode(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[2];
+ int ret;
+
+ /* switch ps8640 mode to spi dl mode */
+ if (ps_bridge->gpio_mode_sel)
+ gpiod_set_value(ps_bridge->gpio_mode_sel, 0);
+
+ /* reset spi interface */
+ ret = ps8640_write_byte(client, PAGE2_SW_RESET,
+ SPI_SW_RESET | MPU_SW_RESET);
+ if (ret)
+ goto exit;
+
+ ret = ps8640_write_byte(client, PAGE2_SW_RESET, MPU_SW_RESET);
+ if (ret)
+ goto exit;
+
+ return 0;
+
+exit:
+ dev_err(&client->dev, "fail reset spi interface: %d\n", ret);
+
+ return ret;
+}
+
+static int ps8640_rom_prepare(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[2];
+ struct device *dev = &client->dev;
+ u8 i, cmd[2];
+ int ret;
+
+ cmd[0] = WRITE_ENABLE_CMD;
+ ret = ps8640_spi_send_cmd(ps_bridge, cmd, 1);
+ if (ret) {
+ dev_err(dev, "failed enable-write-status-register: %d\n", ret);
+ return ret;
+ }
+
+ cmd[0] = WRITE_STATUS_REG_CMD;
+ cmd[1] = CLEAR_ALL_PROTECT;
+ ret = ps8640_spi_send_cmd(ps_bridge, cmd, 2);
+ if (ret) {
+ dev_err(dev, "fail disable all protection: %d\n", ret);
+ return ret;
+ }
+
+ /* wait for SPI module ready */
+ ret = ps8640_wait_rom_idle(ps_bridge);
+ if (ret) {
+ dev_err(dev, "fail wait rom idle: %d\n", ret);
+ return ret;
+ }
+
+ ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_ENABLE);
+ for (i = 0; i < ARRAY_SIZE(enc_ctrl_code); i++)
+ ps8640_write_byte(client, PAGE2_ENCTLSPI_WR, enc_ctrl_code[i]);
+ ps8640_write_byte(client, PAGE2_IROM_CTRL, IROM_DISABLE);
+
+ /* Enable-Write-Status-Register */
+ cmd[0] = WRITE_ENABLE_CMD;
+ ret = ps8640_spi_send_cmd(ps_bridge, cmd, 1);
+ if (ret) {
+ dev_err(dev, "fail enable-write-status-register: %d\n", ret);
+ return ret;
+ }
+
+ /* chip erase command */
+ cmd[0] = CHIP_ERASE_CMD;
+ ret = ps8640_spi_send_cmd(ps_bridge, cmd, 1);
+ if (ret) {
+ dev_err(dev, "fail disable all protection: %d\n", ret);
+ return ret;
+ }
+
+ ret = ps8640_wait_rom_idle(ps_bridge);
+ if (ret) {
+ dev_err(dev, "fail wait rom idle: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ps8640_check_chip_id(struct ps8640 *ps_bridge)
+{
+ struct i2c_client *client = ps_bridge->page[4];
+ u8 buf[4];
+
+ ps8640_read(client, PAGE4_REV_L, buf, 4);
+ return memcmp(buf, hw_chip_id, sizeof(buf));
+}
+
+static int ps8640_validate_firmware(struct ps8640 *ps_bridge,
+ const struct firmware *fw)
+{
+ struct i2c_client *client = ps_bridge->page[0];
+ u16 fw_chip_id;
+
+ /*
+ * Get the chip_id from the firmware. Make sure that it is the
+ * right controller to do the firmware and config update.
+ */
+ fw_chip_id = get_unaligned_le16(fw->data + FW_CHIP_ID_OFFSET);
+
+ if (fw_chip_id != 0x8640 && ps8640_check_chip_id(ps_bridge) == 0) {
+ dev_err(&client->dev,
+ "chip id mismatch: fw 0x%x vs. chip 0x8640\n",
+ fw_chip_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ps8640_write_rom(struct ps8640 *ps_bridge, const struct firmware *fw)
+{
+ struct i2c_client *client = ps_bridge->page[0];
+ struct device *dev = &client->dev;
+ struct i2c_client *client2 = ps_bridge->page[2];
+ struct i2c_client *client7 = ps_bridge->page[7];
+ size_t pos, cpy_len;
+ u8 buf[257];
+ int ret;
+
+ ps8640_write_byte(client2, PAGE2_SPI_CFG3, I2C_TO_SPI_RESET);
+ msleep(100);
+ ps8640_write_byte(client2, PAGE2_SPI_CFG3, 0x00);
+
+ for (pos = 0; pos < fw->size; pos += cpy_len) {
+ buf[0] = PAGE2_ROMADD_BYTE1;
+ buf[1] = pos >> 8;
+ buf[2] = pos >> 16;
+ ret = ps8640_write_bytes(client2, buf, 3);
+ if (ret)
+ goto error;
+ cpy_len = fw->size >= 256 + pos ? 256 : fw->size - pos;
+ buf[0] = 0;
+ memcpy(buf + 1, fw->data + pos, cpy_len);
+ ret = ps8640_write_bytes(client7, buf, cpy_len + 1);
+ if (ret)
+ goto error;
+
+ dev_dbg(dev, "fw update completed %zu / %zu bytes\n", pos,
+ fw->size);
+ }
+ return 0;
+
+error:
+ dev_err(dev, "failed write external flash, %d\n", ret);
+ return ret;
+}
+
+static int ps8640_spi_normal_mode(struct ps8640 *ps_bridge)
+{
+ u8 cmd[2];
+ struct i2c_client *client = ps_bridge->page[2];
+
+ /* Enable-Write-Status-Register */
+ cmd[0] = WRITE_ENABLE_CMD;
+ ps8640_spi_send_cmd(ps_bridge, cmd, 1);
+
+ /* protect BPL/BP0/BP1 */
+ cmd[0] = WRITE_STATUS_REG_CMD;
+ cmd[1] = BLK_PROTECT_BITS | STATUS_REG_PROTECT;
+ ps8640_spi_send_cmd(ps_bridge, cmd, 2);
+
+ /* wait for SPI rom ready */
+ ps8640_wait_rom_idle(ps_bridge);
+
+ /* disable PS8640 mapping function */
+ ps8640_write_byte(client, PAGE2_ENCTLSPI_WR, 0x00);
+
+ if (ps_bridge->gpio_mode_sel)
+ gpiod_set_value(ps_bridge->gpio_mode_sel, 1);
+ return 0;
+}
+
+static int ps8640_enter_bl(struct ps8640 *ps_bridge)
+{
+ ps_bridge->in_fw_update = true;
+ return ps8640_spi_dl_mode(ps_bridge);
+}
+
+static void ps8640_exit_bl(struct ps8640 *ps_bridge, const struct firmware *fw)
+{
+ ps8640_spi_normal_mode(ps_bridge);
+ ps_bridge->in_fw_update = false;
+}
+
+static int ps8640_load_fw(struct ps8640 *ps_bridge, const struct firmware *fw)
+{
+ struct i2c_client *client = ps_bridge->page[0];
+ struct device *dev = &client->dev;
+ int ret;
+ bool ps8640_status_backup = ps_bridge->enabled;
+
+ ret = ps8640_validate_firmware(ps_bridge, fw);
+ if (ret)
+ return ret;
+
+ mutex_lock(&ps_bridge->fw_mutex);
+ if (!ps_bridge->in_fw_update) {
+ if (!ps8640_status_backup)
+ ps8640_pre_enable(&ps_bridge->bridge);
+
+ ret = ps8640_enter_bl(ps_bridge);
+ if (ret)
+ goto exit;
+ }
+
+ ret = ps8640_rom_prepare(ps_bridge);
+ if (ret)
+ goto exit;
+
+ ret = ps8640_write_rom(ps_bridge, fw);
+
+exit:
+ if (ret)
+ dev_err(dev, "Failed to load firmware, %d\n", ret);
+
+ ps8640_exit_bl(ps_bridge, fw);
+ if (!ps8640_status_backup)
+ ps8640_post_disable(&ps_bridge->bridge);
+ mutex_unlock(&ps_bridge->fw_mutex);
+ return ret;
+}
+
+static ssize_t ps8640_update_fw_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ps8640 *ps_bridge = i2c_get_clientdata(client);
+ const struct firmware *fw;
+ int error;
+
+ error = request_firmware(&fw, PS_FW_NAME, dev);
+ if (error) {
+ dev_err(dev, "Unable to open firmware %s: %d\n",
+ PS_FW_NAME, error);
+ return error;
+ }
+
+ error = ps8640_load_fw(ps_bridge, fw);
+ if (error)
+ dev_err(dev, "The firmware update failed(%d)\n", error);
+ else
+ dev_info(dev, "The firmware update succeeded\n");
+
+ release_firmware(fw);
+ return error ? error : count;
+}
+
+static DEVICE_ATTR(fw_version, S_IRUGO, ps8640_fw_version_show, NULL);
+static DEVICE_ATTR(hw_version, S_IRUGO, ps8640_hw_version_show, NULL);
+static DEVICE_ATTR(update_fw, S_IWUSR, NULL, ps8640_update_fw_store);
+
+static struct attribute *ps8640_attrs[] = {
+ &dev_attr_fw_version.attr,
+ &dev_attr_hw_version.attr,
+ &dev_attr_update_fw.attr,
+ NULL
+};
+
+static const struct attribute_group ps8640_attr_group = {
+ .attrs = ps8640_attrs,
+};
+
+static void ps8640_remove_sysfs_group(void *data)
+{
+ struct ps8640 *ps_bridge = data;
+
+ sysfs_remove_group(&ps_bridge->page[0]->dev.kobj, &ps8640_attr_group);
+}
+
+static int ps8640_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct ps8640 *ps_bridge;
+ struct device_node *np = dev->of_node;
+ struct device_node *port, *out_ep;
+ struct device_node *panel_node = NULL;
+ int ret;
+ u32 i;
+
+ ps_bridge = devm_kzalloc(dev, sizeof(*ps_bridge), GFP_KERNEL);
+ if (!ps_bridge)
+ return -ENOMEM;
+
+ /* port@1 is ps8640 output port */
+ port = of_graph_get_port_by_id(np, 1);
+ if (port) {
+ out_ep = of_get_child_by_name(port, "endpoint");
+ of_node_put(port);
+ if (out_ep) {
+ panel_node = of_graph_get_remote_port_parent(out_ep);
+ of_node_put(out_ep);
+ }
+ }
+ if (panel_node) {
+ ps_bridge->panel = of_drm_find_panel(panel_node);
+ of_node_put(panel_node);
+ if (!ps_bridge->panel)
+ return -EPROBE_DEFER;
+ }
+
+ mutex_init(&ps_bridge->fw_mutex);
+ ps_bridge->supplies[0].supply = "vdd33";
+ ps_bridge->supplies[1].supply = "vdd12";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+ if (ret) {
+ dev_info(dev, "failed to get regulators: %d\n", ret);
+ return ret;
+ }
+
+ ps_bridge->gpio_mode_sel = devm_gpiod_get_optional(&client->dev,
+ "mode-sel",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ps_bridge->gpio_mode_sel)) {
+ ret = PTR_ERR(ps_bridge->gpio_mode_sel);
+ dev_err(dev, "cannot get mode-sel %d\n", ret);
+ return ret;
+ }
+
+ ps_bridge->gpio_power_down = devm_gpiod_get(&client->dev, "sleep",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ps_bridge->gpio_power_down)) {
+ ret = PTR_ERR(ps_bridge->gpio_power_down);
+ dev_err(dev, "cannot get sleep: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Request the reset pin low to avoid the bridge being
+ * initialized prematurely
+ */
+ ps_bridge->gpio_reset = devm_gpiod_get(&client->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ps_bridge->gpio_reset)) {
+ ret = PTR_ERR(ps_bridge->gpio_reset);
+ dev_err(dev, "cannot get reset: %d\n", ret);
+ return ret;
+ }
+
+ ps_bridge->bridge.funcs = &ps8640_bridge_funcs;
+ ps_bridge->bridge.of_node = dev->of_node;
+
+ ps_bridge->page[0] = client;
+ ps_bridge->ddc_i2c = i2c_new_dummy(client->adapter, EDID_I2C_ADDR);
+ if (!ps_bridge->ddc_i2c) {
+ dev_err(dev, "failed ddc_i2c dummy device, address%02x\n",
+ EDID_I2C_ADDR);
+ return -EBUSY;
+ }
+ /*
+ * ps8640 uses multiple addresses, use dummy devices for them
+ * page[0]: for DP control
+ * page[1]: for VIDEO Bridge
+ * page[2]: for control top
+ * page[3]: for DSI Link Control1
+ * page[4]: for MIPI Phy
+ * page[5]: for VPLL
+ * page[6]: for DSI Link Control2
+ * page[7]: for spi rom mapping
+ */
+ for (i = 1; i < MAX_DEVS; i++) {
+ ps_bridge->page[i] = i2c_new_dummy(client->adapter,
+ client->addr + i);
+ if (!ps_bridge->page[i]) {
+ dev_err(dev, "failed i2c dummy device, address%02x\n",
+ client->addr + i);
+ ret = -EBUSY;
+ goto exit_dummy;
+ }
+ }
+ i2c_set_clientdata(client, ps_bridge);
+
+ ret = sysfs_create_group(&client->dev.kobj, &ps8640_attr_group);
+ if (ret) {
+ dev_err(dev, "failed to create sysfs entries: %d\n", ret);
+ goto exit_dummy;
+ }
+
+ ret = devm_add_action(dev, ps8640_remove_sysfs_group, ps_bridge);
+ if (ret) {
+ dev_err(dev, "failed to add sysfs cleanup action: %d\n", ret);
+ goto exit_remove_sysfs;
+ }
+
+ ret = drm_bridge_add(&ps_bridge->bridge);
+ if (ret) {
+ dev_err(dev, "Failed to add bridge: %d\n", ret);
+ goto exit_remove_sysfs;
+ }
+ return 0;
+
+exit_remove_sysfs:
+ sysfs_remove_group(&ps_bridge->page[0]->dev.kobj, &ps8640_attr_group);
+exit_dummy:
+ while (--i)
+ i2c_unregister_device(ps_bridge->page[i]);
+ i2c_unregister_device(ps_bridge->ddc_i2c);
+ return ret;
+}
+
+static int ps8640_remove(struct i2c_client *client)
+{
+ struct ps8640 *ps_bridge = i2c_get_clientdata(client);
+ int i = MAX_DEVS;
+
+ drm_bridge_remove(&ps_bridge->bridge);
+ sysfs_remove_group(&ps_bridge->page[0]->dev.kobj, &ps8640_attr_group);
+ while (--i)
+ i2c_unregister_device(ps_bridge->page[i]);
+
+ i2c_unregister_device(ps_bridge->ddc_i2c);
+ return 0;
+}
+
+static const struct i2c_device_id ps8640_i2c_table[] = {
+ { "parade,ps8640", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, ps8640_i2c_table);
+
+static const struct of_device_id ps8640_match[] = {
+ { .compatible = "parade,ps8640" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ps8640_match);
+
+static struct i2c_driver ps8640_driver = {
+ .id_table = ps8640_i2c_table,
+ .probe = ps8640_probe,
+ .remove = ps8640_remove,
+ .driver = {
+ .name = "parade,ps8640",
+ .of_match_table = ps8640_match,
+ },
+};
+module_i2c_driver(ps8640_driver);
+
+MODULE_AUTHOR("Jitao Shi <jitao.shi-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>");
+MODULE_AUTHOR("CK Hu <ck.hu-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>");
+MODULE_DESCRIPTION("PARADE ps8640 DSI-eDP converter driver");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
^ permalink raw reply related
* [v17 1/2] Documentation: bridge: Add documentation for ps8640 DT properties
From: Jitao Shi @ 2016-08-27 6:44 UTC (permalink / raw)
To: David Airlie, Thierry Reding, Matthias Brugger
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Jitao Shi, Ajay Kumar, Inki Dae, Rahul Sharma, Sean Paul,
Vincent Palatin, Andy Yan, Philipp Zabel, Russell King,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w
Add documentation for DT properties supported by
ps8640 DSI-eDP converter.
Signed-off-by: Jitao Shi <jitao.shi-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Reviewed-by: Philipp Zabel <p.zabel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
---
Changes since v16:
- No change.
Changes since v15:
- No change.
Changes since v14:
- change mode-sel-gpios as optional.
---
.../devicetree/bindings/display/bridge/ps8640.txt | 44 ++++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/bridge/ps8640.txt
diff --git a/Documentation/devicetree/bindings/display/bridge/ps8640.txt b/Documentation/devicetree/bindings/display/bridge/ps8640.txt
new file mode 100644
index 0000000..7b13f92
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/ps8640.txt
@@ -0,0 +1,44 @@
+ps8640-bridge bindings
+
+Required properties:
+ - compatible: "parade,ps8640"
+ - reg: first page address of the bridge.
+ - sleep-gpios: OF device-tree gpio specification for PD pin.
+ - reset-gpios: OF device-tree gpio specification for reset pin.
+ - vdd12-supply: OF device-tree regulator specification for 1.2V power.
+ - vdd33-supply: OF device-tree regulator specification for 3.3V power.
+ - ports: The device node can contain video interface port nodes per
+ the video-interfaces bind[1]. For port@0,set the reg = <0> as
+ ps8640 dsi in and port@1,set the reg = <1> as ps8640 eDP out.
+
+Optional properties:
+ - mode-sel-gpios: OF device-tree gpio specification for mode-sel pin.
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+ edp-bridge@18 {
+ compatible = "parade,ps8640";
+ reg = <0x18>;
+ sleep-gpios = <&pio 116 GPIO_ACTIVE_LOW>;
+ reset-gpios = <&pio 115 GPIO_ACTIVE_LOW>;
+ mode-sel-gpios = <&pio 92 GPIO_ACTIVE_HIGH>;
+ vdd12-supply = <&ps8640_fixed_1v2>;
+ vdd33-supply = <&mt6397_vgp2_reg>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ ps8640_in: endpoint {
+ remote-endpoint = <&dsi0_out>;
+ };
+ };
+ port@1 {
+ reg = <1>;
+ ps8640_out: endpoint {
+ remote-endpoint = <&panel_in>;
+ };
+ };
+ };
+ };
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* Re: [PATCH v12 2/4] reset: mediatek: Add MT2701 reset driver
From: James Liao @ 2016-08-27 4:21 UTC (permalink / raw)
To: Stephen Boyd
Cc: Rob Herring, Philipp Zabel, Arnd Bergmann,
devicetree-u79uwXL29TY76Z2rM5mHXA, Erin Lo,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-clk-u79uwXL29TY76Z2rM5mHXA,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Shunli Wang,
Sascha Hauer, Matthias Brugger, Mike Turquette,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20160824175049.GC19826-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
On Wed, 2016-08-24 at 10:50 -0700, Stephen Boyd wrote:
> On 08/22, Erin Lo wrote:
> > diff --git a/drivers/clk/mediatek/clk-mt2701-hif.c b/drivers/clk/mediatek/clk-mt2701-hif.c
> > index 18b4ab5..702fd74 100644
> > --- a/drivers/clk/mediatek/clk-mt2701-hif.c
> > +++ b/drivers/clk/mediatek/clk-mt2701-hif.c
> > @@ -52,11 +52,15 @@ static int mtk_hifsys_init(struct device_node *node)
> > clk_data);
> >
> > r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > - if (r)
> > + if (r) {
> > pr_err("%s(): could not register clock provider: %d\n",
> > __func__, r);
> > + return r;
> > + }
> > +
> > + mtk_register_reset_controller(node, 1, 0x34);
>
> The cleanup here isn't great. mtk_register_reset_controller()
> should really return an error so that we can properly cleanup if
> needed. Fixing that in a later patch would be a good idea.
Hi Stephen,
I think so. This function returns void because it was invoked in
CLK_OF_DECLARE() in previous SoC's drivers. I'll investigate how to make
it return an error code without breaking backward compatibility.
Best regards,
James
^ permalink raw reply
* Re: [PATCH v12 1/4] clk: mediatek: Add MT2701 clock support
From: James Liao @ 2016-08-27 4:16 UTC (permalink / raw)
To: Stephen Boyd
Cc: Erin Lo, Matthias Brugger, Mike Turquette, Rob Herring,
Arnd Bergmann, Sascha Hauer, Daniel Kurtz, Philipp Zabel,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-clk-u79uwXL29TY76Z2rM5mHXA,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w, Shunli Wang
In-Reply-To: <20160824174917.GB19826-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
Hi Stephen,
On Wed, 2016-08-24 at 10:49 -0700, Stephen Boyd wrote:
> On 08/22, Erin Lo wrote:
> > +
> > +static void __init mtk_infrasys_init_early(struct device_node *node)
> > +{
> > + int r, i;
> > +
> > + if (!infra_clk_data) {
> > + infra_clk_data = mtk_alloc_clk_data(CLK_INFRA_NR);
> > +
> > + for (i = 0; i < CLK_INFRA_NR; i++)
> > + infra_clk_data->clks[i] = ERR_PTR(-EPROBE_DEFER);
> > + }
> > +
> > + mtk_clk_register_factors(infra_fixed_divs, ARRAY_SIZE(infra_fixed_divs),
> > + infra_clk_data);
> > +
> > + r = of_clk_add_provider(node, of_clk_src_onecell_get, infra_clk_data);
> > + if (r)
> > + pr_err("%s(): could not register clock provider: %d\n",
> > + __func__, r);
> > +}
> > +CLK_OF_DECLARE(mtk_infra, "mediatek,mt2701-infracfg", mtk_infrasys_init_early);
>
> This should use CLK_OF_DECLARE_DRIVER? Has this been tested on
> latest clk-next? Some recent patches make it so that
> CLK_OF_DECLARE() prevents platform devices from being created for
> the associated DT nodes that match during of_clk_init().
Oops, you are right. Clocks in infra_clks are gone on clk-next, but they
are good on v4.8-rc1.
I register clk13m in infra_fixed_divs through CLK_OF_DECLARE() so that
it can be registered as early as possible because it will be referred by
the timer driver. Is there a formal way to separate clock registrations
on the same clock provider? Or should I move infra_clks registration
into CLK_OF_DECLARE()?
> > +
> > +static int mtk_infrasys_init(struct device_node *node)
> > +{
> > + int r, i;
> > +
> > + if (!infra_clk_data) {
> > + infra_clk_data = mtk_alloc_clk_data(CLK_INFRA_NR);
> > + } else {
> > + for (i = 0; i < CLK_INFRA_NR; i++) {
> > + if (infra_clk_data->clks[i] == ERR_PTR(-EPROBE_DEFER))
> > + infra_clk_data->clks[i] = ERR_PTR(-ENOENT);
> > + }
> > + }
> > +
> > + mtk_clk_register_gates(node, infra_clks, ARRAY_SIZE(infra_clks),
> > + infra_clk_data);
> > + mtk_clk_register_factors(infra_fixed_divs, ARRAY_SIZE(infra_fixed_divs),
> > + infra_clk_data);
> > +
> > + r = of_clk_add_provider(node, of_clk_src_onecell_get, infra_clk_data);
> > + if (r)
> > + pr_err("%s(): could not register clock provider: %d\n",
> > + __func__, r);
> > +
> > + return r;
> > +}
> > +
> > +static const struct mtk_gate_regs peri0_cg_regs = {
> > + .set_ofs = 0x0008,
> > + .clr_ofs = 0x0010,
> > + .sta_ofs = 0x0018,
> > +};
> > +
> > +static const struct mtk_gate_regs peri1_cg_regs = {
> > + .set_ofs = 0x000c,
> > + .clr_ofs = 0x0014,
> > + .sta_ofs = 0x001c,
> > +};
> > +
> > +#define GATE_PERI0(_id, _name, _parent, _shift) { \
> > + .id = _id, \
> > + .name = _name, \
> > + .parent_name = _parent, \
> > + .regs = &peri0_cg_regs, \
> > + .shift = _shift, \
> > + .ops = &mtk_clk_gate_ops_setclr, \
> > + }
> > +
> > +#define GATE_PERI1(_id, _name, _parent, _shift) { \
> > + .id = _id, \
> > + .name = _name, \
> > + .parent_name = _parent, \
> > + .regs = &peri1_cg_regs, \
> > + .shift = _shift, \
> > + .ops = &mtk_clk_gate_ops_setclr, \
> > + }
> > +
> > +static const struct mtk_gate peri_clks[] = {
> > + GATE_PERI1(CLK_PERI_USB0_MCU, "usb0_mcu_ck", "axi_sel", 31),
> > + GATE_PERI1(CLK_PERI_ETH, "eth_ck", "clk26m", 30),
> > + GATE_PERI1(CLK_PERI_SPI0, "spi0_ck", "spi0_sel", 29),
> > + GATE_PERI1(CLK_PERI_AUXADC, "auxadc_ck", "clk26m", 28),
> > + GATE_PERI0(CLK_PERI_I2C3, "i2c3_ck", "clk26m", 27),
> > + GATE_PERI0(CLK_PERI_I2C2, "i2c2_ck", "axi_sel", 26),
> > + GATE_PERI0(CLK_PERI_I2C1, "i2c1_ck", "axi_sel", 25),
> > + GATE_PERI0(CLK_PERI_I2C0, "i2c0_ck", "axi_sel", 24),
> > + GATE_PERI0(CLK_PERI_BTIF, "bitif_ck", "axi_sel", 23),
> > + GATE_PERI0(CLK_PERI_UART3, "uart3_ck", "axi_sel", 22),
> > + GATE_PERI0(CLK_PERI_UART2, "uart2_ck", "axi_sel", 21),
> > + GATE_PERI0(CLK_PERI_UART1, "uart1_ck", "axi_sel", 20),
> > + GATE_PERI0(CLK_PERI_UART0, "uart0_ck", "axi_sel", 19),
> > + GATE_PERI0(CLK_PERI_NLI, "nli_ck", "axi_sel", 18),
> > + GATE_PERI0(CLK_PERI_MSDC50_3, "msdc50_3_ck", "emmc_hclk_sel", 17),
> > + GATE_PERI0(CLK_PERI_MSDC30_3, "msdc30_3_ck", "msdc30_3_sel", 16),
> > + GATE_PERI0(CLK_PERI_MSDC30_2, "msdc30_2_ck", "msdc30_2_sel", 15),
> > + GATE_PERI0(CLK_PERI_MSDC30_1, "msdc30_1_ck", "msdc30_1_sel", 14),
> > + GATE_PERI0(CLK_PERI_MSDC30_0, "msdc30_0_ck", "msdc30_0_sel", 13),
> > + GATE_PERI0(CLK_PERI_AP_DMA, "ap_dma_ck", "axi_sel", 12),
> > + GATE_PERI0(CLK_PERI_USB1, "usb1_ck", "usb20_sel", 11),
> > + GATE_PERI0(CLK_PERI_USB0, "usb0_ck", "usb20_sel", 10),
> > + GATE_PERI0(CLK_PERI_PWM, "pwm_ck", "axi_sel", 9),
> > + GATE_PERI0(CLK_PERI_PWM7, "pwm7_ck", "axi_sel", 8),
> > + GATE_PERI0(CLK_PERI_PWM6, "pwm6_ck", "axi_sel", 7),
> > + GATE_PERI0(CLK_PERI_PWM5, "pwm5_ck", "axi_sel", 6),
> > + GATE_PERI0(CLK_PERI_PWM4, "pwm4_ck", "axi_sel", 5),
> > + GATE_PERI0(CLK_PERI_PWM3, "pwm3_ck", "axi_sel", 4),
> > + GATE_PERI0(CLK_PERI_PWM2, "pwm2_ck", "axi_sel", 3),
> > + GATE_PERI0(CLK_PERI_PWM1, "pwm1_ck", "axi_sel", 2),
> > + GATE_PERI0(CLK_PERI_THERM, "therm_ck", "axi_sel", 1),
> > + GATE_PERI0(CLK_PERI_NFI, "nfi_ck", "nfi2x_sel", 0),
> > +
> > + GATE_PERI1(CLK_PERI_FCI, "fci_ck", "ms_card_sel", 11),
> > + GATE_PERI1(CLK_PERI_SPI2, "spi2_ck", "spi2_sel", 10),
> > + GATE_PERI1(CLK_PERI_SPI1, "spi1_ck", "spi1_sel", 9),
> > + GATE_PERI1(CLK_PERI_HOST89_DVD, "host89_dvd_ck", "aud2dvd_sel", 8),
> > + GATE_PERI1(CLK_PERI_HOST89_SPI, "host89_spi_ck", "spi0_sel", 7),
> > + GATE_PERI1(CLK_PERI_HOST89_INT, "host89_int_ck", "axi_sel", 6),
> > + GATE_PERI1(CLK_PERI_FLASH, "flash_ck", "nfi2x_sel", 5),
> > + GATE_PERI1(CLK_PERI_NFI_PAD, "nfi_pad_ck", "nfi1x_pad", 4),
> > + GATE_PERI1(CLK_PERI_NFI_ECC, "nfi_ecc_ck", "nfi1x_pad", 3),
> > + GATE_PERI1(CLK_PERI_GCPU, "gcpu_ck", "axi_sel", 2),
> > + GATE_PERI1(CLK_PERI_USB_SLV, "usbslv_ck", "axi_sel", 1),
> > + GATE_PERI1(CLK_PERI_USB1_MCU, "usb1_mcu_ck", "axi_sel", 0),
> > +};
> > +
> > +static const char * const uart_ck_sel_parents[] = {
> > + "clk26m",
> > + "uart_sel",
> > +};
> > +
> > +static const struct mtk_composite peri_muxs[] = {
> > + MUX(CLK_PERI_UART0_SEL, "uart0_ck_sel", uart_ck_sel_parents,
> > + 0x40c, 0, 1),
> > + MUX(CLK_PERI_UART1_SEL, "uart1_ck_sel", uart_ck_sel_parents,
> > + 0x40c, 1, 1),
> > + MUX(CLK_PERI_UART2_SEL, "uart2_ck_sel", uart_ck_sel_parents,
> > + 0x40c, 2, 1),
> > + MUX(CLK_PERI_UART3_SEL, "uart3_ck_sel", uart_ck_sel_parents,
> > + 0x40c, 3, 1),
> > +};
> > +
> > +static int mtk_pericfg_init(struct device_node *node)
> > +{
> > + struct clk_onecell_data *clk_data;
> > + void __iomem *base;
> > + int r;
> > +
> > + base = of_iomap(node, 0);
> > + if (!base) {
> > + pr_err("%s(): ioremap failed\n", __func__);
>
> Please pass the device structure to these callbacks so that we
> can use standard devm_ioremap() type APIs like normal platform
> drivers.
I'll change it in next series.
> > + return -ENOMEM;
> > + }
> > +
> > + clk_data = mtk_alloc_clk_data(CLK_PERI_NR);
> > +
> > + mtk_clk_register_gates(node, peri_clks, ARRAY_SIZE(peri_clks),
> > + clk_data);
> > +
> > + mtk_clk_register_composites(peri_muxs, ARRAY_SIZE(peri_muxs), base,
> > + &lock, clk_data);
> > +
> > + r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > + if (r)
> > + pr_err("%s(): could not register clock provider: %d\n",
> > + __func__, r);
> > +
> > + return r;
> > +}
> > +
> > +#define MT8590_PLL_FMAX (2000 * MHZ)
> > +#define CON0_MT8590_RST_BAR BIT(27)
> > +
> > +#define PLL(_id, _name, _reg, _pwr_reg, _en_mask, _flags, _pcwbits, _pd_reg, \
> > + _pd_shift, _tuner_reg, _pcw_reg, _pcw_shift) { \
> > + .id = _id, \
> > + .name = _name, \
> > + .reg = _reg, \
> > + .pwr_reg = _pwr_reg, \
> > + .en_mask = _en_mask, \
> > + .flags = _flags, \
> > + .rst_bar_mask = CON0_MT8590_RST_BAR, \
> > + .fmax = MT8590_PLL_FMAX, \
> > + .pcwbits = _pcwbits, \
> > + .pd_reg = _pd_reg, \
> > + .pd_shift = _pd_shift, \
> > + .tuner_reg = _tuner_reg, \
> > + .pcw_reg = _pcw_reg, \
> > + .pcw_shift = _pcw_shift, \
> > + }
> > +
> > +static const struct mtk_pll_data apmixed_plls[] = {
> > + PLL(CLK_APMIXED_ARMPLL, "armpll", 0x200, 0x20c, 0x80000001,
> > + PLL_AO, 21, 0x204, 24, 0x0, 0x204, 0),
> > + PLL(CLK_APMIXED_MAINPLL, "mainpll", 0x210, 0x21c, 0xf0000001,
> > + HAVE_RST_BAR, 21, 0x210, 4, 0x0, 0x214, 0),
> > + PLL(CLK_APMIXED_UNIVPLL, "univpll", 0x220, 0x22c, 0xf3000001,
> > + HAVE_RST_BAR, 7, 0x220, 4, 0x0, 0x224, 14),
> > + PLL(CLK_APMIXED_MMPLL, "mmpll", 0x230, 0x23c, 0x00000001, 0,
> > + 21, 0x230, 4, 0x0, 0x234, 0),
> > + PLL(CLK_APMIXED_MSDCPLL, "msdcpll", 0x240, 0x24c, 0x00000001, 0,
> > + 21, 0x240, 4, 0x0, 0x244, 0),
> > + PLL(CLK_APMIXED_TVDPLL, "tvdpll", 0x250, 0x25c, 0x00000001, 0,
> > + 21, 0x250, 4, 0x0, 0x254, 0),
> > + PLL(CLK_APMIXED_AUD1PLL, "aud1pll", 0x270, 0x27c, 0x00000001, 0,
> > + 31, 0x270, 4, 0x0, 0x274, 0),
> > + PLL(CLK_APMIXED_TRGPLL, "trgpll", 0x280, 0x28c, 0x00000001, 0,
> > + 31, 0x280, 4, 0x0, 0x284, 0),
> > + PLL(CLK_APMIXED_ETHPLL, "ethpll", 0x290, 0x29c, 0x00000001, 0,
> > + 31, 0x290, 4, 0x0, 0x294, 0),
> > + PLL(CLK_APMIXED_VDECPLL, "vdecpll", 0x2a0, 0x2ac, 0x00000001, 0,
> > + 31, 0x2a0, 4, 0x0, 0x2a4, 0),
> > + PLL(CLK_APMIXED_HADDS2PLL, "hadds2pll", 0x2b0, 0x2bc, 0x00000001, 0,
> > + 31, 0x2b0, 4, 0x0, 0x2b4, 0),
> > + PLL(CLK_APMIXED_AUD2PLL, "aud2pll", 0x2c0, 0x2cc, 0x00000001, 0,
> > + 31, 0x2c0, 4, 0x0, 0x2c4, 0),
> > + PLL(CLK_APMIXED_TVD2PLL, "tvd2pll", 0x2d0, 0x2dc, 0x00000001, 0,
> > + 21, 0x2d0, 4, 0x0, 0x2d4, 0),
> > +};
> > +
> > +static int mtk_apmixedsys_init(struct device_node *node)
> > +{
> > + struct clk_onecell_data *clk_data;
> > + int r;
> > +
> > + clk_data = mtk_alloc_clk_data(CLK_APMIXED_NR);
> > + if (!clk_data)
> > + return -ENOMEM;
> > +
> > + mtk_clk_register_plls(node, apmixed_plls, ARRAY_SIZE(apmixed_plls),
> > + clk_data);
> > +
> > + r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
> > + if (r)
> > + pr_err("%s(): could not register clock provider: %d\n",
> > + __func__, r);
>
> These three lines could be moved to the caller so taht we don't
> duplicate the same logic over and over.
I'll change it in next series.
> > +
> > + return r;
> > +}
> > +
> > +static const struct of_device_id of_match_clk_mt2701[] = {
> > + {
> > + .compatible = "mediatek,mt2701-topckgen",
> > + .data = mtk_topckgen_init,
> > + }, {
> > + .compatible = "mediatek,mt2701-infracfg",
> > + .data = mtk_infrasys_init,
> > + }, {
> > + .compatible = "mediatek,mt2701-pericfg",
> > + .data = mtk_pericfg_init,
> > + }, {
> > + .compatible = "mediatek,mt2701-apmixedsys",
> > + .data = mtk_apmixedsys_init,
> > + }, {
> > + /* sentinel */
> > + }
> > +};
> > +
> > +static int clk_mt2701_probe(struct platform_device *pdev)
> > +{
> > + int (*clk_init)(struct device_node *);
> > + const struct of_device_id *of_id;
> > +
> > + pr_warn("%s():%d: %s\n", __func__, __LINE__, pdev->name);
> > +
> > + of_id = of_match_node(of_match_clk_mt2701, pdev->dev.of_node);
> > + if (!of_id || !of_id->data)
> > + return -EINVAL;
>
> This can be
>
> clk_init = of_device_get_match_data(of_match_clk_mt2701, &pdev->dev);
> if (!clk_init)
> return -EINVAL;
Thanks. I'll change it in next series.
> > +
> > + clk_init = of_id->data;
> > + return clk_init(pdev->dev.of_node);
> > +}
> > +
> > +static struct platform_driver clk_mt2701_drv = {
> > + .probe = clk_mt2701_probe,
> > + .driver = {
> > + .name = "clk-mt2701",
> > + .owner = THIS_MODULE,
> > + .of_match_table = of_match_clk_mt2701,
> > + },
> > +};
> > +
> > +static int __init clk_mt2701_init(void)
> > +{
> > + return platform_driver_register(&clk_mt2701_drv);
> > +}
> > +
> > +arch_initcall(clk_mt2701_init);
> > diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c
> > index bb30f70..6a015a8 100644
> > --- a/drivers/clk/mediatek/clk-mtk.c
> > +++ b/drivers/clk/mediatek/clk-mtk.c
> > @@ -58,6 +58,9 @@ void mtk_clk_register_fixed_clks(const struct mtk_fixed_clk *clks,
> > for (i = 0; i < num; i++) {
> > const struct mtk_fixed_clk *rc = &clks[i];
> >
> > + if (!IS_ERR_OR_NULL(clk_data->clks[rc->id]))
> > + continue;
> > +
> > clk = clk_register_fixed_rate(NULL, rc->name, rc->parent, 0,
> > rc->rate);
> >
> > @@ -81,6 +84,9 @@ void mtk_clk_register_factors(const struct mtk_fixed_factor *clks,
> > for (i = 0; i < num; i++) {
> > const struct mtk_fixed_factor *ff = &clks[i];
> >
> > + if (!IS_ERR_OR_NULL(clk_data->clks[ff->id]))
> > + continue;
> > +
> > clk = clk_register_fixed_factor(NULL, ff->name, ff->parent_name,
> > CLK_SET_RATE_PARENT, ff->mult, ff->div);
> >
> > @@ -116,6 +122,9 @@ int mtk_clk_register_gates(struct device_node *node,
> > for (i = 0; i < num; i++) {
> > const struct mtk_gate *gate = &clks[i];
> >
> > + if (!IS_ERR_OR_NULL(clk_data->clks[gate->id]))
> > + continue;
> > +
> > clk = mtk_clk_register_gate(gate->name, gate->parent_name,
> > regmap,
> > gate->regs->set_ofs,
> > @@ -232,6 +241,9 @@ void mtk_clk_register_composites(const struct mtk_composite *mcs,
> > for (i = 0; i < num; i++) {
> > const struct mtk_composite *mc = &mcs[i];
> >
> > + if (!IS_ERR_OR_NULL(clk_data->clks[mc->id]))
> > + continue;
> > +
> > clk = mtk_clk_register_composite(mc, base, lock);
> >
> > if (IS_ERR(clk)) {
> > @@ -244,3 +256,31 @@ void mtk_clk_register_composites(const struct mtk_composite *mcs,
> > clk_data->clks[mc->id] = clk;
> > }
> > }
> > +
> > +void mtk_clk_register_dividers(const struct mtk_clk_divider *mcds,
> > + int num, void __iomem *base, spinlock_t *lock,
> > + struct clk_onecell_data *clk_data)
> > +{
> > + struct clk *clk;
> > + int i;
> > +
> > + for (i = 0; i < num; i++) {
> > + const struct mtk_clk_divider *mcd = &mcds[i];
> > +
> > + if (!IS_ERR_OR_NULL(clk_data->clks[mcd->id]))
>
> This dereferences clk_data...
>
> > + continue;
> > +
> > + clk = clk_register_divider(NULL, mcd->name, mcd->parent_name,
> > + mcd->flags, base + mcd->div_reg, mcd->div_shift,
> > + mcd->div_width, mcd->clk_divider_flags, lock);
> > +
> > + if (IS_ERR(clk)) {
> > + pr_err("Failed to register clk %s: %ld\n",
> > + mcd->name, PTR_ERR(clk));
> > + continue;
> > + }
> > +
> > + if (clk_data)
>
> And then we check it for NULL here? That doesn't make any sense.
I'll change it in next series.
> > + clk_data->clks[mcd->id] = clk;
> > + }
> > +}
Thanks for your comments.
Best regards,
James
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH net-next v4 0/2] net: ethernet: mediatek: modify to use the PDMA for Ethernet RX
From: David Miller @ 2016-08-27 4:07 UTC (permalink / raw)
To: nelson.chang-NuS5LvNUpcJWk0Htik3J/w
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
nelsonch.tw-Re5JQEeQqe8AvxtiuMwx3w, john-Pj+rj9U5foFAfugRpC6u6w
In-Reply-To: <1472144983-5048-1-git-send-email-nelson.chang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
From: Nelson Chang <nelson.chang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Date: Fri, 26 Aug 2016 01:09:41 +0800
> This series have some modifications and refines to support Ethernet RX by the PDMA.
>
> changes since v4:
> - Remove the redundant OR operation in mtk_hw_init()
>
> changes since v3:
> - Add GDM hardware settings to send packets to PDMA for RX
>
> changes since v2:
> - Fix the bugs of PDMA cpu index and interrupt settings in mtk_poll_rx()
>
> changes since v1:
> - Modify to use the PDMA instead of the QDMA for Ethernet RX
Series applied, thanks.
^ permalink raw reply
* [PATCH -next] iio: adc: mt2701: Remove redundant dev_err call in mt6577_auxadc_probe()
From: Wei Yongjun @ 2016-08-26 14:26 UTC (permalink / raw)
To: Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Matthias Brugger, Zhiyong Tao
Cc: Wei Yongjun, linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
From: Wei Yongjun <weiyongjun1-hv44wF8Li93QT0dZR+AlfA@public.gmane.org>
There is a error message within devm_ioremap_resource
already, so remove the dev_err call to avoid redundant
error message.
Signed-off-by: Wei Yongjun <weiyongjun1-hv44wF8Li93QT0dZR+AlfA@public.gmane.org>
---
drivers/iio/adc/mt6577_auxadc.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/iio/adc/mt6577_auxadc.c b/drivers/iio/adc/mt6577_auxadc.c
index 2d104c8..a268954 100644
--- a/drivers/iio/adc/mt6577_auxadc.c
+++ b/drivers/iio/adc/mt6577_auxadc.c
@@ -206,10 +206,8 @@ static int mt6577_auxadc_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
adc_dev->reg_base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(adc_dev->reg_base)) {
- dev_err(&pdev->dev, "failed to get auxadc base address\n");
+ if (IS_ERR(adc_dev->reg_base))
return PTR_ERR(adc_dev->reg_base);
- }
adc_dev->adc_clk = devm_clk_get(&pdev->dev, "main");
if (IS_ERR(adc_dev->adc_clk)) {
^ permalink raw reply related
* Re: [RESEND PATCH net 06/10] net: ethernet: mediatek: fix the loss
From: Andrew Lunn @ 2016-08-26 14:17 UTC (permalink / raw)
To: Sean Wang; +Cc: john, davem, nbd, netdev, linux-mediatek, keyhaede
In-Reply-To: <1472182419-17998-1-git-send-email-sean.wang@mediatek.com>
> Hi Andrew,
>
> Here pinctrl is used to setup what function the group of the pins is
> for.
Agreed.
> The group of the pins could be configured for the function provided
> by the SoC, such as general purpose I/O or specific function such as
> ethernet depending on what products or boards you design for various
> customers or vendors. Thanks for device tree introducing, it is easy
> to find what resources the board needs including the pins usage is
> also defined here.
All clear. However, if the ethernet driver has loaded, it means the
device tree says the ethernet should be loaded, unless it happens to
be on some discoverable bus. And so the device tree node for the
ethernet should also contain the needed pinctrl properties. The core
driver code should of seen these properties and already enabled the
correct pinctrl state before the driver probes.
This is how every other driver works. Like i said, i don't think i've
seen any other driver do its own pinctrl. So i just need a simple
description, what is different here, why does this driver need to do
it, when no other does?
Andrew
^ permalink raw reply
* Re: [RESEND PATCH, v5 4/5] usb: Add MediaTek USB3 DRD Driver
From: chunfeng yun @ 2016-08-26 9:38 UTC (permalink / raw)
To: Oliver Neukum
Cc: Mark Rutland, devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Felipe Balbi, Pawel Moll,
Ian Campbell, Greg Kroah-Hartman, Sascha Hauer, AlanCooper,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Mathias Nyman, Rob Herring,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, KumarGala,
Matthias Brugger, Alan Stern, Sergei Shtylyov,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1472113973.2877.22.camel-IBi9RG/b67k@public.gmane.org>
Hi,
On Thu, 2016-08-25 at 10:32 +0200, Oliver Neukum wrote:
> On Thu, 2016-08-25 at 11:05 +0800, Chunfeng Yun wrote:
> > This patch adds support for the MediaTek USB3 controller
> > integrated into MT8173. It can be configured as Dual-Role
> > Device (DRD), Peripheral Only and Host Only (xHCI) modes.
> >
>
> > +/**
> > + * General Purpose Descriptor (GPD):
> > + * The format of TX GPD is a little different from RX one.
> > + * And the size of GPD is 16 bytes.
> > + *
> > + * @flag:
> > + * bit0: Hardware Own (HWO)
> > + * bit1: Buffer Descriptor Present (BDP), always 0, BD is not supported
> > + * bit2: Bypass (BPS), 1: HW skips this GPD if HWO = 1
> > + * bit7: Interrupt On Completion (IOC)
> > + * @chksum: This is used to validate the contents of this GPD;
> > + * If TXQ_CS_EN / RXQ_CS_EN bit is set, an interrupt is issued
> > + * when checksum validation fails;
> > + * Checksum value is calculated over the 16 bytes of the GPD by default;
> > + * @data_buf_len (RX ONLY): This value indicates the length of
> > + * the assigned data buffer
> > + * @next_gpd: Physical address of the next GPD
> > + * @buffer: Physical address of the data buffer
> > + * @buf_len:
> > + * (TX): This value indicates the length of the assigned data buffer
> > + * (RX): The total length of data received
> > + * @ext_len: reserved
> > + * @ext_flag:
> > + * bit5 (TX ONLY): Zero Length Packet (ZLP),
> > + */
> > +struct qmu_gpd {
> > + u8 flag;
> > + u8 chksum;
> > + u16 data_buf_len;
> > + u32 next_gpd;
> > + u32 buffer;
> > + u16 buf_len;
> > + u8 ext_len;
> > + u8 ext_flag;
> > +} __packed;
>
> It looks like this is shared with hardware in memory.
> But you leave the endianness of the bigger fields undeclared.
>
The driver only supports Little-Endian SoCs currently, because I have no
Big-Endian platform to test it.
> > +/**
> > +* dma: physical base address of GPD segment
> > +* start: virtual base address of GPD segment
> > +* end: the last GPD element
> > +* enqueue: the first empty GPD to use
> > +* dequeue: the first completed GPD serviced by ISR
> > +* NOTE: the size of GPD ring should be >= 2
> > +*/
> > +struct mtu3_gpd_ring {
> > + dma_addr_t dma;
> > + struct qmu_gpd *start;
> > + struct qmu_gpd *end;
> > + struct qmu_gpd *enqueue;
> > + struct qmu_gpd *dequeue;
> > +};
> > +
> > +/**
> > +* @vbus: vbus 5V used by host mode
> > +* @edev: external connector used to detect vbus and iddig changes
> > +* @vbus_nb: notifier for vbus detection
> > +* @vbus_nb: notifier for iddig(idpin) detection
> > +* @extcon_reg_dwork: delay work for extcon notifier register, waiting for
> > +* xHCI driver initialization, it's necessary for system bootup
> > +* as device.
> > +* @is_u3_drd: whether port0 supports usb3.0 dual-role device or not
> > +* @id_*: used to maually switch between host and device modes by idpin
> > +* @manual_drd_enabled: it's true when supports dual-role device by debugfs
> > +* to switch host/device modes depending on user input.
>
> Please use a common interface for this. The Intel people are introducing
> one.
Can you give me the link address of the patch, I didn't find it.
>
>
> > +#endif
> > diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c
> > new file mode 100644
> > index 0000000..fdc11b6
> > --- /dev/null
> > +++ b/drivers/usb/mtu3/mtu3_core.c
> > @@ -0,0 +1,874 @@
> > +/*
> > + * mtu3_core.c - hardware access layer and gadget init/exit of
> > + * MediaTek usb3 Dual-Role Controller Driver
> > + *
> > + * Copyright (C) 2016 MediaTek Inc.
> > + *
> > + * Author: Chunfeng Yun <chunfeng.yun-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> > + *
> > + * This software is licensed under the terms of the GNU General Public
> > + * License version 2, as published by the Free Software Foundation, and
> > + * may be copied, distributed, and modified under those terms.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> > + * GNU General Public License for more details.
> > + *
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "mtu3.h"
> > +
> > +static int ep_fifo_alloc(struct mtu3_ep *mep, u32 seg_size)
> > +{
> > + struct mtu3_fifo_info *fifo = mep->fifo;
> > + u32 num_bits = DIV_ROUND_UP(seg_size, MTU3_EP_FIFO_UNIT);
> > + u32 start_bit;
> > +
> > + /* ensure that @mep->fifo_seg_size is power of two */
> > + num_bits = roundup_pow_of_two(num_bits);
> > + if (num_bits > fifo->limit)
> > + return -EINVAL;
> > +
> > + mep->fifo_seg_size = num_bits * MTU3_EP_FIFO_UNIT;
> > + num_bits = num_bits * (mep->slot + 1);
> > + start_bit = bitmap_find_next_zero_area(fifo->bitmap,
> > + fifo->limit, 0, num_bits, 0);
> > + if (start_bit >= fifo->limit)
> > + return -EOVERFLOW;
> > +
> > + bitmap_set(fifo->bitmap, start_bit, num_bits);
> > + mep->fifo_size = num_bits * MTU3_EP_FIFO_UNIT;
> > + mep->fifo_addr = fifo->base + MTU3_EP_FIFO_UNIT * start_bit;
> > +
> > + dev_dbg(mep->mtu->dev, "%s fifo:%#x/%#x, start_bit: %d\n",
> > + __func__, mep->fifo_seg_size, mep->fifo_size, start_bit);
>
> If you use the "f" option to dynamic debugging it will give you the
> function name anyway. So why include __func__ ?
>
It's used when dynamic debugging is disable but defines DEBUG macro.
>
> > +/* enable/disable U3D SS function */
> > +static void mtu3_ss_func_set(struct mtu3 *mtu, bool enable)
>
> Inline maybe ?
>
> > +{
> > + /* If usb3_en==0, LTSSM will go to SS.Disable state */
> > + if (enable)
> > + mtu3_setbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN);
> > + else
> > + mtu3_clrbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN);
> > +
> > + dev_dbg(mtu->dev, "USB3_EN = %d\n", !!enable);
> > +}
> > +
>
> [..]
>
>
> > +int mtu3_config_ep(struct mtu3 *mtu, struct mtu3_ep *mep,
> > + int interval, int burst, int mult)
> > +{
> > + void __iomem *mbase = mtu->mac_base;
> > + int epnum = mep->epnum;
> > + u32 csr0, csr1, csr2;
> > + int fifo_sgsz, fifo_addr;
> > + int num_pkts;
> > +
> > + fifo_addr = ep_fifo_alloc(mep, mep->maxp);
> > + if (fifo_addr < 0) {
> > + dev_err(mtu->dev, "alloc ep fifo failed(%d)\n", mep->maxp);
> > + return -ENOMEM;
> > + }
> > + fifo_sgsz = ilog2(mep->fifo_seg_size);
> > + dev_dbg(mtu->dev, "%s fifosz: %x(%x/%x)\n", __func__, fifo_sgsz,
> > + mep->fifo_seg_size, mep->fifo_size);
> > +
> > + if (mep->is_in) {
> > + csr0 = TX_TXMAXPKTSZ(mep->maxp);
> > + csr0 |= TX_DMAREQEN;
> > +
> > + num_pkts = (burst + 1) * (mult + 1) - 1;
> > + csr1 = TX_SS_BURST(burst) | TX_SLOT(mep->slot);
> > + csr1 |= TX_MAX_PKT(num_pkts) | TX_MULT(mult);
> > +
> > + csr2 = TX_FIFOADDR(fifo_addr >> 4);
> > + csr2 |= TX_FIFOSEGSIZE(fifo_sgsz);
> > +
> > + switch (mep->type) {
> > + case USB_ENDPOINT_XFER_BULK:
> > + csr1 |= TX_TYPE(TYPE_BULK);
> > + break;
> > + case USB_ENDPOINT_XFER_ISOC:
> > + csr1 |= TX_TYPE(TYPE_ISO);
> > + csr2 |= TX_BINTERVAL(interval);
> > + break;
> > + case USB_ENDPOINT_XFER_INT:
> > + csr1 |= TX_TYPE(TYPE_INT);
> > + csr2 |= TX_BINTERVAL(interval);
> > + break;
>
> And if it is control?
The function is only called for non control EP.
I will add a comment for it.
>
> > + }
> > +
> > + /* Enable QMU Done interrupt */
> > + mtu3_setbits(mbase, U3D_QIESR0, QMU_TX_DONE_INT(epnum));
> > +
> > + mtu3_writel(mbase, MU3D_EP_TXCR0(epnum), csr0);
> > + mtu3_writel(mbase, MU3D_EP_TXCR1(epnum), csr1);
> > + mtu3_writel(mbase, MU3D_EP_TXCR2(epnum), csr2);
> > +
> > + dev_dbg(mtu->dev, "U3D_TX%d CSR0:%#x, CSR1:%#x, CSR2:%#x\n",
> > + epnum, mtu3_readl(mbase, MU3D_EP_TXCR0(epnum)),
> > + mtu3_readl(mbase, MU3D_EP_TXCR1(epnum)),
> > + mtu3_readl(mbase, MU3D_EP_TXCR2(epnum)));
> > + } else {
> > + csr0 = RX_RXMAXPKTSZ(mep->maxp);
> > + csr0 |= RX_DMAREQEN;
> > +
> > + num_pkts = (burst + 1) * (mult + 1) - 1;
> > + csr1 = RX_SS_BURST(burst) | RX_SLOT(mep->slot);
> > + csr1 |= RX_MAX_PKT(num_pkts) | RX_MULT(mult);
> > +
> > + csr2 = RX_FIFOADDR(fifo_addr >> 4);
> > + csr2 |= RX_FIFOSEGSIZE(fifo_sgsz);
> > +
> > + switch (mep->type) {
> > + case USB_ENDPOINT_XFER_BULK:
> > + csr1 |= RX_TYPE(TYPE_BULK);
> > + break;
> > + case USB_ENDPOINT_XFER_ISOC:
> > + csr1 |= RX_TYPE(TYPE_ISO);
> > + csr2 |= RX_BINTERVAL(interval);
> > + break;
> > + case USB_ENDPOINT_XFER_INT:
> > + csr1 |= RX_TYPE(TYPE_INT);
> > + csr2 |= RX_BINTERVAL(interval);
> > + break;
>
> Again: control endpoints?
Only for non-EP0.
>
> > + }
> > +
> > + /*Enable QMU Done interrupt */
> > + mtu3_setbits(mbase, U3D_QIESR0, QMU_RX_DONE_INT(epnum));
> > +
> > + mtu3_writel(mbase, MU3D_EP_RXCR0(epnum), csr0);
> > + mtu3_writel(mbase, MU3D_EP_RXCR1(epnum), csr1);
> > + mtu3_writel(mbase, MU3D_EP_RXCR2(epnum), csr2);
> > +
> > + dev_dbg(mtu->dev, "U3D_RX%d CSR0:%#x, CSR1:%#x, CSR2:%#x\n",
> > + epnum, mtu3_readl(mbase, MU3D_EP_RXCR0(epnum)),
> > + mtu3_readl(mbase, MU3D_EP_RXCR1(epnum)),
> > + mtu3_readl(mbase, MU3D_EP_RXCR2(epnum)));
> > + }
> > +
> > + dev_dbg(mtu->dev, "csr0:%#x, csr1:%#x, csr2:%#x\n", csr0, csr1, csr2);
> > + dev_dbg(mtu->dev, "%s: %s, fifo-addr:%#x, fifo-size:%#x(%#x/%#x)\n",
> > + __func__, mep->name, mep->fifo_addr, mep->fifo_size,
> > + fifo_sgsz, mep->fifo_seg_size);
> > +
> > + return 0;
> > +}
> > +
>
> [..]
> > +static void mtu3_set_speed(struct mtu3 *mtu)
> > +{
> > + void __iomem *mbase = mtu->mac_base;
> > +
> > + if (!mtu->is_u3_ip && (mtu->max_speed > USB_SPEED_HIGH))
> > + mtu->max_speed = USB_SPEED_HIGH;
>
> You are missing the checks for USB_SPEED_WIRELESS in general.
> Can you just ignore it?
Doesn't support USB_SPEED_WIRELESS, so ignore it.
>
> > +
> > + if (mtu->max_speed == USB_SPEED_FULL) {
> > + /* disable U3 SS function */
> > + mtu3_clrbits(mbase, U3D_USB3_CONFIG, USB3_EN);
> > + /* disable HS function */
> > + mtu3_clrbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
> > + } else if (mtu->max_speed == USB_SPEED_HIGH) {
> > + mtu3_clrbits(mbase, U3D_USB3_CONFIG, USB3_EN);
> > + /* HS/FS detected by HW */
> > + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
> > + }
> > + dev_info(mtu->dev, "max_speed: %s\n",
> > + usb_speed_string(mtu->max_speed));
> > +}
>
> [..]
> > +static irqreturn_t mtu3_link_isr(struct mtu3 *mtu)
> > +{
> > + void __iomem *mbase = mtu->mac_base;
> > + enum usb_device_speed udev_speed;
> > + u32 maxpkt = 64;
> > + u32 link;
> > + u32 speed;
> > +
> > + link = mtu3_readl(mbase, U3D_DEV_LINK_INTR);
> > + link &= mtu3_readl(mbase, U3D_DEV_LINK_INTR_ENABLE);
> > + mtu3_writel(mbase, U3D_DEV_LINK_INTR, link); /* W1C */
> > + dev_dbg(mtu->dev, "=== LINK[%x] ===\n", link);
> > +
> > + if (!(link & SSUSB_DEV_SPEED_CHG_INTR))
> > + return IRQ_NONE;
>
> Shouldn't you do this check before you clear interrupts?
In fact, U3D_DEV_LINK_INTR has only one interrupt,
SSUSB_DEV_SPEED_CHG_INTR, others can be treated as spurious ones.
>
> > + speed = SSUSB_DEV_SPEED(mtu3_readl(mbase, U3D_DEVICE_CONF));
>
> Do you really want to read this out of the hardware on every interrupt?
Yes, the interrupt is triggered only when speed is changed,
and get the new speed here.
>
> > +
> > + switch (speed) {
> > + case MTU3_SPEED_FULL:
> > + udev_speed = USB_SPEED_FULL;
> > + /*BESLCK = 4 < BESLCK_U3 = 10 < BESLDCK = 15 */
> > + mtu3_writel(mbase, U3D_USB20_LPM_PARAMETER, LPM_BESLDCK(0xf)
> > + | LPM_BESLCK(4) | LPM_BESLCK_U3(0xa));
> > + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT,
> > + LPM_BESL_STALL | LPM_BESLD_STALL);
> > + break;
> > + case MTU3_SPEED_HIGH:
> > + udev_speed = USB_SPEED_HIGH;
> > + /*BESLCK = 4 < BESLCK_U3 = 10 < BESLDCK = 15 */
> > + mtu3_writel(mbase, U3D_USB20_LPM_PARAMETER, LPM_BESLDCK(0xf)
> > + | LPM_BESLCK(4) | LPM_BESLCK_U3(0xa));
> > + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT,
> > + LPM_BESL_STALL | LPM_BESLD_STALL);
> > + break;
> > + case MTU3_SPEED_SUPER:
> > + udev_speed = USB_SPEED_SUPER;
> > + maxpkt = 512;
> > + break;
> > + default:
> > + udev_speed = USB_SPEED_UNKNOWN;
> > + break;
> > + }
> > + dev_dbg(mtu->dev, "%s: %s\n", __func__, usb_speed_string(udev_speed));
> > +
> > + mtu->g.speed = udev_speed;
> > + mtu->g.ep0->maxpacket = maxpkt;
> > + mtu->ep0_state = MU3D_EP0_STATE_SETUP;
> > +
> > + if (udev_speed == USB_SPEED_UNKNOWN)
> > + mtu3_gadget_disconnect(mtu);
> > + else
> > + mtu3_ep0_setup(mtu);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static irqreturn_t mtu3_u3_ltssm_isr(struct mtu3 *mtu)
> > +{
> > + void __iomem *mbase = mtu->mac_base;
> > + u32 ltssm;
> > +
> > + ltssm = mtu3_readl(mbase, U3D_LTSSM_INTR);
> > + ltssm &= mtu3_readl(mbase, U3D_LTSSM_INTR_ENABLE);
> > + mtu3_writel(mbase, U3D_LTSSM_INTR, ltssm); /* W1C */
> > + dev_dbg(mtu->dev, "=== LTSSM[%x] ===\n", ltssm);
> > +
> > + if (ltssm & HOT_RST_INTR)
> > + mtu3_gadget_reset(mtu);
> > +
> > + if (ltssm & WARM_RST_INTR)
> > + mtu3_gadget_reset(mtu);
>
> You really would reset the gadget twice if that happens?
> And do the rest of the tests make sense in that case?
I will merge them into one as following:
if (ltssm & (HOT_RST_INTR | WARM_RST_INTR))
...
>
> > +
> > + if (ltssm & VBUS_FALL_INTR)
> > + mtu3_ss_func_set(mtu, false);
> > +
> > + if (ltssm & VBUS_RISE_INTR)
> > + mtu3_ss_func_set(mtu, true);
> > +
> > + if (ltssm & EXIT_U3_INTR)
> > + mtu3_gadget_resume(mtu);
> > +
> > + if (ltssm & ENTER_U3_INTR)
> > + mtu3_gadget_suspend(mtu);
> > +
> > + return IRQ_HANDLED;
> > +}
>
> [..]
>
> > +static int mtu3_hw_init(struct mtu3 *mtu)
> > +{
> > + int ret;
> > +
> > + mtu3_device_reset(mtu);
> > +
> > + ret = mtu3_device_enable(mtu);
> > + if (ret) {
> > + dev_err(mtu->dev, "device enable failed %d\n", ret);
> > + return ret;
> > + }
> > +
> > + ret = mtu3_mem_alloc(mtu);
> > + if (ret) {
> > + dev_err(mtu->dev, "mem alloc failed, aborting\n");
>
> 1. You can't leave the device enabled in that case.
It enable device mac and port, otherwise I can't initialize other
register.
and the function is not enable until udc_start() is called.
> 2. No need for a message. The kernel will already do that if kmalloc()
> fails under such circumstances.
remove it latter.
>
> > + return -ENOMEM;
> > + }
> > +
> > + mtu3_regs_init(mtu);
> > +
> > + return 0;
> > +}
>
> > diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c
> > new file mode 100644
> > index 0000000..f560f93
> > --- /dev/null
> > +++ b/drivers/usb/mtu3/mtu3_dr.c
> > @@ -0,0 +1,375 @@
> > +/*
> > + * mtu3_dr.c - dual role switch and host glue layer
> > + *
> > + * Copyright (C) 2016 MediaTek Inc.
> > + *
> > + * Author: Chunfeng Yun <chunfeng.yun-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> > + *
> > + * This software is licensed under the terms of the GNU General Public
> > + * License version 2, as published by the Free Software Foundation, and
> > + * may be copied, distributed, and modified under those terms.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> > + * GNU General Public License for more details.
> > + *
> > + */
> > +
> > +#include <linux/debugfs.h>
> > +#include <linux/irq.h>
> > +#include <linux/kernel.h>
> > +#include <linux/of_device.h>
> > +#include <linux/pinctrl/consumer.h>
> > +#include <linux/seq_file.h>
> > +#include <linux/uaccess.h>
> > +
> > +#include "mtu3.h"
> > +#include "mtu3_dr.h"
> > +
> > +#define USB2_PORT 2
> > +#define USB3_PORT 3
> > +
[...]
> > +
> > +static ssize_t ssusb_mode_write(struct file *file,
> > + const char __user *ubuf, size_t count, loff_t *ppos)
> > +{
> > + struct seq_file *sf = file->private_data;
> > + struct ssusb_mtk *ssusb = sf->private;
> > + char buf[16];
> > +
> > + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
> > + return -EFAULT;
> > +
> > + if (!strncmp(buf, "host", 4) && !ssusb->is_host)
> > + ssusb_mode_manual_switch(ssusb, 1);
> > + else if (!strncmp(buf, "device", 6) && ssusb->is_host)
> > + ssusb_mode_manual_switch(ssusb, 0);
> > + else
> > + dev_err(ssusb->dev, "wrong or duplicated setting\n");
>
> No proper error returns
add it later.
> > +
> > + return count;
> > +}
> > +
> > +static const struct file_operations ssusb_mode_fops = {
> > + .open = ssusb_mode_open,
> > + .write = ssusb_mode_write,
> > + .read = seq_read,
> > + .llseek = seq_lseek,
> > + .release = single_release,
> > +};
> > +
[..]
> > +static void ssusb_debugfs_exit(struct ssusb_mtk *ssusb)
> > +{
> > + debugfs_remove_recursive(ssusb->dbgfs_root);
> > +}
> > +
> > +int ssusb_otg_switch_init(struct ssusb_mtk *ssusb)
> > +{
> > + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
> > +
> > + INIT_DELAYED_WORK(&otg_sx->extcon_reg_dwork, extcon_register_dwork);
> > +
> > + if (otg_sx->manual_drd_enabled)
> > + ssusb_debugfs_init(ssusb);
> > +
> > + /* It is enough to delay 1s for waiting for host initialization */
> > + schedule_delayed_work(&otg_sx->extcon_reg_dwork, HZ);
>
> You need to handle this still pending when you are deregistered.
OK.
>
> > +
> > + return 0;
> > +}
> > +
> > +void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb)
> > +{
> > + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
> > +
> > + if (otg_sx->edev) {
> > + extcon_unregister_notifier(otg_sx->edev,
> > + EXTCON_USB, &otg_sx->vbus_nb);
> > + extcon_unregister_notifier(otg_sx->edev,
> > + EXTCON_USB_HOST, &otg_sx->id_nb);
> > + }
> > +
> > + if (otg_sx->manual_drd_enabled)
> > + ssusb_debugfs_exit(ssusb);
> > +}
>
> Could you break this up a bit? It is extensively long a patch?
I afraid I can't break the patch into small ones but also provide
complete functionality at the same time.
Sorry for inconvenience.
>
Thank you very much :-)
> Regards
> Oliver
>
^ permalink raw reply
* Re: [RESEND PATCH net 09/10] net: ethernet: mediatek: use devm_mdiobus_alloc instead of mdiobus_alloc inside mtk_mdio_init
From: sean.wang @ 2016-08-26 6:33 UTC (permalink / raw)
To: john; +Cc: davem, nbd, netdev, linux-mediatek, keyhaede
In-Reply-To: <84142b03-2100-1d56-8f5e-1d39b1748a25@phrozen.org>
On Date: Thu, 25 Aug 2016 15:39:08 +0200, John Crispin wrote:
>Hi Sean, >
>small nitpick inline
>
>On 25/08/2016 12:45, Sean Wang wrote:
>> a lot of parts in the driver uses devm_* APIs to gain benefits from the
....
>> - eth->mii_bus = mdiobus_alloc();
>> + eth->mii_bus = devm_mdiobus_alloc(eth->dev);
>> if (!eth->mii_bus) {
>> err = -ENOMEM;
>> goto err_put_node;
>> @@ -318,18 +318,9 @@ static int mtk_mdio_init(struct mtk_eth *eth)
>>
>> snprintf(eth->mii_bus->id, MII_BUS_ID_SIZE, "%s", mii_np->name);
>> err = of_mdiobus_register(eth->mii_bus, mii_np);
>> - if (err)
>> - goto err_free_bus;
>> - of_node_put(mii_np);
>> -
>> - return 0;
>> -
>> -err_free_bus:
>> - mdiobus_free(eth->mii_bus);
>>
>> err_put_node:
>> of_node_put(mii_np);
>> - eth->mii_bus = NULL;
>> return err;
>
>you might want to rename err to ret. that would make it more readable.
>right now it looks like the code always flows through the error path.
>
> John
>
okay, i will change this from err to ret for readable code
and my thought is using common code provided by the framework helps
to
1) have simplified the code flow as [1] says
2) decrease the risk of incorrect error handling by human
3) only a few drivers used it since it ware proposed on linux 3.16,
so just hope to promote for this.
[1] https://patchwork.ozlabs.org/patch/344093/
>> }
>>
>> @@ -339,8 +330,6 @@ static void mtk_mdio_cleanup(struct mtk_eth *eth)
>> return;
>>
>> mdiobus_unregister(eth->mii_bus);
>> - of_node_put(eth->mii_bus->dev.of_node);
>> - mdiobus_free(eth->mii_bus);
>> }
^ permalink raw reply
* drm/mediatek: fixed the calc method of data rate per lane
From: Jitao Shi @ 2016-08-26 6:10 UTC (permalink / raw)
To: Philipp Zabel, ck.hu-NuS5LvNUpcJWk0Htik3J/w, Matthias Brugger
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
Jitao Shi, Ajay Kumar, Inki Dae, Rahul Sharma, Sean Paul,
Vincent Palatin, Andy Yan, Russell King,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w, Sascha Hauer, yingjoe.chen
Tune dsi frame rate by pixel clock, dsi add some extra signal (i.e. Tlpx,
Ths-prepare, Ths-zero, Ths-trail,Ths-exit) when enter and exit LP mode, this
signal will cause h-time larger than normal and reduce FPS.
Need to multiply a coefficient to offset the extra signal's effect.
coefficient = ((htotal*bpp/lane_number)+Tlpx+Ths_prep+Ths_zero+Ths_trail+
Ths_exit)/(htotal*bpp/lane_number))
Signed-off-by: Jitao Shi <jitao.shi-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
---
drivers/gpu/drm/mediatek/mtk_dsi.c | 111 ++++++++++++++++++++++--------------
1 file changed, 67 insertions(+), 44 deletions(-)
diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 28b2044..506aa22 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -85,28 +85,26 @@
#define LD0_WAKEUP_EN BIT(2)
#define DSI_PHY_TIMECON0 0x110
-#define LPX (0xff << 0)
-#define HS_PRPR (0xff << 8)
-#define HS_ZERO (0xff << 16)
-#define HS_TRAIL (0xff << 24)
+#define LPX(x) ((x) << 0)
+#define HS_PRPR(x) ((x) << 8)
+#define HS_ZERO(x) ((x) << 16)
+#define HS_TRAIL(x) ((x) << 24)
#define DSI_PHY_TIMECON1 0x114
-#define TA_GO (0xff << 0)
-#define TA_SURE (0xff << 8)
-#define TA_GET (0xff << 16)
-#define DA_HS_EXIT (0xff << 24)
+#define TA_GO(x) ((x) << 0)
+#define TA_SURE(x) ((x) << 8)
+#define TA_GET(x) ((x) << 16)
+#define DA_HS_EXIT(x) ((x) << 24)
#define DSI_PHY_TIMECON2 0x118
-#define CONT_DET (0xff << 0)
-#define CLK_ZERO (0xff << 16)
-#define CLK_TRAIL (0xff << 24)
+#define CONT_DET(x) ((x) << 0)
+#define CLK_ZERO(x) ((x) << 16)
+#define CLK_TRAIL(x) ((x) << 24)
#define DSI_PHY_TIMECON3 0x11c
-#define CLK_HS_PRPR (0xff << 0)
-#define CLK_HS_POST (0xff << 8)
-#define CLK_HS_EXIT (0xff << 16)
-
-#define NS_TO_CYCLE(n, c) ((n) / (c) + (((n) % (c)) ? 1 : 0))
+#define CLK_HS_PRPR(x) ((x) << 0)
+#define CLK_HS_POST(x) ((x) << 8)
+#define CLK_HS_EXIT(x) ((x) << 16)
struct phy;
@@ -158,28 +156,14 @@ static void mtk_dsi_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data)
writel((temp & ~mask) | (data & mask), dsi->regs + offset);
}
-static void dsi_phy_timconfig(struct mtk_dsi *dsi)
+static void dsi_phy_timconfig(struct mtk_dsi *dsi, u32 phy_timing0,
+ u32 phy_timing1, u32 phy_timing2,
+ u32 phy_timing3)
{
- u32 timcon0, timcon1, timcon2, timcon3;
- unsigned int ui, cycle_time;
- unsigned int lpx;
-
- ui = 1000 / dsi->data_rate + 0x01;
- cycle_time = 8000 / dsi->data_rate + 0x01;
- lpx = 5;
-
- timcon0 = (8 << 24) | (0xa << 16) | (0x6 << 8) | lpx;
- timcon1 = (7 << 24) | (5 * lpx << 16) | ((3 * lpx) / 2) << 8 |
- (4 * lpx);
- timcon2 = ((NS_TO_CYCLE(0x64, cycle_time) + 0xa) << 24) |
- (NS_TO_CYCLE(0x150, cycle_time) << 16);
- timcon3 = (2 * lpx) << 16 | NS_TO_CYCLE(80 + 52 * ui, cycle_time) << 8 |
- NS_TO_CYCLE(0x40, cycle_time);
-
- writel(timcon0, dsi->regs + DSI_PHY_TIMECON0);
- writel(timcon1, dsi->regs + DSI_PHY_TIMECON1);
- writel(timcon2, dsi->regs + DSI_PHY_TIMECON2);
- writel(timcon3, dsi->regs + DSI_PHY_TIMECON3);
+ writel(phy_timing0, dsi->regs + DSI_PHY_TIMECON0);
+ writel(phy_timing1, dsi->regs + DSI_PHY_TIMECON1);
+ writel(phy_timing2, dsi->regs + DSI_PHY_TIMECON2);
+ writel(phy_timing3, dsi->regs + DSI_PHY_TIMECON3);
}
static void mtk_dsi_enable(struct mtk_dsi *dsi)
@@ -202,19 +186,57 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi)
{
struct device *dev = dsi->dev;
int ret;
+ u64 bit_clock, total_bits;
+ u32 htotal, htotal_bits, bit_per_pixel, overhead_cycles, overhead_bits;
+ u32 phy_timing0, phy_timing1, phy_timing2, phy_timing3;
if (++dsi->refcount != 1)
return 0;
+ phy_timing0 = LPX(5) | HS_PRPR(6) | HS_ZERO(10) | HS_TRAIL(8);
+ phy_timing1 = TA_GO(20) | TA_SURE(7) | TA_GET(25) | DA_HS_EXIT(7);
+ phy_timing2 = CLK_ZERO(38) | CLK_TRAIL(22);
+ phy_timing3 = CLK_HS_PRPR(8) | CLK_HS_POST(21) | CLK_HS_EXIT(10);
+
+ switch (dsi->format) {
+ case MIPI_DSI_FMT_RGB565:
+ bit_per_pixel = 16;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ bit_per_pixel = 18;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ case MIPI_DSI_FMT_RGB888:
+ default:
+ bit_per_pixel = 24;
+ break;
+ }
/**
- * data_rate = (pixel_clock / 1000) * pixel_dipth * mipi_ratio;
- * pixel_clock unit is Khz, data_rata unit is MHz, so need divide 1000.
- * mipi_ratio is mipi clk coefficient for balance the pixel clk in mipi.
- * we set mipi_ratio is 1.05.
+ * data_rate = (pixel_clock) * bit_per_pixel * mipi_ratio / lane_num;
+ * vm.pixelclock is Khz, data_rata unit is Hz, so need to multiply 1000
+ * mipi_ratio is (htotal * byte_per_pixel / lane_num + Tlpx + Ths_prep
+ * + Thstrail + Ths_exit + Ths_zero) /
+ * (htotal * byte_per_pixel /lane_number)
*/
- dsi->data_rate = dsi->vm.pixelclock * 3 * 21 / (1 * 1000 * 10);
+ bit_clock = dsi->vm.pixelclock * 1000 * bit_per_pixel;
+ htotal = dsi->vm.hactive + dsi->vm.hback_porch + dsi->vm.hfront_porch +
+ dsi->vm.hsync_len;
+ htotal_bits = htotal * bit_per_pixel;
+
+ /**
+ * overhead = lpx + hs_prepare + hs_zero + hs_trail + hs_exit
+ */
+ overhead_cycles = (phy_timing0 & 0xff) + (phy_timing0 >> 8 & 0xff) +
+ (phy_timing0 >> 16 & 0xff) +
+ (phy_timing0 >> 24 & 0xff) +
+ (phy_timing1 >> 24 & 0xff);
+ overhead_bits = overhead_cycles * dsi->lanes * 8;
+ total_bits = htotal_bits + overhead_bits;
+
+ dsi->data_rate = DIV_ROUND_UP_ULL(bit_clock * total_bits,
+ htotal_bits * dsi->lanes);
- ret = clk_set_rate(dsi->hs_clk, dsi->data_rate * 1000000);
+ ret = clk_set_rate(dsi->hs_clk, dsi->data_rate);
if (ret < 0) {
dev_err(dev, "Failed to set data rate: %d\n", ret);
goto err_refcount;
@@ -236,7 +258,8 @@ static int mtk_dsi_poweron(struct mtk_dsi *dsi)
mtk_dsi_enable(dsi);
mtk_dsi_reset(dsi);
- dsi_phy_timconfig(dsi);
+ dsi_phy_timconfig(dsi, phy_timing0, phy_timing1, phy_timing2,
+ phy_timing3);
return 0;
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* Re: [RESEND PATCH net 07/10] net: ethernet: mediatek: fix issue of driver removal with interface is up
From: Sean Wang @ 2016-08-26 5:50 UTC (permalink / raw)
To: john-Pj+rj9U5foFAfugRpC6u6w
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, nbd-p3rKhJxN3npAfugRpC6u6w,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
davem-fT/PcQaiUtIeIZ0/mPfg9Q, keyhaede-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <2203871f-d73d-3a39-928d-39862ba5e367-Pj+rj9U5foFAfugRpC6u6w@public.gmane.org>
Date: Thu, 25 Aug 2016 15:35:34 +0200, John Crispin wrote:
>On 25/08/2016 12:44, Sean Wang wrote:
>> 1) mtk_stop() must be called to stop for freeing DMA resources
>> acquired and restoring state changed by mtk_open() when module
>> removal.
>>
>> 2) group clock disabled related function into mtk_hw_deinit which
>> could be reused with others functionality such as the whole ethernet
>> reset that would be posted in the later series of patches.
>>
>
>Hi Sean,
>
>these are 2 unrelated changes so they really need to go into two
>separate patches. i also think that change 1) would better fit into the
>future series making use of that functionality.
>
> John
>
okay. splitting makes sense more
I will leave 1) here
and move 2) into the better place that is one part of the future series
about whole ethernet reset, I will post them after the current series is done
thanks for your effort on reviewing
>> Signed-off-by: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
>> ---
>> drivers/net/ethernet/mediatek/mtk_eth_soc.c | 22 ++++++++++++++++++----
>> 1 file changed, 18 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
>> index 0a4c782..c573475 100644
>> --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
>> +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
>> @@ -1478,6 +1478,16 @@ static int __init mtk_hw_init(struct mtk_eth *eth)
>> return 0;
>> }
>>
>> +static int mtk_hw_deinit(struct mtk_eth *eth)
>> +{
>> + clk_disable_unprepare(eth->clk_esw);
>> + clk_disable_unprepare(eth->clk_gp1);
>> + clk_disable_unprepare(eth->clk_gp2);
>> + clk_disable_unprepare(eth->clk_ethif);
>> +
>> + return 0;
>> +}
>> +
>> static int __init mtk_init(struct net_device *dev)
>> {
>> struct mtk_mac *mac = netdev_priv(dev);
>> @@ -1919,11 +1929,15 @@ err_free_dev:
>> static int mtk_remove(struct platform_device *pdev)
>> {
>> struct mtk_eth *eth = platform_get_drvdata(pdev);
>> + int i;
>>
>> - clk_disable_unprepare(eth->clk_ethif);
>> - clk_disable_unprepare(eth->clk_esw);
>> - clk_disable_unprepare(eth->clk_gp1);
>> - clk_disable_unprepare(eth->clk_gp2);
>> + /* stop all devices to make sure that dma is properly shut down */
>> + for (i = 0; i < MTK_MAC_COUNT; i++) {
>> + if (!eth->netdev[i])
>> + continue;
>> + mtk_stop(eth->netdev[i]);
>> + }
>> + mtk_hw_deinit(eth);
>>
>> netif_napi_del(ð->tx_napi);
>> netif_napi_del(ð->rx_napi);
>>
^ 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