|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (C) 2020 MaxLinear, Inc. | 
|  | * | 
|  | * This driver is a hardware monitoring driver for PVT controller | 
|  | * (MR75203) which is used to configure & control Moortec embedded | 
|  | * analog IP to enable multiple embedded temperature sensor(TS), | 
|  | * voltage monitor(VM) & process detector(PD) modules. | 
|  | */ | 
|  | #include <linux/bits.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/debugfs.h> | 
|  | #include <linux/hwmon.h> | 
|  | #include <linux/kstrtox.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mod_devicetable.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/property.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <linux/reset.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/units.h> | 
|  |  | 
|  | /* PVT Common register */ | 
|  | #define PVT_IP_CONFIG	0x04 | 
|  | #define TS_NUM_MSK	GENMASK(4, 0) | 
|  | #define TS_NUM_SFT	0 | 
|  | #define PD_NUM_MSK	GENMASK(12, 8) | 
|  | #define PD_NUM_SFT	8 | 
|  | #define VM_NUM_MSK	GENMASK(20, 16) | 
|  | #define VM_NUM_SFT	16 | 
|  | #define CH_NUM_MSK	GENMASK(31, 24) | 
|  | #define CH_NUM_SFT	24 | 
|  |  | 
|  | #define VM_NUM_MAX	(VM_NUM_MSK >> VM_NUM_SFT) | 
|  |  | 
|  | /* Macro Common Register */ | 
|  | #define CLK_SYNTH		0x00 | 
|  | #define CLK_SYNTH_LO_SFT	0 | 
|  | #define CLK_SYNTH_HI_SFT	8 | 
|  | #define CLK_SYNTH_HOLD_SFT	16 | 
|  | #define CLK_SYNTH_EN		BIT(24) | 
|  | #define CLK_SYS_CYCLES_MAX	514 | 
|  | #define CLK_SYS_CYCLES_MIN	2 | 
|  |  | 
|  | #define SDIF_DISABLE	0x04 | 
|  |  | 
|  | #define SDIF_STAT	0x08 | 
|  | #define SDIF_BUSY	BIT(0) | 
|  | #define SDIF_LOCK	BIT(1) | 
|  |  | 
|  | #define SDIF_W		0x0c | 
|  | #define SDIF_PROG	BIT(31) | 
|  | #define SDIF_WRN_W	BIT(27) | 
|  | #define SDIF_WRN_R	0x00 | 
|  | #define SDIF_ADDR_SFT	24 | 
|  |  | 
|  | #define SDIF_HALT	0x10 | 
|  | #define SDIF_CTRL	0x14 | 
|  | #define SDIF_SMPL_CTRL	0x20 | 
|  |  | 
|  | /* TS & PD Individual Macro Register */ | 
|  | #define COM_REG_SIZE	0x40 | 
|  |  | 
|  | #define SDIF_DONE(n)	(COM_REG_SIZE + 0x14 + 0x40 * (n)) | 
|  | #define SDIF_SMPL_DONE	BIT(0) | 
|  |  | 
|  | #define SDIF_DATA(n)	(COM_REG_SIZE + 0x18 + 0x40 * (n)) | 
|  | #define SAMPLE_DATA_MSK	GENMASK(15, 0) | 
|  |  | 
|  | #define HILO_RESET(n)	(COM_REG_SIZE + 0x2c + 0x40 * (n)) | 
|  |  | 
|  | /* VM Individual Macro Register */ | 
|  | #define VM_COM_REG_SIZE	0x200 | 
|  | #define VM_SDIF_DONE(vm)	(VM_COM_REG_SIZE + 0x34 + 0x200 * (vm)) | 
|  | #define VM_SDIF_DATA(vm, ch)	\ | 
|  | (VM_COM_REG_SIZE + 0x40 + 0x200 * (vm) + 0x4 * (ch)) | 
|  |  | 
|  | /* SDA Slave Register */ | 
|  | #define IP_CTRL			0x00 | 
|  | #define IP_RST_REL		BIT(1) | 
|  | #define IP_RUN_CONT		BIT(3) | 
|  | #define IP_AUTO			BIT(8) | 
|  | #define IP_VM_MODE		BIT(10) | 
|  |  | 
|  | #define IP_CFG			0x01 | 
|  | #define CFG0_MODE_2		BIT(0) | 
|  | #define CFG0_PARALLEL_OUT	0 | 
|  | #define CFG0_12_BIT		0 | 
|  | #define CFG1_VOL_MEAS_MODE	0 | 
|  | #define CFG1_PARALLEL_OUT	0 | 
|  | #define CFG1_14_BIT		0 | 
|  |  | 
|  | #define IP_DATA		0x03 | 
|  |  | 
|  | #define IP_POLL		0x04 | 
|  | #define VM_CH_INIT	BIT(20) | 
|  | #define VM_CH_REQ	BIT(21) | 
|  |  | 
|  | #define IP_TMR			0x05 | 
|  | #define POWER_DELAY_CYCLE_256	0x100 | 
|  | #define POWER_DELAY_CYCLE_64	0x40 | 
|  |  | 
|  | #define PVT_POLL_DELAY_US	20 | 
|  | #define PVT_POLL_TIMEOUT_US	20000 | 
|  | #define PVT_CONV_BITS		10 | 
|  | #define PVT_N_CONST		90 | 
|  | #define PVT_R_CONST		245805 | 
|  |  | 
|  | #define PVT_TEMP_MIN_mC		-40000 | 
|  | #define PVT_TEMP_MAX_mC		125000 | 
|  |  | 
|  | /* Temperature coefficients for series 5 */ | 
|  | #define PVT_SERIES5_H_CONST	200000 | 
|  | #define PVT_SERIES5_G_CONST	60000 | 
|  | #define PVT_SERIES5_J_CONST	-100 | 
|  | #define PVT_SERIES5_CAL5_CONST	4094 | 
|  |  | 
|  | /* Temperature coefficients for series 6 */ | 
|  | #define PVT_SERIES6_H_CONST	249400 | 
|  | #define PVT_SERIES6_G_CONST	57400 | 
|  | #define PVT_SERIES6_J_CONST	0 | 
|  | #define PVT_SERIES6_CAL5_CONST	4096 | 
|  |  | 
|  | #define TEMPERATURE_SENSOR_SERIES_5	5 | 
|  | #define TEMPERATURE_SENSOR_SERIES_6	6 | 
|  |  | 
|  | #define PRE_SCALER_X1	1 | 
|  | #define PRE_SCALER_X2	2 | 
|  |  | 
|  | /** | 
|  | * struct voltage_device - VM single input parameters. | 
|  | * @vm_map: Map channel number to VM index. | 
|  | * @ch_map: Map channel number to channel index. | 
|  | * @pre_scaler: Pre scaler value (1 or 2) used to normalize the voltage output | 
|  | *              result. | 
|  | * | 
|  | * The structure provides mapping between channel-number (0..N-1) to VM-index | 
|  | * (0..num_vm-1) and channel-index (0..ch_num-1) where N = num_vm * ch_num. | 
|  | * It also provides normalization factor for the VM equation. | 
|  | */ | 
|  | struct voltage_device { | 
|  | u32 vm_map; | 
|  | u32 ch_map; | 
|  | u32 pre_scaler; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * struct voltage_channels - VM channel count. | 
|  | * @total: Total number of channels in all VMs. | 
|  | * @max: Maximum number of channels among all VMs. | 
|  | * | 
|  | * The structure provides channel count information across all VMs. | 
|  | */ | 
|  | struct voltage_channels { | 
|  | u32 total; | 
|  | u8 max; | 
|  | }; | 
|  |  | 
|  | struct temp_coeff { | 
|  | u32 h; | 
|  | u32 g; | 
|  | u32 cal5; | 
|  | s32 j; | 
|  | }; | 
|  |  | 
|  | struct pvt_device { | 
|  | struct regmap		*c_map; | 
|  | struct regmap		*t_map; | 
|  | struct regmap		*p_map; | 
|  | struct regmap		*v_map; | 
|  | struct clk		*clk; | 
|  | struct reset_control	*rst; | 
|  | struct dentry		*dbgfs_dir; | 
|  | struct voltage_device	*vd; | 
|  | struct voltage_channels	vm_channels; | 
|  | struct temp_coeff	ts_coeff; | 
|  | u32			t_num; | 
|  | u32			p_num; | 
|  | u32			v_num; | 
|  | u32			ip_freq; | 
|  | }; | 
|  |  | 
|  | static ssize_t pvt_ts_coeff_j_read(struct file *file, char __user *user_buf, | 
|  | size_t count, loff_t *ppos) | 
|  | { | 
|  | struct pvt_device *pvt = file->private_data; | 
|  | unsigned int len; | 
|  | char buf[13]; | 
|  |  | 
|  | len = scnprintf(buf, sizeof(buf), "%d\n", pvt->ts_coeff.j); | 
|  |  | 
|  | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | 
|  | } | 
|  |  | 
|  | static ssize_t pvt_ts_coeff_j_write(struct file *file, | 
|  | const char __user *user_buf, | 
|  | size_t count, loff_t *ppos) | 
|  | { | 
|  | struct pvt_device *pvt = file->private_data; | 
|  | int ret; | 
|  |  | 
|  | ret = kstrtos32_from_user(user_buf, count, 0, &pvt->ts_coeff.j); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static const struct file_operations pvt_ts_coeff_j_fops = { | 
|  | .read = pvt_ts_coeff_j_read, | 
|  | .write = pvt_ts_coeff_j_write, | 
|  | .open = simple_open, | 
|  | .owner = THIS_MODULE, | 
|  | .llseek = default_llseek, | 
|  | }; | 
|  |  | 
|  | static void devm_pvt_ts_dbgfs_remove(void *data) | 
|  | { | 
|  | struct pvt_device *pvt = (struct pvt_device *)data; | 
|  |  | 
|  | debugfs_remove_recursive(pvt->dbgfs_dir); | 
|  | pvt->dbgfs_dir = NULL; | 
|  | } | 
|  |  | 
|  | static int pvt_ts_dbgfs_create(struct pvt_device *pvt, struct device *dev) | 
|  | { | 
|  | pvt->dbgfs_dir = debugfs_create_dir(dev_name(dev), NULL); | 
|  |  | 
|  | debugfs_create_u32("ts_coeff_h", 0644, pvt->dbgfs_dir, | 
|  | &pvt->ts_coeff.h); | 
|  | debugfs_create_u32("ts_coeff_g", 0644, pvt->dbgfs_dir, | 
|  | &pvt->ts_coeff.g); | 
|  | debugfs_create_u32("ts_coeff_cal5", 0644, pvt->dbgfs_dir, | 
|  | &pvt->ts_coeff.cal5); | 
|  | debugfs_create_file("ts_coeff_j", 0644, pvt->dbgfs_dir, pvt, | 
|  | &pvt_ts_coeff_j_fops); | 
|  |  | 
|  | return devm_add_action_or_reset(dev, devm_pvt_ts_dbgfs_remove, pvt); | 
|  | } | 
|  |  | 
|  | static umode_t pvt_is_visible(const void *data, enum hwmon_sensor_types type, | 
|  | u32 attr, int channel) | 
|  | { | 
|  | switch (type) { | 
|  | case hwmon_temp: | 
|  | if (attr == hwmon_temp_input) | 
|  | return 0444; | 
|  | break; | 
|  | case hwmon_in: | 
|  | if (attr == hwmon_in_input) | 
|  | return 0444; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static long pvt_calc_temp(struct pvt_device *pvt, u32 nbs) | 
|  | { | 
|  | /* | 
|  | * Convert the register value to degrees centigrade temperature: | 
|  | * T = G + H * (n / cal5 - 0.5) + J * F | 
|  | */ | 
|  | struct temp_coeff *ts_coeff = &pvt->ts_coeff; | 
|  |  | 
|  | s64 tmp = ts_coeff->g + | 
|  | div_s64(ts_coeff->h * (s64)nbs, ts_coeff->cal5) - | 
|  | ts_coeff->h / 2 + | 
|  | div_s64(ts_coeff->j * (s64)pvt->ip_freq, HZ_PER_MHZ); | 
|  |  | 
|  | return clamp_val(tmp, PVT_TEMP_MIN_mC, PVT_TEMP_MAX_mC); | 
|  | } | 
|  |  | 
|  | static int pvt_read_temp(struct device *dev, u32 attr, int channel, long *val) | 
|  | { | 
|  | struct pvt_device *pvt = dev_get_drvdata(dev); | 
|  | struct regmap *t_map = pvt->t_map; | 
|  | u32 stat, nbs; | 
|  | int ret; | 
|  |  | 
|  | switch (attr) { | 
|  | case hwmon_temp_input: | 
|  | ret = regmap_read_poll_timeout(t_map, SDIF_DONE(channel), | 
|  | stat, stat & SDIF_SMPL_DONE, | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read(t_map, SDIF_DATA(channel), &nbs); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | nbs &= SAMPLE_DATA_MSK; | 
|  |  | 
|  | /* | 
|  | * Convert the register value to | 
|  | * degrees centigrade temperature | 
|  | */ | 
|  | *val = pvt_calc_temp(pvt, nbs); | 
|  |  | 
|  | return 0; | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int pvt_read_in(struct device *dev, u32 attr, int channel, long *val) | 
|  | { | 
|  | struct pvt_device *pvt = dev_get_drvdata(dev); | 
|  | struct regmap *v_map = pvt->v_map; | 
|  | u32 n, stat, pre_scaler; | 
|  | u8 vm_idx, ch_idx; | 
|  | int ret; | 
|  |  | 
|  | if (channel >= pvt->vm_channels.total) | 
|  | return -EINVAL; | 
|  |  | 
|  | vm_idx = pvt->vd[channel].vm_map; | 
|  | ch_idx = pvt->vd[channel].ch_map; | 
|  |  | 
|  | switch (attr) { | 
|  | case hwmon_in_input: | 
|  | ret = regmap_read_poll_timeout(v_map, VM_SDIF_DONE(vm_idx), | 
|  | stat, stat & SDIF_SMPL_DONE, | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read(v_map, VM_SDIF_DATA(vm_idx, ch_idx), &n); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | n &= SAMPLE_DATA_MSK; | 
|  | pre_scaler = pvt->vd[channel].pre_scaler; | 
|  | /* | 
|  | * Convert the N bitstream count into voltage. | 
|  | * To support negative voltage calculation for 64bit machines | 
|  | * n must be cast to long, since n and *val differ both in | 
|  | * signedness and in size. | 
|  | * Division is used instead of right shift, because for signed | 
|  | * numbers, the sign bit is used to fill the vacated bit | 
|  | * positions, and if the number is negative, 1 is used. | 
|  | * BIT(x) may not be used instead of (1 << x) because it's | 
|  | * unsigned. | 
|  | */ | 
|  | *val = pre_scaler * (PVT_N_CONST * (long)n - PVT_R_CONST) / | 
|  | (1 << PVT_CONV_BITS); | 
|  |  | 
|  | return 0; | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int pvt_read(struct device *dev, enum hwmon_sensor_types type, | 
|  | u32 attr, int channel, long *val) | 
|  | { | 
|  | switch (type) { | 
|  | case hwmon_temp: | 
|  | return pvt_read_temp(dev, attr, channel, val); | 
|  | case hwmon_in: | 
|  | return pvt_read_in(dev, attr, channel, val); | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | } | 
|  |  | 
|  | static struct hwmon_channel_info pvt_temp = { | 
|  | .type = hwmon_temp, | 
|  | }; | 
|  |  | 
|  | static struct hwmon_channel_info pvt_in = { | 
|  | .type = hwmon_in, | 
|  | }; | 
|  |  | 
|  | static const struct hwmon_ops pvt_hwmon_ops = { | 
|  | .is_visible = pvt_is_visible, | 
|  | .read = pvt_read, | 
|  | }; | 
|  |  | 
|  | static struct hwmon_chip_info pvt_chip_info = { | 
|  | .ops = &pvt_hwmon_ops, | 
|  | }; | 
|  |  | 
|  | static int pvt_init(struct pvt_device *pvt) | 
|  | { | 
|  | u16 sys_freq, key, middle, low = 4, high = 8; | 
|  | struct regmap *t_map = pvt->t_map; | 
|  | struct regmap *p_map = pvt->p_map; | 
|  | struct regmap *v_map = pvt->v_map; | 
|  | u32 t_num = pvt->t_num; | 
|  | u32 p_num = pvt->p_num; | 
|  | u32 v_num = pvt->v_num; | 
|  | u32 clk_synth, val; | 
|  | int ret; | 
|  |  | 
|  | sys_freq = clk_get_rate(pvt->clk) / HZ_PER_MHZ; | 
|  | while (high >= low) { | 
|  | middle = (low + high + 1) / 2; | 
|  | key = DIV_ROUND_CLOSEST(sys_freq, middle); | 
|  | if (key > CLK_SYS_CYCLES_MAX) { | 
|  | low = middle + 1; | 
|  | continue; | 
|  | } else if (key < CLK_SYS_CYCLES_MIN) { | 
|  | high = middle - 1; | 
|  | continue; | 
|  | } else { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The system supports 'clk_sys' to 'clk_ip' frequency ratios | 
|  | * from 2:1 to 512:1 | 
|  | */ | 
|  | key = clamp_val(key, CLK_SYS_CYCLES_MIN, CLK_SYS_CYCLES_MAX) - 2; | 
|  |  | 
|  | clk_synth = ((key + 1) >> 1) << CLK_SYNTH_LO_SFT | | 
|  | (key >> 1) << CLK_SYNTH_HI_SFT | | 
|  | (key >> 1) << CLK_SYNTH_HOLD_SFT | CLK_SYNTH_EN; | 
|  |  | 
|  | pvt->ip_freq = clk_get_rate(pvt->clk) / (key + 2); | 
|  |  | 
|  | if (t_num) { | 
|  | ret = regmap_write(t_map, SDIF_SMPL_CTRL, 0x0); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(t_map, SDIF_HALT, 0x0); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(t_map, CLK_SYNTH, clk_synth); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(t_map, SDIF_DISABLE, 0x0); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(t_map, SDIF_STAT, | 
|  | val, !(val & SDIF_BUSY), | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | val = CFG0_MODE_2 | CFG0_PARALLEL_OUT | CFG0_12_BIT | | 
|  | IP_CFG << SDIF_ADDR_SFT | SDIF_WRN_W | SDIF_PROG; | 
|  | ret = regmap_write(t_map, SDIF_W, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(t_map, SDIF_STAT, | 
|  | val, !(val & SDIF_BUSY), | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | val = POWER_DELAY_CYCLE_256 | IP_TMR << SDIF_ADDR_SFT | | 
|  | SDIF_WRN_W | SDIF_PROG; | 
|  | ret = regmap_write(t_map, SDIF_W, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(t_map, SDIF_STAT, | 
|  | val, !(val & SDIF_BUSY), | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | val = IP_RST_REL | IP_RUN_CONT | IP_AUTO | | 
|  | IP_CTRL << SDIF_ADDR_SFT | | 
|  | SDIF_WRN_W | SDIF_PROG; | 
|  | ret = regmap_write(t_map, SDIF_W, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (p_num) { | 
|  | ret = regmap_write(p_map, SDIF_HALT, 0x0); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(p_map, SDIF_DISABLE, BIT(p_num) - 1); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(p_map, CLK_SYNTH, clk_synth); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (v_num) { | 
|  | ret = regmap_write(v_map, SDIF_SMPL_CTRL, 0x0); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(v_map, SDIF_HALT, 0x0); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(v_map, CLK_SYNTH, clk_synth); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_write(v_map, SDIF_DISABLE, 0x0); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(v_map, SDIF_STAT, | 
|  | val, !(val & SDIF_BUSY), | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | val = (BIT(pvt->vm_channels.max) - 1) | VM_CH_INIT | | 
|  | IP_POLL << SDIF_ADDR_SFT | SDIF_WRN_W | SDIF_PROG; | 
|  | ret = regmap_write(v_map, SDIF_W, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(v_map, SDIF_STAT, | 
|  | val, !(val & SDIF_BUSY), | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | val = CFG1_VOL_MEAS_MODE | CFG1_PARALLEL_OUT | | 
|  | CFG1_14_BIT | IP_CFG << SDIF_ADDR_SFT | | 
|  | SDIF_WRN_W | SDIF_PROG; | 
|  | ret = regmap_write(v_map, SDIF_W, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(v_map, SDIF_STAT, | 
|  | val, !(val & SDIF_BUSY), | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | val = POWER_DELAY_CYCLE_64 | IP_TMR << SDIF_ADDR_SFT | | 
|  | SDIF_WRN_W | SDIF_PROG; | 
|  | ret = regmap_write(v_map, SDIF_W, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = regmap_read_poll_timeout(v_map, SDIF_STAT, | 
|  | val, !(val & SDIF_BUSY), | 
|  | PVT_POLL_DELAY_US, | 
|  | PVT_POLL_TIMEOUT_US); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | val = IP_RST_REL | IP_RUN_CONT | IP_AUTO | IP_VM_MODE | | 
|  | IP_CTRL << SDIF_ADDR_SFT | | 
|  | SDIF_WRN_W | SDIF_PROG; | 
|  | ret = regmap_write(v_map, SDIF_W, val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct regmap_config pvt_regmap_config = { | 
|  | .reg_bits = 32, | 
|  | .reg_stride = 4, | 
|  | .val_bits = 32, | 
|  | }; | 
|  |  | 
|  | static int pvt_get_regmap(struct platform_device *pdev, char *reg_name, | 
|  | struct pvt_device *pvt) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct regmap **reg_map; | 
|  | void __iomem *io_base; | 
|  |  | 
|  | if (!strcmp(reg_name, "common")) | 
|  | reg_map = &pvt->c_map; | 
|  | else if (!strcmp(reg_name, "ts")) | 
|  | reg_map = &pvt->t_map; | 
|  | else if (!strcmp(reg_name, "pd")) | 
|  | reg_map = &pvt->p_map; | 
|  | else if (!strcmp(reg_name, "vm")) | 
|  | reg_map = &pvt->v_map; | 
|  | else | 
|  | return -EINVAL; | 
|  |  | 
|  | io_base = devm_platform_ioremap_resource_byname(pdev, reg_name); | 
|  | if (IS_ERR(io_base)) | 
|  | return PTR_ERR(io_base); | 
|  |  | 
|  | pvt_regmap_config.name = reg_name; | 
|  | *reg_map = devm_regmap_init_mmio(dev, io_base, &pvt_regmap_config); | 
|  | if (IS_ERR(*reg_map)) { | 
|  | dev_err(dev, "failed to init register map\n"); | 
|  | return PTR_ERR(*reg_map); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void pvt_reset_control_assert(void *data) | 
|  | { | 
|  | struct pvt_device *pvt = data; | 
|  |  | 
|  | reset_control_assert(pvt->rst); | 
|  | } | 
|  |  | 
|  | static int pvt_reset_control_deassert(struct device *dev, struct pvt_device *pvt) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = reset_control_deassert(pvt->rst); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return devm_add_action_or_reset(dev, pvt_reset_control_assert, pvt); | 
|  | } | 
|  |  | 
|  | static int pvt_get_active_channel(struct device *dev, struct pvt_device *pvt, | 
|  | u32 vm_num, u32 ch_num, u8 *vm_idx) | 
|  | { | 
|  | u8 vm_active_ch[VM_NUM_MAX]; | 
|  | int ret, i, j, k; | 
|  |  | 
|  | ret = device_property_read_u8_array(dev, "moortec,vm-active-channels", | 
|  | vm_active_ch, vm_num); | 
|  | if (ret) { | 
|  | /* | 
|  | * Incase "moortec,vm-active-channels" property is not defined, | 
|  | * we assume each VM sensor has all of its channels active. | 
|  | */ | 
|  | memset(vm_active_ch, ch_num, vm_num); | 
|  | pvt->vm_channels.max = ch_num; | 
|  | pvt->vm_channels.total = ch_num * vm_num; | 
|  | } else { | 
|  | for (i = 0; i < vm_num; i++) { | 
|  | if (vm_active_ch[i] > ch_num) { | 
|  | dev_err(dev, "invalid active channels: %u\n", | 
|  | vm_active_ch[i]); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | pvt->vm_channels.total += vm_active_ch[i]; | 
|  |  | 
|  | if (vm_active_ch[i] > pvt->vm_channels.max) | 
|  | pvt->vm_channels.max = vm_active_ch[i]; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Map between the channel-number to VM-index and channel-index. | 
|  | * Example - 3 VMs, "moortec,vm_active_ch" = <5 2 4>: | 
|  | * vm_map = [0 0 0 0 0 1 1 2 2 2 2] | 
|  | * ch_map = [0 1 2 3 4 0 1 0 1 2 3] | 
|  | */ | 
|  | pvt->vd = devm_kcalloc(dev, pvt->vm_channels.total, sizeof(*pvt->vd), | 
|  | GFP_KERNEL); | 
|  | if (!pvt->vd) | 
|  | return -ENOMEM; | 
|  |  | 
|  | k = 0; | 
|  | for (i = 0; i < vm_num; i++) { | 
|  | for (j = 0; j < vm_active_ch[i]; j++) { | 
|  | pvt->vd[k].vm_map = vm_idx[i]; | 
|  | pvt->vd[k].ch_map = j; | 
|  | k++; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int pvt_get_pre_scaler(struct device *dev, struct pvt_device *pvt) | 
|  | { | 
|  | u8 *pre_scaler_ch_list; | 
|  | int i, ret, num_ch; | 
|  | u32 channel; | 
|  |  | 
|  | /* Set default pre-scaler value to be 1. */ | 
|  | for (i = 0; i < pvt->vm_channels.total; i++) | 
|  | pvt->vd[i].pre_scaler = PRE_SCALER_X1; | 
|  |  | 
|  | /* Get number of channels configured in "moortec,vm-pre-scaler-x2". */ | 
|  | num_ch = device_property_count_u8(dev, "moortec,vm-pre-scaler-x2"); | 
|  | if (num_ch <= 0) | 
|  | return 0; | 
|  |  | 
|  | pre_scaler_ch_list = kcalloc(num_ch, sizeof(*pre_scaler_ch_list), | 
|  | GFP_KERNEL); | 
|  | if (!pre_scaler_ch_list) | 
|  | return -ENOMEM; | 
|  |  | 
|  | /* Get list of all channels that have pre-scaler of 2. */ | 
|  | ret = device_property_read_u8_array(dev, "moortec,vm-pre-scaler-x2", | 
|  | pre_scaler_ch_list, num_ch); | 
|  | if (ret) | 
|  | goto out; | 
|  |  | 
|  | for (i = 0; i < num_ch; i++) { | 
|  | channel = pre_scaler_ch_list[i]; | 
|  | pvt->vd[channel].pre_scaler = PRE_SCALER_X2; | 
|  | } | 
|  |  | 
|  | out: | 
|  | kfree(pre_scaler_ch_list); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int pvt_set_temp_coeff(struct device *dev, struct pvt_device *pvt) | 
|  | { | 
|  | struct temp_coeff *ts_coeff = &pvt->ts_coeff; | 
|  | u32 series; | 
|  | int ret; | 
|  |  | 
|  | /* Incase ts-series property is not defined, use default 5. */ | 
|  | ret = device_property_read_u32(dev, "moortec,ts-series", &series); | 
|  | if (ret) | 
|  | series = TEMPERATURE_SENSOR_SERIES_5; | 
|  |  | 
|  | switch (series) { | 
|  | case TEMPERATURE_SENSOR_SERIES_5: | 
|  | ts_coeff->h = PVT_SERIES5_H_CONST; | 
|  | ts_coeff->g = PVT_SERIES5_G_CONST; | 
|  | ts_coeff->j = PVT_SERIES5_J_CONST; | 
|  | ts_coeff->cal5 = PVT_SERIES5_CAL5_CONST; | 
|  | break; | 
|  | case TEMPERATURE_SENSOR_SERIES_6: | 
|  | ts_coeff->h = PVT_SERIES6_H_CONST; | 
|  | ts_coeff->g = PVT_SERIES6_G_CONST; | 
|  | ts_coeff->j = PVT_SERIES6_J_CONST; | 
|  | ts_coeff->cal5 = PVT_SERIES6_CAL5_CONST; | 
|  | break; | 
|  | default: | 
|  | dev_err(dev, "invalid temperature sensor series (%u)\n", | 
|  | series); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | dev_dbg(dev, "temperature sensor series = %u\n", series); | 
|  |  | 
|  | /* Override ts-coeff-h/g/j/cal5 if they are defined. */ | 
|  | device_property_read_u32(dev, "moortec,ts-coeff-h", &ts_coeff->h); | 
|  | device_property_read_u32(dev, "moortec,ts-coeff-g", &ts_coeff->g); | 
|  | device_property_read_u32(dev, "moortec,ts-coeff-j", &ts_coeff->j); | 
|  | device_property_read_u32(dev, "moortec,ts-coeff-cal5", &ts_coeff->cal5); | 
|  |  | 
|  | dev_dbg(dev, "ts-coeff: h = %u, g = %u, j = %d, cal5 = %u\n", | 
|  | ts_coeff->h, ts_coeff->g, ts_coeff->j, ts_coeff->cal5); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int mr75203_probe(struct platform_device *pdev) | 
|  | { | 
|  | u32 ts_num, vm_num, pd_num, ch_num, val, index, i; | 
|  | const struct hwmon_channel_info **pvt_info; | 
|  | struct device *dev = &pdev->dev; | 
|  | u32 *temp_config, *in_config; | 
|  | struct device *hwmon_dev; | 
|  | struct pvt_device *pvt; | 
|  | int ret; | 
|  |  | 
|  | pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); | 
|  | if (!pvt) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ret = pvt_get_regmap(pdev, "common", pvt); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | pvt->clk = devm_clk_get_enabled(dev, NULL); | 
|  | if (IS_ERR(pvt->clk)) | 
|  | return dev_err_probe(dev, PTR_ERR(pvt->clk), "failed to get clock\n"); | 
|  |  | 
|  | pvt->rst = devm_reset_control_get_optional_exclusive(dev, NULL); | 
|  | if (IS_ERR(pvt->rst)) | 
|  | return dev_err_probe(dev, PTR_ERR(pvt->rst), | 
|  | "failed to get reset control\n"); | 
|  |  | 
|  | if (pvt->rst) { | 
|  | ret = pvt_reset_control_deassert(dev, pvt); | 
|  | if (ret) | 
|  | return dev_err_probe(dev, ret, | 
|  | "cannot deassert reset control\n"); | 
|  | } | 
|  |  | 
|  | ret = regmap_read(pvt->c_map, PVT_IP_CONFIG, &val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ts_num = (val & TS_NUM_MSK) >> TS_NUM_SFT; | 
|  | pd_num = (val & PD_NUM_MSK) >> PD_NUM_SFT; | 
|  | vm_num = (val & VM_NUM_MSK) >> VM_NUM_SFT; | 
|  | ch_num = (val & CH_NUM_MSK) >> CH_NUM_SFT; | 
|  | pvt->t_num = ts_num; | 
|  | pvt->p_num = pd_num; | 
|  | pvt->v_num = vm_num; | 
|  | val = 0; | 
|  | if (ts_num) | 
|  | val++; | 
|  | if (vm_num) | 
|  | val++; | 
|  | if (!val) | 
|  | return -ENODEV; | 
|  |  | 
|  | pvt_info = devm_kcalloc(dev, val + 2, sizeof(*pvt_info), GFP_KERNEL); | 
|  | if (!pvt_info) | 
|  | return -ENOMEM; | 
|  | pvt_info[0] = HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ); | 
|  | index = 1; | 
|  |  | 
|  | if (ts_num) { | 
|  | ret = pvt_get_regmap(pdev, "ts", pvt); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = pvt_set_temp_coeff(dev, pvt); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | temp_config = devm_kcalloc(dev, ts_num + 1, | 
|  | sizeof(*temp_config), GFP_KERNEL); | 
|  | if (!temp_config) | 
|  | return -ENOMEM; | 
|  |  | 
|  | memset32(temp_config, HWMON_T_INPUT, ts_num); | 
|  | pvt_temp.config = temp_config; | 
|  | pvt_info[index++] = &pvt_temp; | 
|  |  | 
|  | pvt_ts_dbgfs_create(pvt, dev); | 
|  | } | 
|  |  | 
|  | if (pd_num) { | 
|  | ret = pvt_get_regmap(pdev, "pd", pvt); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (vm_num) { | 
|  | u8 vm_idx[VM_NUM_MAX]; | 
|  |  | 
|  | ret = pvt_get_regmap(pdev, "vm", pvt); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = device_property_read_u8_array(dev, "intel,vm-map", vm_idx, | 
|  | vm_num); | 
|  | if (ret) { | 
|  | /* | 
|  | * Incase intel,vm-map property is not defined, we | 
|  | * assume incremental channel numbers. | 
|  | */ | 
|  | for (i = 0; i < vm_num; i++) | 
|  | vm_idx[i] = i; | 
|  | } else { | 
|  | for (i = 0; i < vm_num; i++) | 
|  | if (vm_idx[i] >= vm_num || vm_idx[i] == 0xff) { | 
|  | pvt->v_num = i; | 
|  | vm_num = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = pvt_get_active_channel(dev, pvt, vm_num, ch_num, vm_idx); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = pvt_get_pre_scaler(dev, pvt); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | in_config = devm_kcalloc(dev, pvt->vm_channels.total + 1, | 
|  | sizeof(*in_config), GFP_KERNEL); | 
|  | if (!in_config) | 
|  | return -ENOMEM; | 
|  |  | 
|  | memset32(in_config, HWMON_I_INPUT, pvt->vm_channels.total); | 
|  | in_config[pvt->vm_channels.total] = 0; | 
|  | pvt_in.config = in_config; | 
|  |  | 
|  | pvt_info[index++] = &pvt_in; | 
|  | } | 
|  |  | 
|  | ret = pvt_init(pvt); | 
|  | if (ret) { | 
|  | dev_err(dev, "failed to init pvt: %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | pvt_chip_info.info = pvt_info; | 
|  | hwmon_dev = devm_hwmon_device_register_with_info(dev, "pvt", | 
|  | pvt, | 
|  | &pvt_chip_info, | 
|  | NULL); | 
|  |  | 
|  | return PTR_ERR_OR_ZERO(hwmon_dev); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id moortec_pvt_of_match[] = { | 
|  | { .compatible = "moortec,mr75203" }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, moortec_pvt_of_match); | 
|  |  | 
|  | static struct platform_driver moortec_pvt_driver = { | 
|  | .driver = { | 
|  | .name = "moortec-pvt", | 
|  | .of_match_table = moortec_pvt_of_match, | 
|  | }, | 
|  | .probe = mr75203_probe, | 
|  | }; | 
|  | module_platform_driver(moortec_pvt_driver); | 
|  |  | 
|  | MODULE_LICENSE("GPL v2"); |