linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY
@ 2020-04-16 18:57 Jernej Skrabec
  2020-04-16 18:57 ` [RFC PATCH 1/4] mfd: Add support for AC200 Jernej Skrabec
                   ` (4 more replies)
  0 siblings, 5 replies; 16+ messages in thread
From: Jernej Skrabec @ 2020-04-16 18:57 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, hkallweit1
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

This is attempt to support Ethernet PHY on AC200 MFD chip. I'm sending
this as RFC because I stumbled on a problem how to properly describe it
in DT. Proper DT documentation will be added later, once DT issue is
solved.

Before Ethernet PHY can be actually used, few things must happen:
1. 24 MHz clock must be enabled and connected to input pin of this
   chip. In this case, PWM is set to generate 24 MHz signal with 50%
   duty cycle.
2. Chip must be put out of reset through I2C
3. Ethernet PHY must be enabled and configured through I2C

All above suggest that AC200 chip must be child node of I2C and Ethernet
PHY child node of AC200 node. This is done in patch 3.

However, mdio bus binding also requires that ethernet PHY is child node
of mdio bus node which can't be, because it's already child node of
AC200 MFD node.

Currently I'm using workaround to have another PHY defined in mdio bus
node as can be seen in patch 4. Then, with careful module loading order,
I make sure that ethernet controller driver is loaded last, after AC200
ethernet PHY driver is loaded. But that's fragile and not acceptable.

Suggestions how to solve that are highly appreciated.

One possible solution is that mdio bus node would contain phandle to
PHY node instead of having actual PHY child node.

Documentation of this chip can be found at
http://linux-sunxi.org/File:AC200_Datasheet_V1.1.pdf

Note that in this case, AC200 IC is copackaged with Allwinner H6 SoC and
all connections between them are internal. So, for example, PWM is the
only way to provide 24 MHz clock to this chip.

Best regards,
Jernej

Jernej Skrabec (4):
  mfd: Add support for AC200
  net: phy: Add support for AC200 EPHY
  arm64: dts: allwinner: h6: Add AC200 EPHY related nodes
  arm64: dts: allwinner: h6: tanix-tx6: Enable ethernet

 .../dts/allwinner/sun50i-h6-tanix-tx6.dts     |  32 +++
 arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi  |  63 ++++++
 drivers/mfd/Kconfig                           |   9 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/ac200.c                           | 188 ++++++++++++++++
 drivers/net/phy/Kconfig                       |   7 +
 drivers/net/phy/Makefile                      |   1 +
 drivers/net/phy/ac200.c                       | 206 +++++++++++++++++
 include/linux/mfd/ac200.h                     | 210 ++++++++++++++++++
 9 files changed, 717 insertions(+)
 create mode 100644 drivers/mfd/ac200.c
 create mode 100644 drivers/net/phy/ac200.c
 create mode 100644 include/linux/mfd/ac200.h

