[PATCH 0/2] hwmon: (pmbus/ibm-cffps) Add version detection capability
The IBM common form factor power supply driver may need to detect which PSU version is connected, since some systems can use a variety of power supplies. This series adds a new compatible string with no version number, and some code to parse the CCIN of the PSU to determine which version is applicable. Eddie James (2): dt-bindings: hwmon: Document ibm,cffps compatible string hwmon: (pmbus/ibm-cffps) Add version detection capability .../devicetree/bindings/hwmon/ibm,cffps1.txt | 3 ++ drivers/hwmon/pmbus/ibm-cffps.c| 37 +++--- 2 files changed, 36 insertions(+), 4 deletions(-) -- 1.8.3.1
[PATCH 1/2] dt-bindings: hwmon: Document ibm,cffps compatible string
Document this string that indicates that any version of the power supply may be connected. In this case, the driver must detect the version automatically. Signed-off-by: Eddie James --- Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt b/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt index 1036f65..d9a2719 100644 --- a/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt +++ b/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt @@ -5,6 +5,9 @@ Required properties: - compatible : Must be one of the following: "ibm,cffps1" "ibm,cffps2" + or "ibm,cffps" if the system + must support any version of the + power supply - reg = < I2C bus address >; : Address of the power supply on the I2C bus. -- 1.8.3.1
[PATCH 2/2] hwmon: (pmbus/ibm-cffps) Add version detection capability
Some systems may plug in either version 1 or version 2 of the IBM common form factor power supply. Add a version-less compatibility string that tells the driver to try and detect which version of the power supply is connected. Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ibm-cffps.c | 37 + 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index d44745e..d61547e 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -3,6 +3,7 @@ * Copyright 2017 IBM Corp. */ +#include #include #include #include @@ -29,6 +30,10 @@ #define CFFPS_INPUT_HISTORY_CMD0xD6 #define CFFPS_INPUT_HISTORY_SIZE 100 +#define CFFPS_CCIN_VERSION GENMASK(15, 8) +#define CFFPS_CCIN_VERSION_10x2b +#define CFFPS_CCIN_VERSION_20x2e + /* STATUS_MFR_SPECIFIC bits */ #define CFFPS_MFR_FAN_FAULTBIT(0) #define CFFPS_MFR_THERMAL_FAULTBIT(1) @@ -54,7 +59,7 @@ enum { CFFPS_DEBUGFS_NUM_ENTRIES }; -enum versions { cffps1, cffps2 }; +enum versions { cffps1, cffps2, cffps_unknown }; struct ibm_cffps_input_history { struct mutex update_lock; @@ -395,7 +400,7 @@ static int ibm_cffps_probe(struct i2c_client *client, const struct i2c_device_id *id) { int i, rc; - enum versions vs; + enum versions vs = cffps_unknown; struct dentry *debugfs; struct dentry *ibm_cffps_dir; struct ibm_cffps *psu; @@ -405,8 +410,27 @@ static int ibm_cffps_probe(struct i2c_client *client, vs = (enum versions)md; else if (id) vs = (enum versions)id->driver_data; - else - vs = cffps1; + + if (vs == cffps_unknown) { + u16 ccin_version = CFFPS_CCIN_VERSION_1; + int ccin = i2c_smbus_read_word_swapped(client, CFFPS_CCIN_CMD); + + if (ccin > 0) + ccin_version = FIELD_GET(CFFPS_CCIN_VERSION, ccin); + + switch (ccin_version) { + default: + case CFFPS_CCIN_VERSION_1: + vs = cffps1; + break; + case CFFPS_CCIN_VERSION_2: + vs = cffps2; + break; + } + + /* Set the client name to include the version number. */ + snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1); + } client->dev.platform_data = &ibm_cffps_pdata; rc = pmbus_do_probe(client, id, &ibm_cffps_info[vs]); @@ -465,6 +489,7 @@ static int ibm_cffps_probe(struct i2c_client *client, static const struct i2c_device_id ibm_cffps_id[] = { { "ibm_cffps1", cffps1 }, { "ibm_cffps2", cffps2 }, + { "ibm_cffps", cffps_unknown }, {} }; MODULE_DEVICE_TABLE(i2c, ibm_cffps_id); @@ -478,6 +503,10 @@ static int ibm_cffps_probe(struct i2c_client *client, .compatible = "ibm,cffps2", .data = (void *)cffps2 }, + { + .compatible = "ibm,cffps", + .data = (void *)cffps_unknown + }, {} }; MODULE_DEVICE_TABLE(of, ibm_cffps_of_match); -- 1.8.3.1
[PATCH v2 1/3] dt-bindings: hwmon: Document ibm,cffps2 compatible string
Document the compatible string for version 2 of the IBM CFFPS PSU. Signed-off-by: Eddie James --- Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt | 8 +--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt b/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt index f68a0a6..1036f65 100644 --- a/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt +++ b/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt @@ -1,8 +1,10 @@ -Device-tree bindings for IBM Common Form Factor Power Supply Version 1 --- +Device-tree bindings for IBM Common Form Factor Power Supply Versions 1 and 2 +- Required properties: - - compatible = "ibm,cffps1"; + - compatible : Must be one of the following: + "ibm,cffps1" + "ibm,cffps2" - reg = < I2C bus address >; : Address of the power supply on the I2C bus. -- 1.8.3.1
[PATCH v2 0/3] pmbus: ibm-cffps: Add support for version 2 of PSU
Version 2 of this PSU supports a second page of data and changes the format of the FW version command. Use the devicetree binding (or the I2C device ID) to determine which version the driver should use. Therefore add the new compatible string to the devicetree documentation and change the Swift system devicetree to use version 2. Changes since v1: - use an enum for the version instead of integers 1, 2, etc Eddie James (3): dt-bindings: hwmon: Document ibm,cffps2 compatible string ARM: dts: aspeed: swift: Change power supplies to version 2 pmbus: ibm-cffps: Add support for version 2 of the PSU .../devicetree/bindings/hwmon/ibm,cffps1.txt | 8 +- arch/arm/boot/dts/aspeed-bmc-opp-swift.dts | 4 +- drivers/hwmon/pmbus/ibm-cffps.c| 110 - 3 files changed, 95 insertions(+), 27 deletions(-) -- 1.8.3.1
[PATCH v2 2/3] ARM: dts: aspeed: swift: Change power supplies to version 2
Swift power supplies are version 2 of the IBM CFFPS. Make it so in the devicetree. Signed-off-by: Eddie James --- arch/arm/boot/dts/aspeed-bmc-opp-swift.dts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts b/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts index f14f745..815b865 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts @@ -494,7 +494,7 @@ }; power-supply@68 { - compatible = "ibm,cffps1"; + compatible = "ibm,cffps2"; reg = <0x68>; }; @@ -504,7 +504,7 @@ }; power-supply@69 { - compatible = "ibm,cffps1"; + compatible = "ibm,cffps2"; reg = <0x69>; }; -- 1.8.3.1
[PATCH v2 3/3] pmbus: ibm-cffps: Add support for version 2 of the PSU
Version 2 of the PSU supports a second page of data and changes the format of the FW version. Use the devicetree binding to differentiate between the version the driver should use. Signed-off-by: Eddie James --- Changes since v1: - use an enum for the version instead of integers 1, 2, etc drivers/hwmon/pmbus/ibm-cffps.c | 110 1 file changed, 88 insertions(+), 22 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index ee2ee9e..d44745e 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "pmbus.h" @@ -20,8 +21,9 @@ #define CFFPS_PN_CMD 0x9B #define CFFPS_SN_CMD 0x9E #define CFFPS_CCIN_CMD 0xBD -#define CFFPS_FW_CMD_START 0xFA -#define CFFPS_FW_NUM_BYTES 4 +#define CFFPS_FW_CMD 0xFA +#define CFFPS1_FW_NUM_BYTES4 +#define CFFPS2_FW_NUM_WORDS3 #define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD0xD6 @@ -52,6 +54,8 @@ enum { CFFPS_DEBUGFS_NUM_ENTRIES }; +enum versions { cffps1, cffps2 }; + struct ibm_cffps_input_history { struct mutex update_lock; unsigned long last_update; @@ -61,6 +65,7 @@ struct ibm_cffps_input_history { }; struct ibm_cffps { + enum versions version; struct i2c_client *client; struct ibm_cffps_input_history input_history; @@ -132,6 +137,8 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, struct ibm_cffps *psu = to_psu(idxp, idx); char data[I2C_SMBUS_BLOCK_MAX] = { 0 }; + pmbus_set_page(psu->client, 0); + switch (idx) { case CFFPS_DEBUGFS_INPUT_HISTORY: return ibm_cffps_read_input_history(psu, buf, count, ppos); @@ -152,16 +159,36 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, rc = snprintf(data, 5, "%04X", rc); goto done; case CFFPS_DEBUGFS_FW: - for (i = 0; i < CFFPS_FW_NUM_BYTES; ++i) { - rc = i2c_smbus_read_byte_data(psu->client, - CFFPS_FW_CMD_START + i); - if (rc < 0) - return rc; + switch (psu->version) { + case cffps1: + for (i = 0; i < CFFPS1_FW_NUM_BYTES; ++i) { + rc = i2c_smbus_read_byte_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 2], 3, "%02X", rc); + } - snprintf(&data[i * 2], 3, "%02X", rc); - } + rc = i * 2; + break; + case cffps2: + for (i = 0; i < CFFPS2_FW_NUM_WORDS; ++i) { + rc = i2c_smbus_read_word_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 4], 5, "%04X", rc); + } - rc = i * 2; + rc = i * 4; + break; + default: + return -EOPNOTSUPP; + } goto done; default: return -EINVAL; @@ -279,6 +306,8 @@ static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, psu->led_state = CFFPS_LED_ON; } + pmbus_set_page(psu->client, 0); + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, psu->led_state); if (rc < 0) @@ -299,6 +328,8 @@ static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, if (led_cdev->brightness == LED_OFF) return 0; + pmbus_set_page(psu->client, 0); + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, CFFPS_LED_BLINK); if (rc < 0) @@ -328,15 +359,32 @@ static void ibm_cffps_create_led_class(struct ibm_cffps *psu) dev_warn(dev, "failed to register led class: %d\n", r
Re: [PATCH 3/3] pmbus: ibm-cffps: Add support for version 2 of the PSU
On 8/30/19 12:36 PM, Guenter Roeck wrote: On Fri, Aug 30, 2019 at 11:09:45AM -0500, Eddie James wrote: Version 2 of the PSU supports a second page of data and changes the format of the FW version. Use the devicetree binding to differentiate between the version the driver should use. Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ibm-cffps.c | 109 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index ee2ee9e..ca26fbd 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -12,16 +12,20 @@ #include #include #include +#include #include #include "pmbus.h" +#define CFFPS_VERSIONS2 + Any chance you can use an enum for the versions ? Using version numbers 1/2 combined with array indices 0/1 is confusing, error prone, and seems unnecessary. Sure, good idea. Thanks, Eddie Thanks, Guenter #define CFFPS_FRU_CMD 0x9A #define CFFPS_PN_CMD 0x9B #define CFFPS_SN_CMD 0x9E #define CFFPS_CCIN_CMD0xBD -#define CFFPS_FW_CMD_START 0xFA -#define CFFPS_FW_NUM_BYTES 4 +#define CFFPS_FW_CMD 0xFA +#define CFFPS1_FW_NUM_BYTES4 +#define CFFPS2_FW_NUM_WORDS3 #define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD 0xD6 @@ -61,6 +65,7 @@ struct ibm_cffps_input_history { }; struct ibm_cffps { + int version; struct i2c_client *client; struct ibm_cffps_input_history input_history; @@ -132,6 +137,8 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, struct ibm_cffps *psu = to_psu(idxp, idx); char data[I2C_SMBUS_BLOCK_MAX] = { 0 }; + pmbus_set_page(psu->client, 0); + switch (idx) { case CFFPS_DEBUGFS_INPUT_HISTORY: return ibm_cffps_read_input_history(psu, buf, count, ppos); @@ -152,16 +159,36 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, rc = snprintf(data, 5, "%04X", rc); goto done; case CFFPS_DEBUGFS_FW: - for (i = 0; i < CFFPS_FW_NUM_BYTES; ++i) { - rc = i2c_smbus_read_byte_data(psu->client, - CFFPS_FW_CMD_START + i); - if (rc < 0) - return rc; + switch (psu->version) { + case 1: + for (i = 0; i < CFFPS1_FW_NUM_BYTES; ++i) { + rc = i2c_smbus_read_byte_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 2], 3, "%02X", rc); + } - snprintf(&data[i * 2], 3, "%02X", rc); - } + rc = i * 2; + break; + case 2: + for (i = 0; i < CFFPS2_FW_NUM_WORDS; ++i) { + rc = i2c_smbus_read_word_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 4], 5, "%04X", rc); + } - rc = i * 2; + rc = i * 4; + break; + default: + return -EOPNOTSUPP; + } goto done; default: return -EINVAL; @@ -279,6 +306,8 @@ static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, psu->led_state = CFFPS_LED_ON; } + pmbus_set_page(psu->client, 0); + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, psu->led_state); if (rc < 0) @@ -299,6 +328,8 @@ static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, if (led_cdev->brightness == LED_OFF) return 0; + pmbus_set_page(psu->client, 0); + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, CFFPS_LED_BLINK); if (rc < 0) @@ -328,15 +359,32 @@ static void ibm_cffps_create_led_class(struct ibm_cffps *psu) dev_warn(dev,
[PATCH 1/3] dt-bindings: hwmon: Document ibm,cffps2 compatible string
Document the compatible string for version 2 of the IBM CFFPS PSU. Signed-off-by: Eddie James --- Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt | 8 +--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt b/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt index f68a0a6..1036f65 100644 --- a/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt +++ b/Documentation/devicetree/bindings/hwmon/ibm,cffps1.txt @@ -1,8 +1,10 @@ -Device-tree bindings for IBM Common Form Factor Power Supply Version 1 --- +Device-tree bindings for IBM Common Form Factor Power Supply Versions 1 and 2 +- Required properties: - - compatible = "ibm,cffps1"; + - compatible : Must be one of the following: + "ibm,cffps1" + "ibm,cffps2" - reg = < I2C bus address >; : Address of the power supply on the I2C bus. -- 1.8.3.1
[PATCH 2/3] ARM: dts: aspeed: swift: Change power supplies to version 2
Swift power supplies are version 2 of the IBM CFFPS. Make it so in the devicetree. Signed-off-by: Eddie James --- arch/arm/boot/dts/aspeed-bmc-opp-swift.dts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts b/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts index f14f745..815b865 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-swift.dts @@ -494,7 +494,7 @@ }; power-supply@68 { - compatible = "ibm,cffps1"; + compatible = "ibm,cffps2"; reg = <0x68>; }; @@ -504,7 +504,7 @@ }; power-supply@69 { - compatible = "ibm,cffps1"; + compatible = "ibm,cffps2"; reg = <0x69>; }; -- 1.8.3.1
[PATCH 3/3] pmbus: ibm-cffps: Add support for version 2 of the PSU
Version 2 of the PSU supports a second page of data and changes the format of the FW version. Use the devicetree binding to differentiate between the version the driver should use. Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ibm-cffps.c | 109 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index ee2ee9e..ca26fbd 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -12,16 +12,20 @@ #include #include #include +#include #include #include "pmbus.h" +#define CFFPS_VERSIONS 2 + #define CFFPS_FRU_CMD 0x9A #define CFFPS_PN_CMD 0x9B #define CFFPS_SN_CMD 0x9E #define CFFPS_CCIN_CMD 0xBD -#define CFFPS_FW_CMD_START 0xFA -#define CFFPS_FW_NUM_BYTES 4 +#define CFFPS_FW_CMD 0xFA +#define CFFPS1_FW_NUM_BYTES4 +#define CFFPS2_FW_NUM_WORDS3 #define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD0xD6 @@ -61,6 +65,7 @@ struct ibm_cffps_input_history { }; struct ibm_cffps { + int version; struct i2c_client *client; struct ibm_cffps_input_history input_history; @@ -132,6 +137,8 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, struct ibm_cffps *psu = to_psu(idxp, idx); char data[I2C_SMBUS_BLOCK_MAX] = { 0 }; + pmbus_set_page(psu->client, 0); + switch (idx) { case CFFPS_DEBUGFS_INPUT_HISTORY: return ibm_cffps_read_input_history(psu, buf, count, ppos); @@ -152,16 +159,36 @@ static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, rc = snprintf(data, 5, "%04X", rc); goto done; case CFFPS_DEBUGFS_FW: - for (i = 0; i < CFFPS_FW_NUM_BYTES; ++i) { - rc = i2c_smbus_read_byte_data(psu->client, - CFFPS_FW_CMD_START + i); - if (rc < 0) - return rc; + switch (psu->version) { + case 1: + for (i = 0; i < CFFPS1_FW_NUM_BYTES; ++i) { + rc = i2c_smbus_read_byte_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 2], 3, "%02X", rc); + } - snprintf(&data[i * 2], 3, "%02X", rc); - } + rc = i * 2; + break; + case 2: + for (i = 0; i < CFFPS2_FW_NUM_WORDS; ++i) { + rc = i2c_smbus_read_word_data(psu->client, + CFFPS_FW_CMD + + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 4], 5, "%04X", rc); + } - rc = i * 2; + rc = i * 4; + break; + default: + return -EOPNOTSUPP; + } goto done; default: return -EINVAL; @@ -279,6 +306,8 @@ static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, psu->led_state = CFFPS_LED_ON; } + pmbus_set_page(psu->client, 0); + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, psu->led_state); if (rc < 0) @@ -299,6 +328,8 @@ static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, if (led_cdev->brightness == LED_OFF) return 0; + pmbus_set_page(psu->client, 0); + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, CFFPS_LED_BLINK); if (rc < 0) @@ -328,15 +359,32 @@ static void ibm_cffps_create_led_class(struct ibm_cffps *psu) dev_warn(dev, "failed to register led class: %d\n", rc); } -static struct pmbus_driver_info ibm_cffps_info = { - .pages = 1, - .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | - PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMB
[PATCH 0/3] pmbus: ibm-cffps: Add support for version 2 of PSU
Version 2 of this PSU supports a second page of data and changes the format of the FW version command. Use the devicetree binding (or the I2C device ID) to determine which version the driver should use. Therefore add the new compatible string to the devicetree documentation and change the Swift system devicetree to use version 2. Eddie James (3): dt-bindings: hwmon: Document ibm,cffps2 compatible string ARM: dts: aspeed: swift: Change power supplies to version 2 pmbus: ibm-cffps: Add support for version 2 of the PSU .../devicetree/bindings/hwmon/ibm,cffps1.txt | 8 +- arch/arm/boot/dts/aspeed-bmc-opp-swift.dts | 4 +- drivers/hwmon/pmbus/ibm-cffps.c| 109 - 3 files changed, 94 insertions(+), 27 deletions(-) -- 1.8.3.1
[PATCH] OCC: FSI and hwmon: Add sequence numbering
Sequence numbering of the commands submitted to the OCC is required by the OCC interface specification. Add sequence numbering and check for the correct sequence number on the response. Signed-off-by: Eddie James --- drivers/fsi/fsi-occ.c | 15 --- drivers/hwmon/occ/common.c | 4 ++-- drivers/hwmon/occ/common.h | 1 + 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c index a2301ce..7da9c81 100644 --- a/drivers/fsi/fsi-occ.c +++ b/drivers/fsi/fsi-occ.c @@ -412,6 +412,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); struct occ *occ = dev_get_drvdata(dev); struct occ_response *resp = response; + u8 seq_no; u16 resp_data_length; unsigned long start; int rc; @@ -426,6 +427,8 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, mutex_lock(&occ->occ_lock); + /* Extract the seq_no from the command (first byte) */ + seq_no = *(const u8 *)request; rc = occ_putsram(occ, OCC_SRAM_CMD_ADDR, request, req_len); if (rc) goto done; @@ -441,11 +444,17 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, if (rc) goto done; - if (resp->return_status == OCC_RESP_CMD_IN_PRG) { + if (resp->return_status == OCC_RESP_CMD_IN_PRG || + resp->seq_no != seq_no) { rc = -ETIMEDOUT; - if (time_after(jiffies, start + timeout)) - break; + if (time_after(jiffies, start + timeout)) { + dev_err(occ->dev, "resp timeout status=%02x " + "resp seq_no=%d our seq_no=%d\n", + resp->return_status, resp->seq_no, + seq_no); + goto done; + } set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(wait_time); diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index d593517..a7d2b16 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -124,12 +124,12 @@ struct extended_sensor { static int occ_poll(struct occ *occ) { int rc; - u16 checksum = occ->poll_cmd_data + 1; + u16 checksum = occ->poll_cmd_data + occ->seq_no + 1; u8 cmd[8]; struct occ_poll_response_header *header; /* big endian */ - cmd[0] = 0; /* sequence number */ + cmd[0] = occ->seq_no++; /* sequence number */ cmd[1] = 0; /* cmd type */ cmd[2] = 0; /* data length msb */ cmd[3] = 1; /* data length lsb */ diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index fc13f3c..67e6968 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -95,6 +95,7 @@ struct occ { struct occ_sensors sensors; int powr_sample_time_us;/* average power sample time */ + u8 seq_no; u8 poll_cmd_data; /* to perform OCC poll command */ int (*send_cmd)(struct occ *occ, u8 *cmd); -- 1.8.3.1
Re: [PATCH] hwmon (occ): Switch power average to between poll responses
On 5/8/19 4:03 PM, Guenter Roeck wrote: On Tue, May 07, 2019 at 02:35:51PM -0500, Eddie James wrote: The average power reported in the hwmon OCC driver is not useful because the time it represents is too short. Instead, store the previous power accumulator reported by the OCC and average it with the latest accumulator to obtain an average between poll responses. This does operate under the assumption that the user polls regularly. That looks really bad. Effectively it means that the number reported as average power is more or less useless/random. On top of that, the code gets so complicated that it is all but impossible to understand. Does it really make sense to report an average that has effectively no useful meaning (and is, for example, influenced just by reading it) ? Yea... that's a good point. Basically our userspace environment has no good way to do this either, so I tried to shoe-horn this into the driver, but this approach is indeed bad. I'll abandon this patch. Thanks! Eddie Guenter Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 133 - drivers/hwmon/occ/common.h | 7 +++ 2 files changed, 103 insertions(+), 37 deletions(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index e6d3fb5..6ffcee7 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -118,6 +118,53 @@ struct extended_sensor { u8 data[6]; } __packed; +static void occ_update_prev_power_avgs(struct occ *occ) +{ + u8 i; + struct power_sensor_1 *ps1; + struct power_sensor_2 *ps2; + struct power_sensor_a0 *psa0; + struct occ_sensor *power = &occ->sensors.power; + struct occ_power_avg *prevs = occ->prev_power_avgs; + + switch (power->version) { + case 1: + for (i = 0; i < power->num_sensors; ++i) { + ps1 = ((struct power_sensor_1 *)power->data) + i; + + prevs[i].accumulator = + get_unaligned_be32(&ps1->accumulator); + prevs[i].update_tag = + get_unaligned_be32(&ps1->update_tag); + } + break; + case 2: + for (i = 0; i < power->num_sensors; ++i) { + ps2 = ((struct power_sensor_2 *)power->data) + i; + + prevs[i].accumulator = + get_unaligned_be64(&ps2->accumulator); + prevs[i].update_tag = + get_unaligned_be32(&ps2->update_tag); + } + break; + case 0xA0: + for (i = 0; i < power->num_sensors; ++i) { + psa0 = ((struct power_sensor_a0 *)power->data) + i; + + prevs[i].accumulator = psa0->system.accumulator; + prevs[i].update_tag = psa0->system.update_tag; + prevs[i + 1].accumulator = psa0->proc.accumulator; + prevs[i + 1].update_tag = psa0->proc.update_tag; + prevs[i + 2].accumulator = psa0->vdd.accumulator; + prevs[i + 2].update_tag = psa0->vdd.update_tag; + prevs[i + 3].accumulator = psa0->vdn.accumulator; + prevs[i + 3].update_tag = psa0->vdn.update_tag; + } + break; + } +} + static int occ_poll(struct occ *occ) { int rc; @@ -135,6 +182,8 @@ static int occ_poll(struct occ *occ) cmd[6] = checksum & 0xFF; /* checksum lsb */ cmd[7] = 0; + occ_update_prev_power_avgs(occ); + /* mutex should already be locked if necessary */ rc = occ->send_cmd(occ, cmd); if (rc) { @@ -208,6 +257,7 @@ int occ_update_response(struct occ *occ) /* limit the maximum rate of polling the OCC */ if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) { rc = occ_poll(occ); + occ->prev_update = occ->last_update; occ->last_update = jiffies; } else { rc = occ->last_error; @@ -364,6 +414,14 @@ static ssize_t occ_show_freq_2(struct device *dev, return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); } +static u64 occ_power_avg(struct occ *occ, u8 idx, u64 accum, u32 samples) +{ + struct occ_power_avg *avg = &occ->prev_power_avgs[idx]; + + return div_u64((accum - avg->accumulator) * 100ULL, + samples - avg->update_tag); +} + static ssize_t occ_show_power_1(struct device *dev, struct device_attribute *attr, char *buf) { @@ -385,13 +443,12 @@ static ssize_t occ_show_power_1(struct device *dev,
[PATCH] hwmon (occ): Switch power average to between poll responses
The average power reported in the hwmon OCC driver is not useful because the time it represents is too short. Instead, store the previous power accumulator reported by the OCC and average it with the latest accumulator to obtain an average between poll responses. This does operate under the assumption that the user polls regularly. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 133 - drivers/hwmon/occ/common.h | 7 +++ 2 files changed, 103 insertions(+), 37 deletions(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index e6d3fb5..6ffcee7 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -118,6 +118,53 @@ struct extended_sensor { u8 data[6]; } __packed; +static void occ_update_prev_power_avgs(struct occ *occ) +{ + u8 i; + struct power_sensor_1 *ps1; + struct power_sensor_2 *ps2; + struct power_sensor_a0 *psa0; + struct occ_sensor *power = &occ->sensors.power; + struct occ_power_avg *prevs = occ->prev_power_avgs; + + switch (power->version) { + case 1: + for (i = 0; i < power->num_sensors; ++i) { + ps1 = ((struct power_sensor_1 *)power->data) + i; + + prevs[i].accumulator = + get_unaligned_be32(&ps1->accumulator); + prevs[i].update_tag = + get_unaligned_be32(&ps1->update_tag); + } + break; + case 2: + for (i = 0; i < power->num_sensors; ++i) { + ps2 = ((struct power_sensor_2 *)power->data) + i; + + prevs[i].accumulator = + get_unaligned_be64(&ps2->accumulator); + prevs[i].update_tag = + get_unaligned_be32(&ps2->update_tag); + } + break; + case 0xA0: + for (i = 0; i < power->num_sensors; ++i) { + psa0 = ((struct power_sensor_a0 *)power->data) + i; + + prevs[i].accumulator = psa0->system.accumulator; + prevs[i].update_tag = psa0->system.update_tag; + prevs[i + 1].accumulator = psa0->proc.accumulator; + prevs[i + 1].update_tag = psa0->proc.update_tag; + prevs[i + 2].accumulator = psa0->vdd.accumulator; + prevs[i + 2].update_tag = psa0->vdd.update_tag; + prevs[i + 3].accumulator = psa0->vdn.accumulator; + prevs[i + 3].update_tag = psa0->vdn.update_tag; + } + break; + } +} + static int occ_poll(struct occ *occ) { int rc; @@ -135,6 +182,8 @@ static int occ_poll(struct occ *occ) cmd[6] = checksum & 0xFF; /* checksum lsb */ cmd[7] = 0; + occ_update_prev_power_avgs(occ); + /* mutex should already be locked if necessary */ rc = occ->send_cmd(occ, cmd); if (rc) { @@ -208,6 +257,7 @@ int occ_update_response(struct occ *occ) /* limit the maximum rate of polling the OCC */ if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) { rc = occ_poll(occ); + occ->prev_update = occ->last_update; occ->last_update = jiffies; } else { rc = occ->last_error; @@ -364,6 +414,14 @@ static ssize_t occ_show_freq_2(struct device *dev, return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); } +static u64 occ_power_avg(struct occ *occ, u8 idx, u64 accum, u32 samples) +{ + struct occ_power_avg *avg = &occ->prev_power_avgs[idx]; + + return div_u64((accum - avg->accumulator) * 100ULL, + samples - avg->update_tag); +} + static ssize_t occ_show_power_1(struct device *dev, struct device_attribute *attr, char *buf) { @@ -385,13 +443,12 @@ static ssize_t occ_show_power_1(struct device *dev, val = get_unaligned_be16(&power->sensor_id); break; case 1: - val = get_unaligned_be32(&power->accumulator) / - get_unaligned_be32(&power->update_tag); - val *= 100ULL; + val = occ_power_avg(occ, sattr->index, + get_unaligned_be32(&power->accumulator), + get_unaligned_be32(&power->update_tag)); break; case 2: - val = (u64)get_unaligned_be32(&power->update_tag) * - occ->powr_sample_time_us; + val = jiffies_to_usecs(occ->last_update - occ->pre
Re: [PATCH v2 1/1] hwmon (occ): Add temp sensor value check
On 4/17/19 1:03 PM, Alexander Amelkin wrote: From: Alexander Soldatov The occ driver supports two formats for the temp sensor value. The OCC firmware for P8 supports only the first format, for which no range checking or error processing is performed in the driver. Inspecting the OCC sources for P8 reveals that OCC may send a special value 0x to indicate that a sensor read timeout has occurred, see https://github.com/open-power/occ/blob/master_p8/src/occ/cmdh/cmdh_fsp_cmds.c#L395 That situation wasn't handled in the driver. This patch adds invalid temp value check for the sensor data format 1 and handles it the same way as it is done for the format 2, where EREMOTEIO is reported for this case. Thanks Alexander and Guenter. Reviewed-by: Eddie James Signed-off-by: Alexander Soldatov Signed-off-by: Alexander Amelkin Reviewed-by: Alexander Amelkin Cc: Edward A. James Cc: Joel Stanley --- drivers/hwmon/occ/common.c | 4 1 file changed, 4 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 4679acb..825631c 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -235,6 +235,12 @@ static ssize_t occ_show_temp_1(struct device *dev, val = get_unaligned_be16(&temp->sensor_id); break; case 1: + /* +* If a sensor reading has expired and couldn't be refreshed, +* OCC returns 0x for that sensor. +*/ + if (temp->value == 0x) + return -EREMOTEIO; val = get_unaligned_be16(&temp->value) * 1000; break; default:
[PATCH v2 3/3] hwmon (occ): Add more details to Kconfig help text
The help text needs to spell out how the driver runs on a BMC, as it previously seemed to indicate it ran on a POWER processor. Signed-off-by: Eddie James --- Changes since v1: fix Signed-off-by; git config got mixed up. drivers/hwmon/occ/Kconfig | 12 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig index 2a3869f..1658634 100644 --- a/drivers/hwmon/occ/Kconfig +++ b/drivers/hwmon/occ/Kconfig @@ -9,8 +9,10 @@ config SENSORS_OCC_P8_I2C select SENSORS_OCC help This option enables support for monitoring sensors provided by the -On-Chip Controller (OCC) on a POWER8 processor. Communications with -the OCC are established through I2C bus. +On-Chip Controller (OCC) on a POWER8 processor. However, this driver +can only run on a baseboard management controller (BMC) connected to +the P8, not the POWER processor itself. Communications with the OCC are +established through I2C bus. This driver can also be built as a module. If so, the module will be called occ-p8-hwmon. @@ -22,8 +24,10 @@ config SENSORS_OCC_P9_SBE select SENSORS_OCC help This option enables support for monitoring sensors provided by the -On-Chip Controller (OCC) on a POWER9 processor. Communications with -the OCC are established through SBE fifo on an FSI bus. +On-Chip Controller (OCC) on a POWER9 processor. However, this driver +can only run on a baseboard management controller (BMC) connected to +the P9, not the POWER processor itself. Communications with the OCC are +established through SBE fifo on an FSI bus. This driver can also be built as a module. If so, the module will be called occ-p9-hwmon. -- 2.7.4
[PATCH 2/3] hwmon (occ): Prevent sysfs error attribute from returning error
The error sysfs attribute returns the stored error state of the OCC and doesn't depend on the OCC poll response. Therefore, split the error attribute into it's own function to avoid failing out of the function if the poll response fails. Signed-off-by: Eddie James --- drivers/hwmon/occ/sysfs.c | 19 +-- 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index 1cb1e65..c73be07 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -63,9 +63,6 @@ static ssize_t occ_sysfs_show(struct device *dev, else val = 1; break; - case 8: - val = occ->error; - break; default: return -EINVAL; } @@ -73,6 +70,16 @@ static ssize_t occ_sysfs_show(struct device *dev, return snprintf(buf, PAGE_SIZE - 1, "%d\n", val); } +static ssize_t occ_error_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct occ *occ = dev_get_drvdata(dev); + + occ_update_response(occ); + + return snprintf(buf, PAGE_SIZE - 1, "%d\n", occ->error); +} + static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_sysfs_show, NULL, 0); static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_sysfs_show, NULL, 1); static SENSOR_DEVICE_ATTR(occ_dvfs_overtemp, 0444, occ_sysfs_show, NULL, 2); @@ -81,7 +88,7 @@ static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4); static SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5); static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6); static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7); -static SENSOR_DEVICE_ATTR(occ_error, 0444, occ_sysfs_show, NULL, 8); +static DEVICE_ATTR_RO(occ_error); static struct attribute *occ_attributes[] = { &sensor_dev_attr_occ_master.dev_attr.attr, @@ -92,7 +99,7 @@ static struct attribute *occ_attributes[] = { &sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr, &sensor_dev_attr_occ_state.dev_attr.attr, &sensor_dev_attr_occs_present.dev_attr.attr, - &sensor_dev_attr_occ_error.dev_attr.attr, + &dev_attr_occ_error.attr, NULL }; @@ -156,7 +163,7 @@ void occ_sysfs_poll_done(struct occ *occ) } if (occ->error && occ->error != occ->prev_error) { - name = sensor_dev_attr_occ_error.dev_attr.attr.name; + name = dev_attr_occ_error.attr.name; sysfs_notify(&occ->bus_dev->kobj, NULL, name); } -- 2.7.4
[PATCH 3/3] hwmon (occ): Add more details to Kconfig help text
From: Eddie James The help text needs to spell out how the driver runs on a BMC, as it previously seemed to indicate it ran on a POWER processor. Signed-off-by: Eddie James --- drivers/hwmon/occ/Kconfig | 12 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig index 2a3869f..1658634 100644 --- a/drivers/hwmon/occ/Kconfig +++ b/drivers/hwmon/occ/Kconfig @@ -9,8 +9,10 @@ config SENSORS_OCC_P8_I2C select SENSORS_OCC help This option enables support for monitoring sensors provided by the -On-Chip Controller (OCC) on a POWER8 processor. Communications with -the OCC are established through I2C bus. +On-Chip Controller (OCC) on a POWER8 processor. However, this driver +can only run on a baseboard management controller (BMC) connected to +the P8, not the POWER processor itself. Communications with the OCC are +established through I2C bus. This driver can also be built as a module. If so, the module will be called occ-p8-hwmon. @@ -22,8 +24,10 @@ config SENSORS_OCC_P9_SBE select SENSORS_OCC help This option enables support for monitoring sensors provided by the -On-Chip Controller (OCC) on a POWER9 processor. Communications with -the OCC are established through SBE fifo on an FSI bus. +On-Chip Controller (OCC) on a POWER9 processor. However, this driver +can only run on a baseboard management controller (BMC) connected to +the P9, not the POWER processor itself. Communications with the OCC are +established through SBE fifo on an FSI bus. This driver can also be built as a module. If so, the module will be called occ-p9-hwmon. -- 2.7.4
[PATCH 1/3] hwmon (occ): Store error condition for rate-limited polls
The OCC driver limits the rate of sending poll commands to the OCC. If a user reads a hwmon entry after a poll response resulted in an error and is rate-limited, the error is invisible to the user. Fix this by storing the last error and returning that in the rate-limited case. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 4 drivers/hwmon/occ/common.h | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 9d197e9..13a6290 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -141,6 +141,7 @@ static int occ_poll(struct occ *occ) /* mutex should already be locked if necessary */ rc = occ->send_cmd(occ, cmd); if (rc) { + occ->last_error = rc; if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD) occ->error = rc; @@ -149,6 +150,7 @@ static int occ_poll(struct occ *occ) /* clear error since communication was successful */ occ->error_count = 0; + occ->last_error = 0; occ->error = 0; /* check for safe state */ @@ -210,6 +212,8 @@ int occ_update_response(struct occ *occ) if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) { rc = occ_poll(occ); occ->last_update = jiffies; + } else { + rc = occ->last_error; } mutex_unlock(&occ->lock); diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index ed2cf42..fc13f3c 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -106,7 +106,8 @@ struct occ { struct attribute_group group; const struct attribute_group *groups[2]; - int error; /* latest transfer error */ + int error; /* final transfer error after retry */ + int last_error; /* latest transfer error */ unsigned int error_count; /* number of xfr errors observed */ unsigned long last_safe;/* time OCC entered "safe" state */ -- 2.7.4
Re: [PATCH] hwmon (occ): Fix extended status bits
On 4/15/19 3:28 PM, Guenter Roeck wrote: On Mon, Apr 15, 2019 at 06:37:20PM +0800, Lei YU wrote: The occ's extended status is checked and shown as sysfs attributes. But the code was incorrectly checking the "status" bits. Fix it by checking the "ext_status" bits. Signed-off-by: Lei YU Looks ok to me, but can I get a confirmation from someone at IBM ? Yes. Thanks Lei. Reviewed-by: Eddie James Thanks, Guenter --- drivers/hwmon/occ/sysfs.c | 8 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index fe3d15e..a71ca94 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -42,16 +42,16 @@ static ssize_t occ_sysfs_show(struct device *dev, val = !!(header->status & OCC_STAT_ACTIVE); break; case 2: - val = !!(header->status & OCC_EXT_STAT_DVFS_OT); + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT); break; case 3: - val = !!(header->status & OCC_EXT_STAT_DVFS_POWER); + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER); break; case 4: - val = !!(header->status & OCC_EXT_STAT_MEM_THROTTLE); + val = !!(header->ext_status & OCC_EXT_STAT_MEM_THROTTLE); break; case 5: - val = !!(header->status & OCC_EXT_STAT_QUICK_DROP); + val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP); break; case 6: val = header->occ_state;
Re: [PATCH v2 2/2] hwmon: OCC drivers are ARM-only
On 4/10/19 5:56 AM, Jean Delvare wrote: These drivers are for a BMC inside PowerPC servers. The BMC runs on ARM hardware, so only propose the drivers on this architecture, unless build-testing. Thanks! Reviewed-by: Eddie James Signed-off-by: Jean Delvare Cc: Eddie James Cc: Guenter Roeck --- If PowerPC BMCs are ever based on another architecture and these drivers are compatible with them, then the list can be extended. drivers/hwmon/occ/Kconfig |2 ++ 1 file changed, 2 insertions(+) --- linux-5.0.orig/drivers/hwmon/occ/Kconfig2019-04-10 11:54:07.014895111 +0200 +++ linux-5.0/drivers/hwmon/occ/Kconfig 2019-04-10 12:12:27.379725640 +0200 @@ -5,6 +5,7 @@ config SENSORS_OCC_P8_I2C tristate "POWER8 OCC through I2C" depends on I2C + depends on ARM || ARM64 || COMPILE_TEST select SENSORS_OCC help This option enables support for monitoring sensors provided by the @@ -17,6 +18,7 @@ config SENSORS_OCC_P8_I2C config SENSORS_OCC_P9_SBE tristate "POWER9 OCC through SBE" depends on FSI_OCC + depends on ARM || ARM64 || COMPILE_TEST select SENSORS_OCC help This option enables support for monitoring sensors provided by the
Re: [PATCH v2 1/2] hwmon: (occ) Move common code to a separate module
On 4/10/19 5:47 AM, Jean Delvare wrote: Instead of duplicating the common code into the 2 (binary) drivers, move the common code to a separate module. This is cleaner. Signed-off-by: Jean Delvare Cc: Eddie James Cc: Guenter Roeck --- Eddie, can you please give it a try and confirm it works? Yes, this works well. Reviewed-by: Eddie James Tested-by: Eddie James Note: I kept the module names as they were before, hence the extra "*-objs :=" statements. They could be removed if we rename the source files, but that's better done in git directly. I don't mind either way personally. drivers/hwmon/occ/Kconfig |3 +-- drivers/hwmon/occ/Makefile |6 -- drivers/hwmon/occ/common.c |7 +++ drivers/hwmon/occ/sysfs.c |2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) --- linux-5.0.orig/drivers/hwmon/occ/Kconfig2019-04-10 11:30:05.579537638 +0200 +++ linux-5.0/drivers/hwmon/occ/Kconfig 2019-04-10 11:31:20.843383376 +0200 @@ -27,5 +27,4 @@ config SENSORS_OCC_P9_SBE called occ-p9-hwmon. config SENSORS_OCC - bool "POWER On-Chip Controller" - depends on SENSORS_OCC_P8_I2C || SENSORS_OCC_P9_SBE + tristate --- linux-5.0.orig/drivers/hwmon/occ/Makefile 2019-03-04 00:21:29.0 +0100 +++ linux-5.0/drivers/hwmon/occ/Makefile2019-04-10 11:33:23.631765535 +0200 @@ -1,5 +1,7 @@ -occ-p8-hwmon-objs := common.o sysfs.o p8_i2c.o -occ-p9-hwmon-objs := common.o sysfs.o p9_sbe.o +occ-hwmon-common-objs := common.o sysfs.o +occ-p8-hwmon-objs := p8_i2c.o +occ-p9-hwmon-objs := p9_sbe.o +obj-$(CONFIG_SENSORS_OCC) += occ-hwmon-common.o obj-$(CONFIG_SENSORS_OCC_P8_I2C) += occ-p8-hwmon.o obj-$(CONFIG_SENSORS_OCC_P9_SBE) += occ-p9-hwmon.o --- linux-5.0.orig/drivers/hwmon/occ/common.c 2019-03-04 00:21:29.0 +0100 +++ linux-5.0/drivers/hwmon/occ/common.c2019-04-10 11:44:53.035573580 +0200 @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include #include #include +#include #include #include #include @@ -1096,3 +1098,8 @@ int occ_setup(struct occ *occ, const cha return rc; } +EXPORT_SYMBOL_GPL(occ_setup); + +MODULE_AUTHOR("Eddie James "); +MODULE_DESCRIPTION("Common OCC hwmon code"); +MODULE_LICENSE("GPL"); --- linux-5.0.orig/drivers/hwmon/occ/sysfs.c2019-03-04 00:21:29.0 +0100 +++ linux-5.0/drivers/hwmon/occ/sysfs.c 2019-04-10 11:39:38.627003382 +0200 @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -186,3 +187,4 @@ void occ_shutdown(struct occ *occ) { sysfs_remove_group(&occ->bus_dev->kobj, &occ_sysfs); } +EXPORT_SYMBOL_GPL(occ_shutdown);
Re: [PATCH] hwmon: OCC drivers are PowerPC-only
On 4/9/19 2:44 PM, Jean Delvare wrote: Hi Eddie, Thanks for the quick answer. On Tue, 9 Apr 2019 10:20:22 -0500, Eddie James wrote: On 4/9/19 7:45 AM, Jean Delvare wrote: Don't propose PowerPC-only drivers on other architectures, unless build-testing. This driver does NOT only run on PowerPC; rather it runs on a BMC processor connected to a PowerPC processor. BMC will most likely be ARM, Thanks for clarifying. So, you have a server with one or more PowerPC processors, running any operating system decided by the customer, and in the same system, is a BMC, running a Linux operating system provided by IBM? Do I understand it correctly? Exactly, though the operating system running on the BMC (OpenBMC) is used/developed by a number of other companies as well. but shouldn't be restricted to that arch only. Why not? Restricting drivers to the architectures or platforms where they make sense significantly eases the work of the maintainers of distribution kernels. Each new kernel version comes with several dozens of new options. Without hints, they have no idea what is needed on which architecture, and they either select everything, resulting in an overweight kernel forever, or nothing, resulting in missing features until someone complains. I see, that makes sense to restrict it then. It is possible someone will want to run OpenBMC managing a Power processor on something other than an ARM chip, but I don't think that configuration is needed right now. Regardless of any dependency at the Kconfig level, the help text of these drivers should explain exactly what you wrote above. At the moment, the reader has no way to guess that the drivers only make sense for an embedded Linux running on a BMC. As I understand it now, general purpose distributions do not need these drivers, right? But for example SUSE kernels included them on all architectures. I just restricted them to ppc64, but apparently I was wrong and it should be disabled there too. A good Kconfig help text should help the user decide whether they need the driver or not. Yep, the Kconfig text can probably be improved as well. I'll look into that. Also drop configuration symbol SENSORS_OCC which serves no purpose that I can see. Signed-off-by: Jean Delvare Cc: Eddie James Cc: Guenter Roeck --- SENSORS_OCC *would* serve a purpose if the common code between the POWER8 driver and the POWER9 driver would go in a separate, shared module, and occ-p8-hwmon and occ-p9-hwmon would only contain the specific code. This would avoid packaging the same code twice in 2 separate modules, therefore saving some storage space for ppc distributions. Well you'd never have both P8 and P9 enabled at once, so space shouldn't be an issue. I agree this could be cleaner but I think I was getting Where "you" is IBM, provider of the embedded Linux running on the BMC? Yes, though again could be any distributor of OpenBMC. Thanks for looking into this! Eddie duplicate symbol errors for the compile test and so I did it this way. If this doesn't lead to errors in the compile test, I'm fine with this (without the change for PPC only though). Dropping SENSORS_OCC can't result in duplicate symbols because it is a no-op. But while this is the easiest solution, it it not the cleanest in my opinion. OTOH, if you are certain that both modules will never be shipped at the same time, then it indeed doesn't matter that much. I can provide a patch going in either direction. Guenter, any preference?
Re: [PATCH] hwmon: OCC drivers are PowerPC-only
On 4/9/19 7:45 AM, Jean Delvare wrote: Don't propose PowerPC-only drivers on other architectures, unless build-testing. This driver does NOT only run on PowerPC; rather it runs on a BMC processor connected to a PowerPC processor. BMC will most likely be ARM, but shouldn't be restricted to that arch only. Also drop configuration symbol SENSORS_OCC which serves no purpose that I can see. Signed-off-by: Jean Delvare Cc: Eddie James Cc: Guenter Roeck --- SENSORS_OCC *would* serve a purpose if the common code between the POWER8 driver and the POWER9 driver would go in a separate, shared module, and occ-p8-hwmon and occ-p9-hwmon would only contain the specific code. This would avoid packaging the same code twice in 2 separate modules, therefore saving some storage space for ppc distributions. Well you'd never have both P8 and P9 enabled at once, so space shouldn't be an issue. I agree this could be cleaner but I think I was getting duplicate symbol errors for the compile test and so I did it this way. If this doesn't lead to errors in the compile test, I'm fine with this (without the change for PPC only though). Thanks, Eddie As far as I can see, this would simply require exporting 2 functions (occ_setup and occ_shutdown). Is there any reason why things were not done that way in the first place? This would look cleaner to me. drivers/hwmon/Makefile|2 +- drivers/hwmon/occ/Kconfig |8 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) --- linux-5.0.orig/drivers/hwmon/occ/Kconfig2019-03-04 00:21:29.0 +0100 +++ linux-5.0/drivers/hwmon/occ/Kconfig 2019-04-09 14:08:41.316551071 +0200 @@ -5,7 +5,7 @@ config SENSORS_OCC_P8_I2C tristate "POWER8 OCC through I2C" depends on I2C - select SENSORS_OCC + depends on POWERPC || COMPILE_TEST help This option enables support for monitoring sensors provided by the On-Chip Controller (OCC) on a POWER8 processor. Communications with @@ -17,7 +17,7 @@ config SENSORS_OCC_P8_I2C config SENSORS_OCC_P9_SBE tristate "POWER9 OCC through SBE" depends on FSI_OCC - select SENSORS_OCC + depends on POWERPC || COMPILE_TEST help This option enables support for monitoring sensors provided by the On-Chip Controller (OCC) on a POWER9 processor. Communications with @@ -25,7 +25,3 @@ config SENSORS_OCC_P9_SBE This driver can also be built as a module. If so, the module will be called occ-p9-hwmon. - -config SENSORS_OCC - bool "POWER On-Chip Controller" - depends on SENSORS_OCC_P8_I2C || SENSORS_OCC_P9_SBE --- linux-5.0.orig/drivers/hwmon/Makefile 2019-03-04 00:21:29.0 +0100 +++ linux-5.0/drivers/hwmon/Makefile2019-04-09 14:33:49.605510047 +0200 @@ -178,7 +178,7 @@ obj-$(CONFIG_SENSORS_WM831X)+= wm831x-h obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o -obj-$(CONFIG_SENSORS_OCC) += occ/ +obj-y += occ/ obj-$(CONFIG_PMBUS) += pmbus/ ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
[PATCH] hwmon: occ: Fix power sensor indexing
In the case of power sensor version 0xA0, the sensor indexing overlapped with the "caps" power sensors, resulting in probe failure and kernel warnings. Fix this by specifying the next index for each power sensor version. Fixes: 54076cb ("hwmon (occ): Add sensor attributes and register ...") Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 6 -- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 391118c..c888f4a 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -889,6 +889,8 @@ static int occ_setup_sensor_attrs(struct occ *occ) s++; } } + + s = (sensors->power.num_sensors * 4) + 1; } else { for (i = 0; i < sensors->power.num_sensors; ++i) { s = i + 1; @@ -917,11 +919,11 @@ static int occ_setup_sensor_attrs(struct occ *occ) show_power, NULL, 3, i); attr++; } - } - if (sensors->caps.num_sensors >= 1) { s = sensors->power.num_sensors + 1; + } + if (sensors->caps.num_sensors >= 1) { snprintf(attr->name, sizeof(attr->name), "power%d_label", s); attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, 0, 0); -- 1.8.3.1
[PATCH v2 1/2] hwmon (occ): Fix license headers
Files have inconsistent license information. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 3 ++- drivers/hwmon/occ/common.h | 3 ++- drivers/hwmon/occ/p8_i2c.c | 3 ++- drivers/hwmon/occ/p9_sbe.c | 3 ++- drivers/hwmon/occ/sysfs.c | 13 ++--- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 391118c..b91a80a 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 7c44df3..ed2cf42 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -1,4 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright IBM Corp 2019 */ #ifndef OCC_COMMON_H #define OCC_COMMON_H diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index b59efc9..76fb787 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c index b65c1d1..f6387cc 100644 --- a/drivers/hwmon/occ/p9_sbe.c +++ b/drivers/hwmon/occ/p9_sbe.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index 743b26ec..fe3d15e 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -1,14 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * OCC hwmon driver sysfs interface - * - * Copyright (C) IBM Corporation 2018 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include -- 1.8.3.1
[PATCH v2 2/2] fsi: occ: Fix license header
OCC driver source is missing the IBM Copyright Signed-off-by: Eddie James --- drivers/fsi/fsi-occ.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c index a2301ce..d75038f 100644 --- a/drivers/fsi/fsi-occ.c +++ b/drivers/fsi/fsi-occ.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include -- 1.8.3.1
[PATCH v2 0/2] occ: FSI and hwmon: Fix license headers
From: Eddie James Source files for the FSI OCC and OCC hwmon drivers had a combination of bad license information and missing IBM copyright. Correct the licenses. Changes since v1: - correct header comment style Eddie James (2): hwmon (occ): Fix license headers fsi: occ: Fix license header drivers/fsi/fsi-occ.c | 3 ++- drivers/hwmon/occ/common.c | 3 ++- drivers/hwmon/occ/common.h | 3 ++- drivers/hwmon/occ/p8_i2c.c | 3 ++- drivers/hwmon/occ/p9_sbe.c | 3 ++- drivers/hwmon/occ/sysfs.c | 13 ++--- 6 files changed, 12 insertions(+), 16 deletions(-) -- 1.8.3.1
Re: [PATCH 1/2] hwmon (occ): Fix license headers
On 01/21/2019 02:19 PM, Joel Stanley wrote: On Tue, 22 Jan 2019 at 06:56, Eddie James wrote: --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -1,4 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 We want to use the /* */ syntax for headers. Take a closer look at Documentation/process/license-rules.rst: Oops. Do I put the copyright in the copyright in the same comment block then? Or use // for that? 2. Style: The SPDX license identifier is added in form of a comment. The comment style depends on the file type:: C source: // SPDX-License-Identifier: C header: /* SPDX-License-Identifier: */
[PATCH 1/2] hwmon (occ): Fix license headers
Files have inconsistent license information. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 3 ++- drivers/hwmon/occ/common.h | 3 ++- drivers/hwmon/occ/p8_i2c.c | 3 ++- drivers/hwmon/occ/p9_sbe.c | 3 ++- drivers/hwmon/occ/sysfs.c | 13 ++--- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 391118c..b91a80a 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 7c44df3..55d4967 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -1,4 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #ifndef OCC_COMMON_H #define OCC_COMMON_H diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index b59efc9..76fb787 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c index b65c1d1..f6387cc 100644 --- a/drivers/hwmon/occ/p9_sbe.c +++ b/drivers/hwmon/occ/p9_sbe.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index 743b26ec..fe3d15e 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -1,14 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * OCC hwmon driver sysfs interface - * - * Copyright (C) IBM Corporation 2018 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - */ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include -- 1.8.3.1
[PATCH 2/2] fsi: occ: Fix license header
OCC driver source is missing the IBM Copyright Signed-off-by: Eddie James --- drivers/fsi/fsi-occ.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c index a2301ce..d75038f 100644 --- a/drivers/fsi/fsi-occ.c +++ b/drivers/fsi/fsi-occ.c @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 #include #include -- 1.8.3.1
[PATCH 0/2] occ: FSI and hwmon: Fix license headers
Source files for the FSI OCC and OCC hwmon drivers had a combination of bad license information and missing IBM copyright. Correct the licenses. Eddie James (2): hwmon (occ): Fix license headers fsi: occ: Fix license header drivers/fsi/fsi-occ.c | 3 ++- drivers/hwmon/occ/common.c | 3 ++- drivers/hwmon/occ/common.h | 3 ++- drivers/hwmon/occ/p8_i2c.c | 3 ++- drivers/hwmon/occ/p9_sbe.c | 3 ++- drivers/hwmon/occ/sysfs.c | 13 ++--- 6 files changed, 12 insertions(+), 16 deletions(-) -- 1.8.3.1
Re: [PATCH] hwmon (occ): Fix potential integer overflow
On 01/07/2019 12:34 PM, Gustavo A. R. Silva wrote: Cast get_unaligned_be32(...) to u64 in order to give the compiler complete information about the proper arithmetic to use and avoid a potential integer overflow. Notice that such function call is used in contexts that expect expressions of type u64 (64 bits, unsigned); and the following expressions are currently being evaluated using 32-bit arithmetic: val = get_unaligned_be32(&power->update_tag) * occ->powr_sample_time_us; val = get_unaligned_be32(&power->vdn.update_tag) * occ->powr_sample_time_us; Thanks, Reviewed-by: Eddie James Addresses-Coverity-ID: 1442357 ("Unintentional integer overflow") Addresses-Coverity-ID: 1442476 ("Unintentional integer overflow") Addresses-Coverity-ID: 1442508 ("Unintentional integer overflow") Fixes: ff692d80b2e2 ("hwmon (occ): Add sensor types and versions") Cc: sta...@vger.kernel.org Signed-off-by: Gustavo A. R. Silva --- drivers/hwmon/occ/common.c | 24 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 423903f87955..391118c8aae8 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -380,8 +380,8 @@ static ssize_t occ_show_power_1(struct device *dev, val *= 100ULL; break; case 2: - val = get_unaligned_be32(&power->update_tag) * - occ->powr_sample_time_us; + val = (u64)get_unaligned_be32(&power->update_tag) * + occ->powr_sample_time_us; break; case 3: val = get_unaligned_be16(&power->value) * 100ULL; @@ -425,8 +425,8 @@ static ssize_t occ_show_power_2(struct device *dev, &power->update_tag); break; case 2: - val = get_unaligned_be32(&power->update_tag) * - occ->powr_sample_time_us; + val = (u64)get_unaligned_be32(&power->update_tag) * + occ->powr_sample_time_us; break; case 3: val = get_unaligned_be16(&power->value) * 100ULL; @@ -463,8 +463,8 @@ static ssize_t occ_show_power_a0(struct device *dev, &power->system.update_tag); break; case 2: - val = get_unaligned_be32(&power->system.update_tag) * - occ->powr_sample_time_us; + val = (u64)get_unaligned_be32(&power->system.update_tag) * + occ->powr_sample_time_us; break; case 3: val = get_unaligned_be16(&power->system.value) * 100ULL; @@ -477,8 +477,8 @@ static ssize_t occ_show_power_a0(struct device *dev, &power->proc.update_tag); break; case 6: - val = get_unaligned_be32(&power->proc.update_tag) * - occ->powr_sample_time_us; + val = (u64)get_unaligned_be32(&power->proc.update_tag) * + occ->powr_sample_time_us; break; case 7: val = get_unaligned_be16(&power->proc.value) * 100ULL; @@ -491,8 +491,8 @@ static ssize_t occ_show_power_a0(struct device *dev, &power->vdd.update_tag); break; case 10: - val = get_unaligned_be32(&power->vdd.update_tag) * - occ->powr_sample_time_us; + val = (u64)get_unaligned_be32(&power->vdd.update_tag) * + occ->powr_sample_time_us; break; case 11: val = get_unaligned_be16(&power->vdd.value) * 100ULL; @@ -505,8 +505,8 @@ static ssize_t occ_show_power_a0(struct device *dev, &power->vdn.update_tag); break; case 14: - val = get_unaligned_be32(&power->vdn.update_tag) * - occ->powr_sample_time_us; + val = (u64)get_unaligned_be32(&power->vdn.update_tag) * + occ->powr_sample_time_us; break; case 15: val = get_unaligned_be16(&power->vdn.value) * 100ULL;
Re: [PATCH v6 00/10] hwmon and fsi: Add On-Chip Controller Driver
On 11/09/2018 05:03 PM, Guenter Roeck wrote: On Thu, Nov 08, 2018 at 03:05:19PM -0600, Eddie James wrote: From: Eddie James This series adds a hwmon driver to support the OCC on POWER8 and POWER9 processors. The OCC is an embedded processor that provides realtime power and thermal monitoring and management. The series also adds a "bus" driver to handle atomic communication between the service processor and the OCC on a POWER9 chip. This communication takes place over FSI bus to the SBE (Self-Boot engine) FIFO, which in turn communicates with the OCC. The driver for the SBEFIFO is already available as an FSI client driver. For POWER8 OCCs, communication between the service processor and the OCC is achieved over I2C bus. I am not entirely happy with the series - there are still lots of proprietary attributes, and I would have preferred the use of the _info API at this point - but this has taken long enough. Series applied to hwmon-next. Please send any fixes as follow-up patches. Thanks a lot Guenter, appreciate your help getting here. Eddie Thanks, Guenter Changes since v5: * Makefile fix when compiling both P8 and P9 versions * Spelling fix in hwmon doc * Added an additional sentence for P9 binding doc to explain that OCC isn't an FSI slave device. Changes since v4: * Make the hwmon attributes conform almost completely to standard names and values. The only exception is powerX_cap_user and powerX_cap_user_source. * Improve hwmon documentation. * Add ibm,p9-occ dt documentation. Changes since v3: * Add the FSI OCC driver. * Pull the sysfs attribute code into it's own file for cleanliness. * Various fixes for attribute creation and integer overflow. Changes since v2: * Add sysfs_notify for the error and throttling attributes when change is detected. * Removed occs_present counting of devices bound. * Improved remove() of P9 driver to avoid bad behavior with relation to OCC driver when unbound. * Added default cases (return EINVAL) for all sensor show functions. * Added temperature fault sensor. * Added back dt binding documentation for P9 to address checkpatch warning. * Added occs_present attribute from the poll response. Changes since v1: * Remove wait loop in P9 code, as that is now handled by FSI OCC driver. * Removed dt binding documentation for P9, FSI OCC driver will probe OCC hwmon driver automatically. * Moved OCC response code definitions to the OCC include file. * Fixed includes. * Changed some structure fields to __beXX as that is what they are. * Changed some errnos. * Removed some dev_err(). * Refactored P8 code a bit to use #defined addresses and magic values, and changed "goto retry" to a loop. * Refactored error handling a bit. Eddie James (10): dt-bindings: fsi: Add P9 OCC device documentation fsi: Add On-Chip Controller (OCC) driver Documentation: hwmon: Add OCC documentation dt-bindings: i2c: Add P8 OCC hwmon device documentation hwmon: Add On-Chip Controller (OCC) hwmon driver hwmon (occ): Add command transport method for P8 and P9 hwmon (occ): Parse OCC poll response hwmon (occ): Add sensor types and versions hwmon (occ): Add sensor attributes and register hwmon device hwmon (occ): Add sysfs attributes for additional OCC data .../devicetree/bindings/fsi/ibm,p9-occ.txt | 16 + .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt | 25 + Documentation/hwmon/occ| 112 ++ drivers/fsi/Kconfig| 10 + drivers/fsi/Makefile |1 + drivers/fsi/fsi-occ.c | 599 +++ drivers/hwmon/Kconfig |2 + drivers/hwmon/Makefile |1 + drivers/hwmon/occ/Kconfig | 31 + drivers/hwmon/occ/Makefile |5 + drivers/hwmon/occ/common.c | 1098 drivers/hwmon/occ/common.h | 128 +++ drivers/hwmon/occ/p8_i2c.c | 255 + drivers/hwmon/occ/p9_sbe.c | 106 ++ drivers/hwmon/occ/sysfs.c | 188 include/linux/fsi-occ.h| 25 + 16 files changed, 2602 insertions(+) create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt create mode 100644 Documentation/hwmon/occ create mode 100644 drivers/fsi/fsi-occ.c create mode 100644 drivers/hwmon/occ/Kconfig create mode 100644 drivers/hwmon/occ/Makefile create mode 100644 drivers/hwmon/occ/common.c create mode 100644 drivers/hwmon/occ/common.h create mode 100644 drivers/hwmon/occ/p8_i2c.c create mode 100644 drivers/hwmon/occ/p9_sbe.c create mode 100644 drivers/h
[PATCH v6 02/10] fsi: Add On-Chip Controller (OCC) driver
From: Eddie James The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system. This driver provides an atomic communications channel between a service processor (e.g. a BMC) and the OCC. The driver is dependent on the FSI SBEFIFO driver to get hardware access through the SBE to the OCC SRAM. Commands are issued to the SBE to send or fetch data to the SRAM. Signed-off-by: Eddie James Signed-off-by: Andrew Jeffery Signed-off-by: Benjamin Herrenschmidt Signed-off-by: Joel Stanley --- drivers/fsi/Kconfig | 10 + drivers/fsi/Makefile| 1 + drivers/fsi/fsi-occ.c | 599 include/linux/fsi-occ.h | 25 ++ 4 files changed, 635 insertions(+) create mode 100644 drivers/fsi/fsi-occ.c create mode 100644 include/linux/fsi-occ.h diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index af3a20d..ea2f4a1 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -64,4 +64,14 @@ config FSI_SBEFIFO a pipe-like FSI device for communicating with the self boot engine (SBE) on POWER processors. +config FSI_OCC + tristate "OCC SBEFIFO client device driver" + depends on FSI_SBEFIFO + ---help--- + This option enables an SBEFIFO based On-Chip Controller (OCC) device + driver. The OCC is a device embedded on a POWER processor that collects + and aggregates sensor data from the processor and system. The OCC can + provide the raw sensor data as well as perform thermal and power + management on the system. + endif diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index a50d6ce..62687ec 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o +obj-$(CONFIG_FSI_OCC) += fsi-occ.o diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c new file mode 100644 index 000..a2301ce --- /dev/null +++ b/drivers/fsi/fsi-occ.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OCC_SRAM_BYTES 4096 +#define OCC_CMD_DATA_BYTES 4090 +#define OCC_RESP_DATA_BYTES4089 + +#define OCC_SRAM_CMD_ADDR 0xFFFBE000 +#define OCC_SRAM_RSP_ADDR 0xFFFBF000 + +/* + * Assume we don't have much FFDC, if we do we'll overflow and + * fail the command. This needs to be big enough for simple + * commands as well. + */ +#define OCC_SBE_STATUS_WORDS 32 + +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +struct occ { + struct device *dev; + struct device *sbefifo; + char name[32]; + int idx; + struct miscdevice mdev; + struct mutex occ_lock; +}; + +#define to_occ(x) container_of((x), struct occ, mdev) + +struct occ_response { + u8 seq_no; + u8 cmd_type; + u8 return_status; + __be16 data_length; + u8 data[OCC_RESP_DATA_BYTES + 2]; /* two bytes checksum */ +} __packed; + +struct occ_client { + struct occ *occ; + struct mutex lock; + size_t data_size; + size_t read_offset; + u8 *buffer; +}; + +#define to_client(x) container_of((x), struct occ_client, xfr) + +static DEFINE_IDA(occ_ida); + +static int occ_open(struct inode *inode, struct file *file) +{ + struct occ_client *client = kzalloc(sizeof(*client), GFP_KERNEL); + struct miscdevice *mdev = file->private_data; + struct occ *occ = to_occ(mdev); + + if (!client) + return -ENOMEM; + + client->buffer = (u8 *)__get_free_page(GFP_KERNEL); + if (!client->buffer) { + kfree(client); + return -ENOMEM; + } + + client->occ = occ; + mutex_init(&client->lock); + file->private_data = client; + + /* We allocate a 1-page buffer, make sure it all fits */ + BUILD_BUG_ON((OCC_CMD_DATA_BYTES + 3) > PAGE_SIZE); + BUILD_BUG_ON((OCC_RESP_DATA_BYTES + 7) > PAGE_SIZE); + + return 0; +} + +static ssize_t occ_read(struct file *file, char __user *buf, size_t len, + loff_t *offset) +{ + struct occ_client *client = file->private_data; + ssize_t rc = 0; + + if (!client) + return -ENODEV; + + if (len > OCC_SRAM_BYTES) + return -EINVAL; + + mutex_lock(&client->lock); + + /* This should not be possible ... */ + if (WARN_ON_ONCE(client->read_offset > client-
[PATCH v6 09/10] hwmon (occ): Add sensor attributes and register hwmon device
From: Eddie James Setup the sensor attributes for every OCC sensor found by the first poll response. Register the attributes with hwmon. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 337 + drivers/hwmon/occ/common.h | 16 +++ 2 files changed, 353 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index f7220132..c6c8161 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include #include #include +#include #include #include "common.h" @@ -641,6 +643,324 @@ static ssize_t occ_show_extended(struct device *dev, return rc; } +/* + * Some helper macros to make it easier to define an occ_attribute. Since these + * are dynamically allocated, we shouldn't use the existing kernel macros which + * stringify the name argument. + */ +#define ATTR_OCC(_name, _mode, _show, _store) { \ + .attr = { \ + .name = _name, \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode),\ + }, \ + .show = _show,\ + .store = _store, \ +} + +#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) {\ + .dev_attr = ATTR_OCC(_name, _mode, _show, _store),\ + .index = _index, \ + .nr = _nr, \ +} + +#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index) \ + ((struct sensor_device_attribute_2) \ + SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index)) + +/* + * Allocate and instatiate sensor_device_attribute_2s. It's most efficient to + * use our own instead of the built-in hwmon attribute types. + */ +static int occ_setup_sensor_attrs(struct occ *occ) +{ + unsigned int i, s, num_attrs = 0; + struct device *dev = occ->bus_dev; + struct occ_sensors *sensors = &occ->sensors; + struct occ_attribute *attr; + struct temp_sensor_2 *temp; + ssize_t (*show_temp)(struct device *, struct device_attribute *, +char *) = occ_show_temp_1; + ssize_t (*show_freq)(struct device *, struct device_attribute *, +char *) = occ_show_freq_1; + ssize_t (*show_power)(struct device *, struct device_attribute *, + char *) = occ_show_power_1; + ssize_t (*show_caps)(struct device *, struct device_attribute *, +char *) = occ_show_caps_1_2; + + switch (sensors->temp.version) { + case 1: + num_attrs += (sensors->temp.num_sensors * 2); + break; + case 2: + num_attrs += (sensors->temp.num_sensors * 4); + show_temp = occ_show_temp_2; + break; + default: + sensors->temp.num_sensors = 0; + } + + switch (sensors->freq.version) { + case 2: + show_freq = occ_show_freq_2; + /* fall through */ + case 1: + num_attrs += (sensors->freq.num_sensors * 2); + break; + default: + sensors->freq.num_sensors = 0; + } + + switch (sensors->power.version) { + case 2: + show_power = occ_show_power_2; + /* fall through */ + case 1: + num_attrs += (sensors->power.num_sensors * 4); + break; + case 0xA0: + num_attrs += (sensors->power.num_sensors * 16); + show_power = occ_show_power_a0; + break; + default: + sensors->power.num_sensors = 0; + } + + switch (sensors->caps.version) { + case 1: + num_attrs += (sensors->caps.num_sensors * 7); + break; + case 3: + show_caps = occ_show_caps_3; + /* fall through */ + case 2: + num_attrs += (sensors->caps.num_sensors * 8); + break; + default: + sensors->caps.num_sensors = 0; + } + + switch (sensors->extended.version) { + case 1: + num_attrs += (sensors->extended.num_sensors * 3); + break; + default: + sensors->extended.num_sensors = 0; + } + + occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * num_attrs, + GFP_KERNEL);
[PATCH v6 03/10] Documentation: hwmon: Add OCC documentation
From: Eddie James Document the hwmon interface for the OCC. Signed-off-by: Eddie James --- Documentation/hwmon/occ | 112 1 file changed, 112 insertions(+) create mode 100644 Documentation/hwmon/occ diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ new file mode 100644 index 000..e787596 --- /dev/null +++ b/Documentation/hwmon/occ @@ -0,0 +1,112 @@ +Kernel driver occ-hwmon +=== + +Supported chips: + * POWER8 + * POWER9 + +Author: Eddie James + +Description +--- + +This driver supports hardware monitoring for the On-Chip Controller (OCC) +embedded on POWER processors. The OCC is a device that collects and aggregates +sensor data from the processor and the system. The OCC can provide the raw +sensor data as well as perform thermal and power management on the system. + +The P8 version of this driver is a client driver of I2C. It may be probed +manually if an "ibm,p8-occ-hwmon" compatible device is found under the +appropriate I2C bus node in the device-tree. + +The P9 version of this driver is a client driver of the FSI-based OCC driver. +It will be probed automatically by the FSI-based OCC driver. + +Sysfs entries +- + +The following attributes are supported. All attributes are read-only unless +specified. + +The OCC sensor ID is an integer that represents the unique identifier of the +sensor with respect to the OCC. For example, a temperature sensor for the third +DIMM slot in the system may have a sensor ID of 7. This mapping is unavailable +to the device driver, which must therefore export the sensor ID as-is. + +Some entries are only present with certain OCC sensor versions or only on +certain OCCs in the system. The version number is not exported to the user +but can be inferred. + +temp[1-n]_labelOCC sensor ID. +[with temperature sensor version 1] +temp[1-n]_inputMeasured temperature of the component in millidegrees + Celsius. +[with temperature sensor version >= 2] +temp[1-n]_type The FRU (Field Replaceable Unit) type + (represented by an integer) for the component + that this sensor measures. +temp[1-n]_faultTemperature sensor fault boolean; 1 to indicate + that a fault is present or 0 to indicate that + no fault is present. +[with type == 3 (FRU type is VRM)] +temp[1-n]_alarmVRM temperature alarm boolean; 1 to indicate + alarm, 0 to indicate no alarm +[else] +temp[1-n]_inputMeasured temperature of the component in + millidegrees Celsius. + +freq[1-n]_labelOCC sensor ID. +freq[1-n]_inputMeasured frequency of the component in MHz. + +power[1-n]_input Latest measured power reading of the component in + microwatts. +power[1-n]_average Average power of the component in microwatts. +power[1-n]_average_intervalThe amount of time over which the power average + was taken in microseconds. +[with power sensor version < 2] +power[1-n]_label OCC sensor ID. +[with power sensor version >= 2] +power[1-n]_label OCC sensor ID + function ID + channel in the form + of a string, delimited by underscores, i.e. "0_15_1". + Both the function ID and channel are integers that + further identify the power sensor. +[with power sensor version 0xa0] +power[1-n]_label OCC sensor ID + sensor type in the form of a string, + delimited by an underscore, i.e. "0_system". Sensor + type will be one of "system", "proc", "vdd" or "vdn". + For this sensor version, OCC sensor ID will be the same + for all power sensors. +[present only on "master" OCC; represents the whole system power; only one of + this type of power sensor will be present] +power[1-n]_label "system" +power[1-n]_input Latest system output power in microwatts. +power[1-n]_cap Current system power cap in microwatts. +power[1-n]_cap_not_redundant System power cap in microwatts when + there is not redundant power. +power[1-n]_cap_max Maximum power cap that the OCC can enforce in + microwatts. +power[1-n]_cap_min Minimum power cap that the OCC can enforce in + microwatts. +power[1-n]_cap_userThe power cap set by the user, in microwatts. + This attribute will retur
[PATCH v6 04/10] dt-bindings: i2c: Add P8 OCC hwmon device documentation
From: Eddie James Document the bindings for I2C-based OCC hwmon device. Signed-off-by: Eddie James Acked-by: Rob Herring --- .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt | 25 ++ 1 file changed, 25 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt diff --git a/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt new file mode 100644 index 000..5dc5d2e --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt @@ -0,0 +1,25 @@ +Device-tree bindings for I2C-based On-Chip Controller hwmon device +-- + +Required properties: + - compatible = "ibm,p8-occ-hwmon"; + - reg = ;: I2C bus address + +Examples: + +i2c-bus@100 { +#address-cells = <1>; +#size-cells = <0>; +clock-frequency = <10>; +< more properties > + +occ-hwmon@1 { +compatible = "ibm,p8-occ-hwmon"; +reg = <0x50>; +}; + +occ-hwmon@2 { +compatible = "ibm,p8-occ-hwmon"; +reg = <0x51>; +}; +}; -- 1.8.3.1
[PATCH v6 08/10] hwmon (occ): Add sensor types and versions
From: Eddie James Add structures to define all sensor types and versions. Add sysfs show and store functions for each sensor type. Add a method to construct the "set user power cap" command and send it to the OCC. Add rate limit to polling the OCC (in case user-space reads our hwmon entries rapidly). Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 621 + drivers/hwmon/occ/common.h | 6 + drivers/hwmon/occ/p8_i2c.c | 1 + drivers/hwmon/occ/p9_sbe.c | 1 + 4 files changed, 629 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index a066509..f7220132 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,10 +1,116 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include +#include #include +#include +#include +#include #include "common.h" +#define EXTN_FLAG_SENSOR_IDBIT(7) + +#define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000) + +#define OCC_TEMP_SENSOR_FAULT 0xFF + +#define OCC_FRU_TYPE_VRM 3 + +/* OCC sensor type and version definitions */ + +struct temp_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct temp_sensor_2 { + u32 sensor_id; + u8 fru_type; + u8 value; +} __packed; + +struct freq_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct freq_sensor_2 { + u32 sensor_id; + u16 value; +} __packed; + +struct power_sensor_1 { + u16 sensor_id; + u32 update_tag; + u32 accumulator; + u16 value; +} __packed; + +struct power_sensor_2 { + u32 sensor_id; + u8 function_id; + u8 apss_channel; + u16 reserved; + u32 update_tag; + u64 accumulator; + u16 value; +} __packed; + +struct power_sensor_data { + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_data_and_time { + u16 update_time; + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_a0 { + u32 sensor_id; + struct power_sensor_data_and_time system; + u32 reserved; + struct power_sensor_data_and_time proc; + struct power_sensor_data vdd; + struct power_sensor_data vdn; +} __packed; + +struct caps_sensor_2 { + u16 cap; + u16 system_power; + u16 n_cap; + u16 max; + u16 min; + u16 user; + u8 user_source; +} __packed; + +struct caps_sensor_3 { + u16 cap; + u16 system_power; + u16 n_cap; + u16 max; + u16 hard_min; + u16 soft_min; + u16 user; + u8 user_source; +} __packed; + +struct extended_sensor { + union { + u8 name[4]; + u32 sensor_id; + }; + u8 flags; + u8 reserved; + u8 data[6]; +} __packed; + static int occ_poll(struct occ *occ) { u16 checksum = occ->poll_cmd_data + 1; @@ -20,9 +126,521 @@ static int occ_poll(struct occ *occ) cmd[6] = checksum & 0xFF; /* checksum lsb */ cmd[7] = 0; + /* mutex should already be locked if necessary */ return occ->send_cmd(occ, cmd); } +static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) +{ + int rc; + u8 cmd[8]; + u16 checksum = 0x24; + __be16 user_power_cap_be = cpu_to_be16(user_power_cap); + + cmd[0] = 0; + cmd[1] = 0x22; + cmd[2] = 0; + cmd[3] = 2; + + memcpy(&cmd[4], &user_power_cap_be, 2); + + checksum += cmd[4] + cmd[5]; + cmd[6] = checksum >> 8; + cmd[7] = checksum & 0xFF; + + rc = mutex_lock_interruptible(&occ->lock); + if (rc) + return rc; + + rc = occ->send_cmd(occ, cmd); + + mutex_unlock(&occ->lock); + + return rc; +} + +static int occ_update_response(struct occ *occ) +{ + int rc = mutex_lock_interruptible(&occ->lock); + + if (rc) + return rc; + + /* limit the maximum rate of polling the OCC */ + if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) { + rc = occ_poll(occ); + occ->last_update = jiffies; + } + + mutex_unlock(&occ->lock); + return rc; +} + +static ssize_t occ_show_temp_1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct temp_sensor_1 *temp; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + temp = ((struct temp_sensor_1 *)sensors->temp.data) + sattr->index; + + switch (sattr->nr) { + case
[PATCH v6 10/10] hwmon (occ): Add sysfs attributes for additional OCC data
From: Eddie James The OCC provides a variety of additional information about the state of the host processor, such as throttling, error conditions, and the number of OCCs detected in the system. This information is essential to service processor applications such as fan control and host management. Therefore, export this data in the form of sysfs attributes attached to the platform device (to which the hwmon device is also attached). Signed-off-by: Eddie James --- drivers/hwmon/occ/Makefile | 4 +- drivers/hwmon/occ/common.c | 45 ++- drivers/hwmon/occ/common.h | 17 drivers/hwmon/occ/p8_i2c.c | 10 +++ drivers/hwmon/occ/p9_sbe.c | 1 + drivers/hwmon/occ/sysfs.c | 188 + 6 files changed, 260 insertions(+), 5 deletions(-) create mode 100644 drivers/hwmon/occ/sysfs.c diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile index 57c0e91..3fec12d 100644 --- a/drivers/hwmon/occ/Makefile +++ b/drivers/hwmon/occ/Makefile @@ -1,5 +1,5 @@ -occ-p8-hwmon-objs := common.o p8_i2c.o -occ-p9-hwmon-objs := common.o p9_sbe.o +occ-p8-hwmon-objs := common.o sysfs.o p8_i2c.o +occ-p9-hwmon-objs := common.o sysfs.o p9_sbe.o obj-$(CONFIG_SENSORS_OCC_P8_I2C) += occ-p8-hwmon.o obj-$(CONFIG_SENSORS_OCC_P9_SBE) += occ-p9-hwmon.o diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index c6c8161..423903f 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -14,6 +14,11 @@ #define EXTN_FLAG_SENSOR_IDBIT(7) +#define OCC_ERROR_COUNT_THRESHOLD 2 /* required by OCC spec */ + +#define OCC_STATE_SAFE 4 +#define OCC_SAFE_TIMEOUT msecs_to_jiffies(6) /* 1 min */ + #define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000) #define OCC_TEMP_SENSOR_FAULT 0xFF @@ -115,8 +120,10 @@ struct extended_sensor { static int occ_poll(struct occ *occ) { + int rc; u16 checksum = occ->poll_cmd_data + 1; u8 cmd[8]; + struct occ_poll_response_header *header; /* big endian */ cmd[0] = 0; /* sequence number */ @@ -129,7 +136,35 @@ static int occ_poll(struct occ *occ) cmd[7] = 0; /* mutex should already be locked if necessary */ - return occ->send_cmd(occ, cmd); + rc = occ->send_cmd(occ, cmd); + if (rc) { + if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD) + occ->error = rc; + + goto done; + } + + /* clear error since communication was successful */ + occ->error_count = 0; + occ->error = 0; + + /* check for safe state */ + header = (struct occ_poll_response_header *)occ->resp.data; + if (header->occ_state == OCC_STATE_SAFE) { + if (occ->last_safe) { + if (time_after(jiffies, + occ->last_safe + OCC_SAFE_TIMEOUT)) + occ->error = -EHOSTDOWN; + } else { + occ->last_safe = jiffies; + } + } else { + occ->last_safe = 0; + } + +done: + occ_sysfs_poll_done(occ); + return rc; } static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) @@ -161,7 +196,7 @@ static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) return rc; } -static int occ_update_response(struct occ *occ) +int occ_update_response(struct occ *occ) { int rc = mutex_lock_interruptible(&occ->lock); @@ -1055,5 +1090,9 @@ int occ_setup(struct occ *occ, const char *name) return rc; } - return 0; + rc = occ_setup_sysfs(occ); + if (rc) + dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc); + + return rc; } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 00ac101..da80e65 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -104,8 +104,25 @@ struct occ { struct occ_attribute *attrs; struct attribute_group group; const struct attribute_group *groups[2]; + + int error; /* latest transfer error */ + unsigned int error_count; /* number of xfr errors observed */ + unsigned long last_safe;/* time OCC entered "safe" state */ + + /* +* Store the previous state data for comparison in order to notify +* sysfs readers of state changes. +*/ + int prev_error; + u8 prev_stat; + u8 prev_ext_stat; + u8 prev_occs_present; }; int occ_setup(struct occ *occ, const char *name); +int occ_setup_sysfs(struct occ *occ); +void occ_shutdown(struct occ *occ); +void occ_sysfs_poll_done(struct occ *occ); +int occ_update_response(struct occ *occ); #endif /* OCC_COMMON_H */ diff --
[PATCH v6 01/10] dt-bindings: fsi: Add P9 OCC device documentation
From: Eddie James Document the bindings for the FSI-attached POWER9 On-Chip Controller. Signed-off-by: Eddie James --- Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt | 16 1 file changed, 16 insertions(+) create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt diff --git a/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt new file mode 100644 index 000..99ca986 --- /dev/null +++ b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt @@ -0,0 +1,16 @@ +Device-tree bindings for FSI-attached POWER9 On-Chip Controller (OCC) +- + +This is the binding for the P9 On-Chip Controller accessed over FSI from a +service processor. See fsi.txt for details on bindings for FSI slave and CFAM +nodes. The OCC is not an FSI slave device itself, rather it is accessed +through the SBE fifo. + +Required properties: + - compatible = "ibm,p9-occ" + +Examples: + +occ { +compatible = "ibm,p9-occ"; +}; -- 1.8.3.1
[PATCH v6 06/10] hwmon (occ): Add command transport method for P8 and P9
From: Eddie James For the P8 OCC, add the procedure to send a command to the OCC over I2C bus. This involves writing the OCC command registers with serial communication operations (SCOMs) interpreted by the I2C slave. For the P9 OCC, add a procedure to use the OCC in-kernel API to send a command to the OCC through the SBE. Signed-off-by: Eddie James --- drivers/hwmon/occ/p8_i2c.c | 185 - drivers/hwmon/occ/p9_sbe.c | 38 +- 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index 4f3937d..e3326ff 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -2,11 +2,29 @@ #include #include +#include #include +#include #include +#include +#include #include "common.h" +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +/* OCB (on-chip control bridge - interface to OCC) registers */ +#define OCB_DATA1 0x6B035 +#define OCB_ADDR 0x6B070 +#define OCB_DATA3 0x6B075 + +/* OCC SRAM address space */ +#define OCC_SRAM_ADDR_CMD 0x6000 +#define OCC_SRAM_ADDR_RESP 0x7000 + +#define OCC_DATA_ATTN 0x2001 + struct p8_i2c_occ { struct occ occ; struct i2c_client *client; @@ -14,9 +32,174 @@ struct p8_i2c_occ { #define to_p8_i2c_occ(x) container_of((x), struct p8_i2c_occ, occ) +static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data) +{ + ssize_t rc; + __be64 buf; + struct i2c_msg msgs[2]; + + /* p8 i2c slave requires shift */ + address <<= 1; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags & I2C_M_TEN; + msgs[0].len = sizeof(u32); + /* address is a scom address; bus-endian */ + msgs[0].buf = (char *)&address; + + /* data from OCC is big-endian */ + msgs[1].addr = client->addr; + msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[1].len = sizeof(u64); + msgs[1].buf = (char *)&buf; + + rc = i2c_transfer(client->adapter, msgs, 2); + if (rc < 0) + return rc; + + *(u64 *)data = be64_to_cpu(buf); + + return 0; +} + +static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data) +{ + u32 buf[3]; + ssize_t rc; + + /* p8 i2c slave requires shift */ + address <<= 1; + + /* address is bus-endian; data passed through from user as-is */ + buf[0] = address; + memcpy(&buf[1], &data[4], sizeof(u32)); + memcpy(&buf[2], data, sizeof(u32)); + + rc = i2c_master_send(client, (const char *)buf, sizeof(buf)); + if (rc < 0) + return rc; + else if (rc != sizeof(buf)) + return -EIO; + + return 0; +} + +static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address, + u32 data0, u32 data1) +{ + u8 buf[8]; + + memcpy(buf, &data0, 4); + memcpy(buf + 4, &data1, 4); + + return p8_i2c_occ_putscom(client, address, buf); +} + +static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address, +u8 *data) +{ + __be32 data0, data1; + + memcpy(&data0, data, 4); + memcpy(&data1, data + 4, 4); + + return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0), + be32_to_cpu(data1)); +} + static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd) { - return -EOPNOTSUPP; + int i, rc; + unsigned long start; + u16 data_length; + const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS); + const long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); + struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ); + struct i2c_client *client = ctx->client; + struct occ_response *resp = &occ->resp; + + start = jiffies; + + /* set sram address for command */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0); + if (rc) + return rc; + + /* write command (expected to already be BE), we need bus-endian... */ + rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd); + if (rc) + return rc; + + /* trigger OCC attention */ + rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0); + if (rc) + return rc; + + do { + /* set sram address for response */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, + OCC_SRAM_ADDR_RESP, 0); + if (rc) + return rc; + + rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8
[PATCH v6 07/10] hwmon (occ): Parse OCC poll response
From: Eddie James Add method to parse the response from the OCC poll command. This only needs to be done during probe(), since the OCC shouldn't change the number or format of sensors while it's running. The parsed response allows quick access to sensor data, as well as information on the number and version of sensors, which we need to instantiate hwmon attributes. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 61 ++ drivers/hwmon/occ/common.h | 55 + 2 files changed, 116 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 81acd4b..a066509 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include "common.h" @@ -22,6 +23,64 @@ static int occ_poll(struct occ *occ) return occ->send_cmd(occ, cmd); } +/* only need to do this once at startup, as OCC won't change sensors on us */ +static void occ_parse_poll_response(struct occ *occ) +{ + unsigned int i, old_offset, offset = 0, size = 0; + struct occ_sensor *sensor; + struct occ_sensors *sensors = &occ->sensors; + struct occ_response *resp = &occ->resp; + struct occ_poll_response *poll = + (struct occ_poll_response *)&resp->data[0]; + struct occ_poll_response_header *header = &poll->header; + struct occ_sensor_data_block *block = &poll->block; + + dev_info(occ->bus_dev, "OCC found, code level: %.16s\n", +header->occ_code_level); + + for (i = 0; i < header->num_sensor_data_blocks; ++i) { + block = (struct occ_sensor_data_block *)((u8 *)block + offset); + old_offset = offset; + offset = (block->header.num_sensors * + block->header.sensor_length) + sizeof(block->header); + size += offset; + + /* validate all the length/size fields */ + if ((size + sizeof(*header)) >= OCC_RESP_DATA_BYTES) { + dev_warn(occ->bus_dev, "exceeded response buffer\n"); + return; + } + + dev_dbg(occ->bus_dev, " %04x..%04x: %.4s (%d sensors)\n", + old_offset, offset - 1, block->header.eye_catcher, + block->header.num_sensors); + + /* match sensor block type */ + if (strncmp(block->header.eye_catcher, "TEMP", 4) == 0) + sensor = &sensors->temp; + else if (strncmp(block->header.eye_catcher, "FREQ", 4) == 0) + sensor = &sensors->freq; + else if (strncmp(block->header.eye_catcher, "POWR", 4) == 0) + sensor = &sensors->power; + else if (strncmp(block->header.eye_catcher, "CAPS", 4) == 0) + sensor = &sensors->caps; + else if (strncmp(block->header.eye_catcher, "EXTN", 4) == 0) + sensor = &sensors->extended; + else { + dev_warn(occ->bus_dev, "sensor not supported %.4s\n", +block->header.eye_catcher); + continue; + } + + sensor->num_sensors = block->header.num_sensors; + sensor->version = block->header.sensor_format; + sensor->data = &block->data; + } + + dev_dbg(occ->bus_dev, "Max resp size: %u+%zd=%zd\n", size, + sizeof(*header), size + sizeof(*header)); +} + int occ_setup(struct occ *occ, const char *name) { int rc; @@ -36,5 +95,7 @@ int occ_setup(struct occ *occ, const char *name) return rc; } + occ_parse_poll_response(occ); + return 0; } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index b44fee2..0a7a107 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -20,10 +20,65 @@ struct occ_response { __be16 checksum; } __packed; +struct occ_sensor_data_block_header { + u8 eye_catcher[4]; + u8 reserved; + u8 sensor_format; + u8 sensor_length; + u8 num_sensors; +} __packed; + +struct occ_sensor_data_block { + struct occ_sensor_data_block_header header; + u32 data; +} __packed; + +struct occ_poll_response_header { + u8 status; + u8 ext_status; + u8 occs_present; + u8 config_data; + u8 occ_state; + u8 mode; + u8 ips_status; + u8 error_log_id; + __be32 error_log_start_address; + __be16 error_log_length; +
[PATCH v6 05/10] hwmon: Add On-Chip Controller (OCC) hwmon driver
From: Eddie James The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system. This driver provides a hwmon interface to the OCC from a service processor (e.g. a BMC). The driver supports both POWER8 and POWER9 OCCs. Communications with the POWER8 OCC are established over standard I2C bus. The driver communicates with the POWER9 OCC through the FSI-based OCC driver, which handles the lower-level communication details. This patch lays out the structure of the OCC hwmon driver. There are two platform drivers, one each for P8 and P9 OCCs. These are probed through the I2C tree and the FSI-based OCC driver, respectively. The patch also defines the first common structures and methods between the two OCC versions. Signed-off-by: Eddie James --- drivers/hwmon/Kconfig | 2 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/occ/Kconfig | 31 + drivers/hwmon/occ/Makefile | 5 drivers/hwmon/occ/common.c | 40 +++ drivers/hwmon/occ/common.h | 34 +++ drivers/hwmon/occ/p8_i2c.c | 61 + drivers/hwmon/occ/p9_sbe.c | 68 ++ 8 files changed, 242 insertions(+) create mode 100644 drivers/hwmon/occ/Kconfig create mode 100644 drivers/hwmon/occ/Makefile create mode 100644 drivers/hwmon/occ/common.c create mode 100644 drivers/hwmon/occ/common.h create mode 100644 drivers/hwmon/occ/p8_i2c.c create mode 100644 drivers/hwmon/occ/p9_sbe.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 81da17a..532a053 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1293,6 +1293,8 @@ config SENSORS_NSA320 This driver can also be built as a module. If so, the module will be called nsa320-hwmon. +source drivers/hwmon/occ/Kconfig + config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 93f7f41..f5c7b44 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -178,6 +178,7 @@ obj-$(CONFIG_SENSORS_WM831X)+= wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE)+= xgene-hwmon.o +obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_PMBUS)+= pmbus/ ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig new file mode 100644 index 000..6668662 --- /dev/null +++ b/drivers/hwmon/occ/Kconfig @@ -0,0 +1,31 @@ +# +# On-Chip Controller configuration +# + +config SENSORS_OCC_P8_I2C + tristate "POWER8 OCC through I2C" + depends on I2C + select SENSORS_OCC + help +This option enables support for monitoring sensors provided by the +On-Chip Controller (OCC) on a POWER8 processor. Communications with +the OCC are established through I2C bus. + +This driver can also be built as a module. If so, the module will be +called occ-p8-hwmon. + +config SENSORS_OCC_P9_SBE + tristate "POWER9 OCC through SBE" + depends on FSI_OCC + select SENSORS_OCC + help +This option enables support for monitoring sensors provided by the +On-Chip Controller (OCC) on a POWER9 processor. Communications with +the OCC are established through SBE fifo on an FSI bus. + +This driver can also be built as a module. If so, the module will be +called occ-p9-hwmon. + +config SENSORS_OCC + bool "POWER On-Chip Controller" + depends on SENSORS_OCC_P8_I2C || SENSORS_OCC_P9_SBE diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile new file mode 100644 index 000..57c0e91 --- /dev/null +++ b/drivers/hwmon/occ/Makefile @@ -0,0 +1,5 @@ +occ-p8-hwmon-objs := common.o p8_i2c.o +occ-p9-hwmon-objs := common.o p9_sbe.o + +obj-$(CONFIG_SENSORS_OCC_P8_I2C) += occ-p8-hwmon.o +obj-$(CONFIG_SENSORS_OCC_P9_SBE) += occ-p9-hwmon.o diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c new file mode 100644 index 000..81acd4b --- /dev/null +++ b/drivers/hwmon/occ/common.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include "common.h" + +static int occ_poll(struct occ *occ) +{ + u16 checksum = occ->poll_cmd_data + 1; + u8 cmd[8]; + + /* big endian */ + cmd[0] = 0; /* sequence number */ + cmd[1] = 0; /* cmd type */ + cmd[2] = 0; /* data length msb */ + cmd[3] = 1; /* data length lsb */ + cmd[4] = occ->poll_cmd_data;/* data */ + cmd[5] = checksum >> 8; /* checksum msb */ + cmd[6]
[PATCH v6 00/10] hwmon and fsi: Add On-Chip Controller Driver
From: Eddie James This series adds a hwmon driver to support the OCC on POWER8 and POWER9 processors. The OCC is an embedded processor that provides realtime power and thermal monitoring and management. The series also adds a "bus" driver to handle atomic communication between the service processor and the OCC on a POWER9 chip. This communication takes place over FSI bus to the SBE (Self-Boot engine) FIFO, which in turn communicates with the OCC. The driver for the SBEFIFO is already available as an FSI client driver. For POWER8 OCCs, communication between the service processor and the OCC is achieved over I2C bus. Changes since v5: * Makefile fix when compiling both P8 and P9 versions * Spelling fix in hwmon doc * Added an additional sentence for P9 binding doc to explain that OCC isn't an FSI slave device. Changes since v4: * Make the hwmon attributes conform almost completely to standard names and values. The only exception is powerX_cap_user and powerX_cap_user_source. * Improve hwmon documentation. * Add ibm,p9-occ dt documentation. Changes since v3: * Add the FSI OCC driver. * Pull the sysfs attribute code into it's own file for cleanliness. * Various fixes for attribute creation and integer overflow. Changes since v2: * Add sysfs_notify for the error and throttling attributes when change is detected. * Removed occs_present counting of devices bound. * Improved remove() of P9 driver to avoid bad behavior with relation to OCC driver when unbound. * Added default cases (return EINVAL) for all sensor show functions. * Added temperature fault sensor. * Added back dt binding documentation for P9 to address checkpatch warning. * Added occs_present attribute from the poll response. Changes since v1: * Remove wait loop in P9 code, as that is now handled by FSI OCC driver. * Removed dt binding documentation for P9, FSI OCC driver will probe OCC hwmon driver automatically. * Moved OCC response code definitions to the OCC include file. * Fixed includes. * Changed some structure fields to __beXX as that is what they are. * Changed some errnos. * Removed some dev_err(). * Refactored P8 code a bit to use #defined addresses and magic values, and changed "goto retry" to a loop. * Refactored error handling a bit. Eddie James (10): dt-bindings: fsi: Add P9 OCC device documentation fsi: Add On-Chip Controller (OCC) driver Documentation: hwmon: Add OCC documentation dt-bindings: i2c: Add P8 OCC hwmon device documentation hwmon: Add On-Chip Controller (OCC) hwmon driver hwmon (occ): Add command transport method for P8 and P9 hwmon (occ): Parse OCC poll response hwmon (occ): Add sensor types and versions hwmon (occ): Add sensor attributes and register hwmon device hwmon (occ): Add sysfs attributes for additional OCC data .../devicetree/bindings/fsi/ibm,p9-occ.txt | 16 + .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt | 25 + Documentation/hwmon/occ| 112 ++ drivers/fsi/Kconfig| 10 + drivers/fsi/Makefile |1 + drivers/fsi/fsi-occ.c | 599 +++ drivers/hwmon/Kconfig |2 + drivers/hwmon/Makefile |1 + drivers/hwmon/occ/Kconfig | 31 + drivers/hwmon/occ/Makefile |5 + drivers/hwmon/occ/common.c | 1098 drivers/hwmon/occ/common.h | 128 +++ drivers/hwmon/occ/p8_i2c.c | 255 + drivers/hwmon/occ/p9_sbe.c | 106 ++ drivers/hwmon/occ/sysfs.c | 188 include/linux/fsi-occ.h| 25 + 16 files changed, 2602 insertions(+) create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt create mode 100644 Documentation/hwmon/occ create mode 100644 drivers/fsi/fsi-occ.c create mode 100644 drivers/hwmon/occ/Kconfig create mode 100644 drivers/hwmon/occ/Makefile create mode 100644 drivers/hwmon/occ/common.c create mode 100644 drivers/hwmon/occ/common.h create mode 100644 drivers/hwmon/occ/p8_i2c.c create mode 100644 drivers/hwmon/occ/p9_sbe.c create mode 100644 drivers/hwmon/occ/sysfs.c create mode 100644 include/linux/fsi-occ.h -- 1.8.3.1
[PATCH v5 03/10] Documentation: hwmon: Add OCC documentation
Document the hwmon interface for the OCC. Signed-off-by: Eddie James --- Documentation/hwmon/occ | 112 1 file changed, 112 insertions(+) create mode 100644 Documentation/hwmon/occ diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ new file mode 100644 index 000..adc2fa4 --- /dev/null +++ b/Documentation/hwmon/occ @@ -0,0 +1,112 @@ +Kernel driver occ-hwmon +=== + +Supported chips: + * POWER8 + * POWER9 + +Author: Eddie James + +Description +--- + +This driver supports hardware monitoring for the On-Chip Controller (OCC) +embedded on POWER processors. The OCC is a device that collects and aggregates +sensor data from the processor and the system. The OCC can provide the raw +sensor data as well as perform thermal and power management on the system. + +The P8 version of this driver is a client driver of I2C. It may be probed +manually if an "ibm,p8-occ-hwmon" compatible device is found under the +appropriate I2C bus node in the device-tree. + +The P9 version of this driver is a client driver of the FSI-based OCC driver. +It will be probed automatically by the FSI-based OCC driver. + +Sysfs entries +- + +The following attributes are supported. All attributes are read-only unless +specified. + +The OCC sensor ID is an integer that represents the unique identififer of the +sensor with respect to the OCC. For example, a temperature sensor for the 3rd +DIMM slot in the system may have a sensor ID of 7. This mapping is unavailable +to the device driver, which must therefore export the sensor ID as-is. + +Some entries are only present with certain OCC sensor versions or only on +certain OCCs in the system. The version number is not exported to the user +but can be inferred. + +temp[1-n]_labelOCC sensor ID. +[with temperature sensor version 1] +temp[1-n]_inputMeasured temperature of the component in millidegrees + Celsius. +[with temperature sensor version >= 2] +temp[1-n]_type The FRU (Field Replaceable Unit) type + (represented by an integer) for the component + that this sensor measures. +temp[1-n]_faultTemperature sensor fault boolean; 1 to indicate + that a fault is present or 0 to indicate that + no fault is present. +[with type == 3 (FRU type is VRM)] +temp[1-n]_alarmVRM temperature alarm boolean; 1 to indicate + alarm, 0 to indicate no alarm +[else] +temp[1-n]_inputMeasured temperature of the component in + millidegrees Celsius. + +freq[1-n]_labelOCC sensor ID. +freq[1-n]_inputMeasured frequency of the component in MHz. + +power[1-n]_input Latest measured power reading of the component in + microwatts. +power[1-n]_average Average power of the component in microwatts. +power[1-n]_average_intervalThe amount of time over which the power average + was taken in microseconds. +[with power sensor version < 2] +power[1-n]_label OCC sensor ID. +[with power sensor version >= 2] +power[1-n]_label OCC sensor ID + function ID + channel in the form + of a string, delimited by underscores, i.e. "0_15_1". + Both the function ID and channel are integers that + further identify the power sensor. +[with power sensor version 0xa0] +power[1-n]_label OCC sensor ID + sensor type in the form of a string, + delimited by an underscore, i.e. "0_system". Sensor + type will be one of "system", "proc", "vdd" or "vdn". + For this sensor version, OCC sensor ID will be the same + for all power sensors. +[present only on "master" OCC; represents the whole system power; only one of + this type of power sensor will be present] +power[1-n]_label "system" +power[1-n]_input Latest system output power in microwatts. +power[1-n]_cap Current system power cap in microwatts. +power[1-n]_cap_not_redundant System power cap in microwatts when + there is not redundant power. +power[1-n]_cap_max Maximum power cap that the OCC can enforce in + microwatts. +power[1-n]_cap_min Minimum power cap that the OCC can enforce in + microwatts. +power[1-n]_cap_userThe power cap set by the user, in microwatts. + This attribute will retur
[PATCH v5 01/10] dt-bindings: fsi: Add P9 OCC device documentation
Document the bindings for the FSI-attached POWER9 On-Chip Controller. Signed-off-by: Eddie James --- Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt | 15 +++ 1 file changed, 15 insertions(+) create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt diff --git a/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt new file mode 100644 index 000..46372f6 --- /dev/null +++ b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt @@ -0,0 +1,15 @@ +Device-tree bindings for FSI-attached POWER9 On-Chip Controller (OCC) +- + +This is the binding for the P9 On-Chip Controller accessed over FSI from a +service processor. See fsi.txt for details on bindings for FSI slave and CFAM +nodes. + +Required properties: + - compatible = "ibm,p9-occ" + +Examples: + +occ { +compatible = "ibm,p9-occ"; +}; -- 1.8.3.1
[PATCH v5 00/10] hwmon and fsi: Add On-Chip Controller Driver
This series adds a hwmon driver to support the OCC on POWER8 and POWER9 processors. The OCC is an embedded processor that provides realtime power and thermal monitoring and management. The series also adds a "bus" driver to handle atomic communication between the service processor and the OCC on a POWER9 chip. This communication takes place over FSI bus to the SBE (Self-Boot engine) FIFO, which in turn communicates with the OCC. The driver for the SBEFIFO is already available as an FSI client driver. For POWER8 OCCs, communication between the service processor and the OCC is achieved over I2C bus. Changes since v4: * Make the hwmon attributes conform almost completely to standard names and values. The only exception is powerX_cap_user and powerX_cap_user_source. * Improve hwmon documentation. * Add ibm,p9-occ dt documentation. Changes since v3: * Add the FSI OCC driver. * Pull the sysfs attribute code into it's own file for cleanliness. * Various fixes for attribute creation and integer overflow. Changes since v2: * Add sysfs_notify for the error and throttling attributes when change is detected. * Removed occs_present counting of devices bound. * Improved remove() of P9 driver to avoid bad behavior with relation to OCC driver when unbound. * Added default cases (return EINVAL) for all sensor show functions. * Added temperature fault sensor. * Added back dt binding documentation for P9 to address checkpatch warning. * Added occs_present attribute from the poll response. Changes since v1: * Remove wait loop in P9 code, as that is now handled by FSI OCC driver. * Removed dt binding documentation for P9, FSI OCC driver will probe OCC hwmon driver automatically. * Moved OCC response code definitions to the OCC include file. * Fixed includes. * Changed some structure fields to __beXX as that is what they are. * Changed some errnos. * Removed some dev_err(). * Refactored P8 code a bit to use #defined addresses and magic values, and changed "goto retry" to a loop. * Refactored error handling a bit. Eddie James (10): dt-bindings: fsi: Add P9 OCC device documentation fsi: Add On-Chip Controller (OCC) driver Documentation: hwmon: Add OCC documentation dt-bindings: i2c: Add P8 OCC hwmon device documentation hwmon: Add On-Chip Controller (OCC) hwmon driver hwmon (occ): Add command transport method for P8 and P9 hwmon (occ): Parse OCC poll response hwmon (occ): Add sensor types and versions hwmon (occ): Add sensor attributes and register hwmon device hwmon (occ): Add sysfs attributes for additional OCC data .../devicetree/bindings/fsi/ibm,p9-occ.txt | 15 + .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt | 25 + Documentation/hwmon/occ| 112 ++ drivers/fsi/Kconfig| 10 + drivers/fsi/Makefile |1 + drivers/fsi/fsi-occ.c | 599 +++ drivers/hwmon/Kconfig |2 + drivers/hwmon/Makefile |1 + drivers/hwmon/occ/Kconfig | 28 + drivers/hwmon/occ/Makefile | 11 + drivers/hwmon/occ/common.c | 1098 drivers/hwmon/occ/common.h | 128 +++ drivers/hwmon/occ/p8_i2c.c | 255 + drivers/hwmon/occ/p9_sbe.c | 106 ++ drivers/hwmon/occ/sysfs.c | 188 include/linux/fsi-occ.h| 25 + 16 files changed, 2604 insertions(+) create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt create mode 100644 Documentation/hwmon/occ create mode 100644 drivers/fsi/fsi-occ.c create mode 100644 drivers/hwmon/occ/Kconfig create mode 100644 drivers/hwmon/occ/Makefile create mode 100644 drivers/hwmon/occ/common.c create mode 100644 drivers/hwmon/occ/common.h create mode 100644 drivers/hwmon/occ/p8_i2c.c create mode 100644 drivers/hwmon/occ/p9_sbe.c create mode 100644 drivers/hwmon/occ/sysfs.c create mode 100644 include/linux/fsi-occ.h -- 1.8.3.1
[PATCH v5 06/10] hwmon (occ): Add command transport method for P8 and P9
For the P8 OCC, add the procedure to send a command to the OCC over I2C bus. This involves writing the OCC command registers with serial communication operations (SCOMs) interpreted by the I2C slave. For the P9 OCC, add a procedure to use the OCC in-kernel API to send a command to the OCC through the SBE. Signed-off-by: Eddie James --- drivers/hwmon/occ/p8_i2c.c | 185 - drivers/hwmon/occ/p9_sbe.c | 38 +- 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index 85e051d..3e34579 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -2,11 +2,29 @@ #include #include +#include #include +#include #include +#include +#include #include "common.h" +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +/* OCB (on-chip control bridge - interface to OCC) registers */ +#define OCB_DATA1 0x6B035 +#define OCB_ADDR 0x6B070 +#define OCB_DATA3 0x6B075 + +/* OCC SRAM address space */ +#define OCC_SRAM_ADDR_CMD 0x6000 +#define OCC_SRAM_ADDR_RESP 0x7000 + +#define OCC_DATA_ATTN 0x2001 + struct p8_i2c_occ { struct occ occ; struct i2c_client *client; @@ -14,9 +32,174 @@ struct p8_i2c_occ { #define to_p8_i2c_occ(x) container_of((x), struct p8_i2c_occ, occ) +static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data) +{ + ssize_t rc; + __be64 buf; + struct i2c_msg msgs[2]; + + /* p8 i2c slave requires shift */ + address <<= 1; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags & I2C_M_TEN; + msgs[0].len = sizeof(u32); + /* address is a scom address; bus-endian */ + msgs[0].buf = (char *)&address; + + /* data from OCC is big-endian */ + msgs[1].addr = client->addr; + msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[1].len = sizeof(u64); + msgs[1].buf = (char *)&buf; + + rc = i2c_transfer(client->adapter, msgs, 2); + if (rc < 0) + return rc; + + *(u64 *)data = be64_to_cpu(buf); + + return 0; +} + +static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data) +{ + u32 buf[3]; + ssize_t rc; + + /* p8 i2c slave requires shift */ + address <<= 1; + + /* address is bus-endian; data passed through from user as-is */ + buf[0] = address; + memcpy(&buf[1], &data[4], sizeof(u32)); + memcpy(&buf[2], data, sizeof(u32)); + + rc = i2c_master_send(client, (const char *)buf, sizeof(buf)); + if (rc < 0) + return rc; + else if (rc != sizeof(buf)) + return -EIO; + + return 0; +} + +static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address, + u32 data0, u32 data1) +{ + u8 buf[8]; + + memcpy(buf, &data0, 4); + memcpy(buf + 4, &data1, 4); + + return p8_i2c_occ_putscom(client, address, buf); +} + +static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address, +u8 *data) +{ + __be32 data0, data1; + + memcpy(&data0, data, 4); + memcpy(&data1, data + 4, 4); + + return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0), + be32_to_cpu(data1)); +} + static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd) { - return -EOPNOTSUPP; + int i, rc; + unsigned long start; + u16 data_length; + const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS); + const long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); + struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ); + struct i2c_client *client = ctx->client; + struct occ_response *resp = &occ->resp; + + start = jiffies; + + /* set sram address for command */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0); + if (rc) + return rc; + + /* write command (expected to already be BE), we need bus-endian... */ + rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd); + if (rc) + return rc; + + /* trigger OCC attention */ + rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0); + if (rc) + return rc; + + do { + /* set sram address for response */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, + OCC_SRAM_ADDR_RESP, 0); + if (rc) + return rc; + + rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)re
[PATCH v5 07/10] hwmon (occ): Parse OCC poll response
Add method to parse the response from the OCC poll command. This only needs to be done during probe(), since the OCC shouldn't change the number or format of sensors while it's running. The parsed response allows quick access to sensor data, as well as information on the number and version of sensors, which we need to instantiate hwmon attributes. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 61 ++ drivers/hwmon/occ/common.h | 55 + 2 files changed, 116 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 81acd4b..a066509 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include "common.h" @@ -22,6 +23,64 @@ static int occ_poll(struct occ *occ) return occ->send_cmd(occ, cmd); } +/* only need to do this once at startup, as OCC won't change sensors on us */ +static void occ_parse_poll_response(struct occ *occ) +{ + unsigned int i, old_offset, offset = 0, size = 0; + struct occ_sensor *sensor; + struct occ_sensors *sensors = &occ->sensors; + struct occ_response *resp = &occ->resp; + struct occ_poll_response *poll = + (struct occ_poll_response *)&resp->data[0]; + struct occ_poll_response_header *header = &poll->header; + struct occ_sensor_data_block *block = &poll->block; + + dev_info(occ->bus_dev, "OCC found, code level: %.16s\n", +header->occ_code_level); + + for (i = 0; i < header->num_sensor_data_blocks; ++i) { + block = (struct occ_sensor_data_block *)((u8 *)block + offset); + old_offset = offset; + offset = (block->header.num_sensors * + block->header.sensor_length) + sizeof(block->header); + size += offset; + + /* validate all the length/size fields */ + if ((size + sizeof(*header)) >= OCC_RESP_DATA_BYTES) { + dev_warn(occ->bus_dev, "exceeded response buffer\n"); + return; + } + + dev_dbg(occ->bus_dev, " %04x..%04x: %.4s (%d sensors)\n", + old_offset, offset - 1, block->header.eye_catcher, + block->header.num_sensors); + + /* match sensor block type */ + if (strncmp(block->header.eye_catcher, "TEMP", 4) == 0) + sensor = &sensors->temp; + else if (strncmp(block->header.eye_catcher, "FREQ", 4) == 0) + sensor = &sensors->freq; + else if (strncmp(block->header.eye_catcher, "POWR", 4) == 0) + sensor = &sensors->power; + else if (strncmp(block->header.eye_catcher, "CAPS", 4) == 0) + sensor = &sensors->caps; + else if (strncmp(block->header.eye_catcher, "EXTN", 4) == 0) + sensor = &sensors->extended; + else { + dev_warn(occ->bus_dev, "sensor not supported %.4s\n", +block->header.eye_catcher); + continue; + } + + sensor->num_sensors = block->header.num_sensors; + sensor->version = block->header.sensor_format; + sensor->data = &block->data; + } + + dev_dbg(occ->bus_dev, "Max resp size: %u+%zd=%zd\n", size, + sizeof(*header), size + sizeof(*header)); +} + int occ_setup(struct occ *occ, const char *name) { int rc; @@ -36,5 +95,7 @@ int occ_setup(struct occ *occ, const char *name) return rc; } + occ_parse_poll_response(occ); + return 0; } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index b44fee2..0a7a107 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -20,10 +20,65 @@ struct occ_response { __be16 checksum; } __packed; +struct occ_sensor_data_block_header { + u8 eye_catcher[4]; + u8 reserved; + u8 sensor_format; + u8 sensor_length; + u8 num_sensors; +} __packed; + +struct occ_sensor_data_block { + struct occ_sensor_data_block_header header; + u32 data; +} __packed; + +struct occ_poll_response_header { + u8 status; + u8 ext_status; + u8 occs_present; + u8 config_data; + u8 occ_state; + u8 mode; + u8 ips_status; + u8 error_log_id; + __be32 error_log_start_address; + __be16 error_log_length; + u16 reserved; +
[PATCH v5 10/10] hwmon (occ): Add sysfs attributes for additional OCC data
The OCC provides a variety of additional information about the state of the host processor, such as throttling, error conditions, and the number of OCCs detected in the system. This information is essential to service processor applications such as fan control and host management. Therefore, export this data in the form of sysfs attributes attached to the platform device (to which the hwmon device is also attached). Signed-off-by: Eddie James --- drivers/hwmon/occ/Makefile | 2 +- drivers/hwmon/occ/common.c | 45 ++- drivers/hwmon/occ/common.h | 17 drivers/hwmon/occ/p8_i2c.c | 10 +++ drivers/hwmon/occ/p9_sbe.c | 1 + drivers/hwmon/occ/sysfs.c | 188 + 6 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 drivers/hwmon/occ/sysfs.c diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile index ab5c3e9..46a8636 100644 --- a/drivers/hwmon/occ/Makefile +++ b/drivers/hwmon/occ/Makefile @@ -1,4 +1,4 @@ -occ-hwmon-objs := common.o +occ-hwmon-objs := common.o sysfs.o ifeq ($(CONFIG_SENSORS_OCC_P9_SBE), y) occ-hwmon-objs += p9_sbe.o diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index c6c8161..423903f 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -14,6 +14,11 @@ #define EXTN_FLAG_SENSOR_IDBIT(7) +#define OCC_ERROR_COUNT_THRESHOLD 2 /* required by OCC spec */ + +#define OCC_STATE_SAFE 4 +#define OCC_SAFE_TIMEOUT msecs_to_jiffies(6) /* 1 min */ + #define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000) #define OCC_TEMP_SENSOR_FAULT 0xFF @@ -115,8 +120,10 @@ struct extended_sensor { static int occ_poll(struct occ *occ) { + int rc; u16 checksum = occ->poll_cmd_data + 1; u8 cmd[8]; + struct occ_poll_response_header *header; /* big endian */ cmd[0] = 0; /* sequence number */ @@ -129,7 +136,35 @@ static int occ_poll(struct occ *occ) cmd[7] = 0; /* mutex should already be locked if necessary */ - return occ->send_cmd(occ, cmd); + rc = occ->send_cmd(occ, cmd); + if (rc) { + if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD) + occ->error = rc; + + goto done; + } + + /* clear error since communication was successful */ + occ->error_count = 0; + occ->error = 0; + + /* check for safe state */ + header = (struct occ_poll_response_header *)occ->resp.data; + if (header->occ_state == OCC_STATE_SAFE) { + if (occ->last_safe) { + if (time_after(jiffies, + occ->last_safe + OCC_SAFE_TIMEOUT)) + occ->error = -EHOSTDOWN; + } else { + occ->last_safe = jiffies; + } + } else { + occ->last_safe = 0; + } + +done: + occ_sysfs_poll_done(occ); + return rc; } static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) @@ -161,7 +196,7 @@ static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) return rc; } -static int occ_update_response(struct occ *occ) +int occ_update_response(struct occ *occ) { int rc = mutex_lock_interruptible(&occ->lock); @@ -1055,5 +1090,9 @@ int occ_setup(struct occ *occ, const char *name) return rc; } - return 0; + rc = occ_setup_sysfs(occ); + if (rc) + dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc); + + return rc; } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 00ac101..da80e65 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -104,8 +104,25 @@ struct occ { struct occ_attribute *attrs; struct attribute_group group; const struct attribute_group *groups[2]; + + int error; /* latest transfer error */ + unsigned int error_count; /* number of xfr errors observed */ + unsigned long last_safe;/* time OCC entered "safe" state */ + + /* +* Store the previous state data for comparison in order to notify +* sysfs readers of state changes. +*/ + int prev_error; + u8 prev_stat; + u8 prev_ext_stat; + u8 prev_occs_present; }; int occ_setup(struct occ *occ, const char *name); +int occ_setup_sysfs(struct occ *occ); +void occ_shutdown(struct occ *occ); +void occ_sysfs_poll_done(struct occ *occ); +int occ_update_response(struct occ *occ); #endif /* OCC_COMMON_H */ diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index a736220..0dc7fa3 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c
[PATCH v5 08/10] hwmon (occ): Add sensor types and versions
Add structures to define all sensor types and versions. Add sysfs show and store functions for each sensor type. Add a method to construct the "set user power cap" command and send it to the OCC. Add rate limit to polling the OCC (in case user-space reads our hwmon entries rapidly). Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 621 + drivers/hwmon/occ/common.h | 6 + drivers/hwmon/occ/p8_i2c.c | 1 + drivers/hwmon/occ/p9_sbe.c | 1 + 4 files changed, 629 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index a066509..f7220132 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,10 +1,116 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include +#include #include +#include +#include +#include #include "common.h" +#define EXTN_FLAG_SENSOR_IDBIT(7) + +#define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000) + +#define OCC_TEMP_SENSOR_FAULT 0xFF + +#define OCC_FRU_TYPE_VRM 3 + +/* OCC sensor type and version definitions */ + +struct temp_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct temp_sensor_2 { + u32 sensor_id; + u8 fru_type; + u8 value; +} __packed; + +struct freq_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct freq_sensor_2 { + u32 sensor_id; + u16 value; +} __packed; + +struct power_sensor_1 { + u16 sensor_id; + u32 update_tag; + u32 accumulator; + u16 value; +} __packed; + +struct power_sensor_2 { + u32 sensor_id; + u8 function_id; + u8 apss_channel; + u16 reserved; + u32 update_tag; + u64 accumulator; + u16 value; +} __packed; + +struct power_sensor_data { + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_data_and_time { + u16 update_time; + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_a0 { + u32 sensor_id; + struct power_sensor_data_and_time system; + u32 reserved; + struct power_sensor_data_and_time proc; + struct power_sensor_data vdd; + struct power_sensor_data vdn; +} __packed; + +struct caps_sensor_2 { + u16 cap; + u16 system_power; + u16 n_cap; + u16 max; + u16 min; + u16 user; + u8 user_source; +} __packed; + +struct caps_sensor_3 { + u16 cap; + u16 system_power; + u16 n_cap; + u16 max; + u16 hard_min; + u16 soft_min; + u16 user; + u8 user_source; +} __packed; + +struct extended_sensor { + union { + u8 name[4]; + u32 sensor_id; + }; + u8 flags; + u8 reserved; + u8 data[6]; +} __packed; + static int occ_poll(struct occ *occ) { u16 checksum = occ->poll_cmd_data + 1; @@ -20,9 +126,521 @@ static int occ_poll(struct occ *occ) cmd[6] = checksum & 0xFF; /* checksum lsb */ cmd[7] = 0; + /* mutex should already be locked if necessary */ return occ->send_cmd(occ, cmd); } +static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) +{ + int rc; + u8 cmd[8]; + u16 checksum = 0x24; + __be16 user_power_cap_be = cpu_to_be16(user_power_cap); + + cmd[0] = 0; + cmd[1] = 0x22; + cmd[2] = 0; + cmd[3] = 2; + + memcpy(&cmd[4], &user_power_cap_be, 2); + + checksum += cmd[4] + cmd[5]; + cmd[6] = checksum >> 8; + cmd[7] = checksum & 0xFF; + + rc = mutex_lock_interruptible(&occ->lock); + if (rc) + return rc; + + rc = occ->send_cmd(occ, cmd); + + mutex_unlock(&occ->lock); + + return rc; +} + +static int occ_update_response(struct occ *occ) +{ + int rc = mutex_lock_interruptible(&occ->lock); + + if (rc) + return rc; + + /* limit the maximum rate of polling the OCC */ + if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) { + rc = occ_poll(occ); + occ->last_update = jiffies; + } + + mutex_unlock(&occ->lock); + return rc; +} + +static ssize_t occ_show_temp_1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct temp_sensor_1 *temp; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + temp = ((struct temp_sensor_1 *)sensors->temp.data) + sattr->index; + + switch (sattr->nr) { + case 0: + v
[PATCH v5 09/10] hwmon (occ): Add sensor attributes and register hwmon device
Setup the sensor attributes for every OCC sensor found by the first poll response. Register the attributes with hwmon. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 337 + drivers/hwmon/occ/common.h | 16 +++ 2 files changed, 353 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index f7220132..c6c8161 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include #include #include +#include #include #include "common.h" @@ -641,6 +643,324 @@ static ssize_t occ_show_extended(struct device *dev, return rc; } +/* + * Some helper macros to make it easier to define an occ_attribute. Since these + * are dynamically allocated, we shouldn't use the existing kernel macros which + * stringify the name argument. + */ +#define ATTR_OCC(_name, _mode, _show, _store) { \ + .attr = { \ + .name = _name, \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode),\ + }, \ + .show = _show,\ + .store = _store, \ +} + +#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) {\ + .dev_attr = ATTR_OCC(_name, _mode, _show, _store),\ + .index = _index, \ + .nr = _nr, \ +} + +#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index) \ + ((struct sensor_device_attribute_2) \ + SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index)) + +/* + * Allocate and instatiate sensor_device_attribute_2s. It's most efficient to + * use our own instead of the built-in hwmon attribute types. + */ +static int occ_setup_sensor_attrs(struct occ *occ) +{ + unsigned int i, s, num_attrs = 0; + struct device *dev = occ->bus_dev; + struct occ_sensors *sensors = &occ->sensors; + struct occ_attribute *attr; + struct temp_sensor_2 *temp; + ssize_t (*show_temp)(struct device *, struct device_attribute *, +char *) = occ_show_temp_1; + ssize_t (*show_freq)(struct device *, struct device_attribute *, +char *) = occ_show_freq_1; + ssize_t (*show_power)(struct device *, struct device_attribute *, + char *) = occ_show_power_1; + ssize_t (*show_caps)(struct device *, struct device_attribute *, +char *) = occ_show_caps_1_2; + + switch (sensors->temp.version) { + case 1: + num_attrs += (sensors->temp.num_sensors * 2); + break; + case 2: + num_attrs += (sensors->temp.num_sensors * 4); + show_temp = occ_show_temp_2; + break; + default: + sensors->temp.num_sensors = 0; + } + + switch (sensors->freq.version) { + case 2: + show_freq = occ_show_freq_2; + /* fall through */ + case 1: + num_attrs += (sensors->freq.num_sensors * 2); + break; + default: + sensors->freq.num_sensors = 0; + } + + switch (sensors->power.version) { + case 2: + show_power = occ_show_power_2; + /* fall through */ + case 1: + num_attrs += (sensors->power.num_sensors * 4); + break; + case 0xA0: + num_attrs += (sensors->power.num_sensors * 16); + show_power = occ_show_power_a0; + break; + default: + sensors->power.num_sensors = 0; + } + + switch (sensors->caps.version) { + case 1: + num_attrs += (sensors->caps.num_sensors * 7); + break; + case 3: + show_caps = occ_show_caps_3; + /* fall through */ + case 2: + num_attrs += (sensors->caps.num_sensors * 8); + break; + default: + sensors->caps.num_sensors = 0; + } + + switch (sensors->extended.version) { + case 1: + num_attrs += (sensors->extended.num_sensors * 3); + break; + default: + sensors->extended.num_sensors = 0; + } + + occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * num_attrs, + GFP_KERNEL); + if (!occ->attrs)
[PATCH v5 04/10] dt-bindings: i2c: Add P8 OCC hwmon device documentation
Document the bindings for I2C-based OCC hwmon device. Signed-off-by: Eddie James Acked-by: Rob Herring --- .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt | 25 ++ 1 file changed, 25 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt diff --git a/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt new file mode 100644 index 000..5dc5d2e --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt @@ -0,0 +1,25 @@ +Device-tree bindings for I2C-based On-Chip Controller hwmon device +-- + +Required properties: + - compatible = "ibm,p8-occ-hwmon"; + - reg = ;: I2C bus address + +Examples: + +i2c-bus@100 { +#address-cells = <1>; +#size-cells = <0>; +clock-frequency = <10>; +< more properties > + +occ-hwmon@1 { +compatible = "ibm,p8-occ-hwmon"; +reg = <0x50>; +}; + +occ-hwmon@2 { +compatible = "ibm,p8-occ-hwmon"; +reg = <0x51>; +}; +}; -- 1.8.3.1
[PATCH v5 05/10] hwmon: Add On-Chip Controller (OCC) hwmon driver
The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system. This driver provides a hwmon interface to the OCC from a service processor (e.g. a BMC). The driver supports both POWER8 and POWER9 OCCs. Communications with the POWER8 OCC are established over standard I2C bus. The driver communicates with the POWER9 OCC through the FSI-based OCC driver, which handles the lower-level communication details. This patch lays out the structure of the OCC hwmon driver. There are two platform drivers, one each for P8 and P9 OCCs. These are probed through the I2C tree and the FSI-based OCC driver, respectively. The patch also defines the first common structures and methods between the two OCC versions. Signed-off-by: Eddie James --- drivers/hwmon/Kconfig | 2 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/occ/Kconfig | 28 +++ drivers/hwmon/occ/Makefile | 11 drivers/hwmon/occ/common.c | 40 +++ drivers/hwmon/occ/common.h | 34 +++ drivers/hwmon/occ/p8_i2c.c | 61 + drivers/hwmon/occ/p9_sbe.c | 68 ++ 8 files changed, 245 insertions(+) create mode 100644 drivers/hwmon/occ/Kconfig create mode 100644 drivers/hwmon/occ/Makefile create mode 100644 drivers/hwmon/occ/common.c create mode 100644 drivers/hwmon/occ/common.h create mode 100644 drivers/hwmon/occ/p8_i2c.c create mode 100644 drivers/hwmon/occ/p9_sbe.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 81da17a..532a053 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1293,6 +1293,8 @@ config SENSORS_NSA320 This driver can also be built as a module. If so, the module will be called nsa320-hwmon. +source drivers/hwmon/occ/Kconfig + config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 93f7f41..f5c7b44 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -178,6 +178,7 @@ obj-$(CONFIG_SENSORS_WM831X)+= wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE)+= xgene-hwmon.o +obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_PMBUS)+= pmbus/ ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig new file mode 100644 index 000..9579b63 --- /dev/null +++ b/drivers/hwmon/occ/Kconfig @@ -0,0 +1,28 @@ +# +# On-Chip Controller configuration +# + +config SENSORS_OCC + tristate "POWER On-Chip Controller" + help +This option enables support for monitoring a variety of system sensors +provided by the On-Chip Controller (OCC) on a POWER processor. + +This driver can also be built as a module. If so, the module will be +called occ-hwmon. + +config SENSORS_OCC_P8_I2C + bool "POWER8 OCC through I2C" + depends on I2C && SENSORS_OCC + help +This option enables support for monitoring sensors provided by the OCC +on a POWER8 processor. Communications with the OCC are established +through I2C bus. + +config SENSORS_OCC_P9_SBE + bool "POWER9 OCC through SBE" + depends on FSI_OCC && SENSORS_OCC + help +This option enables support for monitoring sensors provided by the OCC +on a POWER9 processor. Communications with the OCC are established +through SBEFIFO on an FSI bus. diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile new file mode 100644 index 000..ab5c3e9 --- /dev/null +++ b/drivers/hwmon/occ/Makefile @@ -0,0 +1,11 @@ +occ-hwmon-objs := common.o + +ifeq ($(CONFIG_SENSORS_OCC_P9_SBE), y) +occ-hwmon-objs += p9_sbe.o +endif + +ifeq ($(CONFIG_SENSORS_OCC_P8_I2C), y) +occ-hwmon-objs += p8_i2c.o +endif + +obj-$(CONFIG_SENSORS_OCC) += occ-hwmon.o diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c new file mode 100644 index 000..81acd4b --- /dev/null +++ b/drivers/hwmon/occ/common.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include "common.h" + +static int occ_poll(struct occ *occ) +{ + u16 checksum = occ->poll_cmd_data + 1; + u8 cmd[8]; + + /* big endian */ + cmd[0] = 0; /* sequence number */ + cmd[1] = 0; /* cmd type */ + cmd[2] = 0; /* data length msb */ + cmd[3] = 1; /* data length lsb */ + cmd[4] = occ->poll_cmd_data;/* data */ + cmd[5] = checksum >> 8; /* checksum msb */ + cmd[6] = checksum & 0xFF; /* checksum lsb */ +
[PATCH v5 02/10] fsi: Add On-Chip Controller (OCC) driver
The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system. This driver provides an atomic communications channel between a service processor (e.g. a BMC) and the OCC. The driver is dependent on the FSI SBEFIFO driver to get hardware access through the SBE to the OCC SRAM. Commands are issued to the SBE to send or fetch data to the SRAM. Signed-off-by: Eddie James Signed-off-by: Andrew Jeffery Signed-off-by: Benjamin Herrenschmidt Signed-off-by: Joel Stanley --- drivers/fsi/Kconfig | 10 + drivers/fsi/Makefile| 1 + drivers/fsi/fsi-occ.c | 599 include/linux/fsi-occ.h | 25 ++ 4 files changed, 635 insertions(+) create mode 100644 drivers/fsi/fsi-occ.c create mode 100644 include/linux/fsi-occ.h diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index af3a20d..ea2f4a1 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -64,4 +64,14 @@ config FSI_SBEFIFO a pipe-like FSI device for communicating with the self boot engine (SBE) on POWER processors. +config FSI_OCC + tristate "OCC SBEFIFO client device driver" + depends on FSI_SBEFIFO + ---help--- + This option enables an SBEFIFO based On-Chip Controller (OCC) device + driver. The OCC is a device embedded on a POWER processor that collects + and aggregates sensor data from the processor and system. The OCC can + provide the raw sensor data as well as perform thermal and power + management on the system. + endif diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index a50d6ce..62687ec 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o +obj-$(CONFIG_FSI_OCC) += fsi-occ.o diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c new file mode 100644 index 000..2a2049d --- /dev/null +++ b/drivers/fsi/fsi-occ.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OCC_SRAM_BYTES 4096 +#define OCC_CMD_DATA_BYTES 4090 +#define OCC_RESP_DATA_BYTES4089 + +#define OCC_SRAM_CMD_ADDR 0xFFFBE000 +#define OCC_SRAM_RSP_ADDR 0xFFFBF000 + +/* + * Assume we don't have much FFDC, if we do we'll overflow and + * fail the command. This needs to be big enough for simple + * commands as well. + */ +#define OCC_SBE_STATUS_WORDS 32 + +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +struct occ { + struct device *dev; + struct device *sbefifo; + char name[32]; + int idx; + struct miscdevice mdev; + struct mutex occ_lock; +}; + +#define to_occ(x) container_of((x), struct occ, mdev) + +struct occ_response { + u8 seq_no; + u8 cmd_type; + u8 return_status; + __be16 data_length; + u8 data[OCC_RESP_DATA_BYTES + 2]; /* two bytes checksum */ +} __packed; + +struct occ_client { + struct occ *occ; + struct mutex lock; + size_t data_size; + size_t read_offset; + u8 *buffer; +}; + +#define to_client(x) container_of((x), struct occ_client, xfr) + +static DEFINE_IDA(occ_ida); + +static int occ_open(struct inode *inode, struct file *file) +{ + struct occ_client *client = kzalloc(sizeof(*client), GFP_KERNEL); + struct miscdevice *mdev = file->private_data; + struct occ *occ = to_occ(mdev); + + if (!client) + return -ENOMEM; + + client->buffer = (u8 *)__get_free_page(GFP_KERNEL); + if (!client->buffer) { + kfree(client); + return -ENOMEM; + } + + client->occ = occ; + mutex_init(&client->lock); + file->private_data = client; + + /* We allocate a 1-page buffer, make sure it all fits */ + BUILD_BUG_ON((OCC_CMD_DATA_BYTES + 3) > PAGE_SIZE); + BUILD_BUG_ON((OCC_RESP_DATA_BYTES + 7) > PAGE_SIZE); + + return 0; +} + +static ssize_t occ_read(struct file *file, char __user *buf, size_t len, + loff_t *offset) +{ + struct occ_client *client = file->private_data; + ssize_t rc = 0; + + if (!client) + return -ENODEV; + + if (len > OCC_SRAM_BYTES) + return -EINVAL; + + mutex_lock(&client->lock); + + /* This should not be possible ... */ + if (WARN_ON_ONCE(client->read_offset > client->data_size)) { + rc = -EIO
Re: [PATCH v4 2/9] Documentation: hwmon: Add OCC documentation
On 07/25/2018 11:36 AM, Guenter Roeck wrote: On Wed, Jul 11, 2018 at 04:01:31PM -0500, Eddie James wrote: Document the hwmon interface for the OCC. Signed-off-by: Eddie James --- Documentation/hwmon/occ | 73 + 1 file changed, 73 insertions(+) create mode 100644 Documentation/hwmon/occ diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ new file mode 100644 index 000..465fa1a --- /dev/null +++ b/Documentation/hwmon/occ @@ -0,0 +1,73 @@ +Kernel driver occ-hwmon +=== + +Supported chips: + * POWER8 + * POWER9 + +Author: Eddie James + +Description +--- + +This driver supports hardware monitoring for the On-Chip Controller (OCC) +embedded on POWER processors. The OCC is a device that collects and aggregates +sensor data from the processor and the system. The OCC can provide the raw +sensor data as well as perform thermal and power management on the system. + +The P8 version of this driver is a client driver of I2C. It may be probed +manually if an "ibm,p8-occ-hwmon" compatible device is found under the +appropriate I2C bus node in the device-tree. + +The P9 version of this driver is a client driver of the FSI-based OCC driver. +It will be probed automatically by the FSI-based OCC driver. + +Sysfs entries +- + +The following attributes are supported. All attributes are read-only unless +specified. + +temp[1-n]_labelOCC sensor id. +temp[1-n]_inputMeasured temperature in millidegrees C. +[with temperature sensor version 2+] +temp[1-n]_fru_type Given FRU (Field Replaceable Unit) type. What is this ? An integer ? A string ? +temp[1-n]_faultTemperature sensor fault. + +freq[1-n]_labelOCC sensor id. +freq[1-n]_inputMeasured frequency. What does that have to do with hardware monitoring, and what exactly does it measure ? AC voltage frequency ? Frequency of rainstorms in the surrounding area ? + +power[1-n]_label OCC sensor id. +power[1-n]_input Measured power in microwatts. +power[1-n]_update_tag Number of 250us samples represented in accumulator. update_tag to represent number of samples ? Odd choice for an attribute name. Why not "_samples" ? Also, if each sample represents a specific amount of time, why not report a time ? +power[1-n]_accumulator Accumulation of 250us power readings. There is no explanation of "accumulation". Is this the energy ? If so, why not use energy attributes ? And what is the unit of this measurement ? +[with power sensor version 2+] +power[1-n]_function_id Identifies what the power reading is for. String ? Number ? Slot index ? Bitmap ? And why isn't that reported in the label ? After all, that is what the label is supposed to be used for. +power[1-n]_apss_channelIndicates APSS channel. + Does that provide any value to the user ? +[power version 0xa0 only] +power1_id OCC sensor id. This is inconsistent with the other attributes and even with itself. +power[1-n]_label Sensor type, "system", "proc", "vdd", or "vdn". +power[1-n]_input Most recent power reading in microwatts. Overall I am left with no idea what _id _label _function_id _apps_channel are and how they relate to each other, except that it all looks quite inconsistent. You might want to consider merging all those attributes into the label in some consistent way. +power[1-n]_update_tag Number of samples in the accumulator. +power[1-n]_accumulator Accumulation of power readings. Same as above. +[with sensor type "system" and "proc" only] +power[1-n]_update_time Time in us that the power value is read. + +caps1_current Current OCC power cap in watts. +caps1_reading Current system output power in watts. +caps1_norm Power cap without redundant power. +caps1_max Maximum power cap. Why do those have to be non-standard attributes ? Please explain why you can not use power[1-n]_cap attributes. +[caps version 1 and 2 only] +caps1_min Minimum power cap. +[caps version 3+] +caps1_min_hard Hard minimum cap that can be set and held. +caps1_min_soft Soft minimum cap below hard, not guaranteed. +caps1_user The powercap specified by the user. Will be 0 if no + user powercap exists. This attribute is read-write. +[caps version 1+] +caps1_user_source Indicates how the user power limit was set. + +extn[1-n]_labelASCII id or sensor id. +extn[1-n]_flagsIndicates type of label attribute. +extn[1-n]_inputData. Great non-explanation. Not reviewing the series further. I am sure I asked that each non-standard attribute
[PATCH v4 2/9] Documentation: hwmon: Add OCC documentation
Document the hwmon interface for the OCC. Signed-off-by: Eddie James --- Documentation/hwmon/occ | 73 + 1 file changed, 73 insertions(+) create mode 100644 Documentation/hwmon/occ diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ new file mode 100644 index 000..465fa1a --- /dev/null +++ b/Documentation/hwmon/occ @@ -0,0 +1,73 @@ +Kernel driver occ-hwmon +=== + +Supported chips: + * POWER8 + * POWER9 + +Author: Eddie James + +Description +--- + +This driver supports hardware monitoring for the On-Chip Controller (OCC) +embedded on POWER processors. The OCC is a device that collects and aggregates +sensor data from the processor and the system. The OCC can provide the raw +sensor data as well as perform thermal and power management on the system. + +The P8 version of this driver is a client driver of I2C. It may be probed +manually if an "ibm,p8-occ-hwmon" compatible device is found under the +appropriate I2C bus node in the device-tree. + +The P9 version of this driver is a client driver of the FSI-based OCC driver. +It will be probed automatically by the FSI-based OCC driver. + +Sysfs entries +- + +The following attributes are supported. All attributes are read-only unless +specified. + +temp[1-n]_labelOCC sensor id. +temp[1-n]_inputMeasured temperature in millidegrees C. +[with temperature sensor version 2+] +temp[1-n]_fru_type Given FRU (Field Replaceable Unit) type. +temp[1-n]_faultTemperature sensor fault. + +freq[1-n]_labelOCC sensor id. +freq[1-n]_inputMeasured frequency. + +power[1-n]_label OCC sensor id. +power[1-n]_input Measured power in microwatts. +power[1-n]_update_tag Number of 250us samples represented in accumulator. +power[1-n]_accumulator Accumulation of 250us power readings. +[with power sensor version 2+] +power[1-n]_function_id Identifies what the power reading is for. +power[1-n]_apss_channelIndicates APSS channel. + +[power version 0xa0 only] +power1_id OCC sensor id. +power[1-n]_label Sensor type, "system", "proc", "vdd", or "vdn". +power[1-n]_input Most recent power reading in microwatts. +power[1-n]_update_tag Number of samples in the accumulator. +power[1-n]_accumulator Accumulation of power readings. +[with sensor type "system" and "proc" only] +power[1-n]_update_time Time in us that the power value is read. + +caps1_current Current OCC power cap in watts. +caps1_reading Current system output power in watts. +caps1_norm Power cap without redundant power. +caps1_max Maximum power cap. +[caps version 1 and 2 only] +caps1_min Minimum power cap. +[caps version 3+] +caps1_min_hard Hard minimum cap that can be set and held. +caps1_min_soft Soft minimum cap below hard, not guaranteed. +caps1_user The powercap specified by the user. Will be 0 if no + user powercap exists. This attribute is read-write. +[caps version 1+] +caps1_user_source Indicates how the user power limit was set. + +extn[1-n]_labelASCII id or sensor id. +extn[1-n]_flagsIndicates type of label attribute. +extn[1-n]_inputData. -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v4 7/9] hwmon (occ): Add sensor types and versions
Add structures to define all sensor types and versions. Add sysfs show and store functions for each sensor type. Add a method to construct the "set user power cap" command and send it to the OCC. Add rate limit to polling the OCC (in case user-space reads our hwmon entries rapidly). Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 648 + drivers/hwmon/occ/common.h | 5 + 2 files changed, 653 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 73f62aa..1719536 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -11,10 +11,119 @@ */ #include +#include +#include #include +#include +#include #include "common.h" +#define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000) + +#define OCC_TEMP_SENSOR_FAULT 0xFF + +#define OCC_FRU_TYPE_VRM 0x3 + +/* OCC sensor type and version definitions */ + +struct temp_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct temp_sensor_2 { + u32 sensor_id; + u8 fru_type; + u8 value; +} __packed; + +struct freq_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct freq_sensor_2 { + u32 sensor_id; + u16 value; +} __packed; + +struct power_sensor_1 { + u16 sensor_id; + u32 update_tag; + u32 accumulator; + u16 value; +} __packed; + +struct power_sensor_2 { + u32 sensor_id; + u8 function_id; + u8 apss_channel; + u16 reserved; + u32 update_tag; + u64 accumulator; + u16 value; +} __packed; + +struct power_sensor_data { + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_data_and_time { + u16 update_time; + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_a0 { + u32 sensor_id; + struct power_sensor_data_and_time system; + u32 reserved; + struct power_sensor_data_and_time proc; + struct power_sensor_data vdd; + struct power_sensor_data vdn; +} __packed; + +struct caps_sensor_1 { + u16 curr_powercap; + u16 curr_powerreading; + u16 norm_powercap; + u16 max_powercap; + u16 min_powercap; + u16 user_powerlimit; +} __packed; + +struct caps_sensor_2 { + u16 curr_powercap; + u16 curr_powerreading; + u16 norm_powercap; + u16 max_powercap; + u16 min_powercap; + u16 user_powerlimit; + u8 user_powerlimit_source; +} __packed; + +struct caps_sensor_3 { + u16 curr_powercap; + u16 curr_powerreading; + u16 norm_powercap; + u16 max_powercap; + u16 hard_min_powercap; + u16 soft_min_powercap; + u16 user_powerlimit; + u8 user_powerlimit_source; +} __packed; + +struct extended_sensor { + u8 name[4]; + u8 flags; + u8 reserved; + u8 data[6]; +} __packed; + static int occ_poll(struct occ *occ) { u16 checksum = occ->poll_cmd_data + 1; @@ -30,9 +139,545 @@ static int occ_poll(struct occ *occ) cmd[6] = checksum & 0xFF; /* checksum lsb */ cmd[7] = 0; + /* mutex should already be locked if necessary */ return occ->send_cmd(occ, cmd); } +static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) +{ + int rc; + u8 cmd[8]; + u16 checksum = 0x24; + __be16 user_power_cap_be = cpu_to_be16(user_power_cap); + + cmd[0] = 0; + cmd[1] = 0x22; + cmd[2] = 0; + cmd[3] = 2; + + memcpy(&cmd[4], &user_power_cap_be, 2); + + checksum += cmd[4] + cmd[5]; + cmd[6] = checksum >> 8; + cmd[7] = checksum & 0xFF; + + rc = mutex_lock_interruptible(&occ->lock); + if (rc) + return rc; + + rc = occ->send_cmd(occ, cmd); + + mutex_unlock(&occ->lock); + + return rc; +} + +static int occ_update_response(struct occ *occ) +{ + int rc = mutex_lock_interruptible(&occ->lock); + + if (rc) + return rc; + + /* limit the maximum rate of polling the OCC */ + if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) { + rc = occ_poll(occ); + occ->last_update = jiffies; + } + + mutex_unlock(&occ->lock); + return rc; +} + +static ssize_t occ_show_temp_1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct temp_sensor_1 *temp; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + temp = ((struct temp_
[PATCH v4 6/9] hwmon (occ): Parse OCC poll response
Add method to parse the response from the OCC poll command. This only needs to be done during probe(), since the OCC shouldn't change the number or format of sensors while it's running. The parsed response allows quick access to sensor data, as well as information on the number and version of sensors, which we need to instantiate hwmon attributes. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 61 ++ drivers/hwmon/occ/common.h | 55 + 2 files changed, 116 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 1fd3453..73f62aa 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -11,6 +11,7 @@ */ #include +#include #include "common.h" @@ -32,6 +33,64 @@ static int occ_poll(struct occ *occ) return occ->send_cmd(occ, cmd); } +/* only need to do this once at startup, as OCC won't change sensors on us */ +static void occ_parse_poll_response(struct occ *occ) +{ + unsigned int i, old_offset, offset = 0, size = 0; + struct occ_sensor *sensor; + struct occ_sensors *sensors = &occ->sensors; + struct occ_response *resp = &occ->resp; + struct occ_poll_response *poll = + (struct occ_poll_response *)&resp->data[0]; + struct occ_poll_response_header *header = &poll->header; + struct occ_sensor_data_block *block = &poll->block; + + dev_info(occ->bus_dev, "OCC found, code level: %.16s\n", +header->occ_code_level); + + for (i = 0; i < header->num_sensor_data_blocks; ++i) { + block = (struct occ_sensor_data_block *)((u8 *)block + offset); + old_offset = offset; + offset = (block->header.num_sensors * + block->header.sensor_length) + sizeof(block->header); + size += offset; + + /* validate all the length/size fields */ + if ((size + sizeof(*header)) >= OCC_RESP_DATA_BYTES) { + dev_warn(occ->bus_dev, "exceeded response buffer\n"); + return; + } + + dev_dbg(occ->bus_dev, " %04x..%04x: %.4s (%d sensors)\n", + old_offset, offset - 1, block->header.eye_catcher, + block->header.num_sensors); + + /* match sensor block type */ + if (strncmp(block->header.eye_catcher, "TEMP", 4) == 0) + sensor = &sensors->temp; + else if (strncmp(block->header.eye_catcher, "FREQ", 4) == 0) + sensor = &sensors->freq; + else if (strncmp(block->header.eye_catcher, "POWR", 4) == 0) + sensor = &sensors->power; + else if (strncmp(block->header.eye_catcher, "CAPS", 4) == 0) + sensor = &sensors->caps; + else if (strncmp(block->header.eye_catcher, "EXTN", 4) == 0) + sensor = &sensors->extended; + else { + dev_warn(occ->bus_dev, "sensor not supported %.4s\n", +block->header.eye_catcher); + continue; + } + + sensor->num_sensors = block->header.num_sensors; + sensor->version = block->header.sensor_format; + sensor->data = &block->data; + } + + dev_dbg(occ->bus_dev, "Max resp size: %u+%zd=%zd\n", size, + sizeof(*header), size + sizeof(*header)); +} + int occ_setup(struct occ *occ, const char *name) { int rc; @@ -46,5 +105,7 @@ int occ_setup(struct occ *occ, const char *name) return rc; } + occ_parse_poll_response(occ); + return 0; } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 775b421..cfcd353 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -29,10 +29,65 @@ struct occ_response { __be16 checksum; } __packed; +struct occ_sensor_data_block_header { + u8 eye_catcher[4]; + u8 reserved; + u8 sensor_format; + u8 sensor_length; + u8 num_sensors; +} __packed; + +struct occ_sensor_data_block { + struct occ_sensor_data_block_header header; + u32 data; +} __packed; + +struct occ_poll_response_header { + u8 status; + u8 ext_status; + u8 occs_present; + u8 config_data; + u8 occ_state; + u8 mode; + u8 ips_status; + u8 error_log_id; + __be32 error_log_start_address; + __be16 error_log_length; + u16 reserved; + u8 occ_code_level[
[PATCH v4 4/9] hwmon: Add On-Chip Controller (OCC) hwmon driver
The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system. This driver provides a hwmon interface to the OCC from a service processor (e.g. a BMC). The driver supports both POWER8 and POWER9 OCCs. Communications with the POWER8 OCC are established over standard I2C bus. The driver communicates with the POWER9 OCC through the FSI-based OCC driver, which handles the lower-level communication details. This patch lays out the structure of the OCC hwmon driver. There are two platform drivers, one each for P8 and P9 OCCs. These are probed through the I2C tree and the FSI-based OCC driver, respectively. The patch also defines the first common structures and methods between the two OCC versions. Signed-off-by: Eddie James --- drivers/hwmon/Kconfig | 2 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/occ/Kconfig | 28 + drivers/hwmon/occ/Makefile | 11 +++ drivers/hwmon/occ/common.c | 50 + drivers/hwmon/occ/common.h | 43 + drivers/hwmon/occ/p8_i2c.c | 71 + drivers/hwmon/occ/p9_sbe.c | 78 ++ 8 files changed, 284 insertions(+) create mode 100644 drivers/hwmon/occ/Kconfig create mode 100644 drivers/hwmon/occ/Makefile create mode 100644 drivers/hwmon/occ/common.c create mode 100644 drivers/hwmon/occ/common.h create mode 100644 drivers/hwmon/occ/p8_i2c.c create mode 100644 drivers/hwmon/occ/p9_sbe.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a4e5d3c..9b3871e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1293,6 +1293,8 @@ config SENSORS_NSA320 This driver can also be built as a module. If so, the module will be called nsa320-hwmon. +source drivers/hwmon/occ/Kconfig + config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 93f7f41..f5c7b44 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -178,6 +178,7 @@ obj-$(CONFIG_SENSORS_WM831X)+= wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE)+= xgene-hwmon.o +obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_PMBUS)+= pmbus/ ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig new file mode 100644 index 000..9579b63 --- /dev/null +++ b/drivers/hwmon/occ/Kconfig @@ -0,0 +1,28 @@ +# +# On-Chip Controller configuration +# + +config SENSORS_OCC + tristate "POWER On-Chip Controller" + help +This option enables support for monitoring a variety of system sensors +provided by the On-Chip Controller (OCC) on a POWER processor. + +This driver can also be built as a module. If so, the module will be +called occ-hwmon. + +config SENSORS_OCC_P8_I2C + bool "POWER8 OCC through I2C" + depends on I2C && SENSORS_OCC + help +This option enables support for monitoring sensors provided by the OCC +on a POWER8 processor. Communications with the OCC are established +through I2C bus. + +config SENSORS_OCC_P9_SBE + bool "POWER9 OCC through SBE" + depends on FSI_OCC && SENSORS_OCC + help +This option enables support for monitoring sensors provided by the OCC +on a POWER9 processor. Communications with the OCC are established +through SBEFIFO on an FSI bus. diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile new file mode 100644 index 000..ab5c3e9 --- /dev/null +++ b/drivers/hwmon/occ/Makefile @@ -0,0 +1,11 @@ +occ-hwmon-objs := common.o + +ifeq ($(CONFIG_SENSORS_OCC_P9_SBE), y) +occ-hwmon-objs += p9_sbe.o +endif + +ifeq ($(CONFIG_SENSORS_OCC_P8_I2C), y) +occ-hwmon-objs += p8_i2c.o +endif + +obj-$(CONFIG_SENSORS_OCC) += occ-hwmon.o diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c new file mode 100644 index 000..1fd3453 --- /dev/null +++ b/drivers/hwmon/occ/common.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * OCC hwmon driver common functionality + * + * Copyright (C) IBM Corporation 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include + +#include "common.h" + +static int occ_poll(struct occ *occ) +{ + u16 checksum = occ->poll_cmd_data + 1; + u8 cmd[8]; + + /* big endian */ + cmd[0] = 0; /* sequence number */ +
[PATCH v4 8/9] hwmon (occ): Add sensor attributes and register hwmon device
Setup the sensor attributes for every OCC sensor found by the first poll response. Register the attributes with hwmon. Signed-off-by: Eddie James --- drivers/hwmon/occ/common.c | 452 + drivers/hwmon/occ/common.h | 16 ++ 2 files changed, 468 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 1719536..da55919 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -11,10 +11,12 @@ */ #include +#include #include #include #include #include +#include #include #include "common.h" @@ -678,6 +680,439 @@ static ssize_t occ_show_extended(struct device *dev, return rc; } +/* + * Some helper macros to make it easier to define an occ_attribute. Since these + * are dynamically allocated, we shouldn't use the existing kernel macros which + * stringify the name argument. + */ +#define ATTR_OCC(_name, _mode, _show, _store) { \ + .attr = { \ + .name = _name, \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode),\ + }, \ + .show = _show,\ + .store = _store, \ +} + +#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) {\ + .dev_attr = ATTR_OCC(_name, _mode, _show, _store),\ + .index = _index, \ + .nr = _nr, \ +} + +#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index) \ + ((struct sensor_device_attribute_2) \ + SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index)) + +/* + * Allocate and instatiate sensor_device_attribute_2s. It's most efficient to + * use our own instead of the built-in hwmon attribute types. + */ +static int occ_setup_sensor_attrs(struct occ *occ) +{ + unsigned int i, s, num_attrs = 0; + struct device *dev = occ->bus_dev; + struct occ_sensors *sensors = &occ->sensors; + struct occ_attribute *attr; + struct temp_sensor_2 *temp; + ssize_t (*show_temp)(struct device *, struct device_attribute *, +char *) = occ_show_temp_1; + ssize_t (*show_freq)(struct device *, struct device_attribute *, +char *) = occ_show_freq_1; + ssize_t (*show_power)(struct device *, struct device_attribute *, + char *) = occ_show_power_1; + ssize_t (*show_caps)(struct device *, struct device_attribute *, +char *) = occ_show_caps_1; + + switch (sensors->temp.version) { + case 1: + num_attrs += (sensors->temp.num_sensors * 2); + break; + case 2: + num_attrs += (sensors->temp.num_sensors * 4); + show_temp = occ_show_temp_2; + break; + default: + sensors->temp.num_sensors = 0; + } + + switch (sensors->freq.version) { + case 2: + show_freq = occ_show_freq_2; + /* fall through */ + case 1: + num_attrs += (sensors->freq.num_sensors * 2); + break; + default: + sensors->freq.num_sensors = 0; + } + + switch (sensors->power.version) { + case 1: + num_attrs += (sensors->power.num_sensors * 4); + break; + case 2: + num_attrs += (sensors->power.num_sensors * 6); + show_power = occ_show_power_2; + break; + case 0xA0: + num_attrs += (sensors->power.num_sensors * 19); + show_power = occ_show_power_a0; + break; + default: + sensors->power.num_sensors = 0; + } + + switch (sensors->caps.version) { + case 1: + num_attrs += (sensors->caps.num_sensors * 6); + break; + case 2: + num_attrs += (sensors->caps.num_sensors * 7); + show_caps = occ_show_caps_2; + break; + case 3: + num_attrs += (sensors->caps.num_sensors * 8); + show_caps = occ_show_caps_3; + break; + default: + sensors->caps.num_sensors = 0; + } + + switch (sensors->extended.version) { + case 1: + num_attrs += (sensors->extended.num_sensors * 3); + break; + default: + sensors->extended.num_sensors = 0; + } + + occ->attrs = devm_kzalloc(dev, siz
[PATCH v4 3/9] dt-bindings: i2c: Add P8 OCC hwmon device documentation
Document the bindings for I2C-based OCC hwmon device. Signed-off-by: Eddie James Acked-by: Rob Herring --- .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt | 25 ++ 1 file changed, 25 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt diff --git a/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt new file mode 100644 index 000..5dc5d2e --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt @@ -0,0 +1,25 @@ +Device-tree bindings for I2C-based On-Chip Controller hwmon device +-- + +Required properties: + - compatible = "ibm,p8-occ-hwmon"; + - reg = ;: I2C bus address + +Examples: + +i2c-bus@100 { +#address-cells = <1>; +#size-cells = <0>; +clock-frequency = <10>; +< more properties > + +occ-hwmon@1 { +compatible = "ibm,p8-occ-hwmon"; +reg = <0x50>; +}; + +occ-hwmon@2 { +compatible = "ibm,p8-occ-hwmon"; +reg = <0x51>; +}; +}; -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v4 5/9] hwmon (occ): Add command transport method for P8 and P9
For the P8 OCC, add the procedure to send a command to the OCC over I2C bus. This involves writing the OCC command registers with serial communication operations (SCOMs) interpreted by the I2C slave. For the P9 OCC, add a procedure to use the OCC in-kernel API to send a command to the OCC through the SBE. Signed-off-by: Eddie James --- drivers/hwmon/occ/p8_i2c.c | 185 - drivers/hwmon/occ/p9_sbe.c | 38 +- 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index 899294b..9a920db 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -12,11 +12,29 @@ #include #include +#include #include +#include #include +#include +#include #include "common.h" +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +/* OCB (on-chip control bridge - interface to OCC) registers */ +#define OCB_DATA1 0x6B035 +#define OCB_ADDR 0x6B070 +#define OCB_DATA3 0x6B075 + +/* OCC SRAM address space */ +#define OCC_SRAM_ADDR_CMD 0x6000 +#define OCC_SRAM_ADDR_RESP 0x7000 + +#define OCC_DATA_ATTN 0x2001 + struct p8_i2c_occ { struct occ occ; struct i2c_client *client; @@ -24,9 +42,174 @@ struct p8_i2c_occ { #define to_p8_i2c_occ(x) container_of((x), struct p8_i2c_occ, occ) +static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data) +{ + ssize_t rc; + __be64 buf; + struct i2c_msg msgs[2]; + + /* p8 i2c slave requires shift */ + address <<= 1; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags & I2C_M_TEN; + msgs[0].len = sizeof(u32); + /* address is a scom address; bus-endian */ + msgs[0].buf = (char *)&address; + + /* data from OCC is big-endian */ + msgs[1].addr = client->addr; + msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[1].len = sizeof(u64); + msgs[1].buf = (char *)&buf; + + rc = i2c_transfer(client->adapter, msgs, 2); + if (rc < 0) + return rc; + + *(u64 *)data = be64_to_cpu(buf); + + return 0; +} + +static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data) +{ + u32 buf[3]; + ssize_t rc; + + /* p8 i2c slave requires shift */ + address <<= 1; + + /* address is bus-endian; data passed through from user as-is */ + buf[0] = address; + memcpy(&buf[1], &data[4], sizeof(u32)); + memcpy(&buf[2], data, sizeof(u32)); + + rc = i2c_master_send(client, (const char *)buf, sizeof(buf)); + if (rc < 0) + return rc; + else if (rc != sizeof(buf)) + return -EIO; + + return 0; +} + +static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address, + u32 data0, u32 data1) +{ + u8 buf[8]; + + memcpy(buf, &data0, 4); + memcpy(buf + 4, &data1, 4); + + return p8_i2c_occ_putscom(client, address, buf); +} + +static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address, +u8 *data) +{ + __be32 data0, data1; + + memcpy(&data0, data, 4); + memcpy(&data1, data + 4, 4); + + return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0), + be32_to_cpu(data1)); +} + static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd) { - return -EOPNOTSUPP; + int i, rc; + unsigned long start; + u16 data_length; + const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS); + const long int wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); + struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ); + struct i2c_client *client = ctx->client; + struct occ_response *resp = &occ->resp; + + start = jiffies; + + /* set sram address for command */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0); + if (rc) + return rc; + + /* write command (expected to already be BE), we need bus-endian... */ + rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd); + if (rc) + return rc; + + /* trigger OCC attention */ + rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0); + if (rc) + return rc; + + do { + /* set sram address for response */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, + OCC_SRAM_ADDR_RESP, 0); + if (rc) + return rc; + + rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)re
[PATCH v4 9/9] hwmon (occ): Add sysfs attributes for additional OCC data
The OCC provides a variety of additional information about the state of the host processor, such as throttling, error conditions, and the number of OCCs detected in the system. This information is essential to service processor applications such as fan control and host management. Therefore, export this data in the form of sysfs attributes attached to the platform device (to which the hwmon device is also attached). Signed-off-by: Eddie James --- drivers/hwmon/occ/Makefile | 2 +- drivers/hwmon/occ/common.c | 45 ++- drivers/hwmon/occ/common.h | 17 drivers/hwmon/occ/p8_i2c.c | 10 +++ drivers/hwmon/occ/p9_sbe.c | 1 + drivers/hwmon/occ/sysfs.c | 188 + 6 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 drivers/hwmon/occ/sysfs.c diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile index ab5c3e9..46a8636 100644 --- a/drivers/hwmon/occ/Makefile +++ b/drivers/hwmon/occ/Makefile @@ -1,4 +1,4 @@ -occ-hwmon-objs := common.o +occ-hwmon-objs := common.o sysfs.o ifeq ($(CONFIG_SENSORS_OCC_P9_SBE), y) occ-hwmon-objs += p9_sbe.o diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index da55919..7bc52af 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -21,6 +21,11 @@ #include "common.h" +#define OCC_ERROR_COUNT_THRESHOLD 2 /* required by OCC spec */ + +#define OCC_STATE_SAFE 4 +#define OCC_SAFE_TIMEOUT msecs_to_jiffies(6) /* 1 min */ + #define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000) #define OCC_TEMP_SENSOR_FAULT 0xFF @@ -128,8 +133,10 @@ struct extended_sensor { static int occ_poll(struct occ *occ) { + int rc; u16 checksum = occ->poll_cmd_data + 1; u8 cmd[8]; + struct occ_poll_response_header *header; /* big endian */ cmd[0] = 0; /* sequence number */ @@ -142,7 +149,35 @@ static int occ_poll(struct occ *occ) cmd[7] = 0; /* mutex should already be locked if necessary */ - return occ->send_cmd(occ, cmd); + rc = occ->send_cmd(occ, cmd); + if (rc) { + if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD) + occ->error = rc; + + goto done; + } + + /* clear error since communication was successful */ + occ->error_count = 0; + occ->error = 0; + + /* check for safe state */ + header = (struct occ_poll_response_header *)occ->resp.data; + if (header->occ_state == OCC_STATE_SAFE) { + if (occ->last_safe) { + if (time_after(jiffies, + occ->last_safe + OCC_SAFE_TIMEOUT)) + occ->error = -EHOSTDOWN; + } else { + occ->last_safe = jiffies; + } + } else { + occ->last_safe = 0; + } + +done: + occ_sysfs_poll_done(occ); + return rc; } static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) @@ -174,7 +209,7 @@ static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) return rc; } -static int occ_update_response(struct occ *occ) +int occ_update_response(struct occ *occ) { int rc = mutex_lock_interruptible(&occ->lock); @@ -1207,5 +1242,9 @@ int occ_setup(struct occ *occ, const char *name) return rc; } - return 0; + rc = occ_setup_sysfs(occ); + if (rc) + dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc); + + return rc; } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 2c1c06e..7bacbb6 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -112,8 +112,25 @@ struct occ { struct occ_attribute *attrs; struct attribute_group group; const struct attribute_group *groups[2]; + + int error; /* latest transfer error */ + unsigned int error_count; /* number of xfr errors observed */ + unsigned long last_safe;/* time OCC entered "safe" state */ + + /* +* Store the previous state data for comparison in order to notify +* sysfs readers of state changes. +*/ + int prev_error; + u8 prev_stat; + u8 prev_ext_stat; + u8 prev_occs_present; }; int occ_setup(struct occ *occ, const char *name); +int occ_setup_sysfs(struct occ *occ); +void occ_shutdown(struct occ *occ); +void occ_sysfs_poll_done(struct occ *occ); +int occ_update_response(struct occ *occ); #endif /* OCC_COMMON_H */ diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index 9a920db..63f3db8 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -232,6 +232,15 @@ static
[PATCH v4 1/9] fsi: Add On-Chip Controller (OCC) driver
The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system. This driver provides an atomic communications channel between a service processor (e.g. a BMC) and the OCC. The driver is dependent on the FSI SBEFIFO driver to get hardware access through the SBE to the OCC SRAM. Commands are issued to the SBE to send or fetch data to the SRAM. Signed-off-by: Eddie James Signed-off-by: Andrew Jeffery Signed-off-by: Benjamin Herrenschmidt Signed-off-by: Joel Stanley --- drivers/fsi/Kconfig | 10 + drivers/fsi/Makefile| 1 + drivers/fsi/fsi-occ.c | 609 include/linux/fsi-occ.h | 34 +++ 4 files changed, 654 insertions(+) create mode 100644 drivers/fsi/fsi-occ.c create mode 100644 include/linux/fsi-occ.h diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index 24f84a9..322cec3 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -39,4 +39,14 @@ config FSI_SBEFIFO a pipe-like FSI device for communicating with the self boot engine (SBE) on POWER processors. +config FSI_OCC + tristate "OCC SBEFIFO client device driver" + depends on FSI_SBEFIFO + ---help--- + This option enables an SBEFIFO based On-Chip Controller (OCC) device + driver. The OCC is a device embedded on a POWER processor that collects + and aggregates sensor data from the processor and system. The OCC can + provide the raw sensor data as well as perform thermal and power + management on the system. + endif diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index 851182e..75fdc6d 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o +obj-$(CONFIG_FSI_OCC) += fsi-occ.o diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c new file mode 100644 index 000..bba67a6 --- /dev/null +++ b/drivers/fsi/fsi-occ.c @@ -0,0 +1,609 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * On-Chip Controller Driver + * + * Copyright (C) IBM Corporation 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OCC_SRAM_BYTES 4096 +#define OCC_CMD_DATA_BYTES 4090 +#define OCC_RESP_DATA_BYTES4089 + +#define OCC_SRAM_CMD_ADDR 0xFFFBE000 +#define OCC_SRAM_RSP_ADDR 0xFFFBF000 + +/* + * Assume we don't have much FFDC, if we do we'll overflow and + * fail the command. This needs to be big enough for simple + * commands as well. + */ +#define OCC_SBE_STATUS_WORDS 32 + +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +struct occ { + struct device *dev; + struct device *sbefifo; + char name[32]; + int idx; + struct miscdevice mdev; + struct mutex occ_lock; +}; + +#define to_occ(x) container_of((x), struct occ, mdev) + +struct occ_response { + u8 seq_no; + u8 cmd_type; + u8 return_status; + __be16 data_length; + u8 data[OCC_RESP_DATA_BYTES + 2]; /* two bytes checksum */ +} __packed; + +struct occ_client { + struct occ *occ; + struct mutex lock; + size_t data_size; + size_t read_offset; + u8 *buffer; +}; + +#define to_client(x) container_of((x), struct occ_client, xfr) + +static DEFINE_IDA(occ_ida); + +static int occ_open(struct inode *inode, struct file *file) +{ + struct occ_client *client = kzalloc(sizeof(*client), GFP_KERNEL); + struct miscdevice *mdev = file->private_data; + struct occ *occ = to_occ(mdev); + + if (!client) + return -ENOMEM; + + client->buffer = (u8 *)__get_free_page(GFP_KERNEL); + if (!client->buffer) { + kfree(client); + return -ENOMEM; + } + + client->occ = occ; + mutex_init(&client->lock); + file->private_data = client; + + /* We allocate a 1-page buffer, make sure it all fits */ + BUILD_BUG_ON((OCC_CMD_DATA_BYTES + 3) > PAGE_SIZE); + BUILD_BUG_ON((OCC_RESP_DATA_BYTES + 7) > PAGE_SIZE); + + return 0; +} + +static ssize_t occ_read(struct file *file, char __user *buf, size_t len, + loff_t *offset) +{ + struct occ_client *client = file->private_
[PATCH v4 0/9] hwmon and fsi: Add On-Chip Controller (OCC) driver
This series adds a hwmon driver to support the OCC on POWER8 and POWER9 processors. The OCC is an embedded processor that provides realtime power and thermal monitoring and management. The series also adds a "bus" driver to handle atomic communication between the service processor and the OCC on a POWER9 chip. This communication takes place over FSI bus to the SBE (Self-Boot engine) FIFO, which in turn communicates with the OCC. The driver for the SBEFIFO is already in linux-next as an FSI client driver. For POWER8 OCCs, communication between the service processor and the OCC is achieved over I2C bus. Changes since v3: * Add the FSI OCC driver. * Pull the sysfs attribute code into it's own file for cleanliness. * Various fixes for attribute creation and integer overflow. Changes since v2: * Add sysfs_notify for the error and throttling attributes when change is detected. * Removed occs_present counting of devices bound. * Improved remove() of P9 driver to avoid bad behavior with relation to OCC driver when unbound. * Added default cases (return EINVAL) for all sensor show functions. * Added temperature fault sensor. * Added back dt binding documentation for P9 to address checkpatch warning. * Added occs_present attribute from the poll response. Changes since v1: * Remove wait loop in P9 code, as that is now handled by FSI OCC driver. * Removed dt binding documentation for P9, FSI OCC driver will probe OCC hwmon driver automatically. * Moved OCC response code definitions to the OCC include file. * Fixed includes. * Changed some structure fields to __beXX as that is what they are. * Changed some errnos. * Removed some dev_err(). * Refactored P8 code a bit to use #defined addresses and magic values, and changed "goto retry" to a loop. * Refactored error handling a bit. Eddie James (9): fsi: Add On-Chip Controller (OCC) driver Documentation: hwmon: Add OCC documentation dt-bindings: i2c: Add P8 OCC hwmon device documentation hwmon: Add On-Chip Controller (OCC) hwmon driver hwmon (occ): Add command transport method for P8 and P9 hwmon (occ): Parse OCC poll response hwmon (occ): Add sensor types and versions hwmon (occ): Add sensor attributes and register hwmon device hwmon (occ): Add sysfs attributes for additional OCC data .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt | 25 + Documentation/hwmon/occ| 73 ++ drivers/fsi/Kconfig| 10 + drivers/fsi/Makefile |1 + drivers/fsi/fsi-occ.c | 609 ++ drivers/hwmon/Kconfig |2 + drivers/hwmon/Makefile |1 + drivers/hwmon/occ/Kconfig | 28 + drivers/hwmon/occ/Makefile | 11 + drivers/hwmon/occ/common.c | 1250 drivers/hwmon/occ/common.h | 136 +++ drivers/hwmon/occ/p8_i2c.c | 264 + drivers/hwmon/occ/p9_sbe.c | 115 ++ drivers/hwmon/occ/sysfs.c | 188 +++ include/linux/fsi-occ.h| 34 + 15 files changed, 2747 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt create mode 100644 Documentation/hwmon/occ create mode 100644 drivers/fsi/fsi-occ.c create mode 100644 drivers/hwmon/occ/Kconfig create mode 100644 drivers/hwmon/occ/Makefile create mode 100644 drivers/hwmon/occ/common.c create mode 100644 drivers/hwmon/occ/common.h create mode 100644 drivers/hwmon/occ/p8_i2c.c create mode 100644 drivers/hwmon/occ/p9_sbe.c create mode 100644 drivers/hwmon/occ/sysfs.c create mode 100644 include/linux/fsi-occ.h -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: hwmon driver with misc interface
On 07/08/2018 08:26 PM, Guenter Roeck wrote: On 07/08/2018 06:05 PM, Benjamin Herrenschmidt wrote: On Sun, 2018-07-08 at 16:30 -0700, Guenter Roeck wrote: Trying to be be reasonable Let's make some ground rules. - Do not attach foreign attributes (not related to hardware monitoring) to the hwmon device. Attach foreign attributes to its parent, eg the platform or i2c driver, or to a separate (misc ?) device if that is not feasible for some reason. - Avoid foreign subsystem drivers. If the chip has an input device, a watchdog, and a hardware monitor, there should be three drivers. This is to some degree flexible; for example, PMBus drivers may register as power regulators, and some chips also have gpio support. But what, for example, the applesmc driver does is really not acceptable. This rule can be a bit nasty if the various "parts" of the chip need tight interlock, share an interrupt etc... the solution to that is to have most of the common code in a "parent" driver that creates child devices with separate drivers that directly link onto the parent and use exported functions, but it can easily bloat the driver significantly for little benefit. But that is what mfd drivers are for, or am I missing something ? After all, it has "multi-function device" right in its name. Sure, there is somebloat, but on the plus side it ensures that all bits and pieces are reviewed by the respective maintainers, and the cross-functional API is _forced_ to be clean. That said, this is maybe not *too much* of an issue in the OCC case, see below. - Private hwmon attributes are acceptable as long as they are clearly documented and explained as necessary. This is not a free ride; you should have good reasons for private attributes and be able to explain that and why you need them. In this context, "because the hardware provides the information" is not a valid reason. The use case is important, not the fact that the hardware provides some random information. Can you work with that ? Anything is always possible :-) The main question for me here is whether to keep what we do today: * sbefifo (the transport driver) | * fsi-occ platform driver ("passes occ hwmon commands to sbefifo and adds /dev/occ") | * occ-hwmon Or can I collapse fsi-occ and occ-hwmon into one. Now /dev/occ is just a "raw" interface to send commands to the OCC, via the same path occ-hwmon does. There's locking needed there between the two so it currently happens in fsi-occ. From what you are saying, you prefer that we keep it separate, which is our current design. I find it a bit messy but it's not a huge deal frankly, so let's do so. Other drivers solve that with an API from parent to child, either with a direct function call or with a callback function provided to the child. Another option would be to handle it through regmap; That nowadays supports custom accesses implemented in the parent driver (see regmap_read and regmap_write in struct regmap_config). The child driver gets the regmap pointer and uses the regmap API. I don't see that as messy. The pre-requisite sbefifo driver is now in Greg's tree (and I'll have some fixes for it in -next this week), so you should be able to at least test build when Eddie resubmits. Ok. As for the various sysfs files for monitoring the base functionality of the occ, Eddie, you can always move them to fsi-occ. Yes, I think that would be more appropriate. This still won't work, since then we wouldn't have those attributes available in the P8 version of the driver (which has no fsi-occ driver). In addition, how would the poll response data get from the hwmon driver to the fsi-occ driver? Yet another interface? Seems awkward. How about debugfs? We don't really mind where the attributes are, just that the data is exposed somewhere... Thanks, Eddie Thanks, Guenter Cheers, Ben. -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v7 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 138 +- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index ef2c5bf..70cecb0 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -37,6 +38,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT0xfa #define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd @@ -64,15 +66,24 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; #ifdef CONFIG_GPIOLIB struct gpio_chip gpio; #endif + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -359,6 +370,122 @@ static void ucd9000_probe_gpio(struct i2c_client *client, } #endif /* CONFIG_GPIOLIB */ +#ifdef CONFIG_DEBUG_FS +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + char str[(I2C_SMBUS_BLOCK_MAX * 2) + 2]; + char *res; + int rc; + + rc = ucd9000_get_mfr_status(client, buffer); + if (rc < 0) + return rc; + + res = bin2hex(str, buffer, min(rc, I2C_SMBUS_BLOCK_MAX)); + *res++ = '\n'; + *res = 0; + + return simple_read_from_buffer(buf, count, ppos, str, res - str); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* +* Of the chips this driver supports, only the UCD9090, UCD90160, +* and UCD90910 report GPI faults in their MFR_STATUS register, so only +* create the GPI fault debugfs attributes for those chips. +*/ + if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 || + mid->driver_data == ucd90910) { + entries = devm_kzalloc(&client->dev, + sizeof(*entries) * UCD9000_GPI_COUNT, + GFP_KERNEL); + if (!entries) + ret
[PATCH v7 0/2] hwmon: (ucd9000) Add gpio and debugfs interfaces
The ucd9000 series chips have gpio pins. Add a gpio chip interface to the ucd device so that users can query and set the state of the gpio pins. Add a debugfs interface using the existing pmbus debugfs directory to provide MFR_STATUS and the status of the gpi faults to users. Changes since v6: - don't return null terminator for mfr_status Changes since v5: - enclose gpio code in #ifdef GPIOLIB - don't initialize buffers for mfr_status; set last char to 0 instead - cap the size argument to bin2hex Changes since v4: - max-sized buffers for smbus transfers - used bin2hex instead of my own code Changes since v3: - remove setting of gpio_chip->owner - format the mfr_status data - switch to #ifdef rather than #if IS_ENABLED for debugfs Changes since v2: - split the gpio registration into it's own function Changes since v1: - dropped dev_err messages - made gpio chip registration conditional on having gpio pins - made mfr_status debugfs attribute more simple Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status drivers/hwmon/pmbus/ucd9000.c | 350 +- 1 file changed, 349 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v7 1/2] hwmon: (ucd9000) Add gpio chip interface
From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 212 ++ 1 file changed, 212 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..ef2c5bf 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +37,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT0xfa +#define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT1 + #define UCD9000_MON_TYPE(x)(((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x)((x) & 0x0f) @@ -47,9 +59,17 @@ #define UCD9000_NUM_FAN4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio; +#endif }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +169,196 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +#ifdef CONFIG_GPIOLIB +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, +int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + +
Re: [PATCH v6 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
On 03/16/2018 02:59 PM, Guenter Roeck wrote: On Fri, Mar 16, 2018 at 02:25:59PM -0500, Eddie James wrote: From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 138 +- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index ef2c5bf..88c98fb 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -37,6 +38,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT 0xfa #define UCD9000_GPIO_CONFIG 0xfb #define UCD9000_DEVICE_ID 0xfd @@ -64,15 +66,24 @@ #define UCD901XX_NUM_GPIOS26 #define UCD90910_NUM_GPIOS26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; #ifdef CONFIG_GPIOLIB struct gpio_chip gpio; #endif + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -359,6 +370,122 @@ static void ucd9000_probe_gpio(struct i2c_client *client, } #endif /* CONFIG_GPIOLIB */ +#ifdef CONFIG_DEBUG_FS +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + char str[(I2C_SMBUS_BLOCK_MAX * 2) + 2]; + char *res; + int rc; + + rc = ucd9000_get_mfr_status(client, buffer); + if (rc < 0) + return rc; + + res = bin2hex(str, buffer, min(rc, I2C_SMBUS_BLOCK_MAX)); + *res++ = '\n'; + *res++ = 0; + Unless I am missing something, this now returns the terminating '\0' to the user. Is this really what you want ? Yes, that was my intention. I thought that was normal. It worked fine when I cat'ed the file. Up to you, I can change it if you like. Thanks, Eddie I see other code adding the terminator, but it doesn't usually return it to the user. Guenter + return simple_read_from_buffer(buf, count, ppos, str, res - str); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* +* Of the chips this driver supports, only the UCD9090, UCD90160, +* and UCD90910 report
[PATCH v6 1/2] hwmon: (ucd9000) Add gpio chip interface
From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 212 ++ 1 file changed, 212 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..ef2c5bf 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +37,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT0xfa +#define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT1 + #define UCD9000_MON_TYPE(x)(((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x)((x) & 0x0f) @@ -47,9 +59,17 @@ #define UCD9000_NUM_FAN4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio; +#endif }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +169,196 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +#ifdef CONFIG_GPIOLIB +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, +int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + +
[PATCH v6 0/2] hwmon: (ucd9000) Add gpio and debugfs interfaces
The ucd9000 series chips have gpio pins. Add a gpio chip interface to the ucd device so that users can query and set the state of the gpio pins. Add a debugfs interface using the existing pmbus debugfs directory to provide MFR_STATUS and the status of the gpi faults to users. Changes since v5: - enclose gpio code in #ifdef GPIOLIB - don't initialize buffers for mfr_status; set last char to 0 instead - cap the size argument to bin2hex Changes since v4: - max-sized buffers for smbus transfers - used bin2hex instead of my own code Changes since v3: - remove setting of gpio_chip->owner - format the mfr_status data - switch to #ifdef rather than #if IS_ENABLED for debugfs Changes since v2: - split the gpio registration into it's own function Changes since v1: - dropped dev_err messages - made gpio chip registration conditional on having gpio pins - made mfr_status debugfs attribute more simple Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status drivers/hwmon/pmbus/ucd9000.c | 350 +- 1 file changed, 349 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v6 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 138 +- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index ef2c5bf..88c98fb 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -37,6 +38,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT0xfa #define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd @@ -64,15 +66,24 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; #ifdef CONFIG_GPIOLIB struct gpio_chip gpio; #endif + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -359,6 +370,122 @@ static void ucd9000_probe_gpio(struct i2c_client *client, } #endif /* CONFIG_GPIOLIB */ +#ifdef CONFIG_DEBUG_FS +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + char str[(I2C_SMBUS_BLOCK_MAX * 2) + 2]; + char *res; + int rc; + + rc = ucd9000_get_mfr_status(client, buffer); + if (rc < 0) + return rc; + + res = bin2hex(str, buffer, min(rc, I2C_SMBUS_BLOCK_MAX)); + *res++ = '\n'; + *res++ = 0; + + return simple_read_from_buffer(buf, count, ppos, str, res - str); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* +* Of the chips this driver supports, only the UCD9090, UCD90160, +* and UCD90910 report GPI faults in their MFR_STATUS register, so only +* create the GPI fault debugfs attributes for those chips. +*/ + if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 || + mid->driver_data == ucd90910) { + entries = devm_kzalloc(&client->dev, + sizeof(*entries) * UCD9000_GPI_COUNT, + GFP_KERNEL); + if (!entries) + ret
Re: [PATCH v5 1/2] hwmon: (ucd9000) Add gpio chip interface
On 03/16/2018 08:40 AM, Guenter Roeck wrote: On 03/15/2018 03:21 PM, Eddie James wrote: From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Sorry for not noticing earlier. The 0day reports should be addressed by selecting GPIOLIB in the Kconfig entry. Getting kbuild recursive dependencies when I select GPIOLIB for ucd9000 :( May have to do "depends on" instead and #ifdef GPIOLIB in ucd9000, unless you have another recommendation? Thanks Eddie Guenter Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 201 ++ 1 file changed, 201 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..a34ffc4 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +36,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT 0xfa +#define UCD9000_GPIO_CONFIG 0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT 1 + #define UCD9000_MON_TYPE(x) (((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x) ((x) & 0x0f) @@ -47,9 +58,15 @@ #define UCD9000_NUM_FAN 4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; + struct gpio_chip gpio; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +166,188 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) +
[PATCH v5 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 137 +- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index a34ffc4..c1a1560 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -36,6 +37,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT0xfa #define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd @@ -63,13 +65,22 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; struct gpio_chip gpio; + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -306,6 +317,121 @@ static int ucd9000_gpio_direction_output(struct gpio_chip *gc, val); } +#ifdef CONFIG_DEBUG_FS +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[I2C_SMBUS_BLOCK_MAX]; + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[I2C_SMBUS_BLOCK_MAX] = { 0 }; + char str[(I2C_SMBUS_BLOCK_MAX * 2) + 2] = { 0 }; + char *res; + int rc; + + rc = ucd9000_get_mfr_status(client, buffer); + if (rc < 0) + return rc; + + res = bin2hex(str, buffer, rc); + *res++ = '\n'; + + return simple_read_from_buffer(buf, count, ppos, str, (res - str) + 1); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* +* Of the chips this driver supports, only the UCD9090, UCD90160, +* and UCD90910 report GPI faults in their MFR_STATUS register, so only +* create the GPI fault debugfs attributes for those chips. +*/ + if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 || + mid->driver_data == ucd90910) { + entries = devm_kzalloc(&client->dev, + sizeof(*entries) * UCD9000_GPI_COUNT, + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + for (i =
[PATCH v5 1/2] hwmon: (ucd9000) Add gpio chip interface
From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 201 ++ 1 file changed, 201 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..a34ffc4 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +36,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT0xfa +#define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT1 + #define UCD9000_MON_TYPE(x)(((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x)((x) & 0x0f) @@ -47,9 +58,15 @@ #define UCD9000_NUM_FAN4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; + struct gpio_chip gpio; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +166,188 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, +int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + if (direction_out) { + out_val = requested_out ? UCD9
[PATCH v5 0/2] hwmon: (ucd9000) Add gpio and debugfs interfaces
The ucd9000 series chips have gpio pins. Add a gpio chip interface to the ucd device so that users can query and set the state of the gpio pins. Add a debugfs interface using the existing pmbus debugfs directory to provide MFR_STATUS and the status of the gpi faults to users. Changes since v4: - max-sized buffers for smbus transfers - used bin2hex instead of my own code Changes since v3: - remove setting of gpio_chip->owner - format the mfr_status data - switch to #ifdef rather than #if IS_ENABLED for debugfs Changes since v2: - split the gpio registration into it's own function Changes since v1: - dropped dev_err messages - made gpio chip registration conditional on having gpio pins - made mfr_status debugfs attribute more simple Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status drivers/hwmon/pmbus/ucd9000.c | 338 +- 1 file changed, 337 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v4 0/2] hwmon: (ucd9000) Add gpio and debugfs interfaces
The ucd9000 series chips have gpio pins. Add a gpio chip interface to the ucd device so that users can query and set the state of the gpio pins. Add a debugfs interface using the existing pmbus debugfs directory to provide MFR_STATUS and the status of the gpi faults to users. Changes since v3: - remove setting of gpio_chip->owner - format the mfr_status data - switch to #ifdef rather than #if IS_ENABLED for debugfs Changes since v2: - split the gpio registration into it's own function Changes since v1: - dropped dev_err messages - made gpio chip registration conditional on having gpio pins - made mfr_status debugfs attribute more simple Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status drivers/hwmon/pmbus/ucd9000.c | 354 +- 1 file changed, 353 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v4 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 153 +- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index a34ffc4..f03c404 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -36,6 +37,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT0xfa #define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd @@ -63,13 +65,22 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; struct gpio_chip gpio; + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -306,6 +317,137 @@ static int ucd9000_gpio_direction_output(struct gpio_chip *gc, val); } +#ifdef CONFIG_DEBUG_FS +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + /* +* With the ucd90120 and ucd90124 devices, this command [MFR_STATUS] +* is 2 bytes long (bits 0-15). With the ucd90240 this command is 5 +* bytes long. With all other devices, it is 4 bytes long. +*/ + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[4]; + int ret; + + /* +* This attribute is only created for devices that return 4 bytes for +* status_mfr, so it's safe to call with 4-byte buffer. +*/ + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[5] = { 0 }; /* Need max 5 bytes for any ucd9000 chip. */ + char str[12] = { 0 }; /* Two chars per byte plus \n and \0. */ + int i, num_bytes, num_chars = 0, rc; + + num_bytes = ucd9000_get_mfr_status(client, buffer); + if (num_bytes < 0) + return num_bytes; + + for (i = 0; i < num_bytes; ++i) { + rc = snprintf(&str[num_chars], (sizeof(str) - 1) - num_chars, + "%02x", buffer[i]); + if (rc <= 0) + break; + + num_chars += rc; + } + + str[num_chars] = '\n'; + + return simple_read_from_buffer(buf, count, ppos, str, num_chars + 2); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->n
[PATCH v4 1/2] hwmon: (ucd9000) Add gpio chip interface
From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 201 ++ 1 file changed, 201 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..a34ffc4 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +36,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT0xfa +#define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT1 + #define UCD9000_MON_TYPE(x)(((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x)((x) & 0x0f) @@ -47,9 +58,15 @@ #define UCD9000_NUM_FAN4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; + struct gpio_chip gpio; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +166,188 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, +int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + if (direction_out) { + out_val = requested_out ? UCD9
Re: [PATCH v2 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
On 03/14/2018 02:19 PM, Guenter Roeck wrote: On Tue, Mar 13, 2018 at 03:59:09PM -0500, Eddie James wrote: From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 140 +- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index 023fb9e..b073d8e 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -36,6 +37,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT 0xfa #define UCD9000_GPIO_CONFIG 0xfb #define UCD9000_DEVICE_ID 0xfd @@ -63,13 +65,22 @@ #define UCD901XX_NUM_GPIOS26 #define UCD90910_NUM_GPIOS26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; struct gpio_chip gpio; + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -306,6 +317,124 @@ static int ucd9000_gpio_direction_output(struct gpio_chip *gc, val); } +#if IS_ENABLED(CONFIG_DEBUG_FS) DEBUG_FS is bool, so #ifdef CONFIG_DEBUG_FS is fine here. Ok. +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + /* +* With the ucd90120 and ucd90124 devices, this command [MFR_STATUS] +* is 2 bytes long (bits 0-15). With the ucd90240 this command is 5 +* bytes long. With all other devices, it is 4 bytes long. +*/ + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[4]; + int ret; + + /* +* This attribute is only created for devices that return 4 bytes for +* status_mfr, so it's safe to call with 4-byte buffer. +*/ + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[5] = { 0 }; /* Need max 5 bytes for any ucd9000 chip. */ + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + return simple_read_from_buffer(buf, count, ppos, buffer, ret); Doesn't this report the raw binary data to userspace ? The output should be human-readable. Yes, sorry I thought that was your suggestion when you mentioned hexdump earlier... Will fix. Thanks, Eddie +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) +
Re: [PATCH v2 1/2] hwmon: (ucd9000) Add gpio chip interface
On 03/14/2018 01:55 PM, Guenter Roeck wrote: On Tue, Mar 13, 2018 at 11:13:04PM +0200, Andy Shevchenko wrote: On Tue, Mar 13, 2018 at 10:59 PM, Eddie James wrote: From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. + /* +* Note: +* +* Pinmux support has not been added to the new gpio_chip. +* This support should be added when possible given the mux +* behavior of these IO devices. +*/ + data->gpio.label = (const char *)&client->name; Hmm... Why do you need this casting? + data->gpio.get_direction = ucd9000_gpio_get_direction; + data->gpio.direction_input = ucd9000_gpio_direction_input; + data->gpio.direction_output = ucd9000_gpio_direction_output; + data->gpio.get = ucd9000_gpio_get; + data->gpio.set = ucd9000_gpio_set; + data->gpio.can_sleep = 1; Isn't it type of boolean? You are right. + data->gpio.base = -1; + data->gpio.parent = &client->dev; + data->gpio.owner = THIS_MODULE; + data->gpio.of_node = client->dev.of_node; I think GPIO core does this for you. Same here. I checked and found that it also sets the owner, so maybe this can be dropped as well. Ah true. Especially since there is a TODO: remove chip->owner in the core. + if (data->gpio.ngpio) { Hmm... I would rather reorganize the above part to a separate helper, like static int ..._probe_gpio() { ... switch () { default: return 0; /* GPIO part is optional */ } return 0; } ret = _probe_gpio(); if (ret) dev_warn(); I am neutral to positiva on that. Might as well add the call to devm_gpiochip_add_data() into that function as well. Sure. I can do a v4. Thanks, Eddie + ret = devm_gpiochip_add_data(&client->dev, &data->gpio, +client); + if (ret) + dev_warn(&client->dev, "Could not add gpiochip: %d\n", +ret); + } + return pmbus_do_probe(client, mid, info); } -- 1.8.3.1 -- With Best Regards, Andy Shevchenko -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v3 1/2] hwmon: (ucd9000) Add gpio chip interface
From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 200 ++ 1 file changed, 200 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..321b837 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +36,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT0xfa +#define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT1 + #define UCD9000_MON_TYPE(x)(((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x)((x) & 0x0f) @@ -47,9 +58,15 @@ #define UCD9000_NUM_FAN4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; + struct gpio_chip gpio; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +166,185 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, +int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + if (direction_out) { + out_val = requested_out ? UCD9
[PATCH v3 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 140 +- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index 321b837..4158c23 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -36,6 +37,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT0xfa #define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd @@ -63,13 +65,22 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; struct gpio_chip gpio; + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -306,6 +317,124 @@ static int ucd9000_gpio_direction_output(struct gpio_chip *gc, val); } +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + /* +* With the ucd90120 and ucd90124 devices, this command [MFR_STATUS] +* is 2 bytes long (bits 0-15). With the ucd90240 this command is 5 +* bytes long. With all other devices, it is 4 bytes long. +*/ + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[4]; + int ret; + + /* +* This attribute is only created for devices that return 4 bytes for +* status_mfr, so it's safe to call with 4-byte buffer. +*/ + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[5] = { 0 }; /* Need max 5 bytes for any ucd9000 chip. */ + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + return simple_read_from_buffer(buf, count, ppos, buffer, ret); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* +* Of the chips this driver supports, only the UCD9090, UCD90160, +* and UCD90910 report GPI faults in their MFR_STATUS register, so only +* create the GPI fault debugfs attributes for those chips. +*/ + if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 || + mid->driver_data == ucd90910
[PATCH v3 0/2] hwmon: (ucd9000) Add gpio and debugfs interfaces
The ucd9000 series chips have gpio pins. Add a gpio chip interface to the ucd device so that users can query and set the state of the gpio pins. Add a debugfs interface using the existing pmbus debugfs directory to provide MFR_STATUS and the status of the gpi faults to users. Changes since v2: - split the gpio registration into it's own function Changes since v1: - dropped dev_err messages - made gpio chip registration conditional on having gpio pins - made mfr_status debugfs attribute more simple Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status drivers/hwmon/pmbus/ucd9000.c | 340 +- 1 file changed, 339 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v2 1/2] hwmon: (ucd9000) Add gpio chip interface
From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 201 ++ 1 file changed, 201 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..023fb9e 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +36,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT0xfa +#define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT1 + #define UCD9000_MON_TYPE(x)(((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x)((x) & 0x0f) @@ -47,9 +58,15 @@ #define UCD9000_NUM_FAN4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; + struct gpio_chip gpio; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +166,146 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, +int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_dbg(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_dbg(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(struct gpio_chip *gc, + unsigned int offset, bool direction_out, + int requested_out) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret, config, out_val; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) + return ret; + + if (direction_out) { + out_val = requested_out ? UCD9
[PATCH v2 0/2] hwmon: (ucd9000) Add gpio and debugfs interfaces
The ucd9000 series chips have gpio pins. Add a gpio chip interface to the ucd device so that users can query and set the state of the gpio pins. Add a debugfs interface using the existing pmbus debugfs directory to provide MFR_STATUS and the status of the gpi faults to users. Changes since v1: - dropped dev_err messages - made gpio chip registration conditional on having gpio pins - made mfr_status debugfs attribute more simple Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status drivers/hwmon/pmbus/ucd9000.c | 341 +- 1 file changed, 340 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v2 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 140 +- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index 023fb9e..b073d8e 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -36,6 +37,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT0xfa #define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd @@ -63,13 +65,22 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; struct gpio_chip gpio; + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -306,6 +317,124 @@ static int ucd9000_gpio_direction_output(struct gpio_chip *gc, val); } +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + /* +* With the ucd90120 and ucd90124 devices, this command [MFR_STATUS] +* is 2 bytes long (bits 0-15). With the ucd90240 this command is 5 +* bytes long. With all other devices, it is 4 bytes long. +*/ + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[4]; + int ret; + + /* +* This attribute is only created for devices that return 4 bytes for +* status_mfr, so it's safe to call with 4-byte buffer. +*/ + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static ssize_t ucd9000_debugfs_read_mfr_status(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct i2c_client *client = file->private_data; + u8 buffer[5] = { 0 }; /* Need max 5 bytes for any ucd9000 chip. */ + int ret; + + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) + return ret; + + return simple_read_from_buffer(buf, count, ppos, buffer, ret); +} + +static const struct file_operations ucd9000_debugfs_show_mfr_status_fops = { + .llseek = noop_llseek, + .read = ucd9000_debugfs_read_mfr_status, + .open = simple_open, +}; + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return -ENOENT; + + data->debugfs = debugfs_create_dir(client->name, debugfs); + if (!data->debugfs) + return -ENOENT; + + /* +* Of the chips this driver supports, only the UCD9090, UCD90160, +* and UCD90910 report GPI faults in their MFR_STATUS register, so only +* create the GPI fault debugfs attributes for those chips. +*/ + if (mid->driver_data == ucd9090 || mid->driver_data == ucd90160 || + mid->driver_data == ucd90910
Re: [PATCH 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
On 03/13/2018 02:29 PM, Guenter Roeck wrote: On Tue, Mar 13, 2018 at 02:01:51PM -0500, Eddie James wrote: On 03/10/2018 10:50 AM, Guenter Roeck wrote: On 03/09/2018 11:19 AM, Eddie James wrote: From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 172 +- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index e3a507f..297da0e 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -36,6 +37,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT 0xfa #define UCD9000_GPIO_CONFIG 0xfb #define UCD9000_DEVICE_ID 0xfd @@ -63,13 +65,22 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; struct gpio_chip gpio; + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -328,6 +339,156 @@ static int ucd9000_gpio_direction_output(struct gpio_chip *gc, val); } +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + /* + * With the ucd90120 and ucd90124 devices, this command [MFR_STATUS] + * is 2 bytes long (bits 0-15). With the ucd90240 this command is 5 + * bytes long. With all other devices, it is 4 bytes long. + */ + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[4]; + int ret; + + /* + * This attribute is only created for devices that return 4 bytes for + * status_mfr, so it's safe to call with 4-byte buffer. + */ + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read mfr status. rc:%d\n", + ret); + + return ret; + } + + /* + * Attribute only created for devices with gpi fault bits at bits + * 16-23, which is the second byte of the response. + */ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, + ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static int ucd9000_debugfs_show_mfr_status_word2(void *data, u64 *val) +{ + struct i2c_client *client = data; + __be16 buffer; + int ret; + + ret = ucd9000_get_mfr_status(client, (u8 *)&buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read mfr status. rc:%d\n", + ret); + + return ret; + } + + *val = be16_to_cpu(buffer); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_word2, + ucd9000_debugfs_show_mfr_status_word2, NULL, + "%04llx\n"); + +static int ucd9000_debugfs_show_mfr_status_word4(void *data, u64 *val) +{ + struct i2c_client *client = data; + __be32 buffer; + int ret; + + ret = ucd9000_get_mfr_status(client, (u8 *)&buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read mfr status. rc:%d\n", + ret); + + return ret; + } + + *val = be32_to_cpu(buffer); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_word4, + ucd9000_debugfs_show_mfr_status_word4, NULL, + "%08llx\n"); + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_entry *entries; + int i; + char name[UCD9000_DEBUGFS_NAME_LEN]; + + debugfs = pmbus_get_debugfs_dir(client); +
[PATCH 1/2] hwmon: (ucd9000) Add gpio chip interface
From: Christopher Bostic Add a struct gpio_chip and define some methods so that this device's I/O can be accessed via /sys/class/gpio. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 220 ++ 1 file changed, 220 insertions(+) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index b74dbec..e3a507f 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "pmbus.h" enum chips { ucd9000, ucd90120, ucd90124, ucd90160, ucd9090, ucd90910 }; @@ -35,8 +36,18 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_GPIO_SELECT0xfa +#define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd +/* GPIO CONFIG bits */ +#define UCD9000_GPIO_CONFIG_ENABLE BIT(0) +#define UCD9000_GPIO_CONFIG_OUT_ENABLE BIT(1) +#define UCD9000_GPIO_CONFIG_OUT_VALUE BIT(2) +#define UCD9000_GPIO_CONFIG_STATUS BIT(3) +#define UCD9000_GPIO_INPUT 0 +#define UCD9000_GPIO_OUTPUT1 + #define UCD9000_MON_TYPE(x)(((x) >> 5) & 0x07) #define UCD9000_MON_PAGE(x)((x) & 0x0f) @@ -47,9 +58,15 @@ #define UCD9000_NUM_FAN4 +#define UCD9000_GPIO_NAME_LEN 16 +#define UCD9090_NUM_GPIOS 23 +#define UCD901XX_NUM_GPIOS 26 +#define UCD90910_NUM_GPIOS 26 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; + struct gpio_chip gpio; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -149,6 +166,168 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) }; MODULE_DEVICE_TABLE(of, ucd9000_of_match); +static int ucd9000_gpio_read_config(struct i2c_client *client, + unsigned int offset) +{ + int ret; + + /* No page set required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_SELECT, offset); + if (ret < 0) { + dev_err(&client->dev, "Failed to select GPIO %d: %d\n", offset, + ret); + + return ret; + } + + return i2c_smbus_read_byte_data(client, UCD9000_GPIO_CONFIG); +} + +static int ucd9000_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_err(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + + return ret; + } + + return !!(ret & UCD9000_GPIO_CONFIG_STATUS); +} + +static void ucd9000_gpio_set(struct gpio_chip *gc, unsigned int offset, +int value) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_err(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + + return; + } + + if (value) { + if (ret & UCD9000_GPIO_CONFIG_STATUS) + return; + + ret |= UCD9000_GPIO_CONFIG_STATUS; + } else { + if (!(ret & UCD9000_GPIO_CONFIG_STATUS)) + return; + + ret &= ~UCD9000_GPIO_CONFIG_STATUS; + } + + ret |= UCD9000_GPIO_CONFIG_ENABLE; + + /* Page set not required */ + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) { + dev_err(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); + + return; + } + + ret &= ~UCD9000_GPIO_CONFIG_ENABLE; + + ret = i2c_smbus_write_byte_data(client, UCD9000_GPIO_CONFIG, ret); + if (ret < 0) + dev_err(&client->dev, "Failed to write GPIO %d config: %d\n", + offset, ret); +} + +static int ucd9000_gpio_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct i2c_client *client = gpiochip_get_data(gc); + int ret; + + ret = ucd9000_gpio_read_config(client, offset); + if (ret < 0) { + dev_err(&client->dev, "failed to read GPIO %d config: %d\n", + offset, ret); + + return ret; + } + + return !(ret & UCD9000_GPIO_CONFIG_OUT_ENABLE); +} + +static int ucd9000_gpio_set_direction(st
[PATCH 2/2] hwmon: (ucd9000) Add debugfs attributes to provide mfr_status
From: Christopher Bostic Expose the gpiN_fault fields of mfr_status as individual debugfs attributes. This provides a way for users to be easily notified of gpi faults. Also provide the whole mfr_status register in debugfs. Signed-off-by: Christopher Bostic Signed-off-by: Andrew Jeffery Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ucd9000.c | 172 +- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index e3a507f..297da0e 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include @@ -36,6 +37,7 @@ #define UCD9000_NUM_PAGES 0xd6 #define UCD9000_FAN_CONFIG_INDEX 0xe7 #define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_MFR_STATUS 0xf3 #define UCD9000_GPIO_SELECT0xfa #define UCD9000_GPIO_CONFIG0xfb #define UCD9000_DEVICE_ID 0xfd @@ -63,13 +65,22 @@ #define UCD901XX_NUM_GPIOS 26 #define UCD90910_NUM_GPIOS 26 +#define UCD9000_DEBUGFS_NAME_LEN 24 +#define UCD9000_GPI_COUNT 8 + struct ucd9000_data { u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; struct pmbus_driver_info info; struct gpio_chip gpio; + struct dentry *debugfs; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) +struct ucd9000_debugfs_entry { + struct i2c_client *client; + u8 index; +}; + static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -328,6 +339,156 @@ static int ucd9000_gpio_direction_output(struct gpio_chip *gc, val); } +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer) +{ + int ret = pmbus_set_page(client, 0); + + if (ret < 0) + return ret; + + /* +* With the ucd90120 and ucd90124 devices, this command [MFR_STATUS] +* is 2 bytes long (bits 0-15). With the ucd90240 this command is 5 +* bytes long. With all other devices, it is 4 bytes long. +*/ + return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer); +} + +static int ucd9000_debugfs_show_mfr_status_bit(void *data, u64 *val) +{ + struct ucd9000_debugfs_entry *entry = data; + struct i2c_client *client = entry->client; + u8 buffer[4]; + int ret; + + /* +* This attribute is only created for devices that return 4 bytes for +* status_mfr, so it's safe to call with 4-byte buffer. +*/ + ret = ucd9000_get_mfr_status(client, buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read mfr status. rc:%d\n", + ret); + + return ret; + } + + /* +* Attribute only created for devices with gpi fault bits at bits +* 16-23, which is the second byte of the response. +*/ + *val = !!(buffer[1] & BIT(entry->index)); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_bit, +ucd9000_debugfs_show_mfr_status_bit, NULL, "%1lld\n"); + +static int ucd9000_debugfs_show_mfr_status_word2(void *data, u64 *val) +{ + struct i2c_client *client = data; + __be16 buffer; + int ret; + + ret = ucd9000_get_mfr_status(client, (u8 *)&buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read mfr status. rc:%d\n", + ret); + + return ret; + } + + *val = be16_to_cpu(buffer); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_word2, +ucd9000_debugfs_show_mfr_status_word2, NULL, +"%04llx\n"); + +static int ucd9000_debugfs_show_mfr_status_word4(void *data, u64 *val) +{ + struct i2c_client *client = data; + __be32 buffer; + int ret; + + ret = ucd9000_get_mfr_status(client, (u8 *)&buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read mfr status. rc:%d\n", + ret); + + return ret; + } + + *val = be32_to_cpu(buffer); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucd9000_debugfs_mfr_status_word4, +ucd9000_debugfs_show_mfr_status_word4, NULL, +"%08llx\n"); + +static int ucd9000_init_debugfs(struct i2c_client *client, + const struct i2c_device_id *mid, + struct ucd9000_data *data) +{ + struct dentry *debugfs; + struct ucd9000_debugfs_e
[PATCH 0/2] hwmon: (ucd9000) Add gpio and debugfs interfaces
The ucd9000 series chips have gpio pins. Add a gpio chip interface to the ucd device so that users can query and set the state of the gpio pins. Add a debugfs interface using the existing pmbus debugfs directory to provide MFR_STATUS and the status of the gpi faults to users. Christopher Bostic (2): hwmon: (ucd9000) Add gpio chip interface hwmon: (ucd9000) Add debugfs attributes to provide mfr_status drivers/hwmon/pmbus/ucd9000.c | 392 +- 1 file changed, 391 insertions(+), 1 deletion(-) -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH] hwmon: pmbus: ibm-cffps depends on LEDS_CLASS
On Fri, Jan 12, 2018 at 06:19:00PM +0100, Guenter Roeck wrote: On Fri, Jan 12, 2018 at 04:49:00PM +0100, Arnd Bergmann wrote: > Building without CONFIG_LEDS_CLASS causes a link failure: > > drivers/hwmon/pmbus/ibm-cffps.o: In function `ibm_cffps_probe': > ibm-cffps.c:(.text+0x4f4): undefined reference to `devm_of_led_classdev_register' > > This adds the required dependency. > > Fixes: f69316d62c70 ("hwmon: (pmbus) Add IBM Common Form Factor (CFF) power supply driver") > Signed-off-by: Arnd Bergmann I wanted to let Edward decide if he wants the new dependency or conditional code in the driver. Not having heard from him, I'll take your patch instead for now. Thanks, yes this is a good solution for this driver. Didn't think about that during testing... Thanks Arnd. Eddie Thanks, Guenter > --- > drivers/hwmon/pmbus/Kconfig | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig > index 08479006c7f9..6e4298e99222 100644 > --- a/drivers/hwmon/pmbus/Kconfig > +++ b/drivers/hwmon/pmbus/Kconfig > @@ -39,6 +39,7 @@ config SENSORS_ADM1275 > > config SENSORS_IBM_CFFPS > tristate "IBM Common Form Factor Power Supply" > + depends on LEDS_CLASS > help > If you say yes here you get hardware monitoring support for the IBM > Common Form Factor power supply. > -- > 2.9.0 > -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v3] hwmon (pmbus): cffps: Add led class device for power supply fault led
This power supply device doesn't correctly manage it's own fault led. Add an led class device and register it so that userspace can manage power supply fault led as necessary. Signed-off-by: Eddie James --- Changes since v2: - Use fixed size buffer for led name. Changes since v1: - move led registration into a separate function. - improve comments. drivers/hwmon/pmbus/ibm-cffps.c | 96 + 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 2d6f4f4..93d9a9e 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #define CFFPS_CCIN_CMD 0xBD #define CFFPS_FW_CMD_START 0xFA #define CFFPS_FW_NUM_BYTES 4 +#define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD0xD6 #define CFFPS_INPUT_HISTORY_SIZE 100 @@ -39,6 +41,11 @@ #define CFFPS_MFR_VAUX_FAULT BIT(6) #define CFFPS_MFR_CURRENT_SHARE_WARNINGBIT(7) +#define CFFPS_LED_BLINKBIT(0) +#define CFFPS_LED_ON BIT(1) +#define CFFPS_LED_OFF BIT(2) +#define CFFPS_BLINK_RATE_MS250 + enum { CFFPS_DEBUGFS_INPUT_HISTORY = 0, CFFPS_DEBUGFS_FRU, @@ -63,6 +70,10 @@ struct ibm_cffps { struct ibm_cffps_input_history input_history; int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; + + char led_name[32]; + u8 led_state; + struct led_classdev led; }; #define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) @@ -258,6 +269,69 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page, return rc; } +static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, +enum led_brightness brightness) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + if (brightness == LED_OFF) { + psu->led_state = CFFPS_LED_OFF; + } else { + brightness = LED_FULL; + if (psu->led_state != CFFPS_LED_BLINK) + psu->led_state = CFFPS_LED_ON; + } + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + psu->led_state); + if (rc < 0) + return; + + led_cdev->brightness = brightness; +} + +static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + psu->led_state = CFFPS_LED_BLINK; + + if (led_cdev->brightness == LED_OFF) + return 0; + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_BLINK); + if (rc < 0) + return rc; + + *delay_on = CFFPS_BLINK_RATE_MS; + *delay_off = CFFPS_BLINK_RATE_MS; + + return 0; +} + +static void ibm_cffps_create_led_class(struct ibm_cffps *psu) +{ + int rc; + struct i2c_client *client = psu->client; + struct device *dev = &client->dev; + + snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name, +client->addr); + psu->led.name = psu->led_name; + psu->led.max_brightness = LED_FULL; + psu->led.brightness_set = ibm_cffps_led_brightness_set; + psu->led.blink_set = ibm_cffps_led_blink_set; + + rc = devm_led_classdev_register(dev, &psu->led); + if (rc) + dev_warn(dev, "failed to register led class: %d\n", rc); +} + static struct pmbus_driver_info ibm_cffps_info = { .pages = 1, .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | @@ -286,6 +360,20 @@ static int ibm_cffps_probe(struct i2c_client *client, if (rc) return rc; + /* +* Don't fail the probe if there isn't enough memory for leds and +* debugfs. +*/ + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return 0; + + psu->client = client; + mutex_init(&psu->input_history.update_lock); + psu->input_history.last_update = jiffies - HZ; + + ibm_cffps_create_led_class(psu); + /* Don't fail the probe if we can't create debugfs */ debugfs = pmbus_g
Re: [PATCH v2] hwmon (pmbus): cffps: Add led class device for power supply fault led
On 01/10/2018 12:16 PM, Guenter Roeck wrote: On Wed, Jan 10, 2018 at 11:29:43AM -0600, Eddie James wrote: This power supply device doesn't correctly manage it's own fault led. Add an led class device and register it so that userspace can manage power supply fault led as necessary. Signed-off-by: Eddie James --- Changes since v1: - move led registration into a separate function. - improve comments. drivers/hwmon/pmbus/ibm-cffps.c | 99 + 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 2d6f4f4..cd9f685 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #define CFFPS_CCIN_CMD0xBD #define CFFPS_FW_CMD_START0xFA #define CFFPS_FW_NUM_BYTES4 +#define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD 0xD6 #define CFFPS_INPUT_HISTORY_SIZE 100 @@ -39,6 +41,11 @@ #define CFFPS_MFR_VAUX_FAULT BIT(6) #define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) +#define CFFPS_LED_BLINKBIT(0) +#define CFFPS_LED_ON BIT(1) +#define CFFPS_LED_OFF BIT(2) +#define CFFPS_BLINK_RATE_MS250 + enum { CFFPS_DEBUGFS_INPUT_HISTORY = 0, CFFPS_DEBUGFS_FRU, @@ -63,6 +70,9 @@ struct ibm_cffps { struct ibm_cffps_input_history input_history; int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; + + u8 led_state; + struct led_classdev led; }; #define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) @@ -258,6 +268,69 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page, return rc; } +static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, +enum led_brightness brightness) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + if (brightness == LED_OFF) { + psu->led_state = CFFPS_LED_OFF; + } else { + brightness = LED_FULL; + if (psu->led_state != CFFPS_LED_BLINK) + psu->led_state = CFFPS_LED_ON; + } + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + psu->led_state); + if (rc < 0) + return; + + led_cdev->brightness = brightness; +} + +static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + psu->led_state = CFFPS_LED_BLINK; + + if (led_cdev->brightness == LED_OFF) + return 0; + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_BLINK); + if (rc < 0) + return rc; + + *delay_on = CFFPS_BLINK_RATE_MS; + *delay_off = CFFPS_BLINK_RATE_MS; + + return 0; +} + +static void ibm_cffps_create_led_class(size_t name_size, struct ibm_cffps *psu) +{ + int rc; + struct i2c_client *client = psu->client; + struct device *dev = &client->dev; + char *led_name = (void *)(&psu[1]); + I really dislike this hack. Why not allocate a sufficient fixed length, say, 32 bytes, and just use it ? If you want to be really wasteful, use 48 or 64 bytes; that is still better than this code. That's fair, I found this trick in some sony hid driver, but fixed length is much cleaner. + snprintf(led_name, name_size, "%s-%x", client->name, client->addr); %02x, maybe ? Your call, though. Thanks, Guenter + psu->led.name = led_name; + psu->led.max_brightness = LED_FULL; + psu->led.brightness_set = ibm_cffps_led_brightness_set; + psu->led.blink_set = ibm_cffps_led_blink_set; + + rc = devm_led_classdev_register(dev, &psu->led); + if (rc) + dev_warn(dev, "failed to register led class: %d\n", rc); +} + static struct pmbus_driver_info ibm_cffps_info = { .pages = 1, .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | @@ -277,6 +350,7 @@ static int ibm_cffps_probe(struct i2c_client *client, const struct i2c_device_id *id) { int i, rc; + size_t name_size; struct dentry *debugfs; struct dentry *ibm_cffps_dir; struct ibm_cffps *psu; @@ -286,6 +360,23 @@ stat
[PATCH v2] hwmon (pmbus): cffps: Add led class device for power supply fault led
This power supply device doesn't correctly manage it's own fault led. Add an led class device and register it so that userspace can manage power supply fault led as necessary. Signed-off-by: Eddie James --- Changes since v1: - move led registration into a separate function. - improve comments. drivers/hwmon/pmbus/ibm-cffps.c | 99 + 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 2d6f4f4..cd9f685 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #define CFFPS_CCIN_CMD 0xBD #define CFFPS_FW_CMD_START 0xFA #define CFFPS_FW_NUM_BYTES 4 +#define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD0xD6 #define CFFPS_INPUT_HISTORY_SIZE 100 @@ -39,6 +41,11 @@ #define CFFPS_MFR_VAUX_FAULT BIT(6) #define CFFPS_MFR_CURRENT_SHARE_WARNINGBIT(7) +#define CFFPS_LED_BLINKBIT(0) +#define CFFPS_LED_ON BIT(1) +#define CFFPS_LED_OFF BIT(2) +#define CFFPS_BLINK_RATE_MS250 + enum { CFFPS_DEBUGFS_INPUT_HISTORY = 0, CFFPS_DEBUGFS_FRU, @@ -63,6 +70,9 @@ struct ibm_cffps { struct ibm_cffps_input_history input_history; int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; + + u8 led_state; + struct led_classdev led; }; #define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) @@ -258,6 +268,69 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page, return rc; } +static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, +enum led_brightness brightness) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + if (brightness == LED_OFF) { + psu->led_state = CFFPS_LED_OFF; + } else { + brightness = LED_FULL; + if (psu->led_state != CFFPS_LED_BLINK) + psu->led_state = CFFPS_LED_ON; + } + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + psu->led_state); + if (rc < 0) + return; + + led_cdev->brightness = brightness; +} + +static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + psu->led_state = CFFPS_LED_BLINK; + + if (led_cdev->brightness == LED_OFF) + return 0; + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_BLINK); + if (rc < 0) + return rc; + + *delay_on = CFFPS_BLINK_RATE_MS; + *delay_off = CFFPS_BLINK_RATE_MS; + + return 0; +} + +static void ibm_cffps_create_led_class(size_t name_size, struct ibm_cffps *psu) +{ + int rc; + struct i2c_client *client = psu->client; + struct device *dev = &client->dev; + char *led_name = (void *)(&psu[1]); + + snprintf(led_name, name_size, "%s-%x", client->name, client->addr); + psu->led.name = led_name; + psu->led.max_brightness = LED_FULL; + psu->led.brightness_set = ibm_cffps_led_brightness_set; + psu->led.blink_set = ibm_cffps_led_blink_set; + + rc = devm_led_classdev_register(dev, &psu->led); + if (rc) + dev_warn(dev, "failed to register led class: %d\n", rc); +} + static struct pmbus_driver_info ibm_cffps_info = { .pages = 1, .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | @@ -277,6 +350,7 @@ static int ibm_cffps_probe(struct i2c_client *client, const struct i2c_device_id *id) { int i, rc; + size_t name_size; struct dentry *debugfs; struct dentry *ibm_cffps_dir; struct ibm_cffps *psu; @@ -286,6 +360,23 @@ static int ibm_cffps_probe(struct i2c_client *client, if (rc) return rc; + /* client name, '-', 8 chars for addr, and null terminator */ + name_size = strlen(client->name) + 10; + + /* +* Don't fail the probe if there isn't enough memory for leds and +* debugfs. +*/ + psu = devm_kzalloc(&client->dev, sizeof(*psu) + name_size, GFP_KER
Re: [PATCH] hwmon (pmbus): cffps: Add led class device for power supply fault led
On 01/09/2018 04:50 PM, Guenter Roeck wrote: On Tue, Jan 09, 2018 at 04:05:24PM -0600, Eddie James wrote: This power supply device doesn't correctly manage it's own fault led. Add an led class device and register it so that userspace can manage power supply fault led as necessary. Signed-off-by: Eddie James --- drivers/hwmon/pmbus/ibm-cffps.c | 90 + 1 file changed, 82 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 2d6f4f4..1e95dea 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #define CFFPS_CCIN_CMD0xBD #define CFFPS_FW_CMD_START0xFA #define CFFPS_FW_NUM_BYTES4 +#define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD 0xD6 #define CFFPS_INPUT_HISTORY_SIZE 100 @@ -39,6 +41,11 @@ #define CFFPS_MFR_VAUX_FAULT BIT(6) #define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) +#define CFFPS_LED_BLINKBIT(0) +#define CFFPS_LED_ON BIT(1) +#define CFFPS_LED_OFF BIT(2) +#define CFFPS_BLINK_RATE_MS250 + enum { CFFPS_DEBUGFS_INPUT_HISTORY = 0, CFFPS_DEBUGFS_FRU, @@ -63,6 +70,9 @@ struct ibm_cffps { struct ibm_cffps_input_history input_history; int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; + + u8 led_state; + struct led_classdev led; }; #define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) @@ -258,6 +268,51 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page, return rc; } +static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, +enum led_brightness brightness) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + if (brightness == LED_OFF) { + psu->led_state = CFFPS_LED_OFF; + } else { + brightness = LED_FULL; + if (psu->led_state != CFFPS_LED_BLINK) + psu->led_state = CFFPS_LED_ON; + } + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + psu->led_state); + if (rc < 0) + return; + + led_cdev->brightness = brightness; +} + +static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + psu->led_state = CFFPS_LED_BLINK; + + if (led_cdev->brightness == LED_OFF) + return 0; + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_BLINK); + if (rc < 0) + return rc; + + *delay_on = CFFPS_BLINK_RATE_MS; + *delay_off = CFFPS_BLINK_RATE_MS; + + return 0; +} + static struct pmbus_driver_info ibm_cffps_info = { .pages = 1, .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | @@ -277,6 +332,8 @@ static int ibm_cffps_probe(struct i2c_client *client, const struct i2c_device_id *id) { int i, rc; + char *led_name; + size_t name_length; struct dentry *debugfs; struct dentry *ibm_cffps_dir; struct ibm_cffps *psu; @@ -286,6 +343,31 @@ static int ibm_cffps_probe(struct i2c_client *client, if (rc) return rc; + /* client name, '-', 8 chars for addr, and null */ + name_length = strlen(client->name) + 10; + + /* Don't fail the probe if we can't create led devices */ + psu = devm_kzalloc(&client->dev, sizeof(*psu) + name_length, + GFP_KERNEL); + if (!psu) + return 0; ... and no debugfs either ? Well we need that struct allocated for debugfs as well. I'll clarify the comment. + + psu->client = client; + mutex_init(&psu->input_history.update_lock); + psu->input_history.last_update = jiffies - HZ; + + led_name = (void *)(&psu[1]); + snprintf(led_name, name_length, "%s-%x", client->name, client->addr); + psu->led.name = led_name; + psu->led.max_brightness = LED_FULL; + psu->led.brightness_set = ibm_cffps_led_brightness_set; + psu->led.blink_set = ibm_cffps_led_blink_set; + + rc = devm_led_classdev_register(&client->dev,