Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/811314/?format=api
{ "id": 811314, "url": "http://patchwork.ozlabs.org/api/patches/811314/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openbmc/patch/20170908043919.6924-4-andrew@aj.id.au/", "project": { "id": 56, "url": "http://patchwork.ozlabs.org/api/projects/56/?format=api", "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-4-andrew@aj.id.au>", "list_archive_url": null, "date": "2017-09-08T04:39:19", "name": "[v3,3/3] pmbus: Add driver for Maxim MAX31785 Intelligent Fan Controller", "commit_ref": null, "pull_url": null, "state": "not-applicable", "archived": true, "hash": "2a2b4edc23ad2750f8ce16a84a183c0fd5c39d12", "submitter": { "id": 68332, "url": "http://patchwork.ozlabs.org/api/people/68332/?format=api", "name": "Andrew Jeffery", "email": "andrew@aj.id.au" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/openbmc/patch/20170908043919.6924-4-andrew@aj.id.au/mbox/", "series": [ { "id": 2097, "url": "http://patchwork.ozlabs.org/api/series/2097/?format=api", "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/811314/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/811314/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 [IPv6:2401:3900:2:1::3])\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 3xpPmK5kgSz9sCZ\n\tfor <incoming@patchwork.ozlabs.org>;\n\tFri, 8 Sep 2017 14:41:37 +1000 (AEST)", "from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3])\n\tby lists.ozlabs.org (Postfix) with ESMTP id 3xpPmK2tKSzDqZx\n\tfor <incoming@patchwork.ozlabs.org>;\n\tFri, 8 Sep 2017 14:41:37 +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 3xpPkc23BNzDqZx\n\tfor <openbmc@lists.ozlabs.org>; Fri, 8 Sep 2017 14:40:08 +1000 (AEST)", "from compute4.internal (compute4.nyi.internal [10.202.2.44])\n\tby mailout.nyi.internal (Postfix) with ESMTP id 2679920DA9;\n\tFri, 8 Sep 2017 00:40:06 -0400 (EDT)", "from frontend1 ([10.202.2.160])\n\tby compute4.internal (MEProxy); Fri, 08 Sep 2017 00:40:06 -0400", "from keelia.ozlabs.ibm.com (unknown [122.99.82.10])\n\tby mail.messagingengine.com (Postfix) with ESMTPA id 5060B7E426;\n\tFri, 8 Sep 2017 00:40:02 -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=\"MJlActl0\";\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com header.b=\"OfGJINvJ\"; \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=\"MJlActl0\";\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com header.b=\"OfGJINvJ\"; \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=\"MJlActl0\";\n\tdkim=pass (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com\n\theader.b=\"OfGJINvJ\"; 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=dFn6OC\n\t6KYERs+VCxRcuJe/B/I1U8284QzUgT9FR4V4E=; b=MJlActl0na51M7Kf9c5qSe\n\tWdJ/7L/yMKU6YGUw2fCyBlN/4B2aHGL9bpNw+p2jKXfMknZIbAzbeLKoNf7WjRxS\n\tUU96JMyLQDBWFPVgSfT3eUKQQeCaaloEGcpOIlw5KBsfZEzo2PUMEQiU3sQMxkKK\n\tF6QRYXsi64YMT/u1SDV+NFONc4Ikitp2+kDVxwG5Zc/R7l2TF/qLS/hYxCGpInU1\n\tFZ/rFEilL/Jb2XgXzMru2I5+z9zwZRurCzfEIQx5vDTfria3VK0UJ22qcjd/+mPT\n\tGZntTfeC4+RHaO3PqI7CMytp6Vhxa8X0xG4PRbvNCiqhUWKZOrr9JOYjZs7kA3FQ\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=dFn6OC6KYERs+VCxRcuJe/B/I1U8284QzUgT9FR4V\n\t4E=; b=OfGJINvJoGvFn/GfU31wynx2XVnInIv1duKHwlpszfuhNtfq84wlEDnzq\n\tQXhSgh4pfUYNDYQZ5hEbh7skfLXrrYsr1OzZZMVqWk9eUdwKF/xsdX5Pbal1i46E\n\tmskG4rsrhpco6I8EP4e5dWDXi8UF4/ov7aMOqx49CKEBKgLh28kA/Rt/Xd43wxlr\n\tAVsTlEiGLt0pVhdHlfxpwwVJWsshjhp7IqLCASDkLVXDj0xpkT+QDmGnktD9BK9E\n\t4445XcQ7q+K37RE9qO4Y2LFeijaO5kk31JWXkjL3nN9Yzd/kojys7ewzbr9PMaW+\n\tM3VGYnd9w/NnFEJ2TvucgLHMWgwjg==" ], "X-ME-Sender": "<xms:Jh-yWbFUVh3ZfVvgsE6WgbbH-Atz6jNWKdH8pwSUYEKUkJDqxdno-w>", "X-Sasl-enc": "WtaQqv7bPZoW6G4SkRd0vioy5KIMYwlCtNTIhcles3hP 1504845605", "From": "Andrew Jeffery <andrew@aj.id.au>", "To": "linux@roeck-us.net,\n\tlinux-hwmon@vger.kernel.org", "Subject": "[PATCH v3 3/3] pmbus: Add driver for Maxim MAX31785 Intelligent Fan\n\tController", "Date": "Fri, 8 Sep 2017 14:39:19 +1000", "Message-Id": "<20170908043919.6924-4-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": "The Maxim MAX31785 is a PMBus device providing closed-loop,\nmulti-channel fan management with temperature and remote voltage\nsensing. Various fan control features are provided, including PWM\nfrequency control, temperature hysteresis, dual tachometer measurements,\nand fan health monitoring.\n\nThe driver implementation makes use of the new fan control virtual\nregisters exposed by the pmbus core. It mixes use of the default\nimplementations with some overrides via the read/write handlers to\nhandle FAN_COMMAND_1 on the MAX31785, whose definition breaks the value\nrange into various control bands dependent on RPM or PWM mode.\n\nThe dual tachometer feature is implemented in hardware with a TACHSEL\ninput to indicate the rotor under measurement, and exposed on the bus by\nextending the READ_FAN_SPEED_1 word with two extra bytes*.\nThe need to read the non-standard four-byte response leads to a cut-down\nimplementation of i2c_smbus_xfer_emulated() included in the driver.\nFurther, to expose the second rotor tachometer value to userspace,\nvirtual fans are implemented by re-routing the FAN_CONFIG_1_2 register\nfrom the undefined (in hardware) pages 23-28 to the same register on\npages 0-5, and similarly re-routing READ_FAN_SPEED_1 requests on 23-28\nto pages 0-5 but extracting the second word of the four-byte response.\n\n* The documentation recommends the slower rotor be associated with\nTACHSEL=0, which provides the first word of the response. The TACHSEL=0\nmeasurement is used by the controller's closed-loop fan management.\n\nSigned-off-by: Andrew Jeffery <andrew@aj.id.au>\n---\n drivers/hwmon/pmbus/Kconfig | 10 +\n drivers/hwmon/pmbus/Makefile | 1 +\n drivers/hwmon/pmbus/max31785.c | 673 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 684 insertions(+)\n create mode 100644 drivers/hwmon/pmbus/max31785.c", "diff": "diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig\nindex 68d717a3fd59..04f6baa98a14 100644\n--- a/drivers/hwmon/pmbus/Kconfig\n+++ b/drivers/hwmon/pmbus/Kconfig\n@@ -105,6 +105,16 @@ config SENSORS_MAX20751\n \t This driver can also be built as a module. If so, the module will\n \t be called max20751.\n \n+config SENSORS_MAX31785\n+\ttristate \"Maxim MAX31785 and compatibles\"\n+\tdefault n\n+\thelp\n+\t If you say yes here you get hardware monitoring support for Maxim\n+\t MAX31785.\n+\n+\t This driver can also be built as a module. If so, the module will\n+\t be called max31785.\n+\n config SENSORS_MAX34440\n \ttristate \"Maxim MAX34440 and compatibles\"\n \tdefault n\ndiff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile\nindex 75bb7ca619d9..663801c57a82 100644\n--- a/drivers/hwmon/pmbus/Makefile\n+++ b/drivers/hwmon/pmbus/Makefile\n@@ -11,6 +11,7 @@ obj-$(CONFIG_SENSORS_LTC2978)\t+= ltc2978.o\n obj-$(CONFIG_SENSORS_LTC3815)\t+= ltc3815.o\n obj-$(CONFIG_SENSORS_MAX16064)\t+= max16064.o\n obj-$(CONFIG_SENSORS_MAX20751)\t+= max20751.o\n+obj-$(CONFIG_SENSORS_MAX31785)\t+= max31785.o\n obj-$(CONFIG_SENSORS_MAX34440)\t+= max34440.o\n obj-$(CONFIG_SENSORS_MAX8688)\t+= max8688.o\n obj-$(CONFIG_SENSORS_TPS40422)\t+= tps40422.o\ndiff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c\nnew file mode 100644\nindex 000000000000..a83f59ca31c7\n--- /dev/null\n+++ b/drivers/hwmon/pmbus/max31785.c\n@@ -0,0 +1,673 @@\n+/*\n+ * Copyright (C) 2017 IBM Corp.\n+ *\n+ * This program is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License as published by\n+ * the Free Software Foundation; either version 2 of the License, or\n+ * (at your option) any later version.\n+ */\n+\n+#include <linux/kernel.h>\n+#include <linux/module.h>\n+#include <linux/init.h>\n+#include <linux/err.h>\n+#include <linux/i2c.h>\n+#include \"pmbus.h\"\n+\n+enum max31785_regs {\n+\tMFR_REVISION\t\t= 0x9b,\n+\tMFR_FAULT_RESPONSE\t= 0xd9,\n+\tMFR_TEMP_SENSOR_CONFIG\t= 0xf0,\n+\tMFR_FAN_CONFIG\t\t= 0xf1,\n+\tMFR_READ_FAN_PWM\t= 0xf3,\n+\tMFR_FAN_FAULT_LIMIT\t= 0xf5,\n+\tMFR_FAN_WARN_LIMIT\t= 0xf6,\n+\tMFR_FAN_PWM_AVG\t\t= 0xf8,\n+};\n+\n+#define MAX31785\t\t\t0x3030\n+#define MAX31785A\t\t\t0x3040\n+\n+#define MFR_TEMP_SENSOR_CONFIG_ENABLE\tBIT(15)\n+#define MFR_TEMP_SENSOR_CONFIG_OFFSET\tGENMASK(14, 10)\n+\n+#define MFR_FAN_CONFIG_FREQ\t\tGENMASK(15, 13)\n+#define MFR_FAN_CONFIG_DUAL_TACH\tBIT(12)\n+#define MFR_FAN_CONFIG_HYS\t\tGENMASK(11, 10)\n+#define MFR_FAN_CONFIG_TSFO\t\tBIT(9)\n+#define MFR_FAN_CONFIG_TACHO\t\tBIT(8)\n+#define MFR_FAN_CONFIG_RAMP\t\tGENMASK(7, 5)\n+#define MFR_FAN_CONFIG_HEALTH\t\tBIT(4)\n+#define MFR_FAN_CONFIG_ROTOR_HI_LO\tBIT(3)\n+#define MFR_FAN_CONFIG_ROTOR\t\tBIT(2)\n+#define MFR_FAN_CONFIG_SPIN\t\tGENMASK(1, 0)\n+\n+#define MFR_FAULT_RESPONSE_MONITOR\tBIT(0)\n+\n+#define MAX31785_CAP_READ_DUAL_TACH\tBIT(0)\n+\n+#define MAX31785_NR_PAGES\t\t23\n+\n+static int max31785_read_byte_data(struct i2c_client *client, int page,\n+\t\t\t\t int reg)\n+{\n+\tswitch (reg) {\n+\tcase PMBUS_VOUT_MODE:\n+\t\tif (page < MAX31785_NR_PAGES)\n+\t\t\treturn -ENODATA;\n+\n+\t\treturn -ENOTSUPP;\n+\tcase PMBUS_FAN_CONFIG_12:\n+\t\tif (page < MAX31785_NR_PAGES)\n+\t\t\treturn -ENODATA;\n+\n+\t\treturn pmbus_read_byte_data(client, page - MAX31785_NR_PAGES,\n+\t\t\t\t\t reg);\n+\t}\n+\n+\treturn -ENODATA;\n+}\n+\n+static int max31785_write_byte(struct i2c_client *client, int page, u8 value)\n+{\n+\tif (page < MAX31785_NR_PAGES)\n+\t\treturn -ENODATA;\n+\n+\treturn -ENOTSUPP;\n+}\n+\n+static int max31785_read_long_data(struct i2c_client *client, int page,\n+\t\t\t\t int reg, u32 *data)\n+{\n+\tunsigned char cmdbuf[1];\n+\tunsigned char rspbuf[4];\n+\tint rc;\n+\n+\tstruct i2c_msg msg[2] = {\n+\t\t{\n+\t\t\t.addr = client->addr,\n+\t\t\t.flags = 0,\n+\t\t\t.len = sizeof(cmdbuf),\n+\t\t\t.buf = cmdbuf,\n+\t\t},\n+\t\t{\n+\t\t\t.addr = client->addr,\n+\t\t\t.flags = I2C_M_RD,\n+\t\t\t.len = sizeof(rspbuf),\n+\t\t\t.buf = rspbuf,\n+\t\t},\n+\t};\n+\n+\tcmdbuf[0] = reg;\n+\n+\trc = pmbus_set_page(client, page);\n+\tif (rc < 0)\n+\t\treturn rc;\n+\n+\trc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));\n+\tif (rc < 0)\n+\t\treturn rc;\n+\n+\t*data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) |\n+\t\t(rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8));\n+\n+\treturn rc;\n+}\n+\n+static int max31785_get_pwm(struct i2c_client *client, int page)\n+{\n+\tint config;\n+\tint command;\n+\n+\tconfig = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12);\n+\tif (config < 0)\n+\t\treturn config;\n+\n+\tcommand = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1);\n+\tif (command < 0)\n+\t\treturn command;\n+\n+\tif (!(config & PB_FAN_1_RPM)) {\n+\t\tif (command >= 0x8000)\n+\t\t\treturn 0;\n+\t\telse if (command >= 0x2711)\n+\t\t\treturn 0x2710;\n+\n+\t\treturn command;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int max31785_get_pwm_mode(struct i2c_client *client, int page)\n+{\n+\tint config;\n+\tint command;\n+\n+\tconfig = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12);\n+\tif (config < 0)\n+\t\treturn config;\n+\n+\tcommand = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1);\n+\tif (command < 0)\n+\t\treturn command;\n+\n+\tif (!(config & PB_FAN_1_RPM)) {\n+\t\tif (command >= 0x8000)\n+\t\t\treturn 2;\n+\t\telse if (command >= 0x2711)\n+\t\t\treturn 0;\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn (command >= 0x8000) ? 2 : 1;\n+}\n+\n+static int max31785_read_word_data(struct i2c_client *client, int page,\n+\t\t\t\t int reg)\n+{\n+\tint rv;\n+\n+\tswitch (reg) {\n+\tcase PMBUS_READ_FAN_SPEED_1:\n+\t{\n+\t\tu32 val;\n+\n+\t\tif (page < MAX31785_NR_PAGES)\n+\t\t\treturn -ENODATA;\n+\n+\t\trv = max31785_read_long_data(client, page - MAX31785_NR_PAGES,\n+\t\t\t\t\t reg, &val);\n+\t\tif (rv < 0)\n+\t\t\treturn rv;\n+\n+\t\trv = (val >> 16) & 0xffff;\n+\t\tbreak;\n+\t}\n+\tcase PMBUS_VIRT_PWM_1:\n+\t\tif (page >= MAX31785_NR_PAGES)\n+\t\t\treturn -ENOTSUPP;\n+\n+\t\trv = max31785_get_pwm(client, page);\n+\t\tif (rv < 0)\n+\t\t\treturn rv;\n+\n+\t\trv *= 255;\n+\t\trv /= 100;\n+\t\tbreak;\n+\tcase PMBUS_VIRT_PWM_ENABLE_1:\n+\t\tif (page >= MAX31785_NR_PAGES)\n+\t\t\treturn -ENOTSUPP;\n+\n+\t\trv = max31785_get_pwm_mode(client, page);\n+\t\tbreak;\n+\tdefault:\n+\t\trv = (page >= MAX31785_NR_PAGES) ? -ENXIO : -ENODATA;\n+\t\tbreak;\n+\t}\n+\n+\treturn rv;\n+}\n+\n+static const int max31785_pwm_modes[] = { 0x7fff, 0x2710, 0xffff };\n+\n+static int max31785_write_word_data(struct i2c_client *client, int page,\n+\t\t\t\t int reg, u16 word)\n+{\n+\tif (page >= MAX31785_NR_PAGES)\n+\t\treturn -ENXIO;\n+\n+\tswitch (reg) {\n+\tcase PMBUS_VIRT_PWM_ENABLE_1:\n+\t\tif (word >= ARRAY_SIZE(max31785_pwm_modes))\n+\t\t\treturn -EINVAL;\n+\n+\t\treturn pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM,\n+\t\t\t\t\tmax31785_pwm_modes[word]);\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn -ENODATA;\n+}\n+\n+/*\n+ * Returns negative error codes if an unrecoverable problem is detected, 0 if a\n+ * recoverable problem is detected, or a positive value on success.\n+ */\n+static int max31785_of_fan_config(struct i2c_client *client,\n+\t\t\t\t struct pmbus_driver_info *info,\n+\t\t\t\t u32 capabilities, struct device_node *child)\n+{\n+\tint mfr_cfg = 0, mfr_fault_resp = 0, pb_cfg;\n+\tstruct device *dev = &client->dev;\n+\tchar *lock_polarity = NULL;\n+\tconst char *sval;\n+\tu32 page;\n+\tu32 uval;\n+\tint ret;\n+\n+\tif (!of_device_is_compatible(child, \"pmbus-fan\"))\n+\t\treturn 0;\n+\n+\tret = of_property_read_u32(child, \"reg\", &page);\n+\tif (ret < 0) {\n+\t\tdev_err(&client->dev, \"Missing valid reg property\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tif (!(info->func[page] & PMBUS_HAVE_FAN12)) {\n+\t\tdev_err(dev, \"Page %d does not have fan capabilities\\n\", page);\n+\t\treturn -ENXIO;\n+\t}\n+\n+\tret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tpb_cfg = i2c_smbus_read_byte_data(client, PMBUS_FAN_CONFIG_12);\n+\tif (pb_cfg < 0)\n+\t\treturn pb_cfg;\n+\n+\tif (of_property_read_bool(child->parent, \"use-stored-presence\")) {\n+\t\tif (!(pb_cfg & PB_FAN_1_INSTALLED))\n+\t\t\tdev_info(dev, \"Fan %d is configured but not installed\\n\",\n+\t\t\t\t page);\n+\t} else {\n+\t\tpb_cfg |= PB_FAN_1_INSTALLED;\n+\t}\n+\n+\tret = of_property_read_string(child, \"maxim,fan-rotor-input\", &sval);\n+\tif (ret < 0) {\n+\t\tdev_err(dev, \"Missing valid maxim,fan-rotor-input property for fan %d\\n\",\n+\t\t\t\tpage);\n+\t\treturn ret;\n+\t}\n+\n+\tif (strcmp(\"tach\", sval) && strcmp(\"lock\", sval)) {\n+\t\tdev_err(dev, \"maxim,fan-rotor-input has invalid value for fan %d: %s\\n\",\n+\t\t\t\tpage, sval);\n+\t\treturn -EINVAL;\n+\t} else if (!strcmp(\"lock\", sval)) {\n+\t\tmfr_cfg |= MFR_FAN_CONFIG_ROTOR;\n+\n+\t\tret = i2c_smbus_write_word_data(client, MFR_FAN_FAULT_LIMIT, 1);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\n+\t\tret = of_property_read_string(child, \"maxim,fan-lock-polarity\",\n+\t\t\t\t\t &sval);\n+\t\tif (ret < 0) {\n+\t\t\tdev_err(dev, \"Missing valid maxim,fan-lock-polarity property for fan %d\\n\",\n+\t\t\t\t\tpage);\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tif (strcmp(\"low\", sval) && strcmp(\"high\", sval)) {\n+\t\t\tdev_err(dev, \"maxim,fan-lock-polarity has invalid value for fan %d: %s\\n\",\n+\t\t\t\t\tpage, lock_polarity);\n+\t\t\treturn -EINVAL;\n+\t\t} else if (!strcmp(\"high\", sval))\n+\t\t\tmfr_cfg |= MFR_FAN_CONFIG_ROTOR_HI_LO;\n+\t}\n+\n+\tif (!of_property_read_string(child, \"fan-mode\", &sval)) {\n+\t\tif (!strcmp(\"rpm\", sval))\n+\t\t\tpb_cfg |= PB_FAN_1_RPM;\n+\t\telse if (!strcmp(\"pwm\", sval))\n+\t\t\tpb_cfg &= ~PB_FAN_1_RPM;\n+\t\telse {\n+\t\t\tdev_err(dev, \"fan-mode has invalid value for fan %d: %s\\n\",\n+\t\t\t\t\tpage, sval);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tret = of_property_read_u32(child, \"tach-pulses\", &uval);\n+\tif (ret < 0) {\n+\t\tpb_cfg &= ~PB_FAN_1_PULSE_MASK;\n+\t} else if (uval && (uval - 1) < 4) {\n+\t\tpb_cfg = ((pb_cfg & ~PB_FAN_1_PULSE_MASK) | ((uval - 1) << 4));\n+\t} else {\n+\t\tdev_err(dev, \"tach-pulses has invalid value for fan %d: %u\\n\",\n+\t\t\t\tpage, uval);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (of_property_read_bool(child, \"maxim,fan-health\"))\n+\t\tmfr_cfg |= MFR_FAN_CONFIG_HEALTH;\n+\n+\tif (of_property_read_bool(child, \"maxim,fan-no-watchdog\") ||\n+\t\tof_property_read_bool(child, \"maxim,tmp-no-fault-ramp\"))\n+\t\tmfr_cfg |= MFR_FAN_CONFIG_TSFO;\n+\n+\tif (of_property_read_bool(child, \"maxim,fan-dual-tach\")) {\n+\t\tmfr_cfg |= MFR_FAN_CONFIG_DUAL_TACH;\n+\n+\t\tif (capabilities & MAX31785_CAP_READ_DUAL_TACH) {\n+\t\t\tint virtual = MAX31785_NR_PAGES + page;\n+\n+\t\t\tinfo->pages = max(info->pages, virtual + 1);\n+\t\t\tinfo->func[virtual] |= PMBUS_HAVE_FAN12;\n+\t\t}\n+\t}\n+\n+\tif (of_property_read_bool(child, \"maxim,fan-no-fault-ramp\"))\n+\t\tmfr_cfg |= MFR_FAN_CONFIG_TACHO;\n+\n+\tif (!of_property_read_u32(child, \"maxim,fan-startup\", &uval)) {\n+\t\tuval /= 2;\n+\t\tif (uval < 5) {\n+\t\t\tmfr_cfg |= uval;\n+\t\t} else {\n+\t\t\tdev_err(dev, \"maxim,fan-startup has invalid value for fan %d: %u\\n\",\n+\t\t\t\t\tpage, uval);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tif (!of_property_read_u32(child, \"maxim,fan-ramp\", &uval)) {\n+\t\tif (uval < 8) {\n+\t\t\tmfr_cfg |= uval << 5;\n+\t\t} else {\n+\t\t\tdev_err(dev, \"maxim,fan-ramp has invalid value for fan %d: %u\\n\",\n+\t\t\t\t\tpage, uval);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tif (!of_property_read_u32(child, \"maxim,tmp-hysteresis\", &uval)) {\n+\t\tuval /= 2;\n+\t\tuval -= 1;\n+\t\tif (uval < 4) {\n+\t\t\tmfr_cfg |= uval << 10;\n+\t\t} else {\n+\t\t\tdev_err(dev, \"maxim,tmp-hysteresis has invalid value for fan %d, %u\\n\",\n+\t\t\t\t\tpage, uval);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tif (!of_property_read_u32(child, \"maxim,fan-pwm-freq\", &uval)) {\n+\t\tu16 val;\n+\n+\t\tif (uval == 30) {\n+\t\t\tval = 0;\n+\t\t} else if (uval == 50) {\n+\t\t\tval = 1;\n+\t\t} else if (uval == 100) {\n+\t\t\tval = 2;\n+\t\t} else if (uval == 150) {\n+\t\t\tval = 3;\n+\t\t} else if (uval == 25000) {\n+\t\t\tval = 7;\n+\t\t} else {\n+\t\t\tdev_err(dev, \"maxim,fan-pwm-freq has invalid value for fan %d: %u\\n\",\n+\t\t\t\t\tpage, uval);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tmfr_cfg |= val << 13;\n+\t}\n+\n+\tif (of_property_read_bool(child, \"maxim,fan-fault-pin-mon\"))\n+\t\tmfr_fault_resp |= MFR_FAULT_RESPONSE_MONITOR;\n+\n+\tret = i2c_smbus_write_byte_data(client, PMBUS_FAN_CONFIG_12,\n+\t\t\t\t\tpb_cfg & ~PB_FAN_1_INSTALLED);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = i2c_smbus_write_word_data(client, MFR_FAN_CONFIG, mfr_cfg);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = i2c_smbus_write_byte_data(client, MFR_FAULT_RESPONSE,\n+\t\t\t\t\tmfr_fault_resp);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = i2c_smbus_write_byte_data(client, PMBUS_FAN_CONFIG_12, pb_cfg);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Fans are on pages 0 - 5. If the page property of a fan node is\n+\t * greater than 5 we will have errored in checks above out above.\n+\t * Therefore we don't need to cope with values up to 31, and the int\n+\t * return type is enough.\n+\t *\n+\t * The bit mask return value is used to populate a bitfield of fans\n+\t * who are both configured in the devicetree _and_ reported as\n+\t * installed by the hardware. Any fans that are not configured in the\n+\t * devicetree but are reported as installed by the hardware will have\n+\t * their hardware configuration updated to unset the installed bit.\n+\t */\n+\treturn BIT(page);\n+}\n+\n+static int max31785_of_tmp_config(struct i2c_client *client,\n+\t\t\t\t struct pmbus_driver_info *info,\n+\t\t\t\t struct device_node *child)\n+{\n+\tstruct device *dev = &client->dev;\n+\tstruct device_node *np;\n+\tu16 mfr_tmp_cfg = 0;\n+\tu32 page;\n+\tu32 uval;\n+\tint ret;\n+\tint i;\n+\n+\tif (!of_device_is_compatible(child, \"pmbus-temperature\"))\n+\t\treturn 0;\n+\n+\tret = of_property_read_u32(child, \"reg\", &page);\n+\tif (ret < 0) {\n+\t\tdev_err(&client->dev, \"Missing valid reg property\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tif (!(info->func[page] & PMBUS_HAVE_TEMP)) {\n+\t\tdev_err(dev, \"Page %d does not have temp capabilities\\n\", page);\n+\t\treturn -ENXIO;\n+\t}\n+\n+\tret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tif (!of_property_read_u32(child, \"maxim,tmp-offset\", &uval)) {\n+\t\tif (uval < 32)\n+\t\t\tmfr_tmp_cfg |= uval << 10;\n+\t}\n+\n+\ti = 0;\n+\twhile ((np = of_parse_phandle(child, \"maxim,tmp-fans\", i))) {\n+\t\tif (of_property_read_u32(np, \"reg\", &uval)) {\n+\t\t\tdev_err(&client->dev, \"Failed to read fan reg property for phandle index %d\\n\",\n+\t\t\t\t\ti);\n+\t\t} else {\n+\t\t\tif (uval < 6)\n+\t\t\t\tmfr_tmp_cfg |= BIT(uval);\n+\t\t\telse\n+\t\t\t\tdev_warn(&client->dev, \"Invalid fan page: %d\\n\",\n+\t\t\t\t\t\tuval);\n+\t\t}\n+\t\ti++;\n+\t}\n+\n+\tret = i2c_smbus_write_word_data(client, MFR_TEMP_SENSOR_CONFIG,\n+\t\t\t\t\tmfr_tmp_cfg);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+#define MAX31785_FAN_FUNCS \\\n+\t(PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_PWM12)\n+\n+#define MAX31785_TEMP_FUNCS \\\n+\t(PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP)\n+\n+#define MAX31785_VOUT_FUNCS \\\n+\t(PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT)\n+\n+static const struct pmbus_driver_info max31785_info = {\n+\t.pages = MAX31785_NR_PAGES,\n+\n+\t.write_word_data = max31785_write_word_data,\n+\t.read_byte_data = max31785_read_byte_data,\n+\t.read_word_data = max31785_read_word_data,\n+\t.write_byte = max31785_write_byte,\n+\n+\t/* RPM */\n+\t.format[PSC_FAN] = direct,\n+\t.m[PSC_FAN] = 1,\n+\t.b[PSC_FAN] = 0,\n+\t.R[PSC_FAN] = 0,\n+\t/* PWM */\n+\t.format[PSC_PWM] = direct,\n+\t.m[PSC_PWM] = 1,\n+\t.b[PSC_PWM] = 0,\n+\t.R[PSC_PWM] = 2,\n+\t.func[0] = MAX31785_FAN_FUNCS,\n+\t.func[1] = MAX31785_FAN_FUNCS,\n+\t.func[2] = MAX31785_FAN_FUNCS,\n+\t.func[3] = MAX31785_FAN_FUNCS,\n+\t.func[4] = MAX31785_FAN_FUNCS,\n+\t.func[5] = MAX31785_FAN_FUNCS,\n+\n+\t.format[PSC_TEMPERATURE] = direct,\n+\t.m[PSC_TEMPERATURE] = 1,\n+\t.b[PSC_TEMPERATURE] = 0,\n+\t.R[PSC_TEMPERATURE] = 2,\n+\t.func[6] = MAX31785_TEMP_FUNCS,\n+\t.func[7] = MAX31785_TEMP_FUNCS,\n+\t.func[8] = MAX31785_TEMP_FUNCS,\n+\t.func[9] = MAX31785_TEMP_FUNCS,\n+\t.func[10] = MAX31785_TEMP_FUNCS,\n+\t.func[11] = MAX31785_TEMP_FUNCS,\n+\t.func[12] = MAX31785_TEMP_FUNCS,\n+\t.func[13] = MAX31785_TEMP_FUNCS,\n+\t.func[14] = MAX31785_TEMP_FUNCS,\n+\t.func[15] = MAX31785_TEMP_FUNCS,\n+\t.func[16] = MAX31785_TEMP_FUNCS,\n+\n+\t.format[PSC_VOLTAGE_OUT] = direct,\n+\t.m[PSC_VOLTAGE_OUT] = 1,\n+\t.b[PSC_VOLTAGE_OUT] = 0,\n+\t.R[PSC_VOLTAGE_OUT] = 0,\n+\t.func[17] = MAX31785_VOUT_FUNCS,\n+\t.func[18] = MAX31785_VOUT_FUNCS,\n+\t.func[19] = MAX31785_VOUT_FUNCS,\n+\t.func[20] = MAX31785_VOUT_FUNCS,\n+\t.func[21] = MAX31785_VOUT_FUNCS,\n+\t.func[22] = MAX31785_VOUT_FUNCS,\n+};\n+\n+static int max31785_probe(struct i2c_client *client,\n+\t\t\t const struct i2c_device_id *id)\n+{\n+\tstruct device *dev = &client->dev;\n+\tstruct device_node *child;\n+\tstruct pmbus_driver_info *info;\n+\tu32 fans;\n+\tu32 caps;\n+\ts64 ret;\n+\tint i;\n+\n+\tinfo = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL);\n+\tif (!info)\n+\t\treturn -ENOMEM;\n+\n+\t*info = max31785_info;\n+\n+\tret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = i2c_smbus_read_word_data(client, MFR_REVISION);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tcaps = 0;\n+\tif (!strcmp(\"max31785a\", id->name)) {\n+\t\tif (ret == MAX31785A)\n+\t\t\tcaps |= MAX31785_CAP_READ_DUAL_TACH;\n+\t\telse\n+\t\t\tdev_warn(dev, \"Expected max3175a, found max31785: cannot provide secondary tachometer readings\\n\");\n+\t} else if (!strcmp(\"max31785\", id->name)) {\n+\t\tif (ret == MAX31785A)\n+\t\t\tdev_info(dev, \"Expected max31785, found max3175a: suppressing secondary tachometer attributes\\n\");\n+\t} else {\n+\t\treturn -EINVAL;\n+\t}\n+\n+\n+\tfans = 0;\n+\tfor_each_child_of_node(dev->of_node, child) {\n+\t\tret = max31785_of_fan_config(client, info, caps, child);\n+\t\tif (ret < 0) {\n+\t\t\tof_node_put(child);\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tif (ret)\n+\t\t\tfans |= ret;\n+\n+\t\tret = max31785_of_tmp_config(client, info, child);\n+\t\tif (ret < 0) {\n+\t\t\tof_node_put(child);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < MAX31785_NR_PAGES; i++) {\n+\t\tbool have_fan = !!(info->func[i] & PMBUS_HAVE_FAN12);\n+\t\tbool fan_configured = !!(fans & BIT(i));\n+\n+\t\tif (!have_fan || fan_configured)\n+\t\t\tcontinue;\n+\n+\t\tret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\n+\t\tret = i2c_smbus_read_byte_data(client, PMBUS_FAN_CONFIG_12);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\n+\t\tret &= ~PB_FAN_1_INSTALLED;\n+\t\tret = i2c_smbus_write_word_data(client, PMBUS_FAN_CONFIG_12,\n+\t\t\t\t\t\tret);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn pmbus_do_probe(client, id, info);\n+}\n+\n+static const struct i2c_device_id max31785_id[] = {\n+\t{ \"max31785\", 0 },\n+\t{ \"max31785a\", 0 },\n+\t{ },\n+};\n+\n+MODULE_DEVICE_TABLE(i2c, max31785_id);\n+\n+static struct i2c_driver max31785_driver = {\n+\t.driver = {\n+\t\t.name = \"max31785\",\n+\t},\n+\t.probe = max31785_probe,\n+\t.remove = pmbus_do_remove,\n+\t.id_table = max31785_id,\n+};\n+\n+module_i2c_driver(max31785_driver);\n+\n+MODULE_AUTHOR(\"Andrew Jeffery <andrew@aj.id.au>\");\n+MODULE_DESCRIPTION(\"PMBus driver for the Maxim MAX31785\");\n+MODULE_LICENSE(\"GPL\");\n", "prefixes": [ "v3", "3/3" ] }