-- 
2.26.0


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [RFC PATCH 1/4] mfd: Add support for AC200
  2020-04-16 18:57 [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Jernej Skrabec
@ 2020-04-16 18:57 ` Jernej Skrabec
  2020-04-24  8:05   ` Lee Jones
  2020-04-16 18:57 ` [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY Jernej Skrabec
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 16+ messages in thread
From: Jernej Skrabec @ 2020-04-16 18:57 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, hkallweit1
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

This adds support for AC200 multi functional IC. It can be packaged
standalone or copackaged with SoC like Allwinner H6.

It has analog audio codec, CVBS encoder, RTC and Fast Ethernet PHY.
Documentation also mention eFuses, but it seems that it's not used in
copackaged variant.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 drivers/mfd/Kconfig       |   9 ++
 drivers/mfd/Makefile      |   1 +
 drivers/mfd/ac200.c       | 188 ++++++++++++++++++++++++++++++++++
 include/linux/mfd/ac200.h | 210 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 408 insertions(+)
 create mode 100644 drivers/mfd/ac200.c
 create mode 100644 include/linux/mfd/ac200.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 0a59249198d3..1d6b7f3ae193 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -178,6 +178,15 @@ config MFD_AC100
 	  This driver include only the core APIs. You have to select individual
 	  components like codecs or RTC under the corresponding menus.
 
+config MFD_AC200
+	tristate "X-Powers AC200"
+	select MFD_CORE
+	depends on I2C
+	help
+	  If you say Y here you get support for the X-Powers AC200 IC.
+	  This driver include only the core APIs. You have to select individual
+	  components like Ethernet PHY or RTC under the corresponding menus.
+
 config MFD_AXP20X
 	tristate
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index f935d10cbf0f..a20407290d6f 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -142,6 +142,7 @@ obj-$(CONFIG_MFD_DA9052_SPI)	+= da9052-spi.o
 obj-$(CONFIG_MFD_DA9052_I2C)	+= da9052-i2c.o
 
 obj-$(CONFIG_MFD_AC100)		+= ac100.o
+obj-$(CONFIG_MFD_AC200)		+= ac200.o
 obj-$(CONFIG_MFD_AXP20X)	+= axp20x.o
 obj-$(CONFIG_MFD_AXP20X_I2C)	+= axp20x-i2c.o
 obj-$(CONFIG_MFD_AXP20X_RSB)	+= axp20x-rsb.o
diff --git a/drivers/mfd/ac200.c b/drivers/mfd/ac200.c
new file mode 100644
index 000000000000..cf2710b84879
--- /dev/null
+++ b/drivers/mfd/ac200.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for X-Powers' AC200 IC
+ *
+ * The AC200 is a chip which is co-packaged with Allwinner H6 SoC and
+ * includes analog audio codec, analog TV encoder, ethernet PHY, eFuse
+ * and RTC.
+ *
+ * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ac200.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+/* Interrupts */
+#define AC200_IRQ_RTC  0
+#define AC200_IRQ_EPHY 1
+#define AC200_IRQ_TVE  2
+
+/* IRQ enable register */
+#define AC200_SYS_IRQ_ENABLE_OUT_EN BIT(15)
+#define AC200_SYS_IRQ_ENABLE_RTC    BIT(12)
+#define AC200_SYS_IRQ_ENABLE_EPHY   BIT(8)
+#define AC200_SYS_IRQ_ENABLE_TVE    BIT(4)
+
+static const struct regmap_range_cfg ac200_range_cfg[] = {
+	{
+		.range_min = AC200_SYS_VERSION,
+		.range_max = AC200_IC_CHARA1,
+		.selector_reg = AC200_TWI_REG_ADDR_H,
+		.selector_mask = 0xff,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 256,
+	}
+};
+
+static const struct regmap_config ac200_regmap_config = {
+	.reg_bits	= 8,
+	.val_bits	= 16,
+	.ranges		= ac200_range_cfg,
+	.num_ranges	= ARRAY_SIZE(ac200_range_cfg),
+	.max_register	= AC200_IC_CHARA1,
+};
+
+static const struct regmap_irq ac200_regmap_irqs[] = {
+	REGMAP_IRQ_REG(AC200_IRQ_RTC,  0, AC200_SYS_IRQ_ENABLE_RTC),
+	REGMAP_IRQ_REG(AC200_IRQ_EPHY, 0, AC200_SYS_IRQ_ENABLE_EPHY),
+	REGMAP_IRQ_REG(AC200_IRQ_TVE,  0, AC200_SYS_IRQ_ENABLE_TVE),
+};
+
+static const struct regmap_irq_chip ac200_regmap_irq_chip = {
+	.name			= "ac200_irq_chip",
+	.status_base		= AC200_SYS_IRQ_STATUS,
+	.mask_base		= AC200_SYS_IRQ_ENABLE,
+	.mask_invert		= true,
+	.irqs			= ac200_regmap_irqs,
+	.num_irqs		= ARRAY_SIZE(ac200_regmap_irqs),
+	.num_regs		= 1,
+};
+
+static const struct resource ephy_resource[] = {
+	DEFINE_RES_IRQ(AC200_IRQ_EPHY),
+};
+
+static const struct mfd_cell ac200_cells[] = {
+	{
+		.name		= "ac200-ephy",
+		.num_resources	= ARRAY_SIZE(ephy_resource),
+		.resources	= ephy_resource,
+		.of_compatible	= "x-powers,ac200-ephy",
+	},
+};
+
+static int ac200_i2c_probe(struct i2c_client *i2c,
+			   const struct i2c_device_id *id)
+{
+	struct device *dev = &i2c->dev;
+	struct ac200_dev *ac200;
+	int ret;
+
+	ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
+	if (!ac200)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, ac200);
+
+	ac200->regmap = devm_regmap_init_i2c(i2c, &ac200_regmap_config);
+	if (IS_ERR(ac200->regmap)) {
+		dev_err(dev, "regmap init failed\n");
+		return PTR_ERR(ac200->regmap);
+	}
+
+	ac200->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(ac200->clk)) {
+		dev_err(dev, "Can't obtain the clock!\n");
+		return PTR_ERR(ac200->clk);
+	}
+
+	ret = clk_prepare_enable(ac200->clk);
+	if (ret)
+		return ret;
+
+	/* do a reset to put chip in a known state */
+
+	ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+	if (ret)
+		goto err_free_clk;
+
+	ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1);
+	if (ret)
+		goto err_free_clk;
+
+	/* enable interrupt pin */
+
+	ret = regmap_write(ac200->regmap, AC200_SYS_IRQ_ENABLE,
+			   AC200_SYS_IRQ_ENABLE_OUT_EN);
+	if (ret)
+		goto err_free_clk;
+
+	ret = regmap_add_irq_chip(ac200->regmap, i2c->irq, IRQF_ONESHOT, 0,
+				  &ac200_regmap_irq_chip, &ac200->regmap_irqc);
+	if (ret)
+		goto err_free_clk;
+
+	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
+				   ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
+	if (ret) {
+		dev_err(dev, "failed to add MFD devices: %d\n", ret);
+		goto err_del_irq_chip;
+	}
+
+	return 0;
+
+err_del_irq_chip:
+	regmap_del_irq_chip(i2c->irq, ac200->regmap_irqc);
+err_free_clk:
+	clk_disable_unprepare(ac200->clk);
+
+	return ret;
+}
+
+static int ac200_i2c_remove(struct i2c_client *i2c)
+{
+	struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
+
+	/* put chip in reset state */
+	regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+
+	mfd_remove_devices(&i2c->dev);
+	regmap_del_irq_chip(i2c->irq, ac200->regmap_irqc);
+
+	clk_disable_unprepare(ac200->clk);
+
+	return 0;
+}
+
+static const struct i2c_device_id ac200_ids[] = {
+	{ "ac200", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_ids);
+
+static const struct of_device_id ac200_of_match[] = {
+	{ .compatible = "x-powers,ac200" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static struct i2c_driver ac200_i2c_driver = {
+	.driver = {
+		.name	= "ac200",
+		.of_match_table	= of_match_ptr(ac200_of_match),
+	},
+	.probe	= ac200_i2c_probe,
+	.remove = ac200_i2c_remove,
+	.id_table = ac200_ids,
+};
+module_i2c_driver(ac200_i2c_driver);
+
+MODULE_DESCRIPTION("MFD core driver for AC200");
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/ac200.h b/include/linux/mfd/ac200.h
new file mode 100644
index 000000000000..f75baf0d910c
--- /dev/null
+++ b/include/linux/mfd/ac200.h
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AC200 register list
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#ifndef __LINUX_MFD_AC200_H
+#define __LINUX_MFD_AC200_H
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+/* interface registers (can be accessed from any page) */
+#define AC200_TWI_CHANGE_TO_RSB		0x3E
+#define AC200_TWI_PAD_DELAY		0xC4
+#define AC200_TWI_REG_ADDR_H		0xFE
+
+/* General registers */
+#define AC200_SYS_VERSION		0x0000
+#define AC200_SYS_CONTROL		0x0002
+#define AC200_SYS_IRQ_ENABLE		0x0004
+#define AC200_SYS_IRQ_STATUS		0x0006
+#define AC200_SYS_CLK_CTL		0x0008
+#define AC200_SYS_DLDO_OSC_CTL		0x000A
+#define AC200_SYS_PLL_CTL0		0x000C
+#define AC200_SYS_PLL_CTL1		0x000E
+#define AC200_SYS_AUDIO_CTL0		0x0010
+#define AC200_SYS_AUDIO_CTL1		0x0012
+#define AC200_SYS_EPHY_CTL0		0x0014
+#define AC200_SYS_EPHY_CTL1		0x0016
+#define AC200_SYS_TVE_CTL0		0x0018
+#define AC200_SYS_TVE_CTL1		0x001A
+
+/* Audio Codec registers */
+#define AC200_AC_SYS_CLK_CTL		0x2000
+#define AC200_SYS_MOD_RST		0x2002
+#define AC200_SYS_SAMP_CTL		0x2004
+#define AC200_I2S_CTL			0x2100
+#define AC200_I2S_CLK			0x2102
+#define AC200_I2S_FMT0			0x2104
+#define AC200_I2S_FMT1			0x2108
+#define AC200_I2S_MIX_SRC		0x2114
+#define AC200_I2S_MIX_GAIN		0x2116
+#define AC200_I2S_DACDAT_DVC		0x2118
+#define AC200_I2S_ADCDAT_DVC		0x211A
+#define AC200_AC_DAC_DPC		0x2200
+#define AC200_AC_DAC_MIX_SRC		0x2202
+#define AC200_AC_DAC_MIX_GAIN		0x2204
+#define AC200_DACA_OMIXER_CTRL		0x2220
+#define AC200_OMIXER_SR			0x2222
+#define AC200_LINEOUT_CTRL		0x2224
+#define AC200_AC_ADC_DPC		0x2300
+#define AC200_MBIAS_CTRL		0x2310
+#define AC200_ADC_MIC_CTRL		0x2320
+#define AC200_ADCMIXER_SR		0x2322
+#define AC200_ANALOG_TUNING0		0x232A
+#define AC200_ANALOG_TUNING1		0x232C
+#define AC200_AC_AGC_SEL		0x2480
+#define AC200_ADC_DAPLCTRL		0x2500
+#define AC200_ADC_DAPRCTRL		0x2502
+#define AC200_ADC_DAPLSTA		0x2504
+#define AC200_ADC_DAPRSTA		0x2506
+#define AC200_ADC_DAPLTL		0x2508
+#define AC200_ADC_DAPRTL		0x250A
+#define AC200_ADC_DAPLHAC		0x250C
+#define AC200_ADC_DAPLLAC		0x250E
+#define AC200_ADC_DAPRHAC		0x2510
+#define AC200_ADC_DAPRLAC		0x2512
+#define AC200_ADC_DAPLDT		0x2514
+#define AC200_ADC_DAPLAT		0x2516
+#define AC200_ADC_DAPRDT		0x2518
+#define AC200_ADC_DAPRAT		0x251A
+#define AC200_ADC_DAPNTH		0x251C
+#define AC200_ADC_DAPLHNAC		0x251E
+#define AC200_ADC_DAPLLNAC		0x2520
+#define AC200_ADC_DAPRHNAC		0x2522
+#define AC200_ADC_DAPRLNAC		0x2524
+#define AC200_AC_DAPHHPFC		0x2526
+#define AC200_AC_DAPLHPFC		0x2528
+#define AC200_AC_DAPOPT			0x252A
+#define AC200_AC_DAC_DAPCTRL		0x3000
+#define AC200_AC_DRC_HHPFC		0x3002
+#define AC200_AC_DRC_LHPFC		0x3004
+#define AC200_AC_DRC_CTRL		0x3006
+#define AC200_AC_DRC_LPFHAT		0x3008
+#define AC200_AC_DRC_LPFLAT		0x300A
+#define AC200_AC_DRC_RPFHAT		0x300C
+#define AC200_AC_DRC_RPFLAT		0x300E
+#define AC200_AC_DRC_LPFHRT		0x3010
+#define AC200_AC_DRC_LPFLRT		0x3012
+#define AC200_AC_DRC_RPFHRT		0x3014
+#define AC200_AC_DRC_RPFLRT		0x3016
+#define AC200_AC_DRC_LRMSHAT		0x3018
+#define AC200_AC_DRC_LRMSLAT		0x301A
+#define AC200_AC_DRC_RRMSHAT		0x301C
+#define AC200_AC_DRC_RRMSLAT		0x301E
+#define AC200_AC_DRC_HCT		0x3020
+#define AC200_AC_DRC_LCT		0x3022
+#define AC200_AC_DRC_HKC		0x3024
+#define AC200_AC_DRC_LKC		0x3026
+#define AC200_AC_DRC_HOPC		0x3028
+#define AC200_AC_DRC_LOPC		0x302A
+#define AC200_AC_DRC_HLT		0x302C
+#define AC200_AC_DRC_LLT		0x302E
+#define AC200_AC_DRC_HKI		0x3030
+#define AC200_AC_DRC_LKI		0x3032
+#define AC200_AC_DRC_HOPL		0x3034
+#define AC200_AC_DRC_LOPL		0x3036
+#define AC200_AC_DRC_HET		0x3038
+#define AC200_AC_DRC_LET		0x303A
+#define AC200_AC_DRC_HKE		0x303C
+#define AC200_AC_DRC_LKE		0x303E
+#define AC200_AC_DRC_HOPE		0x3040
+#define AC200_AC_DRC_LOPE		0x3042
+#define AC200_AC_DRC_HKN		0x3044
+#define AC200_AC_DRC_LKN		0x3046
+#define AC200_AC_DRC_SFHAT		0x3048
+#define AC200_AC_DRC_SFLAT		0x304A
+#define AC200_AC_DRC_SFHRT		0x304C
+#define AC200_AC_DRC_SFLRT		0x304E
+#define AC200_AC_DRC_MXGHS		0x3050
+#define AC200_AC_DRC_MXGLS		0x3052
+#define AC200_AC_DRC_MNGHS		0x3054
+#define AC200_AC_DRC_MNGLS		0x3056
+#define AC200_AC_DRC_EPSHC		0x3058
+#define AC200_AC_DRC_EPSLC		0x305A
+#define AC200_AC_DRC_HPFHGAIN		0x305E
+#define AC200_AC_DRC_HPFLGAIN		0x3060
+#define AC200_AC_DRC_BISTCR		0x3100
+#define AC200_AC_DRC_BISTST		0x3102
+
+/* TVE registers */
+#define AC200_TVE_CTL0			0x4000
+#define AC200_TVE_CTL1			0x4002
+#define AC200_TVE_MOD0			0x4004
+#define AC200_TVE_MOD1			0x4006
+#define AC200_TVE_DAC_CFG0		0x4008
+#define AC200_TVE_DAC_CFG1		0x400A
+#define AC200_TVE_YC_DELAY		0x400C
+#define AC200_TVE_YC_FILTER		0x400E
+#define AC200_TVE_BURST_FRQ0		0x4010
+#define AC200_TVE_BURST_FRQ1		0x4012
+#define AC200_TVE_FRONT_PORCH		0x4014
+#define AC200_TVE_BACK_PORCH		0x4016
+#define AC200_TVE_TOTAL_LINE		0x401C
+#define AC200_TVE_FIRST_ACTIVE		0x401E
+#define AC200_TVE_BLACK_LEVEL		0x4020
+#define AC200_TVE_BLANK_LEVEL		0x4022
+#define AC200_TVE_PLUG_EN		0x4030
+#define AC200_TVE_PLUG_IRQ_EN		0x4032
+#define AC200_TVE_PLUG_IRQ_STA		0x4034
+#define AC200_TVE_PLUG_STA		0x4038
+#define AC200_TVE_PLUG_DEBOUNCE		0x4040
+#define AC200_TVE_DAC_TEST		0x4042
+#define AC200_TVE_PLUG_PULSE_LEVEL	0x40F4
+#define AC200_TVE_PLUG_PULSE_START	0x40F8
+#define AC200_TVE_PLUG_PULSE_PERIOD	0x40FA
+#define AC200_TVE_IF_CTL		0x5000
+#define AC200_TVE_IF_TIM0		0x5008
+#define AC200_TVE_IF_TIM1		0x500A
+#define AC200_TVE_IF_TIM2		0x500C
+#define AC200_TVE_IF_TIM3		0x500E
+#define AC200_TVE_IF_SYNC0		0x5010
+#define AC200_TVE_IF_SYNC1		0x5012
+#define AC200_TVE_IF_SYNC2		0x5014
+#define AC200_TVE_IF_TIM4		0x5016
+#define AC200_TVE_IF_STATUS		0x5018
+
+/* EPHY registers */
+#define AC200_EPHY_CTL			0x6000
+#define AC200_EPHY_BIST			0x6002
+
+/* eFuse registers (0x8000 - 0x9FFF, layout unknown) */
+
+/* RTC registers */
+#define AC200_LOSC_CTRL0		0xA000
+#define AC200_LOSC_CTRL1		0xA002
+#define AC200_LOSC_AUTO_SWT_STA		0xA004
+#define AC200_INTOSC_CLK_PRESCAL	0xA008
+#define AC200_RTC_YY_MM_DD0		0xA010
+#define AC200_RTC_YY_MM_DD1		0xA012
+#define AC200_RTC_HH_MM_SS0		0xA014
+#define AC200_RTC_HH_MM_SS1		0xA016
+#define AC200_ALARM0_CUR_VLU0		0xA024
+#define AC200_ALARM0_CUR_VLU1		0xA026
+#define AC200_ALARM0_ENABLE		0xA028
+#define AC200_ALARM0_IRQ_EN		0xA02C
+#define AC200_ALARM0_IRQ_STA		0xA030
+#define AC200_ALARM1_WK_HH_MM_SS0	0xA040
+#define AC200_ALARM1_WK_HH_MM_SS1	0xA042
+#define AC200_ALARM1_ENABLE		0xA044
+#define AC200_ALARM1_IRQ_EN		0xA048
+#define AC200_ALARM1_IRQ_STA		0xA04C
+#define AC200_ALARM_CONFIG		0xA050
+#define AC200_LOSC_OUT_GATING		0xA060
+#define AC200_GP_DATA(x)		(0xA100 + (x) * 2)
+#define AC200_RTC_DEB			0xA170
+#define AC200_GPL_HOLD_OUTPUT		0xA180
+#define AC200_VDD_RTC			0xA190
+#define AC200_IC_CHARA0			0xA1F0
+#define AC200_IC_CHARA1			0xA1F2
+
+struct ac200_dev {
+	struct clk			*clk;
+	struct regmap			*regmap;
+	struct regmap_irq_chip_data	*regmap_irqc;
+};
+
+#endif /* __LINUX_MFD_AC200_H */
-- 
2.26.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-16 18:57 [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Jernej Skrabec
  2020-04-16 18:57 ` [RFC PATCH 1/4] mfd: Add support for AC200 Jernej Skrabec
@ 2020-04-16 18:57 ` Jernej Skrabec
  2020-04-16 19:18   ` Florian Fainelli
  2020-04-16 20:18   ` Heiner Kallweit
  2020-04-16 18:57 ` [RFC PATCH 3/4] arm64: dts: allwinner: h6: Add AC200 EPHY related nodes Jernej Skrabec
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 16+ messages in thread
From: Jernej Skrabec @ 2020-04-16 18:57 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, hkallweit1
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 drivers/net/phy/Kconfig  |   7 ++
 drivers/net/phy/Makefile |   1 +
 drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 214 insertions(+)
 create mode 100644 drivers/net/phy/ac200.c

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 3fa33d27eeba..16af69f69eaf 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -288,6 +288,13 @@ config ADIN_PHY
 	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
 	    Ethernet PHY
 
+config AC200_PHY
+	tristate "AC200 EPHY"
+	depends on NVMEM
+	depends on OF
+	help
+	  Fast ethernet PHY as found in X-Powers AC200 multi-function device.
+
 config AMD_PHY
 	tristate "AMD PHYs"
 	---help---
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 2f5c7093a65b..b0c5b91900fa 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
 sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
 obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
 
+obj-$(CONFIG_AC200_PHY)		+= ac200.o
 obj-$(CONFIG_ADIN_PHY)		+= adin.o
 obj-$(CONFIG_AMD_PHY)		+= amd.o
 aquantia-objs			+= aquantia_main.o
diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
new file mode 100644
index 000000000000..3d7856ff8f91
--- /dev/null
+++ b/drivers/net/phy/ac200.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ * Driver for AC200 Ethernet PHY
+ *
+ * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/ac200.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#define AC200_EPHY_ID			0x00441400
+#define AC200_EPHY_ID_MASK		0x0ffffff0
+
+/* macros for system ephy control 0 register */
+#define AC200_EPHY_RESET_INVALID	BIT(0)
+#define AC200_EPHY_SYSCLK_GATING	BIT(1)
+
+/* macros for system ephy control 1 register */
+#define AC200_EPHY_E_EPHY_MII_IO_EN	BIT(0)
+#define AC200_EPHY_E_LNK_LED_IO_EN	BIT(1)
+#define AC200_EPHY_E_SPD_LED_IO_EN	BIT(2)
+#define AC200_EPHY_E_DPX_LED_IO_EN	BIT(3)
+
+/* macros for ephy control register */
+#define AC200_EPHY_SHUTDOWN		BIT(0)
+#define AC200_EPHY_LED_POL		BIT(1)
+#define AC200_EPHY_CLK_SEL		BIT(2)
+#define AC200_EPHY_ADDR(x)		(((x) & 0x1F) << 4)
+#define AC200_EPHY_XMII_SEL		BIT(11)
+#define AC200_EPHY_CALIB(x)		(((x) & 0xF) << 12)
+
+struct ac200_ephy_dev {
+	struct phy_driver	*ephy;
+	struct regmap		*regmap;
+};
+
+static char *ac200_phy_name = "AC200 EPHY";
+
+static int ac200_ephy_config_init(struct phy_device *phydev)
+{
+	const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
+	unsigned int value;
+	int ret;
+
+	phy_write(phydev, 0x1f, 0x0100);	/* Switch to Page 1 */
+	phy_write(phydev, 0x12, 0x4824);	/* Disable APS */
+
+	phy_write(phydev, 0x1f, 0x0200);	/* Switch to Page 2 */
+	phy_write(phydev, 0x18, 0x0000);	/* PHYAFE TRX optimization */
+
+	phy_write(phydev, 0x1f, 0x0600);	/* Switch to Page 6 */
+	phy_write(phydev, 0x14, 0x708f);	/* PHYAFE TX optimization */
+	phy_write(phydev, 0x13, 0xF000);	/* PHYAFE RX optimization */
+	phy_write(phydev, 0x15, 0x1530);
+
+	phy_write(phydev, 0x1f, 0x0800);	/* Switch to Page 6 */
+	phy_write(phydev, 0x18, 0x00bc);	/* PHYAFE TRX optimization */
+
+	phy_write(phydev, 0x1f, 0x0100);	/* switch to page 1 */
+	phy_clear_bits(phydev, 0x17, BIT(3));	/* disable intelligent IEEE */
+
+	/* next two blocks disable 802.3az IEEE */
+	phy_write(phydev, 0x1f, 0x0200);	/* switch to page 2 */
+	phy_write(phydev, 0x18, 0x0000);
+
+	phy_write(phydev, 0x1f, 0x0000);	/* switch to page 0 */
+	phy_clear_bits_mmd(phydev, 0x7, 0x3c, BIT(1));
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RMII)
+		value = AC200_EPHY_XMII_SEL;
+	else
+		value = 0;
+
+	ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL,
+				 AC200_EPHY_XMII_SEL, value);
+	if (ret)
+		return ret;
+
+	/* FIXME: This is H6 specific */
+	phy_set_bits(phydev, 0x13, BIT(12));
+
+	return 0;
+}
+
+static int ac200_ephy_probe(struct platform_device *pdev)
+{
+	struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent);
+	struct device *dev = &pdev->dev;
+	struct ac200_ephy_dev *priv;
+	struct nvmem_cell *calcell;
+	struct phy_driver *ephy;
+	u16 *caldata, calib;
+	size_t callen;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
+	if (!ephy)
+		return -ENOMEM;
+
+	calcell = devm_nvmem_cell_get(dev, "calibration");
+	if (IS_ERR(calcell)) {
+		dev_err(dev, "Unable to find calibration data!\n");
+		return PTR_ERR(calcell);
+	}
+
+	caldata = nvmem_cell_read(calcell, &callen);
+	if (IS_ERR(caldata)) {
+		dev_err(dev, "Unable to read calibration data!\n");
+		return PTR_ERR(caldata);
+	}
+
+	if (callen != 2) {
+		dev_err(dev, "Calibration data has wrong length: 2 != %zu\n",
+			callen);
+		kfree(caldata);
+		return -EINVAL;
+	}
+
+	calib = *caldata + 3;
+	kfree(caldata);
+
+	ephy->phy_id = AC200_EPHY_ID;
+	ephy->phy_id_mask = AC200_EPHY_ID_MASK;
+	ephy->name = ac200_phy_name;
+	ephy->driver_data = priv;
+	ephy->soft_reset = genphy_soft_reset;
+	ephy->config_init = ac200_ephy_config_init;
+	ephy->suspend = genphy_suspend;
+	ephy->resume = genphy_resume;
+
+	priv->ephy = ephy;
+	priv->regmap = ac200->regmap;
+	platform_set_drvdata(pdev, priv);
+
+	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL0,
+			   AC200_EPHY_RESET_INVALID |
+			   AC200_EPHY_SYSCLK_GATING);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL1,
+			   AC200_EPHY_E_EPHY_MII_IO_EN |
+			   AC200_EPHY_E_LNK_LED_IO_EN |
+			   AC200_EPHY_E_SPD_LED_IO_EN |
+			   AC200_EPHY_E_DPX_LED_IO_EN);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(ac200->regmap, AC200_EPHY_CTL,
+			   AC200_EPHY_LED_POL |
+			   AC200_EPHY_CLK_SEL |
+			   AC200_EPHY_ADDR(1) |
+			   AC200_EPHY_CALIB(calib));
+	if (ret)
+		return ret;
+
+	ret = phy_driver_register(priv->ephy, THIS_MODULE);
+	if (ret) {
+		dev_err(dev, "Unable to register phy\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ac200_ephy_remove(struct platform_device *pdev)
+{
+	struct ac200_ephy_dev *priv = platform_get_drvdata(pdev);
+
+	phy_driver_unregister(priv->ephy);
+
+	regmap_write(priv->regmap, AC200_EPHY_CTL, AC200_EPHY_SHUTDOWN);
+	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1, 0);
+	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0, 0);
+
+	return 0;
+}
+
+static const struct of_device_id ac200_ephy_match[] = {
+	{ .compatible = "x-powers,ac200-ephy" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_ephy_match);
+
+static struct platform_driver ac200_ephy_driver = {
+	.probe		= ac200_ephy_probe,
+	.remove		= ac200_ephy_remove,
+	.driver		= {
+		.name		= "ac200-ephy",
+		.of_match_table	= ac200_ephy_match,
+	},
+};
+module_platform_driver(ac200_ephy_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_DESCRIPTION("AC200 Ethernet PHY driver");
+MODULE_LICENSE("GPL");
-- 
2.26.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 3/4] arm64: dts: allwinner: h6: Add AC200 EPHY related nodes
  2020-04-16 18:57 [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Jernej Skrabec
  2020-04-16 18:57 ` [RFC PATCH 1/4] mfd: Add support for AC200 Jernej Skrabec
  2020-04-16 18:57 ` [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY Jernej Skrabec
@ 2020-04-16 18:57 ` Jernej Skrabec
  2020-04-16 18:57 ` [RFC PATCH 4/4] arm64: dts: allwinner: h6: tanix-tx6: Enable ethernet Jernej Skrabec
  2020-04-16 21:54 ` [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Andrew Lunn
  4 siblings, 0 replies; 16+ messages in thread
From: Jernej Skrabec @ 2020-04-16 18:57 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, hkallweit1
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

Allwinner H6 contains copackaged AC200 multi functional IC which takes
care for analog audio, CVBS output, (another) RTC and Ethernet PHY.

Add support for Ethernet PHY for now.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 63 ++++++++++++++++++++
 1 file changed, 63 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index a5ee68388bd3..8663d2146e0f 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -16,6 +16,16 @@ / {
 	#address-cells = <1>;
 	#size-cells = <1>;
 
+	ac200_pwm_clk: ac200_clk {
+		compatible = "pwm-clock";
+		#clock-cells = <0>;
+		clock-frequency = <24000000>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&pwm1_pin>;
+		pwms = <&pwm 1 42 0>;
+		status = "disabled";
+	};
+
 	cpus {
 		#address-cells = <1>;
 		#size-cells = <0>;
@@ -250,6 +260,10 @@ sid: efuse@3006000 {
 			ths_calibration: thermal-sensor-calibration@14 {
 				reg = <0x14 0x8>;
 			};
+
+			ephy_calibration: ephy-calibration@2c {
+				reg = <0x2c 0x2>;
+			};
 		};
 
 		watchdog: watchdog@30090a0 {
@@ -294,6 +308,14 @@ ext_rgmii_pins: rgmii-pins {
 				drive-strength = <40>;
 			};
 
+			/omit-if-no-ref/
+			ext_rmii_pins: rmii_pins {
+				pins = "PA0", "PA1", "PA2", "PA3", "PA4",
+				       "PA5", "PA6", "PA7", "PA8", "PA9";
+				function = "emac";
+				drive-strength = <40>;
+			};
+
 			hdmi_pins: hdmi-pins {
 				pins = "PH8", "PH9", "PH10";
 				function = "hdmi";
@@ -314,6 +336,11 @@ i2c2_pins: i2c2-pins {
 				function = "i2c2";
 			};
 
+			i2c3_pins: i2c3-pins {
+				pins = "PB17", "PB18";
+				function = "i2c3";
+			};
+
 			mmc0_pins: mmc0-pins {
 				pins = "PF0", "PF1", "PF2", "PF3",
 				       "PF4", "PF5";
@@ -331,6 +358,11 @@ mmc1_pins: mmc1-pins {
 				bias-pull-up;
 			};
 
+			pwm1_pin: pwm1-pin {
+				pins = "PB19";
+				function = "pwm1";
+			};
+
 			mmc2_pins: mmc2-pins {
 				pins = "PC1", "PC4", "PC5", "PC6",
 				       "PC7", "PC8", "PC9", "PC10",
@@ -531,6 +563,37 @@ i2c2: i2c@5002800 {
 			#size-cells = <0>;
 		};
 
+		i2c3: i2c@5002c00 {
+			compatible = "allwinner,sun50i-h6-i2c",
+				     "allwinner,sun6i-a31-i2c";
+			reg = <0x05002c00 0x400>;
+			interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&ccu CLK_BUS_I2C3>;
+			resets = <&ccu RST_BUS_I2C3>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&i2c3_pins>;
+			status = "disabled";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ac200: mfd@10 {
+				compatible = "x-powers,ac200";
+				reg = <0x10>;
+				clocks = <&ac200_pwm_clk>;
+				interrupt-parent = <&pio>;
+				interrupts = <1 20 IRQ_TYPE_LEVEL_LOW>;
+				interrupt-controller;
+				#interrupt-cells = <1>;
+
+				ac200_ephy: phy {
+					compatible = "x-powers,ac200-ephy";
+					nvmem-cells = <&ephy_calibration>;
+					nvmem-cell-names = "calibration";
+					status = "disabled";
+				};
+			};
+		};
+
 		spi0: spi@5010000 {
 			compatible = "allwinner,sun50i-h6-spi",
 				     "allwinner,sun8i-h3-spi";
-- 
2.26.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 4/4] arm64: dts: allwinner: h6: tanix-tx6: Enable ethernet
  2020-04-16 18:57 [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Jernej Skrabec
                   ` (2 preceding siblings ...)
  2020-04-16 18:57 ` [RFC PATCH 3/4] arm64: dts: allwinner: h6: Add AC200 EPHY related nodes Jernej Skrabec
@ 2020-04-16 18:57 ` Jernej Skrabec
  2020-04-16 21:54 ` [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Andrew Lunn
  4 siblings, 0 replies; 16+ messages in thread
From: Jernej Skrabec @ 2020-04-16 18:57 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, hkallweit1
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

Tanix TX6 uses ethernet PHY from copackaged AC200 IC.

Enable it.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 .../dts/allwinner/sun50i-h6-tanix-tx6.dts     | 32 +++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts b/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts
index 83e6cb0e59ce..41a2e3454be5 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6-tanix-tx6.dts
@@ -12,6 +12,7 @@ / {
 	compatible = "oranth,tanix-tx6", "allwinner,sun50i-h6";
 
 	aliases {
+		ethernet0 = &emac;
 		serial0 = &uart0;
 	};
 
@@ -39,6 +40,14 @@ reg_vcc3v3: vcc3v3 {
 	};
 };
 
+&ac200_ephy {
+	status = "okay";
+};
+
+&ac200_pwm_clk {
+	status = "okay";
+};
+
 &de {
 	status = "okay";
 };
@@ -47,6 +56,14 @@ &dwc3 {
 	status = "okay";
 };
 
+&emac {
+	pinctrl-names = "default";
+	pinctrl-0 = <&ext_rmii_pins>;
+	phy-mode = "rmii";
+	phy-handle = <&ext_rmii_phy>;
+	status = "okay";
+};
+
 &ehci0 {
 	status = "okay";
 };
@@ -69,6 +86,17 @@ hdmi_out_con: endpoint {
 	};
 };
 
+&i2c3 {
+	status = "okay";
+};
+
+&mdio {
+	ext_rmii_phy: ethernet-phy@1 {
+		compatible = "ethernet-phy-ieee802.3-c22";
+		reg = <1>;
+	};
+};
+
 &mmc0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&mmc0_pins>;
@@ -86,6 +114,10 @@ &ohci3 {
 	status = "okay";
 };
 
+&pwm {
+	status = "okay";
+};
+
 &r_ir {
 	linux,rc-map-name = "rc-tanix-tx5max";
 	status = "okay";
-- 
2.26.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-16 18:57 ` [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY Jernej Skrabec
@ 2020-04-16 19:18   ` Florian Fainelli
  2020-04-17 15:01     ` Jernej Škrabec
  2020-04-16 20:18   ` Heiner Kallweit
  1 sibling, 1 reply; 16+ messages in thread
From: Florian Fainelli @ 2020-04-16 19:18 UTC (permalink / raw)
  To: Jernej Skrabec, robh+dt, andrew, hkallweit1
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev



On 4/16/2020 11:57 AM, Jernej Skrabec wrote:
> AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
> 
> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
>   drivers/net/phy/Kconfig  |   7 ++
>   drivers/net/phy/Makefile |   1 +
>   drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
>   3 files changed, 214 insertions(+)
>   create mode 100644 drivers/net/phy/ac200.c
> 
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index 3fa33d27eeba..16af69f69eaf 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -288,6 +288,13 @@ config ADIN_PHY
>   	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
>   	    Ethernet PHY
>   
> +config AC200_PHY
> +	tristate "AC200 EPHY"
> +	depends on NVMEM
> +	depends on OF
> +	help
> +	  Fast ethernet PHY as found in X-Powers AC200 multi-function device.
> +
>   config AMD_PHY
>   	tristate "AMD PHYs"
>   	---help---
> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> index 2f5c7093a65b..b0c5b91900fa 100644
> --- a/drivers/net/phy/Makefile
> +++ b/drivers/net/phy/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
>   sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
>   obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
>   
> +obj-$(CONFIG_AC200_PHY)		+= ac200.o
>   obj-$(CONFIG_ADIN_PHY)		+= adin.o
>   obj-$(CONFIG_AMD_PHY)		+= amd.o
>   aquantia-objs			+= aquantia_main.o
> diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
> new file mode 100644
> index 000000000000..3d7856ff8f91
> --- /dev/null
> +++ b/drivers/net/phy/ac200.c
> @@ -0,0 +1,206 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/**
> + * Driver for AC200 Ethernet PHY
> + *
> + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mfd/ac200.h>
> +#include <linux/nvmem-consumer.h>
> +#include <linux/of.h>
> +#include <linux/phy.h>
> +#include <linux/platform_device.h>
> +
> +#define AC200_EPHY_ID			0x00441400
> +#define AC200_EPHY_ID_MASK		0x0ffffff0
> +
> +/* macros for system ephy control 0 register */
> +#define AC200_EPHY_RESET_INVALID	BIT(0)
> +#define AC200_EPHY_SYSCLK_GATING	BIT(1)
> +
> +/* macros for system ephy control 1 register */
> +#define AC200_EPHY_E_EPHY_MII_IO_EN	BIT(0)
> +#define AC200_EPHY_E_LNK_LED_IO_EN	BIT(1)
> +#define AC200_EPHY_E_SPD_LED_IO_EN	BIT(2)
> +#define AC200_EPHY_E_DPX_LED_IO_EN	BIT(3)
> +
> +/* macros for ephy control register */
> +#define AC200_EPHY_SHUTDOWN		BIT(0)
> +#define AC200_EPHY_LED_POL		BIT(1)
> +#define AC200_EPHY_CLK_SEL		BIT(2)
> +#define AC200_EPHY_ADDR(x)		(((x) & 0x1F) << 4)
> +#define AC200_EPHY_XMII_SEL		BIT(11)
> +#define AC200_EPHY_CALIB(x)		(((x) & 0xF) << 12)
> +
> +struct ac200_ephy_dev {
> +	struct phy_driver	*ephy;
> +	struct regmap		*regmap;
> +};
> +
> +static char *ac200_phy_name = "AC200 EPHY";
> +
> +static int ac200_ephy_config_init(struct phy_device *phydev)
> +{
> +	const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
> +	unsigned int value;
> +	int ret;
> +
> +	phy_write(phydev, 0x1f, 0x0100);	/* Switch to Page 1 */

You could define a macro for accessing the page and you may consider 
implementing .read_page and .write_page and use the 
phy_read_paged()/phy_write_paged() helper functions.

> +	phy_write(phydev, 0x12, 0x4824);	/* Disable APS */
> +
> +	phy_write(phydev, 0x1f, 0x0200);	/* Switch to Page 2 */
> +	phy_write(phydev, 0x18, 0x0000);	/* PHYAFE TRX optimization */
> +
> +	phy_write(phydev, 0x1f, 0x0600);	/* Switch to Page 6 */
> +	phy_write(phydev, 0x14, 0x708f);	/* PHYAFE TX optimization */
> +	phy_write(phydev, 0x13, 0xF000);	/* PHYAFE RX optimization */
> +	phy_write(phydev, 0x15, 0x1530);
> +
> +	phy_write(phydev, 0x1f, 0x0800);	/* Switch to Page 6 */

Seems like the comment does not match the code, that should be Page 8, no?

> +	phy_write(phydev, 0x18, 0x00bc);	/* PHYAFE TRX optimization */
> +
> +	phy_write(phydev, 0x1f, 0x0100);	/* switch to page 1 */
> +	phy_clear_bits(phydev, 0x17, BIT(3));	/* disable intelligent IEEE */

Intelligent EEE maybe?
-- 
Florian

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-16 18:57 ` [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY Jernej Skrabec
  2020-04-16 19:18   ` Florian Fainelli
@ 2020-04-16 20:18   ` Heiner Kallweit
  2020-04-17 16:03     ` Jernej Škrabec
  2020-04-17 16:15     ` Jernej Škrabec
  1 sibling, 2 replies; 16+ messages in thread
From: Heiner Kallweit @ 2020-04-16 20:18 UTC (permalink / raw)
  To: Jernej Skrabec, robh+dt, andrew, f.fainelli
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

On 16.04.2020 20:57, Jernej Skrabec wrote:
> AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
> 
> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
>  drivers/net/phy/Kconfig  |   7 ++
>  drivers/net/phy/Makefile |   1 +
>  drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
>  3 files changed, 214 insertions(+)
>  create mode 100644 drivers/net/phy/ac200.c
> 
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index 3fa33d27eeba..16af69f69eaf 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -288,6 +288,13 @@ config ADIN_PHY
>  	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
>  	    Ethernet PHY
>  
> +config AC200_PHY
> +	tristate "AC200 EPHY"
> +	depends on NVMEM
> +	depends on OF
> +	help
> +	  Fast ethernet PHY as found in X-Powers AC200 multi-function device.
> +
>  config AMD_PHY
>  	tristate "AMD PHYs"
>  	---help---
> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> index 2f5c7093a65b..b0c5b91900fa 100644
> --- a/drivers/net/phy/Makefile
> +++ b/drivers/net/phy/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
>  sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
>  obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
>  
> +obj-$(CONFIG_AC200_PHY)		+= ac200.o
>  obj-$(CONFIG_ADIN_PHY)		+= adin.o
>  obj-$(CONFIG_AMD_PHY)		+= amd.o
>  aquantia-objs			+= aquantia_main.o
> diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
> new file mode 100644
> index 000000000000..3d7856ff8f91
> --- /dev/null
> +++ b/drivers/net/phy/ac200.c
> @@ -0,0 +1,206 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/**
> + * Driver for AC200 Ethernet PHY
> + *
> + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mfd/ac200.h>
> +#include <linux/nvmem-consumer.h>
> +#include <linux/of.h>
> +#include <linux/phy.h>
> +#include <linux/platform_device.h>
> +
> +#define AC200_EPHY_ID			0x00441400
> +#define AC200_EPHY_ID_MASK		0x0ffffff0
> +

You could use PHY_ID_MATCH_MODEL() here.

> +/* macros for system ephy control 0 register */
> +#define AC200_EPHY_RESET_INVALID	BIT(0)
> +#define AC200_EPHY_SYSCLK_GATING	BIT(1)
> +
> +/* macros for system ephy control 1 register */
> +#define AC200_EPHY_E_EPHY_MII_IO_EN	BIT(0)
> +#define AC200_EPHY_E_LNK_LED_IO_EN	BIT(1)
> +#define AC200_EPHY_E_SPD_LED_IO_EN	BIT(2)
> +#define AC200_EPHY_E_DPX_LED_IO_EN	BIT(3)
> +
> +/* macros for ephy control register */
> +#define AC200_EPHY_SHUTDOWN		BIT(0)
> +#define AC200_EPHY_LED_POL		BIT(1)
> +#define AC200_EPHY_CLK_SEL		BIT(2)
> +#define AC200_EPHY_ADDR(x)		(((x) & 0x1F) << 4)
> +#define AC200_EPHY_XMII_SEL		BIT(11)
> +#define AC200_EPHY_CALIB(x)		(((x) & 0xF) << 12)
> +
> +struct ac200_ephy_dev {
> +	struct phy_driver	*ephy;

Why embedding a pointer and not a struct phy_driver directly?
Then you could omit the separate allocation.

ephy is not the best naming. It may be read as a phy_device.
Better use phydrv.

> +	struct regmap		*regmap;
> +};
> +
> +static char *ac200_phy_name = "AC200 EPHY";
> +
Why not using the name directly in the assignment?
And better naming: "AC200 Fast Ethernet"

> +static int ac200_ephy_config_init(struct phy_device *phydev)
> +{
> +	const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
> +	unsigned int value;
> +	int ret;
> +
> +	phy_write(phydev, 0x1f, 0x0100);	/* Switch to Page 1 */
> +	phy_write(phydev, 0x12, 0x4824);	/* Disable APS */
> +
> +	phy_write(phydev, 0x1f, 0x0200);	/* Switch to Page 2 */
> +	phy_write(phydev, 0x18, 0x0000);	/* PHYAFE TRX optimization */
> +
> +	phy_write(phydev, 0x1f, 0x0600);	/* Switch to Page 6 */
> +	phy_write(phydev, 0x14, 0x708f);	/* PHYAFE TX optimization */
> +	phy_write(phydev, 0x13, 0xF000);	/* PHYAFE RX optimization */
> +	phy_write(phydev, 0x15, 0x1530);
> +
> +	phy_write(phydev, 0x1f, 0x0800);	/* Switch to Page 6 */
> +	phy_write(phydev, 0x18, 0x00bc);	/* PHYAFE TRX optimization */
> +
> +	phy_write(phydev, 0x1f, 0x0100);	/* switch to page 1 */
> +	phy_clear_bits(phydev, 0x17, BIT(3));	/* disable intelligent IEEE */
> +
> +	/* next two blocks disable 802.3az IEEE */
> +	phy_write(phydev, 0x1f, 0x0200);	/* switch to page 2 */
> +	phy_write(phydev, 0x18, 0x0000);
> +
> +	phy_write(phydev, 0x1f, 0x0000);	/* switch to page 0 */
> +	phy_clear_bits_mmd(phydev, 0x7, 0x3c, BIT(1));

Better use the following:
phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0000);
It makes clear that you disable advertising EEE completely.

> +
> +	if (phydev->interface == PHY_INTERFACE_MODE_RMII)
> +		value = AC200_EPHY_XMII_SEL;
> +	else
> +		value = 0;
> +
> +	ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL,
> +				 AC200_EPHY_XMII_SEL, value);
> +	if (ret)
> +		return ret;
> +

I had a brief look at the spec, and it's not fully clear
to me what this register setting does. Does it affect the
MAC side and/or the PHY side?
If it affects the PHY side, then I'd expect that the chip
has to talk to the PHY via the MDIO bus. Means there should
be a PHY register for setting MII vs. RMII.
In this case the setup could be very much simplified.
Then the PHY driver wouldn't have to be embedded in the
platform driver.

> +	/* FIXME: This is H6 specific */
> +	phy_set_bits(phydev, 0x13, BIT(12));
> +

This seems to indicate that the same PHY is used in a slightly
different version with other Hx models. Do they use different
PHY ID's?

> +	return 0;
> +}
> +
> +static int ac200_ephy_probe(struct platform_device *pdev)
> +{
> +	struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent);
> +	struct device *dev = &pdev->dev;
> +	struct ac200_ephy_dev *priv;
> +	struct nvmem_cell *calcell;
> +	struct phy_driver *ephy;
> +	u16 *caldata, calib;
> +	size_t callen;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
> +	if (!ephy)
> +		return -ENOMEM;
> +
> +	calcell = devm_nvmem_cell_get(dev, "calibration");
> +	if (IS_ERR(calcell)) {
> +		dev_err(dev, "Unable to find calibration data!\n");
> +		return PTR_ERR(calcell);
> +	}
> +
> +	caldata = nvmem_cell_read(calcell, &callen);
> +	if (IS_ERR(caldata)) {
> +		dev_err(dev, "Unable to read calibration data!\n");
> +		return PTR_ERR(caldata);
> +	}
> +
> +	if (callen != 2) {
> +		dev_err(dev, "Calibration data has wrong length: 2 != %zu\n",
> +			callen);
> +		kfree(caldata);
> +		return -EINVAL;
> +	}
> +
> +	calib = *caldata + 3;
> +	kfree(caldata);
> +
> +	ephy->phy_id = AC200_EPHY_ID;
> +	ephy->phy_id_mask = AC200_EPHY_ID_MASK;
> +	ephy->name = ac200_phy_name;
> +	ephy->driver_data = priv;
> +	ephy->soft_reset = genphy_soft_reset;
> +	ephy->config_init = ac200_ephy_config_init;
> +	ephy->suspend = genphy_suspend;
> +	ephy->resume = genphy_resume;
> +
> +	priv->ephy = ephy;
> +	priv->regmap = ac200->regmap;
> +	platform_set_drvdata(pdev, priv);
> +
> +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL0,
> +			   AC200_EPHY_RESET_INVALID |
> +			   AC200_EPHY_SYSCLK_GATING);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL1,
> +			   AC200_EPHY_E_EPHY_MII_IO_EN |
> +			   AC200_EPHY_E_LNK_LED_IO_EN |
> +			   AC200_EPHY_E_SPD_LED_IO_EN |
> +			   AC200_EPHY_E_DPX_LED_IO_EN);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(ac200->regmap, AC200_EPHY_CTL,
> +			   AC200_EPHY_LED_POL |
> +			   AC200_EPHY_CLK_SEL |
> +			   AC200_EPHY_ADDR(1) |
> +			   AC200_EPHY_CALIB(calib));
> +	if (ret)
> +		return ret;
> +
> +	ret = phy_driver_register(priv->ephy, THIS_MODULE);
> +	if (ret) {
> +		dev_err(dev, "Unable to register phy\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ac200_ephy_remove(struct platform_device *pdev)
> +{
> +	struct ac200_ephy_dev *priv = platform_get_drvdata(pdev);
> +
> +	phy_driver_unregister(priv->ephy);
> +
> +	regmap_write(priv->regmap, AC200_EPHY_CTL, AC200_EPHY_SHUTDOWN);
> +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1, 0);
> +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0, 0);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id ac200_ephy_match[] = {
> +	{ .compatible = "x-powers,ac200-ephy" },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, ac200_ephy_match);
> +
> +static struct platform_driver ac200_ephy_driver = {
> +	.probe		= ac200_ephy_probe,
> +	.remove		= ac200_ephy_remove,
> +	.driver		= {
> +		.name		= "ac200-ephy",
> +		.of_match_table	= ac200_ephy_match,
> +	},
> +};
> +module_platform_driver(ac200_ephy_driver);
> +
> +MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
> +MODULE_DESCRIPTION("AC200 Ethernet PHY driver");
> +MODULE_LICENSE("GPL");
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY
  2020-04-16 18:57 [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Jernej Skrabec
                   ` (3 preceding siblings ...)
  2020-04-16 18:57 ` [RFC PATCH 4/4] arm64: dts: allwinner: h6: tanix-tx6: Enable ethernet Jernej Skrabec
@ 2020-04-16 21:54 ` Andrew Lunn
  4 siblings, 0 replies; 16+ messages in thread
From: Andrew Lunn @ 2020-04-16 21:54 UTC (permalink / raw)
  To: Jernej Skrabec
  Cc: robh+dt, f.fainelli, hkallweit1, devicetree, netdev, linux,
	mripard, linux-kernel, wens, lee.jones, davem, linux-arm-kernel

On Thu, Apr 16, 2020 at 08:57:54PM +0200, Jernej Skrabec wrote:
> This is attempt to support Ethernet PHY on AC200 MFD chip. I'm sending
> this as RFC because I stumbled on a problem how to properly describe it
> in DT. Proper DT documentation will be added later, once DT issue is
> solved.
> 
> Before Ethernet PHY can be actually used, few things must happen:
> 1. 24 MHz clock must be enabled and connected to input pin of this
>    chip. In this case, PWM is set to generate 24 MHz signal with 50%
>    duty cycle.
> 2. Chip must be put out of reset through I2C
> 3. Ethernet PHY must be enabled and configured through I2C

Hi Jernej

This is going to be interesting to describe in DT.

At what point does the PHY start responding to MDIO request? In
particular, you can read the ID registers 2 and 3? You list above what
is needed to make it usable. But we are also interested in what is
required to make is probe'able on the MDIO bus. We have more
flexibility if we can first probe it, and then later make it usable.

Thanks
	Andrew

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-16 19:18   ` Florian Fainelli
@ 2020-04-17 15:01     ` Jernej Škrabec
  0 siblings, 0 replies; 16+ messages in thread
From: Jernej Škrabec @ 2020-04-17 15:01 UTC (permalink / raw)
  To: robh+dt, andrew, hkallweit1, Florian Fainelli
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

Dne četrtek, 16. april 2020 ob 21:18:29 CEST je Florian Fainelli napisal(a):
> On 4/16/2020 11:57 AM, Jernej Skrabec wrote:
> > AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
> > 
> > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> > ---
> > 
> >   drivers/net/phy/Kconfig  |   7 ++
> >   drivers/net/phy/Makefile |   1 +
> >   drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
> >   3 files changed, 214 insertions(+)
> >   create mode 100644 drivers/net/phy/ac200.c
> > 
> > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> > index 3fa33d27eeba..16af69f69eaf 100644
> > --- a/drivers/net/phy/Kconfig
> > +++ b/drivers/net/phy/Kconfig
> > @@ -288,6 +288,13 @@ config ADIN_PHY
> > 
> >   	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
> >   	  
> >   	    Ethernet PHY
> > 
> > +config AC200_PHY
> > +	tristate "AC200 EPHY"
> > +	depends on NVMEM
> > +	depends on OF
> > +	help
> > +	  Fast ethernet PHY as found in X-Powers AC200 multi-function 
device.
> > +
> > 
> >   config AMD_PHY
> >   
> >   	tristate "AMD PHYs"
> >   	---help---
> > 
> > diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> > index 2f5c7093a65b..b0c5b91900fa 100644
> > --- a/drivers/net/phy/Makefile
> > +++ b/drivers/net/phy/Makefile
> > @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
> > 
> >   sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
> >   obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
> > 
> > +obj-$(CONFIG_AC200_PHY)		+= ac200.o
> > 
> >   obj-$(CONFIG_ADIN_PHY)		+= adin.o
> >   obj-$(CONFIG_AMD_PHY)		+= amd.o
> >   aquantia-objs			+= aquantia_main.o
> > 
> > diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
> > new file mode 100644
> > index 000000000000..3d7856ff8f91
> > --- /dev/null
> > +++ b/drivers/net/phy/ac200.c
> > @@ -0,0 +1,206 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/**
> > + * Driver for AC200 Ethernet PHY
> > + *
> > + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/mfd/ac200.h>
> > +#include <linux/nvmem-consumer.h>
> > +#include <linux/of.h>
> > +#include <linux/phy.h>
> > +#include <linux/platform_device.h>
> > +
> > +#define AC200_EPHY_ID			0x00441400
> > +#define AC200_EPHY_ID_MASK		0x0ffffff0
> > +
> > +/* macros for system ephy control 0 register */
> > +#define AC200_EPHY_RESET_INVALID	BIT(0)
> > +#define AC200_EPHY_SYSCLK_GATING	BIT(1)
> > +
> > +/* macros for system ephy control 1 register */
> > +#define AC200_EPHY_E_EPHY_MII_IO_EN	BIT(0)
> > +#define AC200_EPHY_E_LNK_LED_IO_EN	BIT(1)
> > +#define AC200_EPHY_E_SPD_LED_IO_EN	BIT(2)
> > +#define AC200_EPHY_E_DPX_LED_IO_EN	BIT(3)
> > +
> > +/* macros for ephy control register */
> > +#define AC200_EPHY_SHUTDOWN		BIT(0)
> > +#define AC200_EPHY_LED_POL		BIT(1)
> > +#define AC200_EPHY_CLK_SEL		BIT(2)
> > +#define AC200_EPHY_ADDR(x)		(((x) & 0x1F) << 4)
> > +#define AC200_EPHY_XMII_SEL		BIT(11)
> > +#define AC200_EPHY_CALIB(x)		(((x) & 0xF) << 12)
> > +
> > +struct ac200_ephy_dev {
> > +	struct phy_driver	*ephy;
> > +	struct regmap		*regmap;
> > +};
> > +
> > +static char *ac200_phy_name = "AC200 EPHY";
> > +
> > +static int ac200_ephy_config_init(struct phy_device *phydev)
> > +{
> > +	const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
> > +	unsigned int value;
> > +	int ret;
> > +
> > +	phy_write(phydev, 0x1f, 0x0100);	/* Switch to Page 1 */
> 
> You could define a macro for accessing the page and you may consider
> implementing .read_page and .write_page and use the
> phy_read_paged()/phy_write_paged() helper functions.

Yeah, I saw that, but they bring some overhead - there is no need to switch 
page back after write, because next write changes it anyway. But it will 
probably be more readable and it's done only once so overhead is acceptable.

> 
> > +	phy_write(phydev, 0x12, 0x4824);	/* Disable APS */
> > +
> > +	phy_write(phydev, 0x1f, 0x0200);	/* Switch to Page 2 */
> > +	phy_write(phydev, 0x18, 0x0000);	/* PHYAFE TRX optimization */
> > +
> > +	phy_write(phydev, 0x1f, 0x0600);	/* Switch to Page 6 */
> > +	phy_write(phydev, 0x14, 0x708f);	/* PHYAFE TX optimization */
> > +	phy_write(phydev, 0x13, 0xF000);	/* PHYAFE RX optimization */
> > +	phy_write(phydev, 0x15, 0x1530);
> > +
> > +	phy_write(phydev, 0x1f, 0x0800);	/* Switch to Page 6 */
> 
> Seems like the comment does not match the code, that should be Page 8, no?

Right, I copy that from BSP driver. If they made this copy and paste error, I 
wonder if all other comments are ok. I have no documentation about there 
registers.

> 
> > +	phy_write(phydev, 0x18, 0x00bc);	/* PHYAFE TRX optimization */
> > +
> > +	phy_write(phydev, 0x1f, 0x0100);	/* switch to page 1 */
> > +	phy_clear_bits(phydev, 0x17, BIT(3));	/* disable intelligent 
IEEE */
> 
> Intelligent EEE maybe?

Not sure. As I said before, I just copied comments from BSP driver:
https://github.com/Allwinner-Homlet/H6-BSP4.9-linux/blob/master/drivers/net/
phy/sunxi-ephy.c

This is my first take at ethernet phy drivers, so I don't really know if all 
comments above make sense.

Best regards,
Jernej





^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-16 20:18   ` Heiner Kallweit
@ 2020-04-17 16:03     ` Jernej Škrabec
  2020-04-17 16:29       ` Heiner Kallweit
  2020-04-17 16:15     ` Jernej Škrabec
  1 sibling, 1 reply; 16+ messages in thread
From: Jernej Škrabec @ 2020-04-17 16:03 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, Heiner Kallweit
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

Dne četrtek, 16. april 2020 ob 22:18:52 CEST je Heiner Kallweit napisal(a):
> On 16.04.2020 20:57, Jernej Skrabec wrote:
> > AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
> > 
> > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> > ---
> > 
> >  drivers/net/phy/Kconfig  |   7 ++
> >  drivers/net/phy/Makefile |   1 +
> >  drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 214 insertions(+)
> >  create mode 100644 drivers/net/phy/ac200.c
> > 
> > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> > index 3fa33d27eeba..16af69f69eaf 100644
> > --- a/drivers/net/phy/Kconfig
> > +++ b/drivers/net/phy/Kconfig
> > @@ -288,6 +288,13 @@ config ADIN_PHY
> > 
> >  	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
> >  	  
> >  	    Ethernet PHY
> > 
> > +config AC200_PHY
> > +	tristate "AC200 EPHY"
> > +	depends on NVMEM
> > +	depends on OF
> > +	help
> > +	  Fast ethernet PHY as found in X-Powers AC200 multi-function 
device.
> > +
> > 
> >  config AMD_PHY
> >  
> >  	tristate "AMD PHYs"
> >  	---help---
> > 
> > diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> > index 2f5c7093a65b..b0c5b91900fa 100644
> > --- a/drivers/net/phy/Makefile
> > +++ b/drivers/net/phy/Makefile
> > @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
> > 
> >  sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
> >  obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
> > 
> > +obj-$(CONFIG_AC200_PHY)		+= ac200.o
> > 
> >  obj-$(CONFIG_ADIN_PHY)		+= adin.o
> >  obj-$(CONFIG_AMD_PHY)		+= amd.o
> >  aquantia-objs			+= aquantia_main.o
> > 
> > diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
> > new file mode 100644
> > index 000000000000..3d7856ff8f91
> > --- /dev/null
> > +++ b/drivers/net/phy/ac200.c
> > @@ -0,0 +1,206 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/**
> > + * Driver for AC200 Ethernet PHY
> > + *
> > + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/mfd/ac200.h>
> > +#include <linux/nvmem-consumer.h>
> > +#include <linux/of.h>
> > +#include <linux/phy.h>
> > +#include <linux/platform_device.h>
> > +
> > +#define AC200_EPHY_ID			0x00441400
> > +#define AC200_EPHY_ID_MASK		0x0ffffff0
> > +
> 
> You could use PHY_ID_MATCH_MODEL() here.

Ok.

> 
> > +/* macros for system ephy control 0 register */
> > +#define AC200_EPHY_RESET_INVALID	BIT(0)
> > +#define AC200_EPHY_SYSCLK_GATING	BIT(1)
> > +
> > +/* macros for system ephy control 1 register */
> > +#define AC200_EPHY_E_EPHY_MII_IO_EN	BIT(0)
> > +#define AC200_EPHY_E_LNK_LED_IO_EN	BIT(1)
> > +#define AC200_EPHY_E_SPD_LED_IO_EN	BIT(2)
> > +#define AC200_EPHY_E_DPX_LED_IO_EN	BIT(3)
> > +
> > +/* macros for ephy control register */
> > +#define AC200_EPHY_SHUTDOWN		BIT(0)
> > +#define AC200_EPHY_LED_POL		BIT(1)
> > +#define AC200_EPHY_CLK_SEL		BIT(2)
> > +#define AC200_EPHY_ADDR(x)		(((x) & 0x1F) << 4)
> > +#define AC200_EPHY_XMII_SEL		BIT(11)
> > +#define AC200_EPHY_CALIB(x)		(((x) & 0xF) << 12)
> > +
> > +struct ac200_ephy_dev {
> > +	struct phy_driver	*ephy;
> 
> Why embedding a pointer and not a struct phy_driver directly?
> Then you could omit the separate allocation.

Right.

> 
> ephy is not the best naming. It may be read as a phy_device.
> Better use phydrv.

Ok.

> 
> > +	struct regmap		*regmap;
> > +};
> > +
> > +static char *ac200_phy_name = "AC200 EPHY";
> > +
> 
> Why not using the name directly in the assignment?

phy_driver->name is pointer. Wouldn't that mean that string is allocated on 
stack and next time pointer is used, it will return garbage?

> And better naming: "AC200 Fast Ethernet"

Ok.

> 
> > +static int ac200_ephy_config_init(struct phy_device *phydev)
> > +{
> > +	const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
> > +	unsigned int value;
> > +	int ret;
> > +
> > +	phy_write(phydev, 0x1f, 0x0100);	/* Switch to Page 1 */
> > +	phy_write(phydev, 0x12, 0x4824);	/* Disable APS */
> > +
> > +	phy_write(phydev, 0x1f, 0x0200);	/* Switch to Page 2 */
> > +	phy_write(phydev, 0x18, 0x0000);	/* PHYAFE TRX optimization */
> > +
> > +	phy_write(phydev, 0x1f, 0x0600);	/* Switch to Page 6 */
> > +	phy_write(phydev, 0x14, 0x708f);	/* PHYAFE TX optimization */
> > +	phy_write(phydev, 0x13, 0xF000);	/* PHYAFE RX optimization */
> > +	phy_write(phydev, 0x15, 0x1530);
> > +
> > +	phy_write(phydev, 0x1f, 0x0800);	/* Switch to Page 6 */
> > +	phy_write(phydev, 0x18, 0x00bc);	/* PHYAFE TRX optimization */
> > +
> > +	phy_write(phydev, 0x1f, 0x0100);	/* switch to page 1 */
> > +	phy_clear_bits(phydev, 0x17, BIT(3));	/* disable intelligent 
IEEE */
> > +
> > +	/* next two blocks disable 802.3az IEEE */
> > +	phy_write(phydev, 0x1f, 0x0200);	/* switch to page 2 */
> > +	phy_write(phydev, 0x18, 0x0000);
> > +
> > +	phy_write(phydev, 0x1f, 0x0000);	/* switch to page 0 */
> > +	phy_clear_bits_mmd(phydev, 0x7, 0x3c, BIT(1));
> 
> Better use the following:
> phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0000);
> It makes clear that you disable advertising EEE completely.

Ok.

> 
> > +
> > +	if (phydev->interface == PHY_INTERFACE_MODE_RMII)
> > +		value = AC200_EPHY_XMII_SEL;
> > +	else
> > +		value = 0;
> > +
> > +	ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL,
> > +				 AC200_EPHY_XMII_SEL, value);
> > +	if (ret)
> > +		return ret;
> > +
> 
> I had a brief look at the spec, and it's not fully clear
> to me what this register setting does. Does it affect the
> MAC side and/or the PHY side?

It's my understanding that it selects interface mode on PHY. Besides datasheet 
mentioned in cover letter, BSP drivers (one for MFD and one for PHY) are the 
only other source of information. BSP PHY driver is located here:
https://github.com/Allwinner-Homlet/H6-BSP4.9-linux/blob/master/drivers/net/
phy/sunxi-ephy.c

> If it affects the PHY side, then I'd expect that the chip
> has to talk to the PHY via the MDIO bus. Means there should
> be a PHY register for setting MII vs. RMII.
> In this case the setup could be very much simplified.
> Then the PHY driver wouldn't have to be embedded in the
> platform driver.

Actually, PHY has to be configured first through I2C and then through MDIO. I2C 
is used to enable it (power it up), configure LED polarity, set PHY address, 
write calibration value stored elsewhere.

Based on all available documentation I have (code and datasheet), this I2C 
register is the only way to select MII or RMII mode.

> 
> > +	/* FIXME: This is H6 specific */
> > +	phy_set_bits(phydev, 0x13, BIT(12));
> > +
> 
> This seems to indicate that the same PHY is used in a slightly
> different version with other Hx models. Do they use different
> PHY ID's?

Situation is a bit complicated. Same PHY, at least with same PHY ID, is used 
in different ways.
1. as part of standalone AC200 MFD IC
2. as part of AC200 wafer copackaged with H6 SoC wafer in same package. This 
in theory shouldn't be any different than standalone IC, but it apparently is, 
based on the BSP driver code.
3. integrated directly in SoCs like H3, H5 and V3s. There is no I2C access to 
configuration register. Instead, it's memory mapped and slightly different.

In all cases PHY ID is same, just glue logic is different.

I asked Allwinner if above setting is really necessary for H6 and what it 
does, but I didn't get any useful answer back.

So maybe another compatible is needed for H6.

Best regards,
Jernej

> 
> > +	return 0;
> > +}
> > +
> > +static int ac200_ephy_probe(struct platform_device *pdev)
> > +{
> > +	struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent);
> > +	struct device *dev = &pdev->dev;
> > +	struct ac200_ephy_dev *priv;
> > +	struct nvmem_cell *calcell;
> > +	struct phy_driver *ephy;
> > +	u16 *caldata, calib;
> > +	size_t callen;
> > +	int ret;
> > +
> > +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > +	if (!priv)
> > +		return -ENOMEM;
> > +
> > +	ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
> > +	if (!ephy)
> > +		return -ENOMEM;
> > +
> > +	calcell = devm_nvmem_cell_get(dev, "calibration");
> > +	if (IS_ERR(calcell)) {
> > +		dev_err(dev, "Unable to find calibration data!\n");
> > +		return PTR_ERR(calcell);
> > +	}
> > +
> > +	caldata = nvmem_cell_read(calcell, &callen);
> > +	if (IS_ERR(caldata)) {
> > +		dev_err(dev, "Unable to read calibration data!\n");
> > +		return PTR_ERR(caldata);
> > +	}
> > +
> > +	if (callen != 2) {
> > +		dev_err(dev, "Calibration data has wrong length: 2 != 
%zu\n",
> > +			callen);
> > +		kfree(caldata);
> > +		return -EINVAL;
> > +	}
> > +
> > +	calib = *caldata + 3;
> > +	kfree(caldata);
> > +
> > +	ephy->phy_id = AC200_EPHY_ID;
> > +	ephy->phy_id_mask = AC200_EPHY_ID_MASK;
> > +	ephy->name = ac200_phy_name;
> > +	ephy->driver_data = priv;
> > +	ephy->soft_reset = genphy_soft_reset;
> > +	ephy->config_init = ac200_ephy_config_init;
> > +	ephy->suspend = genphy_suspend;
> > +	ephy->resume = genphy_resume;
> > +
> > +	priv->ephy = ephy;
> > +	priv->regmap = ac200->regmap;
> > +	platform_set_drvdata(pdev, priv);
> > +
> > +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL0,
> > +			   AC200_EPHY_RESET_INVALID |
> > +			   AC200_EPHY_SYSCLK_GATING);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL1,
> > +			   AC200_EPHY_E_EPHY_MII_IO_EN |
> > +			   AC200_EPHY_E_LNK_LED_IO_EN |
> > +			   AC200_EPHY_E_SPD_LED_IO_EN |
> > +			   AC200_EPHY_E_DPX_LED_IO_EN);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regmap_write(ac200->regmap, AC200_EPHY_CTL,
> > +			   AC200_EPHY_LED_POL |
> > +			   AC200_EPHY_CLK_SEL |
> > +			   AC200_EPHY_ADDR(1) |
> > +			   AC200_EPHY_CALIB(calib));
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = phy_driver_register(priv->ephy, THIS_MODULE);
> > +	if (ret) {
> > +		dev_err(dev, "Unable to register phy\n");
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int ac200_ephy_remove(struct platform_device *pdev)
> > +{
> > +	struct ac200_ephy_dev *priv = platform_get_drvdata(pdev);
> > +
> > +	phy_driver_unregister(priv->ephy);
> > +
> > +	regmap_write(priv->regmap, AC200_EPHY_CTL, AC200_EPHY_SHUTDOWN);
> > +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1, 0);
> > +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0, 0);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id ac200_ephy_match[] = {
> > +	{ .compatible = "x-powers,ac200-ephy" },
> > +	{ /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, ac200_ephy_match);
> > +
> > +static struct platform_driver ac200_ephy_driver = {
> > +	.probe		= ac200_ephy_probe,
> > +	.remove		= ac200_ephy_remove,
> > +	.driver		= {
> > +		.name		= "ac200-ephy",
> > +		.of_match_table	= ac200_ephy_match,
> > +	},
> > +};
> > +module_platform_driver(ac200_ephy_driver);
> > +
> > +MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
> > +MODULE_DESCRIPTION("AC200 Ethernet PHY driver");
> > +MODULE_LICENSE("GPL");





^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-16 20:18   ` Heiner Kallweit
  2020-04-17 16:03     ` Jernej Škrabec
@ 2020-04-17 16:15     ` Jernej Škrabec
  2020-04-17 16:16       ` Heiner Kallweit
  2020-04-17 17:01       ` Andrew Lunn
  1 sibling, 2 replies; 16+ messages in thread
From: Jernej Škrabec @ 2020-04-17 16:15 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, Heiner Kallweit
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

Dne četrtek, 16. april 2020 ob 22:18:52 CEST je Heiner Kallweit napisal(a):
> On 16.04.2020 20:57, Jernej Skrabec wrote:
> > AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
> > 
> > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> > ---
> > 
> >  drivers/net/phy/Kconfig  |   7 ++
> >  drivers/net/phy/Makefile |   1 +
> >  drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 214 insertions(+)
> >  create mode 100644 drivers/net/phy/ac200.c
> > 
> > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> > index 3fa33d27eeba..16af69f69eaf 100644
> > --- a/drivers/net/phy/Kconfig
> > +++ b/drivers/net/phy/Kconfig
> > @@ -288,6 +288,13 @@ config ADIN_PHY
> > 
> >  	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
> >  	  
> >  	    Ethernet PHY
> > 
> > +config AC200_PHY
> > +	tristate "AC200 EPHY"
> > +	depends on NVMEM
> > +	depends on OF
> > +	help
> > +	  Fast ethernet PHY as found in X-Powers AC200 multi-function 
device.
> > +
> > 
> >  config AMD_PHY
> >  
> >  	tristate "AMD PHYs"
> >  	---help---
> > 
> > diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> > index 2f5c7093a65b..b0c5b91900fa 100644
> > --- a/drivers/net/phy/Makefile
> > +++ b/drivers/net/phy/Makefile
> > @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
> > 
> >  sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
> >  obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
> > 
> > +obj-$(CONFIG_AC200_PHY)		+= ac200.o
> > 
> >  obj-$(CONFIG_ADIN_PHY)		+= adin.o
> >  obj-$(CONFIG_AMD_PHY)		+= amd.o
> >  aquantia-objs			+= aquantia_main.o
> > 
> > diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
> > new file mode 100644
> > index 000000000000..3d7856ff8f91
> > --- /dev/null
> > +++ b/drivers/net/phy/ac200.c
> > @@ -0,0 +1,206 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/**
> > + * Driver for AC200 Ethernet PHY
> > + *
> > + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/mfd/ac200.h>
> > +#include <linux/nvmem-consumer.h>
> > +#include <linux/of.h>
> > +#include <linux/phy.h>
> > +#include <linux/platform_device.h>
> > +
> > +#define AC200_EPHY_ID			0x00441400
> > +#define AC200_EPHY_ID_MASK		0x0ffffff0
> > +
> 
> You could use PHY_ID_MATCH_MODEL() here.

Hm... This doesn't work with dynamically allocated memory, right?

Best regards,
Jernej



^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-17 16:15     ` Jernej Škrabec
@ 2020-04-17 16:16       ` Heiner Kallweit
  2020-04-17 17:01       ` Andrew Lunn
  1 sibling, 0 replies; 16+ messages in thread
From: Heiner Kallweit @ 2020-04-17 16:16 UTC (permalink / raw)
  To: Jernej Škrabec, robh+dt, andrew, f.fainelli
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

On 17.04.2020 18:15, Jernej Škrabec wrote:
> Dne četrtek, 16. april 2020 ob 22:18:52 CEST je Heiner Kallweit napisal(a):
>> On 16.04.2020 20:57, Jernej Skrabec wrote:
>>> AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
>>>
>>> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
>>> ---
>>>
>>>  drivers/net/phy/Kconfig  |   7 ++
>>>  drivers/net/phy/Makefile |   1 +
>>>  drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
>>>  3 files changed, 214 insertions(+)
>>>  create mode 100644 drivers/net/phy/ac200.c
>>>
>>> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
>>> index 3fa33d27eeba..16af69f69eaf 100644
>>> --- a/drivers/net/phy/Kconfig
>>> +++ b/drivers/net/phy/Kconfig
>>> @@ -288,6 +288,13 @@ config ADIN_PHY
>>>
>>>  	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
>>>  	  
>>>  	    Ethernet PHY
>>>
>>> +config AC200_PHY
>>> +	tristate "AC200 EPHY"
>>> +	depends on NVMEM
>>> +	depends on OF
>>> +	help
>>> +	  Fast ethernet PHY as found in X-Powers AC200 multi-function 
> device.
>>> +
>>>
>>>  config AMD_PHY
>>>  
>>>  	tristate "AMD PHYs"
>>>  	---help---
>>>
>>> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
>>> index 2f5c7093a65b..b0c5b91900fa 100644
>>> --- a/drivers/net/phy/Makefile
>>> +++ b/drivers/net/phy/Makefile
>>> @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
>>>
>>>  sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
>>>  obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
>>>
>>> +obj-$(CONFIG_AC200_PHY)		+= ac200.o
>>>
>>>  obj-$(CONFIG_ADIN_PHY)		+= adin.o
>>>  obj-$(CONFIG_AMD_PHY)		+= amd.o
>>>  aquantia-objs			+= aquantia_main.o
>>>
>>> diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
>>> new file mode 100644
>>> index 000000000000..3d7856ff8f91
>>> --- /dev/null
>>> +++ b/drivers/net/phy/ac200.c
>>> @@ -0,0 +1,206 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/**
>>> + * Driver for AC200 Ethernet PHY
>>> + *
>>> + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
>>> + */
>>> +
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mfd/ac200.h>
>>> +#include <linux/nvmem-consumer.h>
>>> +#include <linux/of.h>
>>> +#include <linux/phy.h>
>>> +#include <linux/platform_device.h>
>>> +
>>> +#define AC200_EPHY_ID			0x00441400
>>> +#define AC200_EPHY_ID_MASK		0x0ffffff0
>>> +
>>
>> You could use PHY_ID_MATCH_MODEL() here.
> 
> Hm... This doesn't work with dynamically allocated memory, right?
> 
Right ..

> Best regards,
> Jernej
> 
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-17 16:03     ` Jernej Škrabec
@ 2020-04-17 16:29       ` Heiner Kallweit
  2020-04-17 17:15         ` Jernej Škrabec
  0 siblings, 1 reply; 16+ messages in thread
From: Heiner Kallweit @ 2020-04-17 16:29 UTC (permalink / raw)
  To: Jernej Škrabec, robh+dt, andrew, f.fainelli
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

On 17.04.2020 18:03, Jernej Škrabec wrote:
> Dne četrtek, 16. april 2020 ob 22:18:52 CEST je Heiner Kallweit napisal(a):
>> On 16.04.2020 20:57, Jernej Skrabec wrote:
>>> AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
>>>
>>> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
>>> ---
>>>
>>>  drivers/net/phy/Kconfig  |   7 ++
>>>  drivers/net/phy/Makefile |   1 +
>>>  drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
>>>  3 files changed, 214 insertions(+)
>>>  create mode 100644 drivers/net/phy/ac200.c
>>>
>>> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
>>> index 3fa33d27eeba..16af69f69eaf 100644
>>> --- a/drivers/net/phy/Kconfig
>>> +++ b/drivers/net/phy/Kconfig
>>> @@ -288,6 +288,13 @@ config ADIN_PHY
>>>
>>>  	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
>>>  	  
>>>  	    Ethernet PHY
>>>
>>> +config AC200_PHY
>>> +	tristate "AC200 EPHY"
>>> +	depends on NVMEM
>>> +	depends on OF
>>> +	help
>>> +	  Fast ethernet PHY as found in X-Powers AC200 multi-function 
> device.
>>> +
>>>
>>>  config AMD_PHY
>>>  
>>>  	tristate "AMD PHYs"
>>>  	---help---
>>>
>>> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
>>> index 2f5c7093a65b..b0c5b91900fa 100644
>>> --- a/drivers/net/phy/Makefile
>>> +++ b/drivers/net/phy/Makefile
>>> @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
>>>
>>>  sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
>>>  obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
>>>
>>> +obj-$(CONFIG_AC200_PHY)		+= ac200.o
>>>
>>>  obj-$(CONFIG_ADIN_PHY)		+= adin.o
>>>  obj-$(CONFIG_AMD_PHY)		+= amd.o
>>>  aquantia-objs			+= aquantia_main.o
>>>
>>> diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
>>> new file mode 100644
>>> index 000000000000..3d7856ff8f91
>>> --- /dev/null
>>> +++ b/drivers/net/phy/ac200.c
>>> @@ -0,0 +1,206 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/**
>>> + * Driver for AC200 Ethernet PHY
>>> + *
>>> + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
>>> + */
>>> +
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mfd/ac200.h>
>>> +#include <linux/nvmem-consumer.h>
>>> +#include <linux/of.h>
>>> +#include <linux/phy.h>
>>> +#include <linux/platform_device.h>
>>> +
>>> +#define AC200_EPHY_ID			0x00441400
>>> +#define AC200_EPHY_ID_MASK		0x0ffffff0
>>> +
>>
>> You could use PHY_ID_MATCH_MODEL() here.
> 
> Ok.
> 
>>
>>> +/* macros for system ephy control 0 register */
>>> +#define AC200_EPHY_RESET_INVALID	BIT(0)
>>> +#define AC200_EPHY_SYSCLK_GATING	BIT(1)
>>> +
>>> +/* macros for system ephy control 1 register */
>>> +#define AC200_EPHY_E_EPHY_MII_IO_EN	BIT(0)
>>> +#define AC200_EPHY_E_LNK_LED_IO_EN	BIT(1)
>>> +#define AC200_EPHY_E_SPD_LED_IO_EN	BIT(2)
>>> +#define AC200_EPHY_E_DPX_LED_IO_EN	BIT(3)
>>> +
>>> +/* macros for ephy control register */
>>> +#define AC200_EPHY_SHUTDOWN		BIT(0)
>>> +#define AC200_EPHY_LED_POL		BIT(1)
>>> +#define AC200_EPHY_CLK_SEL		BIT(2)
>>> +#define AC200_EPHY_ADDR(x)		(((x) & 0x1F) << 4)
>>> +#define AC200_EPHY_XMII_SEL		BIT(11)
>>> +#define AC200_EPHY_CALIB(x)		(((x) & 0xF) << 12)
>>> +
>>> +struct ac200_ephy_dev {
>>> +	struct phy_driver	*ephy;
>>
>> Why embedding a pointer and not a struct phy_driver directly?
>> Then you could omit the separate allocation.
> 
> Right.
> 
>>
>> ephy is not the best naming. It may be read as a phy_device.
>> Better use phydrv.
> 
> Ok.
> 
>>
>>> +	struct regmap		*regmap;
>>> +};
>>> +
>>> +static char *ac200_phy_name = "AC200 EPHY";
>>> +
>>
>> Why not using the name directly in the assignment?
> 
> phy_driver->name is pointer. Wouldn't that mean that string is allocated on 
> stack and next time pointer is used, it will return garbage?
> 
No, it's not on the stack. No problem here.

>> And better naming: "AC200 Fast Ethernet"
> 
> Ok.
> 
>>
>>> +static int ac200_ephy_config_init(struct phy_device *phydev)
>>> +{
>>> +	const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
>>> +	unsigned int value;
>>> +	int ret;
>>> +
>>> +	phy_write(phydev, 0x1f, 0x0100);	/* Switch to Page 1 */
>>> +	phy_write(phydev, 0x12, 0x4824);	/* Disable APS */
>>> +
>>> +	phy_write(phydev, 0x1f, 0x0200);	/* Switch to Page 2 */
>>> +	phy_write(phydev, 0x18, 0x0000);	/* PHYAFE TRX optimization */
>>> +
>>> +	phy_write(phydev, 0x1f, 0x0600);	/* Switch to Page 6 */
>>> +	phy_write(phydev, 0x14, 0x708f);	/* PHYAFE TX optimization */
>>> +	phy_write(phydev, 0x13, 0xF000);	/* PHYAFE RX optimization */
>>> +	phy_write(phydev, 0x15, 0x1530);
>>> +
>>> +	phy_write(phydev, 0x1f, 0x0800);	/* Switch to Page 6 */
>>> +	phy_write(phydev, 0x18, 0x00bc);	/* PHYAFE TRX optimization */
>>> +
>>> +	phy_write(phydev, 0x1f, 0x0100);	/* switch to page 1 */
>>> +	phy_clear_bits(phydev, 0x17, BIT(3));	/* disable intelligent 
> IEEE */
>>> +
>>> +	/* next two blocks disable 802.3az IEEE */
>>> +	phy_write(phydev, 0x1f, 0x0200);	/* switch to page 2 */
>>> +	phy_write(phydev, 0x18, 0x0000);
>>> +
>>> +	phy_write(phydev, 0x1f, 0x0000);	/* switch to page 0 */
>>> +	phy_clear_bits_mmd(phydev, 0x7, 0x3c, BIT(1));
>>
>> Better use the following:
>> phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0000);
>> It makes clear that you disable advertising EEE completely.
> 
> Ok.
> 
>>
>>> +
>>> +	if (phydev->interface == PHY_INTERFACE_MODE_RMII)
>>> +		value = AC200_EPHY_XMII_SEL;
>>> +	else
>>> +		value = 0;
>>> +
>>> +	ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL,
>>> +				 AC200_EPHY_XMII_SEL, value);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>
>> I had a brief look at the spec, and it's not fully clear
>> to me what this register setting does. Does it affect the
>> MAC side and/or the PHY side?
> 
> It's my understanding that it selects interface mode on PHY. Besides datasheet 
> mentioned in cover letter, BSP drivers (one for MFD and one for PHY) are the 
> only other source of information. BSP PHY driver is located here:
> https://github.com/Allwinner-Homlet/H6-BSP4.9-linux/blob/master/drivers/net/
> phy/sunxi-ephy.c
> 
>> If it affects the PHY side, then I'd expect that the chip
>> has to talk to the PHY via the MDIO bus. Means there should
>> be a PHY register for setting MII vs. RMII.
>> In this case the setup could be very much simplified.
>> Then the PHY driver wouldn't have to be embedded in the
>> platform driver.
> 
> Actually, PHY has to be configured first through I2C and then through MDIO. I2C 
> is used to enable it (power it up), configure LED polarity, set PHY address, 
> write calibration value stored elsewhere.
> 
> Based on all available documentation I have (code and datasheet), this I2C 
> register is the only way to select MII or RMII mode.
> 
Then how and where is the PHY interface mode configured on the MAC side?
If there is no such setting, then I'd assume that this register bit
configures both sides. This leads to the question whether the interface
mode really needs to be set in the PHY driver's config_init().
If we could avoid this, then you could make the PHY driver static.

You could set the PHY interface mode as soon as the PHY interface mode
is read from DT. So why not set the interface mode at the place where
you configure the other values like PHY address?

>>
>>> +	/* FIXME: This is H6 specific */
>>> +	phy_set_bits(phydev, 0x13, BIT(12));
>>> +
>>
>> This seems to indicate that the same PHY is used in a slightly
>> different version with other Hx models. Do they use different
>> PHY ID's?
> 
> Situation is a bit complicated. Same PHY, at least with same PHY ID, is used 
> in different ways.
> 1. as part of standalone AC200 MFD IC
> 2. as part of AC200 wafer copackaged with H6 SoC wafer in same package. This 
> in theory shouldn't be any different than standalone IC, but it apparently is, 
> based on the BSP driver code.
> 3. integrated directly in SoCs like H3, H5 and V3s. There is no I2C access to 
> configuration register. Instead, it's memory mapped and slightly different.
> 
> In all cases PHY ID is same, just glue logic is different.
> 
> I asked Allwinner if above setting is really necessary for H6 and what it 
> does, but I didn't get any useful answer back.
> 
> So maybe another compatible is needed for H6.
> 
> Best regards,
> Jernej
> 
>>
>>> +	return 0;
>>> +}
>>> +
>>> +static int ac200_ephy_probe(struct platform_device *pdev)
>>> +{
>>> +	struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent);
>>> +	struct device *dev = &pdev->dev;
>>> +	struct ac200_ephy_dev *priv;
>>> +	struct nvmem_cell *calcell;
>>> +	struct phy_driver *ephy;
>>> +	u16 *caldata, calib;
>>> +	size_t callen;
>>> +	int ret;
>>> +
>>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>> +	if (!priv)
>>> +		return -ENOMEM;
>>> +
>>> +	ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
>>> +	if (!ephy)
>>> +		return -ENOMEM;
>>> +
>>> +	calcell = devm_nvmem_cell_get(dev, "calibration");
>>> +	if (IS_ERR(calcell)) {
>>> +		dev_err(dev, "Unable to find calibration data!\n");
>>> +		return PTR_ERR(calcell);
>>> +	}
>>> +
>>> +	caldata = nvmem_cell_read(calcell, &callen);
>>> +	if (IS_ERR(caldata)) {
>>> +		dev_err(dev, "Unable to read calibration data!\n");
>>> +		return PTR_ERR(caldata);
>>> +	}
>>> +
>>> +	if (callen != 2) {
>>> +		dev_err(dev, "Calibration data has wrong length: 2 != 
> %zu\n",
>>> +			callen);
>>> +		kfree(caldata);
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	calib = *caldata + 3;
>>> +	kfree(caldata);
>>> +
>>> +	ephy->phy_id = AC200_EPHY_ID;
>>> +	ephy->phy_id_mask = AC200_EPHY_ID_MASK;
>>> +	ephy->name = ac200_phy_name;
>>> +	ephy->driver_data = priv;
>>> +	ephy->soft_reset = genphy_soft_reset;
>>> +	ephy->config_init = ac200_ephy_config_init;
>>> +	ephy->suspend = genphy_suspend;
>>> +	ephy->resume = genphy_resume;
>>> +
>>> +	priv->ephy = ephy;
>>> +	priv->regmap = ac200->regmap;
>>> +	platform_set_drvdata(pdev, priv);
>>> +
>>> +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL0,
>>> +			   AC200_EPHY_RESET_INVALID |
>>> +			   AC200_EPHY_SYSCLK_GATING);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL1,
>>> +			   AC200_EPHY_E_EPHY_MII_IO_EN |
>>> +			   AC200_EPHY_E_LNK_LED_IO_EN |
>>> +			   AC200_EPHY_E_SPD_LED_IO_EN |
>>> +			   AC200_EPHY_E_DPX_LED_IO_EN);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	ret = regmap_write(ac200->regmap, AC200_EPHY_CTL,
>>> +			   AC200_EPHY_LED_POL |
>>> +			   AC200_EPHY_CLK_SEL |
>>> +			   AC200_EPHY_ADDR(1) |
>>> +			   AC200_EPHY_CALIB(calib));
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	ret = phy_driver_register(priv->ephy, THIS_MODULE);
>>> +	if (ret) {
>>> +		dev_err(dev, "Unable to register phy\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int ac200_ephy_remove(struct platform_device *pdev)
>>> +{
>>> +	struct ac200_ephy_dev *priv = platform_get_drvdata(pdev);
>>> +
>>> +	phy_driver_unregister(priv->ephy);
>>> +
>>> +	regmap_write(priv->regmap, AC200_EPHY_CTL, AC200_EPHY_SHUTDOWN);
>>> +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1, 0);
>>> +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0, 0);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct of_device_id ac200_ephy_match[] = {
>>> +	{ .compatible = "x-powers,ac200-ephy" },
>>> +	{ /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, ac200_ephy_match);
>>> +
>>> +static struct platform_driver ac200_ephy_driver = {
>>> +	.probe		= ac200_ephy_probe,
>>> +	.remove		= ac200_ephy_remove,
>>> +	.driver		= {
>>> +		.name		= "ac200-ephy",
>>> +		.of_match_table	= ac200_ephy_match,
>>> +	},
>>> +};
>>> +module_platform_driver(ac200_ephy_driver);
>>> +
>>> +MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
>>> +MODULE_DESCRIPTION("AC200 Ethernet PHY driver");
>>> +MODULE_LICENSE("GPL");
> 
> 
> 
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-17 16:15     ` Jernej Škrabec
  2020-04-17 16:16       ` Heiner Kallweit
@ 2020-04-17 17:01       ` Andrew Lunn
  1 sibling, 0 replies; 16+ messages in thread
From: Andrew Lunn @ 2020-04-17 17:01 UTC (permalink / raw)
  To: Jernej Škrabec
  Cc: robh+dt, f.fainelli, Heiner Kallweit, devicetree, netdev, linux,
	mripard, linux-kernel, wens, lee.jones, davem, linux-arm-kernel

> > You could use PHY_ID_MATCH_MODEL() here.
> 
> Hm... This doesn't work with dynamically allocated memory, right?

I would suggest we get the right structure first, then figure out
details like this.

Depending on when the device will respond to MDIO, we might be able to
make this a normal PHY driver. It then probes in the normal way, and
all the horrible dependencies you talked about, module loading order,
etc all go away.

There were 3 things you talked about to make the PHY usable:

1) Clock
2) Reset
3) Must be enabled and configured through I2C

