summaryrefslogtreecommitdiff
path: root/drivers/w1
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/w1')
-rw-r--r--drivers/w1/slaves/w1_therm.c137
1 files changed, 137 insertions, 0 deletions
diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c
index aa5678369c0b..932a0738b52a 100644
--- a/drivers/w1/slaves/w1_therm.c
+++ b/drivers/w1/slaves/w1_therm.c
@@ -43,8 +43,21 @@
static int w1_strong_pullup = 1;
module_param_named(strong_pullup, w1_strong_pullup, int, 0);
+/* Nb of try for an operation */
+#define W1_THERM_MAX_TRY 5
+
+/* ms delay to retry bus mutex */
+#define W1_THERM_RETRY_DELAY 20
+
/* Helpers Macros */
+/*
+ * return the power mode of the sl slave : 1-ext, 0-parasite, <0 unknown
+ * always test family data existence before using this macro
+ */
+#define SLAVE_POWERMODE(sl) \
+ (((struct w1_therm_family_data *)(sl->family_data))->external_powered)
+
/* return the address of the refcnt in the family data */
#define THERM_REFCNT(family_data) \
(&((struct w1_therm_family_data *)family_data)->refcnt)
@@ -73,10 +86,14 @@ struct w1_therm_family_converter {
* struct w1_therm_family_data - device data
* @rom: ROM device id (64bit Lasered ROM code + 1 CRC byte)
* @refcnt: ref count
+ * @external_powered: 1 device powered externally,
+ * 0 device parasite powered,
+ * -x error or undefined
*/
struct w1_therm_family_data {
uint8_t rom[9];
atomic_t refcnt;
+ int external_powered;
};
/**
@@ -109,6 +126,20 @@ struct therm_info {
*/
static int reset_select_slave(struct w1_slave *sl);
+/**
+ * read_powermode() - Query the power mode of the slave
+ * @sl: slave to retrieve the power mode
+ *
+ * Ask the device to get its power mode (external or parasite)
+ * and store the power status in the &struct w1_therm_family_data.
+ *
+ * Return:
+ * * 0 parasite powered device
+ * * 1 externally powered device
+ * * <0 kernel error code
+ */
+static int read_powermode(struct w1_slave *sl);
+
/* Sysfs interface declaration */
static ssize_t w1_slave_show(struct device *device,
@@ -120,10 +151,14 @@ static ssize_t w1_slave_store(struct device *device,
static ssize_t w1_seq_show(struct device *device,
struct device_attribute *attr, char *buf);
+static ssize_t ext_power_show(struct device *device,
+ struct device_attribute *attr, char *buf);
+
/* Attributes declarations */
static DEVICE_ATTR_RW(w1_slave);
static DEVICE_ATTR_RO(w1_seq);
+static DEVICE_ATTR_RO(ext_power);
/* Interface Functions declaration */
@@ -151,12 +186,14 @@ static void w1_therm_remove_slave(struct w1_slave *sl);
static struct attribute *w1_therm_attrs[] = {
&dev_attr_w1_slave.attr,
+ &dev_attr_ext_power.attr,
NULL,
};
static struct attribute *w1_ds28ea00_attrs[] = {
&dev_attr_w1_slave.attr,
&dev_attr_w1_seq.attr,
+ &dev_attr_ext_power.attr,
NULL,
};
@@ -433,6 +470,34 @@ static struct w1_therm_family_converter w1_therm_families[] = {
/* Helpers Functions */
/**
+ * bus_mutex_lock() - Acquire the mutex
+ * @lock: w1 bus mutex to acquire
+ *
+ * It try to acquire the mutex W1_THERM_MAX_TRY times and wait
+ * W1_THERM_RETRY_DELAY between 2 attempts.
+ *
+ * Return: true is mutex is acquired and lock, false otherwise
+ */
+static inline bool bus_mutex_lock(struct mutex *lock)
+{
+ int max_trying = W1_THERM_MAX_TRY;
+
+ /* try to acquire the mutex, if not, sleep retry_delay before retry) */
+ while (mutex_lock_interruptible(lock) != 0 && max_trying > 0) {
+ unsigned long sleep_rem;
+
+ sleep_rem = msleep_interruptible(W1_THERM_RETRY_DELAY);
+ if (!sleep_rem)
+ max_trying--;
+ }
+
+ if (!max_trying)
+ return false; /* Didn't acquire the bus mutex */
+
+ return true;
+}
+
+/**
* w1_convert_temp() - temperature conversion binding function
* @rom: data read from device RAM (8 data bytes + 1 CRC byte)
* @fid: device family id
@@ -461,7 +526,19 @@ static int w1_therm_add_slave(struct w1_slave *sl)
GFP_KERNEL);
if (!sl->family_data)
return -ENOMEM;
+
atomic_set(THERM_REFCNT(sl->family_data), 1);
+
+ /* Getting the power mode of the device {external, parasite} */
+ SLAVE_POWERMODE(sl) = read_powermode(sl);
+
+ if (SLAVE_POWERMODE(sl) < 0) {
+ /* no error returned as device has been added */
+ dev_warn(&sl->dev,
+ "%s: Device has been added, but power_mode may be corrupted. err=%d\n",
+ __func__, SLAVE_POWERMODE(sl));
+ }
+
return 0;
}
@@ -661,6 +738,44 @@ error:
return ret;
}
+static int read_powermode(struct w1_slave *sl)
+{
+ struct w1_master *dev_master = sl->master;
+ int max_trying = W1_THERM_MAX_TRY;
+ int ret = -ENODEV;
+
+ if (!sl->family_data)
+ goto error;
+
+ /* prevent the slave from going away in sleep */
+ atomic_inc(THERM_REFCNT(sl->family_data));
+
+ if (!bus_mutex_lock(&dev_master->bus_mutex)) {
+ ret = -EAGAIN; /* Didn't acquire the mutex */
+ goto dec_refcnt;
+ }
+
+ while ((max_trying--) && (ret < 0)) {
+ /* safe version to select slave */
+ if (!reset_select_slave(sl)) {
+ w1_write_8(dev_master, W1_READ_PSUPPLY);
+ /*
+ * Emit a read time slot and read only one bit,
+ * 1 is externally powered,
+ * 0 is parasite powered
+ */
+ ret = w1_touch_bit(dev_master, 1);
+ /* ret should be either 1 either 0 */
+ }
+ }
+ mutex_unlock(&dev_master->bus_mutex);
+
+dec_refcnt:
+ atomic_dec(THERM_REFCNT(sl->family_data));
+error:
+ return ret;
+}
+
/* Sysfs Interface definition */
static ssize_t w1_slave_show(struct device *device,
@@ -722,6 +837,28 @@ static ssize_t w1_slave_store(struct device *device,
return ret ? : size;
}
+static ssize_t ext_power_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct w1_slave *sl = dev_to_w1_slave(device);
+
+ if (!sl->family_data) {
+ dev_info(device,
+ "%s: Device not supported by the driver\n", __func__);
+ return 0; /* No device family */
+ }
+
+ /* Getting the power mode of the device {external, parasite} */
+ SLAVE_POWERMODE(sl) = read_powermode(sl);
+
+ if (SLAVE_POWERMODE(sl) < 0) {
+ dev_dbg(device,
+ "%s: Power_mode may be corrupted. err=%d\n",
+ __func__, SLAVE_POWERMODE(sl));
+ }
+ return sprintf(buf, "%d\n", SLAVE_POWERMODE(sl));
+}
+
#if IS_REACHABLE(CONFIG_HWMON)
static int w1_read_temp(struct device *device, u32 attr, int channel,
long *val)