Re: [PATCH v9 5/6] fpga: dfl: add support for N3000 Nios private feature

From: Tom Rix
Date: Mon Oct 12 2020 - 10:19:55 EST



On 10/11/20 11:51 PM, Xu Yilun wrote:
> On Sat, Oct 10, 2020 at 02:21:45PM -0700, Tom Rix wrote:
>> On 10/10/20 12:09 AM, Xu Yilun wrote:
>>> This patch adds support for the Nios handshake private feature on Intel
>>> PAC (Programmable Acceleration Card) N3000.
>>>
>>> The Nios is the embedded processor on the FPGA card. This private feature
>>> provides a handshake interface to FPGA Nios firmware, which receives
>>> retimer configuration command from host and executes via an internal SPI
>>> master (spi-altera). When Nios finishes the configuration, host takes over
>>> the ownership of the SPI master to control an Intel MAX10 BMC (Board
>>> Management Controller) Chip on the SPI bus.
>>>
>>> For Nios firmware handshake part, this driver requests the retimer
>>> configuration for Nios firmware on probe, and adds some sysfs nodes for
>>> user to query the onboard retimer's working mode and Nios firmware
>>> version.
>>>
>>> For SPI part, this driver adds a spi-altera platform device as well as
>>> the MAX10 BMC spi slave info. A spi-altera driver will be matched to
>>> handle the following SPI work.
>>>
>>> [mdf@xxxxxxxxxx: Fixed up MAINTAINERS file to include added ABI doc]
>>> Signed-off-by: Xu Yilun <yilun.xu@xxxxxxxxx>
>>> Signed-off-by: Wu Hao <hao.wu@xxxxxxxxx>
>>> Signed-off-by: Matthew Gerlach <matthew.gerlach@xxxxxxxxxxxxxxx>
>>> Signed-off-by: Russ Weight <russell.h.weight@xxxxxxxxx>
>>> ---
>>> v3: add the doc for this driver
>>> minor fixes for comments from Tom
>>> v4: move the err log in regmap implementation, and delete
>>> n3000_nios_writel/readl(), they have nothing to wrapper now.
>>> some minor fixes and comments improvement.
>>> v5: fix the output of fec_mode sysfs inf to "no" on 10G configuration,
>>> cause no FEC mode could be configured for 10G.
>>> rename the dfl_n3000_nios_* to n3000_nios_*
>>> improves comments.
>>> v6: fix the output of fec_mode sysfs inf to "not supported" if in 10G,
>>> or the firmware version major < 3.
>>> minor fixes and improves comments.
>>> v7: improves comments.
>>> v8: add sysfs interfaces for retimer mode, also doc update.
>>> delete duplicated sysfs interfaces description in doc.
>>> minor fixes.
>>> v9: delete the retimer FEC mode configuration via module_parameter.
>>> update the kernel version of the sysfs interfaces in Doc.
>>> merge the patch "Make m10_n3000_info static" (https://lore.kernel.org/linux-fpga/52d8411e-13d8-1e91-756d-131802f5f445@xxxxxxxxxx/T/#t)
>>> remove the tags of Maintainers, this implementation is changed.
>>> ---
>>> .../ABI/testing/sysfs-bus-dfl-devices-n3000-nios | 47 ++
>>> MAINTAINERS | 2 +-
>>> drivers/fpga/Kconfig | 11 +
>>> drivers/fpga/Makefile | 2 +
>>> drivers/fpga/dfl-n3000-nios.c | 566 +++++++++++++++++++++
>>> 5 files changed, 627 insertions(+), 1 deletion(-)
>>> create mode 100644 Documentation/ABI/testing/sysfs-bus-dfl-devices-n3000-nios
>>> create mode 100644 drivers/fpga/dfl-n3000-nios.c
>>>
>>> diff --git a/Documentation/ABI/testing/sysfs-bus-dfl-devices-n3000-nios b/Documentation/ABI/testing/sysfs-bus-dfl-devices-n3000-nios
>>> new file mode 100644
>>> index 0000000..f2f6e5e
>>> --- /dev/null
>>> +++ b/Documentation/ABI/testing/sysfs-bus-dfl-devices-n3000-nios
>>> @@ -0,0 +1,47 @@
>>> +What: /sys/bus/dfl/devices/dfl_dev.X/fec_mode
>>> +Date: Oct 2020
>>> +KernelVersion: 5.11
>>> +Contact: Xu Yilun <yilun.xu@xxxxxxxxx>
>>> +Description: Read-only. It returns the FEC mode of the 25G links of the
>> Could drop the 'It' and start with 'Returns'  here and similar
> Yes, I can change it.
>
>>> + ethernet retimers configured by Nios firmware. "rs" for Reed
>>> + Solomon FEC, "kr" for Fire Code FEC, "no" for NO FEC.
>>> + "not supported" if the FEC mode setting is not supported, this
>> setting ? the description says 'Read-only'
> The setting is done by Nios firmware, host cannot change it at runtime
> so it is 'Read-only'. It is previously described in this section. Is it
> good?
ok
>
>>> + happens when the Nios firmware version major < 3, or no link is
>>> + configured to 25G.
>>> + Format: string
>>> +
>>> +What: /sys/bus/dfl/devices/dfl_dev.X/retimer_A_mode
>>> +Date: Oct 2020
>>> +KernelVersion: 5.11
>>> +Contact: Xu Yilun <yilun.xu@xxxxxxxxx>
>>> +Description: Read-only. It returns the enumeration value of the working mode
>>> + of the retimer A configured by the Nios firmware. The value is
>>> + read out from shared registers filled by the Nios firmware. Now
>>> + the values could be:
>>> +
>>> + - "0": Reset
>>> + - "1": 4x10G
>>> + - "2": 4x25G
>>> + - "3": 2x25G
>>> + - "4": 2x25G+2x10G
>>> + - "5": 1x25G
>>> +
>>> + If the Nios firmware is updated in future to support more
>>> + retimer modes, more enumeration value is expected.
>>> + Format: 0x%x
>>> +
>>> +What: /sys/bus/dfl/devices/dfl_dev.X/retimer_B_mode
>>> +Date: Oct 2020
>>> +KernelVersion: 5.11
>>> +Contact: Xu Yilun <yilun.xu@xxxxxxxxx>
>>> +Description: Read-only. It returns the enumeration value of the working mode
>>> + of the retimer B configured by the Nios firmware. The value
>>> + format is the same as retimer_A_mode.
>>> +
>>> +What: /sys/bus/dfl/devices/dfl_dev.X/nios_fw_version
>>> +Date: Oct 2020
>>> +KernelVersion: 5.11
>>> +Contact: Xu Yilun <yilun.xu@xxxxxxxxx>
>>> +Description: Read-only. It returns the version of the Nios firmware in FPGA.
>> Can drop 'in FPGA'.
> The PAC N3000 card has 2 Nios firmwares. The one for this driver is
> embedded in A10 FPGA. Another one is embedded in MAX10 BMC chip.
>
> So to be clear, I think we may keep the 'in FPGA'.

ok, then change to

'in the FPGA'

>
>>> + Its format is "major.minor.patch".
>>> + Format: %x.%x.%x
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 9d46097..3a81cac 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -6881,7 +6881,7 @@ M: Wu Hao <hao.wu@xxxxxxxxx>
>>> R: Tom Rix <trix@xxxxxxxxxx>
>>> L: linux-fpga@xxxxxxxxxxxxxxx
>>> S: Maintained
>>> -F: Documentation/ABI/testing/sysfs-bus-dfl
>>> +F: Documentation/ABI/testing/sysfs-bus-dfl*
>>> F: Documentation/fpga/dfl.rst
>>> F: drivers/fpga/dfl*
>>> F: include/linux/fpga/dfl.h
>>> diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
>>> index 7cd5a29..38c7130 100644
>>> --- a/drivers/fpga/Kconfig
>>> +++ b/drivers/fpga/Kconfig
>>> @@ -191,6 +191,17 @@ config FPGA_DFL_AFU
>>> to the FPGA infrastructure via a Port. There may be more than one
>>> Port/AFU per DFL based FPGA device.
>>>
>>> +config FPGA_DFL_NIOS_INTEL_PAC_N3000
>>> + tristate "FPGA DFL NIOS Driver for Intel PAC N3000"
>>> + depends on FPGA_DFL
>> Would 'depends on FPGA_DFL_PCI' be better ?
> The relationship between the 2 config is, the n3000 nios private feature
> now only appears on PAC N3000 which is a PCIe card.
>
> But the code has no dependency to FPGA_DFL_PCI. Is it necessary we add the
> 'depends on'?

as-is is fine

i am thinking a card specific config is needed but that is another topic.

>
>>> + select REGMAP
>>> + help
>> Last 3 lines have whitespace issues, need to convert spaces to tabs
> Thanks for catching this.
>
>>> + This is the driver for the N3000 Nios private feature on Intel
>>> + PAC (Programmable Acceleration Card) N3000. It communicates
>>> + with the embedded Nios processor to configure the retimers on
>>> + the card. It also instantiates the SPI master (spi-altera) for
>>> + the card's BMC (Board Management Controller) control.
>> can drop 'control'
>>
>> If you read out 'card's board mangement controller control' it sounds awkward.
> It's true. I'll drop it.
>
>>> +
>>> config FPGA_DFL_PCI
>>> tristate "FPGA DFL PCIe Device Driver"
>>> depends on PCI && FPGA_DFL
>>> diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
>>> index d8e21df..18dc9885 100644
>>> --- a/drivers/fpga/Makefile
>>> +++ b/drivers/fpga/Makefile
>>> @@ -44,5 +44,7 @@ dfl-fme-objs += dfl-fme-perf.o
>>> dfl-afu-objs := dfl-afu-main.o dfl-afu-region.o dfl-afu-dma-region.o
>>> dfl-afu-objs += dfl-afu-error.o
>>>
>>> +obj-$(CONFIG_FPGA_DFL_NIOS_INTEL_PAC_N3000) += dfl-n3000-nios.o
>>> +
>>> # Drivers for FPGAs which implement DFL
>>> obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o
>>> diff --git a/drivers/fpga/dfl-n3000-nios.c b/drivers/fpga/dfl-n3000-nios.c
>>> new file mode 100644
>>> index 0000000..e7c3862
>>> --- /dev/null
>>> +++ b/drivers/fpga/dfl-n3000-nios.c
>>> @@ -0,0 +1,566 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * DFL device driver for Nios private feature on Intel PAC (Programmable
>>> + * Acceleration Card) N3000
>>> + *
>>> + * Copyright (C) 2019-2020 Intel Corporation, Inc.
>>> + *
>>> + * Authors:
>>> + * Wu Hao <hao.wu@xxxxxxxxx>
>>> + * Xu Yilun <yilun.xu@xxxxxxxxx>
>>> + */
>>> +#include <linux/bitfield.h>
>>> +#include <linux/errno.h>
>>> +#include <linux/fpga/dfl.h>
>>> +#include <linux/io.h>
>>> +#include <linux/io-64-nonatomic-lo-hi.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/stddef.h>
>>> +#include <linux/spi/altera.h>
>>> +#include <linux/spi/spi.h>
>>> +#include <linux/types.h>
>>> +
>>> +/* N3000 Nios private feature registers */
>>> +#define NIOS_SPI_PARAM 0x8
>> The #defines should have a consistent prefix that is unlikely to conflict.
>>
>> Maybe N3000_NIOS_
> Yes, I'll try to refactor the macro names.
> The register names in spec are NIOS_SPI_xx, how about we compress the prefixes,
> 'N3000_NS' & 'n3000_ns' for 'N3000 NIOS SPI'
ok for n3000_ns.  just move the comment for 'ns' up this first use.
>
>>> +#define PARAM_SHIFT_MODE_MSK BIT_ULL(1)
>>> +#define PARAM_SHIFT_MODE_MSB 0
>>> +#define PARAM_SHIFT_MODE_LSB 1
>>> +#define PARAM_DATA_WIDTH GENMASK_ULL(7, 2)
>>> +#define PARAM_NUM_CS GENMASK_ULL(13, 8)
>>> +#define PARAM_CLK_POL BIT_ULL(14)
>>> +#define PARAM_CLK_PHASE BIT_ULL(15)
>>> +#define PARAM_PERIPHERAL_ID GENMASK_ULL(47, 32)
>>> +
>>> +#define NIOS_SPI_CTRL 0x10
>>> +#define CTRL_WR_DATA GENMASK_ULL(31, 0)
>>> +#define CTRL_ADDR GENMASK_ULL(44, 32)
>>> +#define CTRL_CMD_MSK GENMASK_ULL(63, 62)
>>> +#define CTRL_CMD_NOP 0
>>> +#define CTRL_CMD_RD 1
>>> +#define CTRL_CMD_WR 2
>>> +
>>> +#define NIOS_SPI_STAT 0x18
>>> +#define STAT_RD_DATA GENMASK_ULL(31, 0)
>>> +#define STAT_RW_VAL BIT_ULL(32)
>>> +
>>> +/* Nios handshake registers, indirect access */
>>> +#define NIOS_INIT 0x1000
>>> +#define NIOS_INIT_DONE BIT(0)
>>> +#define NIOS_INIT_START BIT(1)
>>> +/* Mode for retimer A, link 0, the same below */
>>> +#define REQ_FEC_MODE_A0_MSK GENMASK(9, 8)
>>> +#define REQ_FEC_MODE_A1_MSK GENMASK(11, 10)
>>> +#define REQ_FEC_MODE_A2_MSK GENMASK(13, 12)
>>> +#define REQ_FEC_MODE_A3_MSK GENMASK(15, 14)
>>> +#define REQ_FEC_MODE_B0_MSK GENMASK(17, 16)
>>> +#define REQ_FEC_MODE_B1_MSK GENMASK(19, 18)
>>> +#define REQ_FEC_MODE_B2_MSK GENMASK(21, 20)
>>> +#define REQ_FEC_MODE_B3_MSK GENMASK(23, 22)
>>> +#define REQ_FEC_MODE_NO 0x0
>>> +#define REQ_FEC_MODE_KR 0x1
>>> +#define REQ_FEC_MODE_RS 0x2
>>> +
>>> +#define NIOS_FW_VERSION 0x1004
>>> +#define NIOS_FW_VERSION_PATCH GENMASK(23, 20)
>>> +#define NIOS_FW_VERSION_MINOR GENMASK(27, 24)
>>> +#define NIOS_FW_VERSION_MAJOR GENMASK(31, 28)
>>> +
>>> +/* The retimers we use on Intel PAC N3000 is Parkvale, abbreviated to PKVL */
>> Parkvale is codename, why not just RETIMER_ ?
> The register fields names in Hardware spec is PKVL_, so I mean to keep
> align with the spec.
ok
>
>>> +#define PKVL_A_MODE_STS 0x1020
>>> +#define PKVL_B_MODE_STS 0x1024
>>> +#define PKVL_MODE_STS_GROUP_MSK GENMASK(15, 8)
>>> +#define PKVL_MODE_STS_GROUP_OK 0x0
>>> +#define PKVL_MODE_STS_ID_MSK GENMASK(7, 0)
>>> +/* When GROUP MASK field == GROUP_OK */
>>> +#define PKVL_MODE_ID_RESET 0x0
>>> +#define PKVL_MODE_ID_4X10G 0x1
>>> +#define PKVL_MODE_ID_4X25G 0x2
>>> +#define PKVL_MODE_ID_2X25G 0x3
>>> +#define PKVL_MODE_ID_2X25G_2X10G 0x4
>>> +#define PKVL_MODE_ID_1X25G 0x5
>>> +
>>> +#define NS_REGBUS_WAIT_TIMEOUT 10000 /* loop count */
>> NS ? Expand.
> I could explain the NS - NIOS SPI in comments, and is N3000_NS_REGBUS_RETRY_COUNT good?
yes this is fine. for use of 'ns' which has a more common meaning of 'nano second' need to take care when meaning could be confused with 'nios spi'
>
>> 'WAIT_TIMEOUT' should be changed to 'RETRY_COUNT' to reflect its use
> It makes sense.
>
>>> +#define NIOS_INIT_TIMEOUT 10000000 /* usec */
>>> +#define NIOS_INIT_TIME_INTV 100000 /* usec */
>>> +
>>> +struct n3000_nios {
>>> + void __iomem *base;
>>> + struct regmap *regmap;
>>> + struct device *dev;
>>> + struct platform_device *altera_spi;
>>> +};
>>> +
>>> +static ssize_t nios_fw_version_show(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + struct n3000_nios *ns = dev_get_drvdata(dev);
>>> + unsigned int val;
>>> + int ret;
>>> +
>>> + ret = regmap_read(ns->regmap, NIOS_FW_VERSION, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return sprintf(buf, "%x.%x.%x\n",
>>> + (u8)FIELD_GET(NIOS_FW_VERSION_MAJOR, val),
>>> + (u8)FIELD_GET(NIOS_FW_VERSION_MINOR, val),
>>> + (u8)FIELD_GET(NIOS_FW_VERSION_PATCH, val));
>>> +}
>>> +static DEVICE_ATTR_RO(nios_fw_version);
>>> +
>>> +#define IS_MODE_STATUS_OK(mode_stat) \
>>> + (FIELD_GET(PKVL_MODE_STS_GROUP_MSK, (mode_stat)) == \
>>> + PKVL_MODE_STS_GROUP_OK)
>>> +
>>> +#define IS_RETIMER_FEC_SUPPORTED(retimer_mode) \
>>> + ((retimer_mode) != PKVL_MODE_ID_RESET && \
>>> + (retimer_mode) != PKVL_MODE_ID_4X10G)
>>> +
>>> +static int get_retimer_mode(struct n3000_nios *ns, unsigned int mode_stat_reg,
>>> + unsigned int *retimer_mode)
>>> +{
>>> + unsigned int val;
>>> + int ret;
>>> +
>>> + ret = regmap_read(ns->regmap, mode_stat_reg, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (!IS_MODE_STATUS_OK(val))
>>> + return -EFAULT;
>>> +
>>> + *retimer_mode = FIELD_GET(PKVL_MODE_STS_ID_MSK, val);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static ssize_t retimer_A_mode_show(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + struct n3000_nios *ns = dev_get_drvdata(dev);
>>> + unsigned int mode;
>>> + int ret;
>>> +
>>> + ret = get_retimer_mode(ns, PKVL_A_MODE_STS, &mode);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return sprintf(buf, "0x%x\n", mode);
>>> +}
>>> +static DEVICE_ATTR_RO(retimer_A_mode);
>>> +
>>> +static ssize_t retimer_B_mode_show(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + struct n3000_nios *ns = dev_get_drvdata(dev);
>>> + unsigned int mode;
>>> + int ret;
>>> +
>>> + ret = get_retimer_mode(ns, PKVL_B_MODE_STS, &mode);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return sprintf(buf, "0x%x\n", mode);
>>> +}
>>> +static DEVICE_ATTR_RO(retimer_B_mode);
>>> +
>>> +static ssize_t fec_mode_show(struct device *dev,
>>> + struct device_attribute *attr, char *buf)
>>> +{
>>> + unsigned int val, retimer_a_mode, retimer_b_mode, fec_mode;
>>> + struct n3000_nios *ns = dev_get_drvdata(dev);
>>> + int ret;
>>> +
>>> + /* FEC mode setting is not supported in early FW versions */
>>> + ret = regmap_read(ns->regmap, NIOS_FW_VERSION, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (FIELD_GET(NIOS_FW_VERSION_MAJOR, val) < 3)
>>> + return sprintf(buf, "not supported\n");
>>> +
>>> + /* If no 25G links, FEC mode setting is not supported either */
>>> + ret = get_retimer_mode(ns, PKVL_A_MODE_STS, &retimer_a_mode);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = get_retimer_mode(ns, PKVL_B_MODE_STS, &retimer_b_mode);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (!IS_RETIMER_FEC_SUPPORTED(retimer_a_mode) &&
>>> + !IS_RETIMER_FEC_SUPPORTED(retimer_b_mode))
>>> + return sprintf(buf, "not supported\n");
>>> +
>>> + /* get the valid FEC mode for 25G links */
>>> + ret = regmap_read(ns->regmap, NIOS_INIT, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /*
>>> + * FEC mode should always be the same for all links, as we set them
>>> + * in this way.
>>> + */
>>> + fec_mode = FIELD_GET(REQ_FEC_MODE_A0_MSK, val);
>>> + if (fec_mode != FIELD_GET(REQ_FEC_MODE_A1_MSK, val) ||
>>> + fec_mode != FIELD_GET(REQ_FEC_MODE_A2_MSK, val) ||
>>> + fec_mode != FIELD_GET(REQ_FEC_MODE_A3_MSK, val) ||
>>> + fec_mode != FIELD_GET(REQ_FEC_MODE_B0_MSK, val) ||
>>> + fec_mode != FIELD_GET(REQ_FEC_MODE_B1_MSK, val) ||
>>> + fec_mode != FIELD_GET(REQ_FEC_MODE_B2_MSK, val) ||
>>> + fec_mode != FIELD_GET(REQ_FEC_MODE_B3_MSK, val))
>>> + return -EFAULT;
>> a precomputed table setting the fields would reduce the this to a lookup and 1 check.
>>
>> similar table could be used below for the setting.
> Yes, I can do it.
>
>>> +
>>> + switch (fec_mode) {
>>> + case REQ_FEC_MODE_NO:
>>> + return sprintf(buf, "no\n");
>>> + case REQ_FEC_MODE_KR:
>>> + return sprintf(buf, "kr\n");
>>> + case REQ_FEC_MODE_RS:
>>> + return sprintf(buf, "rs\n");
>>> + }
>>> +
>>> + return -EFAULT;
>>> +}
>>> +static DEVICE_ATTR_RO(fec_mode);
>>> +
>>> +static struct attribute *n3000_nios_attrs[] = {
>>> + &dev_attr_nios_fw_version.attr,
>>> + &dev_attr_retimer_A_mode.attr,
>>> + &dev_attr_retimer_B_mode.attr,
>>> + &dev_attr_fec_mode.attr,
>>> + NULL,
>>> +};
>>> +ATTRIBUTE_GROUPS(n3000_nios);
>>> +
>>> +static bool init_error_detected(struct n3000_nios *ns)
>>> +{
>>> + unsigned int val;
>>> +
>>> + if (regmap_read(ns->regmap, PKVL_A_MODE_STS, &val))
>>> + return true;
>>> +
>>> + if (!IS_MODE_STATUS_OK(val))
>>> + return true;
>>> +
>>> + if (regmap_read(ns->regmap, PKVL_B_MODE_STS, &val))
>>> + return true;
>>> +
>>> + if (!IS_MODE_STATUS_OK(val))
>>> + return true;
>>> +
>>> + return false;
>>> +}
>>> +
>>> +static void dump_error_stat(struct n3000_nios *ns)
>>> +{
>>> + unsigned int val;
>>> +
>>> + if (regmap_read(ns->regmap, PKVL_A_MODE_STS, &val))
>>> + return;
>>> +
>>> + dev_err(ns->dev, "PKVL_A_MODE_STS 0x%x\n", val);
>>> +
>>> + if (regmap_read(ns->regmap, PKVL_B_MODE_STS, &val))
>>> + return;
>>> +
>>> + dev_err(ns->dev, "PKVL_B_MODE_STS 0x%x\n", val);
>>> +}
>>> +
>>> +static int n3000_nios_init_done_check(struct n3000_nios *ns)
>>> +{
>>> + struct device *dev = ns->dev;
>>> + unsigned int val, mode;
>>> + int ret;
>>> +
>>> + /*
>>> + * this SPI is shared by Nios core inside FPGA, Nios will use this SPI
>> 'by the Nios core inside the FPGA ... '
> Yes.
>
>>> + * master to do some one time initialization after power up, and then
>>> + * release the control to OS. driver needs to poll on INIT_DONE to
>> 'The driver..'
> Yes.
>
>>> + * see when driver could take the control.
>>> + *
>>> + * Please note that after Nios firmware version 3.0.0, INIT_START is
>>> + * introduced, so driver needs to trigger START firstly and then check
>>> + * INIT_DONE.
>>> + */
>>> +
>>> + ret = regmap_read(ns->regmap, NIOS_FW_VERSION, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /*
>>> + * If Nios version register is totally uninitialized(== 0x0), then the
>>> + * Nios firmware is missing. So host could take control of SPI master
>>> + * safely, but initialization work for Nios is not done. To restore the
>>> + * card, we need to reprogram a new Nios firmware via the BMC chip on
>>> + * SPI bus. So the driver doesn't error out, it continues to create the
>>> + * spi controller device and spi_board_info for BMC.
>>> + */
>>> + if (val == 0) {
>>> + dev_err(dev, "Nios version reg = 0x%x, skip INIT_DONE check, but the retimer may be uninitialized\n",
>>> + val);
>>> + return 0;
>>> + }
>>> +
>>> + if (FIELD_GET(NIOS_FW_VERSION_MAJOR, val) >= 3) {
>>> + /* read NIOS_INIT to check if retimer initialization is done */
>>> + ret = regmap_read(ns->regmap, NIOS_INIT, &val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /* check if retimers are initialized already */
>>> + if (val & NIOS_INIT_DONE || val & NIOS_INIT_START)
>> val & (NIOS_INIT_DONE | NIOS_INIT_START)
> Yes.
>
>>> + goto nios_init_done;
>>> +
>>> + /* configure FEC mode per module param */
>>> + val = NIOS_INIT_START;
>>> +
>>> + /*
>>> + * When the retimer is to be set to 10G mode, there is no FEC
>>> + * mode setting, so the REQ_FEC_MODE field will be ignored by
>>> + * Nios firmware in this case. But we should still fill the FEC
>>> + * mode field cause host could not get the retimer working mode
>>> + * until the Nios init is done.
>>> + *
>>> + * For now the driver doesn't support the retimer FEC mode
>>> + * switching per user's request. It is always set to Reed
>>> + * Solomon FEC.
>>> + */
>>> + mode = REQ_FEC_MODE_RS;
>>> +
>>> + /* set the same FEC mode for all links */
>>> + val |= FIELD_PREP(REQ_FEC_MODE_A0_MSK, mode) |
>>> + FIELD_PREP(REQ_FEC_MODE_A1_MSK, mode) |
>>> + FIELD_PREP(REQ_FEC_MODE_A2_MSK, mode) |
>>> + FIELD_PREP(REQ_FEC_MODE_A3_MSK, mode) |
>>> + FIELD_PREP(REQ_FEC_MODE_B0_MSK, mode) |
>>> + FIELD_PREP(REQ_FEC_MODE_B1_MSK, mode) |
>>> + FIELD_PREP(REQ_FEC_MODE_B2_MSK, mode) |
>>> + FIELD_PREP(REQ_FEC_MODE_B3_MSK, mode);
> I'll also optimize the code here.
>
>>> +
>>> + ret = regmap_write(ns->regmap, NIOS_INIT, val);
>>> + if (ret)
>>> + return ret;
>>> + }
>>> +
>>> +nios_init_done:
>>> + /* polls on NIOS_INIT_DONE */
>>> + ret = regmap_read_poll_timeout(ns->regmap, NIOS_INIT, val,
>>> + val & NIOS_INIT_DONE,
>>> + NIOS_INIT_TIME_INTV,
>>> + NIOS_INIT_TIMEOUT);
>>> + if (ret) {
>>> + dev_err(dev, "NIOS_INIT_DONE %s\n",
>>> + (ret == -ETIMEDOUT) ? "timed out" : "check error");
>>> + goto dump_sts;
>>> + }
>>> +
>>> + /*
>>> + * After INIT_DONE is detected, it still needs to check if any error
>>> + * detected.
>>> + */
>>> + if (init_error_detected(ns)) {
>>> + /*
>>> + * If the retimer configuration is failed, the Nios firmware
>>> + * will still release the spi controller for host to
>>> + * communicate with the BMC. It makes possible for people to
>>> + * reprogram a new Nios firmware and restore the card. So the
>>> + * driver doesn't error out, it continues to create the spi
>>> + * controller device and spi_board_info for BMC.
>>> + */
>>> + dev_err(dev, "NIOS_INIT_DONE OK, but err found during init\n");
>>> + goto dump_sts;
>>> + }
>>> + return 0;
>>> +
>>> +dump_sts:
>>> + dump_error_stat(ns);
>> Could init_error_detected and dump_error_stat be combined ?
> Yes, I could try to refactor it.
>
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static struct spi_board_info m10_n3000_info = {
>>> + .modalias = "m10-n3000",
>>> + .max_speed_hz = 12500000,
>>> + .bus_num = 0,
>>> + .chip_select = 0,
>>> +};
>>> +
>>> +static int create_altera_spi_controller(struct n3000_nios *ns)
>>> +{
>>> + struct altera_spi_platform_data pdata = { 0 };
>>> + struct platform_device_info pdevinfo = { 0 };
>>> + void __iomem *base = ns->base;
>>> + u64 v;
>>> +
>>> + v = readq(base + NIOS_SPI_PARAM);
>>> +
>>> + pdata.mode_bits = SPI_CS_HIGH;
>>> + if (FIELD_GET(PARAM_CLK_POL, v))
>>> + pdata.mode_bits |= SPI_CPOL;
>>> + if (FIELD_GET(PARAM_CLK_PHASE, v))
>>> + pdata.mode_bits |= SPI_CPHA;
>>> +
>>> + pdata.num_chipselect = FIELD_GET(PARAM_NUM_CS, v);
>>> + pdata.bits_per_word_mask =
>>> + SPI_BPW_RANGE_MASK(1, FIELD_GET(PARAM_DATA_WIDTH, v));
>>> +
>>> + pdata.num_devices = 1;
>>> + pdata.devices = &m10_n3000_info;
>>> +
>>> + dev_dbg(ns->dev, "%s cs %u bpm 0x%x mode 0x%x\n", __func__,
>>> + pdata.num_chipselect, pdata.bits_per_word_mask,
>>> + pdata.mode_bits);
>>> +
>>> + pdevinfo.name = "subdev_spi_altera";
>>> + pdevinfo.id = PLATFORM_DEVID_AUTO;
>>> + pdevinfo.parent = ns->dev;
>>> + pdevinfo.data = &pdata;
>>> + pdevinfo.size_data = sizeof(pdata);
>>> +
>>> + ns->altera_spi = platform_device_register_full(&pdevinfo);
>>> + return PTR_ERR_OR_ZERO(ns->altera_spi);
>>> +}
>>> +
>>> +static void destroy_altera_spi_controller(struct n3000_nios *ns)
>>> +{
>>> + platform_device_unregister(ns->altera_spi);
>>> +}
>>> +
>>> +/* ns is the abbreviation of nios_spi */
>>> +static int ns_poll_stat_timeout(void __iomem *base, u64 *v)
>>> +{
>>> + int loops = NS_REGBUS_WAIT_TIMEOUT;
>>> +
>>> + /*
>>> + * We don't use the time based timeout here for performance.
>>> + *
>>> + * The regbus read/write is on the critical path of Intel PAC N3000
>>> + * image programing. The time based timeout checking will add too much
>>> + * overhead on it. Usually the state changes in 1 or 2 loops on the
>>> + * test server, and we set 10000 times loop here for safety.
>>> + */
>>> + do {
>>> + *v = readq(base + NIOS_SPI_STAT);
>>> + if (*v & STAT_RW_VAL)
>>> + break;
>>> + cpu_relax();
>>> + } while (--loops);
>> This becomes an infinite loop if loops <= 0
> The loops count is always initialized at the start of the function.
> Do you mean we should lower the impact when the developer inputs the
> wrong loop count?
>
> Is it better we change to:
>
> unsigned int loops = NS_REGBUS_WAIT_TIMEOUT;
>
> while (loops--) {
>
> }
yes
>>> +
>>> + return loops ? 0 : -ETIMEDOUT;
>>> +}
>>> +
>>> +static int ns_reg_write(void *context, unsigned int reg, unsigned int val)
>>> +{
>>> + struct n3000_nios *ns = context;
>>> + u64 v = 0;
>>> + int ret;
>>> +
>>> + v |= FIELD_PREP(CTRL_CMD_MSK, CTRL_CMD_WR);
>>> + v |= FIELD_PREP(CTRL_ADDR, reg);
>>> + v |= FIELD_PREP(CTRL_WR_DATA, val);
>>> + writeq(v, ns->base + NIOS_SPI_CTRL);
>>> +
>>> + ret = ns_poll_stat_timeout(ns->base, &v);
>>> + if (ret)
>>> + dev_err(ns->dev, "fail to write reg 0x%x val 0x%x: %d\n",
>>> + reg, val, ret);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int ns_reg_read(void *context, unsigned int reg, unsigned int *val)
>>> +{
>>> + struct n3000_nios *ns = context;
>>> + u64 v = 0;
>>> + int ret;
>>> +
>>> + v |= FIELD_PREP(CTRL_CMD_MSK, CTRL_CMD_RD);
>> v = , and save the intialiation
>>
>> or v = ( ... | ... )
> Yes.
>
>>> + v |= FIELD_PREP(CTRL_ADDR, reg);
>>> + writeq(v, ns->base + NIOS_SPI_CTRL);
>>> +
>>> + ret = ns_poll_stat_timeout(ns->base, &v);
>>> + if (ret)
>>> + dev_err(ns->dev, "fail to read reg 0x%x: %d\n", reg, ret);
>>> + else
>>> + *val = FIELD_GET(STAT_RD_DATA, v);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static const struct regmap_config ns_regbus_cfg = {
>>> + .reg_bits = 32,
>>> + .reg_stride = 4,
>>> + .val_bits = 32,
>>> + .fast_io = true,
>>> +
>>> + .reg_write = ns_reg_write,
>>> + .reg_read = ns_reg_read,
>>> +};
>>> +
>>> +static int n3000_nios_probe(struct dfl_device *ddev)
>>> +{
>>> + struct device *dev = &ddev->dev;
>>> + struct n3000_nios *ns;
>>> + int ret;
>>> +
>>> + ns = devm_kzalloc(dev, sizeof(*ns), GFP_KERNEL);
>>> + if (!ns)
>>> + return -ENOMEM;
>>> +
>>> + dev_set_drvdata(&ddev->dev, ns);
>>> +
>>> + ns->dev = dev;
>>> +
>>> + ns->base = devm_ioremap_resource(&ddev->dev, &ddev->mmio_res);
>>> + if (IS_ERR(ns->base))
>>> + return PTR_ERR(ns->base);
>>> +
>>> + ns->regmap = devm_regmap_init(dev, NULL, ns, &ns_regbus_cfg);
>>> + if (IS_ERR(ns->regmap))
>>> + return PTR_ERR(ns->regmap);
>>> +
>>> + ret = n3000_nios_init_done_check(ns);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = create_altera_spi_controller(ns);
>>> + if (ret)
>>> + dev_err(dev, "altera spi controller create failed: %d\n", ret);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static void n3000_nios_remove(struct dfl_device *ddev)
>>> +{
>>> + struct n3000_nios *ns = dev_get_drvdata(&ddev->dev);
>>> +
>>> + destroy_altera_spi_controller(ns);
>>> +}
>>> +
>>> +#define FME_FEATURE_ID_N3000_NIOS 0xd
>>> +
>>> +static const struct dfl_device_id n3000_nios_ids[] = {
>>> + { FME_ID, FME_FEATURE_ID_N3000_NIOS },
>>> + { }
>>> +};
>>> +
>>> +static struct dfl_driver n3000_nios_driver = {
>>> + .drv = {
>>> + .name = "n3000-nios",
>> "dfl-n3000-nios" ?
> Yes. I think it is better.
>
> Thanks,
> Yilun

Tom

>> Tom
>>
>>> + .dev_groups = n3000_nios_groups,
>>> + },
>>> + .id_table = n3000_nios_ids,
>>> + .probe = n3000_nios_probe,
>>> + .remove = n3000_nios_remove,
>>> +};
>>> +
>>> +module_dfl_driver(n3000_nios_driver);
>>> +
>>> +MODULE_DEVICE_TABLE(dfl, n3000_nios_ids);
>>> +MODULE_DESCRIPTION("Driver for Nios private feature on Intel PAC N3000");
>>> +MODULE_AUTHOR("Intel Corporation");
>>> +MODULE_LICENSE("GPL v2");