Skip to content

Instantly share code, notes, and snippets.

@krisk0
Last active September 2, 2024 12:57
Show Gist options
  • Save krisk0/61654db6f11db61a1d10f5143dc7e0d9 to your computer and use it in GitHub Desktop.
Save krisk0/61654db6f11db61a1d10f5143dc7e0d9 to your computer and use it in GitHub Desktop.
mr75202.c patch that enables voltage output
--- a/drivers/hwmon/mr75202.c 2024-07-22 12:23:44.000000000 +0300
+++ b/drivers/hwmon/mr75202.c 2024-09-02 10:59:07.000000000 +0300
@@ -27,6 +27,9 @@
#define MR75202_VM_BASE 0x184
#define MR75202_PD_BASE 0xab0
+/* Offset of VM register set from TS register set */
+#define MR75202_VM_TS_OFFSET (MR75202_VM_BASE - MR75202_TS_BASE)
+
#define MR75202_TS_REG(a) (MR75202_TS_BASE + (a))
#define MR75202_VM_REG(a) (MR75202_VM_BASE + (a))
@@ -79,6 +82,7 @@
#define MR75202_SDA_IP_CTRL_RESET BIT(1)
#define MR75202_SDA_IP_CTRL_RUN_ONCE BIT(2)
#define MR75202_SDA_IP_CTRL_AUTO BIT(8)
+#define MR75202_SDA_IP_CTRL_VM_MODE BIT(10)
#define MR75202_SDA_IP_TMR 0x5
#define MR75202_SDA_IP_DATA_DAT GENMASK(15, 0)
@@ -90,13 +94,30 @@
#define MR75202_DELAY_US 500
#define MR75202_TIMEOUT_US 50000
+/* MR75202_VM_...: offsets from priv->base */
+#define MR75202_VM_CLK_SYNTH 0x184
+#define MR75202_VM_SDIF_DISABLE 0x188
+#define MR75202_VM_SDIF_STATUS 0x18C
+#define MR75202_VM_DATA(n) (0x1B8 + 64 * (n))
+#define MR75202_VM_SMPL_STATUS 0x3FC
+
+/* masks */
+#define MR75202_VM_SDIF_STATUS_LOCK BIT(1)
+
+/* Convenience value used to run voltage sensors */
+#define MR75202_VOLTAGE_RUN_ONCE (MR75202_SDA_IP_CTRL_RUN_ONCE | \
+ MR75202_SDA_IP_CTRL_AUTO | \
+ MR75202_SDA_IP_CTRL_VM_MODE)
+
struct mr75202_priv {
void __iomem *base;
struct clk *clk;
struct reset_control *rst;
const char *labels[8];
- struct mutex mutex;
+ struct mutex mutex_t;
+ struct mutex mutex_v;
size_t nts;
+ size_t nvs;
};
static umode_t mr75202_is_visible(const void *data,
@@ -107,12 +128,29 @@
switch (type) {
case hwmon_temp:
+ if (channel >= priv->nts)
+ break;
switch (attr) {
case hwmon_temp_input:
return 0444;
case hwmon_temp_label:
if (priv->labels[channel])
return 0444;
+ default:
+ break;
+ }
+ break;
+ case hwmon_in:
+ if (channel >= priv->nvs)
+ break;
+ switch (attr) {
+ case hwmon_in_input:
+ return 0444;
+ case hwmon_in_label:
+ if (priv->labels[channel])
+ return 0444;
+ default:
+ break;
}
break;
default:
@@ -122,12 +160,18 @@
return 0;
}
-static int mr75202_write_ts(struct mr75202_priv *priv, u32 address, u32 data)
+/*
+ * When block_offset is zero, mr75202_write operates on temperature registers.
+ * When block_offset is not zero, mr75202_write operates on another register set.
+ */
+static int mr75202_write(struct mr75202_priv *priv, u32 address, u32 data,
+ size_t block_offset)
{
u32 val;
int ret;
+ void __iomem *status = priv->base + MR75202_TS_SDIF_STATUS + block_offset;
- ret = readl_poll_timeout(priv->base + MR75202_TS_SDIF_STATUS, val,
+ ret = readl_poll_timeout(status, val,
!(val & MR75202_TS_SDIF_STATUS_BUSY),
MR75202_DELAY_US, MR75202_TIMEOUT_US);
if (ret)
@@ -136,7 +180,7 @@
data = FIELD_PREP(MR75202_TS_SDIF_WDATA, data) |
FIELD_PREP(MR75202_TS_SDIF_ADDR, address) |
MR75202_TS_SDIF_WRN | MR75202_TS_SDIF_PROG;
- writel(data, priv->base + MR75202_TS_SDIF);
+ writel(data, priv->base + MR75202_TS_SDIF + block_offset);
return 0;
}
@@ -148,16 +192,20 @@
u32 nbs;
int ret;
- mutex_lock(&priv->mutex);
+ if (channel >= priv->nts)
+ return -EINVAL;
+
+ mutex_lock(&priv->mutex_t);
switch (attr) {
case hwmon_temp_input:
/* Deassert sample done bit in TS_SMPL_STATUS by reading old
* data. */
readl(priv->base + MR75202_TS_DATA(channel));
- mr75202_write_ts(priv, MR75202_SDA_IP_CTRL,
- MR75202_SDA_IP_CTRL_RUN_ONCE |
- MR75202_SDA_IP_CTRL_AUTO);
+ mr75202_write(priv, MR75202_SDA_IP_CTRL,
+ MR75202_SDA_IP_CTRL_RUN_ONCE |
+ MR75202_SDA_IP_CTRL_AUTO,
+ 0);
/* TODO: This takes ~8 ms, so it may be worth using IRQs */
ret = readl_poll_timeout(priv->base + MR75202_TS_SMPL_STATUS,
@@ -180,17 +228,82 @@
break;
}
- mutex_unlock(&priv->mutex);
+ mutex_unlock(&priv->mutex_t);
return ret;
}
+/* Error codes:
+ * -ETIMEDOUT if status register does not show that result is available
+ * -EIO if device returns failure
+ * -ECONNREFUSED if subsystem is turned off
+ */
+static int mr75202_read_voltage(struct mr75202_priv *priv, int channel, long *val)
+{
+ uint32_t raw;
+ int ret;
+
+ /* page 56 of PVT controller spec: discard old value */
+ (void)readl(priv->base + MR75202_VM_DATA(channel));
+
+ /* run once */
+ mr75202_write(priv, MR75202_SDA_IP_CTRL, MR75202_VOLTAGE_RUN_ONCE,
+ MR75202_VM_TS_OFFSET);
+
+ /* wait until bit no. channel is unset in status register */
+ ret = readl_poll_timeout(priv->base + MR75202_VM_SMPL_STATUS,
+ raw, !(raw & BIT(channel)),
+ MR75202_DELAY_US, MR75202_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ /* extract data */
+ raw = readl(priv->base + MR75202_VM_DATA(channel));
+
+ /* check _fault and _type bits */
+ if(raw & (MR75202_SDA_IP_DATA_TYPE | MR75202_SDA_IP_DATA_FAULT))
+ return -EIO;
+
+ /* leave junior bits */
+ raw &= MR75202_SDA_IP_DATA_DAT;
+
+ /* if zero, check lock status */
+ if ((!raw) && (readl(priv->base + MR75202_VM_SDIF_STATUS) &
+ MR75202_VM_SDIF_STATUS_LOCK))
+ /* subsystem is down */
+ return -ECONNREFUSED;
+
+ *val = ((uint64_t)raw + 1) * 1224 / 256;
+ return 0;
+}
+
+static int mr75202_read_volt(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct mr75202_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if (channel >= priv->nvs)
+ return -EINVAL;
+ if (attr != hwmon_in_input)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&priv->mutex_v);
+ ret = mr75202_read_voltage(priv, channel, val);
+ mutex_unlock(&priv->mutex_v);
+ return ret;
+}
+
static int mr75202_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
+ if (channel < 0)
+ return 0;
switch (type) {
case hwmon_temp:
return mr75202_read_temp(dev, attr, channel, val);
+ case hwmon_in:
+ return mr75202_read_volt(dev, attr, channel, val);
default:
return -EOPNOTSUPP;
}
@@ -203,6 +316,7 @@
switch (attr) {
case hwmon_temp_label:
+ case hwmon_in_label:
*str = priv->labels[channel];
return 0;
default:
@@ -210,7 +324,94 @@
}
}
-static int mr75202_init_temp(struct device *dev, struct mr75202_priv *priv)
+static size_t mr75202_try_bits(void __iomem *adr, size_t bound)
+{
+ size_t res = 0;
+
+ do {
+ writel(BIT(res), adr);
+ } while (readl(adr) == BIT(res++));
+ res -= 1;
+ if (res > bound)
+ res = bound;
+
+ return res;
+}
+
+static void mr75202_count_sensors(struct mr75202_priv *priv)
+{
+ const size_t bound = ARRAY_SIZE(priv->labels);
+
+ /* MR75202 doesn't have a register with device configuration, but we
+ * can probe it with a trick. */
+ priv->nts = mr75202_try_bits(priv->base + MR75202_TS_SDIF_DISABLE, bound);
+ priv->nvs = mr75202_try_bits(priv->base + MR75202_VM_SDIF_DISABLE, bound);
+}
+
+static int mr75202_init_sensors(struct mr75202_priv *priv, unsigned div,
+ size_t block_offset)
+{
+ /* MR75202_TS_SDIF_DISABLE is already zeroed, no need to do it again */
+ writel(0, priv->base + MR75202_TS_SDIF_CTRL + block_offset);
+
+ writel(FIELD_PREP(MR75202_TS_CLK_SYNTH_LO, div) |
+ FIELD_PREP(MR75202_TS_CLK_SYNTH_HI, div) |
+ FIELD_PREP(MR75202_TS_CLK_SYNTH_STROBE, 1) |
+ MR75202_TS_CLK_SYNTH_ENA,
+ priv->base + MR75202_TS_CLK_SYNTH + block_offset);
+
+ /* No need to deassert sensor PD and RESET, this will be done
+ * automatically by Serial Data Adapter/PVT slave (MR75005) as we use
+ * data readout in auto-mode. */
+
+ /* According to PVT Controller specification section 11.6.6,
+ * TS4 sensor power-up time should be 254 for temperature and 64 for
+ * voltage.
+ *
+ * Experiment and make it larger, say 1024 */
+ return mr75202_write(priv, MR75202_SDA_IP_TMR,
+ 1024,
+ block_offset);
+}
+
+static int mr75202_wakeup_vm_channels(struct mr75202_priv *priv)
+{
+ int i, ret;
+ uint32_t all_channels = (1 << priv->nvs) - 1;
+ u32 val;
+ void __iomem *adr = priv->base + MR75202_VM_SMPL_STATUS;
+
+ /* Read old data from all channels */
+ for(i = 0; i < priv->nvs; i++)
+ (void)readl(priv->base + MR75202_VM_DATA(i));
+
+ ret = mr75202_write(priv, MR75202_SDA_IP_CTRL, MR75202_VOLTAGE_RUN_ONCE,
+ MR75202_VM_TS_OFFSET);
+ if (ret)
+ return ret;
+
+ /* Wait until vm_smpl_status & all_channels > 0 */
+ ret = readl_poll_timeout(adr, val, val & all_channels, MR75202_DELAY_US,
+ MR75202_TIMEOUT_US);
+ return ret;
+}
+
+static int mr75202_check_voltage_ready(struct mr75202_priv *priv)
+{
+ int wakeup_result;
+
+ /* Loop until mr75202_wakeup_vm_channels returns zero */
+ return read_poll_timeout_atomic(mr75202_wakeup_vm_channels,
+ wakeup_result,
+ !wakeup_result,
+ MR75202_DELAY_US,
+ MR75202_TIMEOUT_US,
+ false,
+ priv);
+}
+
+static int mr75202_init_temp_volt(struct device *dev,
+ struct mr75202_priv *priv)
{
/* TODO: skip if no temperature sensors */
/* TODO: split-out TSx-specific code into separate function */
@@ -227,6 +428,7 @@
unsigned long rate = clk_get_rate(priv->clk);
unsigned int div = rate / (2 * minfreq);
+ int ret;
if (div < 1 || div > 256) {
dev_err(dev,
@@ -234,41 +436,32 @@
return -EINVAL;
}
- /* MR75202 doesn't have a register with device configuration, but we
- * can probe it with a trick. */
- do {
- writel(BIT(priv->nts), priv->base + MR75202_TS_SDIF_DISABLE);
- } while (readl(priv->base + MR75202_TS_SDIF_DISABLE) == BIT(priv->nts++));
- priv->nts -= 1;
-
- /* MR75202_TS_SDIF_DISABLE is already zeroed, no need to do it again */
- writel(0, priv->base + MR75202_TS_SDIF_CTRL);
-
- dev_info(dev, "Configure TS SDA clock: %lu Hz, div = %u\n",
+ dev_info(dev, "Configure sensors SDA clock: %lu Hz, div = %u\n",
rate / (2 * div), div);
- writel(FIELD_PREP(MR75202_TS_CLK_SYNTH_LO, div - 1) |
- FIELD_PREP(MR75202_TS_CLK_SYNTH_HI, div - 1) |
- FIELD_PREP(MR75202_TS_CLK_SYNTH_STROBE, 1) |
- MR75202_TS_CLK_SYNTH_ENA, priv->base + MR75202_TS_CLK_SYNTH);
- /* No need to deassert sensor PD and RESET, this will be done
- * automatically by Serial Data Adapter/PVT slave (MR75005) as we use
- * data readout in auto-mode. */
+ --div;
+ /* TODO: ignore return code of mr75202_init_sensors like
+ * mr75202_init_temp return code is ignored in previous version */
+ ret = mr75202_init_sensors(priv, div, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = mr75202_init_sensors(priv, div, MR75202_VM_TS_OFFSET);
+ if (ret < 0)
+ return ret;
- /* TS4 sensor power-up time should be >= 50 us */
- return mr75202_write_ts(priv, MR75202_SDA_IP_TMR,
- DIV_ROUND_UP(50 * rate, 1000000));
+ ret = mr75202_check_voltage_ready(priv);
+ return ret;
/* TODO: Reset samples counter */
/* TODO: Disable interrupts */
}
-
static int mr75202_init(struct device *dev, struct mr75202_priv *priv)
{
- mr75202_init_temp(dev, priv);
-
- return 0;
+ mr75202_count_sensors(priv);
+ /* TODO: Ignore return code, return 0 always */
+ return mr75202_init_temp_volt(dev, priv);
}
static const struct hwmon_ops mr75202_hwmon_ops = {
@@ -292,9 +485,15 @@
/* .config is initialized in mr75202_init_chip_info() */
};
+static struct hwmon_channel_info mr75202_channel_volt_info = {
+ .type = hwmon_in
+ /* .config is initialized in mr75202_init_chip_info() */
+};
+
static const struct hwmon_channel_info *mr75202_channel_info[] = {
&mr75202_channel_chip_info,
&mr75202_channel_temp_info,
+ &mr75202_channel_volt_info,
NULL
};
@@ -309,17 +508,25 @@
const struct mr75202_priv *priv)
{
size_t i;
- u32 *config = devm_kcalloc(dev, priv->nts + 1, sizeof(u32), GFP_KERNEL);
+ u32 *config_t = devm_kcalloc(dev, 1 + priv->nts, sizeof(u32), GFP_KERNEL);
+ u32 *config_v = devm_kcalloc(dev, 1 + priv->nvs, sizeof(u32), GFP_KERNEL);
- if (!config)
+ if ((!config_t) || (!config_v))
return NULL;
- memset32(config, HWMON_T_INPUT, priv->nts);
- for (i = 0; i < min(priv->nts, ARRAY_SIZE(priv->labels)); i++)
- if (priv->labels[i])
- config[i] |= HWMON_T_LABEL;
+ /* nts and nvs are not more than size of of labels array. Thus, there
+ * is no out-of-bound access in loops below */
+ for (i = 0; i < priv->nts; i++) {
+ config_t[i] = HWMON_T_INPUT |
+ (priv->labels[i] ? HWMON_T_LABEL : 0);
+ }
+ for (i = 0; i < priv->nvs; i++) {
+ config_v[i] = HWMON_I_INPUT |
+ (priv->labels[i] ? HWMON_I_LABEL : 0);
+ }
- mr75202_channel_temp_info.config = config;
+ mr75202_channel_temp_info.config = config_t;
+ mr75202_channel_volt_info.config = config_v;
return &mr75202_chip_info;
}
@@ -374,7 +581,8 @@
of_property_read_string_array(pdev->dev.of_node, "moortec,labels",
priv->labels, ARRAY_SIZE(priv->labels));
- mutex_init(&priv->mutex);
+ mutex_init(&priv->mutex_t);
+ mutex_init(&priv->mutex_v);
ret = mr75202_init(&pdev->dev, priv);
if (ret) {
dev_err(&pdev->dev,
@@ -397,12 +605,14 @@
}
dev_set_drvdata(&pdev->dev, hdev);
- dev_info(&pdev->dev, "%zu temperature sensors\n", priv->nts);
+ dev_info(&pdev->dev, "%zu/%zu temperature/voltage sensors\n",
+ priv->nts, priv->nvs);
return 0;
err_reset_assert:
- mutex_destroy(&priv->mutex);
+ mutex_destroy(&priv->mutex_t);
+ mutex_destroy(&priv->mutex_v);
reset_control_assert(priv->rst);
err_clk_disable:
clk_disable_unprepare(priv->clk);
@@ -410,14 +620,46 @@
return ret;
}
+static void mr75202_done_temp(struct mr75202_priv *priv)
+{
+ /* Disable all channels */
+ writel(0xFF, priv->base + MR75202_TS_SDIF_DISABLE);
+
+ /*
+ Setting the ip_ctrl register to its default value will disable data
+ acquisitions and shutdown the embedded IP i.e. ip_ctrl = 0x000001
+ */
+ (void)mr75202_write(priv, MR75202_SDA_IP_CTRL, MR75202_SDA_IP_CTRL_PD, 0);
+
+ /* Turn off clock */
+ writel(0, priv->base + MR75202_TS_CLK_SYNTH);
+}
+
+static void mr75202_done_volt(struct mr75202_priv *priv)
+{
+ /* Disable all channels */
+ writel(0xFF, priv->base + MR75202_VM_SDIF_DISABLE);
+
+ /* ip_ctrl = 0x000001 */
+ (void)mr75202_write(priv, MR75202_SDA_IP_CTRL, MR75202_SDA_IP_CTRL_PD,
+ MR75202_VM_TS_OFFSET);
+
+ /* Turn off clock */
+ writel(0, priv->base + MR75202_VM_CLK_SYNTH);
+}
+
static int mr75202_remove(struct platform_device *pdev)
{
struct device *hdev = dev_get_drvdata(&pdev->dev);
struct mr75202_priv *priv = dev_get_drvdata(hdev);
- mutex_destroy(&priv->mutex);
+ mutex_destroy(&priv->mutex_t);
+ mutex_destroy(&priv->mutex_v);
+
reset_control_assert(priv->rst);
clk_disable_unprepare(priv->clk);
+ mr75202_done_volt(priv);
+ mr75202_done_temp(priv);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment