[PATCH 2/2] phy: Add driver for mixel dphy
Sam Ravnborg
sam at ravnborg.org
Fri Jan 25 16:53:55 UTC 2019
Hi Guido.
Patch looks good but a few comments below.
Sam
On Fri, Jan 25, 2019 at 11:14:46AM +0100, Guido Günther wrote:
> This adds support for the Mixel DPHY as found on i.MX8 CPUs but since
> this is an IP core it will likely be found on others in the future. So
> instead of adding this to the nwl host driver make it a generic PHY
> driver.
>
> The driver supports the i.MX8MQ. Support for i.MX8QM and i.MX8QXP can be
> added once the necessary system controller bits are in via
> mixel_dpy_ops.
>
> Signed-off-by: Guido Günther <agx at sigxcpu.org>
> ---
> drivers/phy/Kconfig | 7 +
> drivers/phy/Makefile | 1 +
> drivers/phy/phy-mixel-mipi-dphy.c | 449 ++++++++++++++++++++++++++++++
> 3 files changed, 457 insertions(+)
> create mode 100644 drivers/phy/phy-mixel-mipi-dphy.c
>
> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
> index 250abe290ca1..9195b5876bcc 100644
> --- a/drivers/phy/Kconfig
> +++ b/drivers/phy/Kconfig
> @@ -48,6 +48,13 @@ config PHY_XGENE
> help
> This option enables support for APM X-Gene SoC multi-purpose PHY.
>
> +config PHY_MIXEL_MIPI_DPHY
> + bool
> + depends on OF
> + select GENERIC_PHY
> + select GENERIC_PHY_MIPI_DPHY
> + default ARCH_MXC && ARM64
Is it correct that driver is mandatory if ARCH_MXC is y?
There is no prompt to allow the user to select it.
Or in other words - will all i.MX8 user need it?
> +
> source "drivers/phy/allwinner/Kconfig"
> source "drivers/phy/amlogic/Kconfig"
> source "drivers/phy/broadcom/Kconfig"
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 0d9fddc498a6..264f570b67bf 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -15,6 +15,7 @@ obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
> obj-$(CONFIG_ARCH_RENESAS) += renesas/
> obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
> obj-$(CONFIG_ARCH_TEGRA) += tegra/
> +obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-mixel-mipi-dphy.o
> obj-y += broadcom/ \
> cadence/ \
> freescale/ \
> diff --git a/drivers/phy/phy-mixel-mipi-dphy.c b/drivers/phy/phy-mixel-mipi-dphy.c
> new file mode 100644
> index 000000000000..8a43dab79cee
> --- /dev/null
> +++ b/drivers/phy/phy-mixel-mipi-dphy.c
There is already a PHY named phy-fsl-imx8mq-usb, located in the
freescale subdirectory.
Why locate another imx8 PHY in the top level directory with
another naming convention?
> @@ -0,0 +1,449 @@
> +/*
> + * Copyright 2017 NXP
> + * Copyright 2019 Purism SPC
> + *
> + * SPDX-License-Identifier: GPL-2.0
> + */
SPDX-License-Identifier goes in at first line with //.
It is documented somewhere.
Also, did you double check that GPL 2.0 is correct?
> +
> +/* #define DEBUG 1 */
There is no reference to DEBUG in this file - delete?
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +
> +/* DPHY registers */
> +#define DPHY_PD_DPHY 0x00
> +#define DPHY_M_PRG_HS_PREPARE 0x04
> +#define DPHY_MC_PRG_HS_PREPARE 0x08
> +#define DPHY_M_PRG_HS_ZERO 0x0c
> +#define DPHY_MC_PRG_HS_ZERO 0x10
> +#define DPHY_M_PRG_HS_TRAIL 0x14
> +#define DPHY_MC_PRG_HS_TRAIL 0x18
> +#define DPHY_PD_PLL 0x1c
> +#define DPHY_TST 0x20
> +#define DPHY_CN 0x24
> +#define DPHY_CM 0x28
> +#define DPHY_CO 0x2c
> +#define DPHY_LOCK 0x30
> +#define DPHY_LOCK_BYP 0x34
> +#define DPHY_TX_RCAL 0x38
> +#define DPHY_AUTO_PD_EN 0x3c
> +#define DPHY_RXLPRP 0x40
> +#define DPHY_RXCDRP 0x44
> +
> +#define MBPS(x) ((x) * 1000000)
> +
> +#define DATA_RATE_MAX_SPEED MBPS(1500)
> +#define DATA_RATE_MIN_SPEED MBPS(80)
> +
> +#define CN_BUF 0xcb7a89c0
> +#define CO_BUF 0x63
> +#define CM(x) ( \
> + ((x) < 32)?0xe0|((x)-16) : \
> + ((x) < 64)?0xc0|((x)-32) : \
> + ((x) < 128)?0x80|((x)-64) : \
> + ((x) - 128))
> +#define CN(x) (((x) == 1)?0x1f : (((CN_BUF)>>((x)-1))&0x1f))
> +#define CO(x) ((CO_BUF)>>(8-(x))&0x3)
> +
> +/* PHY power on is LOW_ENABLE */
> +#define PWR_ON 0
> +#define PWR_OFF 1
> +
> +struct mixel_dphy_cfg {
> + u32 cm;
> + u32 cn;
> + u32 co;
> + unsigned long hs_clk_rate;
> + u8 mc_prg_hs_prepare;
> + u8 m_prg_hs_prepare;
> + u8 mc_prg_hs_zero;
> + u8 m_prg_hs_zero;
> + u8 mc_prg_hs_trail;
> + u8 m_prg_hs_trail;
> +};
For the naive reader it would be helpful to spell out the names in a comment.
As I assume the names comes from the data sheet the short names are OK - but
let others know the purpose.
> +
> +struct mixel_dphy_priv;
> +struct mixel_dphy_ops {
> + int (*probe)(struct mixel_dphy_priv *priv);
> + int (*power_on)(struct phy *phy);
> + int (*power_off)(struct phy *phy);
> +};
Consider same argument for all three ops, less suprises.
But then probe() is called before we have a phy, so this may be
the best option.
> +
> +struct mixel_dphy_priv {
> + struct mixel_dphy_cfg cfg;
> + void __iomem *regs;
> + struct clk *phy_ref_clk;
> + struct mutex lock;
> + const struct mixel_dphy_ops *ops;
> +};
Document what the lock protects, or find a better name for the lock to document it
> +
> +/* Find a ratio close to the desired one using continued fraction
> + approximation ending either at exact match or maximum allowed
> + nominator, denominator. */
Use kernel style comments
/*
* Bla bla
* more bla
*/
> +static void get_best_ratio(unsigned long *pnum, unsigned long *pdenom, unsigned max_n, unsigned max_d)
Wrap line to stay below 80 chars.
Use checkpatch to help you sport things like this.
> +{
> + unsigned long a = *pnum;
> + unsigned long b = *pdenom;
> + unsigned long c;
> + unsigned n[] = {0, 1};
> + unsigned d[] = {1, 0};
> + unsigned whole;
> + unsigned i = 1;
> + while (b) {
Add empty line after last local variable.
> + i ^= 1;
> + whole = a / b;
> + n[i] += (n[i ^ 1] * whole);
> + d[i] += (d[i ^ 1] * whole);
> + if ((n[i] > max_n) || (d[i] > max_d)) {
> + i ^= 1;
> + break;
> + }
> + c = a - (b * whole);
> + a = b;
> + b = c;
> + }
> + *pnum = n[i];
> + *pdenom = d[i];
> +}
> +
> +static int mixel_dphy_config_from_opts(struct phy *phy,
> + struct phy_configure_opts_mipi_dphy *dphy_opts,
> + struct mixel_dphy_cfg *cfg)
> +{
Align extra paratmers below the first parameter using tabs and add necessary
spaces.
> + struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent);
> + unsigned long ref_clk = clk_get_rate(priv->phy_ref_clk);
> + int i;
> + unsigned long numerator, denominator, frequency;
> + unsigned step;
> +
> +static int mixel_dphy_ref_power_on(struct phy *phy)
> +{
> + struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
> + u32 lock, timeout;
> + int ret = 0;
> +
> + mutex_lock(&priv->lock);
> + clk_prepare_enable(priv->phy_ref_clk);
> +
> + phy_write(phy, PWR_ON, DPHY_PD_DPHY);
> + phy_write(phy, PWR_ON, DPHY_PD_PLL);
> +
> + timeout = 100;
> + while (!(lock = phy_read(phy, DPHY_LOCK))) {
> + udelay(10);
> + if (--timeout == 0) {
> + dev_err(&phy->dev, "Could not get DPHY lock!\n");
> + mutex_unlock(&priv->lock);
> + return -EINVAL;
> + }
USe goto to have a single exit path where you do mutex_unlock()
> + }
> + mutex_unlock(&priv->lock);
> +
> + return ret;
> +}
> +
> +
> + mutex_lock(&priv->lock);
> +
> + phy_write(phy, 0x00, DPHY_LOCK_BYP);
> + phy_write(phy, 0x01, DPHY_TX_RCAL);
> + phy_write(phy, 0x00, DPHY_AUTO_PD_EN);
> + phy_write(phy, 0x01, DPHY_RXLPRP);
> + phy_write(phy, 0x01, DPHY_RXCDRP);
> + phy_write(phy, 0x25, DPHY_TST);
> +
> + mixel_phy_set_hs_timings(phy);
> + ret = mixel_dphy_set_pll_params(phy);
> + if (ret < 0) {
> + mutex_unlock(&priv->lock);
> + return ret;
> + }
USe goto to have a single exit path where you do mutex_unlock()
> +
> + mutex_unlock(&priv->lock);
> +
> + return 0;
> +}
> +
> +
> +/*
> + * This is the reference implementation of DPHY hooks. Specific integration of
> + * this IP may have to re-implement some of them depending on how they decided
> + * to wire things in the SoC.
> + */
> +static const struct mixel_dphy_ops mixel_dphy_ref_ops = {
> + .power_on = mixel_dphy_ref_power_on,
> + .power_off = mixel_dphy_ref_power_off,
> +};
> +
> +static const struct phy_ops mixel_dphy_ops = {
> + .power_on = mixel_dphy_power_on,
> + .power_off = mixel_dphy_power_off,
> + .configure = mixel_dphy_configure,
> + .validate = mixel_dphy_validate,
> + .owner = THIS_MODULE,
> +};
This is confusing.
We have struct mixel_dphy_ops => mixel_dphy_ref_ops
And then struct phy_ops => mixel_dphy_ops
So reading this there are to uses of mixel_dphy_ops,
one is a struct, and another is an instance of another type.
Try to find a niming scheme that is less confusing.
> +
> +static const struct of_device_id mixel_dphy_of_match[] = {
> + { .compatible = "mixel,imx8mq-mipi-dphy", .data = &mixel_dphy_ref_ops },
> + { /* sentinel */ },
> +};
Multi-line to keep line shorter?
> +MODULE_DEVICE_TABLE(of, mixel_dphy_of_match);
> +
> +static int mixel_dphy_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct phy_provider *phy_provider;
> + struct mixel_dphy_priv *priv;
> + struct resource *res;
> + struct phy *phy;
> + int ret;
> +
> + if (!np)
> + return -ENODEV;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->ops = of_device_get_match_data(&pdev->dev);
> + if (!priv->ops)
> + return -EINVAL;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + return -ENODEV;
> +
> + priv->regs = devm_ioremap(dev, res->start, SZ_256);
> + if (IS_ERR(priv->regs))
> + return PTR_ERR(priv->regs);
> +
> + priv->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref");
> + if (IS_ERR(priv->phy_ref_clk)) {
> + dev_err(dev, "No phy_ref clock found");
> + return PTR_ERR(priv->phy_ref_clk);
> + }
> + dev_dbg(dev, "phy_ref clock rate: %lu", clk_get_rate(priv->phy_ref_clk));
> +
> + mutex_init(&priv->lock);
> + dev_set_drvdata(dev, priv);
> +
> + if (priv->ops->probe) {
> + ret = priv->ops->probe(priv);
> + if (ret)
> + return ret;
> + }
> +
> + phy = devm_phy_create(dev, np, &mixel_dphy_ops);
> + if (IS_ERR(phy)) {
> + dev_err(dev, "Failed to create phy %ld\n", PTR_ERR(phy));
> + return PTR_ERR(phy);
> + }
> + phy_set_drvdata(phy, priv);
> +
> + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
> +
> + return PTR_ERR_OR_ZERO(phy_provider);
> +}
> +
> +static struct platform_driver mixel_dphy_driver = {
> + .probe = mixel_dphy_probe,
> + .driver = {
> + .name = "mixel-mipi-dphy",
> + .of_match_table = mixel_dphy_of_match,
> + }
> +};
> +module_platform_driver(mixel_dphy_driver);
> +
> +MODULE_AUTHOR("NXP Semiconductor");
> +MODULE_DESCRIPTION("Mixel MIPI-DSI PHY driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.20.1
>
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
More information about the dri-devel
mailing list