Last active
September 2, 2024 12:57
-
-
Save krisk0/61654db6f11db61a1d10f5143dc7e0d9 to your computer and use it in GitHub Desktop.
mr75202.c patch that enables voltage output
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- 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