We already have the concept of a PHY device having a reset controller
as a property. e.g. Documentation/devicetree/bindings/net/ethernet-phy.yaml

resets = <&rst 8>;

So if the MFD exports a reset controller, we can control that from the
PHY core. If the MFD has not probed yet, the reset core code will
return EPROBE_DEFFER, and the PHY probe will get differed until late.
That solves a lot of probe order issues.

The clock can be handled in two different ways, depending on if the
clock needs to be ticking to read the PHY ID registers. If it does
need to be ticking, we add support for a clks property in just the
same way we have support for the reset property. The PHY core will
clk_enable_prepare() the clock before probing the PHY. If the clock is
not needed for probing, the PHY driver can enable the clock as needed.

The last part, Must be enabled and configured through I2C, we need to
look at the details. It could be the reset controller also enabled the
PHY. If that is enough that the PHY then probes, the PHY driver can
then configure the PHY as needed via i2c.

     Andrew

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY
  2020-04-17 16:29       ` Heiner Kallweit
@ 2020-04-17 17:15         ` Jernej Škrabec
  0 siblings, 0 replies; 16+ messages in thread
From: Jernej Škrabec @ 2020-04-17 17:15 UTC (permalink / raw)
  To: robh+dt, andrew, f.fainelli, Heiner Kallweit
  Cc: mripard, wens, lee.jones, linux, davem, devicetree,
	linux-arm-kernel, linux-kernel, netdev

Dne petek, 17. april 2020 ob 18:29:04 CEST je Heiner Kallweit napisal(a):
> On 17.04.2020 18:03, Jernej Škrabec wrote:
> > Dne četrtek, 16. april 2020 ob 22:18:52 CEST je Heiner Kallweit 
napisal(a):
> >> On 16.04.2020 20:57, Jernej Skrabec wrote:
> >>> AC200 MFD IC supports Fast Ethernet PHY. Add a driver for it.
> >>> 
> >>> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> >>> ---
> >>> 
> >>>  drivers/net/phy/Kconfig  |   7 ++
> >>>  drivers/net/phy/Makefile |   1 +
> >>>  drivers/net/phy/ac200.c  | 206 +++++++++++++++++++++++++++++++++++++++
> >>>  3 files changed, 214 insertions(+)
> >>>  create mode 100644 drivers/net/phy/ac200.c
> >>> 
> >>> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> >>> index 3fa33d27eeba..16af69f69eaf 100644
> >>> --- a/drivers/net/phy/Kconfig
> >>> +++ b/drivers/net/phy/Kconfig
> >>> @@ -288,6 +288,13 @@ config ADIN_PHY
> >>> 
> >>>  	  - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
> >>>  	  
> >>>  	    Ethernet PHY
> >>> 
> >>> +config AC200_PHY
> >>> +	tristate "AC200 EPHY"
> >>> +	depends on NVMEM
> >>> +	depends on OF
> >>> +	help
> >>> +	  Fast ethernet PHY as found in X-Powers AC200 multi-function
> > 
> > device.
> > 
> >>> +
> >>> 
> >>>  config AMD_PHY
> >>>  
> >>>  	tristate "AMD PHYs"
> >>>  	---help---
> >>> 
> >>> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> >>> index 2f5c7093a65b..b0c5b91900fa 100644
> >>> --- a/drivers/net/phy/Makefile
> >>> +++ b/drivers/net/phy/Makefile
> >>> @@ -53,6 +53,7 @@ obj-$(CONFIG_SFP)		+= sfp.o
> >>> 
> >>>  sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
> >>>  obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
> >>> 
> >>> +obj-$(CONFIG_AC200_PHY)		+= ac200.o
> >>> 
> >>>  obj-$(CONFIG_ADIN_PHY)		+= adin.o
> >>>  obj-$(CONFIG_AMD_PHY)		+= amd.o
> >>>  aquantia-objs			+= aquantia_main.o
> >>> 
> >>> diff --git a/drivers/net/phy/ac200.c b/drivers/net/phy/ac200.c
> >>> new file mode 100644
> >>> index 000000000000..3d7856ff8f91
> >>> --- /dev/null
> >>> +++ b/drivers/net/phy/ac200.c
> >>> @@ -0,0 +1,206 @@
> >>> +// SPDX-License-Identifier: GPL-2.0+
> >>> +/**
> >>> + * Driver for AC200 Ethernet PHY
> >>> + *
> >>> + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
> >>> + */
> >>> +
> >>> +#include <linux/kernel.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/mfd/ac200.h>
> >>> +#include <linux/nvmem-consumer.h>
> >>> +#include <linux/of.h>
> >>> +#include <linux/phy.h>
> >>> +#include <linux/platform_device.h>
> >>> +
> >>> +#define AC200_EPHY_ID			0x00441400
> >>> +#define AC200_EPHY_ID_MASK		0x0ffffff0
> >>> +
> >> 
> >> You could use PHY_ID_MATCH_MODEL() here.
> > 
> > Ok.
> > 
> >>> +/* macros for system ephy control 0 register */
> >>> +#define AC200_EPHY_RESET_INVALID	BIT(0)
> >>> +#define AC200_EPHY_SYSCLK_GATING	BIT(1)
> >>> +
> >>> +/* macros for system ephy control 1 register */
> >>> +#define AC200_EPHY_E_EPHY_MII_IO_EN	BIT(0)
> >>> +#define AC200_EPHY_E_LNK_LED_IO_EN	BIT(1)
> >>> +#define AC200_EPHY_E_SPD_LED_IO_EN	BIT(2)
> >>> +#define AC200_EPHY_E_DPX_LED_IO_EN	BIT(3)
> >>> +
> >>> +/* macros for ephy control register */
> >>> +#define AC200_EPHY_SHUTDOWN		BIT(0)
> >>> +#define AC200_EPHY_LED_POL		BIT(1)
> >>> +#define AC200_EPHY_CLK_SEL		BIT(2)
> >>> +#define AC200_EPHY_ADDR(x)		(((x) & 0x1F) << 4)
> >>> +#define AC200_EPHY_XMII_SEL		BIT(11)
> >>> +#define AC200_EPHY_CALIB(x)		(((x) & 0xF) << 12)
> >>> +
> >>> +struct ac200_ephy_dev {
> >>> +	struct phy_driver	*ephy;
> >> 
> >> Why embedding a pointer and not a struct phy_driver directly?
> >> Then you could omit the separate allocation.
> > 
> > Right.
> > 
> >> ephy is not the best naming. It may be read as a phy_device.
> >> Better use phydrv.
> > 
> > Ok.
> > 
> >>> +	struct regmap		*regmap;
> >>> +};
> >>> +
> >>> +static char *ac200_phy_name = "AC200 EPHY";
> >>> +
> >> 
> >> Why not using the name directly in the assignment?
> > 
> > phy_driver->name is pointer. Wouldn't that mean that string is allocated
> > on
> > stack and next time pointer is used, it will return garbage?
> 
> No, it's not on the stack. No problem here.
> 
> >> And better naming: "AC200 Fast Ethernet"
> > 
> > Ok.
> > 
> >>> +static int ac200_ephy_config_init(struct phy_device *phydev)
> >>> +{
> >>> +	const struct ac200_ephy_dev *priv = phydev->drv->driver_data;
> >>> +	unsigned int value;
> >>> +	int ret;
> >>> +
> >>> +	phy_write(phydev, 0x1f, 0x0100);	/* Switch to Page 1 */
> >>> +	phy_write(phydev, 0x12, 0x4824);	/* Disable APS */
> >>> +
> >>> +	phy_write(phydev, 0x1f, 0x0200);	/* Switch to Page 2 */
> >>> +	phy_write(phydev, 0x18, 0x0000);	/* PHYAFE TRX optimization */
> >>> +
> >>> +	phy_write(phydev, 0x1f, 0x0600);	/* Switch to Page 6 */
> >>> +	phy_write(phydev, 0x14, 0x708f);	/* PHYAFE TX optimization */
> >>> +	phy_write(phydev, 0x13, 0xF000);	/* PHYAFE RX optimization */
> >>> +	phy_write(phydev, 0x15, 0x1530);
> >>> +
> >>> +	phy_write(phydev, 0x1f, 0x0800);	/* Switch to Page 6 */
> >>> +	phy_write(phydev, 0x18, 0x00bc);	/* PHYAFE TRX optimization */
> >>> +
> >>> +	phy_write(phydev, 0x1f, 0x0100);	/* switch to page 1 */
> >>> +	phy_clear_bits(phydev, 0x17, BIT(3));	/* disable intelligent
> > 
> > IEEE */
> > 
> >>> +
> >>> +	/* next two blocks disable 802.3az IEEE */
> >>> +	phy_write(phydev, 0x1f, 0x0200);	/* switch to page 2 */
> >>> +	phy_write(phydev, 0x18, 0x0000);
> >>> +
> >>> +	phy_write(phydev, 0x1f, 0x0000);	/* switch to page 0 */
> >>> +	phy_clear_bits_mmd(phydev, 0x7, 0x3c, BIT(1));
> >> 
> >> Better use the following:
> >> phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0000);
> >> It makes clear that you disable advertising EEE completely.
> > 
> > Ok.
> > 
> >>> +
> >>> +	if (phydev->interface == PHY_INTERFACE_MODE_RMII)
> >>> +		value = AC200_EPHY_XMII_SEL;
> >>> +	else
> >>> +		value = 0;
> >>> +
> >>> +	ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL,
> >>> +				 AC200_EPHY_XMII_SEL, value);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >> 
> >> I had a brief look at the spec, and it's not fully clear
> >> to me what this register setting does. Does it affect the
> >> MAC side and/or the PHY side?
> > 
> > It's my understanding that it selects interface mode on PHY. Besides
> > datasheet mentioned in cover letter, BSP drivers (one for MFD and one for
> > PHY) are the only other source of information. BSP PHY driver is located
> > here:
> > https://github.com/Allwinner-Homlet/H6-BSP4.9-linux/blob/master/drivers/ne
> > t/ phy/sunxi-ephy.c
> > 
> >> If it affects the PHY side, then I'd expect that the chip
> >> has to talk to the PHY via the MDIO bus. Means there should
> >> be a PHY register for setting MII vs. RMII.
> >> In this case the setup could be very much simplified.
> >> Then the PHY driver wouldn't have to be embedded in the
> >> platform driver.
> > 
> > Actually, PHY has to be configured first through I2C and then through
> > MDIO. I2C is used to enable it (power it up), configure LED polarity, set
> > PHY address, write calibration value stored elsewhere.
> > 
> > Based on all available documentation I have (code and datasheet), this I2C
> > register is the only way to select MII or RMII mode.
> 
> Then how and where is the PHY interface mode configured on the MAC side?
> If there is no such setting, then I'd assume that this register bit
> configures both sides. This leads to the question whether the interface
> mode really needs to be set in the PHY driver's config_init().
> If we could avoid this, then you could make the PHY driver static.

Check patch 4. There is emac node added with property phy-mode = "rmii"; 
AC200 and H6 are internally connected through RMII interface.

Note that MAC has multiplexed interface and can either uses this copackaged 
PHY or external PHY. External PHY usually uses RGMII interface.

In 99% cases, this PHY driver will be used for copackaged AC200 with H6 SoC, 
which means it will be used in RMII mode. Even if someone uses standalone 
AC200 IC with mainline Linux, will probably still use RMII. But there is still 
very small chance that someone will use it in MII mode.

Anyway, if that special setting for H6 proves important, we will still need a 
way to convey that information from platform driver to PHY. Easiest way to do 
that is through driver_data.

> 
> You could set the PHY interface mode as soon as the PHY interface mode
> is read from DT. So why not set the interface mode at the place where
> you configure the other values like PHY address?

PHY interface mode is actually set on MAC side in DT. This PHY has acutally 
programmable PHY address through I2C. Currently I hardcoded it to 1.

As I explained in cover letter, I don't really know how to properly present 
this device in DT. Based on current DT documentation, PHY node would have to 
be child node of mdio bus node and MFD node at the same time which is not 
possible.

> 
> >>> +	/* FIXME: This is H6 specific */
> >>> +	phy_set_bits(phydev, 0x13, BIT(12));
> >>> +
> >> 
> >> This seems to indicate that the same PHY is used in a slightly
> >> different version with other Hx models. Do they use different
> >> PHY ID's?
> > 
> > Situation is a bit complicated. Same PHY, at least with same PHY ID, is
> > used in different ways.
> > 1. as part of standalone AC200 MFD IC
> > 2. as part of AC200 wafer copackaged with H6 SoC wafer in same package.
> > This in theory shouldn't be any different than standalone IC, but it
> > apparently is, based on the BSP driver code.
> > 3. integrated directly in SoCs like H3, H5 and V3s. There is no I2C access
> > to configuration register. Instead, it's memory mapped and slightly
> > different.
> > 
> > In all cases PHY ID is same, just glue logic is different.
> > 
> > I asked Allwinner if above setting is really necessary for H6 and what it
> > does, but I didn't get any useful answer back.
> > 
> > So maybe another compatible is needed for H6.
> > 
> > Best regards,
> > Jernej
> > 
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int ac200_ephy_probe(struct platform_device *pdev)
> >>> +{
> >>> +	struct ac200_dev *ac200 = dev_get_drvdata(pdev->dev.parent);
> >>> +	struct device *dev = &pdev->dev;
> >>> +	struct ac200_ephy_dev *priv;
> >>> +	struct nvmem_cell *calcell;
> >>> +	struct phy_driver *ephy;
> >>> +	u16 *caldata, calib;
> >>> +	size_t callen;
> >>> +	int ret;
> >>> +
> >>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> >>> +	if (!priv)
> >>> +		return -ENOMEM;
> >>> +
> >>> +	ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
> >>> +	if (!ephy)
> >>> +		return -ENOMEM;
> >>> +
> >>> +	calcell = devm_nvmem_cell_get(dev, "calibration");
> >>> +	if (IS_ERR(calcell)) {
> >>> +		dev_err(dev, "Unable to find calibration data!\n");
> >>> +		return PTR_ERR(calcell);
> >>> +	}
> >>> +
> >>> +	caldata = nvmem_cell_read(calcell, &callen);
> >>> +	if (IS_ERR(caldata)) {
> >>> +		dev_err(dev, "Unable to read calibration data!\n");
> >>> +		return PTR_ERR(caldata);
> >>> +	}
> >>> +
> >>> +	if (callen != 2) {
> >>> +		dev_err(dev, "Calibration data has wrong length: 2 !=
> > 
> > %zu\n",
> > 
> >>> +			callen);
> >>> +		kfree(caldata);
> >>> +		return -EINVAL;
> >>> +	}
> >>> +
> >>> +	calib = *caldata + 3;
> >>> +	kfree(caldata);
> >>> +
> >>> +	ephy->phy_id = AC200_EPHY_ID;
> >>> +	ephy->phy_id_mask = AC200_EPHY_ID_MASK;
> >>> +	ephy->name = ac200_phy_name;
> >>> +	ephy->driver_data = priv;
> >>> +	ephy->soft_reset = genphy_soft_reset;
> >>> +	ephy->config_init = ac200_ephy_config_init;
> >>> +	ephy->suspend = genphy_suspend;
> >>> +	ephy->resume = genphy_resume;
> >>> +
> >>> +	priv->ephy = ephy;
> >>> +	priv->regmap = ac200->regmap;
> >>> +	platform_set_drvdata(pdev, priv);
> >>> +
> >>> +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL0,
> >>> +			   AC200_EPHY_RESET_INVALID |
> >>> +			   AC200_EPHY_SYSCLK_GATING);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	ret = regmap_write(ac200->regmap, AC200_SYS_EPHY_CTL1,
> >>> +			   AC200_EPHY_E_EPHY_MII_IO_EN |
> >>> +			   AC200_EPHY_E_LNK_LED_IO_EN |
> >>> +			   AC200_EPHY_E_SPD_LED_IO_EN |
> >>> +			   AC200_EPHY_E_DPX_LED_IO_EN);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	ret = regmap_write(ac200->regmap, AC200_EPHY_CTL,
> >>> +			   AC200_EPHY_LED_POL |
> >>> +			   AC200_EPHY_CLK_SEL |
> >>> +			   AC200_EPHY_ADDR(1) |
> >>> +			   AC200_EPHY_CALIB(calib));
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	ret = phy_driver_register(priv->ephy, THIS_MODULE);
> >>> +	if (ret) {
> >>> +		dev_err(dev, "Unable to register phy\n");
> >>> +		return ret;
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int ac200_ephy_remove(struct platform_device *pdev)
> >>> +{
> >>> +	struct ac200_ephy_dev *priv = platform_get_drvdata(pdev);
> >>> +
> >>> +	phy_driver_unregister(priv->ephy);
> >>> +
> >>> +	regmap_write(priv->regmap, AC200_EPHY_CTL, AC200_EPHY_SHUTDOWN);
> >>> +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL1, 0);
> >>> +	regmap_write(priv->regmap, AC200_SYS_EPHY_CTL0, 0);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static const struct of_device_id ac200_ephy_match[] = {
> >>> +	{ .compatible = "x-powers,ac200-ephy" },
> >>> +	{ /* sentinel */ }
> >>> +};
> >>> +MODULE_DEVICE_TABLE(of, ac200_ephy_match);
> >>> +
> >>> +static struct platform_driver ac200_ephy_driver = {
> >>> +	.probe		= ac200_ephy_probe,
> >>> +	.remove		= ac200_ephy_remove,
> >>> +	.driver		= {
> >>> +		.name		= "ac200-ephy",
> >>> +		.of_match_table	= ac200_ephy_match,
> >>> +	},
> >>> +};
> >>> +module_platform_driver(ac200_ephy_driver);
> >>> +
> >>> +MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
> >>> +MODULE_DESCRIPTION("AC200 Ethernet PHY driver");
> >>> +MODULE_LICENSE("GPL");





^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 1/4] mfd: Add support for AC200
  2020-04-16 18:57 ` [RFC PATCH 1/4] mfd: Add support for AC200 Jernej Skrabec
@ 2020-04-24  8:05   ` Lee Jones
  0 siblings, 0 replies; 16+ messages in thread
From: Lee Jones @ 2020-04-24  8:05 UTC (permalink / raw)
  To: Jernej Skrabec
  Cc: robh+dt, andrew, f.fainelli, hkallweit1, mripard, wens, linux,
	davem, devicetree, linux-arm-kernel, linux-kernel, netdev

On Thu, 16 Apr 2020, Jernej Skrabec wrote:

> This adds support for AC200 multi functional IC. It can be packaged
> standalone or copackaged with SoC like Allwinner H6.
> 
> It has analog audio codec, CVBS encoder, RTC and Fast Ethernet PHY.
> Documentation also mention eFuses, but it seems that it's not used in
> copackaged variant.
> 
> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
>  drivers/mfd/Kconfig       |   9 ++
>  drivers/mfd/Makefile      |   1 +
>  drivers/mfd/ac200.c       | 188 ++++++++++++++++++++++++++++++++++
>  include/linux/mfd/ac200.h | 210 ++++++++++++++++++++++++++++++++++++++
>  4 files changed, 408 insertions(+)
>  create mode 100644 drivers/mfd/ac200.c
>  create mode 100644 include/linux/mfd/ac200.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 0a59249198d3..1d6b7f3ae193 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -178,6 +178,15 @@ config MFD_AC100
>  	  This driver include only the core APIs. You have to select individual
>  	  components like codecs or RTC under the corresponding menus.
>  
> +config MFD_AC200
> +	tristate "X-Powers AC200"
> +	select MFD_CORE
> +	depends on I2C
> +	help
> +	  If you say Y here you get support for the X-Powers AC200 IC.

