* [PATCH v6 10/13] hwmon: peci: Add cputemp driver
From: Iwona Winiarska @ 2022-01-25 1:11 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska,
Jae Hyun Yoo
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
Add peci-cputemp driver for Digital Thermal Sensor (DTS) thermal
readings of the processor package and processor cores that are
accessible via the PECI interface.
The main use case for the driver (and PECI interface) is out-of-band
management, where we're able to obtain the DTS readings from an external
entity connected with PECI, e.g. BMC on server platforms.
Co-developed-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
---
MAINTAINERS | 6 +
drivers/hwmon/Kconfig | 2 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/peci/Kconfig | 18 ++
drivers/hwmon/peci/Makefile | 5 +
drivers/hwmon/peci/common.h | 58 ++++
drivers/hwmon/peci/cputemp.c | 592 +++++++++++++++++++++++++++++++++++
7 files changed, 682 insertions(+)
create mode 100644 drivers/hwmon/peci/Kconfig
create mode 100644 drivers/hwmon/peci/Makefile
create mode 100644 drivers/hwmon/peci/common.h
create mode 100644 drivers/hwmon/peci/cputemp.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 7c31e29c885a..369ac3524f33 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15099,6 +15099,12 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/peaq-wmi.c
+PECI HARDWARE MONITORING DRIVERS
+M: Iwona Winiarska <iwona.winiarska@intel.com>
+L: linux-hwmon@vger.kernel.org
+S: Supported
+F: drivers/hwmon/peci/
+
PECI SUBSYSTEM
M: Iwona Winiarska <iwona.winiarska@intel.com>
L: openbmc@lists.ozlabs.org (moderated for non-subscribers)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 8df25f1079ba..af6c3bd65ebd 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1538,6 +1538,8 @@ config SENSORS_PCF8591
These devices are hard to detect and rarely found on mainstream
hardware. If unsure, say N.
+source "drivers/hwmon/peci/Kconfig"
+
source "drivers/hwmon/pmbus/Kconfig"
config SENSORS_PWM_FAN
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 185f946d698b..6139e5a5aa00 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -208,6 +208,7 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o
obj-$(CONFIG_SENSORS_OCC) += occ/
+obj-$(CONFIG_SENSORS_PECI) += peci/
obj-$(CONFIG_PMBUS) += pmbus/
ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
diff --git a/drivers/hwmon/peci/Kconfig b/drivers/hwmon/peci/Kconfig
new file mode 100644
index 000000000000..e10eed68d70a
--- /dev/null
+++ b/drivers/hwmon/peci/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SENSORS_PECI_CPUTEMP
+ tristate "PECI CPU temperature monitoring client"
+ depends on PECI
+ select SENSORS_PECI
+ select PECI_CPU
+ help
+ If you say yes here you get support for the generic Intel PECI
+ cputemp driver which provides Digital Thermal Sensor (DTS) thermal
+ readings of the CPU package and CPU cores that are accessible via
+ the processor PECI interface.
+
+ This driver can also be built as a module. If so, the module
+ will be called peci-cputemp.
+
+config SENSORS_PECI
+ tristate
diff --git a/drivers/hwmon/peci/Makefile b/drivers/hwmon/peci/Makefile
new file mode 100644
index 000000000000..e8a0ada5ab1f
--- /dev/null
+++ b/drivers/hwmon/peci/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+peci-cputemp-y := cputemp.o
+
+obj-$(CONFIG_SENSORS_PECI_CPUTEMP) += peci-cputemp.o
diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h
new file mode 100644
index 000000000000..734506b0eca2
--- /dev/null
+++ b/drivers/hwmon/peci/common.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2021 Intel Corporation */
+
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+#ifndef __PECI_HWMON_COMMON_H
+#define __PECI_HWMON_COMMON_H
+
+#define PECI_HWMON_UPDATE_INTERVAL HZ
+
+/**
+ * struct peci_sensor_state - PECI state information
+ * @valid: flag to indicate the sensor value is valid
+ * @last_updated: time of the last update in jiffies
+ * @lock: mutex to protect sensor access
+ */
+struct peci_sensor_state {
+ bool valid;
+ unsigned long last_updated;
+ struct mutex lock; /* protect sensor access */
+};
+
+/**
+ * struct peci_sensor_data - PECI sensor information
+ * @value: sensor value in milli units
+ * @state: sensor update state
+ */
+
+struct peci_sensor_data {
+ s32 value;
+ struct peci_sensor_state state;
+};
+
+/**
+ * peci_sensor_need_update() - check whether sensor update is needed or not
+ * @sensor: pointer to sensor data struct
+ *
+ * Return: true if update is needed, false if not.
+ */
+
+static inline bool peci_sensor_need_update(struct peci_sensor_state *state)
+{
+ return !state->valid ||
+ time_after(jiffies, state->last_updated + PECI_HWMON_UPDATE_INTERVAL);
+}
+
+/**
+ * peci_sensor_mark_updated() - mark the sensor is updated
+ * @sensor: pointer to sensor data struct
+ */
+static inline void peci_sensor_mark_updated(struct peci_sensor_state *state)
+{
+ state->valid = true;
+ state->last_updated = jiffies;
+}
+
+#endif /* __PECI_HWMON_COMMON_H */
diff --git a/drivers/hwmon/peci/cputemp.c b/drivers/hwmon/peci/cputemp.c
new file mode 100644
index 000000000000..12156328f5cf
--- /dev/null
+++ b/drivers/hwmon/peci/cputemp.c
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2018-2021 Intel Corporation
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/peci.h>
+#include <linux/peci-cpu.h>
+#include <linux/units.h>
+
+#include "common.h"
+
+#define CORE_NUMS_MAX 64
+
+#define BASE_CHANNEL_NUMS 5
+#define CPUTEMP_CHANNEL_NUMS (BASE_CHANNEL_NUMS + CORE_NUMS_MAX)
+
+#define TEMP_TARGET_FAN_TEMP_MASK GENMASK(15, 8)
+#define TEMP_TARGET_REF_TEMP_MASK GENMASK(23, 16)
+#define TEMP_TARGET_TJ_OFFSET_MASK GENMASK(29, 24)
+
+#define DTS_MARGIN_MASK GENMASK(15, 0)
+#define PCS_MODULE_TEMP_MASK GENMASK(15, 0)
+
+struct resolved_cores_reg {
+ u8 bus;
+ u8 dev;
+ u8 func;
+ u8 offset;
+};
+
+struct cpu_info {
+ struct resolved_cores_reg *reg;
+ u8 min_peci_revision;
+ s32 (*thermal_margin_to_millidegree)(u16 val);
+};
+
+struct peci_temp_target {
+ s32 tcontrol;
+ s32 tthrottle;
+ s32 tjmax;
+ struct peci_sensor_state state;
+};
+
+enum peci_temp_target_type {
+ tcontrol_type,
+ tthrottle_type,
+ tjmax_type,
+ crit_hyst_type,
+};
+
+struct peci_cputemp {
+ struct peci_device *peci_dev;
+ struct device *dev;
+ const char *name;
+ const struct cpu_info *gen_info;
+ struct {
+ struct peci_temp_target target;
+ struct peci_sensor_data die;
+ struct peci_sensor_data dts;
+ struct peci_sensor_data core[CORE_NUMS_MAX];
+ } temp;
+ const char **coretemp_label;
+ DECLARE_BITMAP(core_mask, CORE_NUMS_MAX);
+};
+
+enum cputemp_channels {
+ channel_die,
+ channel_dts,
+ channel_tcontrol,
+ channel_tthrottle,
+ channel_tjmax,
+ channel_core,
+};
+
+static const char * const cputemp_label[BASE_CHANNEL_NUMS] = {
+ "Die",
+ "DTS",
+ "Tcontrol",
+ "Tthrottle",
+ "Tjmax",
+};
+
+static int update_temp_target(struct peci_cputemp *priv)
+{
+ s32 tthrottle_offset, tcontrol_margin;
+ u32 pcs;
+ int ret;
+
+ if (!peci_sensor_need_update(&priv->temp.target.state))
+ return 0;
+
+ ret = peci_pcs_read(priv->peci_dev, PECI_PCS_TEMP_TARGET, 0, &pcs);
+ if (ret)
+ return ret;
+
+ priv->temp.target.tjmax =
+ FIELD_GET(TEMP_TARGET_REF_TEMP_MASK, pcs) * MILLIDEGREE_PER_DEGREE;
+
+ tcontrol_margin = FIELD_GET(TEMP_TARGET_FAN_TEMP_MASK, pcs);
+ tcontrol_margin = sign_extend32(tcontrol_margin, 7) * MILLIDEGREE_PER_DEGREE;
+ priv->temp.target.tcontrol = priv->temp.target.tjmax - tcontrol_margin;
+
+ tthrottle_offset = FIELD_GET(TEMP_TARGET_TJ_OFFSET_MASK, pcs) * MILLIDEGREE_PER_DEGREE;
+ priv->temp.target.tthrottle = priv->temp.target.tjmax - tthrottle_offset;
+
+ peci_sensor_mark_updated(&priv->temp.target.state);
+
+ return 0;
+}
+
+static int get_temp_target(struct peci_cputemp *priv, enum peci_temp_target_type type, long *val)
+{
+ int ret;
+
+ mutex_lock(&priv->temp.target.state.lock);
+
+ ret = update_temp_target(priv);
+ if (ret)
+ goto unlock;
+
+ switch (type) {
+ case tcontrol_type:
+ *val = priv->temp.target.tcontrol;
+ break;
+ case tthrottle_type:
+ *val = priv->temp.target.tthrottle;
+ break;
+ case tjmax_type:
+ *val = priv->temp.target.tjmax;
+ break;
+ case crit_hyst_type:
+ *val = priv->temp.target.tjmax - priv->temp.target.tcontrol;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+unlock:
+ mutex_unlock(&priv->temp.target.state.lock);
+
+ return ret;
+}
+
+/*
+ * Error codes:
+ * 0x8000: General sensor error
+ * 0x8001: Reserved
+ * 0x8002: Underflow on reading value
+ * 0x8003-0x81ff: Reserved
+ */
+static bool dts_valid(u16 val)
+{
+ return val < 0x8000 || val > 0x81ff;
+}
+
+/*
+ * Processors return a value of DTS reading in S10.6 fixed point format
+ * (16 bits: 10-bit signed magnitude, 6-bit fraction).
+ */
+static s32 dts_ten_dot_six_to_millidegree(u16 val)
+{
+ return sign_extend32(val, 15) * MILLIDEGREE_PER_DEGREE / 64;
+}
+
+/*
+ * For older processors, thermal margin reading is returned in S8.8 fixed
+ * point format (16 bits: 8-bit signed magnitude, 8-bit fraction).
+ */
+static s32 dts_eight_dot_eight_to_millidegree(u16 val)
+{
+ return sign_extend32(val, 15) * MILLIDEGREE_PER_DEGREE / 256;
+}
+
+static int get_die_temp(struct peci_cputemp *priv, long *val)
+{
+ int ret = 0;
+ long tjmax;
+ u16 temp;
+
+ mutex_lock(&priv->temp.die.state.lock);
+ if (!peci_sensor_need_update(&priv->temp.die.state))
+ goto skip_update;
+
+ ret = peci_temp_read(priv->peci_dev, &temp);
+ if (ret)
+ goto err_unlock;
+
+ if (!dts_valid(temp)) {
+ ret = -EIO;
+ goto err_unlock;
+ }
+
+ ret = get_temp_target(priv, tjmax_type, &tjmax);
+ if (ret)
+ goto err_unlock;
+
+ priv->temp.die.value = (s32)tjmax + dts_ten_dot_six_to_millidegree(temp);
+
+ peci_sensor_mark_updated(&priv->temp.die.state);
+
+skip_update:
+ *val = priv->temp.die.value;
+err_unlock:
+ mutex_unlock(&priv->temp.die.state.lock);
+ return ret;
+}
+
+static int get_dts(struct peci_cputemp *priv, long *val)
+{
+ int ret = 0;
+ u16 thermal_margin;
+ long tcontrol;
+ u32 pcs;
+
+ mutex_lock(&priv->temp.dts.state.lock);
+ if (!peci_sensor_need_update(&priv->temp.dts.state))
+ goto skip_update;
+
+ ret = peci_pcs_read(priv->peci_dev, PECI_PCS_THERMAL_MARGIN, 0, &pcs);
+ if (ret)
+ goto err_unlock;
+
+ thermal_margin = FIELD_GET(DTS_MARGIN_MASK, pcs);
+ if (!dts_valid(thermal_margin)) {
+ ret = -EIO;
+ goto err_unlock;
+ }
+
+ ret = get_temp_target(priv, tcontrol_type, &tcontrol);
+ if (ret)
+ goto err_unlock;
+
+ /* Note that the tcontrol should be available before calling it */
+ priv->temp.dts.value =
+ (s32)tcontrol - priv->gen_info->thermal_margin_to_millidegree(thermal_margin);
+
+ peci_sensor_mark_updated(&priv->temp.dts.state);
+
+skip_update:
+ *val = priv->temp.dts.value;
+err_unlock:
+ mutex_unlock(&priv->temp.dts.state.lock);
+ return ret;
+}
+
+static int get_core_temp(struct peci_cputemp *priv, int core_index, long *val)
+{
+ int ret = 0;
+ u16 core_dts_margin;
+ long tjmax;
+ u32 pcs;
+
+ mutex_lock(&priv->temp.core[core_index].state.lock);
+ if (!peci_sensor_need_update(&priv->temp.core[core_index].state))
+ goto skip_update;
+
+ ret = peci_pcs_read(priv->peci_dev, PECI_PCS_MODULE_TEMP, core_index, &pcs);
+ if (ret)
+ goto err_unlock;
+
+ core_dts_margin = FIELD_GET(PCS_MODULE_TEMP_MASK, pcs);
+ if (!dts_valid(core_dts_margin)) {
+ ret = -EIO;
+ goto err_unlock;
+ }
+
+ ret = get_temp_target(priv, tjmax_type, &tjmax);
+ if (ret)
+ goto err_unlock;
+
+ /* Note that the tjmax should be available before calling it */
+ priv->temp.core[core_index].value =
+ (s32)tjmax + dts_ten_dot_six_to_millidegree(core_dts_margin);
+
+ peci_sensor_mark_updated(&priv->temp.core[core_index].state);
+
+skip_update:
+ *val = priv->temp.core[core_index].value;
+err_unlock:
+ mutex_unlock(&priv->temp.core[core_index].state.lock);
+ return ret;
+}
+
+static int cputemp_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct peci_cputemp *priv = dev_get_drvdata(dev);
+
+ if (attr != hwmon_temp_label)
+ return -EOPNOTSUPP;
+
+ *str = channel < channel_core ?
+ cputemp_label[channel] : priv->coretemp_label[channel - channel_core];
+
+ return 0;
+}
+
+static int cputemp_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct peci_cputemp *priv = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_temp_input:
+ switch (channel) {
+ case channel_die:
+ return get_die_temp(priv, val);
+ case channel_dts:
+ return get_dts(priv, val);
+ case channel_tcontrol:
+ return get_temp_target(priv, tcontrol_type, val);
+ case channel_tthrottle:
+ return get_temp_target(priv, tthrottle_type, val);
+ case channel_tjmax:
+ return get_temp_target(priv, tjmax_type, val);
+ default:
+ return get_core_temp(priv, channel - channel_core, val);
+ }
+ break;
+ case hwmon_temp_max:
+ return get_temp_target(priv, tcontrol_type, val);
+ case hwmon_temp_crit:
+ return get_temp_target(priv, tjmax_type, val);
+ case hwmon_temp_crit_hyst:
+ return get_temp_target(priv, crit_hyst_type, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t cputemp_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct peci_cputemp *priv = data;
+
+ if (channel > CPUTEMP_CHANNEL_NUMS)
+ return 0;
+
+ if (channel < channel_core)
+ return 0444;
+
+ if (test_bit(channel - channel_core, priv->core_mask))
+ return 0444;
+
+ return 0;
+}
+
+static int init_core_mask(struct peci_cputemp *priv)
+{
+ struct peci_device *peci_dev = priv->peci_dev;
+ struct resolved_cores_reg *reg = priv->gen_info->reg;
+ u64 core_mask;
+ u32 data;
+ int ret;
+
+ /* Get the RESOLVED_CORES register value */
+ switch (peci_dev->info.model) {
+ case INTEL_FAM6_ICELAKE_X:
+ case INTEL_FAM6_ICELAKE_D:
+ ret = peci_ep_pci_local_read(peci_dev, 0, reg->bus, reg->dev,
+ reg->func, reg->offset + 4, &data);
+ if (ret)
+ return ret;
+
+ core_mask = (u64)data << 32;
+
+ ret = peci_ep_pci_local_read(peci_dev, 0, reg->bus, reg->dev,
+ reg->func, reg->offset, &data);
+ if (ret)
+ return ret;
+
+ core_mask |= data;
+
+ break;
+ default:
+ ret = peci_pci_local_read(peci_dev, reg->bus, reg->dev,
+ reg->func, reg->offset, &data);
+ if (ret)
+ return ret;
+
+ core_mask = data;
+
+ break;
+ }
+
+ if (!core_mask)
+ return -EIO;
+
+ bitmap_from_u64(priv->core_mask, core_mask);
+
+ return 0;
+}
+
+static int create_temp_label(struct peci_cputemp *priv)
+{
+ unsigned long core_max = find_last_bit(priv->core_mask, CORE_NUMS_MAX);
+ int i;
+
+ priv->coretemp_label = devm_kzalloc(priv->dev, core_max * sizeof(char *), GFP_KERNEL);
+ if (!priv->coretemp_label)
+ return -ENOMEM;
+
+ for_each_set_bit(i, priv->core_mask, CORE_NUMS_MAX) {
+ priv->coretemp_label[i] = devm_kasprintf(priv->dev, GFP_KERNEL, "Core %d", i);
+ if (!priv->coretemp_label[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void check_resolved_cores(struct peci_cputemp *priv)
+{
+ /*
+ * Failure to resolve cores is non-critical, we're still able to
+ * provide other sensor data.
+ */
+
+ if (init_core_mask(priv))
+ return;
+
+ if (create_temp_label(priv))
+ bitmap_zero(priv->core_mask, CORE_NUMS_MAX);
+}
+
+static void sensor_init(struct peci_cputemp *priv)
+{
+ int i;
+
+ mutex_init(&priv->temp.target.state.lock);
+ mutex_init(&priv->temp.die.state.lock);
+ mutex_init(&priv->temp.dts.state.lock);
+
+ for_each_set_bit(i, priv->core_mask, CORE_NUMS_MAX)
+ mutex_init(&priv->temp.core[i].state.lock);
+}
+
+static const struct hwmon_ops peci_cputemp_ops = {
+ .is_visible = cputemp_is_visible,
+ .read_string = cputemp_read_string,
+ .read = cputemp_read,
+};
+
+static const u32 peci_cputemp_temp_channel_config[] = {
+ /* Die temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST,
+ /* DTS margin */
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST,
+ /* Tcontrol temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT,
+ /* Tthrottle temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ /* Tjmax temperature */
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ /* Core temperature - for all core channels */
+ [channel_core ... CPUTEMP_CHANNEL_NUMS - 1] = HWMON_T_LABEL | HWMON_T_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info peci_cputemp_temp_channel = {
+ .type = hwmon_temp,
+ .config = peci_cputemp_temp_channel_config,
+};
+
+static const struct hwmon_channel_info *peci_cputemp_info[] = {
+ &peci_cputemp_temp_channel,
+ NULL
+};
+
+static const struct hwmon_chip_info peci_cputemp_chip_info = {
+ .ops = &peci_cputemp_ops,
+ .info = peci_cputemp_info,
+};
+
+static int peci_cputemp_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct peci_device *peci_dev = to_peci_device(dev->parent);
+ struct peci_cputemp *priv;
+ struct device *hwmon_dev;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->name = devm_kasprintf(dev, GFP_KERNEL, "peci_cputemp.cpu%d",
+ peci_dev->info.socket_id);
+ if (!priv->name)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->peci_dev = peci_dev;
+ priv->gen_info = (const struct cpu_info *)id->driver_data;
+
+ /*
+ * This is just a sanity check. Since we're using commands that are
+ * guaranteed to be supported on a given platform, we should never see
+ * revision lower than expected.
+ */
+ if (peci_dev->info.peci_revision < priv->gen_info->min_peci_revision)
+ dev_warn(priv->dev,
+ "Unexpected PECI revision %#x, some features may be unavailable\n",
+ peci_dev->info.peci_revision);
+
+ check_resolved_cores(priv);
+
+ sensor_init(priv);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(priv->dev, priv->name,
+ priv, &peci_cputemp_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+/*
+ * RESOLVED_CORES PCI configuration register may have different location on
+ * different platforms.
+ */
+static struct resolved_cores_reg resolved_cores_reg_hsx = {
+ .bus = 1,
+ .dev = 30,
+ .func = 3,
+ .offset = 0xb4,
+};
+
+static struct resolved_cores_reg resolved_cores_reg_icx = {
+ .bus = 14,
+ .dev = 30,
+ .func = 3,
+ .offset = 0xd0,
+};
+
+static const struct cpu_info cpu_hsx = {
+ .reg = &resolved_cores_reg_hsx,
+ .min_peci_revision = 0x33,
+ .thermal_margin_to_millidegree = &dts_eight_dot_eight_to_millidegree,
+};
+
+static const struct cpu_info cpu_icx = {
+ .reg = &resolved_cores_reg_icx,
+ .min_peci_revision = 0x40,
+ .thermal_margin_to_millidegree = &dts_ten_dot_six_to_millidegree,
+};
+
+static const struct auxiliary_device_id peci_cputemp_ids[] = {
+ {
+ .name = "peci_cpu.cputemp.hsx",
+ .driver_data = (kernel_ulong_t)&cpu_hsx,
+ },
+ {
+ .name = "peci_cpu.cputemp.bdx",
+ .driver_data = (kernel_ulong_t)&cpu_hsx,
+ },
+ {
+ .name = "peci_cpu.cputemp.bdxd",
+ .driver_data = (kernel_ulong_t)&cpu_hsx,
+ },
+ {
+ .name = "peci_cpu.cputemp.skx",
+ .driver_data = (kernel_ulong_t)&cpu_hsx,
+ },
+ {
+ .name = "peci_cpu.cputemp.icx",
+ .driver_data = (kernel_ulong_t)&cpu_icx,
+ },
+ {
+ .name = "peci_cpu.cputemp.icxd",
+ .driver_data = (kernel_ulong_t)&cpu_icx,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, peci_cputemp_ids);
+
+static struct auxiliary_driver peci_cputemp_driver = {
+ .probe = peci_cputemp_probe,
+ .id_table = peci_cputemp_ids,
+};
+
+module_auxiliary_driver(peci_cputemp_driver);
+
+MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
+MODULE_AUTHOR("Iwona Winiarska <iwona.winiarska@intel.com>");
+MODULE_DESCRIPTION("PECI cputemp driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PECI_CPU);
--
2.31.1
^ permalink raw reply related
* [PATCH v6 11/13] hwmon: peci: Add dimmtemp driver
From: Iwona Winiarska @ 2022-01-25 1:11 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska,
Jae Hyun Yoo
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
Add peci-dimmtemp driver for Temperature Sensor on DIMM readings that
are accessible via the processor PECI interface.
The main use case for the driver (and PECI interface) is out-of-band
management, where we're able to obtain thermal readings from an external
entity connected with PECI, e.g. BMC on server platforms.
Co-developed-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
---
drivers/hwmon/peci/Kconfig | 13 +
drivers/hwmon/peci/Makefile | 2 +
drivers/hwmon/peci/dimmtemp.c | 630 ++++++++++++++++++++++++++++++++++
3 files changed, 645 insertions(+)
create mode 100644 drivers/hwmon/peci/dimmtemp.c
diff --git a/drivers/hwmon/peci/Kconfig b/drivers/hwmon/peci/Kconfig
index e10eed68d70a..9d32a57badfe 100644
--- a/drivers/hwmon/peci/Kconfig
+++ b/drivers/hwmon/peci/Kconfig
@@ -14,5 +14,18 @@ config SENSORS_PECI_CPUTEMP
This driver can also be built as a module. If so, the module
will be called peci-cputemp.
+config SENSORS_PECI_DIMMTEMP
+ tristate "PECI DIMM temperature monitoring client"
+ depends on PECI
+ select SENSORS_PECI
+ select PECI_CPU
+ help
+ If you say yes here you get support for the generic Intel PECI hwmon
+ driver which provides Temperature Sensor on DIMM readings that are
+ accessible via the processor PECI interface.
+
+ This driver can also be built as a module. If so, the module
+ will be called peci-dimmtemp.
+
config SENSORS_PECI
tristate
diff --git a/drivers/hwmon/peci/Makefile b/drivers/hwmon/peci/Makefile
index e8a0ada5ab1f..191cfa0227f3 100644
--- a/drivers/hwmon/peci/Makefile
+++ b/drivers/hwmon/peci/Makefile
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
peci-cputemp-y := cputemp.o
+peci-dimmtemp-y := dimmtemp.o
obj-$(CONFIG_SENSORS_PECI_CPUTEMP) += peci-cputemp.o
+obj-$(CONFIG_SENSORS_PECI_DIMMTEMP) += peci-dimmtemp.o
diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c
new file mode 100644
index 000000000000..c8222354c005
--- /dev/null
+++ b/drivers/hwmon/peci/dimmtemp.c
@@ -0,0 +1,630 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2018-2021 Intel Corporation
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/peci.h>
+#include <linux/peci-cpu.h>
+#include <linux/units.h>
+#include <linux/workqueue.h>
+
+#include "common.h"
+
+#define DIMM_MASK_CHECK_DELAY_JIFFIES msecs_to_jiffies(5000)
+
+/* Max number of channel ranks and DIMM index per channel */
+#define CHAN_RANK_MAX_ON_HSX 8
+#define DIMM_IDX_MAX_ON_HSX 3
+#define CHAN_RANK_MAX_ON_BDX 4
+#define DIMM_IDX_MAX_ON_BDX 3
+#define CHAN_RANK_MAX_ON_BDXD 2
+#define DIMM_IDX_MAX_ON_BDXD 2
+#define CHAN_RANK_MAX_ON_SKX 6
+#define DIMM_IDX_MAX_ON_SKX 2
+#define CHAN_RANK_MAX_ON_ICX 8
+#define DIMM_IDX_MAX_ON_ICX 2
+#define CHAN_RANK_MAX_ON_ICXD 4
+#define DIMM_IDX_MAX_ON_ICXD 2
+
+#define CHAN_RANK_MAX CHAN_RANK_MAX_ON_HSX
+#define DIMM_IDX_MAX DIMM_IDX_MAX_ON_HSX
+#define DIMM_NUMS_MAX (CHAN_RANK_MAX * DIMM_IDX_MAX)
+
+#define CPU_SEG_MASK GENMASK(23, 16)
+#define GET_CPU_SEG(x) (((x) & CPU_SEG_MASK) >> 16)
+#define CPU_BUS_MASK GENMASK(7, 0)
+#define GET_CPU_BUS(x) ((x) & CPU_BUS_MASK)
+
+#define DIMM_TEMP_MAX GENMASK(15, 8)
+#define DIMM_TEMP_CRIT GENMASK(23, 16)
+#define GET_TEMP_MAX(x) (((x) & DIMM_TEMP_MAX) >> 8)
+#define GET_TEMP_CRIT(x) (((x) & DIMM_TEMP_CRIT) >> 16)
+
+#define NO_DIMM_RETRY_COUNT_MAX 5
+
+struct peci_dimmtemp;
+
+struct dimm_info {
+ int chan_rank_max;
+ int dimm_idx_max;
+ u8 min_peci_revision;
+ int (*read_thresholds)(struct peci_dimmtemp *priv, int dimm_order,
+ int chan_rank, u32 *data);
+};
+
+struct peci_dimm_thresholds {
+ long temp_max;
+ long temp_crit;
+ struct peci_sensor_state state;
+};
+
+enum peci_dimm_threshold_type {
+ temp_max_type,
+ temp_crit_type,
+};
+
+struct peci_dimmtemp {
+ struct peci_device *peci_dev;
+ struct device *dev;
+ const char *name;
+ const struct dimm_info *gen_info;
+ struct delayed_work detect_work;
+ struct {
+ struct peci_sensor_data temp;
+ struct peci_dimm_thresholds thresholds;
+ } dimm[DIMM_NUMS_MAX];
+ char **dimmtemp_label;
+ DECLARE_BITMAP(dimm_mask, DIMM_NUMS_MAX);
+ u8 no_dimm_retry_count;
+};
+
+static u8 __dimm_temp(u32 reg, int dimm_order)
+{
+ return (reg >> (dimm_order * 8)) & 0xff;
+}
+
+static int get_dimm_temp(struct peci_dimmtemp *priv, int dimm_no, long *val)
+{
+ int dimm_order = dimm_no % priv->gen_info->dimm_idx_max;
+ int chan_rank = dimm_no / priv->gen_info->dimm_idx_max;
+ int ret = 0;
+ u32 data;
+
+ mutex_lock(&priv->dimm[dimm_no].temp.state.lock);
+ if (!peci_sensor_need_update(&priv->dimm[dimm_no].temp.state))
+ goto skip_update;
+
+ ret = peci_pcs_read(priv->peci_dev, PECI_PCS_DDR_DIMM_TEMP, chan_rank, &data);
+ if (ret)
+ goto unlock;
+
+ priv->dimm[dimm_no].temp.value = __dimm_temp(data, dimm_order) * MILLIDEGREE_PER_DEGREE;
+
+ peci_sensor_mark_updated(&priv->dimm[dimm_no].temp.state);
+
+skip_update:
+ *val = priv->dimm[dimm_no].temp.value;
+unlock:
+ mutex_unlock(&priv->dimm[dimm_no].temp.state.lock);
+ return ret;
+}
+
+static int update_thresholds(struct peci_dimmtemp *priv, int dimm_no)
+{
+ int dimm_order = dimm_no % priv->gen_info->dimm_idx_max;
+ int chan_rank = dimm_no / priv->gen_info->dimm_idx_max;
+ u32 data;
+ int ret;
+
+ if (!peci_sensor_need_update(&priv->dimm[dimm_no].thresholds.state))
+ return 0;
+
+ ret = priv->gen_info->read_thresholds(priv, dimm_order, chan_rank, &data);
+ if (ret == -ENODATA) /* Use default or previous value */
+ return 0;
+ if (ret)
+ return ret;
+
+ priv->dimm[dimm_no].thresholds.temp_max = GET_TEMP_MAX(data) * MILLIDEGREE_PER_DEGREE;
+ priv->dimm[dimm_no].thresholds.temp_crit = GET_TEMP_CRIT(data) * MILLIDEGREE_PER_DEGREE;
+
+ peci_sensor_mark_updated(&priv->dimm[dimm_no].thresholds.state);
+
+ return 0;
+}
+
+static int get_dimm_thresholds(struct peci_dimmtemp *priv, enum peci_dimm_threshold_type type,
+ int dimm_no, long *val)
+{
+ int ret;
+
+ mutex_lock(&priv->dimm[dimm_no].thresholds.state.lock);
+ ret = update_thresholds(priv, dimm_no);
+ if (ret)
+ goto unlock;
+
+ switch (type) {
+ case temp_max_type:
+ *val = priv->dimm[dimm_no].thresholds.temp_max;
+ break;
+ case temp_crit_type:
+ *val = priv->dimm[dimm_no].thresholds.temp_crit;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+unlock:
+ mutex_unlock(&priv->dimm[dimm_no].thresholds.state.lock);
+
+ return ret;
+}
+
+static int dimmtemp_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct peci_dimmtemp *priv = dev_get_drvdata(dev);
+
+ if (attr != hwmon_temp_label)
+ return -EOPNOTSUPP;
+
+ *str = (const char *)priv->dimmtemp_label[channel];
+
+ return 0;
+}
+
+static int dimmtemp_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct peci_dimmtemp *priv = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_temp_input:
+ return get_dimm_temp(priv, channel, val);
+ case hwmon_temp_max:
+ return get_dimm_thresholds(priv, temp_max_type, channel, val);
+ case hwmon_temp_crit:
+ return get_dimm_thresholds(priv, temp_crit_type, channel, val);
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static umode_t dimmtemp_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct peci_dimmtemp *priv = data;
+
+ if (test_bit(channel, priv->dimm_mask))
+ return 0444;
+
+ return 0;
+}
+
+static const struct hwmon_ops peci_dimmtemp_ops = {
+ .is_visible = dimmtemp_is_visible,
+ .read_string = dimmtemp_read_string,
+ .read = dimmtemp_read,
+};
+
+static int check_populated_dimms(struct peci_dimmtemp *priv)
+{
+ int chan_rank_max = priv->gen_info->chan_rank_max;
+ int dimm_idx_max = priv->gen_info->dimm_idx_max;
+ u32 chan_rank_empty = 0;
+ u64 dimm_mask = 0;
+ int chan_rank, dimm_idx, ret;
+ u32 pcs;
+
+ BUILD_BUG_ON(BITS_PER_TYPE(chan_rank_empty) < CHAN_RANK_MAX);
+ BUILD_BUG_ON(BITS_PER_TYPE(dimm_mask) < DIMM_NUMS_MAX);
+ if (chan_rank_max * dimm_idx_max > DIMM_NUMS_MAX) {
+ WARN_ONCE(1, "Unsupported number of DIMMs - chan_rank_max: %d, dimm_idx_max: %d",
+ chan_rank_max, dimm_idx_max);
+ return -EINVAL;
+ }
+
+ for (chan_rank = 0; chan_rank < chan_rank_max; chan_rank++) {
+ ret = peci_pcs_read(priv->peci_dev, PECI_PCS_DDR_DIMM_TEMP, chan_rank, &pcs);
+ if (ret) {
+ /*
+ * Overall, we expect either success or -EINVAL in
+ * order to determine whether DIMM is populated or not.
+ * For anything else we fall back to deferring the
+ * detection to be performed at a later point in time.
+ */
+ if (ret == -EINVAL) {
+ chan_rank_empty |= BIT(chan_rank);
+ continue;
+ }
+
+ return -EAGAIN;
+ }
+
+ for (dimm_idx = 0; dimm_idx < dimm_idx_max; dimm_idx++)
+ if (__dimm_temp(pcs, dimm_idx))
+ dimm_mask |= BIT(chan_rank * dimm_idx_max + dimm_idx);
+ }
+
+ /*
+ * If we got all -EINVALs, it means that the CPU doesn't have any
+ * DIMMs. Unfortunately, it may also happen at the very start of
+ * host platform boot. Retrying a couple of times lets us make sure
+ * that the state is persistent.
+ */
+ if (chan_rank_empty == GENMASK(chan_rank_max - 1, 0)) {
+ if (priv->no_dimm_retry_count < NO_DIMM_RETRY_COUNT_MAX) {
+ priv->no_dimm_retry_count++;
+
+ return -EAGAIN;
+ }
+
+ return -ENODEV;
+ }
+
+ /*
+ * It's possible that memory training is not done yet. In this case we
+ * defer the detection to be performed at a later point in time.
+ */
+ if (!dimm_mask) {
+ priv->no_dimm_retry_count = 0;
+ return -EAGAIN;
+ }
+
+ dev_dbg(priv->dev, "Scanned populated DIMMs: %#llx\n", dimm_mask);
+
+ bitmap_from_u64(priv->dimm_mask, dimm_mask);
+
+ return 0;
+}
+
+static int create_dimm_temp_label(struct peci_dimmtemp *priv, int chan)
+{
+ int rank = chan / priv->gen_info->dimm_idx_max;
+ int idx = chan % priv->gen_info->dimm_idx_max;
+
+ priv->dimmtemp_label[chan] = devm_kasprintf(priv->dev, GFP_KERNEL,
+ "DIMM %c%d", 'A' + rank,
+ idx + 1);
+ if (!priv->dimmtemp_label[chan])
+ return -ENOMEM;
+
+ return 0;
+}
+
+static const u32 peci_dimmtemp_temp_channel_config[] = {
+ [0 ... DIMM_NUMS_MAX - 1] = HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT,
+ 0
+};
+
+static const struct hwmon_channel_info peci_dimmtemp_temp_channel = {
+ .type = hwmon_temp,
+ .config = peci_dimmtemp_temp_channel_config,
+};
+
+static const struct hwmon_channel_info *peci_dimmtemp_temp_info[] = {
+ &peci_dimmtemp_temp_channel,
+ NULL
+};
+
+static const struct hwmon_chip_info peci_dimmtemp_chip_info = {
+ .ops = &peci_dimmtemp_ops,
+ .info = peci_dimmtemp_temp_info,
+};
+
+static int create_dimm_temp_info(struct peci_dimmtemp *priv)
+{
+ int ret, i, channels;
+ struct device *dev;
+
+ /*
+ * We expect to either find populated DIMMs and carry on with creating
+ * sensors, or find out that there are no DIMMs populated.
+ * All other states mean that the platform never reached the state that
+ * allows to check DIMM state - causing us to retry later on.
+ */
+ ret = check_populated_dimms(priv);
+ if (ret == -ENODEV) {
+ dev_dbg(priv->dev, "No DIMMs found\n");
+ return 0;
+ } else if (ret) {
+ schedule_delayed_work(&priv->detect_work, DIMM_MASK_CHECK_DELAY_JIFFIES);
+ dev_dbg(priv->dev, "Deferred populating DIMM temp info\n");
+ return ret;
+ }
+
+ channels = priv->gen_info->chan_rank_max * priv->gen_info->dimm_idx_max;
+
+ priv->dimmtemp_label = devm_kzalloc(priv->dev, channels * sizeof(char *), GFP_KERNEL);
+ if (!priv->dimmtemp_label)
+ return -ENOMEM;
+
+ for_each_set_bit(i, priv->dimm_mask, DIMM_NUMS_MAX) {
+ ret = create_dimm_temp_label(priv, i);
+ if (ret)
+ return ret;
+ mutex_init(&priv->dimm[i].thresholds.state.lock);
+ mutex_init(&priv->dimm[i].temp.state.lock);
+ }
+
+ dev = devm_hwmon_device_register_with_info(priv->dev, priv->name, priv,
+ &peci_dimmtemp_chip_info, NULL);
+ if (IS_ERR(dev)) {
+ dev_err(priv->dev, "Failed to register hwmon device\n");
+ return PTR_ERR(dev);
+ }
+
+ dev_dbg(priv->dev, "%s: sensor '%s'\n", dev_name(dev), priv->name);
+
+ return 0;
+}
+
+static void create_dimm_temp_info_delayed(struct work_struct *work)
+{
+ struct peci_dimmtemp *priv = container_of(to_delayed_work(work),
+ struct peci_dimmtemp,
+ detect_work);
+ int ret;
+
+ ret = create_dimm_temp_info(priv);
+ if (ret && ret != -EAGAIN)
+ dev_err(priv->dev, "Failed to populate DIMM temp info\n");
+}
+
+static void remove_delayed_work(void *_priv)
+{
+ struct peci_dimmtemp *priv = _priv;
+
+ cancel_delayed_work_sync(&priv->detect_work);
+}
+
+static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct peci_device *peci_dev = to_peci_device(dev->parent);
+ struct peci_dimmtemp *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->name = devm_kasprintf(dev, GFP_KERNEL, "peci_dimmtemp.cpu%d",
+ peci_dev->info.socket_id);
+ if (!priv->name)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->peci_dev = peci_dev;
+ priv->gen_info = (const struct dimm_info *)id->driver_data;
+
+ /*
+ * This is just a sanity check. Since we're using commands that are
+ * guaranteed to be supported on a given platform, we should never see
+ * revision lower than expected.
+ */
+ if (peci_dev->info.peci_revision < priv->gen_info->min_peci_revision)
+ dev_warn(priv->dev,
+ "Unexpected PECI revision %#x, some features may be unavailable\n",
+ peci_dev->info.peci_revision);
+
+ INIT_DELAYED_WORK(&priv->detect_work, create_dimm_temp_info_delayed);
+
+ ret = devm_add_action_or_reset(priv->dev, remove_delayed_work, priv);
+ if (ret)
+ return ret;
+
+ ret = create_dimm_temp_info(priv);
+ if (ret && ret != -EAGAIN) {
+ dev_err(dev, "Failed to populate DIMM temp info\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+read_thresholds_hsx(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u32 *data)
+{
+ u8 dev, func;
+ u16 reg;
+ int ret;
+
+ /*
+ * Device 20, Function 0: IMC 0 channel 0 -> rank 0
+ * Device 20, Function 1: IMC 0 channel 1 -> rank 1
+ * Device 21, Function 0: IMC 0 channel 2 -> rank 2
+ * Device 21, Function 1: IMC 0 channel 3 -> rank 3
+ * Device 23, Function 0: IMC 1 channel 0 -> rank 4
+ * Device 23, Function 1: IMC 1 channel 1 -> rank 5
+ * Device 24, Function 0: IMC 1 channel 2 -> rank 6
+ * Device 24, Function 1: IMC 1 channel 3 -> rank 7
+ */
+ dev = 20 + chan_rank / 2 + chan_rank / 4;
+ func = chan_rank % 2;
+ reg = 0x120 + dimm_order * 4;
+
+ ret = peci_pci_local_read(priv->peci_dev, 1, dev, func, reg, data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int
+read_thresholds_bdxd(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u32 *data)
+{
+ u8 dev, func;
+ u16 reg;
+ int ret;
+
+ /*
+ * Device 10, Function 2: IMC 0 channel 0 -> rank 0
+ * Device 10, Function 6: IMC 0 channel 1 -> rank 1
+ * Device 12, Function 2: IMC 1 channel 0 -> rank 2
+ * Device 12, Function 6: IMC 1 channel 1 -> rank 3
+ */
+ dev = 10 + chan_rank / 2 * 2;
+ func = (chan_rank % 2) ? 6 : 2;
+ reg = 0x120 + dimm_order * 4;
+
+ ret = peci_pci_local_read(priv->peci_dev, 2, dev, func, reg, data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int
+read_thresholds_skx(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u32 *data)
+{
+ u8 dev, func;
+ u16 reg;
+ int ret;
+
+ /*
+ * Device 10, Function 2: IMC 0 channel 0 -> rank 0
+ * Device 10, Function 6: IMC 0 channel 1 -> rank 1
+ * Device 11, Function 2: IMC 0 channel 2 -> rank 2
+ * Device 12, Function 2: IMC 1 channel 0 -> rank 3
+ * Device 12, Function 6: IMC 1 channel 1 -> rank 4
+ * Device 13, Function 2: IMC 1 channel 2 -> rank 5
+ */
+ dev = 10 + chan_rank / 3 * 2 + (chan_rank % 3 == 2 ? 1 : 0);
+ func = chan_rank % 3 == 1 ? 6 : 2;
+ reg = 0x120 + dimm_order * 4;
+
+ ret = peci_pci_local_read(priv->peci_dev, 2, dev, func, reg, data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int
+read_thresholds_icx(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u32 *data)
+{
+ u32 reg_val;
+ u64 offset;
+ int ret;
+ u8 dev;
+
+ ret = peci_ep_pci_local_read(priv->peci_dev, 0, 13, 0, 2, 0xd4, ®_val);
+ if (ret || !(reg_val & BIT(31)))
+ return -ENODATA; /* Use default or previous value */
+
+ ret = peci_ep_pci_local_read(priv->peci_dev, 0, 13, 0, 2, 0xd0, ®_val);
+ if (ret)
+ return -ENODATA; /* Use default or previous value */
+
+ /*
+ * Device 26, Offset 224e0: IMC 0 channel 0 -> rank 0
+ * Device 26, Offset 264e0: IMC 0 channel 1 -> rank 1
+ * Device 27, Offset 224e0: IMC 1 channel 0 -> rank 2
+ * Device 27, Offset 264e0: IMC 1 channel 1 -> rank 3
+ * Device 28, Offset 224e0: IMC 2 channel 0 -> rank 4
+ * Device 28, Offset 264e0: IMC 2 channel 1 -> rank 5
+ * Device 29, Offset 224e0: IMC 3 channel 0 -> rank 6
+ * Device 29, Offset 264e0: IMC 3 channel 1 -> rank 7
+ */
+ dev = 26 + chan_rank / 2;
+ offset = 0x224e0 + dimm_order * 4 + (chan_rank % 2) * 0x4000;
+
+ ret = peci_mmio_read(priv->peci_dev, 0, GET_CPU_SEG(reg_val), GET_CPU_BUS(reg_val),
+ dev, 0, offset, data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct dimm_info dimm_hsx = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_HSX,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_HSX,
+ .min_peci_revision = 0x33,
+ .read_thresholds = &read_thresholds_hsx,
+};
+
+static const struct dimm_info dimm_bdx = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_BDX,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_BDX,
+ .min_peci_revision = 0x33,
+ .read_thresholds = &read_thresholds_hsx,
+};
+
+static const struct dimm_info dimm_bdxd = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_BDXD,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_BDXD,
+ .min_peci_revision = 0x33,
+ .read_thresholds = &read_thresholds_bdxd,
+};
+
+static const struct dimm_info dimm_skx = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_SKX,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_SKX,
+ .min_peci_revision = 0x33,
+ .read_thresholds = &read_thresholds_skx,
+};
+
+static const struct dimm_info dimm_icx = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_ICX,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_ICX,
+ .min_peci_revision = 0x40,
+ .read_thresholds = &read_thresholds_icx,
+};
+
+static const struct dimm_info dimm_icxd = {
+ .chan_rank_max = CHAN_RANK_MAX_ON_ICXD,
+ .dimm_idx_max = DIMM_IDX_MAX_ON_ICXD,
+ .min_peci_revision = 0x40,
+ .read_thresholds = &read_thresholds_icx,
+};
+
+static const struct auxiliary_device_id peci_dimmtemp_ids[] = {
+ {
+ .name = "peci_cpu.dimmtemp.hsx",
+ .driver_data = (kernel_ulong_t)&dimm_hsx,
+ },
+ {
+ .name = "peci_cpu.dimmtemp.bdx",
+ .driver_data = (kernel_ulong_t)&dimm_bdx,
+ },
+ {
+ .name = "peci_cpu.dimmtemp.bdxd",
+ .driver_data = (kernel_ulong_t)&dimm_bdxd,
+ },
+ {
+ .name = "peci_cpu.dimmtemp.skx",
+ .driver_data = (kernel_ulong_t)&dimm_skx,
+ },
+ {
+ .name = "peci_cpu.dimmtemp.icx",
+ .driver_data = (kernel_ulong_t)&dimm_icx,
+ },
+ {
+ .name = "peci_cpu.dimmtemp.icxd",
+ .driver_data = (kernel_ulong_t)&dimm_icxd,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, peci_dimmtemp_ids);
+
+static struct auxiliary_driver peci_dimmtemp_driver = {
+ .probe = peci_dimmtemp_probe,
+ .id_table = peci_dimmtemp_ids,
+};
+
+module_auxiliary_driver(peci_dimmtemp_driver);
+
+MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
+MODULE_AUTHOR("Iwona Winiarska <iwona.winiarska@intel.com>");
+MODULE_DESCRIPTION("PECI dimmtemp driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PECI_CPU);
--
2.31.1
^ permalink raw reply related
* [PATCH v6 06/13] peci: Add device detection
From: Iwona Winiarska @ 2022-01-25 1:10 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
Since PECI devices are discoverable, we can dynamically detect devices
that are actually available in the system.
This change complements the earlier implementation by rescanning PECI
bus to detect available devices. For this purpose, it also introduces the
minimal API for PECI requests.
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
---
drivers/peci/Makefile | 2 +-
drivers/peci/core.c | 33 +++++++++++
drivers/peci/device.c | 120 ++++++++++++++++++++++++++++++++++++++++
drivers/peci/internal.h | 14 +++++
drivers/peci/request.c | 55 ++++++++++++++++++
include/linux/peci.h | 2 +
6 files changed, 225 insertions(+), 1 deletion(-)
create mode 100644 drivers/peci/device.c
create mode 100644 drivers/peci/request.c
diff --git a/drivers/peci/Makefile b/drivers/peci/Makefile
index 926d8df15cbd..c5f9d3fe21bb 100644
--- a/drivers/peci/Makefile
+++ b/drivers/peci/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
# Core functionality
-peci-y := core.o
+peci-y := core.o request.o device.o
obj-$(CONFIG_PECI) += peci.o
# Hardware specific bus drivers
diff --git a/drivers/peci/core.c b/drivers/peci/core.c
index 73ad0a47fa9d..c3361e6e043a 100644
--- a/drivers/peci/core.c
+++ b/drivers/peci/core.c
@@ -29,6 +29,20 @@ struct device_type peci_controller_type = {
.release = peci_controller_dev_release,
};
+static int peci_controller_scan_devices(struct peci_controller *controller)
+{
+ int ret;
+ u8 addr;
+
+ for (addr = PECI_BASE_ADDR; addr < PECI_BASE_ADDR + PECI_DEVICE_NUM_MAX; addr++) {
+ ret = peci_device_create(controller, addr);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static struct peci_controller *peci_controller_alloc(struct device *dev,
struct peci_controller_ops *ops)
{
@@ -64,10 +78,23 @@ static struct peci_controller *peci_controller_alloc(struct device *dev,
return ERR_PTR(ret);
}
+static int unregister_child(struct device *dev, void *dummy)
+{
+ peci_device_destroy(to_peci_device(dev));
+
+ return 0;
+}
+
static void unregister_controller(void *_controller)
{
struct peci_controller *controller = _controller;
+ /*
+ * Detach any active PECI devices. This can't fail, thus we do not
+ * check the returned value.
+ */
+ device_for_each_child_reverse(&controller->dev, NULL, unregister_child);
+
device_unregister(&controller->dev);
fwnode_handle_put(controller->dev.fwnode);
@@ -113,6 +140,12 @@ struct peci_controller *devm_peci_controller_add(struct device *dev,
if (ret)
return ERR_PTR(ret);
+ /*
+ * Ignoring retval since failures during scan are non-critical for
+ * controller itself.
+ */
+ peci_controller_scan_devices(controller);
+
return controller;
err_fwnode:
diff --git a/drivers/peci/device.c b/drivers/peci/device.c
new file mode 100644
index 000000000000..2b3a2d893aaf
--- /dev/null
+++ b/drivers/peci/device.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2018-2021 Intel Corporation
+
+#include <linux/peci.h>
+#include <linux/slab.h>
+
+#include "internal.h"
+
+/*
+ * PECI device can be removed using sysfs, but the removal can also happen as
+ * a result of controller being removed.
+ * Mutex is used to protect PECI device from being double-deleted.
+ */
+static DEFINE_MUTEX(peci_device_del_lock);
+
+static int peci_detect(struct peci_controller *controller, u8 addr)
+{
+ /*
+ * PECI Ping is a command encoded by tx_len = 0, rx_len = 0.
+ * We expect correct Write FCS if the device at the target address
+ * is able to respond.
+ */
+ struct peci_request req = { 0 };
+ int ret;
+
+ mutex_lock(&controller->bus_lock);
+ ret = controller->ops->xfer(controller, addr, &req);
+ mutex_unlock(&controller->bus_lock);
+
+ return ret;
+}
+
+static bool peci_addr_valid(u8 addr)
+{
+ return addr >= PECI_BASE_ADDR && addr < PECI_BASE_ADDR + PECI_DEVICE_NUM_MAX;
+}
+
+static int peci_dev_exists(struct device *dev, void *data)
+{
+ struct peci_device *device = to_peci_device(dev);
+ u8 *addr = data;
+
+ if (device->addr == *addr)
+ return -EBUSY;
+
+ return 0;
+}
+
+int peci_device_create(struct peci_controller *controller, u8 addr)
+{
+ struct peci_device *device;
+ int ret;
+
+ if (!peci_addr_valid(addr))
+ return -EINVAL;
+
+ /* Check if we have already detected this device before. */
+ ret = device_for_each_child(&controller->dev, &addr, peci_dev_exists);
+ if (ret)
+ return 0;
+
+ ret = peci_detect(controller, addr);
+ if (ret) {
+ /*
+ * Device not present or host state doesn't allow successful
+ * detection at this time.
+ */
+ if (ret == -EIO || ret == -ETIMEDOUT)
+ return 0;
+
+ return ret;
+ }
+
+ device = kzalloc(sizeof(*device), GFP_KERNEL);
+ if (!device)
+ return -ENOMEM;
+
+ device_initialize(&device->dev);
+
+ device->addr = addr;
+ device->dev.parent = &controller->dev;
+ device->dev.bus = &peci_bus_type;
+ device->dev.type = &peci_device_type;
+
+ ret = dev_set_name(&device->dev, "%d-%02x", controller->id, device->addr);
+ if (ret)
+ goto err_put;
+
+ ret = device_add(&device->dev);
+ if (ret)
+ goto err_put;
+
+ return 0;
+
+err_put:
+ put_device(&device->dev);
+
+ return ret;
+}
+
+void peci_device_destroy(struct peci_device *device)
+{
+ mutex_lock(&peci_device_del_lock);
+ if (!device->deleted) {
+ device_unregister(&device->dev);
+ device->deleted = true;
+ }
+ mutex_unlock(&peci_device_del_lock);
+}
+
+static void peci_device_release(struct device *dev)
+{
+ struct peci_device *device = to_peci_device(dev);
+
+ kfree(device);
+}
+
+struct device_type peci_device_type = {
+ .release = peci_device_release,
+};
diff --git a/drivers/peci/internal.h b/drivers/peci/internal.h
index 918dea745a86..57d11a902c5d 100644
--- a/drivers/peci/internal.h
+++ b/drivers/peci/internal.h
@@ -8,6 +8,20 @@
#include <linux/types.h>
struct peci_controller;
+struct peci_device;
+struct peci_request;
+
+/* PECI CPU address range 0x30-0x37 */
+#define PECI_BASE_ADDR 0x30
+#define PECI_DEVICE_NUM_MAX 8
+
+struct peci_request *peci_request_alloc(struct peci_device *device, u8 tx_len, u8 rx_len);
+void peci_request_free(struct peci_request *req);
+
+extern struct device_type peci_device_type;
+
+int peci_device_create(struct peci_controller *controller, u8 addr);
+void peci_device_destroy(struct peci_device *device);
extern struct bus_type peci_bus_type;
diff --git a/drivers/peci/request.c b/drivers/peci/request.c
new file mode 100644
index 000000000000..7dee51c50dd2
--- /dev/null
+++ b/drivers/peci/request.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2021 Intel Corporation
+
+#include <linux/export.h>
+#include <linux/peci.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "internal.h"
+
+/**
+ * peci_request_alloc() - allocate &struct peci_requests
+ * @device: PECI device to which request is going to be sent
+ * @tx_len: TX length
+ * @rx_len: RX length
+ *
+ * Return: A pointer to a newly allocated &struct peci_request on success or NULL otherwise.
+ */
+struct peci_request *peci_request_alloc(struct peci_device *device, u8 tx_len, u8 rx_len)
+{
+ struct peci_request *req;
+
+ /*
+ * TX and RX buffers are fixed length members of peci_request, this is
+ * just a warn for developers to make sure to expand the buffers (or
+ * change the allocation method) if we go over the current limit.
+ */
+ if (WARN_ON_ONCE(tx_len > PECI_REQUEST_MAX_BUF_SIZE || rx_len > PECI_REQUEST_MAX_BUF_SIZE))
+ return NULL;
+ /*
+ * PECI controllers that we are using now don't support DMA, this
+ * should be converted to DMA API once support for controllers that do
+ * allow it is added to avoid an extra copy.
+ */
+ req = kzalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ req->device = device;
+ req->tx.len = tx_len;
+ req->rx.len = rx_len;
+
+ return req;
+}
+EXPORT_SYMBOL_NS_GPL(peci_request_alloc, PECI);
+
+/**
+ * peci_request_free() - free peci_request
+ * @req: the PECI request to be freed
+ */
+void peci_request_free(struct peci_request *req)
+{
+ kfree(req);
+}
+EXPORT_SYMBOL_NS_GPL(peci_request_free, PECI);
diff --git a/include/linux/peci.h b/include/linux/peci.h
index 26e0a4e73b50..7e35673f3786 100644
--- a/include/linux/peci.h
+++ b/include/linux/peci.h
@@ -60,6 +60,7 @@ static inline struct peci_controller *to_peci_controller(void *d)
* @dev: device object to register PECI device to the device model
* @controller: manages the bus segment hosting this PECI device
* @addr: address used on the PECI bus connected to the parent controller
+ * @deleted: indicates that PECI device was already deleted
*
* A peci_device identifies a single device (i.e. CPU) connected to a PECI bus.
* The behaviour exposed to the rest of the system is defined by the PECI driver
@@ -68,6 +69,7 @@ static inline struct peci_controller *to_peci_controller(void *d)
struct peci_device {
struct device dev;
u8 addr;
+ bool deleted;
};
static inline struct peci_device *to_peci_device(struct device *d)
--
2.31.1
^ permalink raw reply related
* [PATCH v6 05/13] peci: Add peci-aspeed controller driver
From: Iwona Winiarska @ 2022-01-25 1:10 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Jae Hyun Yoo,
Iwona Winiarska
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
From: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
ASPEED AST24xx/AST25xx/AST26xx SoCs support the PECI electrical
interface (a.k.a PECI wire) that provides a communication channel with
Intel processors.
This driver allows BMC to discover devices connected to it and
communicate with them using PECI protocol.
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Co-developed-by: Iwona Winiarska <iwona.winiarska@intel.com>
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
---
MAINTAINERS | 8 +
drivers/peci/Kconfig | 6 +
drivers/peci/Makefile | 3 +
drivers/peci/controller/Kconfig | 18 +
drivers/peci/controller/Makefile | 3 +
drivers/peci/controller/peci-aspeed.c | 599 ++++++++++++++++++++++++++
6 files changed, 637 insertions(+)
create mode 100644 drivers/peci/controller/Kconfig
create mode 100644 drivers/peci/controller/Makefile
create mode 100644 drivers/peci/controller/peci-aspeed.c
diff --git a/MAINTAINERS b/MAINTAINERS
index aa7ae643fdb0..6c9b12004e4e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2985,6 +2985,14 @@ S: Maintained
F: Documentation/devicetree/bindings/net/asix,ax88796c.yaml
F: drivers/net/ethernet/asix/ax88796c_*
+ASPEED PECI CONTROLLER
+M: Iwona Winiarska <iwona.winiarska@intel.com>
+L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers)
+L: openbmc@lists.ozlabs.org (moderated for non-subscribers)
+S: Supported
+F: Documentation/devicetree/bindings/peci/peci-aspeed.yaml
+F: drivers/peci/controller/peci-aspeed.c
+
ASPEED PINCTRL DRIVERS
M: Andrew Jeffery <andrew@aj.id.au>
L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers)
diff --git a/drivers/peci/Kconfig b/drivers/peci/Kconfig
index 71a4ad81225a..99279df97a78 100644
--- a/drivers/peci/Kconfig
+++ b/drivers/peci/Kconfig
@@ -13,3 +13,9 @@ menuconfig PECI
This support is also available as a module. If so, the module
will be called peci.
+
+if PECI
+
+source "drivers/peci/controller/Kconfig"
+
+endif # PECI
diff --git a/drivers/peci/Makefile b/drivers/peci/Makefile
index e789a354e842..926d8df15cbd 100644
--- a/drivers/peci/Makefile
+++ b/drivers/peci/Makefile
@@ -3,3 +3,6 @@
# Core functionality
peci-y := core.o
obj-$(CONFIG_PECI) += peci.o
+
+# Hardware specific bus drivers
+obj-y += controller/
diff --git a/drivers/peci/controller/Kconfig b/drivers/peci/controller/Kconfig
new file mode 100644
index 000000000000..32bd8d685c0d
--- /dev/null
+++ b/drivers/peci/controller/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config PECI_ASPEED
+ tristate "ASPEED PECI support"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ depends on OF
+ depends on HAS_IOMEM
+ select COMMON_CLK
+ help
+ This option enables PECI controller driver for ASPEED AST2400,
+ AST2500 and AST2600 SoCs. It allows BMC to discover devices
+ connected to it, and communicate with them using PECI protocol.
+
+ Say Y here if your system runs on ASPEED SoC and you are using it
+ as BMC for Intel platform.
+
+ This driver can also be built as a module. If so, the module will
+ be called peci-aspeed.
diff --git a/drivers/peci/controller/Makefile b/drivers/peci/controller/Makefile
new file mode 100644
index 000000000000..022c28ef1bf0
--- /dev/null
+++ b/drivers/peci/controller/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_PECI_ASPEED) += peci-aspeed.o
diff --git a/drivers/peci/controller/peci-aspeed.c b/drivers/peci/controller/peci-aspeed.c
new file mode 100644
index 000000000000..1925ddc13f00
--- /dev/null
+++ b/drivers/peci/controller/peci-aspeed.c
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2012-2017 ASPEED Technology Inc.
+// Copyright (c) 2018-2021 Intel Corporation
+
+#include <asm/unaligned.h>
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/peci.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+/* ASPEED PECI Registers */
+/* Control Register */
+#define ASPEED_PECI_CTRL 0x00
+#define ASPEED_PECI_CTRL_SAMPLING_MASK GENMASK(19, 16)
+#define ASPEED_PECI_CTRL_RD_MODE_MASK GENMASK(13, 12)
+#define ASPEED_PECI_CTRL_RD_MODE_DBG BIT(13)
+#define ASPEED_PECI_CTRL_RD_MODE_COUNT BIT(12)
+#define ASPEED_PECI_CTRL_CLK_SRC_HCLK BIT(11)
+#define ASPEED_PECI_CTRL_CLK_DIV_MASK GENMASK(10, 8)
+#define ASPEED_PECI_CTRL_INVERT_OUT BIT(7)
+#define ASPEED_PECI_CTRL_INVERT_IN BIT(6)
+#define ASPEED_PECI_CTRL_BUS_CONTENTION_EN BIT(5)
+#define ASPEED_PECI_CTRL_PECI_EN BIT(4)
+#define ASPEED_PECI_CTRL_PECI_CLK_EN BIT(0)
+
+/* Timing Negotiation Register */
+#define ASPEED_PECI_TIMING_NEGOTIATION 0x04
+#define ASPEED_PECI_T_NEGO_MSG_MASK GENMASK(15, 8)
+#define ASPEED_PECI_T_NEGO_ADDR_MASK GENMASK(7, 0)
+
+/* Command Register */
+#define ASPEED_PECI_CMD 0x08
+#define ASPEED_PECI_CMD_PIN_MONITORING BIT(31)
+#define ASPEED_PECI_CMD_STS_MASK GENMASK(27, 24)
+#define ASPEED_PECI_CMD_STS_ADDR_T_NEGO 0x3
+#define ASPEED_PECI_CMD_IDLE_MASK \
+ (ASPEED_PECI_CMD_STS_MASK | ASPEED_PECI_CMD_PIN_MONITORING)
+#define ASPEED_PECI_CMD_FIRE BIT(0)
+
+/* Read/Write Length Register */
+#define ASPEED_PECI_RW_LENGTH 0x0c
+#define ASPEED_PECI_AW_FCS_EN BIT(31)
+#define ASPEED_PECI_RD_LEN_MASK GENMASK(23, 16)
+#define ASPEED_PECI_WR_LEN_MASK GENMASK(15, 8)
+#define ASPEED_PECI_TARGET_ADDR_MASK GENMASK(7, 0)
+
+/* Expected FCS Data Register */
+#define ASPEED_PECI_EXPECTED_FCS 0x10
+#define ASPEED_PECI_EXPECTED_RD_FCS_MASK GENMASK(23, 16)
+#define ASPEED_PECI_EXPECTED_AW_FCS_AUTO_MASK GENMASK(15, 8)
+#define ASPEED_PECI_EXPECTED_WR_FCS_MASK GENMASK(7, 0)
+
+/* Captured FCS Data Register */
+#define ASPEED_PECI_CAPTURED_FCS 0x14
+#define ASPEED_PECI_CAPTURED_RD_FCS_MASK GENMASK(23, 16)
+#define ASPEED_PECI_CAPTURED_WR_FCS_MASK GENMASK(7, 0)
+
+/* Interrupt Register */
+#define ASPEED_PECI_INT_CTRL 0x18
+#define ASPEED_PECI_TIMING_NEGO_SEL_MASK GENMASK(31, 30)
+#define ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO 0
+#define ASPEED_PECI_2ND_BIT_OF_ADDR_NEGO 1
+#define ASPEED_PECI_MESSAGE_NEGO 2
+#define ASPEED_PECI_INT_MASK GENMASK(4, 0)
+#define ASPEED_PECI_INT_BUS_TIMEOUT BIT(4)
+#define ASPEED_PECI_INT_BUS_CONTENTION BIT(3)
+#define ASPEED_PECI_INT_WR_FCS_BAD BIT(2)
+#define ASPEED_PECI_INT_WR_FCS_ABORT BIT(1)
+#define ASPEED_PECI_INT_CMD_DONE BIT(0)
+
+/* Interrupt Status Register */
+#define ASPEED_PECI_INT_STS 0x1c
+#define ASPEED_PECI_INT_TIMING_RESULT_MASK GENMASK(29, 16)
+ /* bits[4..0]: Same bit fields in the 'Interrupt Register' */
+
+/* Rx/Tx Data Buffer Registers */
+#define ASPEED_PECI_WR_DATA0 0x20
+#define ASPEED_PECI_WR_DATA1 0x24
+#define ASPEED_PECI_WR_DATA2 0x28
+#define ASPEED_PECI_WR_DATA3 0x2c
+#define ASPEED_PECI_RD_DATA0 0x30
+#define ASPEED_PECI_RD_DATA1 0x34
+#define ASPEED_PECI_RD_DATA2 0x38
+#define ASPEED_PECI_RD_DATA3 0x3c
+#define ASPEED_PECI_WR_DATA4 0x40
+#define ASPEED_PECI_WR_DATA5 0x44
+#define ASPEED_PECI_WR_DATA6 0x48
+#define ASPEED_PECI_WR_DATA7 0x4c
+#define ASPEED_PECI_RD_DATA4 0x50
+#define ASPEED_PECI_RD_DATA5 0x54
+#define ASPEED_PECI_RD_DATA6 0x58
+#define ASPEED_PECI_RD_DATA7 0x5c
+#define ASPEED_PECI_DATA_BUF_SIZE_MAX 32
+
+/* Timing Negotiation */
+#define ASPEED_PECI_CLK_FREQUENCY_MIN 2000
+#define ASPEED_PECI_CLK_FREQUENCY_DEFAULT 1000000
+#define ASPEED_PECI_CLK_FREQUENCY_MAX 2000000
+#define ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT 8
+/* Timeout */
+#define ASPEED_PECI_IDLE_CHECK_TIMEOUT_US (50 * USEC_PER_MSEC)
+#define ASPEED_PECI_IDLE_CHECK_INTERVAL_US (10 * USEC_PER_MSEC)
+#define ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT 1000
+#define ASPEED_PECI_CMD_TIMEOUT_MS_MAX 1000
+
+#define ASPEED_PECI_CLK_DIV1(msg_timing) (4 * (msg_timing) + 1)
+#define ASPEED_PECI_CLK_DIV2(clk_div_exp) BIT(clk_div_exp)
+#define ASPEED_PECI_CLK_DIV(msg_timing, clk_div_exp) \
+ (4 * ASPEED_PECI_CLK_DIV1(msg_timing) * ASPEED_PECI_CLK_DIV2(clk_div_exp))
+
+struct aspeed_peci {
+ struct peci_controller *controller;
+ struct device *dev;
+ void __iomem *base;
+ struct reset_control *rst;
+ int irq;
+ spinlock_t lock; /* to sync completion status handling */
+ struct completion xfer_complete;
+ struct clk *clk;
+ u32 clk_frequency;
+ u32 status;
+ u32 cmd_timeout_ms;
+};
+
+struct clk_aspeed_peci {
+ struct clk_hw hw;
+ struct aspeed_peci *aspeed_peci;
+};
+
+static void aspeed_peci_controller_enable(struct aspeed_peci *priv)
+{
+ u32 val = readl(priv->base + ASPEED_PECI_CTRL);
+
+ val |= ASPEED_PECI_CTRL_PECI_CLK_EN;
+ val |= ASPEED_PECI_CTRL_PECI_EN;
+
+ writel(val, priv->base + ASPEED_PECI_CTRL);
+}
+
+static void aspeed_peci_init_regs(struct aspeed_peci *priv)
+{
+ u32 val;
+
+ /* Clear interrupts */
+ writel(ASPEED_PECI_INT_MASK, priv->base + ASPEED_PECI_INT_STS);
+
+ /* Set timing negotiation mode and enable interrupts */
+ val = FIELD_PREP(ASPEED_PECI_TIMING_NEGO_SEL_MASK, ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO);
+ val |= ASPEED_PECI_INT_MASK;
+ writel(val, priv->base + ASPEED_PECI_INT_CTRL);
+
+ val = FIELD_PREP(ASPEED_PECI_CTRL_SAMPLING_MASK, ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT);
+ writel(val, priv->base + ASPEED_PECI_CTRL);
+}
+
+static int aspeed_peci_check_idle(struct aspeed_peci *priv)
+{
+ u32 cmd_sts = readl(priv->base + ASPEED_PECI_CMD);
+ int ret;
+
+ /*
+ * Under normal circumstances, we expect to be idle here.
+ * In case there were any errors/timeouts that led to the situation
+ * where the hardware is not in idle state - we need to reset and
+ * reinitialize it to avoid potential controller hang.
+ */
+ if (FIELD_GET(ASPEED_PECI_CMD_STS_MASK, cmd_sts)) {
+ ret = reset_control_assert(priv->rst);
+ if (ret) {
+ dev_err(priv->dev, "cannot assert reset control\n");
+ return ret;
+ }
+
+ ret = reset_control_deassert(priv->rst);
+ if (ret) {
+ dev_err(priv->dev, "cannot deassert reset control\n");
+ return ret;
+ }
+
+ aspeed_peci_init_regs(priv);
+
+ ret = clk_set_rate(priv->clk, priv->clk_frequency);
+ if (ret < 0) {
+ dev_err(priv->dev, "cannot set clock frequency\n");
+ return ret;
+ }
+
+ aspeed_peci_controller_enable(priv);
+ }
+
+ return readl_poll_timeout(priv->base + ASPEED_PECI_CMD,
+ cmd_sts,
+ !(cmd_sts & ASPEED_PECI_CMD_IDLE_MASK),
+ ASPEED_PECI_IDLE_CHECK_INTERVAL_US,
+ ASPEED_PECI_IDLE_CHECK_TIMEOUT_US);
+}
+
+static int aspeed_peci_xfer(struct peci_controller *controller,
+ u8 addr, struct peci_request *req)
+{
+ struct aspeed_peci *priv = dev_get_drvdata(controller->dev.parent);
+ unsigned long timeout = msecs_to_jiffies(priv->cmd_timeout_ms);
+ u32 peci_head;
+ int ret, i;
+
+ if (req->tx.len > ASPEED_PECI_DATA_BUF_SIZE_MAX ||
+ req->rx.len > ASPEED_PECI_DATA_BUF_SIZE_MAX)
+ return -EINVAL;
+
+ /* Check command sts and bus idle state */
+ ret = aspeed_peci_check_idle(priv);
+ if (ret)
+ return ret; /* -ETIMEDOUT */
+
+ spin_lock_irq(&priv->lock);
+ reinit_completion(&priv->xfer_complete);
+
+ peci_head = FIELD_PREP(ASPEED_PECI_TARGET_ADDR_MASK, addr) |
+ FIELD_PREP(ASPEED_PECI_WR_LEN_MASK, req->tx.len) |
+ FIELD_PREP(ASPEED_PECI_RD_LEN_MASK, req->rx.len);
+
+ writel(peci_head, priv->base + ASPEED_PECI_RW_LENGTH);
+
+ for (i = 0; i < req->tx.len; i += 4) {
+ u32 reg = (i < 16 ? ASPEED_PECI_WR_DATA0 : ASPEED_PECI_WR_DATA4) + i % 16;
+
+ writel(get_unaligned_le32(&req->tx.buf[i]), priv->base + reg);
+ }
+
+#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG)
+ dev_dbg(priv->dev, "HEAD : %#08x\n", peci_head);
+ print_hex_dump_bytes("TX : ", DUMP_PREFIX_NONE, req->tx.buf, req->tx.len);
+#endif
+
+ priv->status = 0;
+ writel(ASPEED_PECI_CMD_FIRE, priv->base + ASPEED_PECI_CMD);
+ spin_unlock_irq(&priv->lock);
+
+ ret = wait_for_completion_interruptible_timeout(&priv->xfer_complete, timeout);
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0) {
+ dev_dbg(priv->dev, "timeout waiting for a response\n");
+ return -ETIMEDOUT;
+ }
+
+ spin_lock_irq(&priv->lock);
+
+ if (priv->status != ASPEED_PECI_INT_CMD_DONE) {
+ spin_unlock_irq(&priv->lock);
+ dev_dbg(priv->dev, "no valid response, status: %#02x\n", priv->status);
+ return -EIO;
+ }
+
+ spin_unlock_irq(&priv->lock);
+
+ /*
+ * We need to use dword reads for register access, make sure that the
+ * buffer size is multiple of 4-bytes.
+ */
+ BUILD_BUG_ON(PECI_REQUEST_MAX_BUF_SIZE % 4);
+
+ for (i = 0; i < req->rx.len; i += 4) {
+ u32 reg = (i < 16 ? ASPEED_PECI_RD_DATA0 : ASPEED_PECI_RD_DATA4) + i % 16;
+ u32 rx_data = readl(priv->base + reg);
+
+ put_unaligned_le32(rx_data, &req->rx.buf[i]);
+ }
+
+#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG)
+ print_hex_dump_bytes("RX : ", DUMP_PREFIX_NONE, req->rx.buf, req->rx.len);
+#endif
+ return 0;
+}
+
+static irqreturn_t aspeed_peci_irq_handler(int irq, void *arg)
+{
+ struct aspeed_peci *priv = arg;
+ u32 status;
+
+ spin_lock(&priv->lock);
+ status = readl(priv->base + ASPEED_PECI_INT_STS);
+ writel(status, priv->base + ASPEED_PECI_INT_STS);
+ priv->status |= (status & ASPEED_PECI_INT_MASK);
+
+ /*
+ * All commands should be ended up with a ASPEED_PECI_INT_CMD_DONE bit
+ * set even in an error case.
+ */
+ if (status & ASPEED_PECI_INT_CMD_DONE)
+ complete(&priv->xfer_complete);
+
+ writel(0, priv->base + ASPEED_PECI_CMD);
+
+ spin_unlock(&priv->lock);
+
+ return IRQ_HANDLED;
+}
+
+static void clk_aspeed_peci_find_div_values(unsigned long rate, int *msg_timing, int *clk_div_exp)
+{
+ unsigned long best_diff = ~0ul, diff;
+ int msg_timing_temp, clk_div_exp_temp, i, j;
+
+ for (i = 1; i <= 255; i++)
+ for (j = 0; j < 8; j++) {
+ diff = abs(rate - ASPEED_PECI_CLK_DIV1(i) * ASPEED_PECI_CLK_DIV2(j));
+ if (diff < best_diff) {
+ msg_timing_temp = i;
+ clk_div_exp_temp = j;
+ best_diff = diff;
+ }
+ }
+
+ *msg_timing = msg_timing_temp;
+ *clk_div_exp = clk_div_exp_temp;
+}
+
+static int clk_aspeed_peci_get_div(unsigned long rate, const unsigned long *prate)
+{
+ unsigned long this_rate = *prate / (4 * rate);
+ int msg_timing, clk_div_exp;
+
+ clk_aspeed_peci_find_div_values(this_rate, &msg_timing, &clk_div_exp);
+
+ return ASPEED_PECI_CLK_DIV(msg_timing, clk_div_exp);
+}
+
+static int clk_aspeed_peci_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct clk_aspeed_peci *peci_clk = container_of(hw, struct clk_aspeed_peci, hw);
+ struct aspeed_peci *aspeed_peci = peci_clk->aspeed_peci;
+ unsigned long this_rate = prate / (4 * rate);
+ int clk_div_exp, msg_timing;
+ u32 val;
+
+ clk_aspeed_peci_find_div_values(this_rate, &msg_timing, &clk_div_exp);
+
+ val = readl(aspeed_peci->base + ASPEED_PECI_CTRL);
+ val |= FIELD_PREP(ASPEED_PECI_CTRL_CLK_DIV_MASK, clk_div_exp);
+ writel(val, aspeed_peci->base + ASPEED_PECI_CTRL);
+
+ val = FIELD_PREP(ASPEED_PECI_T_NEGO_MSG_MASK, msg_timing);
+ val |= FIELD_PREP(ASPEED_PECI_T_NEGO_ADDR_MASK, msg_timing);
+ writel(val, aspeed_peci->base + ASPEED_PECI_TIMING_NEGOTIATION);
+
+ return 0;
+}
+
+static long clk_aspeed_peci_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ int div = clk_aspeed_peci_get_div(rate, prate);
+
+ return DIV_ROUND_UP_ULL(*prate, div);
+}
+
+static unsigned long clk_aspeed_peci_recalc_rate(struct clk_hw *hw, unsigned long prate)
+{
+ struct clk_aspeed_peci *peci_clk = container_of(hw, struct clk_aspeed_peci, hw);
+ struct aspeed_peci *aspeed_peci = peci_clk->aspeed_peci;
+ int div, msg_timing, addr_timing, clk_div_exp;
+ u32 reg;
+
+ reg = readl(aspeed_peci->base + ASPEED_PECI_TIMING_NEGOTIATION);
+ msg_timing = FIELD_GET(ASPEED_PECI_T_NEGO_MSG_MASK, reg);
+ addr_timing = FIELD_GET(ASPEED_PECI_T_NEGO_ADDR_MASK, reg);
+
+ if (msg_timing != addr_timing)
+ return 0;
+
+ reg = readl(aspeed_peci->base + ASPEED_PECI_CTRL);
+ clk_div_exp = FIELD_GET(ASPEED_PECI_CTRL_CLK_DIV_MASK, reg);
+
+ div = ASPEED_PECI_CLK_DIV(msg_timing, clk_div_exp);
+
+ return DIV_ROUND_UP_ULL(prate, div);
+}
+
+static const struct clk_ops clk_aspeed_peci_ops = {
+ .set_rate = clk_aspeed_peci_set_rate,
+ .round_rate = clk_aspeed_peci_round_rate,
+ .recalc_rate = clk_aspeed_peci_recalc_rate,
+};
+
+/*
+ * PECI HW contains a clock divider which is a combination of:
+ * div0: 4 (fixed divider)
+ * div1: x + 1
+ * div2: 1 << y
+ * In other words, out_clk = in_clk / (div0 * div1 * div2)
+ * The resulting frequency is used by PECI Controller to drive the PECI bus to
+ * negotiate optimal transfer rate.
+ */
+static struct clk *devm_aspeed_peci_register_clk_div(struct device *dev, struct clk *parent,
+ struct aspeed_peci *priv)
+{
+ struct clk_aspeed_peci *peci_clk;
+ struct clk_init_data init;
+ const char *parent_name;
+ char name[32];
+ int ret;
+
+ snprintf(name, sizeof(name), "%s_div", dev_name(dev));
+
+ parent_name = __clk_get_name(parent);
+
+ init.ops = &clk_aspeed_peci_ops;
+ init.name = name;
+ init.parent_names = (const char* []) { parent_name };
+ init.num_parents = 1;
+ init.flags = 0;
+
+ peci_clk = devm_kzalloc(dev, sizeof(struct clk_aspeed_peci), GFP_KERNEL);
+ if (!peci_clk)
+ return ERR_PTR(-ENOMEM);
+
+ peci_clk->hw.init = &init;
+ peci_clk->aspeed_peci = priv;
+
+ ret = devm_clk_hw_register(dev, &peci_clk->hw);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return peci_clk->hw.clk;
+}
+
+static void aspeed_peci_property_sanitize(struct device *dev, const char *propname,
+ u32 min, u32 max, u32 default_val, u32 *propval)
+{
+ u32 val;
+ int ret;
+
+ ret = device_property_read_u32(dev, propname, &val);
+ if (ret) {
+ val = default_val;
+ } else if (val > max || val < min) {
+ dev_warn(dev, "invalid %s: %u, falling back to: %u\n",
+ propname, val, default_val);
+
+ val = default_val;
+ }
+
+ *propval = val;
+}
+
+static void aspeed_peci_property_setup(struct aspeed_peci *priv)
+{
+ aspeed_peci_property_sanitize(priv->dev, "clock-frequency",
+ ASPEED_PECI_CLK_FREQUENCY_MIN, ASPEED_PECI_CLK_FREQUENCY_MAX,
+ ASPEED_PECI_CLK_FREQUENCY_DEFAULT, &priv->clk_frequency);
+ aspeed_peci_property_sanitize(priv->dev, "cmd-timeout-ms",
+ 1, ASPEED_PECI_CMD_TIMEOUT_MS_MAX,
+ ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT, &priv->cmd_timeout_ms);
+}
+
+static struct peci_controller_ops aspeed_ops = {
+ .xfer = aspeed_peci_xfer,
+};
+
+static void aspeed_peci_reset_control_release(void *data)
+{
+ reset_control_assert(data);
+}
+
+static int devm_aspeed_peci_reset_control_deassert(struct device *dev, struct reset_control *rst)
+{
+ int ret;
+
+ ret = reset_control_deassert(rst);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, aspeed_peci_reset_control_release, rst);
+}
+
+static void aspeed_peci_clk_release(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int devm_aspeed_peci_clk_enable(struct device *dev, struct clk *clk)
+{
+ int ret;
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, aspeed_peci_clk_release, clk);
+}
+
+static int aspeed_peci_probe(struct platform_device *pdev)
+{
+ struct peci_controller *controller;
+ struct aspeed_peci *priv;
+ struct clk *ref_clk;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+ dev_set_drvdata(priv->dev, priv);
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->irq = platform_get_irq(pdev, 0);
+ if (!priv->irq)
+ return priv->irq;
+
+ ret = devm_request_irq(&pdev->dev, priv->irq, aspeed_peci_irq_handler,
+ 0, "peci-aspeed", priv);
+ if (ret)
+ return ret;
+
+ init_completion(&priv->xfer_complete);
+ spin_lock_init(&priv->lock);
+
+ priv->rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->rst))
+ return dev_err_probe(priv->dev, PTR_ERR(priv->rst),
+ "failed to get reset control\n");
+
+ ret = devm_aspeed_peci_reset_control_deassert(priv->dev, priv->rst);
+ if (ret)
+ return dev_err_probe(priv->dev, ret, "cannot deassert reset control\n");
+
+ aspeed_peci_property_setup(priv);
+
+ aspeed_peci_init_regs(priv);
+
+ ref_clk = devm_clk_get(priv->dev, NULL);
+ if (IS_ERR(ref_clk))
+ return dev_err_probe(priv->dev, PTR_ERR(ref_clk), "failed to get ref clock\n");
+
+ priv->clk = devm_aspeed_peci_register_clk_div(priv->dev, ref_clk, priv);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(priv->dev, PTR_ERR(priv->clk), "cannot register clock\n");
+
+ ret = clk_set_rate(priv->clk, priv->clk_frequency);
+ if (ret < 0)
+ return dev_err_probe(priv->dev, ret, "cannot set clock frequency\n");
+
+ ret = devm_aspeed_peci_clk_enable(priv->dev, priv->clk);
+ if (ret)
+ return dev_err_probe(priv->dev, ret, "failed to enable clock\n");
+
+ aspeed_peci_controller_enable(priv);
+
+ controller = devm_peci_controller_add(priv->dev, &aspeed_ops);
+ if (IS_ERR(controller))
+ return dev_err_probe(priv->dev, PTR_ERR(controller),
+ "failed to add aspeed peci controller\n");
+
+ priv->controller = controller;
+
+ return 0;
+}
+
+static const struct of_device_id aspeed_peci_of_table[] = {
+ { .compatible = "aspeed,ast2400-peci", },
+ { .compatible = "aspeed,ast2500-peci", },
+ { .compatible = "aspeed,ast2600-peci", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, aspeed_peci_of_table);
+
+static struct platform_driver aspeed_peci_driver = {
+ .probe = aspeed_peci_probe,
+ .driver = {
+ .name = "peci-aspeed",
+ .of_match_table = aspeed_peci_of_table,
+ },
+};
+module_platform_driver(aspeed_peci_driver);
+
+MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
+MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
+MODULE_DESCRIPTION("ASPEED PECI driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PECI);
--
2.31.1
^ permalink raw reply related
* [PATCH v6 09/13] peci: Add peci-cpu driver
From: Iwona Winiarska @ 2022-01-25 1:11 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
PECI is an interface that may be used by different types of devices.
Add a peci-cpu driver compatible with Intel processors. The driver is
responsible for handling auxiliary devices that can subsequently be used
by other drivers (e.g. hwmons).
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
---
MAINTAINERS | 1 +
drivers/peci/Kconfig | 15 ++
drivers/peci/Makefile | 2 +
drivers/peci/cpu.c | 343 +++++++++++++++++++++++++++++++++++++++
drivers/peci/device.c | 1 +
drivers/peci/internal.h | 27 +++
drivers/peci/request.c | 213 ++++++++++++++++++++++++
include/linux/peci-cpu.h | 40 +++++
include/linux/peci.h | 8 -
9 files changed, 642 insertions(+), 8 deletions(-)
create mode 100644 drivers/peci/cpu.c
create mode 100644 include/linux/peci-cpu.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 6c9b12004e4e..7c31e29c885a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15105,6 +15105,7 @@ L: openbmc@lists.ozlabs.org (moderated for non-subscribers)
S: Supported
F: Documentation/devicetree/bindings/peci/
F: drivers/peci/
+F: include/linux/peci-cpu.h
F: include/linux/peci.h
PENSANDO ETHERNET DRIVERS
diff --git a/drivers/peci/Kconfig b/drivers/peci/Kconfig
index 99279df97a78..89872ad83320 100644
--- a/drivers/peci/Kconfig
+++ b/drivers/peci/Kconfig
@@ -16,6 +16,21 @@ menuconfig PECI
if PECI
+config PECI_CPU
+ tristate "PECI CPU"
+ select AUXILIARY_BUS
+ help
+ This option enables peci-cpu driver for Intel processors. It is
+ responsible for creating auxiliary devices that can subsequently
+ be used by other drivers in order to perform various
+ functionalities such as e.g. temperature monitoring.
+
+ Additional drivers must be enabled in order to use the functionality
+ of the device.
+
+ This driver can also be built as a module. If so, the module
+ will be called peci-cpu.
+
source "drivers/peci/controller/Kconfig"
endif # PECI
diff --git a/drivers/peci/Makefile b/drivers/peci/Makefile
index 917f689e147a..7de18137e738 100644
--- a/drivers/peci/Makefile
+++ b/drivers/peci/Makefile
@@ -3,6 +3,8 @@
# Core functionality
peci-y := core.o request.o device.o sysfs.o
obj-$(CONFIG_PECI) += peci.o
+peci-cpu-y := cpu.o
+obj-$(CONFIG_PECI_CPU) += peci-cpu.o
# Hardware specific bus drivers
obj-y += controller/
diff --git a/drivers/peci/cpu.c b/drivers/peci/cpu.c
new file mode 100644
index 000000000000..68eb61c65d34
--- /dev/null
+++ b/drivers/peci/cpu.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2021 Intel Corporation
+
+#include <linux/auxiliary_bus.h>
+#include <linux/module.h>
+#include <linux/peci.h>
+#include <linux/peci-cpu.h>
+#include <linux/slab.h>
+
+#include "internal.h"
+
+/**
+ * peci_temp_read() - read the maximum die temperature from PECI target device
+ * @device: PECI device to which request is going to be sent
+ * @temp_raw: where to store the read temperature
+ *
+ * It uses GetTemp PECI command.
+ *
+ * Return: 0 if succeeded, other values in case errors.
+ */
+int peci_temp_read(struct peci_device *device, s16 *temp_raw)
+{
+ struct peci_request *req;
+
+ req = peci_xfer_get_temp(device);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ *temp_raw = peci_request_temp_read(req);
+
+ peci_request_free(req);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(peci_temp_read, PECI_CPU);
+
+/**
+ * peci_pcs_read() - read PCS register
+ * @device: PECI device to which request is going to be sent
+ * @index: PCS index
+ * @param: PCS parameter
+ * @data: where to store the read data
+ *
+ * It uses RdPkgConfig PECI command.
+ *
+ * Return: 0 if succeeded, other values in case errors.
+ */
+int peci_pcs_read(struct peci_device *device, u8 index, u16 param, u32 *data)
+{
+ struct peci_request *req;
+ int ret;
+
+ req = peci_xfer_pkg_cfg_readl(device, index, param);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ ret = peci_request_status(req);
+ if (ret)
+ goto out_req_free;
+
+ *data = peci_request_data_readl(req);
+out_req_free:
+ peci_request_free(req);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(peci_pcs_read, PECI_CPU);
+
+/**
+ * peci_pci_local_read() - read 32-bit memory location using raw address
+ * @device: PECI device to which request is going to be sent
+ * @bus: bus
+ * @dev: device
+ * @func: function
+ * @reg: register
+ * @data: where to store the read data
+ *
+ * It uses RdPCIConfigLocal PECI command.
+ *
+ * Return: 0 if succeeded, other values in case errors.
+ */
+int peci_pci_local_read(struct peci_device *device, u8 bus, u8 dev, u8 func,
+ u16 reg, u32 *data)
+{
+ struct peci_request *req;
+ int ret;
+
+ req = peci_xfer_pci_cfg_local_readl(device, bus, dev, func, reg);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ ret = peci_request_status(req);
+ if (ret)
+ goto out_req_free;
+
+ *data = peci_request_data_readl(req);
+out_req_free:
+ peci_request_free(req);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(peci_pci_local_read, PECI_CPU);
+
+/**
+ * peci_ep_pci_local_read() - read 32-bit memory location using raw address
+ * @device: PECI device to which request is going to be sent
+ * @seg: PCI segment
+ * @bus: bus
+ * @dev: device
+ * @func: function
+ * @reg: register
+ * @data: where to store the read data
+ *
+ * Like &peci_pci_local_read, but it uses RdEndpointConfig PECI command.
+ *
+ * Return: 0 if succeeded, other values in case errors.
+ */
+int peci_ep_pci_local_read(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg, u32 *data)
+{
+ struct peci_request *req;
+ int ret;
+
+ req = peci_xfer_ep_pci_cfg_local_readl(device, seg, bus, dev, func, reg);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ ret = peci_request_status(req);
+ if (ret)
+ goto out_req_free;
+
+ *data = peci_request_data_readl(req);
+out_req_free:
+ peci_request_free(req);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(peci_ep_pci_local_read, PECI_CPU);
+
+/**
+ * peci_mmio_read() - read 32-bit memory location using 64-bit bar offset address
+ * @device: PECI device to which request is going to be sent
+ * @bar: PCI bar
+ * @seg: PCI segment
+ * @bus: bus
+ * @dev: device
+ * @func: function
+ * @address: 64-bit MMIO address
+ * @data: where to store the read data
+ *
+ * It uses RdEndpointConfig PECI command.
+ *
+ * Return: 0 if succeeded, other values in case errors.
+ */
+int peci_mmio_read(struct peci_device *device, u8 bar, u8 seg,
+ u8 bus, u8 dev, u8 func, u64 address, u32 *data)
+{
+ struct peci_request *req;
+ int ret;
+
+ req = peci_xfer_ep_mmio64_readl(device, bar, seg, bus, dev, func, address);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ ret = peci_request_status(req);
+ if (ret)
+ goto out_req_free;
+
+ *data = peci_request_data_readl(req);
+out_req_free:
+ peci_request_free(req);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(peci_mmio_read, PECI_CPU);
+
+static const char * const peci_adev_types[] = {
+ "cputemp",
+ "dimmtemp",
+};
+
+struct peci_cpu {
+ struct peci_device *device;
+ const struct peci_device_id *id;
+};
+
+static void adev_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+ auxiliary_device_uninit(adev);
+
+ kfree(adev->name);
+ kfree(adev);
+}
+
+static struct auxiliary_device *adev_alloc(struct peci_cpu *priv, int idx)
+{
+ struct peci_controller *controller = to_peci_controller(priv->device->dev.parent);
+ struct auxiliary_device *adev;
+ const char *name;
+ int ret;
+
+ adev = kzalloc(sizeof(*adev), GFP_KERNEL);
+ if (!adev)
+ return ERR_PTR(-ENOMEM);
+
+ name = kasprintf(GFP_KERNEL, "%s.%s", peci_adev_types[idx], (const char *)priv->id->data);
+ if (!name) {
+ ret = -ENOMEM;
+ goto free_adev;
+ }
+
+ adev->name = name;
+ adev->dev.parent = &priv->device->dev;
+ adev->dev.release = adev_release;
+ adev->id = (controller->id << 16) | (priv->device->addr);
+
+ ret = auxiliary_device_init(adev);
+ if (ret)
+ goto free_name;
+
+ return adev;
+
+free_name:
+ kfree(name);
+free_adev:
+ kfree(adev);
+ return ERR_PTR(ret);
+}
+
+static void unregister_adev(void *_adev)
+{
+ struct auxiliary_device *adev = _adev;
+
+ auxiliary_device_delete(adev);
+}
+
+static int devm_adev_add(struct device *dev, int idx)
+{
+ struct peci_cpu *priv = dev_get_drvdata(dev);
+ struct auxiliary_device *adev;
+ int ret;
+
+ adev = adev_alloc(priv, idx);
+ if (IS_ERR(adev))
+ return PTR_ERR(adev);
+
+ ret = auxiliary_device_add(adev);
+ if (ret) {
+ auxiliary_device_uninit(adev);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(&priv->device->dev, unregister_adev, adev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void peci_cpu_add_adevices(struct peci_cpu *priv)
+{
+ struct device *dev = &priv->device->dev;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(peci_adev_types); i++) {
+ ret = devm_adev_add(dev, i);
+ if (ret) {
+ dev_warn(dev, "Failed to register PECI auxiliary: %s, ret = %d\n",
+ peci_adev_types[i], ret);
+ continue;
+ }
+ }
+}
+
+static int
+peci_cpu_probe(struct peci_device *device, const struct peci_device_id *id)
+{
+ struct device *dev = &device->dev;
+ struct peci_cpu *priv;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+ priv->device = device;
+ priv->id = id;
+
+ peci_cpu_add_adevices(priv);
+
+ return 0;
+}
+
+static const struct peci_device_id peci_cpu_device_ids[] = {
+ { /* Haswell Xeon */
+ .family = 6,
+ .model = INTEL_FAM6_HASWELL_X,
+ .data = "hsx",
+ },
+ { /* Broadwell Xeon */
+ .family = 6,
+ .model = INTEL_FAM6_BROADWELL_X,
+ .data = "bdx",
+ },
+ { /* Broadwell Xeon D */
+ .family = 6,
+ .model = INTEL_FAM6_BROADWELL_D,
+ .data = "bdxd",
+ },
+ { /* Skylake Xeon */
+ .family = 6,
+ .model = INTEL_FAM6_SKYLAKE_X,
+ .data = "skx",
+ },
+ { /* Icelake Xeon */
+ .family = 6,
+ .model = INTEL_FAM6_ICELAKE_X,
+ .data = "icx",
+ },
+ { /* Icelake Xeon D */
+ .family = 6,
+ .model = INTEL_FAM6_ICELAKE_D,
+ .data = "icxd",
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(peci, peci_cpu_device_ids);
+
+static struct peci_driver peci_cpu_driver = {
+ .probe = peci_cpu_probe,
+ .id_table = peci_cpu_device_ids,
+ .driver = {
+ .name = "peci-cpu",
+ },
+};
+module_peci_driver(peci_cpu_driver);
+
+MODULE_AUTHOR("Iwona Winiarska <iwona.winiarska@intel.com>");
+MODULE_DESCRIPTION("PECI CPU driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(PECI);
diff --git a/drivers/peci/device.c b/drivers/peci/device.c
index 184b5e650b0b..e6b0bffb14f4 100644
--- a/drivers/peci/device.c
+++ b/drivers/peci/device.c
@@ -3,6 +3,7 @@
#include <linux/bitfield.h>
#include <linux/peci.h>
+#include <linux/peci-cpu.h>
#include <linux/slab.h>
#include "internal.h"
diff --git a/drivers/peci/internal.h b/drivers/peci/internal.h
index 52c02e12874f..9d75ea54504c 100644
--- a/drivers/peci/internal.h
+++ b/drivers/peci/internal.h
@@ -22,6 +22,7 @@ void peci_request_free(struct peci_request *req);
int peci_request_status(struct peci_request *req);
u64 peci_request_dib_read(struct peci_request *req);
+s16 peci_request_temp_read(struct peci_request *req);
u8 peci_request_data_readb(struct peci_request *req);
u16 peci_request_data_readw(struct peci_request *req);
@@ -36,6 +37,32 @@ struct peci_request *peci_xfer_pkg_cfg_readw(struct peci_device *device, u8 inde
struct peci_request *peci_xfer_pkg_cfg_readl(struct peci_device *device, u8 index, u16 param);
struct peci_request *peci_xfer_pkg_cfg_readq(struct peci_device *device, u8 index, u16 param);
+struct peci_request *peci_xfer_pci_cfg_local_readb(struct peci_device *device,
+ u8 bus, u8 dev, u8 func, u16 reg);
+struct peci_request *peci_xfer_pci_cfg_local_readw(struct peci_device *device,
+ u8 bus, u8 dev, u8 func, u16 reg);
+struct peci_request *peci_xfer_pci_cfg_local_readl(struct peci_device *device,
+ u8 bus, u8 dev, u8 func, u16 reg);
+
+struct peci_request *peci_xfer_ep_pci_cfg_local_readb(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg);
+struct peci_request *peci_xfer_ep_pci_cfg_local_readw(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg);
+struct peci_request *peci_xfer_ep_pci_cfg_local_readl(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg);
+
+struct peci_request *peci_xfer_ep_pci_cfg_readb(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg);
+struct peci_request *peci_xfer_ep_pci_cfg_readw(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg);
+struct peci_request *peci_xfer_ep_pci_cfg_readl(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg);
+
+struct peci_request *peci_xfer_ep_mmio32_readl(struct peci_device *device, u8 bar, u8 seg,
+ u8 bus, u8 dev, u8 func, u64 offset);
+
+struct peci_request *peci_xfer_ep_mmio64_readl(struct peci_device *device, u8 bar, u8 seg,
+ u8 bus, u8 dev, u8 func, u64 offset);
/**
* struct peci_device_id - PECI device data to match
* @data: pointer to driver private data specific to device
diff --git a/drivers/peci/request.c b/drivers/peci/request.c
index a49eb351cda3..8d6dd7b6b559 100644
--- a/drivers/peci/request.c
+++ b/drivers/peci/request.c
@@ -3,6 +3,7 @@
#include <linux/bug.h>
#include <linux/export.h>
+#include <linux/pci.h>
#include <linux/peci.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -15,6 +16,10 @@
#define PECI_GET_DIB_WR_LEN 1
#define PECI_GET_DIB_RD_LEN 8
+#define PECI_GET_TEMP_CMD 0x01
+#define PECI_GET_TEMP_WR_LEN 1
+#define PECI_GET_TEMP_RD_LEN 2
+
#define PECI_RDPKGCFG_CMD 0xa1
#define PECI_RDPKGCFG_WR_LEN 5
#define PECI_RDPKGCFG_RD_LEN_BASE 1
@@ -22,6 +27,45 @@
#define PECI_WRPKGCFG_WR_LEN_BASE 6
#define PECI_WRPKGCFG_RD_LEN 1
+#define PECI_RDIAMSR_CMD 0xb1
+#define PECI_RDIAMSR_WR_LEN 5
+#define PECI_RDIAMSR_RD_LEN 9
+#define PECI_WRIAMSR_CMD 0xb5
+#define PECI_RDIAMSREX_CMD 0xd1
+#define PECI_RDIAMSREX_WR_LEN 6
+#define PECI_RDIAMSREX_RD_LEN 9
+
+#define PECI_RDPCICFG_CMD 0x61
+#define PECI_RDPCICFG_WR_LEN 6
+#define PECI_RDPCICFG_RD_LEN 5
+#define PECI_RDPCICFG_RD_LEN_MAX 24
+#define PECI_WRPCICFG_CMD 0x65
+
+#define PECI_RDPCICFGLOCAL_CMD 0xe1
+#define PECI_RDPCICFGLOCAL_WR_LEN 5
+#define PECI_RDPCICFGLOCAL_RD_LEN_BASE 1
+#define PECI_WRPCICFGLOCAL_CMD 0xe5
+#define PECI_WRPCICFGLOCAL_WR_LEN_BASE 6
+#define PECI_WRPCICFGLOCAL_RD_LEN 1
+
+#define PECI_ENDPTCFG_TYPE_LOCAL_PCI 0x03
+#define PECI_ENDPTCFG_TYPE_PCI 0x04
+#define PECI_ENDPTCFG_TYPE_MMIO 0x05
+#define PECI_ENDPTCFG_ADDR_TYPE_PCI 0x04
+#define PECI_ENDPTCFG_ADDR_TYPE_MMIO_D 0x05
+#define PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q 0x06
+#define PECI_RDENDPTCFG_CMD 0xc1
+#define PECI_RDENDPTCFG_PCI_WR_LEN 12
+#define PECI_RDENDPTCFG_MMIO_WR_LEN_BASE 10
+#define PECI_RDENDPTCFG_MMIO_D_WR_LEN 14
+#define PECI_RDENDPTCFG_MMIO_Q_WR_LEN 18
+#define PECI_RDENDPTCFG_RD_LEN_BASE 1
+#define PECI_WRENDPTCFG_CMD 0xc5
+#define PECI_WRENDPTCFG_PCI_WR_LEN_BASE 13
+#define PECI_WRENDPTCFG_MMIO_D_WR_LEN_BASE 15
+#define PECI_WRENDPTCFG_MMIO_Q_WR_LEN_BASE 19
+#define PECI_WRENDPTCFG_RD_LEN 1
+
/* Device Specific Completion Code (CC) Definition */
#define PECI_CC_SUCCESS 0x40
#define PECI_CC_NEED_RETRY 0x80
@@ -202,6 +246,27 @@ struct peci_request *peci_xfer_get_dib(struct peci_device *device)
}
EXPORT_SYMBOL_NS_GPL(peci_xfer_get_dib, PECI);
+struct peci_request *peci_xfer_get_temp(struct peci_device *device)
+{
+ struct peci_request *req;
+ int ret;
+
+ req = peci_request_alloc(device, PECI_GET_TEMP_WR_LEN, PECI_GET_TEMP_RD_LEN);
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ req->tx.buf[0] = PECI_GET_TEMP_CMD;
+
+ ret = peci_request_xfer(req);
+ if (ret) {
+ peci_request_free(req);
+ return ERR_PTR(ret);
+ }
+
+ return req;
+}
+EXPORT_SYMBOL_NS_GPL(peci_xfer_get_temp, PECI);
+
static struct peci_request *
__pkg_cfg_read(struct peci_device *device, u8 index, u16 param, u8 len)
{
@@ -226,6 +291,108 @@ __pkg_cfg_read(struct peci_device *device, u8 index, u16 param, u8 len)
return req;
}
+static u32 __get_pci_addr(u8 bus, u8 dev, u8 func, u16 reg)
+{
+ return reg | PCI_DEVID(bus, PCI_DEVFN(dev, func)) << 12;
+}
+
+static struct peci_request *
+__pci_cfg_local_read(struct peci_device *device, u8 bus, u8 dev, u8 func, u16 reg, u8 len)
+{
+ struct peci_request *req;
+ u32 pci_addr;
+ int ret;
+
+ req = peci_request_alloc(device, PECI_RDPCICFGLOCAL_WR_LEN,
+ PECI_RDPCICFGLOCAL_RD_LEN_BASE + len);
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ pci_addr = __get_pci_addr(bus, dev, func, reg);
+
+ req->tx.buf[0] = PECI_RDPCICFGLOCAL_CMD;
+ req->tx.buf[1] = 0;
+ put_unaligned_le24(pci_addr, &req->tx.buf[2]);
+
+ ret = peci_request_xfer_retry(req);
+ if (ret) {
+ peci_request_free(req);
+ return ERR_PTR(ret);
+ }
+
+ return req;
+}
+
+static struct peci_request *
+__ep_pci_cfg_read(struct peci_device *device, u8 msg_type, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg, u8 len)
+{
+ struct peci_request *req;
+ u32 pci_addr;
+ int ret;
+
+ req = peci_request_alloc(device, PECI_RDENDPTCFG_PCI_WR_LEN,
+ PECI_RDENDPTCFG_RD_LEN_BASE + len);
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ pci_addr = __get_pci_addr(bus, dev, func, reg);
+
+ req->tx.buf[0] = PECI_RDENDPTCFG_CMD;
+ req->tx.buf[1] = 0;
+ req->tx.buf[2] = msg_type;
+ req->tx.buf[3] = 0;
+ req->tx.buf[4] = 0;
+ req->tx.buf[5] = 0;
+ req->tx.buf[6] = PECI_ENDPTCFG_ADDR_TYPE_PCI;
+ req->tx.buf[7] = seg; /* PCI Segment */
+ put_unaligned_le32(pci_addr, &req->tx.buf[8]);
+
+ ret = peci_request_xfer_retry(req);
+ if (ret) {
+ peci_request_free(req);
+ return ERR_PTR(ret);
+ }
+
+ return req;
+}
+
+static struct peci_request *
+__ep_mmio_read(struct peci_device *device, u8 bar, u8 addr_type, u8 seg,
+ u8 bus, u8 dev, u8 func, u64 offset, u8 tx_len, u8 len)
+{
+ struct peci_request *req;
+ int ret;
+
+ req = peci_request_alloc(device, tx_len, PECI_RDENDPTCFG_RD_LEN_BASE + len);
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ req->tx.buf[0] = PECI_RDENDPTCFG_CMD;
+ req->tx.buf[1] = 0;
+ req->tx.buf[2] = PECI_ENDPTCFG_TYPE_MMIO;
+ req->tx.buf[3] = 0; /* Endpoint ID */
+ req->tx.buf[4] = 0; /* Reserved */
+ req->tx.buf[5] = bar;
+ req->tx.buf[6] = addr_type;
+ req->tx.buf[7] = seg; /* PCI Segment */
+ req->tx.buf[8] = PCI_DEVFN(dev, func);
+ req->tx.buf[9] = bus; /* PCI Bus */
+
+ if (addr_type == PECI_ENDPTCFG_ADDR_TYPE_MMIO_D)
+ put_unaligned_le32(offset, &req->tx.buf[10]);
+ else
+ put_unaligned_le64(offset, &req->tx.buf[10]);
+
+ ret = peci_request_xfer_retry(req);
+ if (ret) {
+ peci_request_free(req);
+ return ERR_PTR(ret);
+ }
+
+ return req;
+}
+
u8 peci_request_data_readb(struct peci_request *req)
{
return req->rx.buf[1];
@@ -256,6 +423,12 @@ u64 peci_request_dib_read(struct peci_request *req)
}
EXPORT_SYMBOL_NS_GPL(peci_request_dib_read, PECI);
+s16 peci_request_temp_read(struct peci_request *req)
+{
+ return get_unaligned_le16(&req->rx.buf[0]);
+}
+EXPORT_SYMBOL_NS_GPL(peci_request_temp_read, PECI);
+
#define __read_pkg_config(x, type) \
struct peci_request *peci_xfer_pkg_cfg_##x(struct peci_device *device, u8 index, u16 param) \
{ \
@@ -267,3 +440,43 @@ __read_pkg_config(readb, u8);
__read_pkg_config(readw, u16);
__read_pkg_config(readl, u32);
__read_pkg_config(readq, u64);
+
+#define __read_pci_config_local(x, type) \
+struct peci_request * \
+peci_xfer_pci_cfg_local_##x(struct peci_device *device, u8 bus, u8 dev, u8 func, u16 reg) \
+{ \
+ return __pci_cfg_local_read(device, bus, dev, func, reg, sizeof(type)); \
+} \
+EXPORT_SYMBOL_NS_GPL(peci_xfer_pci_cfg_local_##x, PECI)
+
+__read_pci_config_local(readb, u8);
+__read_pci_config_local(readw, u16);
+__read_pci_config_local(readl, u32);
+
+#define __read_ep_pci_config(x, msg_type, type) \
+struct peci_request * \
+peci_xfer_ep_pci_cfg_##x(struct peci_device *device, u8 seg, u8 bus, u8 dev, u8 func, u16 reg) \
+{ \
+ return __ep_pci_cfg_read(device, msg_type, seg, bus, dev, func, reg, sizeof(type)); \
+} \
+EXPORT_SYMBOL_NS_GPL(peci_xfer_ep_pci_cfg_##x, PECI)
+
+__read_ep_pci_config(local_readb, PECI_ENDPTCFG_TYPE_LOCAL_PCI, u8);
+__read_ep_pci_config(local_readw, PECI_ENDPTCFG_TYPE_LOCAL_PCI, u16);
+__read_ep_pci_config(local_readl, PECI_ENDPTCFG_TYPE_LOCAL_PCI, u32);
+__read_ep_pci_config(readb, PECI_ENDPTCFG_TYPE_PCI, u8);
+__read_ep_pci_config(readw, PECI_ENDPTCFG_TYPE_PCI, u16);
+__read_ep_pci_config(readl, PECI_ENDPTCFG_TYPE_PCI, u32);
+
+#define __read_ep_mmio(x, y, addr_type, type1, type2) \
+struct peci_request *peci_xfer_ep_mmio##y##_##x(struct peci_device *device, u8 bar, u8 seg, \
+ u8 bus, u8 dev, u8 func, u64 offset) \
+{ \
+ return __ep_mmio_read(device, bar, addr_type, seg, bus, dev, func, \
+ offset, PECI_RDENDPTCFG_MMIO_WR_LEN_BASE + sizeof(type1), \
+ sizeof(type2)); \
+} \
+EXPORT_SYMBOL_NS_GPL(peci_xfer_ep_mmio##y##_##x, PECI)
+
+__read_ep_mmio(readl, 32, PECI_ENDPTCFG_ADDR_TYPE_MMIO_D, u32, u32);
+__read_ep_mmio(readl, 64, PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q, u64, u32);
diff --git a/include/linux/peci-cpu.h b/include/linux/peci-cpu.h
new file mode 100644
index 000000000000..ff8ae9c26c80
--- /dev/null
+++ b/include/linux/peci-cpu.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2021 Intel Corporation */
+
+#ifndef __LINUX_PECI_CPU_H
+#define __LINUX_PECI_CPU_H
+
+#include <linux/types.h>
+
+#include "../../arch/x86/include/asm/intel-family.h"
+
+#define PECI_PCS_PKG_ID 0 /* Package Identifier Read */
+#define PECI_PKG_ID_CPU_ID 0x0000 /* CPUID Info */
+#define PECI_PKG_ID_PLATFORM_ID 0x0001 /* Platform ID */
+#define PECI_PKG_ID_DEVICE_ID 0x0002 /* Uncore Device ID */
+#define PECI_PKG_ID_MAX_THREAD_ID 0x0003 /* Max Thread ID */
+#define PECI_PKG_ID_MICROCODE_REV 0x0004 /* CPU Microcode Update Revision */
+#define PECI_PKG_ID_MCA_ERROR_LOG 0x0005 /* Machine Check Status */
+#define PECI_PCS_MODULE_TEMP 9 /* Per Core DTS Temperature Read */
+#define PECI_PCS_THERMAL_MARGIN 10 /* DTS thermal margin */
+#define PECI_PCS_DDR_DIMM_TEMP 14 /* DDR DIMM Temperature */
+#define PECI_PCS_TEMP_TARGET 16 /* Temperature Target Read */
+#define PECI_PCS_TDP_UNITS 30 /* Units for power/energy registers */
+
+struct peci_device;
+
+int peci_temp_read(struct peci_device *device, s16 *temp_raw);
+
+int peci_pcs_read(struct peci_device *device, u8 index,
+ u16 param, u32 *data);
+
+int peci_pci_local_read(struct peci_device *device, u8 bus, u8 dev,
+ u8 func, u16 reg, u32 *data);
+
+int peci_ep_pci_local_read(struct peci_device *device, u8 seg,
+ u8 bus, u8 dev, u8 func, u16 reg, u32 *data);
+
+int peci_mmio_read(struct peci_device *device, u8 bar, u8 seg,
+ u8 bus, u8 dev, u8 func, u64 address, u32 *data);
+
+#endif /* __LINUX_PECI_CPU_H */
diff --git a/include/linux/peci.h b/include/linux/peci.h
index 4eda423ba10c..06e6ef935297 100644
--- a/include/linux/peci.h
+++ b/include/linux/peci.h
@@ -14,14 +14,6 @@
*/
#define PECI_REQUEST_MAX_BUF_SIZE 32
-#define PECI_PCS_PKG_ID 0 /* Package Identifier Read */
-#define PECI_PKG_ID_CPU_ID 0x0000 /* CPUID Info */
-#define PECI_PKG_ID_PLATFORM_ID 0x0001 /* Platform ID */
-#define PECI_PKG_ID_DEVICE_ID 0x0002 /* Uncore Device ID */
-#define PECI_PKG_ID_MAX_THREAD_ID 0x0003 /* Max Thread ID */
-#define PECI_PKG_ID_MICROCODE_REV 0x0004 /* CPU Microcode Update Revision */
-#define PECI_PKG_ID_MCA_ERROR_LOG 0x0005 /* Machine Check Status */
-
struct peci_controller;
struct peci_request;
--
2.31.1
^ permalink raw reply related
* [PATCH v6 02/13] dt-bindings: Add bindings for peci-aspeed
From: Iwona Winiarska @ 2022-01-25 1:10 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska,
Jae Hyun Yoo
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
Add device tree bindings for the peci-aspeed controller driver.
Co-developed-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
---
.../devicetree/bindings/peci/peci-aspeed.yaml | 72 +++++++++++++++++++
1 file changed, 72 insertions(+)
create mode 100644 Documentation/devicetree/bindings/peci/peci-aspeed.yaml
diff --git a/Documentation/devicetree/bindings/peci/peci-aspeed.yaml b/Documentation/devicetree/bindings/peci/peci-aspeed.yaml
new file mode 100644
index 000000000000..1e68a801a92a
--- /dev/null
+++ b/Documentation/devicetree/bindings/peci/peci-aspeed.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/peci/peci-aspeed.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Aspeed PECI Bus Device Tree Bindings
+
+maintainers:
+ - Iwona Winiarska <iwona.winiarska@intel.com>
+ - Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
+
+allOf:
+ - $ref: peci-controller.yaml#
+
+properties:
+ compatible:
+ enum:
+ - aspeed,ast2400-peci
+ - aspeed,ast2500-peci
+ - aspeed,ast2600-peci
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ description:
+ Clock source for PECI controller. Should reference the external
+ oscillator clock.
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+ cmd-timeout-ms:
+ minimum: 1
+ maximum: 1000
+ default: 1000
+
+ clock-frequency:
+ description:
+ The desired operation frequency of PECI controller in Hz.
+ minimum: 2000
+ maximum: 2000000
+ default: 1000000
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - resets
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/ast2600-clock.h>
+ peci-controller@1e78b000 {
+ compatible = "aspeed,ast2600-peci";
+ reg = <0x1e78b000 0x100>;
+ interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&syscon ASPEED_CLK_GATE_REF0CLK>;
+ resets = <&syscon ASPEED_RESET_PECI>;
+ cmd-timeout-ms = <1000>;
+ clock-frequency = <1000000>;
+ };
+...
--
2.31.1
^ permalink raw reply related
* [PATCH v6 03/13] ARM: dts: aspeed: Add PECI controller nodes
From: Iwona Winiarska @ 2022-01-25 1:10 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska,
Jae Hyun Yoo
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
Add PECI controller nodes with all required information.
Co-developed-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
---
arch/arm/boot/dts/aspeed-g4.dtsi | 11 +++++++++++
arch/arm/boot/dts/aspeed-g5.dtsi | 11 +++++++++++
arch/arm/boot/dts/aspeed-g6.dtsi | 11 +++++++++++
3 files changed, 33 insertions(+)
diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi
index f14dace34c5a..fa8b581c3d6c 100644
--- a/arch/arm/boot/dts/aspeed-g4.dtsi
+++ b/arch/arm/boot/dts/aspeed-g4.dtsi
@@ -392,6 +392,17 @@ uart_routing: uart-routing@9c {
};
};
+ peci0: peci-controller@1e78b000 {
+ compatible = "aspeed,ast2400-peci";
+ reg = <0x1e78b000 0x60>;
+ interrupts = <15>;
+ clocks = <&syscon ASPEED_CLK_GATE_REFCLK>;
+ resets = <&syscon ASPEED_RESET_PECI>;
+ cmd-timeout-ms = <1000>;
+ clock-frequency = <1000000>;
+ status = "disabled";
+ };
+
uart2: serial@1e78d000 {
compatible = "ns16550a";
reg = <0x1e78d000 0x20>;
diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi
index 7495f93c5069..4147b397c883 100644
--- a/arch/arm/boot/dts/aspeed-g5.dtsi
+++ b/arch/arm/boot/dts/aspeed-g5.dtsi
@@ -516,6 +516,17 @@ ibt: ibt@140 {
};
};
+ peci0: peci-controller@1e78b000 {
+ compatible = "aspeed,ast2500-peci";
+ reg = <0x1e78b000 0x60>;
+ interrupts = <15>;
+ clocks = <&syscon ASPEED_CLK_GATE_REFCLK>;
+ resets = <&syscon ASPEED_RESET_PECI>;
+ cmd-timeout-ms = <1000>;
+ clock-frequency = <1000000>;
+ status = "disabled";
+ };
+
uart2: serial@1e78d000 {
compatible = "ns16550a";
reg = <0x1e78d000 0x20>;
diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi
index c32e87fad4dc..3d5ce9da42c3 100644
--- a/arch/arm/boot/dts/aspeed-g6.dtsi
+++ b/arch/arm/boot/dts/aspeed-g6.dtsi
@@ -512,6 +512,17 @@ wdt4: watchdog@1e7850c0 {
status = "disabled";
};
+ peci0: peci-controller@1e78b000 {
+ compatible = "aspeed,ast2600-peci";
+ reg = <0x1e78b000 0x100>;
+ interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&syscon ASPEED_CLK_GATE_REF0CLK>;
+ resets = <&syscon ASPEED_RESET_PECI>;
+ cmd-timeout-ms = <1000>;
+ clock-frequency = <1000000>;
+ status = "disabled";
+ };
+
lpc: lpc@1e789000 {
compatible = "aspeed,ast2600-lpc-v2", "simple-mfd", "syscon";
reg = <0x1e789000 0x1000>;
--
2.31.1
^ permalink raw reply related
* [PATCH v6 00/13] Introduce PECI subsystem
From: Iwona Winiarska @ 2022-01-25 1:10 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska
Hi!
Small updates, mainly in ASPEED PECI driver.
Biggest one is replacing memcpy_toio/fromio with regular readl/writel,
to fix problems observed when PECI request/response are unaligned.
Here is the usual cover letter from the previous revision:
The Platform Environment Control Interface (PECI) is a communication
interface between Intel processors and management controllers (e.g.
Baseboard Management Controller, BMC).
This series adds a PECI subsystem and introduces drivers which run in
the Linux instance on the management controller (not the main Intel
processor) and is intended to be used by the OpenBMC [1], a Linux
distribution for BMC devices.
The information exposed over PECI (like processor and DIMM
temperature) refers to the Intel processor and can be consumed by
daemons running on the BMC to, for example, display the processor
temperature in its web interface.
The PECI bus is collection of code that provides interface support
between PECI devices (that actually represent processors) and PECI
controllers (such as the "peci-aspeed" controller) that allow to
access physical PECI interface. PECI devices are bound to PECI
drivers that provides access to PECI services. This series introduces
a generic "peci-cpu" driver that exposes hardware monitoring "cputemp"
and "dimmtemp" using the auxiliary bus.
Exposing "raw" PECI to userspace, either to write userspace drivers or
for debug/testing purpose was left out of this series to encourage
writing kernel drivers instead, but may be pursued in the future.
Introducing PECI to upstream Linux was already attempted before [2].
Since it's been over a year since last revision, and the series
changed quite a bit in the meantime, I've decided to start from v1.
I would also like to give credit to everyone who helped me with
different aspects of preliminary review:
- Pierre-Louis Bossart,
- Tony Luck,
- Andy Shevchenko,
- Dave Hansen.
[1] https://github.com/openbmc/openbmc
[2] https://lore.kernel.org/openbmc/20191211194624.2872-1-jae.hyun.yoo@linux.intel.com/
Changes v5 -> v6:
* Added missing COMMON_CLK selection (lkp@intel.com)
* Fixed WARN_ON always evaluated to true (lkp@intel.com)
* Clean interrupt status unconditionally (Joel)
* Replaced memcpy_toio()/memcpy_fromio() with writel()/readl() to
avoid issues when submitting unaligned PECI commands
Changes v4 -> v5:
* Added clk_aspeed_peci to express controller programming using common
clock framework (Billy)
* Modified peci-aspeed DTS schema to match clock changes (Billy)
* Added workaround for peci-aspeed controller hang (Billy)
* Removed unnecessary "else after return" (Guenter)
Changes v3 -> v4:
* Fixed an issue where peci doesn't work after host shutdown (Zev)
* Replaced kill_device() with peci_device_del_lock (Greg)
* Fixed dts_valid() parameter type (Guenter)
* Removed Jae from MAINTAINERS file (Jae)
Changes v2 -> v3:
* Dropped x86/cpu patches (Boris)
* Dropped pr_fmt() for PECI module (Dan)
* Fixed releasing peci controller device flow (Dan)
* Improved peci-aspeed commit-msg and Kconfig help (Dan)
* Fixed aspeed_peci_xfer() to use the proper spin_lock function (Dan)
* Wrapped print_hex_dump_bytes() in CONFIG_DYNAMIC_DEBUG (Dan)
* Removed debug status logs from aspeed_peci_irq_handler() (Dan)
* Renamed functions using devres to start with "devm" (Dan)
* Changed request to be allocated on stack in peci_detect (Dan)
* Removed redundant WARN_ON on invalid PECI addr (Dan)
* Changed peci_device_create() to use device_initialize() + device_add() pattern (Dan)
* Fixed peci_device_destroy() to use kill_device() avoiding double-free (Dan)
* Renamed functions that perform xfer using "peci_xfer_*" prefix (Dan)
* Renamed peci_request_data_dib(temp) -> peci_request_dib(temp)_read (Dan)
* Fixed thermal margin readings for older Intel processors (Zev)
* Misc hwmon simplifications (Guenter)
* Used BIT_PER_TYPE to verify macro value constrains (Guenter)
* Improved WARN_ON message to print chan_rank_max and idx_dimm_max (Guenter)
* Improved dimmtemp to not reattempt probe if no dimms are populated
Changes v1 -> v2:
Biggest changes when it comes to diffstat are locking in HWMON
(I decided to clean things up a bit while adding it), switching to
devres usage in more places and exposing sysfs interface in separate patch.
* Moved extending X86 ARCHITECTURE MAINTAINERS earlier in series (Dan)
* Removed "default n" for GENERIC_LIB_X86 (Dan)
* Added vendor prefix for peci-aspeed specific properties (Rob)
* Refactored PECI to use devres consistently (Dan)
* Added missing sysfs documentation and excluded adding peci-sysfs to
separate patch (Dan)
* Used module_init() instead of subsys_init() for peci module initialization (Dan)
* Removed redundant struct peci_device member (Dan)
* Improved PECI Kconfig help (Randy/Dan)
* Fixed/removed log messages (Dan, Guenter)
* Refactored peci-cputemp and peci-dimmtemp and added missing locks (Guenter)
* Removed unused dev_set_drvdata() in peci-cputemp and peci-dimmtemp (Guenter)
* Fixed used types, names, fixed broken and added additional comments
to peci-hwmon (Guenter, Zev)
* Refactored peci-dimmtemp to not return -ETIMEDOUT (Guenter)
* Added sanity check for min_peci_revision in peci-hwmon drivers (Zev)
* Added assert for DIMM_NUMS_MAX and additional warning in peci-dimmtemp (Zev)
* Fixed macro names in peci-aspeed (Zev)
* Refactored peci-aspeed sanitizing properties to a single helper function (Zev)
* Fixed peci_cpu_device_ids definition for Broadwell Xeon D (David)
* Refactor peci_request to use a single allocation (Zev)
* Used min_t() to improve code readability (Zev)
* Added macro for PECI_RDENDPTCFG_MMIO_WR_LEN_BASE and fixed adev type
array name to more descriptive (Zev)
* Fixed peci-hwmon commit-msg and documentation (Zev)
Thanks
-Iwona
Iwona Winiarska (11):
dt-bindings: Add generic bindings for PECI
dt-bindings: Add bindings for peci-aspeed
ARM: dts: aspeed: Add PECI controller nodes
peci: Add core infrastructure
peci: Add device detection
peci: Add sysfs interface for PECI bus
peci: Add support for PECI device drivers
peci: Add peci-cpu driver
hwmon: peci: Add cputemp driver
hwmon: peci: Add dimmtemp driver
docs: Add PECI documentation
Jae Hyun Yoo (2):
peci: Add peci-aspeed controller driver
docs: hwmon: Document PECI drivers
Documentation/ABI/testing/sysfs-bus-peci | 16 +
.../devicetree/bindings/peci/peci-aspeed.yaml | 72 ++
.../bindings/peci/peci-controller.yaml | 33 +
Documentation/hwmon/index.rst | 2 +
Documentation/hwmon/peci-cputemp.rst | 90 +++
Documentation/hwmon/peci-dimmtemp.rst | 57 ++
Documentation/index.rst | 1 +
Documentation/peci/index.rst | 16 +
Documentation/peci/peci.rst | 51 ++
MAINTAINERS | 26 +
arch/arm/boot/dts/aspeed-g4.dtsi | 11 +
arch/arm/boot/dts/aspeed-g5.dtsi | 11 +
arch/arm/boot/dts/aspeed-g6.dtsi | 11 +
drivers/Kconfig | 3 +
drivers/Makefile | 1 +
drivers/hwmon/Kconfig | 2 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/peci/Kconfig | 31 +
drivers/hwmon/peci/Makefile | 7 +
drivers/hwmon/peci/common.h | 58 ++
drivers/hwmon/peci/cputemp.c | 592 ++++++++++++++++
drivers/hwmon/peci/dimmtemp.c | 630 ++++++++++++++++++
drivers/peci/Kconfig | 36 +
drivers/peci/Makefile | 10 +
drivers/peci/controller/Kconfig | 18 +
drivers/peci/controller/Makefile | 3 +
drivers/peci/controller/peci-aspeed.c | 599 +++++++++++++++++
drivers/peci/core.c | 236 +++++++
drivers/peci/cpu.c | 343 ++++++++++
drivers/peci/device.c | 252 +++++++
drivers/peci/internal.h | 136 ++++
drivers/peci/request.c | 482 ++++++++++++++
drivers/peci/sysfs.c | 82 +++
include/linux/peci-cpu.h | 40 ++
include/linux/peci.h | 112 ++++
35 files changed, 4071 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-peci
create mode 100644 Documentation/devicetree/bindings/peci/peci-aspeed.yaml
create mode 100644 Documentation/devicetree/bindings/peci/peci-controller.yaml
create mode 100644 Documentation/hwmon/peci-cputemp.rst
create mode 100644 Documentation/hwmon/peci-dimmtemp.rst
create mode 100644 Documentation/peci/index.rst
create mode 100644 Documentation/peci/peci.rst
create mode 100644 drivers/hwmon/peci/Kconfig
create mode 100644 drivers/hwmon/peci/Makefile
create mode 100644 drivers/hwmon/peci/common.h
create mode 100644 drivers/hwmon/peci/cputemp.c
create mode 100644 drivers/hwmon/peci/dimmtemp.c
create mode 100644 drivers/peci/Kconfig
create mode 100644 drivers/peci/Makefile
create mode 100644 drivers/peci/controller/Kconfig
create mode 100644 drivers/peci/controller/Makefile
create mode 100644 drivers/peci/controller/peci-aspeed.c
create mode 100644 drivers/peci/core.c
create mode 100644 drivers/peci/cpu.c
create mode 100644 drivers/peci/device.c
create mode 100644 drivers/peci/internal.h
create mode 100644 drivers/peci/request.c
create mode 100644 drivers/peci/sysfs.c
create mode 100644 include/linux/peci-cpu.h
create mode 100644 include/linux/peci.h
--
2.31.1
^ permalink raw reply
* [PATCH v6 01/13] dt-bindings: Add generic bindings for PECI
From: Iwona Winiarska @ 2022-01-25 1:10 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska,
Rob Herring
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
Add device tree bindings for the PECI controller.
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Rob Herring <robh@kernel.org>
Reviewed-by: Joel Stanley <joel@jms.id.au>
---
.../bindings/peci/peci-controller.yaml | 33 +++++++++++++++++++
1 file changed, 33 insertions(+)
create mode 100644 Documentation/devicetree/bindings/peci/peci-controller.yaml
diff --git a/Documentation/devicetree/bindings/peci/peci-controller.yaml b/Documentation/devicetree/bindings/peci/peci-controller.yaml
new file mode 100644
index 000000000000..bbc3d3f3a929
--- /dev/null
+++ b/Documentation/devicetree/bindings/peci/peci-controller.yaml
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/peci/peci-controller.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic Device Tree Bindings for PECI
+
+maintainers:
+ - Iwona Winiarska <iwona.winiarska@intel.com>
+
+description:
+ PECI (Platform Environment Control Interface) is an interface that provides a
+ communication channel from Intel processors and chipset components to external
+ monitoring or control devices.
+
+properties:
+ $nodename:
+ pattern: "^peci-controller(@.*)?$"
+
+ cmd-timeout-ms:
+ description:
+ Command timeout in units of ms.
+
+additionalProperties: true
+
+examples:
+ - |
+ peci-controller@1e78b000 {
+ reg = <0x1e78b000 0x100>;
+ cmd-timeout-ms = <500>;
+ };
+...
--
2.31.1
^ permalink raw reply related
* Re: [v3 3/3] drm/msm/dsi: Add 10nm dsi phy tuning configuration support
From: Dmitry Baryshkov @ 2022-01-25 1:56 UTC (permalink / raw)
To: Rajeev Nandan, dri-devel, linux-arm-msm, freedreno, devicetree
Cc: linux-kernel, sean, robdclark, robh+dt, robh, quic_abhinavk,
quic_kalyant, quic_mkrishn, jonathan, airlied, daniel, swboyd
In-Reply-To: <1642538320-1127-4-git-send-email-quic_rajeevny@quicinc.com>
On 18/01/2022 23:38, Rajeev Nandan wrote:
> The clock and data lanes of the DSI PHY have a calibration circuitry
> feature. As per the MSM DSI PHY tuning guidelines, the drive strength
> tuning can be done by adjusting rescode offset for hstop/hsbot, and
> the drive level tuning can be done by adjusting the LDO output level
> for the HSTX drive.
>
> Signed-off-by: Rajeev Nandan <quic_rajeevny@quicinc.com>
> ---
>
> Changes in v2:
> - Split into generic code and 10nm-specific part (Dmitry Baryshkov)
> - Fix the backward compatibility (Dmitry Baryshkov)
>
> Changes in v3:
> - Address comments for phy tuning data structure (Dmitry Baryshkov)
> - Make changes as per updated dt-bindings
>
>
> drivers/gpu/drm/msm/dsi/phy/dsi_phy_10nm.c | 97 ++++++++++++++++++++++++++++--
> 1 file changed, 91 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_10nm.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_10nm.c
> index d8128f5..2d225fb 100644
> --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy_10nm.c
> +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy_10nm.c
> @@ -83,6 +83,18 @@ struct dsi_pll_10nm {
>
> #define to_pll_10nm(x) container_of(x, struct dsi_pll_10nm, clk_hw)
>
> +/**
> + * struct dsi_phy_10nm_tuning_cfg - Holds 10nm PHY tuning config parameters.
> + * @rescode_offset_top: Offset for pull-up legs rescode.
> + * @rescode_offset_bot: Offset for pull-down legs rescode.
> + * @vreg_ctrl: vreg ctrl to drive LDO level
> + */
> +struct dsi_phy_10nm_tuning_cfg {
> + u8 rescode_offset_top[DSI_LANE_MAX];
> + u8 rescode_offset_bot[DSI_LANE_MAX];
> + u8 vreg_ctrl;
> +};
> +
> /*
> * Global list of private DSI PLL struct pointers. We need this for bonded DSI
> * mode, where the master PLL's clk_ops needs access the slave's private data
> @@ -747,6 +759,7 @@ static void dsi_phy_hw_v3_0_lane_settings(struct msm_dsi_phy *phy)
> int i;
> u8 tx_dctrl[] = { 0x00, 0x00, 0x00, 0x04, 0x01 };
> void __iomem *lane_base = phy->lane_base;
> + struct dsi_phy_10nm_tuning_cfg *tuning_cfg = phy->tuning_cfg;
>
> if (phy->cfg->quirks & DSI_PHY_10NM_QUIRK_OLD_TIMINGS)
> tx_dctrl[3] = 0x02;
> @@ -775,10 +788,13 @@ static void dsi_phy_hw_v3_0_lane_settings(struct msm_dsi_phy *phy)
> dsi_phy_write(lane_base + REG_DSI_10nm_PHY_LN_CFG2(i), 0x0);
> dsi_phy_write(lane_base + REG_DSI_10nm_PHY_LN_CFG3(i),
> i == 4 ? 0x80 : 0x0);
> - dsi_phy_write(lane_base +
> - REG_DSI_10nm_PHY_LN_OFFSET_TOP_CTRL(i), 0x0);
> - dsi_phy_write(lane_base +
> - REG_DSI_10nm_PHY_LN_OFFSET_BOT_CTRL(i), 0x0);
> +
> + /* platform specific dsi phy drive strength adjustment */
> + dsi_phy_write(lane_base + REG_DSI_10nm_PHY_LN_OFFSET_TOP_CTRL(i),
> + tuning_cfg->rescode_offset_top[i]);
> + dsi_phy_write(lane_base + REG_DSI_10nm_PHY_LN_OFFSET_BOT_CTRL(i),
> + tuning_cfg->rescode_offset_bot[i]);
> +
> dsi_phy_write(lane_base + REG_DSI_10nm_PHY_LN_TX_DCTRL(i),
> tx_dctrl[i]);
> }
> @@ -799,6 +815,7 @@ static int dsi_10nm_phy_enable(struct msm_dsi_phy *phy,
> u32 const timeout_us = 1000;
> struct msm_dsi_dphy_timing *timing = &phy->timing;
> void __iomem *base = phy->base;
> + struct dsi_phy_10nm_tuning_cfg *tuning_cfg = phy->tuning_cfg;
> u32 data;
>
> DBG("");
> @@ -834,8 +851,9 @@ static int dsi_10nm_phy_enable(struct msm_dsi_phy *phy,
> /* Select MS1 byte-clk */
> dsi_phy_write(base + REG_DSI_10nm_PHY_CMN_GLBL_CTRL, 0x10);
>
> - /* Enable LDO */
> - dsi_phy_write(base + REG_DSI_10nm_PHY_CMN_VREG_CTRL, 0x59);
> + /* Enable LDO with platform specific drive level/amplitude adjustment */
> + dsi_phy_write(base + REG_DSI_10nm_PHY_CMN_VREG_CTRL,
> + tuning_cfg->vreg_ctrl);
>
> /* Configure PHY lane swap (TODO: we need to calculate this) */
> dsi_phy_write(base + REG_DSI_10nm_PHY_CMN_LANE_CFG0, 0x21);
> @@ -922,6 +940,71 @@ static void dsi_10nm_phy_disable(struct msm_dsi_phy *phy)
> DBG("DSI%d PHY disabled", phy->id);
> }
>
> +static int dsi_10nm_phy_parse_dt(struct msm_dsi_phy *phy)
> +{
> + struct device *dev = &phy->pdev->dev;
> + struct dsi_phy_10nm_tuning_cfg *tuning_cfg;
> + u8 offset_top[DSI_LANE_MAX] = { 0 }; /* No offset */
> + u8 offset_bot[DSI_LANE_MAX] = { 0 }; /* No offset */
> + u32 ldo_level = 400; /* 400mV */
> + u8 level;
> + int ret, i;
> +
> + tuning_cfg = devm_kzalloc(dev, sizeof(*tuning_cfg), GFP_KERNEL);
> + if (!tuning_cfg)
> + return -ENOMEM;
> +
> + /* Drive strength adjustment parameters */
> + ret = of_property_read_u8_array(dev->of_node, "qcom,phy-rescode-offset-top",
> + offset_top, DSI_LANE_MAX);
> + if (ret && ret != -EINVAL)
> + DRM_DEV_ERROR(dev, "failed to parse qcom,phy-rescode-offset-top, %d\n", ret);
> +
> + for (i = 0; i < DSI_LANE_MAX; i++)
> + tuning_cfg->rescode_offset_top[i] = 0x3f & offset_top[i];
You should validate offset_top and offset_bot to be withing expected
ranges and return an error otherwise.
> +
> + ret = of_property_read_u8_array(dev->of_node, "qcom,phy-rescode-offset-bot",
> + offset_bot, DSI_LANE_MAX);
> + if (ret && ret != -EINVAL)
> + DRM_DEV_ERROR(dev, "failed to parse qcom,phy-rescode-offset-bot, %d\n", ret);
> +
> + for (i = 0; i < DSI_LANE_MAX; i++)
> + tuning_cfg->rescode_offset_bot[i] = 0x3f & offset_bot[i];
> +
> + /* Drive level/amplitude adjustment parameters */
> + ret = of_property_read_u32(dev->of_node, "qcom,phy-drive-ldo-level", &ldo_level);
> + if (ret && ret != -EINVAL)
> + DRM_DEV_ERROR(dev, "failed to parse qcom,phy-drive-ldo-level, %d\n", ret);
> +
> + switch (ldo_level) {
> + case 375:
> + level = 0;
> + break;
> + case 400:
> + level = 1;
> + break;
> + case 425:
> + level = 2;
> + break;
> + case 450:
> + level = 3;
> + break;
> + case 475:
> + level = 4;
> + break;
> + case 500:
> + level = 5;
> + break;
> + default:
> + level = 1; /* 400mV */
Please change to:
dev_err(....);
return -EINVAL;
> + }
> + tuning_cfg->vreg_ctrl = 0x58 | (0x7 & level);
> +
> + phy->tuning_cfg = tuning_cfg;
> +
> + return 0;
> +}
> +
> const struct msm_dsi_phy_cfg dsi_phy_10nm_cfgs = {
> .has_phy_lane = true,
> .reg_cfg = {
> @@ -936,6 +1019,7 @@ const struct msm_dsi_phy_cfg dsi_phy_10nm_cfgs = {
> .pll_init = dsi_pll_10nm_init,
> .save_pll_state = dsi_10nm_pll_save_state,
> .restore_pll_state = dsi_10nm_pll_restore_state,
> + .parse_dt_properties = dsi_10nm_phy_parse_dt,
> },
> .min_pll_rate = 1000000000UL,
> .max_pll_rate = 3500000000UL,
> @@ -957,6 +1041,7 @@ const struct msm_dsi_phy_cfg dsi_phy_10nm_8998_cfgs = {
> .pll_init = dsi_pll_10nm_init,
> .save_pll_state = dsi_10nm_pll_save_state,
> .restore_pll_state = dsi_10nm_pll_restore_state,
> + .parse_dt_properties = dsi_10nm_phy_parse_dt,
> },
> .min_pll_rate = 1000000000UL,
> .max_pll_rate = 3500000000UL,
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [v3 2/3] drm/msm/dsi: Add dsi phy tuning configuration support
From: Dmitry Baryshkov @ 2022-01-25 1:56 UTC (permalink / raw)
To: Rajeev Nandan, dri-devel, linux-arm-msm, freedreno, devicetree
Cc: linux-kernel, sean, robdclark, robh+dt, robh, quic_abhinavk,
quic_kalyant, quic_mkrishn, jonathan, airlied, daniel, swboyd
In-Reply-To: <1642538320-1127-3-git-send-email-quic_rajeevny@quicinc.com>
On 18/01/2022 23:38, Rajeev Nandan wrote:
> Add support for MSM DSI PHY tuning configuration. Current design is
> to support drive strength and drive level/amplitude tuning for
> 10nm PHY version, but this can be extended to other PHY versions.
>
> Signed-off-by: Rajeev Nandan <quic_rajeevny@quicinc.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> ---
>
> Changes in v2:
> - New.
> - Split into generic code and 10nm-specific part (Dmitry Baryshkov)
>
> Changes in v3:
> - s/ops.tuning_cfg_init/ops.parse_dt_properties
> To parse phy version specific DT properties (Dmitry Baryshkov)
> - Address comments for phy tuning data structure (Dmitry Baryshkov)
>
>
> drivers/gpu/drm/msm/dsi/phy/dsi_phy.c | 6 ++++++
> drivers/gpu/drm/msm/dsi/phy/dsi_phy.h | 4 ++++
> 2 files changed, 10 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.c b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.c
> index 8c65ef6..fcbca76 100644
> --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.c
> +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.c
> @@ -739,6 +739,12 @@ static int dsi_phy_driver_probe(struct platform_device *pdev)
> }
> }
>
> + if (phy->cfg->ops.parse_dt_properties) {
> + ret = phy->cfg->ops.parse_dt_properties(phy);
> + if (ret)
> + goto fail;
> + }
> +
> ret = dsi_phy_regulator_init(phy);
> if (ret)
> goto fail;
> diff --git a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h
> index b91303a..9e08081 100644
> --- a/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h
> +++ b/drivers/gpu/drm/msm/dsi/phy/dsi_phy.h
> @@ -25,6 +25,7 @@ struct msm_dsi_phy_ops {
> void (*save_pll_state)(struct msm_dsi_phy *phy);
> int (*restore_pll_state)(struct msm_dsi_phy *phy);
> bool (*set_continuous_clock)(struct msm_dsi_phy *phy, bool enable);
> + int (*parse_dt_properties)(struct msm_dsi_phy *phy);
> };
>
> struct msm_dsi_phy_cfg {
> @@ -81,6 +82,8 @@ struct msm_dsi_dphy_timing {
> #define DSI_PIXEL_PLL_CLK 1
> #define NUM_PROVIDED_CLKS 2
>
> +#define DSI_LANE_MAX 5
> +
> struct msm_dsi_phy {
> struct platform_device *pdev;
> void __iomem *base;
> @@ -98,6 +101,7 @@ struct msm_dsi_phy {
>
> struct msm_dsi_dphy_timing timing;
> const struct msm_dsi_phy_cfg *cfg;
> + void *tuning_cfg;
>
> enum msm_dsi_phy_usecase usecase;
> bool regulator_ldo_mode;
--
With best wishes
Dmitry
^ permalink raw reply
* [PATCH] ARM: dts: suniv: Add MMC and clock macros.
From: Jesse Taube @ 2022-01-25 1:13 UTC (permalink / raw)
To: devicetree; +Cc: robh+dt, Mr.Bossman075, andre.przywara, Mesih Kilinc
Include clock and reset macros and replace magic numbers.
Add MMC node.
Signed-off-by: Mesih Kilinc <mesihkilinc@gmail.com>
Signed-off-by: Jesse Taube <Mr.Bossman075@gmail.com>
---
arch/arm/boot/dts/suniv-f1c100s.dtsi | 41 +++++++++++++++++++++++-----
1 file changed, 34 insertions(+), 7 deletions(-)
diff --git a/arch/arm/boot/dts/suniv-f1c100s.dtsi b/arch/arm/boot/dts/suniv-f1c100s.dtsi
index 6100d3b75f61..32872bb29917 100644
--- a/arch/arm/boot/dts/suniv-f1c100s.dtsi
+++ b/arch/arm/boot/dts/suniv-f1c100s.dtsi
@@ -4,6 +4,9 @@
* Copyright 2018 Mesih Kilinc <mesihkilinc@gmail.com>
*/
+#include <dt-bindings/clock/suniv-ccu-f1c100s.h>
+#include <dt-bindings/reset/suniv-ccu-f1c100s.h>
+
/ {
#address-cells = <1>;
#size-cells = <1>;
@@ -82,7 +85,7 @@ pio: pinctrl@1c20800 {
compatible = "allwinner,suniv-f1c100s-pinctrl";
reg = <0x01c20800 0x400>;
interrupts = <38>, <39>, <40>;
- clocks = <&ccu 37>, <&osc24M>, <&osc32k>;
+ clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
interrupt-controller;
@@ -93,6 +96,11 @@ uart0_pe_pins: uart0-pe-pins {
pins = "PE0", "PE1";
function = "uart0";
};
+
+ mmc0_pins: mmc0-pins {
+ pins = "PF0", "PF1", "PF2", "PF3", "PF4", "PF5";
+ function = "mmc0";
+ };
};
timer@1c20c00 {
@@ -108,14 +116,33 @@ wdt: watchdog@1c20ca0 {
reg = <0x01c20ca0 0x20>;
};
+ mmc0: mmc@1c0f000 {
+ compatible = "allwinner,suniv-f1c100s-mmc",
+ "allwinner,sun7i-a20-mmc";
+ reg = <0x01c0f000 0x1000>;
+ clocks = <&ccu CLK_BUS_MMC0>,
+ <&ccu CLK_MMC0>,
+ <&ccu CLK_MMC0_OUTPUT>,
+ <&ccu CLK_MMC0_SAMPLE>;
+ clock-names = "ahb", "mmc", "output", "sample";
+ resets = <&ccu RST_BUS_MMC0>;
+ reset-names = "ahb";
+ interrupts = <23>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&mmc0_pins>;
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
uart0: serial@1c25000 {
compatible = "snps,dw-apb-uart";
reg = <0x01c25000 0x400>;
interrupts = <1>;
reg-shift = <2>;
reg-io-width = <4>;
- clocks = <&ccu 38>;
- resets = <&ccu 24>;
+ clocks = <&ccu CLK_BUS_UART0>;
+ resets = <&ccu RST_BUS_UART0>;
status = "disabled";
};
@@ -125,8 +152,8 @@ uart1: serial@1c25400 {
interrupts = <2>;
reg-shift = <2>;
reg-io-width = <4>;
- clocks = <&ccu 39>;
- resets = <&ccu 25>;
+ clocks = <&ccu CLK_BUS_UART1>;
+ resets = <&ccu RST_BUS_UART1>;
status = "disabled";
};
@@ -136,8 +163,8 @@ uart2: serial@1c25800 {
interrupts = <3>;
reg-shift = <2>;
reg-io-width = <4>;
- clocks = <&ccu 40>;
- resets = <&ccu 26>;
+ clocks = <&ccu CLK_BUS_UART2>;
+ resets = <&ccu RST_BUS_UART2>;
status = "disabled";
};
};
--
2.34.1
^ permalink raw reply related
* Re: [PATCH 1/2] dt-bindings: input: Add bindings for Azoteq IQS7222A/B/C
From: Jeff LaBundy @ 2022-01-25 1:14 UTC (permalink / raw)
To: Rob Herring; +Cc: dmitry.torokhov, linux-input, devicetree
In-Reply-To: <Ye8158K42TLfG+7I@robh.at.kernel.org>
Hi Rob,
Thank you for your prompt review.
I missed the new DT_CHECKER_FLAGS since I last submitted a binding; I
will take a look at what is happening there.
On Mon, Jan 24, 2022 at 05:27:35PM -0600, Rob Herring wrote:
> On Sun, Jan 23, 2022 at 01:42:31PM -0600, Jeff LaBundy wrote:
> > This patch adds bindings for the Azoteq IQS7222A/B/C family of
> > capacitive touch controllers.
>
> Overall, wow, that's a lot of properties. It leaves me wondering why
> does this h/w need so many and others don't?
Indeed, we had a similar discussion in [1] for a related device. The
reason is that these devices support several additional sensing modes
(e.g. self/mutual capacitance, inductive, Hall), with many registers
that may need to be configured differently based on use-case.
Devices with this level of configurability (e.g. touchscreens) often
lack long bindings because they simply rely on firmware from a vendor;
however, these particular devices have no nonvolatile memory.
[1] https://patchwork.ozlabs.org/patch/1273068/
>
> >
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > ---
> > .../devicetree/bindings/input/iqs7222.yaml | 967 ++++++++++++++++++
>
> azoteq,iqs7222.yaml
Is this a new standard? I'm happy to do this; just trying to understand
if I should send a separate patch to rename all other .../input/iqs*.yaml
bindings that don't follow this convention.
>
> > 1 file changed, 967 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/input/iqs7222.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/input/iqs7222.yaml b/Documentation/devicetree/bindings/input/iqs7222.yaml
> > new file mode 100644
> > index 000000000000..0c23d1d49ebd
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/input/iqs7222.yaml
> > @@ -0,0 +1,967 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/input/iqs7222.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Azoteq IQS7222A/B/C Capacitive Touch Controller
> > +
> > +maintainers:
> > + - Jeff LaBundy <jeff@labundy.com>
> > +
> > +description: |
> > + The Azoteq IQS7222A, IQS7222B and IQS7222C are multichannel capacitive touch
> > + controllers that feature additional sensing capabilities.
> > +
> > + Link to datasheets: https://www.azoteq.com/
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - azoteq,iqs7222a
> > + - azoteq,iqs7222b
> > + - azoteq,iqs7222c
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + irq-gpios:
>
> Use 'interrupts'. Interrupt capable GPIO lines are also interrupt
> providers.
This device is a bit like the Goodix touchscreen where we need to bring
in the interrupt as a GPIO for some additional functions besides just an
interrupt. From a comment I added to the driver:
/*
* The RDY pin behaves as an interrupt, but must also be polled ahead
* of unsolicited I2C communication. As such, it is first opened as a
* GPIO and then passed to gpiod_to_irq() to register the interrupt.
*/
There is no equivalent irq_to_gpio(), so I opted not to use 'interrupts'.
The Goodix example specifies both, but this is redundant. In the driver
I have added logic to apply the GPIO polarity specified in dts to the
IRQF flags.
>
> > + maxItems: 1
> > + description:
> > + Specifies the GPIO connected to the device's active-low RDY output.
> > +
> > + reset-gpios:
> > + maxItems: 1
> > + description:
> > + Specifies the GPIO connected to the device's active-low MCLR input. The
> > + device is temporarily held in hardware reset prior to initialization if
> > + this property is present.
> > +
> > + "#address-cells":
> > + const: 1
> > +
> > + "#size-cells":
> > + const: 0
> > +
> > + azoteq,rf-filt-enable:
> > + type: boolean
> > + description: Enables the device's internal RF filter.
> > +
> > + azoteq,max-counts:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [0, 1, 2, 3]
> > + description: |
> > + Specifies the maximum counts as follows:
>
> Counts of what?
ADC counts before an internal interrupt is generated; I will add some more
details here.
>
> > + 0: 1023
> > + 1: 2047
> > + 2: 4095
> > + 3: 16384
> > +
> > + azoteq,auto-mode:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [0, 1, 2, 3]
> > + description: |
> > + Specifies the number of conversions to occur before an interrupt is
> > + generated as follows:
> > + 0: 4
> > + 1: 8
> > + 2: 16
> > + 3: 32
> > +
> > + azoteq,ati-frac-div-fine:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 31
> > + description: Specifies the preloaded ATI fine fractional divider.
> > +
> > + azoteq,ati-frac-div-coarse:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 31
> > + description: Specifies the preloaded ATI coarse fractional divider.
> > +
> > + azoteq,ati-comp-select:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 1023
> > + description: Specifies the preloaded ATI compensation selection.
> > +
> > + azoteq,lta-beta-lp:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description:
> > + Specifies the long-term average filter damping factor to be applied during
> > + low-power mode.
> > +
> > + azoteq,lta-beta-np:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description:
> > + Specifies the long-term average filter damping factor to be applied during
> > + normal-power mode.
> > +
> > + azoteq,counts-beta-lp:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description:
> > + Specifies the counts filter damping factor to be applied during low-power
> > + mode.
> > +
> > + azoteq,counts-beta-np:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description:
> > + Specifies the counts filter damping factor to be applied during normal-
> > + power mode.
> > +
> > + azoteq,lta-fast-beta-lp:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description:
> > + Specifies the long-term average filter fast damping factor to be applied
> > + during low-power mode.
> > +
> > + azoteq,lta-fast-beta-np:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description:
> > + Specifies the long-term average filter fast damping factor to be applied
> > + during normal-power mode.
> > +
> > + azoteq,timeout-ati-ms:
> > + multipleOf: 500
> > + minimum: 0
> > + maximum: 32767500
> > + description:
> > + Specifies the delay (in ms) before ATI is retried following an ATI error.
> > +
> > + azoteq,rate-ati-ms:
> > + minimum: 0
> > + maximum: 65535
> > + description: Specifies the rate (in ms) at which ATI status is evaluated.
> > +
> > + azoteq,timeout-np-ms:
> > + minimum: 0
> > + maximum: 65535
> > + description:
> > + Specifies the length of time (in ms) to wait for an event before moving
> > + from normal-power mode to low-power mode.
> > +
> > + azoteq,rate-np-ms:
> > + minimum: 0
> > + maximum: 3000
> > + description: Specifies the report rate (in ms) during normal-power mode.
> > +
> > + azoteq,timeout-lp-ms:
> > + minimum: 0
> > + maximum: 65535
> > + description:
> > + Specifies the length of time (in ms) to wait for an event before moving
> > + from low-power mode to ultra-low-power mode.
> > +
> > + azoteq,rate-lp-ms:
> > + minimum: 0
> > + maximum: 3000
> > + description: Specifies the report rate (in ms) during low-power mode.
> > +
> > + azoteq,timeout-ulp-ms:
> > + minimum: 0
> > + maximum: 65535
> > + description:
> > + Specifies the rate (in ms) at which channels not regularly sampled during
> > + ultra-low-power mode are updated.
> > +
> > + azoteq,rate-ulp-ms:
> > + minimum: 0
> > + maximum: 3000
> > + description: Specifies the report rate (in ms) during ultra-low-power mode.
> > +
> > +patternProperties:
> > + "^cycle-[0-9]$":
> > + type: object
> > + description: Represents a conversion cycle serving two sensing channels.
> > +
> > + properties:
> > + azoteq,conv-period:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 255
> > + description: Specifies the cycle's conversion period.
> > +
> > + azoteq,conv-frac:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 255
> > + description: Specifies the cycle's conversion frequency fraction.
> > +
> > + azoteq,tx-enable:
> > + $ref: /schemas/types.yaml#/definitions/uint32-array
> > + minItems: 1
> > + maxItems: 9
> > + items:
> > + minimum: 0
> > + maximum: 8
> > + description: Specifies the CTx pin(s) associated with the cycle.
> > +
> > + azoteq,rx-float-inactive:
> > + type: boolean
> > + description: Floats any inactive CRx pins instead of grounding them.
> > +
> > + azoteq,dead-time-enable:
> > + type: boolean
> > + description:
> > + Increases the denominator of the conversion frequency formula by one.
> > +
> > + azoteq,tx-freq-fosc:
> > + type: boolean
> > + description:
> > + Fixes the conversion frequency to that of the device's core clock.
> > +
> > + azoteq,vbias-enable:
> > + type: boolean
> > + description: Enables the bias voltage for use during inductive sensing.
> > +
> > + azoteq,sense-mode:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [0, 1, 2, 3]
> > + description: |
> > + Specifies the cycle's sensing mode as follows:
> > + 0: None
> > + 1: Self capacitive
> > + 2: Mutual capacitive
> > + 3: Inductive
> > +
> > + Note that in the case of IQS7222A, cycles 5 and 6 are restricted to
> > + Hall-effect sensing.
> > +
> > + azoteq,iref-enable:
> > + type: boolean
> > + description:
> > + Enables the current reference for use during various sensing modes.
> > +
> > + azoteq,iref-level:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description: Specifies the cycle's current reference level.
> > +
> > + azoteq,iref-trim:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description: Specifies the cycle's current reference trim.
> > +
> > + dependencies:
> > + azoteq,iref-level: ["azoteq,iref-enable"]
> > + azoteq,iref-trim: ["azoteq,iref-enable"]
> > +
> > + additionalProperties: false
> > +
> > + "^channel-[0-19]$":
> > + type: object
> > + description:
> > + Represents a single sensing channel. A channel is active if defined and
> > + inactive otherwise.
> > +
> > + Note that in the case of IQS7222A, channels 10 and 11 are restricted to
> > + Hall-effect sensing with events reported on channel 10 only.
> > +
> > + properties:
> > + azoteq,ulp-allow:
> > + type: boolean
> > + description:
> > + Permits the device to enter ultra-low-power mode while the channel
> > + lies in a state of touch or proximity.
> > +
> > + azoteq,ref-select:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 9
> > + description: Specifies a separate reference channel to be followed.
> > +
> > + azoteq,ref-weight:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 65535
> > + description: Specifies the relative weight of the reference channel.
> > +
> > + azoteq,use-prox:
> > + type: boolean
> > + description:
> > + Activates the reference channel in response to proximity events
> > + instead of touch events.
> > +
> > + azoteq,ati-band:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [0, 1, 2, 3]
> > + description: |
> > + Specifies the channel's ATI band as a fraction of its ATI target as
> > + follows:
> > + 0: 1/16
> > + 1: 1/8
> > + 2: 1/4
> > + 3: 1/2
> > +
> > + azoteq,global-halt:
> > + type: boolean
> > + description:
> > + Specifies that the channel's long-term average is to freeze if any
> > + other participating channel lies in a proximity or touch state.
> > +
> > + azoteq,invert-enable:
> > + type: boolean
> > + description:
> > + Inverts the polarity of the states reported for proximity and touch
> > + events relative to their respective thresholds.
> > +
> > + azoteq,dual-direction:
> > + type: boolean
> > + description:
> > + Specifies that the channel's long-term average is to freeze in the
> > + presence of either increasing or decreasing counts, thereby permit-
> > + ting events to be reported in either direction.
> > +
> > + azoteq,rx-enable:
> > + $ref: /schemas/types.yaml#/definitions/uint32-array
> > + minItems: 1
> > + maxItems: 4
> > + items:
> > + minimum: 0
> > + maximum: 7
> > + description: Specifies the CRx pin(s) associated with the channel.
> > +
> > + azoteq,samp-cap-double:
> > + type: boolean
> > + description: Doubles the sampling capacitance from 40 pF to 80 pF.
> > +
> > + azoteq,vref-half:
> > + type: boolean
> > + description: Halves the discharge threshold from 1.0 V to 0.5 V.
> > +
> > + azoteq,proj-bias:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [0, 1, 2, 3]
> > + description: |
> > + Specifies the bias current applied during mutual (projected)
> > + capacitive sensing as follows:
> > + 0: 2 uA
> > + 1: 5 uA
> > + 2: 7 uA
> > + 3: 10 uA
> > +
> > + azoteq,ati-target:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + multipleOf: 8
> > + minimum: 0
> > + maximum: 2040
> > + description: Specifies the channel's ATI target.
> > +
> > + azoteq,ati-base:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + multipleOf: 16
> > + minimum: 0
> > + maximum: 496
> > + description: Specifies the channel's ATI base.
> > +
> > + azoteq,ati-mode:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [0, 1, 2, 3, 4, 5]
> > + description: |
> > + Specifies the channel's ATI mode as follows:
> > + 0: Disabled
> > + 1: Compensation
> > + 2: Compensation divider
> > + 3: Fine fractional divider
> > + 4: Coarse fractional divider
> > + 5: Full
> > +
> > + azoteq,ati-frac-div-fine:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 31
> > + description: Specifies the channel's ATI fine fractional divider.
> > +
> > + azoteq,ati-frac-mult-coarse:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description: Specifies the channel's ATI coarse fractional multiplier.
> > +
> > + azoteq,ati-frac-div-coarse:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 31
> > + description: Specifies the channel's ATI coarse fractional divider.
> > +
> > + azoteq,ati-comp-div:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 31
> > + description: Specifies the channel's ATI compensation divider.
> > +
> > + azoteq,ati-comp-select:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 1023
> > + description: Specifies the channel's ATI compensation selection.
> > +
> > + azoteq,debounce-enter:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description: Specifies the channel's debounce entrance factor.
> > +
> > + azoteq,debounce-exit:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 15
> > + description: Specifies the channel's debounce exit factor.
> > +
> > + patternProperties:
> > + "^event-(prox|touch)$":
> > + type: object
> > + description:
> > + Represents a proximity or touch event reported by the channel.
> > +
> > + properties:
> > + azoteq,gpio-select:
> > + $ref: /schemas/types.yaml#/definitions/uint32-array
> > + minItems: 1
> > + maxItems: 3
> > + items:
> > + minimum: 0
> > + maximum: 2
> > + description: |
> > + Specifies one or more GPIO mapped to the event as follows:
> > + 0: GPIO0
> > + 1: GPIO3 (IQS7222C only)
> > + 2: GPIO4 (IQS7222C only)
> > +
> > + Note that although multiple events can be mapped to a single
> > + GPIO, they must all be of the same type (proximity, touch or
> > + slider gesture).
> > +
> > + azoteq,thresh:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description:
> > + Specifies the threshold for the event. Valid entries range from
> > + 0-127 and 0-255 for proximity and touch events, respectively.
> > +
> > + azoteq,hyst:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 255
> > + description:
> > + Specifies the hysteresis for the event (touch events only).
> > +
> > + azoteq,timeout-press-ms:
> > + multipleOf: 500
> > + minimum: 0
> > + maximum: 127500
> > + description:
> > + Specifies the length of time (in ms) to wait before automatically
> > + releasing a press event. Specify zero to allow the press state to
> > + persist indefinitely.
> > +
> > + The IQS7222B does not feature channel-specific timeouts; the time-
> > + out specified for any one channel applies to all channels.
> > +
> > + linux,code:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description:
> > + Numeric key or switch code associated with the event. Specify
> > + KEY_RESERVED (0) to opt out of event reporting.
> > +
> > + linux,input-type:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + enum: [1, 5]
> > + default: 1
> > + description:
> > + Specifies whether the event is to be interpreted as a key (1)
> > + or a switch (5).
> > +
> > + required:
> > + - linux,code
> > +
> > + additionalProperties: false
> > +
> > + dependencies:
> > + azoteq,ref-weight: ["azoteq,ref-select"]
> > + azoteq,use-prox: ["azoteq,ref-select"]
> > +
> > + additionalProperties: false
> > +
> > + "^slider-[0-1]$":
> > + type: object
> > + description: Represents a slider comprising three or four channels.
> > +
> > + properties:
> > + azoteq,channel-select:
> > + $ref: /schemas/types.yaml#/definitions/uint32-array
> > + minItems: 3
> > + maxItems: 4
> > + items:
> > + minimum: 0
> > + maximum: 9
> > + description:
> > + Specifies the order of the channels that participate in the slider.
> > +
> > + azoteq,slider-size:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 65535
> > + description:
> > + Specifies the slider's one-dimensional resolution, equal to the
> > + maximum coordinate plus one.
> > +
> > + azoteq,lower-cal:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 255
> > + description: Specifies the slider's lower starting point.
> > +
> > + azoteq,upper-cal:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 255
> > + description: Specifies the slider's upper starting point.
> > +
> > + azoteq,top-speed:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 65535
> > + description:
> > + Specifies the speed of movement after which coordinate filtering is
> > + no longer applied.
> > +
> > + azoteq,bottom-speed:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + multipleOf: 4
> > + minimum: 0
> > + maximum: 1020
> > + description:
> > + Specifies the speed of movement after which coordinate filtering is
> > + linearly reduced.
> > +
> > + azoteq,bottom-beta:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + minimum: 0
> > + maximum: 7
> > + description:
> > + Specifies the coordinate filter damping factor to be applied
> > + while the speed of movement is below that which is specified
> > + by azoteq,bottom-speed.
> > +
> > + azoteq,static-beta:
> > + type: boolean
> > + description:
> > + Applies the coordinate filter damping factor specified by
> > + azoteq,bottom-beta regardless of the speed of movement.
> > +
> > + azoteq,use-prox:
> > + type: boolean
> > + description:
> > + Directs the slider to respond to the proximity states of the selected
> > + channels instead of their corresponding touch states. Note the slider
> > + cannot report granular coordinates during a state of proximity.
> > +
> > + linux,axis:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description:
> > + Specifies the absolute axis to which coordinates are mapped. Specify
> > + ABS_WHEEL to operate the slider as a wheel (IQS7222C only).
> > +
> > + patternProperties:
> > + "^event-(press|tap|(swipe|flick)-(pos|neg))$":
> > + type: object
> > + description:
> > + Represents a press or gesture (IQS7222A only) event reported by
> > + the slider.
> > +
> > + properties:
> > + linux,code:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: Numeric key code associated with the event.
> > +
> > + azoteq,gesture-max-ms:
> > + multipleOf: 4
> > + minimum: 0
> > + maximum: 1020
> > + description:
> > + Specifies the length of time (in ms) within which a tap, swipe
> > + or flick gesture must be completed in order to be acknowledged
> > + by the device. The number specified for any one swipe or flick
> > + gesture applies to all remaining swipe or flick gestures.
> > +
> > + azoteq,gesture-min-ms:
> > + multipleOf: 4
> > + minimum: 0
> > + maximum: 124
> > + description:
> > + Specifies the length of time (in ms) for which a tap gesture must
> > + be held in order to be acknowledged by the device.
> > +
> > + azoteq,gesture-dist:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + multipleOf: 16
> > + minimum: 0
> > + maximum: 4080
> > + description:
> > + Specifies the distance across which a swipe or flick gesture must
> > + travel in order to be acknowledged by the device. The number spec-
> > + ified for any one swipe or flick gesture applies to all remaining
> > + swipe or flick gestures.
> > +
> > + azoteq,gpio-select:
> > + $ref: /schemas/types.yaml#/definitions/uint32-array
> > + minItems: 1
> > + maxItems: 1
> > + items:
> > + minimum: 0
> > + maximum: 0
> > + description: |
> > + Specifies an individual GPIO mapped to a tap, swipe or flick
> > + gesture as follows:
> > + 0: GPIO0
> > + 1: GPIO3 (reserved)
> > + 2: GPIO4 (reserved)
> > +
> > + Note that although multiple events can be mapped to a single
> > + GPIO, they must all be of the same type (proximity, touch or
> > + slider gesture).
> > +
> > + required:
> > + - linux,code
> > +
> > + additionalProperties: false
> > +
> > + required:
> > + - azoteq,channel-select
> > +
> > + additionalProperties: false
> > +
> > + "^gpio-[0-2]$":
> > + type: object
> > + description: |
> > + Represents a GPIO mapped to one or more events as follows:
> > + gpio-0: GPIO0
> > + gpio-1: GPIO3 (IQS7222C only)
> > + gpio-2: GPIO4 (IQS7222C only)
> > +
> > + allOf:
> > + - $ref: ../pinctrl/pincfg-node.yaml#
> > +
> > + properties:
> > + drive-open-drain: true
> > +
> > + additionalProperties: false
> > +
> > +allOf:
> > + - if:
> > + properties:
> > + compatible:
> > + contains:
> > + const: azoteq,iqs7222b
> > +
> > + then:
> > + patternProperties:
> > + "^cycle-[0-9]$":
> > + properties:
> > + azoteq,iref-enable: false
> > +
> > + "^channel-[0-19]$":
> > + properties:
> > + azoteq,ref-select: false
> > +
> > + patternProperties:
> > + "^event-(prox|touch)$":
> > + properties:
> > + azoteq,gpio-select: false
> > +
> > + "^slider-[0-1]$": false
> > +
> > + "^gpio-[0-2]$": false
> > +
> > + - if:
> > + properties:
> > + compatible:
> > + contains:
> > + const: azoteq,iqs7222a
> > +
> > + then:
> > + patternProperties:
> > + "^channel-[0-19]$":
> > + patternProperties:
> > + "^event-(prox|touch)$":
> > + properties:
> > + azoteq,gpio-select:
> > + maxItems: 1
> > + items:
> > + maximum: 0
> > +
> > + "^slider-[0-1]$":
> > + properties:
> > + azoteq,slider-size:
> > + multipleOf: 16
> > + maximum: 4080
> > +
> > + azoteq,top-speed:
> > + multipleOf: 4
> > + maximum: 1020
> > +
> > + else:
> > + patternProperties:
> > + "^channel-[0-19]$":
> > + properties:
> > + azoteq,ulp-allow: false
> > +
> > + "^slider-[0-1]$":
> > + patternProperties:
> > + "^event-(press|tap|(swipe|flick)-(pos|neg))$":
> > + properties:
> > + azoteq,gesture-max-ms: false
> > +
> > + azoteq,gesture-min-ms: false
> > +
> > + azoteq,gesture-dist: false
> > +
> > + azoteq,gpio-select: false
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - irq-gpios
> > + - "#address-cells"
> > + - "#size-cells"
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > + - |
> > + #include <dt-bindings/gpio/gpio.h>
> > + #include <dt-bindings/input/input.h>
> > +
> > + i2c {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + iqs7222a@44 {
> > + compatible = "azoteq,iqs7222a";
> > + reg = <0x44>;
> > + irq-gpios = <&gpio 4 GPIO_ACTIVE_LOW>;
> > + azoteq,lta-beta-lp = <7>;
> > + azoteq,lta-beta-np = <8>;
> > + azoteq,counts-beta-lp = <2>;
> > + azoteq,counts-beta-np = <3>;
> > + azoteq,lta-fast-beta-lp = <3>;
> > + azoteq,lta-fast-beta-np = <4>;
> > +
> > + cycle-0 {
> > + azoteq,conv-period = <5>;
> > + azoteq,conv-frac = <127>;
> > + azoteq,tx-enable = <1>, <2>, <4>, <5>;
> > + azoteq,dead-time-enable;
> > + azoteq,sense-mode = <2>;
> > + };
> > +
> > + cycle-1 {
> > + azoteq,conv-period = <5>;
> > + azoteq,conv-frac = <127>;
> > + azoteq,tx-enable = <5>;
> > + azoteq,dead-time-enable;
> > + azoteq,sense-mode = <2>;
> > + };
> > +
> > + cycle-2 {
> > + azoteq,conv-period = <5>;
> > + azoteq,conv-frac = <127>;
> > + azoteq,tx-enable = <4>;
> > + azoteq,dead-time-enable;
> > + azoteq,sense-mode = <2>;
> > + };
> > +
> > + cycle-3 {
> > + azoteq,conv-period = <5>;
> > + azoteq,conv-frac = <127>;
> > + azoteq,tx-enable = <2>;
> > + azoteq,dead-time-enable;
> > + azoteq,sense-mode = <2>;
> > + };
> > +
> > + cycle-4 {
> > + azoteq,conv-period = <5>;
> > + azoteq,conv-frac = <127>;
> > + azoteq,tx-enable = <1>;
> > + azoteq,dead-time-enable;
> > + azoteq,sense-mode = <2>;
> > + };
> > +
> > + cycle-5 {
> > + azoteq,conv-period = <2>;
> > + azoteq,conv-frac = <0>;
> > + };
> > +
> > + cycle-6 {
> > + azoteq,conv-period = <2>;
> > + azoteq,conv-frac = <0>;
> > + };
> > +
> > + channel-0 {
> > + azoteq,ulp-allow;
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <3>;
> > + azoteq,ati-target = <800>;
> > + azoteq,ati-base = <208>;
> > + azoteq,ati-mode = <5>;
> > + };
> > +
> > + channel-1 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <3>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <208>;
> > + azoteq,ati-mode = <5>;
> > + };
> > +
> > + channel-2 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <3>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <208>;
> > + azoteq,ati-mode = <5>;
> > + };
> > +
> > + channel-3 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <3>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <208>;
> > + azoteq,ati-mode = <5>;
> > + };
> > +
> > + channel-4 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <3>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <208>;
> > + azoteq,ati-mode = <5>;
> > + };
> > +
> > + channel-5 {
> > + azoteq,ulp-allow;
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <6>;
> > + azoteq,ati-target = <800>;
> > + azoteq,ati-base = <144>;
> > + azoteq,ati-mode = <5>;
> > + };
> > +
> > + channel-6 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <6>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <160>;
> > + azoteq,ati-mode = <5>;
> > +
> > + event-touch {
> > + linux,code = <KEY_MUTE>;
> > + };
> > + };
> > +
> > + channel-7 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <6>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <160>;
> > + azoteq,ati-mode = <5>;
> > +
> > + event-touch {
> > + linux,code = <KEY_VOLUMEDOWN>;
> > + };
> > + };
> > +
> > + channel-8 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <6>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <160>;
> > + azoteq,ati-mode = <5>;
> > +
> > + event-touch {
> > + linux,code = <KEY_VOLUMEUP>;
> > + };
> > + };
> > +
> > + channel-9 {
> > + azoteq,global-halt;
> > + azoteq,invert-enable;
> > + azoteq,rx-enable = <6>;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <160>;
> > + azoteq,ati-mode = <5>;
> > +
> > + event-touch {
> > + linux,code = <KEY_POWER>;
> > + };
> > + };
> > +
> > + channel-10 {
> > + azoteq,ulp-allow;
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <112>;
> > +
> > + event-touch {
> > + linux,code = <SW_LID>;
> > + linux,input-type = <EV_SW>;
> > + };
> > + };
> > +
> > + channel-11 {
> > + azoteq,ati-target = <496>;
> > + azoteq,ati-base = <112>;
> > + };
> > +
> > + slider-0 {
> > + azoteq,channel-select = <1>, <2>, <3>, <4>;
> > + azoteq,slider-size = <4080>;
> > + azoteq,upper-cal = <50>;
> > + azoteq,lower-cal = <30>;
> > + azoteq,top-speed = <200>;
> > + azoteq,bottom-speed = <1>;
> > + azoteq,bottom-beta = <3>;
> > +
> > + event-tap {
> > + linux,code = <KEY_PLAYPAUSE>;
> > + azoteq,gesture-max-ms = <600>;
> > + azoteq,gesture-min-ms = <24>;
> > + };
> > +
> > + event-flick-pos {
> > + linux,code = <KEY_NEXTSONG>;
> > + azoteq,gesture-max-ms = <600>;
> > + azoteq,gesture-dist = <816>;
> > + };
> > +
> > + event-flick-neg {
> > + linux,code = <KEY_PREVIOUSSONG>;
> > + };
> > + };
> > + };
> > + };
> > +
> > +...
> > --
> > 2.25.1
> >
> >
Kind regards,
Jeff LaBundy
^ permalink raw reply
* RE: [EXT] Re: [PATCH v8 04/15] media:Add v4l2 event codec_error and skip
From: Ming Qian @ 2022-01-25 1:54 UTC (permalink / raw)
To: Nicolas Dufresne, mchehab@kernel.org, shawnguo@kernel.org,
robh+dt@kernel.org, s.hauer@pengutronix.de
Cc: hverkuil-cisco@xs4all.nl, kernel@pengutronix.de,
festevam@gmail.com, dl-linux-imx, linux-media@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org
In-Reply-To: <a90c49239b86d8212045b1f2ef591c4bbf3cb8fd.camel@ndufresne.ca>
> -----Original Message-----
> From: Nicolas Dufresne [mailto:nicolas@ndufresne.ca]
> Sent: Saturday, January 22, 2022 6:25 AM
> To: Ming Qian <ming.qian@nxp.com>; mchehab@kernel.org;
> shawnguo@kernel.org; robh+dt@kernel.org; s.hauer@pengutronix.de
> Cc: hverkuil-cisco@xs4all.nl; kernel@pengutronix.de; festevam@gmail.com;
> dl-linux-imx <linux-imx@nxp.com>; linux-media@vger.kernel.org;
> linux-kernel@vger.kernel.org; devicetree@vger.kernel.org;
> linux-arm-kernel@lists.infradead.org
> Subject: Re: [EXT] Re: [PATCH v8 04/15] media:Add v4l2 event codec_error and
> skip
>
> Caution: EXT Email
>
> Le jeudi 13 janvier 2022 à 07:18 +0000, Ming Qian a écrit :
> > Hi Nicolas,
> >
> > I have question about skip event or similar concepts.
> > If the client control the input frame count, and it won't queue any more
> frames unless some frame is decoded.
> > But after seek, There is no requirement to begin queuing coded data starting
> exactly from a resume point (e.g. SPS or a keyframe). Any queued OUTPUT
> buffers will be processed and returned to the client until a suitable resume
> point is found. While looking for a resume point, the decoder should not
> produce any decoded frames into CAPTURE buffers.
> >
> > So client may have queued some frames but without any resume point, in
> this case the decoder won't produce any decoded frames into CAPTURE buffers,
> and the client won't queue frames into output buffers. This creates some kind
> of deadlock.
> >
> > In our previous solution, we send skip event to client to tell it that some
> frame is skipped instead of decoded, then the client can continue to queue
> frames.
> > But the skip event is flawed, so we need some solution to resolve it.
> > 1. decoder can produce an empty buffer with V4L2_BUF_FLAG_SKIPPED (or
> V4L2_BUF_FLAG_ERROR) as you advised, but this seems to conflict with the
> above description in specification.
> > 2. Define a notification mechanism to notify the client
> >
> > Can you give some advice? This constraint of frame depth is common on
> > android
>
> Without going against the spec, you can as of today pop a capture buffer and
> mark it done with error. As it has nothing valid in it, I would also set the
> payload size to 0.
>
> So I'd say, for every unique input timestamp, that didn't yield a frame (skipped),
> pop a capture buffer, copy the timestamp, set the payload size to 0 and set it
> as done with error.
>
> I'm not sure though if we that we can specify this, as I'm not sure this is
> possible with all the existing HW. I must admit, I don't myself had to deal with
> that issue as I'm not using a dummy framework. In GStreamer, we take care of
> locating the next sync point. So unless there was an error in the framework,
> this case does not exist for us.
>
Hi Nicolas,
If the decoder can detect the output buffer that may trigger a error, is it better setting error flag on the output buffer, but without producing an empty capture buffer with error flag set?, or we should return both output and capture buffer with error flag set?
As I can see the following description in spec:
if the decoder is able to precisely report the OUTPUT buffer that triggered the error, such buffer will be returned with the V4L2_BUF_FLAG_ERROR flag set.
> >
> > Ming
> >
> > > > > > + * - ``V4L2_EVENT_SKIP``
> > > > > > + - 8
> > > > > > + - This event is triggered when one frame is decoded,
> > > > > > + but it won't
> > > > > > be
> > > > > outputed
> > > > > > + to the display. So the application can't get this frame,
> > > > > > + and the
> > > > > > input
> > > > > frame count
> > > > > > + is dismatch with the output frame count. And this evevt
> > > > > > + is telling
> > > > > > the
> > > > > client to
> > > > > > + handle this case.
> > > > >
> > > > > Similar to my previous comment, this event is flawed, since
> > > > > userspace cannot know were the skip is located in the queued
> > > > > buffers. Currently, all decoders are mandated to support
> > > > > V4L2_BUF_FLAG_TIMESTAMP_COPY. The timestamp must NOT be
> > > interpreted
> > > > > by the driver and must be reproduce as-is in the associated
> > > > > CAPTURE buffer. It is possible to "garbage" collect skipped
> > > > > frames with this method, though tedious.
> > > > >
> > > > > An alternative, and I think it would be much nicer then this,
> > > > > would be to use the v4l2_buffer.sequence counter, and just make
> > > > > it skip 1 on skips. Though, the down side is that userspace must
> > > > > also know how to reorder frames (a driver job for stateless
> > > > > codecs) in order to identify which frame was skipped. So this is
> > > > > perhaps not that useful, other then knowing something was skipped in
> the past.
> > > > >
> > > > > A third option would be to introduce V4L2_BUF_FLAG_SKIPPED. This
> > > > > way the driver could return an empty payload (bytesused = 0)
> > > > > buffer with this flag set, and the proper timestamp properly
> > > > > copied. This would let the driver communicate skipped frames in
> > > > > real-time. Note that this could break with existing userspace,
> > > > > so it would need to be opted-in somehow (a control or some flags).
> > > >
> > > > Hi Nicolas,
> > > > The problem we meet is that userspace doesn't care which frame
> > > > is skipped, it just need to know that there are a frame is
> > > > skipped, the driver should promise the input frame count is equals
> > > > to the output frame
> > > count.
> > > > Your first method is possible in theory, but we find the
> > > > timestamp may be unreliable, we meet many timestamp issues that
> > > > userspace may enqueue invalid timestamp or repeated timestamp and
> > > > so on, so we can't
> > > accept this solution.
> > >
> > > The driver should not interpret the provided timestamp, so it should
> > > not be able to say if the timestamp is valid or not, this is not the driver's
> task.
> > >
> > > The driver task is to match the timestamp to the CAPTURE buffer (if
> > > that buffer was produced), and reproduce it exactly.
> > >
> > > > I think your second option is better. And there are only 1
> > > > question, we find some application prefer to use the
> > > > V4L2_EVENT_EOS to check the eos, not checking the empty buffer, if
> > > > we use this method to check skipped frame, the
> > >
> > > Checking the empty buffer is a legacy method, only available in
> > > Samsung MFC driver. The spec says that the last buffer should be
> > > flagged with _LAST, and any further attempt to poll should unblock and
> DQBUF return EPIPE.
> > >
> > > > application should check empty buffer instead of V4L2_EVENT_EOS,
> > > > otherwise if the last frame is skipped, the application will miss it.
> > > > Of course this is not a problem, it just increases the complexity
> > > > of the userspace implementation
> > >
> > > The EPIPE mechanism covers this issue, which we initially had with
> > > the LAST flag.
> > >
> > > > I don't think your third method is feasible, the reasons are as below
> > > > 1. usually the empty payload means eos, and as you
> > > > say, it may introduce confusion.
> > > > 2. The driver may not have the opportunity to return an
> > > > empty payload during decoding, in our driver, driver will pass the
> > > > capture buffer to firmware, and when some frame is skipped, the
> > > > firmware won't return the buffer, driver may not find an available
> > > > capture buffer to return to userspace.
> > > >
> > > > The requirement is that userspace need to match the input frame
> > > > count and output frame count. It doesn't care which frame is
> > > > skipped, so the V4L2_EVENT_SKIP is the easiest way for driver and
> userspace.
> > > > If you think this event is really inappropriate, I prefer to
> > > > adopt your second option
> > >
> > > Please, drop SKIP from you driver and this patchset and fix your
> > > draining process handling to follow the spec. The Samsung OMX
> > > component is irrelevant to mainline submission, the OMX code should
> > > be updated to follow the spec.
> > >
> > > >
^ permalink raw reply
* Re: [PATCH] clk: at91: sama7g5: Allow MCK1 to be exported and referenced in DT
From: Stephen Boyd @ 2022-01-25 1:05 UTC (permalink / raw)
To: Tudor Ambarus, claudiu.beznea, mturquette
Cc: nicolas.ferre, alexandre.belloni, ludovic.desroches, robh+dt,
rdunlap, unixbhaskar, linux-clk, linux-arm-kernel, linux-kernel,
devicetree, Tudor Ambarus
In-Reply-To: <20220111125310.902856-1-tudor.ambarus@microchip.com>
Quoting Tudor Ambarus (2022-01-11 04:53:10)
> MCK1 feeds the External Bus Interface (EBI). EBI's clock rate is used
> to translate EBI's timmings to SMC timings, thus we need to handle MCK1
> in the EBI driver. Allow MCK1 to be referenced as a PMC_TYPE_CORE clock
> from phandle in DT.
>
> Signed-off-by: Tudor Ambarus <tudor.ambarus@microchip.com>
> ---
Applied to clk-next
^ permalink raw reply
* Re: [PATCH] clk: at91: allow setting PMC_AUDIOPINCK clock parents via DT
From: Stephen Boyd @ 2022-01-25 1:05 UTC (permalink / raw)
To: Alexandre Belloni, Ludovic Desroches, Michael Turquette,
Nicolas Ferre, Rob Herring, Zixun LI
Cc: Zixun LI, Claudiu Beznea, linux-clk, linux-arm-kernel,
linux-kernel, devicetree
In-Reply-To: <20220111142051.37957-1-admin@hifiphile.com>
Quoting Zixun LI (2022-01-11 06:20:50)
> Make AUDIOPINCK accessible via phandle to select it
> as peripheral clock parent using assigned-clock-parents in DT
> where available.
>
> Signed-off-by: Zixun LI <admin@hifiphile.com>
> Reviewed-by: Claudiu Beznea <Claudiu.Beznea@microchip.com>
> ---
Applied to clk-next
^ permalink raw reply
* Re: [PATCH v8 06/10] clk: Add Sunplus SP7021 clock driver
From: Stephen Boyd @ 2022-01-25 1:39 UTC (permalink / raw)
To: Qin Jian, robh+dt
Cc: mturquette, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
In-Reply-To: <34eeb839f4657491a704d00b532b2d8858eb35b6.1642751015.git.qinjian@cqplus1.com>
Quoting Qin Jian (2022-01-20 23:53:14)
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index c5b3dc973..91656de22 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -334,6 +334,15 @@ config COMMON_CLK_VC5
> This driver supports the IDT VersaClock 5 and VersaClock 6
> programmable clock generators.
>
> +config COMMON_CLK_SP7021
> + bool "Clock driver for Sunplus SP7021 SoC"
> + default SOC_SP7021
Also would be good to have
depends on SOC_SP7021 || COMPILE_TEST
> + help
> + This driver supports the Sunplus SP7021 SoC clocks.
> + It implements SP7021 PLLs/gate.
> + Not all features of the PLL are currently supported
> + by the driver.
> +
> config COMMON_CLK_STM32MP157
> def_bool COMMON_CLK && MACH_STM32MP157
> help
> diff --git a/drivers/clk/clk-sp7021.c b/drivers/clk/clk-sp7021.c
> new file mode 100644
> index 000000000..8a3e28a2a
> --- /dev/null
> +++ b/drivers/clk/clk-sp7021.c
> @@ -0,0 +1,753 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/*
> + * Copyright (C) Sunplus Technology Co., Ltd.
> + * All rights reserved.
> + */
> +//#define DEBUG
> +#include <linux/clk-provider.h>
> +#include <linux/clkdev.h>
IS the clkdev get path used? If not, please drop it.
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/bitfield.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <dt-bindings/clock/sp-sp7021.h>
> +
> +#define REG(i) (pll_regs + (i) * 4)
> +
> +#define PLLA_CTL REG(7)
> +#define PLLE_CTL REG(12)
> +#define PLLF_CTL REG(13)
> +#define PLLTV_CTL REG(14)
> +#define PLLSYS_CTL REG(26)
> +
> +/* speical div_width values for PLLTV/PLLA */
> +#define DIV_TV 33
> +#define DIV_A 34
> +
> +/* PLLTV parameters */
> +enum {
> + SEL_FRA,
> + SDM_MOD,
> + PH_SEL,
> + NFRA,
> + DIVR,
> + DIVN,
> + DIVM,
> + P_MAX
> +};
> +
> +#define MASK_SEL_FRA GENMASK(1, 1)
> +#define MASK_SDM_MOD GENMASK(2, 2)
> +#define MASK_PH_SEL GENMASK(4, 4)
> +#define MASK_NFRA GENMASK(12, 6)
> +#define MASK_DIVR GENMASK(8, 7)
> +#define MASK_DIVN GENMASK(7, 0)
> +#define MASK_DIVM GENMASK(14, 8)
> +
> +/* HIWORD_MASK FIELD_PREP */
> +#define HWM_FIELD_PREP(mask, value) \
> +({ \
> + u32 m = mask; \
> + (m << 16) | FIELD_PREP(m, value); \
> +})
> +
> +struct sp_pll {
> + struct clk_hw hw;
> + void __iomem *reg;
> + spinlock_t *lock; /* lock for reg */
> + int div_shift;
> + int div_width;
> + int pd_bit; /* power down bit idx */
> + int bp_bit; /* bypass bit idx */
> + unsigned long brate; /* base rate, TODO: replace brate with muldiv */
> + u32 p[P_MAX]; /* for hold PLLTV/PLLA parameters */
> +};
> +
> +#define to_sp_pll(_hw) container_of(_hw, struct sp_pll, hw)
> +
> +#define clk_regs (sp_clk_base + 0x000) /* G0 ~ CLKEN */
> +#define pll_regs (sp_clk_base + 0x200) /* G4 ~ PLL */
> +static void __iomem *sp_clk_base;
> +static struct clk_hw_onecell_data *sp_clk_data;
> +
> +#define F_CRITICAL BIT(31) /* clock is critical */
> +#define F_EXTCLK BIT(16) /* parent clock is EXTCLK */
> +const struct clk_parent_data parents[] = {
static? 'parents' is too generic, and this is in the global namespace.
Please make it driver specific.
> + { .name = "pllsys" },
> + { .name = "extclk" },
> + { .name = "plle" },
The .name member of clk_parent_data shouldn't be used for new drivers or
bindings. Instead, use .fw_name or the .index, preferably the index so
the struct is smaller.
> +};
> +
> +#define PD_PLLSYS (&parents[0])
> +#define PD_EXTCLK (&parents[1])
> +#define PD_PLLE (&parents[2])
> +
> +static const u32 gates[] = {
'gates' is too generic, please name this something driver specific.
> + CLK_SYSTEM | F_CRITICAL,
> + CLK_RTC,
> + CLK_IOCTL | F_CRITICAL,
> + CLK_IOP | F_CRITICAL,
> + CLK_OTPRX,
> + CLK_NOC,
> + CLK_BR,
> + CLK_RBUS_L00 | F_CRITICAL,
> + CLK_SPIFL,
> + CLK_SDCTRL0 | F_CRITICAL,
> + CLK_PERI0 | F_EXTCLK,
> + CLK_A926 | F_CRITICAL,
> + CLK_UMCTL2 | F_CRITICAL,
> + CLK_PERI1 | F_EXTCLK,
> +
> + CLK_DDR_PHY0 | F_CRITICAL,
> + CLK_ACHIP | F_CRITICAL,
Do these gates have children? What is the benefit of modeling critical
clks that have no children? It would be simpler to make sure they're
enabled at driver probe time and then not do anything else and skip
registering them with the clk framework when they'll do nothing.
> + CLK_STC0,
> + CLK_STC_AV0,
> + CLK_STC_AV1,
> + CLK_STC_AV2,
> + CLK_UA0 | F_EXTCLK,
> + CLK_UA1 | F_EXTCLK,
> + CLK_UA2 | F_EXTCLK,
> + CLK_UA3 | F_EXTCLK,
> + CLK_UA4 | F_EXTCLK,
> + CLK_HWUA | F_EXTCLK,
> + CLK_DDC0,
> + CLK_UADMA | F_EXTCLK,
> +
> + CLK_CBDMA0,
> + CLK_CBDMA1,
> + CLK_SPI_COMBO_0,
> + CLK_SPI_COMBO_1,
> + CLK_SPI_COMBO_2,
> + CLK_SPI_COMBO_3,
> + CLK_AUD,
> + CLK_USBC0,
> + CLK_USBC1,
> + CLK_UPHY0,
> + CLK_UPHY1,
> +
> + CLK_I2CM0,
> + CLK_I2CM1,
> + CLK_I2CM2,
> + CLK_I2CM3,
> + CLK_PMC,
> + CLK_CARD_CTL0,
> + CLK_CARD_CTL1,
> +
> + CLK_CARD_CTL4,
> + CLK_BCH,
> + CLK_DDFCH,
> + CLK_CSIIW0,
> + CLK_CSIIW1,
> + CLK_MIPICSI0,
> + CLK_MIPICSI1,
> +
> + CLK_HDMI_TX,
> + CLK_VPOST,
> +
> + CLK_TGEN,
> + CLK_DMIX,
> + CLK_TCON,
> + CLK_INTERRUPT | F_CRITICAL,
> +
> + CLK_RGST | F_CRITICAL,
> + CLK_GPIO,
> + CLK_RBUS_TOP | F_CRITICAL,
> +
> + CLK_MAILBOX,
> + CLK_SPIND,
> + CLK_I2C2CBUS,
> + CLK_SEC,
> + CLK_GPOST0,
> + CLK_DVE,
> +
> + CLK_OSD0,
> + CLK_DISP_PWM,
> + CLK_UADBG,
> + CLK_DUMMY_MASTER,
> + CLK_FIO_CTL,
> + CLK_FPGA,
> + CLK_L2SW,
> + CLK_ICM,
> + CLK_AXI_GLOBAL,
> +};
> +
> +static DEFINE_SPINLOCK(plla_lock);
> +static DEFINE_SPINLOCK(plle_lock);
> +static DEFINE_SPINLOCK(pllf_lock);
> +static DEFINE_SPINLOCK(pllsys_lock);
> +static DEFINE_SPINLOCK(plltv_lock);
> +
> +#define _M 1000000UL
> +#define F_27M (27 * _M)
> +
> +/*********************************** PLL_TV **********************************/
> +
> +/* TODO: set proper FVCO range */
> +#define FVCO_MIN (100 * _M)
> +#define FVCO_MAX (200 * _M)
> +
> +#define F_MIN (FVCO_MIN / 8)
> +#define F_MAX (FVCO_MAX)
> +
> +static long plltv_integer_div(struct sp_pll *clk, unsigned long freq)
> +{
> + /* valid m values: 27M must be divisible by m, 0 means end */
> + static const u32 m_table[] = {
> + 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 0
> + };
> + u32 m, n, r;
> + unsigned long fvco, nf;
> +
> + /* check freq */
> + if (freq < F_MIN) {
> + pr_warn("%s: %s freq:%lu < F_MIN:%lu, round up\n",
> + __func__, clk_hw_get_name(&clk->hw), freq, F_MIN);
> + freq = F_MIN;
> + } else if (freq > F_MAX) {
> + pr_warn("%s: %s freq:%lu > F_MAX:%lu, round down\n",
> + __func__, clk_hw_get_name(&clk->hw), freq, F_MAX);
> + freq = F_MAX;
> + }
Just use clamp() and ignore the warnings?
> +
> + /* DIVR 0~3 */
> + for (r = 0; r <= 3; r++) {
> + fvco = freq << r;
> + if (fvco <= FVCO_MAX)
> + break;
> + }
> +
> + /* DIVM */
> + for (m = 0; m_table[m]; m++) {
> + nf = fvco * m_table[m];
> + n = nf / F_27M;
> + if ((n * F_27M) == nf)
> + break;
> + }
> + m = m_table[m];
> +
> + if (!m) {
> + pr_err("%s: %s freq:%lu not found a valid setting\n",
> + __func__, clk_hw_get_name(&clk->hw), freq);
> + return -EINVAL;
> + }
> +
> + /* save parameters */
> + clk->p[SEL_FRA] = 0;
> + clk->p[DIVR] = r;
> + clk->p[DIVN] = n;
> + clk->p[DIVM] = m;
> +
> + return freq;
> +}
> +
> +/* parameters for PLLTV fractional divider */
> +static const u32 pt[][5] = {
> + /* conventional fractional */
> + {
> + 1, // factor
> + 5, // 5 * p0 (nint)
> + 1, // 1 * p0
> + F_27M, // F_27M / p0
> + 1, // p0 / p2
> + },
> + /* phase rotation */
> + {
> + 10, // factor
> + 54, // 5.4 * p0 (nint)
> + 2, // 0.2 * p0
> + F_27M / 10, // F_27M / p0
> + 5, // p0 / p2
> + },
> +};
> +
> +static const u32 mods[] = { 91, 55 }; /* SDM_MOD mod values */
> +
> +static long plltv_fractional_div(struct sp_pll *clk, unsigned long freq)
> +{
> + u32 m, r;
> + u32 nint, nfra;
> + u32 df_quotient_min = 210000000;
> + u32 df_remainder_min = 0;
> + unsigned long fvco, nf, f, fout = 0;
> + int sdm, ph;
> +
> + /* check freq */
> + if (freq < F_MIN) {
> + pr_warn("%s: %s freq:%lu < F_MIN:%lu, round up\n",
> + __func__, clk_hw_get_name(&clk->hw), freq, F_MIN);
> + freq = F_MIN;
> + } else if (freq > F_MAX) {
> + pr_warn("%s: %s freq:%lu > F_MAX:%lu, round down\n",
> + __func__, clk_hw_get_name(&clk->hw), freq, F_MAX);
> + freq = F_MAX;
> + }
Just use clamp() and ignore the warnings?
> +
> + /* DIVR 0~3 */
> + for (r = 0; r <= 3; r++) {
> + fvco = freq << r;
> + if (fvco <= FVCO_MAX)
> + break;
> + }
> + f = F_27M >> r;
> +
> + /* PH_SEL 1/0 */
> + for (ph = 1; ph >= 0; ph--) {
> + const u32 *pp = pt[ph];
> + u32 ms = 1;
> +
> + /* SDM_MOD 0/1 */
> + for (sdm = 0; sdm <= 1; sdm++) {
> + u32 mod = mods[sdm];
> +
> + /* DIVM 1~32 */
> + for (m = ms; m <= 32; m++) {
> + u32 df; /* diff freq */
> + u32 df_quotient = 0, df_remainder = 0;
Please leave these unassigned.
> +
> + nf = fvco * m;
> + nint = nf / pp[3];
> +
> + if (nint < pp[1])
> + continue;
> + if (nint > pp[1])
> + break;
> +
> + nfra = (((nf % pp[3]) * mod * pp[4]) + (F_27M / 2)) / F_27M;
> + if (nfra)
> + df = (f * (nint + pp[2]) / pp[0]) -
> + (f * (mod - nfra) / mod / pp[4]);
> + else
> + df = (f * (nint) / pp[0]);
> +
> + df_quotient = df / m;
> + df_remainder = ((df % m) * 1000) / m;
> +
> + if (freq > df_quotient) {
> + df_quotient = freq - df_quotient - 1;
> + df_remainder = 1000 - df_remainder;
> + } else {
> + df_quotient = df_quotient - freq;
> + }
> +
> + if (df_quotient_min > df_quotient ||
> + (df_quotient_min == df_quotient &&
> + df_remainder_min > df_remainder)) {
> + /* found a closer freq, save parameters */
> + clk->p[SEL_FRA] = 1;
> + clk->p[SDM_MOD] = sdm;
> + clk->p[PH_SEL] = ph;
> + clk->p[NFRA] = nfra;
> + clk->p[DIVR] = r;
> + clk->p[DIVM] = m;
> +
> + fout = df / m;
> + df_quotient_min = df_quotient;
> + df_remainder_min = df_remainder;
> + }
> + }
> + }
> + }
> +
> + if (!fout) {
> + pr_err("%s: %s freq:%lu not found a valid setting\n",
> + __func__, clk_hw_get_name(&clk->hw), freq);
> + return -EINVAL;
> + }
> +
> + return fout;
> +}
> +
> +static long plltv_div(struct sp_pll *clk, unsigned long freq)
> +{
> + if (freq % 100)
> + return plltv_fractional_div(clk, freq);
> +
> + return plltv_integer_div(clk, freq);
> +}
> +
> +static void plltv_set_rate(struct sp_pll *clk)
> +{
> + u32 reg;
> +
> + reg = HWM_FIELD_PREP(MASK_SEL_FRA, clk->p[SEL_FRA]);
> + reg |= HWM_FIELD_PREP(MASK_SDM_MOD, clk->p[SDM_MOD]);
> + reg |= HWM_FIELD_PREP(MASK_PH_SEL, clk->p[PH_SEL]);
> + reg |= HWM_FIELD_PREP(MASK_NFRA, clk->p[NFRA]);
> + writel(reg, clk->reg);
> +
> + reg = HWM_FIELD_PREP(MASK_DIVR, clk->p[DIVR]);
> + writel(reg, clk->reg + 4);
> +
> + reg = HWM_FIELD_PREP(MASK_DIVN, clk->p[DIVN] - 1);
> + reg |= HWM_FIELD_PREP(MASK_DIVM, clk->p[DIVM] - 1);
> + writel(reg, clk->reg + 8);
> +}
> +
> +/*********************************** PLL_A ***********************************/
> +
> +/* from Q628_PLLs_REG_setting.xlsx */
> +static const struct {
> + u32 rate;
> + u32 regs[5];
> +} pa[] = {
> + {
> + .rate = 135475200,
> + .regs = {
> + 0x4801,
> + 0x02df,
> + 0x248f,
> + 0x0211,
> + 0x33e9
> + }
> + },
> + {
> + .rate = 147456000,
> + .regs = {
> + 0x4801,
> + 0x1adf,
> + 0x2490,
> + 0x0349,
> + 0x33e9
> + }
> + },
> + {
> + .rate = 196608000,
> + .regs = {
> + 0x4801,
> + 0x42ef,
> + 0x2495,
> + 0x01c6,
> + 0x33e9
> + }
> + },
> +};
> +
> +static void plla_set_rate(struct sp_pll *clk)
> +{
> + const u32 *pp = pa[clk->p[0]].regs;
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(pa->regs); i++)
> + writel(0xffff0000 | pp[i], clk->reg + (i * 4));
> +}
> +
> +static long plla_round_rate(struct sp_pll *clk, unsigned long rate)
> +{
> + int i = ARRAY_SIZE(pa);
> +
> + while (--i) {
> + if (rate >= pa[i].rate)
> + break;
> + }
> + clk->p[0] = i;
> +
> + return pa[i].rate;
> +}
> +
> +/********************************** SP_PLL ***********************************/
> +
> +static long sp_pll_calc_div(struct sp_pll *clk, unsigned long rate)
> +{
> + u32 fbdiv;
> + u32 max = 1 << clk->div_width;
> +
> + fbdiv = DIV_ROUND_CLOSEST(rate, clk->brate);
> + if (fbdiv > max)
> + fbdiv = max;
> +
> + return fbdiv;
> +}
> +
> +static long sp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *prate)
> +{
> + struct sp_pll *clk = to_sp_pll(hw);
> + long ret;
> +
> + if (rate == *prate)
> + ret = *prate; /* bypass */
> + else if (clk->div_width == DIV_A) {
> + ret = plla_round_rate(clk, rate);
> + } else if (clk->div_width == DIV_TV) {
> + ret = plltv_div(clk, rate);
> + if (ret < 0)
> + ret = *prate;
> + } else {
> + ret = sp_pll_calc_div(clk, rate) * clk->brate;
> + }
> +
> + return ret;
> +}
> +
> +static unsigned long sp_pll_recalc_rate(struct clk_hw *hw,
> + unsigned long prate)
> +{
> + struct sp_pll *clk = to_sp_pll(hw);
> + u32 reg = readl(clk->reg);
> + unsigned long ret;
> +
> + if (reg & BIT(clk->bp_bit))
> + ret = prate; /* bypass */
> + else if (clk->div_width == DIV_A)
> + ret = pa[clk->p[0]].rate;
> + else if (clk->div_width == DIV_TV) {
> + u32 m, r, reg2;
> +
> + r = FIELD_GET(MASK_DIVR, readl(clk->reg + 4));
> + reg2 = readl(clk->reg + 8);
> + m = FIELD_GET(MASK_DIVM, reg2) + 1;
> +
> + if (reg & BIT(1)) { /* SEL_FRA */
> + /* fractional divider */
> + u32 sdm = FIELD_GET(MASK_SDM_MOD, reg);
> + u32 ph = FIELD_GET(MASK_PH_SEL, reg);
> + u32 nfra = FIELD_GET(MASK_NFRA, reg);
> + const u32 *pp = pt[ph];
> +
> + ret = prate >> r;
> + ret = (ret * (pp[1] + pp[2]) / pp[0]) -
> + (ret * (mods[sdm] - nfra) / mods[sdm] / pp[4]);
> + ret /= m;
> + } else {
> + /* integer divider */
> + u32 n = FIELD_GET(MASK_DIVN, reg2) + 1;
> +
> + ret = (prate / m * n) >> r;
> + }
> + } else {
> + u32 fbdiv = ((reg >> clk->div_shift) & ((1 << clk->div_width) - 1)) + 1;
> +
> + ret = clk->brate * fbdiv;
> + }
> +
> + return ret;
> +}
> +
> +static int sp_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long prate)
> +{
> + struct sp_pll *clk = to_sp_pll(hw);
> + unsigned long flags;
> + u32 reg;
> +
> + spin_lock_irqsave(clk->lock, flags);
> +
> + reg = BIT(clk->bp_bit + 16); /* HIWORD_MASK */
> +
> + if (rate == prate)
> + reg |= BIT(clk->bp_bit); /* bypass */
> + else if (clk->div_width == DIV_A)
> + plla_set_rate(clk);
> + else if (clk->div_width == DIV_TV)
> + plltv_set_rate(clk);
Please add braces to all these if arms, because the else if below has
one.
> + else if (clk->div_width) {
> + u32 fbdiv = sp_pll_calc_div(clk, rate);
> + u32 mask = GENMASK(clk->div_shift + clk->div_width - 1, clk->div_shift);
> +
> + reg |= (mask << 16) | (((fbdiv - 1) << clk->div_shift) & mask);
> + }
> +
> + writel(reg, clk->reg);
> +
> + spin_unlock_irqrestore(clk->lock, flags);
> +
> + return 0;
> +}
> +
> +static int sp_pll_enable(struct clk_hw *hw)
> +{
> + struct sp_pll *clk = to_sp_pll(hw);
> + unsigned long flags;
> +
> + spin_lock_irqsave(clk->lock, flags);
> + writel(BIT(clk->pd_bit + 16) | BIT(clk->pd_bit), clk->reg); /* power up */
> + spin_unlock_irqrestore(clk->lock, flags);
> +
> + return 0;
> +}
> +
> +static void sp_pll_disable(struct clk_hw *hw)
> +{
> + struct sp_pll *clk = to_sp_pll(hw);
> + unsigned long flags;
> +
> + spin_lock_irqsave(clk->lock, flags);
> + writel(BIT(clk->pd_bit + 16), clk->reg); /* power down */
> + spin_unlock_irqrestore(clk->lock, flags);
> +}
> +
> +static int sp_pll_is_enabled(struct clk_hw *hw)
> +{
> + struct sp_pll *clk = to_sp_pll(hw);
> +
> + return readl(clk->reg) & BIT(clk->pd_bit);
> +}
> +
> +static const struct clk_ops sp_pll_ops = {
> + .enable = sp_pll_enable,
> + .disable = sp_pll_disable,
> + .is_enabled = sp_pll_is_enabled,
> + .round_rate = sp_pll_round_rate,
> + .recalc_rate = sp_pll_recalc_rate,
> + .set_rate = sp_pll_set_rate
> +};
> +
> +static const struct clk_ops sp_pll_sub_ops = {
> + .enable = sp_pll_enable,
> + .disable = sp_pll_disable,
> + .is_enabled = sp_pll_is_enabled,
> + .recalc_rate = sp_pll_recalc_rate,
> +};
> +
> +struct clk_hw *sp_pll_register(struct device *dev, const char *name,
> + const struct clk_parent_data *parent_data,
> + void __iomem *reg, int pd_bit, int bp_bit,
> + unsigned long brate, int shift, int width,
> + spinlock_t *lock)
> +{
> + struct sp_pll *pll;
> + struct clk_hw *hw;
> + struct clk_init_data initd = {
> + .name = name,
> + .parent_data = parent_data,
> + .ops = (bp_bit >= 0) ? &sp_pll_ops : &sp_pll_sub_ops,
> + .num_parents = 1,
> + /* system clock, should not be disabled */
> + .flags = (reg == PLLSYS_CTL) ? CLK_IS_CRITICAL : 0,
> + };
> + int ret;
> +
> + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
> + if (!pll)
> + return ERR_PTR(-ENOMEM);
> +
> + pll->hw.init = &initd;
> + pll->reg = reg;
> + pll->pd_bit = pd_bit;
> + pll->bp_bit = bp_bit;
> + pll->brate = brate;
> + pll->div_shift = shift;
> + pll->div_width = width;
> + pll->lock = lock;
> +
> + hw = &pll->hw;
> + ret = devm_clk_hw_register(dev, hw);
> + if (ret) {
> + kfree(pll);
> + hw = ERR_PTR(ret);
> + } else {
> + pr_debug("%-20s%lu\n", name, clk_hw_get_rate(hw));
> + devm_clk_hw_register_clkdev(dev, hw, NULL, name);
> + }
> +
> + return hw;
> +}
> +
> +static int sp7021_clk_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + int i, ret;
> + struct clk_hw **hws;
> + struct resource *res;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + sp_clk_base = devm_ioremap(dev, res->start, resource_size(res));
Use devm_platform_ioremap_resource()?
> + if (WARN_ON(!sp_clk_base))
Drop the WARN_ON() as it already prints a stacktrace if things fail.
> + return -ENXIO;
> +
> + sp_clk_data = devm_kzalloc(dev, struct_size(sp_clk_data, hws, CLK_MAX),
> + GFP_KERNEL);
> + if (!sp_clk_data)
> + return -ENOMEM;
> +
> + hws = sp_clk_data->hws;
> +
> + /* PLL_A */
> + hws[PLL_A] = sp_pll_register(dev, "plla", PD_EXTCLK, PLLA_CTL,
> + 11, 12, 27000000, 0, DIV_A, &plla_lock);
> + if (IS_ERR(hws[PLL_A]))
> + return PTR_ERR(hws[PLL_A]);
> +
> + /* PLL_E */
> + hws[PLL_E] = sp_pll_register(dev, "plle", PD_EXTCLK, PLLE_CTL,
> + 6, 2, 50000000, 0, 0, &plle_lock);
> + if (IS_ERR(hws[PLL_E]))
> + return PTR_ERR(hws[PLL_E]);
> + hws[PLL_E_2P5] = sp_pll_register(dev, "plle_2p5", PD_PLLE, PLLE_CTL,
> + 13, -1, 2500000, 0, 0, &plle_lock);
> + if (IS_ERR(hws[PLL_E_2P5]))
> + return PTR_ERR(hws[PLL_E_2P5]);
> + hws[PLL_E_25] = sp_pll_register(dev, "plle_25", PD_PLLE, PLLE_CTL,
> + 12, -1, 25000000, 0, 0, &plle_lock);
> + if (IS_ERR(hws[PLL_E_25]))
> + return PTR_ERR(hws[PLL_E_25]);
> + hws[PLL_E_112P5] = sp_pll_register(dev, "plle_112p5", PD_PLLE, PLLE_CTL,
> + 11, -1, 112500000, 0, 0, &plle_lock);
> + if (IS_ERR(hws[PLL_E_112P5]))
> + return PTR_ERR(hws[PLL_E_112P5]);
> +
> + /* PLL_F */
> + hws[PLL_F] = sp_pll_register(dev, "pllf", PD_EXTCLK, PLLF_CTL,
> + 0, 10, 13500000, 1, 4, &pllf_lock);
> + if (IS_ERR(hws[PLL_F]))
> + return PTR_ERR(hws[PLL_F]);
> +
> + /* PLL_TV */
> + hws[PLL_TV] = sp_pll_register(dev, "plltv", PD_EXTCLK, PLLTV_CTL,
> + 0, 15, 27000000, 0, DIV_TV, &plltv_lock);
> + if (IS_ERR(hws[PLL_TV]))
> + return PTR_ERR(hws[PLL_TV]);
> + hws[PLL_TV_A] = devm_clk_hw_register_divider(dev, "plltv_a", "plltv", 0,
> + PLLTV_CTL + 4, 5, 1,
> + CLK_DIVIDER_POWER_OF_TWO,
> + &plltv_lock);
> + if (IS_ERR(hws[PLL_TV_A]))
> + return PTR_ERR(hws[PLL_TV_A]);
> + ret = devm_clk_hw_register_clkdev(dev, hws[PLL_TV_A], NULL, "plltv_a");
> + if (ret)
> + return ret;
> +
> + /* PLL_SYS */
> + hws[PLL_SYS] = sp_pll_register(dev, "pllsys", PD_EXTCLK, PLLSYS_CTL,
> + 10, 9, 13500000, 0, 4, &pllsys_lock);
> + if (IS_ERR(hws[PLL_SYS]))
> + return PTR_ERR(hws[PLL_SYS]);
> +
> + /* gates */
> + for (i = 0; i < ARRAY_SIZE(gates); i++) {
> + u32 f = gates[i];
> + const char *parent_name = parents[!!(f & F_EXTCLK)].name;
> + bool critical = f & F_CRITICAL;
> + int j = f & 0xffff;
> + char s[10];
> +
> + sprintf(s, "clken%02x", j);
> + hws[j] = clk_hw_register_gate(dev, s, parent_name,
> + critical ? CLK_IS_CRITICAL : 0,
> + clk_regs + (j >> 4) * 4,
> + j & 0x0f,
> + CLK_GATE_HIWORD_MASK, NULL);
> + if (IS_ERR(hws[j]))
> + return PTR_ERR(hws[j]);
> + }
> +
> + sp_clk_data->num = CLK_MAX;
> + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, sp_clk_data);
> +}
> +
> +static const struct of_device_id sp7021_clk_dt_ids[] = {
> + { .compatible = "sunplus,sp7021-clkc", },
> + { }
> +};
> +
> +static struct platform_driver sp7021_clk_driver = {
> + .probe = sp7021_clk_probe,
> + .driver = {
> + .name = "sp7021-clk",
> + .of_match_table = sp7021_clk_dt_ids,
> + },
> +};
> +builtin_platform_driver(sp7021_clk_driver);
Can it be a module?
^ permalink raw reply
* [PATCH v6 13/13] docs: Add PECI documentation
From: Iwona Winiarska @ 2022-01-25 1:11 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Iwona Winiarska
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
Add a brief overview of PECI and PECI wire interface.
The documentation also contains kernel-doc for PECI subsystem internals
and PECI CPU Driver API.
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
---
Documentation/index.rst | 1 +
Documentation/peci/index.rst | 16 +++++++++++
Documentation/peci/peci.rst | 51 ++++++++++++++++++++++++++++++++++++
MAINTAINERS | 1 +
4 files changed, 69 insertions(+)
create mode 100644 Documentation/peci/index.rst
create mode 100644 Documentation/peci/peci.rst
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 2b4de3926858..deaa7f669fcd 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -138,6 +138,7 @@ needed).
scheduler/index
mhi/index
tty/index
+ peci/index
Architecture-agnostic documentation
-----------------------------------
diff --git a/Documentation/peci/index.rst b/Documentation/peci/index.rst
new file mode 100644
index 000000000000..989de10416e7
--- /dev/null
+++ b/Documentation/peci/index.rst
@@ -0,0 +1,16 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+====================
+Linux PECI Subsystem
+====================
+
+.. toctree::
+
+ peci
+
+.. only:: subproject and html
+
+ Indices
+ =======
+
+ * :ref:`genindex`
diff --git a/Documentation/peci/peci.rst b/Documentation/peci/peci.rst
new file mode 100644
index 000000000000..331b1ec00e22
--- /dev/null
+++ b/Documentation/peci/peci.rst
@@ -0,0 +1,51 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+========
+Overview
+========
+
+The Platform Environment Control Interface (PECI) is a communication
+interface between Intel processor and management controllers
+(e.g. Baseboard Management Controller, BMC).
+PECI provides services that allow the management controller to
+configure, monitor and debug platform by accessing various registers.
+It defines a dedicated command protocol, where the management
+controller is acting as a PECI originator and the processor - as
+a PECI responder.
+PECI can be used in both single processor and multiple-processor based
+systems.
+
+NOTE:
+Intel PECI specification is not released as a dedicated document,
+instead it is a part of External Design Specification (EDS) for given
+Intel CPU. External Design Specifications are usually not publicly
+available.
+
+PECI Wire
+---------
+
+PECI Wire interface uses a single wire for self-clocking and data
+transfer. It does not require any additional control lines - the
+physical layer is a self-clocked one-wire bus signal that begins each
+bit with a driven, rising edge from an idle near zero volts. The
+duration of the signal driven high allows to determine whether the bit
+value is logic '0' or logic '1'. PECI Wire also includes variable data
+rate established with every message.
+
+For PECI Wire, each processor package will utilize unique, fixed
+addresses within a defined range and that address should
+have a fixed relationship with the processor socket ID - if one of the
+processors is removed, it does not affect addresses of remaining
+processors.
+
+PECI subsystem internals
+------------------------
+
+.. kernel-doc:: include/linux/peci.h
+.. kernel-doc:: drivers/peci/internal.h
+.. kernel-doc:: drivers/peci/core.c
+.. kernel-doc:: drivers/peci/request.c
+
+PECI CPU Driver API
+-------------------
+.. kernel-doc:: drivers/peci/cpu.c
diff --git a/MAINTAINERS b/MAINTAINERS
index d821aeaaa2d3..06449cc42a10 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15112,6 +15112,7 @@ M: Iwona Winiarska <iwona.winiarska@intel.com>
L: openbmc@lists.ozlabs.org (moderated for non-subscribers)
S: Supported
F: Documentation/devicetree/bindings/peci/
+F: Documentation/peci/
F: drivers/peci/
F: include/linux/peci-cpu.h
F: include/linux/peci.h
--
2.31.1
^ permalink raw reply related
* [PATCH v6 12/13] docs: hwmon: Document PECI drivers
From: Iwona Winiarska @ 2022-01-25 1:11 UTC (permalink / raw)
To: linux-kernel, openbmc, Greg Kroah-Hartman
Cc: devicetree, linux-aspeed, linux-arm-kernel, linux-hwmon,
linux-doc, Rob Herring, Joel Stanley, Andrew Jeffery,
Jean Delvare, Guenter Roeck, Arnd Bergmann, Olof Johansson,
Jonathan Corbet, Borislav Petkov, Pierre-Louis Bossart, Tony Luck,
Andy Shevchenko, Dan Williams, Randy Dunlap, Zev Weiss,
David Muller, Dave Hansen, Billy Tsai, Jae Hyun Yoo,
Iwona Winiarska
In-Reply-To: <20220125011104.2480133-1-iwona.winiarska@intel.com>
From: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Add documentation for peci-cputemp driver that provides DTS thermal
readings for CPU packages and CPU cores, and peci-dimmtemp driver that
provides Temperature Sensor on DIMM readings.
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
Co-developed-by: Iwona Winiarska <iwona.winiarska@intel.com>
Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
---
Documentation/hwmon/index.rst | 2 +
Documentation/hwmon/peci-cputemp.rst | 90 +++++++++++++++++++++++++++
Documentation/hwmon/peci-dimmtemp.rst | 57 +++++++++++++++++
MAINTAINERS | 2 +
4 files changed, 151 insertions(+)
create mode 100644 Documentation/hwmon/peci-cputemp.rst
create mode 100644 Documentation/hwmon/peci-dimmtemp.rst
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index df20022c741f..f387f661e1d7 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -161,6 +161,8 @@ Hardware Monitoring Kernel Drivers
pcf8591
pim4328
pm6764tr
+ peci-cputemp
+ peci-dimmtemp
pmbus
powr1220
pxe1610
diff --git a/Documentation/hwmon/peci-cputemp.rst b/Documentation/hwmon/peci-cputemp.rst
new file mode 100644
index 000000000000..fe0422248dc5
--- /dev/null
+++ b/Documentation/hwmon/peci-cputemp.rst
@@ -0,0 +1,90 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+Kernel driver peci-cputemp
+==========================
+
+Supported chips:
+ One of Intel server CPUs listed below which is connected to a PECI bus.
+ * Intel Xeon E5/E7 v3 server processors
+ Intel Xeon E5-14xx v3 family
+ Intel Xeon E5-24xx v3 family
+ Intel Xeon E5-16xx v3 family
+ Intel Xeon E5-26xx v3 family
+ Intel Xeon E5-46xx v3 family
+ Intel Xeon E7-48xx v3 family
+ Intel Xeon E7-88xx v3 family
+ * Intel Xeon E5/E7 v4 server processors
+ Intel Xeon E5-16xx v4 family
+ Intel Xeon E5-26xx v4 family
+ Intel Xeon E5-46xx v4 family
+ Intel Xeon E7-48xx v4 family
+ Intel Xeon E7-88xx v4 family
+ * Intel Xeon Scalable server processors
+ Intel Xeon D family
+ Intel Xeon Bronze family
+ Intel Xeon Silver family
+ Intel Xeon Gold family
+ Intel Xeon Platinum family
+
+ Datasheet: Available from http://www.intel.com/design/literature.htm
+
+Author: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
+
+Description
+-----------
+
+This driver implements a generic PECI hwmon feature which provides Digital
+Thermal Sensor (DTS) thermal readings of the CPU package and CPU cores that are
+accessible via the processor PECI interface.
+
+All temperature values are given in millidegree Celsius and will be measurable
+only when the target CPU is powered on.
+
+Sysfs interface
+-------------------
+
+======================= =======================================================
+temp1_label "Die"
+temp1_input Provides current die temperature of the CPU package.
+temp1_max Provides thermal control temperature of the CPU package
+ which is also known as Tcontrol.
+temp1_crit Provides shutdown temperature of the CPU package which
+ is also known as the maximum processor junction
+ temperature, Tjmax or Tprochot.
+temp1_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of
+ the CPU package.
+
+temp2_label "DTS"
+temp2_input Provides current temperature of the CPU package scaled
+ to match DTS thermal profile.
+temp2_max Provides thermal control temperature of the CPU package
+ which is also known as Tcontrol.
+temp2_crit Provides shutdown temperature of the CPU package which
+ is also known as the maximum processor junction
+ temperature, Tjmax or Tprochot.
+temp2_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of
+ the CPU package.
+
+temp3_label "Tcontrol"
+temp3_input Provides current Tcontrol temperature of the CPU
+ package which is also known as Fan Temperature target.
+ Indicates the relative value from thermal monitor trip
+ temperature at which fans should be engaged.
+temp3_crit Provides Tcontrol critical value of the CPU package
+ which is same to Tjmax.
+
+temp4_label "Tthrottle"
+temp4_input Provides current Tthrottle temperature of the CPU
+ package. Used for throttling temperature. If this value
+ is allowed and lower than Tjmax - the throttle will
+ occur and reported at lower than Tjmax.
+
+temp5_label "Tjmax"
+temp5_input Provides the maximum junction temperature, Tjmax of the
+ CPU package.
+
+temp[6-N]_label Provides string "Core X", where X is resolved core
+ number.
+temp[6-N]_input Provides current temperature of each core.
+
+======================= =======================================================
diff --git a/Documentation/hwmon/peci-dimmtemp.rst b/Documentation/hwmon/peci-dimmtemp.rst
new file mode 100644
index 000000000000..e562aed620de
--- /dev/null
+++ b/Documentation/hwmon/peci-dimmtemp.rst
@@ -0,0 +1,57 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver peci-dimmtemp
+===========================
+
+Supported chips:
+ One of Intel server CPUs listed below which is connected to a PECI bus.
+ * Intel Xeon E5/E7 v3 server processors
+ Intel Xeon E5-14xx v3 family
+ Intel Xeon E5-24xx v3 family
+ Intel Xeon E5-16xx v3 family
+ Intel Xeon E5-26xx v3 family
+ Intel Xeon E5-46xx v3 family
+ Intel Xeon E7-48xx v3 family
+ Intel Xeon E7-88xx v3 family
+ * Intel Xeon E5/E7 v4 server processors
+ Intel Xeon E5-16xx v4 family
+ Intel Xeon E5-26xx v4 family
+ Intel Xeon E5-46xx v4 family
+ Intel Xeon E7-48xx v4 family
+ Intel Xeon E7-88xx v4 family
+ * Intel Xeon Scalable server processors
+ Intel Xeon D family
+ Intel Xeon Bronze family
+ Intel Xeon Silver family
+ Intel Xeon Gold family
+ Intel Xeon Platinum family
+
+ Datasheet: Available from http://www.intel.com/design/literature.htm
+
+Author: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
+
+Description
+-----------
+
+This driver implements a generic PECI hwmon feature which provides
+Temperature sensor on DIMM readings that are accessible via the processor PECI interface.
+
+All temperature values are given in millidegree Celsius and will be measurable
+only when the target CPU is powered on.
+
+Sysfs interface
+-------------------
+
+======================= =======================================================
+
+temp[N]_label Provides string "DIMM CI", where C is DIMM channel and
+ I is DIMM index of the populated DIMM.
+temp[N]_input Provides current temperature of the populated DIMM.
+temp[N]_max Provides thermal control temperature of the DIMM.
+temp[N]_crit Provides shutdown temperature of the DIMM.
+
+======================= =======================================================
+
+Note:
+ DIMM temperature attributes will appear when the client CPU's BIOS
+ completes memory training and testing.
diff --git a/MAINTAINERS b/MAINTAINERS
index 369ac3524f33..d821aeaaa2d3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15103,6 +15103,8 @@ PECI HARDWARE MONITORING DRIVERS
M: Iwona Winiarska <iwona.winiarska@intel.com>
L: linux-hwmon@vger.kernel.org
S: Supported
+F: Documentation/hwmon/peci-cputemp.rst
+F: Documentation/hwmon/peci-dimmtemp.rst
F: drivers/hwmon/peci/
PECI SUBSYSTEM
--
2.31.1
^ permalink raw reply related
* Re: [PATCH V3 1/2] dt-bindings: spi: Convert spi-mt65xx to json-schema
From: Leilk Liu @ 2022-01-25 1:18 UTC (permalink / raw)
To: Mark Brown
Cc: Rob Herring, Matthias Brugger, devicetree, linux-kernel,
linux-arm-kernel, linux-spi, linux-mediatek
In-Reply-To: <Ye6nQIVoti5TKh+k@sirena.org.uk>
On Mon, 2022-01-24 at 13:18 +0000, Mark Brown wrote:
> On Mon, Jan 24, 2022 at 02:12:37PM +0800, Leilk Liu wrote:
> > Convert Mediatek ARM SOC's SPI Master controller binding
> > to json-schema format.
>
> Please make any YAML conversion patches the last patches in a series
> -
> there's frequently a backlog in reviewing them so having the DT
> binding
> patches first blocks any progress on everything else in the series.
OK, I mistake to lost " dt-bindings: spi: Convert spi-slave-mt27xx to
json-schema" on V3 series, I'll send it again, thanks
> _______________________________________________
> Linux-mediatek mailing list
> Linux-mediatek@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-mediatek
^ permalink raw reply
* [PATCH V4 1/3] dt-bindings: spi: Convert spi-slave-mt27xx to json-schema
From: Leilk Liu @ 2022-01-25 1:23 UTC (permalink / raw)
To: Mark Brown, Rob Herring
Cc: Matthias Brugger, devicetree, linux-kernel, linux-arm-kernel,
linux-spi, linux-mediatek, Leilk Liu
In-Reply-To: <20220125012330.13449-1-leilk.liu@mediatek.com>
Convert Mediatek ARM SOC's SPI Slave controller binding
to json-schema format.
Signed-off-by: Leilk Liu <leilk.liu@mediatek.com>
---
.../spi/mediatek,spi-slave-mt27xx.yaml | 58 +++++++++++++++++++
.../bindings/spi/spi-slave-mt27xx.txt | 33 -----------
2 files changed, 58 insertions(+), 33 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/mediatek,spi-slave-mt27xx.yaml
delete mode 100644 Documentation/devicetree/bindings/spi/spi-slave-mt27xx.txt
diff --git a/Documentation/devicetree/bindings/spi/mediatek,spi-slave-mt27xx.yaml b/Documentation/devicetree/bindings/spi/mediatek,spi-slave-mt27xx.yaml
new file mode 100644
index 000000000000..7977799a8ee1
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/mediatek,spi-slave-mt27xx.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/mediatek,spi-slave-mt27xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SPI Slave controller for MediaTek ARM SoCs
+
+maintainers:
+ - Leilk Liu <leilk.liu@mediatek.com>
+
+allOf:
+ - $ref: "/schemas/spi/spi-controller.yaml#"
+
+properties:
+ compatible:
+ enum:
+ - mediatek,mt2712-spi-slave
+ - mediatek,mt8195-spi-slave
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: spi
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/mt2712-clk.h>
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi@10013000 {
+ compatible = "mediatek,mt2712-spi-slave";
+ reg = <0x10013000 0x100>;
+ interrupts = <GIC_SPI 283 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&infracfg CLK_INFRA_AO_SPI1>;
+ clock-names = "spi";
+ assigned-clocks = <&topckgen CLK_TOP_SPISLV_SEL>;
+ assigned-clock-parents = <&topckgen CLK_TOP_UNIVPLL1_D2>;
+ };
diff --git a/Documentation/devicetree/bindings/spi/spi-slave-mt27xx.txt b/Documentation/devicetree/bindings/spi/spi-slave-mt27xx.txt
deleted file mode 100644
index 9192724540fd..000000000000
--- a/Documentation/devicetree/bindings/spi/spi-slave-mt27xx.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-Binding for MTK SPI Slave controller
-
-Required properties:
-- compatible: should be one of the following.
- - mediatek,mt2712-spi-slave: for mt2712 platforms
- - mediatek,mt8195-spi-slave: for mt8195 platforms
-- reg: Address and length of the register set for the device.
-- interrupts: Should contain spi interrupt.
-- clocks: phandles to input clocks.
- It's clock gate, and should be <&infracfg CLK_INFRA_AO_SPI1>.
-- clock-names: should be "spi" for the clock gate.
-
-Optional properties:
-- assigned-clocks: it's mux clock, should be <&topckgen CLK_TOP_SPISLV_SEL>.
-- assigned-clock-parents: parent of mux clock.
- It's PLL, and should be one of the following.
- - <&topckgen CLK_TOP_UNIVPLL1_D2>: specify parent clock 312MHZ.
- It's the default one.
- - <&topckgen CLK_TOP_UNIVPLL1_D4>: specify parent clock 156MHZ.
- - <&topckgen CLK_TOP_UNIVPLL2_D4>: specify parent clock 104MHZ.
- - <&topckgen CLK_TOP_UNIVPLL1_D8>: specify parent clock 78MHZ.
-
-Example:
-- SoC Specific Portion:
-spis1: spi@10013000 {
- compatible = "mediatek,mt2712-spi-slave";
- reg = <0 0x10013000 0 0x100>;
- interrupts = <GIC_SPI 283 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&infracfg CLK_INFRA_AO_SPI1>;
- clock-names = "spi";
- assigned-clocks = <&topckgen CLK_TOP_SPISLV_SEL>;
- assigned-clock-parents = <&topckgen CLK_TOP_UNIVPLL1_D2>;
-};
--
2.25.1
^ permalink raw reply related
* [PATCH V4 0/3] Add compatible for Mediatek MT8186
From: Leilk Liu @ 2022-01-25 1:23 UTC (permalink / raw)
To: Mark Brown, Rob Herring
Cc: Matthias Brugger, devicetree, linux-kernel, linux-arm-kernel,
linux-spi, linux-mediatek
Subject: [PATCH V4 0/3] Add compatible for Mediatek MT8186
V4:
1. add "dt-bindings: spi: Convert spi-slave-mt27xx to json-schema" to
this series again.
v3:
1. Fix Rob review comment in v2.
v2:
1. Fix Rob review comment.
2. split spi-mt65xx & spi-slave-mt27xx to 2 patches.
Leilk Liu (3):
dt-bindings: spi: Convert spi-slave-mt27xx to json-schema
dt-bindings: spi: Convert spi-mt65xx to json-schema
dt-bindings: spi: Add compatible for Mediatek MT8186
.../bindings/spi/mediatek,spi-mt65xx.yaml | 102 ++++++++++++++++++
.../spi/mediatek,spi-slave-mt27xx.yaml | 58 ++++++++++
.../devicetree/bindings/spi/spi-mt65xx.txt | 68 ------------
.../bindings/spi/spi-slave-mt27xx.txt | 33 ------
4 files changed, 160 insertions(+), 101 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml
create mode 100644 Documentation/devicetree/bindings/spi/mediatek,spi-slave-mt27xx.yaml
delete mode 100644 Documentation/devicetree/bindings/spi/spi-mt65xx.txt
delete mode 100644 Documentation/devicetree/bindings/spi/spi-slave-mt27xx.txt
--
2.18.0
^ permalink raw reply
* [PATCH V4 2/3] dt-bindings: spi: Convert spi-mt65xx to json-schema
From: Leilk Liu @ 2022-01-25 1:23 UTC (permalink / raw)
To: Mark Brown, Rob Herring
Cc: Matthias Brugger, devicetree, linux-kernel, linux-arm-kernel,
linux-spi, linux-mediatek, Leilk Liu
In-Reply-To: <20220125012330.13449-1-leilk.liu@mediatek.com>
Convert Mediatek ARM SOC's SPI Master controller binding
to json-schema format.
Signed-off-by: Leilk Liu <leilk.liu@mediatek.com>
---
.../bindings/spi/mediatek,spi-mt65xx.yaml | 101 ++++++++++++++++++
.../devicetree/bindings/spi/spi-mt65xx.txt | 68 ------------
2 files changed, 101 insertions(+), 68 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml
delete mode 100644 Documentation/devicetree/bindings/spi/spi-mt65xx.txt
diff --git a/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml b/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml
new file mode 100644
index 000000000000..ea977fba49a7
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml
@@ -0,0 +1,101 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/mediatek,spi-mt65xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SPI Bus controller for MediaTek ARM SoCs
+
+maintainers:
+ - Leilk Liu <leilk.liu@mediatek.com>
+
+allOf:
+ - $ref: "/schemas/spi/spi-controller.yaml#"
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - enum:
+ - mediatek,mt7629-spi
+ - const: mediatek,mt7622-spi
+ - items:
+ - enum:
+ - mediatek,mt8516-spi
+ - const: mediatek,mt2712-spi
+ - items:
+ - enum:
+ - mediatek,mt6779-spi
+ - mediatek,mt8192-spi
+ - mediatek,mt8195-spi
+ - const: mediatek,mt6765-spi
+ - items:
+ - enum:
+ - mediatek,mt2701-spi
+ - mediatek,mt2712-spi
+ - mediatek,mt6589-spi
+ - mediatek,mt6765-spi
+ - mediatek,mt6893-spi
+ - mediatek,mt7622-spi
+ - mediatek,mt8135-spi
+ - mediatek,mt8173-spi
+ - mediatek,mt8183-spi
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: clock used for the parent clock
+ - description: clock used for the muxes clock
+ - description: clock used for the clock gate
+
+ clock-names:
+ items:
+ - const: parent-clk
+ - const: sel-clk
+ - const: spi-clk
+
+ mediatek,pad-select:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ maxItems: 4
+ items:
+ enum: [0, 1, 2, 3]
+ description:
+ specify which pins group(ck/mi/mo/cs) spi controller used.
+ This is an array.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - '#address-cells'
+ - '#size-cells'
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/mt8173-clk.h>
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi@1100a000 {
+ compatible = "mediatek,mt8173-spi";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x1100a000 0x1000>;
+ interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&topckgen CLK_TOP_SYSPLL3_D2>,
+ <&topckgen CLK_TOP_SPI_SEL>,
+ <&pericfg CLK_PERI_SPI0>;
+ clock-names = "parent-clk", "sel-clk", "spi-clk";
+ cs-gpios = <&pio 105 GPIO_ACTIVE_LOW>, <&pio 72 GPIO_ACTIVE_LOW>;
+ mediatek,pad-select = <1>, <0>;
+ };
diff --git a/Documentation/devicetree/bindings/spi/spi-mt65xx.txt b/Documentation/devicetree/bindings/spi/spi-mt65xx.txt
deleted file mode 100644
index 2a24969159cc..000000000000
--- a/Documentation/devicetree/bindings/spi/spi-mt65xx.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-Binding for MTK SPI controller
-
-Required properties:
-- compatible: should be one of the following.
- - mediatek,mt2701-spi: for mt2701 platforms
- - mediatek,mt2712-spi: for mt2712 platforms
- - mediatek,mt6589-spi: for mt6589 platforms
- - mediatek,mt6765-spi: for mt6765 platforms
- - mediatek,mt7622-spi: for mt7622 platforms
- - "mediatek,mt7629-spi", "mediatek,mt7622-spi": for mt7629 platforms
- - mediatek,mt8135-spi: for mt8135 platforms
- - mediatek,mt8173-spi: for mt8173 platforms
- - mediatek,mt8183-spi: for mt8183 platforms
- - mediatek,mt6893-spi: for mt6893 platforms
- - "mediatek,mt8192-spi", "mediatek,mt6765-spi": for mt8192 platforms
- - "mediatek,mt8195-spi", "mediatek,mt6765-spi": for mt8195 platforms
- - "mediatek,mt8516-spi", "mediatek,mt2712-spi": for mt8516 platforms
- - "mediatek,mt6779-spi", "mediatek,mt6765-spi": for mt6779 platforms
-
-- #address-cells: should be 1.
-
-- #size-cells: should be 0.
-
-- reg: Address and length of the register set for the device
-
-- interrupts: Should contain spi interrupt
-
-- clocks: phandles to input clocks.
- The first should be one of the following. It's PLL.
- - <&clk26m>: specify parent clock 26MHZ.
- - <&topckgen CLK_TOP_SYSPLL3_D2>: specify parent clock 109MHZ.
- It's the default one.
- - <&topckgen CLK_TOP_SYSPLL4_D2>: specify parent clock 78MHZ.
- - <&topckgen CLK_TOP_UNIVPLL2_D4>: specify parent clock 104MHZ.
- - <&topckgen CLK_TOP_UNIVPLL1_D8>: specify parent clock 78MHZ.
- The second should be <&topckgen CLK_TOP_SPI_SEL>. It's clock mux.
- The third is <&pericfg CLK_PERI_SPI0>. It's clock gate.
-
-- clock-names: shall be "parent-clk" for the parent clock, "sel-clk" for the
- muxes clock, and "spi-clk" for the clock gate.
-
-Optional properties:
--cs-gpios: see spi-bus.txt.
-
-- mediatek,pad-select: specify which pins group(ck/mi/mo/cs) spi
- controller used. This is an array, the element value should be 0~3,
- only required for MT8173.
- 0: specify GPIO69,70,71,72 for spi pins.
- 1: specify GPIO102,103,104,105 for spi pins.
- 2: specify GPIO128,129,130,131 for spi pins.
- 3: specify GPIO5,6,7,8 for spi pins.
-
-Example:
-
-- SoC Specific Portion:
-spi: spi@1100a000 {
- compatible = "mediatek,mt8173-spi";
- #address-cells = <1>;
- #size-cells = <0>;
- reg = <0 0x1100a000 0 0x1000>;
- interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&topckgen CLK_TOP_SYSPLL3_D2>,
- <&topckgen CLK_TOP_SPI_SEL>,
- <&pericfg CLK_PERI_SPI0>;
- clock-names = "parent-clk", "sel-clk", "spi-clk";
- cs-gpios = <&pio 105 GPIO_ACTIVE_LOW>, <&pio 72 GPIO_ACTIVE_LOW>;
- mediatek,pad-select = <1>, <0>;
-};
--
2.25.1
^ permalink raw reply related
* [PATCH V4 3/3] dt-bindings: spi: Add compatible for Mediatek MT8186
From: Leilk Liu @ 2022-01-25 1:23 UTC (permalink / raw)
To: Mark Brown, Rob Herring
Cc: Matthias Brugger, devicetree, linux-kernel, linux-arm-kernel,
linux-spi, linux-mediatek, Leilk Liu, Rob Herring
In-Reply-To: <20220125012330.13449-1-leilk.liu@mediatek.com>
This commit adds dt-binding documentation of spi bus for Mediatek MT8186 SoC
Platform.
Signed-off-by: Leilk Liu <leilk.liu@mediatek.com>
Acked-by: Rob Herring <robh@kernel.org>
---
Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml b/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml
index ea977fba49a7..bfa44acb1bdd 100644
--- a/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml
+++ b/Documentation/devicetree/bindings/spi/mediatek,spi-mt65xx.yaml
@@ -26,6 +26,7 @@ properties:
- items:
- enum:
- mediatek,mt6779-spi
+ - mediatek,mt8186-spi
- mediatek,mt8192-spi
- mediatek,mt8195-spi
- const: mediatek,mt6765-spi
--
2.25.1
^ permalink raw reply related
* Re: [RFC PATCH 1/2] dt-bindings: mtd: partitions: Document new dynamic-partitions node
From: Ansuel Smith @ 2022-01-24 22:12 UTC (permalink / raw)
To: Rafał Miłecki
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Rob Herring, linux-mtd, devicetree, linux-kernel
In-Reply-To: <a823e730-853d-901b-1b9f-937e1ec76444@gmail.com>
On Mon, Jan 24, 2022 at 11:02:24PM +0100, Rafał Miłecki wrote:
> On 20.01.2022 21:26, Ansuel Smith wrote:
> > Document new dynamic-partitions node used to provide an of node for
> > partition registred at runtime by parsers. This is required for nvmem
> > system to declare and detect nvmem-cells.
> >
> > Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
> > ---
> > .../mtd/partitions/dynamic-partitions.yaml | 59 +++++++++++++++++++
> > 1 file changed, 59 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/mtd/partitions/dynamic-partitions.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/mtd/partitions/dynamic-partitions.yaml b/Documentation/devicetree/bindings/mtd/partitions/dynamic-partitions.yaml
> > new file mode 100644
> > index 000000000000..7528e49f2d7e
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/mtd/partitions/dynamic-partitions.yaml
> > @@ -0,0 +1,59 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/mtd/partitions/dynamic-partitions.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Dynamic partitions
> > +
> > +description: |
> > + This binding can be used on platforms which have partitions registered at
> > + runtime by parsers or partition table present on the flash. Example are
> > + partitions declared from smem parser or cmdlinepart. This will create an
> > + of node for these dynamic partition where systems like Nvmem can get a
> > + reference to register nvmem-cells.
> > +
> > + The partition table should be a node named "dynamic-partitions".
> > + Partitions are then defined as subnodes. Only the label is required
> > + as any other data will be taken from the parser.
> > +
> > +maintainers:
> > + - Ansuel Smith <ansuelsmth@gmail.com>
> > +
> > +properties:
> > + compatible:
> > + const: dynamic-partitions
> > +
> > +patternProperties:
> > + "@[0-9a-f]+$":
> > + $ref: "partition.yaml#"
> > +
> > +additionalProperties: true
> > +
> > +examples:
> > + - |
> > + partitions {
> > + compatible = "qcom,smem";
> > + #address-cells = <1>;
> > + #size-cells = <1>;
> > + };
> > +
> > + dynamic-partitions {
> > + compatible = "dynamic-partitions";
> > +
> > + art: art {
> > + label = "0:art";
> > + read-only;
> > + compatible = "nvmem-cells";
> > + #address-cells = <1>;
> > + #size-cells = <1>;
> > +
> > + macaddr_art_0: macaddr@0 {
> > + reg = <0x0 0x6>;
> > + };
> > +
> > + macaddr_art_6: macaddr@6 {
> > + reg = <0x6 0x6>;
> > + };
> > + };
> > + };
>
> First of all: I fully support such a feature. I need it for Broadom
> platforms that use "brcm,bcm947xx-cfe-partitions" dynamic partitions.
> In my case bootloader partition is created dynamically (it doesn't have
> const offset and size). It contains NVMEM data however that needs to be
> described in DT.
>
> This binding however looks loose and confusing to me.
>
I agree.
> First of all did you really mean to use "qcom,smem"? My first guess is
> you meant "qcom,smem-part".
>
Yes sorry, I was referring to the smem parser qcom,smem-part
> Secondly can't we have partitions defined just as subnodes of the
> partitions { ... }; node?
>
I would love to use it. My only concern is that due to the fact
that we have to support legacy partition declaring, wonder if this could
create some problem. I'm referring to declaring fixed partition without
using any compatible/standard binding name.
I remember we improved that with the introduction of the nvmem binding
by making the fixed-partition compatible mandatory. But I would like to
have extra check. Wonder if to be on the safe part we can consider
appending to the "dynamic parser" a compatible like "dynamic-partitions"
and use your way to declare them (aka keeping the dynamic-partition and
removing the extra parallel partitions list)
Feel free to tell me it's just a stupid and unnecessary idea. I just
have fear to introduce regression in the partition parsing logic.
>
> I think sth like below would make more sense:
>
> partitions {
> compatible = "qcom,smem-part";
>
> art {
> label = "0:art";
> read-only;
> compatible = "nvmem-cells";
> #address-cells = <1>;
> #size-cells = <1>;
>
> macaddr_art_0: macaddr@0 {
> reg = <0x0 0x6>;
> };
>
> macaddr_art_6: macaddr@6 {
> reg = <0x6 0x6>;
> };
> };
> };
>
>
> Then I could also reuse that for something like:
>
> partitions {
> compatible = "brcm,bcm947xx-cfe-partitions";
>
> partition-0 {
> compatible = "nvmem-cells";
> label = "boot";
>
> #address-cells = <1>;
> #size-cells = <1>;
>
> mac: macaddr@0 {
> reg = <0x100 0x6>;
> };
> }
> };
--
Ansuel
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox