{"id":811313,"url":"http://patchwork.ozlabs.org/api/1.2/patches/811313/?format=json","web_url":"http://patchwork.ozlabs.org/project/openbmc/patch/20170908043919.6924-3-andrew@aj.id.au/","project":{"id":56,"url":"http://patchwork.ozlabs.org/api/1.2/projects/56/?format=json","name":"OpenBMC development","link_name":"openbmc","list_id":"openbmc.lists.ozlabs.org","list_email":"openbmc@lists.ozlabs.org","web_url":"http://github.com/openbmc/","scm_url":"","webscm_url":"","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<20170908043919.6924-3-andrew@aj.id.au>","list_archive_url":null,"date":"2017-09-08T04:39:18","name":"[v3,2/3] hwmon: pmbus: Add fan control support","commit_ref":null,"pull_url":null,"state":"not-applicable","archived":true,"hash":"66990862b19825ee708da9bc46f977247c1d2c6c","submitter":{"id":68332,"url":"http://patchwork.ozlabs.org/api/1.2/people/68332/?format=json","name":"Andrew Jeffery","email":"andrew@aj.id.au"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/openbmc/patch/20170908043919.6924-3-andrew@aj.id.au/mbox/","series":[{"id":2097,"url":"http://patchwork.ozlabs.org/api/1.2/series/2097/?format=json","web_url":"http://patchwork.ozlabs.org/project/openbmc/list/?series=2097","date":"2017-09-08T04:39:16","name":"pmbus: Expand fan support and add MAX31785 driver","version":3,"mbox":"http://patchwork.ozlabs.org/series/2097/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/811313/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/811313/checks/","tags":{},"related":[],"headers":{"Return-Path":"<openbmc-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org>","X-Original-To":["incoming@patchwork.ozlabs.org","openbmc@lists.ozlabs.org"],"Delivered-To":["patchwork-incoming@bilbo.ozlabs.org","openbmc@lists.ozlabs.org"],"Received":["from lists.ozlabs.org (lists.ozlabs.org [103.22.144.68])\n\t(using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xpPlx4Xnvz9s82\n\tfor <incoming@patchwork.ozlabs.org>;\n\tFri,  8 Sep 2017 14:41:17 +1000 (AEST)","from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3])\n\tby lists.ozlabs.org (Postfix) with ESMTP id 3xpPlx2sjyzDrZt\n\tfor <incoming@patchwork.ozlabs.org>;\n\tFri,  8 Sep 2017 14:41:17 +1000 (AEST)","from out1-smtp.messagingengine.com (out1-smtp.messagingengine.com\n\t[66.111.4.25])\n\t(using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby lists.ozlabs.org (Postfix) with ESMTPS id 3xpPkX1XmNzDrZ8\n\tfor <openbmc@lists.ozlabs.org>; Fri,  8 Sep 2017 14:40:04 +1000 (AEST)","from compute4.internal (compute4.nyi.internal [10.202.2.44])\n\tby mailout.nyi.internal (Postfix) with ESMTP id 10F1820C2C;\n\tFri,  8 Sep 2017 00:40:02 -0400 (EDT)","from frontend1 ([10.202.2.160])\n\tby compute4.internal (MEProxy); Fri, 08 Sep 2017 00:40:02 -0400","from keelia.ozlabs.ibm.com (unknown [122.99.82.10])\n\tby mail.messagingengine.com (Postfix) with ESMTPA id 46C9B7E426;\n\tFri,  8 Sep 2017 00:39:58 -0400 (EDT)"],"Authentication-Results":["ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=aj.id.au header.i=@aj.id.au header.b=\"QwiUxdOX\";\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com header.b=\"rH3mkp+L\"; \n\tdkim-atps=neutral","lists.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=aj.id.au header.i=@aj.id.au header.b=\"QwiUxdOX\";\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com header.b=\"rH3mkp+L\"; \n\tdkim-atps=neutral","ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=aj.id.au\n\t(client-ip=66.111.4.25; helo=out1-smtp.messagingengine.com;\n\tenvelope-from=andrew@aj.id.au; receiver=<UNKNOWN>)","lists.ozlabs.org; dkim=pass (2048-bit key;\n\tunprotected) header.d=aj.id.au header.i=@aj.id.au header.b=\"QwiUxdOX\";\n\tdkim=pass (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com\n\theader.b=\"rH3mkp+L\"; dkim-atps=neutral"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/relaxed; d=aj.id.au; h=cc\n\t:date:from:in-reply-to:message-id:references:subject:to\n\t:x-me-sender:x-me-sender:x-sasl-enc:x-sasl-enc; s=fm1; bh=C+sJz6\n\t62l2yuGXF4rsR+E0BEGQY3blUm2tfYJiYEq6s=; b=QwiUxdOX2LAdM6LX+O1v1O\n\tKB2Bgn/g5OZso9N+cHFT4KnKdzvVtUw8fOtx9mAy75k82IPkdpHbk8Qs30Ssp1Vo\n\tIwYUEzlUJxEoYYl1FXPO6anphG9TsoLB0qTTjAOc2hOzNhlG36aaycorNhkrF0Jo\n\ttH9b9q4nfbCJZPbW20bahtC3dhOn3FoiABhkm2oFaCUyHGaEjIzl5czgm6T8EYHm\n\tWbWYVVroXuKZn7jBXHeTDw4+fUirvwl1n2vuyqijLA5PooWEO3FyyIlaUCNoVHeE\n\tGZwjU++Sr59oq3oihus+6m9iQqtyRJY/CJO+Kv9AFT8BwRX8TOby84lhEcGQ0q2A\n\t==","v=1; a=rsa-sha256; c=relaxed/relaxed; d=\n\tmessagingengine.com; h=cc:date:from:in-reply-to:message-id\n\t:references:subject:to:x-me-sender:x-me-sender:x-sasl-enc\n\t:x-sasl-enc; s=fm1; bh=C+sJz662l2yuGXF4rsR+E0BEGQY3blUm2tfYJiYEq\n\t6s=; b=rH3mkp+LVnPuP0ThFaKVlA1QkpXcNL4AD1JETX7JwuaaEFva4MMhKxFqo\n\tcCiOikVKWVyueUS84DEaYFwpLVZDC+tIj4swl3DYAghzFc/rYBXk6Qx0NHjM9WMQ\n\tRASdVqC556Y93XHqAMsLKOAge1livD5POk03IJZpPf8uxGeEsizyd+x0b+F8eeZ/\n\tgCqBI32x4tKzk0HzyUApu8Li6bGWI46znYsn/QVV9gU+nk0pwk8ajvNW0TlKyfV/\n\t+TKzyU+L+Q4V5qpqiQYdVEU60uiV3RhV4TBOmVeMqHvcX8Ilko0KF5lQVCIk+IIK\n\tSoX4cthWhK53PQz5t2RIRBuG8ZZ1w=="],"X-ME-Sender":"<xms:IR-yWfvS1PxIeOhNZHMA3qkjBXP1YzGO9H8IbdzA-c24hxsD7lyaGw>","X-Sasl-enc":"St1blQtSRU1zrm6gyKrzu5HjDBqihvxUekI4fiyD54Al 1504845601","From":"Andrew Jeffery <andrew@aj.id.au>","To":"linux@roeck-us.net,\n\tlinux-hwmon@vger.kernel.org","Subject":"[PATCH v3 2/3] hwmon: pmbus: Add fan control support","Date":"Fri,  8 Sep 2017 14:39:18 +1000","Message-Id":"<20170908043919.6924-3-andrew@aj.id.au>","X-Mailer":"git-send-email 2.11.0","In-Reply-To":"<20170908043919.6924-1-andrew@aj.id.au>","References":"<20170908043919.6924-1-andrew@aj.id.au>","X-BeenThere":"openbmc@lists.ozlabs.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"Development list for OpenBMC <openbmc.lists.ozlabs.org>","List-Unsubscribe":"<https://lists.ozlabs.org/options/openbmc>,\n\t<mailto:openbmc-request@lists.ozlabs.org?subject=unsubscribe>","List-Archive":"<http://lists.ozlabs.org/pipermail/openbmc/>","List-Post":"<mailto:openbmc@lists.ozlabs.org>","List-Help":"<mailto:openbmc-request@lists.ozlabs.org?subject=help>","List-Subscribe":"<https://lists.ozlabs.org/listinfo/openbmc>,\n\t<mailto:openbmc-request@lists.ozlabs.org?subject=subscribe>","Cc":"mark.rutland@arm.com, devicetree@vger.kernel.org, jdelvare@suse.com,\n\tAndrew Jeffery <andrew@aj.id.au>, openbmc@lists.ozlabs.org,\n\tlinux-kernel@vger.kernel.org, robh+dt@kernel.org","Errors-To":"openbmc-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org","Sender":"\"openbmc\"\n\t<openbmc-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org>"},"content":"Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.\n\nFans in a PMBus device are driven by the configuration of two registers:\nFAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan\nand the tacho operate (if installed), while FAN_COMMAND_x sets the\ndesired fan rate. The unit of FAN_COMMAND_x is dependent on the\noperational fan mode, RPM or PWM percent duty, as determined by the\ncorresponding FAN_CONFIG_x_y.\n\nThe mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and\nFAN_COMMAND_x is implemented with the addition of virtual registers and\ngeneric implementations in the core:\n\n1. PMBUS_VIRT_FAN_TARGET_x\n2. PMBUS_VIRT_PWM_x\n3. PMBUS_VIRT_PWM_ENABLE_x\n\nThe virtual registers facilitate the necessary side-effects of each\naccess. Examples of the read case, assuming m = 1, b = 0, R = 0:\n\n             Read     |              With              || Gives\n         -------------------------------------------------------\n           Attribute  | FAN_CONFIG_x_y | FAN_COMMAND_y || Value\n         ----------------------------------------------++-------\n          fanX_target | ~PB_FAN_z_RPM  | 0x0001        || 1\n          pwm1        | ~PB_FAN_z_RPM  | 0x0064        || 255\n          pwmX_enable | ~PB_FAN_z_RPM  | 0x0001        || 1\n          fanX_target |  PB_FAN_z_RPM  | 0x0001        || 1\n          pwm1        |  PB_FAN_z_RPM  | 0x0064        || 0\n          pwmX_enable |  PB_FAN_z_RPM  | 0x0001        || 1\n\nAnd the write case:\n\n             Write    | With  ||               Sets\n         -------------+-------++----------------+---------------\n           Attribute  | Value || FAN_CONFIG_x_y | FAN_COMMAND_x\n         -------------+-------++----------------+---------------\n          fanX_target | 1     ||  PB_FAN_z_RPM  | 0x0001\n          pwmX        | 255   || ~PB_FAN_z_RPM  | 0x0064\n          pwmX_enable | 1     || ~PB_FAN_z_RPM  | 0x0064\n\nAlso, the DIRECT mode scaling of some controllers is different between\nRPM and PWM percent duty control modes, so PSC_PWM is introduced to\ncapture the necessary coefficients.\n\nSigned-off-by: Andrew Jeffery <andrew@aj.id.au>\n---\n drivers/hwmon/pmbus/pmbus.h      |  29 +++++\n drivers/hwmon/pmbus/pmbus_core.c | 224 ++++++++++++++++++++++++++++++++++++---\n 2 files changed, 238 insertions(+), 15 deletions(-)","diff":"diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h\nindex bfcb13bae34b..a863b8fed16f 100644\n--- a/drivers/hwmon/pmbus/pmbus.h\n+++ b/drivers/hwmon/pmbus/pmbus.h\n@@ -190,6 +190,28 @@ enum pmbus_regs {\n \tPMBUS_VIRT_VMON_UV_FAULT_LIMIT,\n \tPMBUS_VIRT_VMON_OV_FAULT_LIMIT,\n \tPMBUS_VIRT_STATUS_VMON,\n+\n+\t/*\n+\t * RPM and PWM Fan control\n+\t *\n+\t * Drivers wanting to expose PWM control must define the behaviour of\n+\t * PMBUS_VIRT_PWM_ENABLE_[1-4] in the {read,write}_word_data callback.\n+\t *\n+\t * pmbus core provides default implementations for\n+\t * PMBUS_VIRT_FAN_TARGET_[1-4] and PMBUS_VIRT_PWM_[1-4].\n+\t */\n+\tPMBUS_VIRT_FAN_TARGET_1,\n+\tPMBUS_VIRT_FAN_TARGET_2,\n+\tPMBUS_VIRT_FAN_TARGET_3,\n+\tPMBUS_VIRT_FAN_TARGET_4,\n+\tPMBUS_VIRT_PWM_1,\n+\tPMBUS_VIRT_PWM_2,\n+\tPMBUS_VIRT_PWM_3,\n+\tPMBUS_VIRT_PWM_4,\n+\tPMBUS_VIRT_PWM_ENABLE_1,\n+\tPMBUS_VIRT_PWM_ENABLE_2,\n+\tPMBUS_VIRT_PWM_ENABLE_3,\n+\tPMBUS_VIRT_PWM_ENABLE_4,\n };\n \n /*\n@@ -223,6 +245,8 @@ enum pmbus_regs {\n #define PB_FAN_1_RPM\t\t\tBIT(6)\n #define PB_FAN_1_INSTALLED\t\tBIT(7)\n \n+enum pmbus_fan_mode { percent = 0, rpm };\n+\n /*\n  * STATUS_BYTE, STATUS_WORD (lower)\n  */\n@@ -313,6 +337,7 @@ enum pmbus_sensor_classes {\n \tPSC_POWER,\n \tPSC_TEMPERATURE,\n \tPSC_FAN,\n+\tPSC_PWM,\n \tPSC_NUM_CLASSES\t\t/* Number of power sensor classes */\n };\n \n@@ -339,6 +364,8 @@ enum pmbus_sensor_classes {\n #define PMBUS_HAVE_STATUS_FAN34\tBIT(17)\n #define PMBUS_HAVE_VMON\t\tBIT(18)\n #define PMBUS_HAVE_STATUS_VMON\tBIT(19)\n+#define PMBUS_HAVE_PWM12\tBIT(20)\n+#define PMBUS_HAVE_PWM34\tBIT(21)\n \n enum pmbus_data_format { linear = 0, direct, vid };\n enum vrm_version { vr11 = 0, vr12 };\n@@ -413,6 +440,8 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg,\n \t\t\t  u8 value);\n int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,\n \t\t\t   u8 mask, u8 value);\n+int pmbus_update_fan(struct i2c_client *client, int page, int id,\n+\t\t     u8 config, u8 mask, u16 command);\n void pmbus_clear_faults(struct i2c_client *client);\n bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);\n bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);\ndiff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c\nindex f1eff6b6c798..5ed9cbf1daf9 100644\n--- a/drivers/hwmon/pmbus/pmbus_core.c\n+++ b/drivers/hwmon/pmbus/pmbus_core.c\n@@ -63,6 +63,7 @@ struct pmbus_sensor {\n \tu16 reg;\t\t/* register */\n \tenum pmbus_sensor_classes class;\t/* sensor class */\n \tbool update;\t\t/* runtime sensor update needed */\n+\tbool convert;\t\t/* Whether or not to apply linear/vid/direct */\n \tint data;\t\t/* Sensor data.\n \t\t\t\t   Negative if there was a read error */\n };\n@@ -118,6 +119,27 @@ struct pmbus_data {\n \tu8 currpage;\n };\n \n+static const int pmbus_fan_rpm_mask[] = {\n+\tPB_FAN_1_RPM,\n+\tPB_FAN_2_RPM,\n+\tPB_FAN_1_RPM,\n+\tPB_FAN_2_RPM,\n+};\n+\n+static const int pmbus_fan_config_registers[] = {\n+\tPMBUS_FAN_CONFIG_12,\n+\tPMBUS_FAN_CONFIG_12,\n+\tPMBUS_FAN_CONFIG_34,\n+\tPMBUS_FAN_CONFIG_34\n+};\n+\n+static const int pmbus_fan_command_registers[] = {\n+\tPMBUS_FAN_COMMAND_1,\n+\tPMBUS_FAN_COMMAND_2,\n+\tPMBUS_FAN_COMMAND_3,\n+\tPMBUS_FAN_COMMAND_4,\n+};\n+\n void pmbus_clear_cache(struct i2c_client *client)\n {\n \tstruct pmbus_data *data = i2c_get_clientdata(client);\n@@ -188,6 +210,31 @@ int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, u16 word)\n }\n EXPORT_SYMBOL_GPL(pmbus_write_word_data);\n \n+int pmbus_update_fan(struct i2c_client *client, int page, int id,\n+\t\t\t       u8 config, u8 mask, u16 command)\n+{\n+\tint from, rv;\n+\tu8 to;\n+\n+\tfrom = pmbus_read_byte_data(client, page,\n+\t\t\t\t    pmbus_fan_config_registers[id]);\n+\tif (from < 0)\n+\t\treturn from;\n+\n+\tto = (from & ~mask) | (config & mask);\n+\n+\tif (to != from) {\n+\t\trv = pmbus_write_byte_data(client, page,\n+\t\t\t\t\t   pmbus_fan_config_registers[id], to);\n+\t\tif (rv < 0)\n+\t\t\treturn rv;\n+\t}\n+\n+\treturn pmbus_write_word_data(client, page,\n+\t\t\t\t     pmbus_fan_command_registers[id], command);\n+}\n+EXPORT_SYMBOL_GPL(pmbus_update_fan);\n+\n /*\n  * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if\n  * a device specific mapping function exists and calls it if necessary.\n@@ -204,8 +251,40 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,\n \t\tif (status != -ENODATA)\n \t\t\treturn status;\n \t}\n-\tif (reg >= PMBUS_VIRT_BASE)\n-\t\treturn -ENXIO;\n+\tif (reg >= PMBUS_VIRT_BASE) {\n+\t\tint id, bit;\n+\n+\t\tswitch (reg) {\n+\t\tcase PMBUS_VIRT_FAN_TARGET_1:\n+\t\tcase PMBUS_VIRT_FAN_TARGET_2:\n+\t\tcase PMBUS_VIRT_FAN_TARGET_3:\n+\t\tcase PMBUS_VIRT_FAN_TARGET_4:\n+\t\t\tid = reg - PMBUS_VIRT_FAN_TARGET_1;\n+\t\t\tbit = pmbus_fan_rpm_mask[id];\n+\t\t\tstatus = pmbus_update_fan(client, page, id, bit, bit,\n+\t\t\t\t\t\t  word);\n+\t\t\tbreak;\n+\t\tcase PMBUS_VIRT_PWM_1:\n+\t\tcase PMBUS_VIRT_PWM_2:\n+\t\tcase PMBUS_VIRT_PWM_3:\n+\t\tcase PMBUS_VIRT_PWM_4:\n+\t\t{\n+\t\t\tu32 command = word;\n+\n+\t\t\tid = reg - PMBUS_VIRT_PWM_1;\n+\t\t\tbit = pmbus_fan_rpm_mask[id];\n+\t\t\tcommand *= 100;\n+\t\t\tcommand /= 255;\n+\t\t\tstatus = pmbus_update_fan(client, page, id, 0, bit,\n+\t\t\t\t\t\t  command);\n+\t\t\tbreak;\n+\t\t}\n+\t\tdefault:\n+\t\t\tstatus = -ENXIO;\n+\t\t\tbreak;\n+\t\t}\n+\t\treturn status;\n+\t}\n \treturn pmbus_write_word_data(client, page, reg, word);\n }\n \n@@ -221,6 +300,9 @@ int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg)\n }\n EXPORT_SYMBOL_GPL(pmbus_read_word_data);\n \n+static int pmbus_get_fan_command(struct i2c_client *client, int page, int id,\n+\t\t\t\t enum pmbus_fan_mode mode);\n+\n /*\n  * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if\n  * a device specific mapping function exists and calls it if necessary.\n@@ -236,8 +318,42 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)\n \t\tif (status != -ENODATA)\n \t\t\treturn status;\n \t}\n-\tif (reg >= PMBUS_VIRT_BASE)\n-\t\treturn -ENXIO;\n+\tif (reg >= PMBUS_VIRT_BASE) {\n+\t\tint id;\n+\n+\t\tswitch (reg) {\n+\t\tcase PMBUS_VIRT_FAN_TARGET_1:\n+\t\tcase PMBUS_VIRT_FAN_TARGET_2:\n+\t\tcase PMBUS_VIRT_FAN_TARGET_3:\n+\t\tcase PMBUS_VIRT_FAN_TARGET_4:\n+\t\t\tid = reg - PMBUS_VIRT_FAN_TARGET_1;\n+\t\t\tstatus = pmbus_get_fan_command(client, page, id, rpm);\n+\t\t\tbreak;\n+\t\tcase PMBUS_VIRT_PWM_1:\n+\t\tcase PMBUS_VIRT_PWM_2:\n+\t\tcase PMBUS_VIRT_PWM_3:\n+\t\tcase PMBUS_VIRT_PWM_4:\n+\t\t{\n+\t\t\tint rv;\n+\n+\t\t\tid = reg - PMBUS_VIRT_PWM_1;\n+\t\t\trv = pmbus_get_fan_command(client, page, id, percent);\n+\t\t\tif (rv < 0)\n+\t\t\t\treturn rv;\n+\n+\t\t\trv *= 255;\n+\t\t\trv /= 100;\n+\n+\t\t\tstatus = rv;\n+\t\t\tbreak;\n+\t\t}\n+\t\tdefault:\n+\t\t\tstatus = -ENXIO;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\treturn status;\n+\t}\n \treturn pmbus_read_word_data(client, page, reg);\n }\n \n@@ -304,6 +420,28 @@ static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg)\n \treturn pmbus_read_byte_data(client, page, reg);\n }\n \n+static int pmbus_get_fan_command(struct i2c_client *client, int page, int id,\n+\t\t\t\t enum pmbus_fan_mode mode)\n+{\n+\tint config;\n+\n+\tconfig = _pmbus_read_byte_data(client, page,\n+\t\t\t\t       pmbus_fan_config_registers[id]);\n+\tif (config < 0)\n+\t\treturn config;\n+\n+\t/*\n+\t * We can't meaningfully translate between PWM and RPM, so if the\n+\t * attribute mode (fan[1-*]_target is RPM, pwm[1-*] and pwm[1-*]_enable\n+\t * are PWM) doesn't match the hardware mode, then report 0 instead.\n+\t */\n+\tif ((mode == rpm) != (!!(config & pmbus_fan_rpm_mask[id])))\n+\t\treturn 0;\n+\n+\treturn _pmbus_read_word_data(client, page,\n+\t\t\t\t     pmbus_fan_command_registers[id]);\n+}\n+\n static void pmbus_clear_fault_page(struct i2c_client *client, int page)\n {\n \t_pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);\n@@ -489,7 +627,7 @@ static long pmbus_reg2data_direct(struct pmbus_data *data,\n \t/* X = 1/m * (Y * 10^-R - b) */\n \tR = -R;\n \t/* scale result to milli-units for everything but fans */\n-\tif (sensor->class != PSC_FAN) {\n+\tif (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {\n \t\tR += 3;\n \t\tb *= 1000;\n \t}\n@@ -539,6 +677,9 @@ static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor)\n {\n \tlong val;\n \n+\tif (!sensor->convert)\n+\t\treturn sensor->data;\n+\n \tswitch (data->info->format[sensor->class]) {\n \tcase direct:\n \t\tval = pmbus_reg2data_direct(data, sensor);\n@@ -642,7 +783,7 @@ static u16 pmbus_data2reg_direct(struct pmbus_data *data,\n \t}\n \n \t/* Calculate Y = (m * X + b) * 10^R */\n-\tif (sensor->class != PSC_FAN) {\n+\tif (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {\n \t\tR -= 3;\t\t/* Adjust R and b for data in milli-units */\n \t\tb *= 1000;\n \t}\n@@ -673,6 +814,9 @@ static u16 pmbus_data2reg(struct pmbus_data *data,\n {\n \tu16 regval;\n \n+\tif (!sensor->convert)\n+\t\treturn val;\n+\n \tswitch (data->info->format[sensor->class]) {\n \tcase direct:\n \t\tregval = pmbus_data2reg_direct(data, sensor, val);\n@@ -895,12 +1039,18 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data,\n \t\treturn NULL;\n \ta = &sensor->attribute;\n \n-\tsnprintf(sensor->name, sizeof(sensor->name), \"%s%d_%s\",\n-\t\t name, seq, type);\n+\tif (type)\n+\t\tsnprintf(sensor->name, sizeof(sensor->name), \"%s%d_%s\",\n+\t\t\t name, seq, type);\n+\telse\n+\t\tsnprintf(sensor->name, sizeof(sensor->name), \"%s%d\",\n+\t\t\t name, seq);\n+\n \tsensor->page = page;\n \tsensor->reg = reg;\n \tsensor->class = class;\n \tsensor->update = update;\n+\tsensor->convert = true;\n \tpmbus_dev_attr_init(a, sensor->name,\n \t\t\t    readonly ? S_IRUGO : S_IRUGO | S_IWUSR,\n \t\t\t    pmbus_show_sensor, pmbus_set_sensor);\n@@ -1558,13 +1708,6 @@ static const int pmbus_fan_registers[] = {\n \tPMBUS_READ_FAN_SPEED_4\n };\n \n-static const int pmbus_fan_config_registers[] = {\n-\tPMBUS_FAN_CONFIG_12,\n-\tPMBUS_FAN_CONFIG_12,\n-\tPMBUS_FAN_CONFIG_34,\n-\tPMBUS_FAN_CONFIG_34\n-};\n-\n static const int pmbus_fan_status_registers[] = {\n \tPMBUS_STATUS_FAN_12,\n \tPMBUS_STATUS_FAN_12,\n@@ -1587,6 +1730,48 @@ static const u32 pmbus_fan_status_flags[] = {\n };\n \n /* Fans */\n+static int pmbus_add_fan_ctrl(struct i2c_client *client,\n+\t\tstruct pmbus_data *data, int index, int page, int id,\n+\t\tu8 config)\n+{\n+\tstruct pmbus_sensor *sensor;\n+\tint rv;\n+\n+\trv = _pmbus_read_word_data(client, page,\n+\t\t\t\t   pmbus_fan_command_registers[id]);\n+\tif (rv < 0)\n+\t\treturn rv;\n+\n+\tsensor = pmbus_add_sensor(data, \"fan\", \"target\", index, page,\n+\t\t\t\t  PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN,\n+\t\t\t\t  true, false);\n+\n+\tif (!sensor)\n+\t\treturn -ENOMEM;\n+\n+\tif (!((data->info->func[page] & PMBUS_HAVE_PWM12) ||\n+\t\t\t(data->info->func[page] & PMBUS_HAVE_PWM34)))\n+\t\treturn 0;\n+\n+\tsensor = pmbus_add_sensor(data, \"pwm\", NULL, index, page,\n+\t\t\t\t  PMBUS_VIRT_PWM_1 + id, PSC_PWM,\n+\t\t\t\t  true, false);\n+\n+\tif (!sensor)\n+\t\treturn -ENOMEM;\n+\n+\tsensor = pmbus_add_sensor(data, \"pwm\", \"enable\", index, page,\n+\t\t\t\t  PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM,\n+\t\t\t\t  true, false);\n+\n+\tif (!sensor)\n+\t\treturn -ENOMEM;\n+\n+\tsensor->convert = false;\n+\n+\treturn 0;\n+}\n+\n static int pmbus_add_fan_attributes(struct i2c_client *client,\n \t\t\t\t    struct pmbus_data *data)\n {\n@@ -1624,6 +1809,15 @@ static int pmbus_add_fan_attributes(struct i2c_client *client,\n \t\t\t\t\t     PSC_FAN, true, true) == NULL)\n \t\t\t\treturn -ENOMEM;\n \n+\t\t\t/* Fan control */\n+\t\t\tif (pmbus_check_word_register(client, page,\n+\t\t\t\t\tpmbus_fan_command_registers[f])) {\n+\t\t\t\tret = pmbus_add_fan_ctrl(client, data, index,\n+\t\t\t\t\t\t\t page, f, regval);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\treturn ret;\n+\t\t\t}\n+\n \t\t\t/*\n \t\t\t * Each fan status register covers multiple fans,\n \t\t\t * so we have to do some magic.\n","prefixes":["v3","2/3"]}