Please describe the IC here.

> +	  This driver include only the core APIs. You have to select individual
> +	  components like Ethernet PHY or RTC under the corresponding menus.
> +
>  config MFD_AXP20X
>  	tristate
>  	select MFD_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index f935d10cbf0f..a20407290d6f 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -142,6 +142,7 @@ obj-$(CONFIG_MFD_DA9052_SPI)	+= da9052-spi.o
>  obj-$(CONFIG_MFD_DA9052_I2C)	+= da9052-i2c.o
>  
>  obj-$(CONFIG_MFD_AC100)		+= ac100.o
> +obj-$(CONFIG_MFD_AC200)		+= ac200.o
>  obj-$(CONFIG_MFD_AXP20X)	+= axp20x.o
>  obj-$(CONFIG_MFD_AXP20X_I2C)	+= axp20x-i2c.o
>  obj-$(CONFIG_MFD_AXP20X_RSB)	+= axp20x-rsb.o
> diff --git a/drivers/mfd/ac200.c b/drivers/mfd/ac200.c
> new file mode 100644
> index 000000000000..cf2710b84879
> --- /dev/null
> +++ b/drivers/mfd/ac200.c
> @@ -0,0 +1,188 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * MFD core driver for X-Powers' AC200 IC
> + *
> + * The AC200 is a chip which is co-packaged with Allwinner H6 SoC and
> + * includes analog audio codec, analog TV encoder, ethernet PHY, eFuse
> + * and RTC.
> + *
> + * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>

This usually goes higher in the header comment.

> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/ac200.h>
> +#include <linux/module.h>
> +#include <linux/of.h>

Alphabetical.

> +/* Interrupts */
> +#define AC200_IRQ_RTC  0
> +#define AC200_IRQ_EPHY 1
> +#define AC200_IRQ_TVE  2
> +
> +/* IRQ enable register */
> +#define AC200_SYS_IRQ_ENABLE_OUT_EN BIT(15)
> +#define AC200_SYS_IRQ_ENABLE_RTC    BIT(12)
> +#define AC200_SYS_IRQ_ENABLE_EPHY   BIT(8)
> +#define AC200_SYS_IRQ_ENABLE_TVE    BIT(4)
> +
> +static const struct regmap_range_cfg ac200_range_cfg[] = {
> +	{
> +		.range_min = AC200_SYS_VERSION,
> +		.range_max = AC200_IC_CHARA1,
> +		.selector_reg = AC200_TWI_REG_ADDR_H,
> +		.selector_mask = 0xff,
> +		.selector_shift = 0,
> +		.window_start = 0,
> +		.window_len = 256,
> +	}
> +};
> +
> +static const struct regmap_config ac200_regmap_config = {
> +	.reg_bits	= 8,
> +	.val_bits	= 16,
> +	.ranges		= ac200_range_cfg,
> +	.num_ranges	= ARRAY_SIZE(ac200_range_cfg),
> +	.max_register	= AC200_IC_CHARA1,
> +};
> +
> +static const struct regmap_irq ac200_regmap_irqs[] = {
> +	REGMAP_IRQ_REG(AC200_IRQ_RTC,  0, AC200_SYS_IRQ_ENABLE_RTC),
> +	REGMAP_IRQ_REG(AC200_IRQ_EPHY, 0, AC200_SYS_IRQ_ENABLE_EPHY),
> +	REGMAP_IRQ_REG(AC200_IRQ_TVE,  0, AC200_SYS_IRQ_ENABLE_TVE),
> +};
> +
> +static const struct regmap_irq_chip ac200_regmap_irq_chip = {
> +	.name			= "ac200_irq_chip",
> +	.status_base		= AC200_SYS_IRQ_STATUS,
> +	.mask_base		= AC200_SYS_IRQ_ENABLE,
> +	.mask_invert		= true,
> +	.irqs			= ac200_regmap_irqs,
> +	.num_irqs		= ARRAY_SIZE(ac200_regmap_irqs),
> +	.num_regs		= 1,
> +};
> +
> +static const struct resource ephy_resource[] = {
> +	DEFINE_RES_IRQ(AC200_IRQ_EPHY),
> +};
> +
> +static const struct mfd_cell ac200_cells[] = {
> +	{
> +		.name		= "ac200-ephy",
> +		.num_resources	= ARRAY_SIZE(ephy_resource),
> +		.resources	= ephy_resource,
> +		.of_compatible	= "x-powers,ac200-ephy",
> +	},
> +};

Where are the reset of the devices?

