From patchwork Tue Jul 18 03:36:51 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Jeffery X-Patchwork-Id: 9846733 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id AB4AA60212 for ; Tue, 18 Jul 2017 03:37:53 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A849624B5B for ; Tue, 18 Jul 2017 03:37:53 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9C38927E5A; Tue, 18 Jul 2017 03:37:53 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9FE5624B5B for ; Tue, 18 Jul 2017 03:37:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751391AbdGRDhQ (ORCPT ); Mon, 17 Jul 2017 23:37:16 -0400 Received: from out1-smtp.messagingengine.com ([66.111.4.25]:40875 "EHLO out1-smtp.messagingengine.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751336AbdGRDhN (ORCPT ); Mon, 17 Jul 2017 23:37:13 -0400 Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailout.nyi.internal (Postfix) with ESMTP id 3A15F2074E; Mon, 17 Jul 2017 23:37:12 -0400 (EDT) Received: from frontend1 ([10.202.2.160]) by compute4.internal (MEProxy); Mon, 17 Jul 2017 23:37:12 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=aj.id.au; h=cc :date:from:in-reply-to:message-id:references:subject:to :x-me-sender:x-me-sender:x-sasl-enc:x-sasl-enc; s=fm1; bh=acEzQd ojBg5zsF+8/DBfbMfMDPIjirZoVIEMCl1/ckQ=; b=VfA1Ofm4xgvbsivH89swua ZvwJIT/YeENj7kGKSZcjo9GFGSafs3TGDvQZ3Km3DiyX2IiHv//l+V9kshSzcSR0 bTFHWGTEoGX132RAupKZVpvEOQDhgkH6tz8qJXr6v/nGhKT4n9Tg1XdzwJUwQgmO hAcdBxrnfGveJQl7Yxqrdu9H001l5AvpgBtHESTDcyZ+otirD+jqwBgeLJY7Gj4V cvJtspQMroDapha6GxaPwq79OWnEyRzSS6T4OOpjakrGzrbgs9plj6dTG2AVRkWm 1OXpeWwZAPlAUJofjTod5QgDa+4MNFfqEnMEJqZ9HbeVrz2qXlLr/ivOi+8BowCQ == DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:date:from:in-reply-to:message-id :references:subject:to:x-me-sender:x-me-sender:x-sasl-enc :x-sasl-enc; s=fm1; bh=acEzQdojBg5zsF+8/DBfbMfMDPIjirZoVIEMCl1/c kQ=; b=QE2mT4Ey2aMh3R0T6tWycUPQOeidG7PABPMdB2y7Ow1yJYOU7KVXFg52R QVPiNTG9PM08+7YdtP7i1H7E+RAPHwryyT4LVNdSR9XeolrHMhREa5OUTKMnzhnt HYIPvHr+JYujyhJMw8K2Fi+ZzuBIQ3MedTFjU0i4Lq+Ys6T4xMsTi108fVrX8PCn KBXUBkVvBkwy4FLi5TFxxbhoiXHrZ1MflwFKS11GNQpy6J5wwVcAhQ3a+Ci0BZV0 ekT42DWcVJfHoLnZUjToP1yLRgETom/aFBF0LQ/+6H4yRi1rcB2IbJD2TlGRIbah 5GLW2TeB6L1WpqELrq6P4khIikQlA== X-ME-Sender: X-Sasl-enc: 61uAmEGIykDm1klc7Hsxx11KXxifjwMj6mgLsWXVcMhv 1500349031 Received: from keelia.base64.com.au (unknown [203.0.153.9]) by mail.messagingengine.com (Postfix) with ESMTPA id 480FC7E792; Mon, 17 Jul 2017 23:37:07 -0400 (EDT) From: Andrew Jeffery To: linux@roeck-us.net, linux-hwmon@vger.kernel.org Cc: Andrew Jeffery , jdelvare@suse.com, linux-kernel@vger.kernel.org, joel@jms.id.au, openbmc@lists.ozlabs.org, msbarth@linux.vnet.ibm.com, mspinler@linux.vnet.ibm.com Subject: [RFC PATCH v2 1/3] hwmon: pmbus: Add fan control support Date: Tue, 18 Jul 2017 13:06:51 +0930 Message-Id: <20170718033653.10298-2-andrew@aj.id.au> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20170718033653.10298-1-andrew@aj.id.au> References: <20170718033653.10298-1-andrew@aj.id.au> Sender: linux-hwmon-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-hwmon@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes. Fans in a PMBus device are driven by the configuration of two registers: FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan and the tacho operate (if installed), while FAN_COMMAND_x sets the desired fan rate. The unit of FAN_COMMAND_x is dependent on the operational fan mode, RPM or PWM percent duty, as determined by the corresponding FAN_CONFIG_x_y. The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and FAN_COMMAND_x is implemented with the addition of virtual registers and generic implementations in the core: 1. PMBUS_VIRT_FAN_TARGET_x 2. PMBUS_VIRT_PWM_x 3. PMBUS_VIRT_PWM_ENABLE_x The facilitate the necessary side-effects of each access. Examples of the read case, assuming m = 1, b = 0, R = 0: Read | With || Gives ------------------------------------------------------- Attribute | FAN_CONFIG_x_y | FAN_COMMAND_y || Value ----------------------------------------------++------- fanX_target | ~PB_FAN_z_RPM | 0x0001 || 1 pwm1 | ~PB_FAN_z_RPM | 0x0064 || 255 pwmX_enable | ~PB_FAN_z_RPM | 0x0001 || 1 fanX_target | PB_FAN_z_RPM | 0x0001 || 1 pwm1 | PB_FAN_z_RPM | 0x0064 || 0 pwmX_enable | PB_FAN_z_RPM | 0x0001 || 1 And the write case: Write | With || Sets -------------+-------++----------------+--------------- Attribute | Value || FAN_CONFIG_x_y | FAN_COMMAND_x -------------+-------++----------------+--------------- fanX_target | 1 || PB_FAN_z_RPM | 0x0001 pwmX | 255 || ~PB_FAN_z_RPM | 0x0064 pwmX_enable | 1 || ~PB_FAN_z_RPM | 0x0064 Also, the DIRECT mode scaling of some controllers is different between RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the necessary coefficients. Signed-off-by: Andrew Jeffery --- v1 -> v2: * Convert to using virtual registers * Drop struct pmbus_fan_ctrl * Introduce PSC_PWM * Drop struct pmbus_coeffs * Drop additional callbacks drivers/hwmon/pmbus/pmbus.h | 19 ++++ drivers/hwmon/pmbus/pmbus_core.c | 215 +++++++++++++++++++++++++++++++++++---- 2 files changed, 217 insertions(+), 17 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index bfcb13bae34b..226a37bd525f 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -190,6 +190,20 @@ enum pmbus_regs { PMBUS_VIRT_VMON_UV_FAULT_LIMIT, PMBUS_VIRT_VMON_OV_FAULT_LIMIT, PMBUS_VIRT_STATUS_VMON, + + /* Fan control */ + PMBUS_VIRT_FAN_TARGET_1, + PMBUS_VIRT_FAN_TARGET_2, + PMBUS_VIRT_FAN_TARGET_3, + PMBUS_VIRT_FAN_TARGET_4, + PMBUS_VIRT_PWM_1, + PMBUS_VIRT_PWM_2, + PMBUS_VIRT_PWM_3, + PMBUS_VIRT_PWM_4, + PMBUS_VIRT_PWM_ENABLE_1, + PMBUS_VIRT_PWM_ENABLE_2, + PMBUS_VIRT_PWM_ENABLE_3, + PMBUS_VIRT_PWM_ENABLE_4, }; /* @@ -223,6 +237,8 @@ enum pmbus_regs { #define PB_FAN_1_RPM BIT(6) #define PB_FAN_1_INSTALLED BIT(7) +enum pmbus_fan_mode { percent = 0, rpm }; + /* * STATUS_BYTE, STATUS_WORD (lower) */ @@ -313,6 +329,7 @@ enum pmbus_sensor_classes { PSC_POWER, PSC_TEMPERATURE, PSC_FAN, + PSC_PWM, PSC_NUM_CLASSES /* Number of power sensor classes */ }; @@ -413,6 +430,8 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value); int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, u8 mask, u8 value); +int pmbus_update_fan(struct i2c_client *client, int page, int id, + int config, int mask, int command); void pmbus_clear_faults(struct i2c_client *client); bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index ba59eaef2e07..712a8b6c4bd6 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -63,6 +63,7 @@ struct pmbus_sensor { u16 reg; /* register */ enum pmbus_sensor_classes class; /* sensor class */ bool update; /* runtime sensor update needed */ + bool convert; /* Whether or not to apply linear/vid/direct */ int data; /* Sensor data. Negative if there was a read error */ }; @@ -118,6 +119,27 @@ struct pmbus_data { u8 currpage; }; +static const int pmbus_fan_rpm_mask[] = { + PB_FAN_1_RPM, + PB_FAN_2_RPM, + PB_FAN_1_RPM, + PB_FAN_2_RPM, +}; + +static const int pmbus_fan_config_registers[] = { + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_34, + PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_command_registers[] = { + PMBUS_FAN_COMMAND_1, + PMBUS_FAN_COMMAND_2, + PMBUS_FAN_COMMAND_3, + PMBUS_FAN_COMMAND_4, +}; + void pmbus_clear_cache(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); @@ -188,6 +210,29 @@ int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, u16 word) } EXPORT_SYMBOL_GPL(pmbus_write_word_data); +int pmbus_update_fan(struct i2c_client *client, int page, int id, + int config, int mask, int command) +{ + int from, to; + int rv; + + from = pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (from < 0) + return from; + + to = (from & ~mask) | (config & mask); + + rv = pmbus_write_byte_data(client, page, + pmbus_fan_config_registers[id], to); + if (rv < 0) + return rv; + + return pmbus_write_word_data(client, page, + pmbus_fan_command_registers[id], command); +} +EXPORT_SYMBOL_GPL(pmbus_update_fan); + /* * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if * a device specific mapping function exists and calls it if necessary. @@ -197,15 +242,47 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, { struct pmbus_data *data = i2c_get_clientdata(client); const struct pmbus_driver_info *info = data->info; - int status; + int status = -ENODATA; if (info->write_word_data) { status = info->write_word_data(client, page, reg, word); if (status != -ENODATA) return status; } - if (reg >= PMBUS_VIRT_BASE) - return -ENXIO; + if (status == -ENODATA && reg >= PMBUS_VIRT_BASE) { + int id, bit; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1: + case PMBUS_VIRT_FAN_TARGET_2: + case PMBUS_VIRT_FAN_TARGET_3: + case PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + bit = pmbus_fan_rpm_mask[id]; + status = pmbus_update_fan(client, page, id, bit, bit, + word); + break; + case PMBUS_VIRT_PWM_1: + case PMBUS_VIRT_PWM_2: + case PMBUS_VIRT_PWM_3: + case PMBUS_VIRT_PWM_4: + { + long command = word; + + id = reg - PMBUS_VIRT_PWM_1; + bit = pmbus_fan_rpm_mask[id]; + command *= 100; + command /= 255; + status = pmbus_update_fan(client, page, id, 0, bit, + command); + break; + } + default: + status = -ENXIO; + break; + } + return status; + } return pmbus_write_word_data(client, page, reg, word); } @@ -221,6 +298,9 @@ int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg) } EXPORT_SYMBOL_GPL(pmbus_read_word_data); +static int pmbus_get_fan_command(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode); + /* * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if * a device specific mapping function exists and calls it if necessary. @@ -229,15 +309,49 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg) { struct pmbus_data *data = i2c_get_clientdata(client); const struct pmbus_driver_info *info = data->info; - int status; + int status = -ENODATA; if (info->read_word_data) { status = info->read_word_data(client, page, reg); if (status != -ENODATA) return status; } - if (reg >= PMBUS_VIRT_BASE) - return -ENXIO; + if (status == -ENODATA && reg >= PMBUS_VIRT_BASE) { + int id; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1: + case PMBUS_VIRT_FAN_TARGET_2: + case PMBUS_VIRT_FAN_TARGET_3: + case PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + status = pmbus_get_fan_command(client, page, id, rpm); + break; + case PMBUS_VIRT_PWM_1: + case PMBUS_VIRT_PWM_2: + case PMBUS_VIRT_PWM_3: + case PMBUS_VIRT_PWM_4: + { + long rv; + + id = reg - PMBUS_VIRT_PWM_1; + rv = pmbus_get_fan_command(client, page, id, percent); + if (rv < 0) + return rv; + + rv *= 255; + rv /= 100; + + status = rv; + break; + } + default: + status = -ENXIO; + break; + } + + return status; + } return pmbus_read_word_data(client, page, reg); } @@ -304,6 +418,23 @@ static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) return pmbus_read_byte_data(client, page, reg); } +static int pmbus_get_fan_command(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode) +{ + int config; + + config = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (config < 0) + return config; + + if ((mode == rpm) != (!!(config & pmbus_fan_rpm_mask[id]))) + return 0; + + return _pmbus_read_word_data(client, page, + pmbus_fan_command_registers[id]); +} + static void pmbus_clear_fault_page(struct i2c_client *client, int page) { _pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); @@ -489,7 +620,7 @@ static long pmbus_reg2data_direct(struct pmbus_data *data, /* X = 1/m * (Y * 10^-R - b) */ R = -R; /* scale result to milli-units for everything but fans */ - if (sensor->class != PSC_FAN) { + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { R += 3; b *= 1000; } @@ -539,6 +670,9 @@ static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) { long val; + if (!sensor->convert) + return sensor->data; + switch (data->info->format[sensor->class]) { case direct: val = pmbus_reg2data_direct(data, sensor); @@ -642,7 +776,7 @@ static u16 pmbus_data2reg_direct(struct pmbus_data *data, } /* Calculate Y = (m * X + b) * 10^R */ - if (sensor->class != PSC_FAN) { + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { R -= 3; /* Adjust R and b for data in milli-units */ b *= 1000; } @@ -673,6 +807,9 @@ static u16 pmbus_data2reg(struct pmbus_data *data, { u16 regval; + if (!sensor->convert) + return val; + switch (data->info->format[sensor->class]) { case direct: regval = pmbus_data2reg_direct(data, sensor, val); @@ -895,12 +1032,13 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, return NULL; a = &sensor->attribute; - snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", - name, seq, type); + snprintf(sensor->name, sizeof(sensor->name), "%s%d%s%s", + name, seq, type ? "_" : "", type ? type : ""); sensor->page = page; sensor->reg = reg; sensor->class = class; sensor->update = update; + sensor->convert = true; pmbus_dev_attr_init(a, sensor->name, readonly ? S_IRUGO : S_IRUGO | S_IWUSR, pmbus_show_sensor, pmbus_set_sensor); @@ -1558,13 +1696,6 @@ static const int pmbus_fan_registers[] = { PMBUS_READ_FAN_SPEED_4 }; -static const int pmbus_fan_config_registers[] = { - PMBUS_FAN_CONFIG_12, - PMBUS_FAN_CONFIG_12, - PMBUS_FAN_CONFIG_34, - PMBUS_FAN_CONFIG_34 -}; - static const int pmbus_fan_status_registers[] = { PMBUS_STATUS_FAN_12, PMBUS_STATUS_FAN_12, @@ -1587,6 +1718,47 @@ static const u32 pmbus_fan_status_flags[] = { }; /* Fans */ +static int pmbus_add_fan_ctrl(struct i2c_client *client, + struct pmbus_data *data, int index, int page, int id, + u8 config) +{ + struct pmbus_sensor *sensor; + int rv; + + rv = _pmbus_read_word_data(client, page, + pmbus_fan_command_registers[id]); + if (rv < 0) + return rv; + + sensor = pmbus_add_sensor(data, "fan", "target", index, page, + PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN, + true, false); + + if (!sensor) + return -ENOMEM; + + if (!data->info->m[PSC_PWM]) + return 0; + + sensor = pmbus_add_sensor(data, "pwm", NULL, index, page, + PMBUS_VIRT_PWM_1 + id, PSC_PWM, + true, false); + + if (!sensor) + return -ENOMEM; + + sensor = pmbus_add_sensor(data, "pwm", "enable", index, page, + PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM, + true, false); + + if (!sensor) + return -ENOMEM; + + sensor->convert = false; + + return 0; +} + static int pmbus_add_fan_attributes(struct i2c_client *client, struct pmbus_data *data) { @@ -1624,6 +1796,15 @@ static int pmbus_add_fan_attributes(struct i2c_client *client, PSC_FAN, true, true) == NULL) return -ENOMEM; + /* Fan control */ + if (pmbus_check_word_register(client, page, + pmbus_fan_command_registers[f])) { + ret = pmbus_add_fan_ctrl(client, data, index, + page, f, regval); + if (ret < 0) + return ret; + } + /* * Each fan status register covers multiple fans, * so we have to do some magic.