> +static int ac200_i2c_probe(struct i2c_client *i2c,
> +			   const struct i2c_device_id *id)
> +{
> +	struct device *dev = &i2c->dev;
> +	struct ac200_dev *ac200;

struct ac200_ddata *ddata;

> +	int ret;
> +
> +	ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
> +	if (!ac200)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(i2c, ac200);
> +
> +	ac200->regmap = devm_regmap_init_i2c(i2c, &ac200_regmap_config);
> +	if (IS_ERR(ac200->regmap)) {
> +		dev_err(dev, "regmap init failed\n");
> +		return PTR_ERR(ac200->regmap);
> +	}
> +
> +	ac200->clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(ac200->clk)) {
> +		dev_err(dev, "Can't obtain the clock!\n");
> +		return PTR_ERR(ac200->clk);
> +	}
> +
> +	ret = clk_prepare_enable(ac200->clk);
> +	if (ret)
> +		return ret;
> +
> +	/* do a reset to put chip in a known state */

If you define the magic values here, this comment become superfluous.

> +	ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
> +	if (ret)
> +		goto err_free_clk;
> +
> +	ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1);
> +	if (ret)
> +		goto err_free_clk;
> +
> +	/* enable interrupt pin */

This comment can be dropped.

> +	ret = regmap_write(ac200->regmap, AC200_SYS_IRQ_ENABLE,
> +			   AC200_SYS_IRQ_ENABLE_OUT_EN);
> +	if (ret)
> +		goto err_free_clk;
> +
> +	ret = regmap_add_irq_chip(ac200->regmap, i2c->irq, IRQF_ONESHOT, 0,
> +				  &ac200_regmap_irq_chip, &ac200->regmap_irqc);
> +	if (ret)
> +		goto err_free_clk;
> +
> +	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
> +				   ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
> +	if (ret) {
> +		dev_err(dev, "failed to add MFD devices: %d\n", ret);

"Failed to register child devices"

> +		goto err_del_irq_chip;
> +	}

Do you want to leave the clock running when you leave?

Seems wasteful.

> +	return 0;
> +
> +err_del_irq_chip:
> +	regmap_del_irq_chip(i2c->irq, ac200->regmap_irqc);

Use the devm_* version, then you do not have to clear up.

> +err_free_clk:
> +	clk_disable_unprepare(ac200->clk);
> +
> +	return ret;
> +}
> +
> +static int ac200_i2c_remove(struct i2c_client *i2c)
> +{
> +	struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
> +
> +	/* put chip in reset state */

Use defines, then remove the comment.

> +	regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
> +
> +	mfd_remove_devices(&i2c->dev);

Use devm_* instead.

> +	regmap_del_irq_chip(i2c->irq, ac200->regmap_irqc);

Use devm_* instead.

> +	clk_disable_unprepare(ac200->clk);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ac200_ids[] = {
> +	{ "ac200", },
> +	{ /* sentinel */ }

No need for this comment.

In fact, no need for this table.  It can be removed.

> +};
> +MODULE_DEVICE_TABLE(i2c, ac200_ids);
> +
> +static const struct of_device_id ac200_of_match[] = {
> +	{ .compatible = "x-powers,ac200" },
> +	{ /* sentinel */ }

No need for this comment.

> +};
> +MODULE_DEVICE_TABLE(of, ac200_of_match);
> +
> +static struct i2c_driver ac200_i2c_driver = {
> +	.driver = {
> +		.name	= "ac200",
> +		.of_match_table	= of_match_ptr(ac200_of_match),
> +	},
> +	.probe	= ac200_i2c_probe,
> +	.remove = ac200_i2c_remove,
> +	.id_table = ac200_ids,
> +};
> +module_i2c_driver(ac200_i2c_driver);
> +
> +MODULE_DESCRIPTION("MFD core driver for AC200");

"Parent driver ..."

> +MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/ac200.h b/include/linux/mfd/ac200.h
> new file mode 100644
> index 000000000000..f75baf0d910c
> --- /dev/null
> +++ b/include/linux/mfd/ac200.h
> @@ -0,0 +1,210 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * AC200 register list
> + *
> + * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@siol.net>

This usually sits higher.

> + */
> +
> +#ifndef __LINUX_MFD_AC200_H
> +#define __LINUX_MFD_AC200_H
> +
> +#include <linux/clk.h>
> +#include <linux/regmap.h>
> +
> +/* interface registers (can be accessed from any page) */

Please use correct grammar in comments.

"Interface"

> +#define AC200_TWI_CHANGE_TO_RSB		0x3E
> +#define AC200_TWI_PAD_DELAY		0xC4
> +#define AC200_TWI_REG_ADDR_H		0xFE
> +
> +/* General registers */
> +#define AC200_SYS_VERSION		0x0000
> +#define AC200_SYS_CONTROL		0x0002
> +#define AC200_SYS_IRQ_ENABLE		0x0004
> +#define AC200_SYS_IRQ_STATUS		0x0006
> +#define AC200_SYS_CLK_CTL		0x0008
> +#define AC200_SYS_DLDO_OSC_CTL		0x000A
> +#define AC200_SYS_PLL_CTL0		0x000C
> +#define AC200_SYS_PLL_CTL1		0x000E
> +#define AC200_SYS_AUDIO_CTL0		0x0010
> +#define AC200_SYS_AUDIO_CTL1		0x0012
> +#define AC200_SYS_EPHY_CTL0		0x0014
> +#define AC200_SYS_EPHY_CTL1		0x0016
> +#define AC200_SYS_TVE_CTL0		0x0018
> +#define AC200_SYS_TVE_CTL1		0x001A
> +
> +/* Audio Codec registers */
> +#define AC200_AC_SYS_CLK_CTL		0x2000
> +#define AC200_SYS_MOD_RST		0x2002
> +#define AC200_SYS_SAMP_CTL		0x2004
> +#define AC200_I2S_CTL			0x2100
> +#define AC200_I2S_CLK			0x2102
> +#define AC200_I2S_FMT0			0x2104
> +#define AC200_I2S_FMT1			0x2108
> +#define AC200_I2S_MIX_SRC		0x2114
> +#define AC200_I2S_MIX_GAIN		0x2116
> +#define AC200_I2S_DACDAT_DVC		0x2118
> +#define AC200_I2S_ADCDAT_DVC		0x211A
> +#define AC200_AC_DAC_DPC		0x2200
> +#define AC200_AC_DAC_MIX_SRC		0x2202
> +#define AC200_AC_DAC_MIX_GAIN		0x2204
> +#define AC200_DACA_OMIXER_CTRL		0x2220
> +#define AC200_OMIXER_SR			0x2222
> +#define AC200_LINEOUT_CTRL		0x2224
> +#define AC200_AC_ADC_DPC		0x2300
> +#define AC200_MBIAS_CTRL		0x2310
> +#define AC200_ADC_MIC_CTRL		0x2320
> +#define AC200_ADCMIXER_SR		0x2322
> +#define AC200_ANALOG_TUNING0		0x232A
> +#define AC200_ANALOG_TUNING1		0x232C
> +#define AC200_AC_AGC_SEL		0x2480
> +#define AC200_ADC_DAPLCTRL		0x2500
> +#define AC200_ADC_DAPRCTRL		0x2502
> +#define AC200_ADC_DAPLSTA		0x2504
> +#define AC200_ADC_DAPRSTA		0x2506
> +#define AC200_ADC_DAPLTL		0x2508
> +#define AC200_ADC_DAPRTL		0x250A
> +#define AC200_ADC_DAPLHAC		0x250C
> +#define AC200_ADC_DAPLLAC		0x250E
> +#define AC200_ADC_DAPRHAC		0x2510
> +#define AC200_ADC_DAPRLAC		0x2512
> +#define AC200_ADC_DAPLDT		0x2514
> +#define AC200_ADC_DAPLAT		0x2516
> +#define AC200_ADC_DAPRDT		0x2518
> +#define AC200_ADC_DAPRAT		0x251A
> +#define AC200_ADC_DAPNTH		0x251C
> +#define AC200_ADC_DAPLHNAC		0x251E
> +#define AC200_ADC_DAPLLNAC		0x2520
> +#define AC200_ADC_DAPRHNAC		0x2522
> +#define AC200_ADC_DAPRLNAC		0x2524
> +#define AC200_AC_DAPHHPFC		0x2526
> +#define AC200_AC_DAPLHPFC		0x2528
> +#define AC200_AC_DAPOPT			0x252A
> +#define AC200_AC_DAC_DAPCTRL		0x3000
> +#define AC200_AC_DRC_HHPFC		0x3002
> +#define AC200_AC_DRC_LHPFC		0x3004
> +#define AC200_AC_DRC_CTRL		0x3006
> +#define AC200_AC_DRC_LPFHAT		0x3008
> +#define AC200_AC_DRC_LPFLAT		0x300A
> +#define AC200_AC_DRC_RPFHAT		0x300C
> +#define AC200_AC_DRC_RPFLAT		0x300E
> +#define AC200_AC_DRC_LPFHRT		0x3010
> +#define AC200_AC_DRC_LPFLRT		0x3012
> +#define AC200_AC_DRC_RPFHRT		0x3014
> +#define AC200_AC_DRC_RPFLRT		0x3016
> +#define AC200_AC_DRC_LRMSHAT		0x3018
> +#define AC200_AC_DRC_LRMSLAT		0x301A
> +#define AC200_AC_DRC_RRMSHAT		0x301C
> +#define AC200_AC_DRC_RRMSLAT		0x301E
> +#define AC200_AC_DRC_HCT		0x3020
> +#define AC200_AC_DRC_LCT		0x3022
> +#define AC200_AC_DRC_HKC		0x3024
> +#define AC200_AC_DRC_LKC		0x3026
> +#define AC200_AC_DRC_HOPC		0x3028
> +#define AC200_AC_DRC_LOPC		0x302A
> +#define AC200_AC_DRC_HLT		0x302C
> +#define AC200_AC_DRC_LLT		0x302E
> +#define AC200_AC_DRC_HKI		0x3030
> +#define AC200_AC_DRC_LKI		0x3032
> +#define AC200_AC_DRC_HOPL		0x3034
> +#define AC200_AC_DRC_LOPL		0x3036
> +#define AC200_AC_DRC_HET		0x3038
> +#define AC200_AC_DRC_LET		0x303A
> +#define AC200_AC_DRC_HKE		0x303C
> +#define AC200_AC_DRC_LKE		0x303E
> +#define AC200_AC_DRC_HOPE		0x3040
> +#define AC200_AC_DRC_LOPE		0x3042
> +#define AC200_AC_DRC_HKN		0x3044
> +#define AC200_AC_DRC_LKN		0x3046
> +#define AC200_AC_DRC_SFHAT		0x3048
> +#define AC200_AC_DRC_SFLAT		0x304A
> +#define AC200_AC_DRC_SFHRT		0x304C
> +#define AC200_AC_DRC_SFLRT		0x304E
> +#define AC200_AC_DRC_MXGHS		0x3050
> +#define AC200_AC_DRC_MXGLS		0x3052
> +#define AC200_AC_DRC_MNGHS		0x3054
> +#define AC200_AC_DRC_MNGLS		0x3056
> +#define AC200_AC_DRC_EPSHC		0x3058
> +#define AC200_AC_DRC_EPSLC		0x305A
> +#define AC200_AC_DRC_HPFHGAIN		0x305E
> +#define AC200_AC_DRC_HPFLGAIN		0x3060
> +#define AC200_AC_DRC_BISTCR		0x3100
> +#define AC200_AC_DRC_BISTST		0x3102
> +
> +/* TVE registers */
> +#define AC200_TVE_CTL0			0x4000
> +#define AC200_TVE_CTL1			0x4002
> +#define AC200_TVE_MOD0			0x4004
> +#define AC200_TVE_MOD1			0x4006
> +#define AC200_TVE_DAC_CFG0		0x4008
> +#define AC200_TVE_DAC_CFG1		0x400A
> +#define AC200_TVE_YC_DELAY		0x400C
> +#define AC200_TVE_YC_FILTER		0x400E
> +#define AC200_TVE_BURST_FRQ0		0x4010
> +#define AC200_TVE_BURST_FRQ1		0x4012
> +#define AC200_TVE_FRONT_PORCH		0x4014
> +#define AC200_TVE_BACK_PORCH		0x4016
> +#define AC200_TVE_TOTAL_LINE		0x401C
> +#define AC200_TVE_FIRST_ACTIVE		0x401E
> +#define AC200_TVE_BLACK_LEVEL		0x4020
> +#define AC200_TVE_BLANK_LEVEL		0x4022
> +#define AC200_TVE_PLUG_EN		0x4030
> +#define AC200_TVE_PLUG_IRQ_EN		0x4032
> +#define AC200_TVE_PLUG_IRQ_STA		0x4034
> +#define AC200_TVE_PLUG_STA		0x4038
> +#define AC200_TVE_PLUG_DEBOUNCE		0x4040
> +#define AC200_TVE_DAC_TEST		0x4042
> +#define AC200_TVE_PLUG_PULSE_LEVEL	0x40F4
> +#define AC200_TVE_PLUG_PULSE_START	0x40F8
> +#define AC200_TVE_PLUG_PULSE_PERIOD	0x40FA
> +#define AC200_TVE_IF_CTL		0x5000
> +#define AC200_TVE_IF_TIM0		0x5008
> +#define AC200_TVE_IF_TIM1		0x500A
> +#define AC200_TVE_IF_TIM2		0x500C
> +#define AC200_TVE_IF_TIM3		0x500E
> +#define AC200_TVE_IF_SYNC0		0x5010
> +#define AC200_TVE_IF_SYNC1		0x5012
> +#define AC200_TVE_IF_SYNC2		0x5014
> +#define AC200_TVE_IF_TIM4		0x5016
> +#define AC200_TVE_IF_STATUS		0x5018
> +
> +/* EPHY registers */
> +#define AC200_EPHY_CTL			0x6000
> +#define AC200_EPHY_BIST			0x6002
> +
> +/* eFuse registers (0x8000 - 0x9FFF, layout unknown) */
> +
> +/* RTC registers */
> +#define AC200_LOSC_CTRL0		0xA000
> +#define AC200_LOSC_CTRL1		0xA002
> +#define AC200_LOSC_AUTO_SWT_STA		0xA004
> +#define AC200_INTOSC_CLK_PRESCAL	0xA008
> +#define AC200_RTC_YY_MM_DD0		0xA010
> +#define AC200_RTC_YY_MM_DD1		0xA012
> +#define AC200_RTC_HH_MM_SS0		0xA014
> +#define AC200_RTC_HH_MM_SS1		0xA016
> +#define AC200_ALARM0_CUR_VLU0		0xA024
> +#define AC200_ALARM0_CUR_VLU1		0xA026
> +#define AC200_ALARM0_ENABLE		0xA028
> +#define AC200_ALARM0_IRQ_EN		0xA02C
> +#define AC200_ALARM0_IRQ_STA		0xA030
> +#define AC200_ALARM1_WK_HH_MM_SS0	0xA040
> +#define AC200_ALARM1_WK_HH_MM_SS1	0xA042
> +#define AC200_ALARM1_ENABLE		0xA044
> +#define AC200_ALARM1_IRQ_EN		0xA048
> +#define AC200_ALARM1_IRQ_STA		0xA04C
> +#define AC200_ALARM_CONFIG		0xA050
> +#define AC200_LOSC_OUT_GATING		0xA060
> +#define AC200_GP_DATA(x)		(0xA100 + (x) * 2)
> +#define AC200_RTC_DEB			0xA170
> +#define AC200_GPL_HOLD_OUTPUT		0xA180
> +#define AC200_VDD_RTC			0xA190
> +#define AC200_IC_CHARA0			0xA1F0
> +#define AC200_IC_CHARA1			0xA1F2
> +
> +struct ac200_dev {
> +	struct clk			*clk;
> +	struct regmap			*regmap;
> +	struct regmap_irq_chip_data	*regmap_irqc;
> +};
> +
> +#endif /* __LINUX_MFD_AC200_H */

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2020-04-24  8:05 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-04-16 18:57 [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Jernej Skrabec
2020-04-16 18:57 ` [RFC PATCH 1/4] mfd: Add support for AC200 Jernej Skrabec
2020-04-24  8:05   ` Lee Jones
2020-04-16 18:57 ` [RFC PATCH 2/4] net: phy: Add support for AC200 EPHY Jernej Skrabec
2020-04-16 19:18   ` Florian Fainelli
2020-04-17 15:01     ` Jernej Škrabec
2020-04-16 20:18   ` Heiner Kallweit
2020-04-17 16:03     ` Jernej Škrabec
2020-04-17 16:29       ` Heiner Kallweit
2020-04-17 17:15         ` Jernej Škrabec
2020-04-17 16:15     ` Jernej Škrabec
2020-04-17 16:16       ` Heiner Kallweit
2020-04-17 17:01       ` Andrew Lunn
2020-04-16 18:57 ` [RFC PATCH 3/4] arm64: dts: allwinner: h6: Add AC200 EPHY related nodes Jernej Skrabec
2020-04-16 18:57 ` [RFC PATCH 4/4] arm64: dts: allwinner: h6: tanix-tx6: Enable ethernet Jernej Skrabec
2020-04-16 21:54 ` [RFC PATCH 0/4] net: mfd: AC200 Ethernet PHY Andrew Lunn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).