mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
97ff4ca46d
Here is the "large" pull request for char and misc and other assorted smaller driver subsystems for 5.3-rc1. It seems that this tree is becoming the funnel point of lots of smaller driver subsystems, which is fine for me, but that's why it is getting larger over time and does not just contain stuff under drivers/char/ and drivers/misc. Lots of small updates all over the place here from different driver subsystems: - habana driver updates - coresight driver updates - documentation file movements and updates - Android binder fixes and updates - extcon driver updates - google firmware driver updates - fsi driver updates - smaller misc and char driver updates - soundwire driver updates - nvmem driver updates - w1 driver fixes All of these have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCXSXmoQ8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ylV9wCgyJGbpPch8v/ecrZGFHYS4sIMexIAoMco3zf6 wnqFmXiz1O0tyo1sgV9R =7sqO -----END PGP SIGNATURE----- Merge tag 'char-misc-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc Pull char / misc driver updates from Greg KH: "Here is the "large" pull request for char and misc and other assorted smaller driver subsystems for 5.3-rc1. It seems that this tree is becoming the funnel point of lots of smaller driver subsystems, which is fine for me, but that's why it is getting larger over time and does not just contain stuff under drivers/char/ and drivers/misc. Lots of small updates all over the place here from different driver subsystems: - habana driver updates - coresight driver updates - documentation file movements and updates - Android binder fixes and updates - extcon driver updates - google firmware driver updates - fsi driver updates - smaller misc and char driver updates - soundwire driver updates - nvmem driver updates - w1 driver fixes All of these have been in linux-next for a while with no reported issues" * tag 'char-misc-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (188 commits) coresight: Do not default to CPU0 for missing CPU phandle dt-bindings: coresight: Change CPU phandle to required property ocxl: Allow contexts to be attached with a NULL mm fsi: sbefifo: Don't fail operations when in SBE IPL state coresight: tmc: Smatch: Fix potential NULL pointer dereference coresight: etm3x: Smatch: Fix potential NULL pointer dereference coresight: Potential uninitialized variable in probe() coresight: etb10: Do not call smp_processor_id from preemptible coresight: tmc-etf: Do not call smp_processor_id from preemptible coresight: tmc-etr: alloc_perf_buf: Do not call smp_processor_id from preemptible coresight: tmc-etr: Do not call smp_processor_id() from preemptible docs: misc-devices: convert files without extension to ReST fpga: dfl: fme: align PR buffer size per PR datawidth fpga: dfl: fme: remove copy_to_user() in ioctl for PR fpga: dfl-fme-mgr: fix FME_PR_INTFC_ID register address. intel_th: msu: Start read iterator from a non-empty window intel_th: msu: Split sgt array and pointer in multiwindow mode intel_th: msu: Support multipage blocks intel_th: pci: Add Ice Lake NNPI support intel_th: msu: Fix single mode with disabled IOMMU ...
1119 lines
26 KiB
C
1119 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
// Copyright IBM Corp 2019
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/export.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/sysfs.h>
|
|
#include <asm/unaligned.h>
|
|
|
|
#include "common.h"
|
|
|
|
#define EXTN_FLAG_SENSOR_ID BIT(7)
|
|
|
|
#define OCC_ERROR_COUNT_THRESHOLD 2 /* required by OCC spec */
|
|
|
|
#define OCC_STATE_SAFE 4
|
|
#define OCC_SAFE_TIMEOUT msecs_to_jiffies(60000) /* 1 min */
|
|
|
|
#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)
|
|
{
|
|
int rc;
|
|
u16 checksum = occ->poll_cmd_data + occ->seq_no + 1;
|
|
u8 cmd[8];
|
|
struct occ_poll_response_header *header;
|
|
|
|
/* big endian */
|
|
cmd[0] = occ->seq_no++; /* 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 */
|
|
cmd[7] = 0;
|
|
|
|
/* 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;
|
|
|
|
goto done;
|
|
}
|
|
|
|
/* clear error since communication was successful */
|
|
occ->error_count = 0;
|
|
occ->last_error = 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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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;
|
|
} else {
|
|
rc = occ->last_error;
|
|
}
|
|
|
|
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:
|
|
val = get_unaligned_be16(&temp->sensor_id);
|
|
break;
|
|
case 1:
|
|
/*
|
|
* If a sensor reading has expired and couldn't be refreshed,
|
|
* OCC returns 0xFFFF for that sensor.
|
|
*/
|
|
if (temp->value == 0xFFFF)
|
|
return -EREMOTEIO;
|
|
val = get_unaligned_be16(&temp->value) * 1000;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
|
|
}
|
|
|
|
static ssize_t occ_show_temp_2(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u32 val = 0;
|
|
struct temp_sensor_2 *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_2 *)sensors->temp.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
val = get_unaligned_be32(&temp->sensor_id);
|
|
break;
|
|
case 1:
|
|
val = temp->value;
|
|
if (val == OCC_TEMP_SENSOR_FAULT)
|
|
return -EREMOTEIO;
|
|
|
|
/*
|
|
* VRM doesn't return temperature, only alarm bit. This
|
|
* attribute maps to tempX_alarm instead of tempX_input for
|
|
* VRM
|
|
*/
|
|
if (temp->fru_type != OCC_FRU_TYPE_VRM) {
|
|
/* sensor not ready */
|
|
if (val == 0)
|
|
return -EAGAIN;
|
|
|
|
val *= 1000;
|
|
}
|
|
break;
|
|
case 2:
|
|
val = temp->fru_type;
|
|
break;
|
|
case 3:
|
|
val = temp->value == OCC_TEMP_SENSOR_FAULT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
|
|
}
|
|
|
|
static ssize_t occ_show_freq_1(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u16 val = 0;
|
|
struct freq_sensor_1 *freq;
|
|
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;
|
|
|
|
freq = ((struct freq_sensor_1 *)sensors->freq.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
val = get_unaligned_be16(&freq->sensor_id);
|
|
break;
|
|
case 1:
|
|
val = get_unaligned_be16(&freq->value);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
|
|
}
|
|
|
|
static ssize_t occ_show_freq_2(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u32 val = 0;
|
|
struct freq_sensor_2 *freq;
|
|
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;
|
|
|
|
freq = ((struct freq_sensor_2 *)sensors->freq.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
val = get_unaligned_be32(&freq->sensor_id);
|
|
break;
|
|
case 1:
|
|
val = get_unaligned_be16(&freq->value);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
|
|
}
|
|
|
|
static ssize_t occ_show_power_1(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u64 val = 0;
|
|
struct power_sensor_1 *power;
|
|
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;
|
|
|
|
power = ((struct power_sensor_1 *)sensors->power.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
val = get_unaligned_be16(&power->sensor_id);
|
|
break;
|
|
case 1:
|
|
val = get_unaligned_be32(&power->accumulator) /
|
|
get_unaligned_be32(&power->update_tag);
|
|
val *= 1000000ULL;
|
|
break;
|
|
case 2:
|
|
val = (u64)get_unaligned_be32(&power->update_tag) *
|
|
occ->powr_sample_time_us;
|
|
break;
|
|
case 3:
|
|
val = get_unaligned_be16(&power->value) * 1000000ULL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
|
|
}
|
|
|
|
static u64 occ_get_powr_avg(u64 *accum, u32 *samples)
|
|
{
|
|
return div64_u64(get_unaligned_be64(accum) * 1000000ULL,
|
|
get_unaligned_be32(samples));
|
|
}
|
|
|
|
static ssize_t occ_show_power_2(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u64 val = 0;
|
|
struct power_sensor_2 *power;
|
|
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;
|
|
|
|
power = ((struct power_sensor_2 *)sensors->power.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u_%u_%u\n",
|
|
get_unaligned_be32(&power->sensor_id),
|
|
power->function_id, power->apss_channel);
|
|
case 1:
|
|
val = occ_get_powr_avg(&power->accumulator,
|
|
&power->update_tag);
|
|
break;
|
|
case 2:
|
|
val = (u64)get_unaligned_be32(&power->update_tag) *
|
|
occ->powr_sample_time_us;
|
|
break;
|
|
case 3:
|
|
val = get_unaligned_be16(&power->value) * 1000000ULL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
|
|
}
|
|
|
|
static ssize_t occ_show_power_a0(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u64 val = 0;
|
|
struct power_sensor_a0 *power;
|
|
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;
|
|
|
|
power = ((struct power_sensor_a0 *)sensors->power.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u_system\n",
|
|
get_unaligned_be32(&power->sensor_id));
|
|
case 1:
|
|
val = occ_get_powr_avg(&power->system.accumulator,
|
|
&power->system.update_tag);
|
|
break;
|
|
case 2:
|
|
val = (u64)get_unaligned_be32(&power->system.update_tag) *
|
|
occ->powr_sample_time_us;
|
|
break;
|
|
case 3:
|
|
val = get_unaligned_be16(&power->system.value) * 1000000ULL;
|
|
break;
|
|
case 4:
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u_proc\n",
|
|
get_unaligned_be32(&power->sensor_id));
|
|
case 5:
|
|
val = occ_get_powr_avg(&power->proc.accumulator,
|
|
&power->proc.update_tag);
|
|
break;
|
|
case 6:
|
|
val = (u64)get_unaligned_be32(&power->proc.update_tag) *
|
|
occ->powr_sample_time_us;
|
|
break;
|
|
case 7:
|
|
val = get_unaligned_be16(&power->proc.value) * 1000000ULL;
|
|
break;
|
|
case 8:
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u_vdd\n",
|
|
get_unaligned_be32(&power->sensor_id));
|
|
case 9:
|
|
val = occ_get_powr_avg(&power->vdd.accumulator,
|
|
&power->vdd.update_tag);
|
|
break;
|
|
case 10:
|
|
val = (u64)get_unaligned_be32(&power->vdd.update_tag) *
|
|
occ->powr_sample_time_us;
|
|
break;
|
|
case 11:
|
|
val = get_unaligned_be16(&power->vdd.value) * 1000000ULL;
|
|
break;
|
|
case 12:
|
|
return snprintf(buf, PAGE_SIZE - 1, "%u_vdn\n",
|
|
get_unaligned_be32(&power->sensor_id));
|
|
case 13:
|
|
val = occ_get_powr_avg(&power->vdn.accumulator,
|
|
&power->vdn.update_tag);
|
|
break;
|
|
case 14:
|
|
val = (u64)get_unaligned_be32(&power->vdn.update_tag) *
|
|
occ->powr_sample_time_us;
|
|
break;
|
|
case 15:
|
|
val = get_unaligned_be16(&power->vdn.value) * 1000000ULL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
|
|
}
|
|
|
|
static ssize_t occ_show_caps_1_2(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u64 val = 0;
|
|
struct caps_sensor_2 *caps;
|
|
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;
|
|
|
|
caps = ((struct caps_sensor_2 *)sensors->caps.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
return snprintf(buf, PAGE_SIZE - 1, "system\n");
|
|
case 1:
|
|
val = get_unaligned_be16(&caps->cap) * 1000000ULL;
|
|
break;
|
|
case 2:
|
|
val = get_unaligned_be16(&caps->system_power) * 1000000ULL;
|
|
break;
|
|
case 3:
|
|
val = get_unaligned_be16(&caps->n_cap) * 1000000ULL;
|
|
break;
|
|
case 4:
|
|
val = get_unaligned_be16(&caps->max) * 1000000ULL;
|
|
break;
|
|
case 5:
|
|
val = get_unaligned_be16(&caps->min) * 1000000ULL;
|
|
break;
|
|
case 6:
|
|
val = get_unaligned_be16(&caps->user) * 1000000ULL;
|
|
break;
|
|
case 7:
|
|
if (occ->sensors.caps.version == 1)
|
|
return -EINVAL;
|
|
|
|
val = caps->user_source;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
|
|
}
|
|
|
|
static ssize_t occ_show_caps_3(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
u64 val = 0;
|
|
struct caps_sensor_3 *caps;
|
|
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;
|
|
|
|
caps = ((struct caps_sensor_3 *)sensors->caps.data) + sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
return snprintf(buf, PAGE_SIZE - 1, "system\n");
|
|
case 1:
|
|
val = get_unaligned_be16(&caps->cap) * 1000000ULL;
|
|
break;
|
|
case 2:
|
|
val = get_unaligned_be16(&caps->system_power) * 1000000ULL;
|
|
break;
|
|
case 3:
|
|
val = get_unaligned_be16(&caps->n_cap) * 1000000ULL;
|
|
break;
|
|
case 4:
|
|
val = get_unaligned_be16(&caps->max) * 1000000ULL;
|
|
break;
|
|
case 5:
|
|
val = get_unaligned_be16(&caps->hard_min) * 1000000ULL;
|
|
break;
|
|
case 6:
|
|
val = get_unaligned_be16(&caps->user) * 1000000ULL;
|
|
break;
|
|
case 7:
|
|
val = caps->user_source;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
|
|
}
|
|
|
|
static ssize_t occ_store_caps_user(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int rc;
|
|
u16 user_power_cap;
|
|
unsigned long long value;
|
|
struct occ *occ = dev_get_drvdata(dev);
|
|
|
|
rc = kstrtoull(buf, 0, &value);
|
|
if (rc)
|
|
return rc;
|
|
|
|
user_power_cap = div64_u64(value, 1000000ULL); /* microwatt to watt */
|
|
|
|
rc = occ_set_user_power_cap(occ, user_power_cap);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t occ_show_extended(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int rc;
|
|
struct extended_sensor *extn;
|
|
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;
|
|
|
|
extn = ((struct extended_sensor *)sensors->extended.data) +
|
|
sattr->index;
|
|
|
|
switch (sattr->nr) {
|
|
case 0:
|
|
if (extn->flags & EXTN_FLAG_SENSOR_ID)
|
|
rc = snprintf(buf, PAGE_SIZE - 1, "%u",
|
|
get_unaligned_be32(&extn->sensor_id));
|
|
else
|
|
rc = snprintf(buf, PAGE_SIZE - 1, "%02x%02x%02x%02x\n",
|
|
extn->name[0], extn->name[1],
|
|
extn->name[2], extn->name[3]);
|
|
break;
|
|
case 1:
|
|
rc = snprintf(buf, PAGE_SIZE - 1, "%02x\n", extn->flags);
|
|
break;
|
|
case 2:
|
|
rc = snprintf(buf, PAGE_SIZE - 1, "%02x%02x%02x%02x%02x%02x\n",
|
|
extn->data[0], extn->data[1], extn->data[2],
|
|
extn->data[3], extn->data[4], extn->data[5]);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
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)
|
|
return -ENOMEM;
|
|
|
|
/* null-terminated list */
|
|
occ->group.attrs = devm_kzalloc(dev, sizeof(*occ->group.attrs) *
|
|
num_attrs + 1, GFP_KERNEL);
|
|
if (!occ->group.attrs)
|
|
return -ENOMEM;
|
|
|
|
attr = occ->attrs;
|
|
|
|
for (i = 0; i < sensors->temp.num_sensors; ++i) {
|
|
s = i + 1;
|
|
temp = ((struct temp_sensor_2 *)sensors->temp.data) + i;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "temp%d_label", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
|
|
0, i);
|
|
attr++;
|
|
|
|
if (sensors->temp.version > 1 &&
|
|
temp->fru_type == OCC_FRU_TYPE_VRM) {
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"temp%d_alarm", s);
|
|
} else {
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"temp%d_input", s);
|
|
}
|
|
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
|
|
1, i);
|
|
attr++;
|
|
|
|
if (sensors->temp.version > 1) {
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"temp%d_fru_type", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_temp, NULL, 2, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"temp%d_fault", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_temp, NULL, 3, i);
|
|
attr++;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < sensors->freq.num_sensors; ++i) {
|
|
s = i + 1;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "freq%d_label", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
|
|
0, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "freq%d_input", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
|
|
1, i);
|
|
attr++;
|
|
}
|
|
|
|
if (sensors->power.version == 0xA0) {
|
|
/*
|
|
* Special case for many-attribute power sensor. Split it into
|
|
* a sensor number per power type, emulating several sensors.
|
|
*/
|
|
for (i = 0; i < sensors->power.num_sensors; ++i) {
|
|
unsigned int j;
|
|
unsigned int nr = 0;
|
|
|
|
s = (i * 4) + 1;
|
|
|
|
for (j = 0; j < 4; ++j) {
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_label", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL,
|
|
nr++, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_average", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL,
|
|
nr++, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_average_interval", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL,
|
|
nr++, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_input", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL,
|
|
nr++, i);
|
|
attr++;
|
|
|
|
s++;
|
|
}
|
|
}
|
|
|
|
s = (sensors->power.num_sensors * 4) + 1;
|
|
} else {
|
|
for (i = 0; i < sensors->power.num_sensors; ++i) {
|
|
s = i + 1;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_label", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL, 0, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_average", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL, 1, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_average_interval", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL, 2, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_input", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_power, NULL, 3, i);
|
|
attr++;
|
|
}
|
|
|
|
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);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "power%d_cap", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
|
|
1, 0);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "power%d_input", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
|
|
2, 0);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_cap_not_redundant", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
|
|
3, 0);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "power%d_cap_max", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
|
|
4, 0);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "power%d_cap_min", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
|
|
5, 0);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "power%d_cap_user",
|
|
s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0644, show_caps,
|
|
occ_store_caps_user, 6, 0);
|
|
attr++;
|
|
|
|
if (sensors->caps.version > 1) {
|
|
snprintf(attr->name, sizeof(attr->name),
|
|
"power%d_cap_user_source", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
show_caps, NULL, 7, 0);
|
|
attr++;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < sensors->extended.num_sensors; ++i) {
|
|
s = i + 1;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "extn%d_label", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
occ_show_extended, NULL, 0, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "extn%d_flags", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
occ_show_extended, NULL, 1, i);
|
|
attr++;
|
|
|
|
snprintf(attr->name, sizeof(attr->name), "extn%d_input", s);
|
|
attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
|
|
occ_show_extended, NULL, 2, i);
|
|
attr++;
|
|
}
|
|
|
|
/* put the sensors in the group */
|
|
for (i = 0; i < num_attrs; ++i) {
|
|
sysfs_attr_init(&occ->attrs[i].sensor.dev_attr.attr);
|
|
occ->group.attrs[i] = &occ->attrs[i].sensor.dev_attr.attr;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
mutex_init(&occ->lock);
|
|
occ->groups[0] = &occ->group;
|
|
|
|
/* no need to lock */
|
|
rc = occ_poll(occ);
|
|
if (rc == -ESHUTDOWN) {
|
|
dev_info(occ->bus_dev, "host is not ready\n");
|
|
return rc;
|
|
} else if (rc < 0) {
|
|
dev_err(occ->bus_dev, "failed to get OCC poll response: %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
occ_parse_poll_response(occ);
|
|
|
|
rc = occ_setup_sensor_attrs(occ);
|
|
if (rc) {
|
|
dev_err(occ->bus_dev, "failed to setup sensor attrs: %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev, name,
|
|
occ, occ->groups);
|
|
if (IS_ERR(occ->hwmon)) {
|
|
rc = PTR_ERR(occ->hwmon);
|
|
dev_err(occ->bus_dev, "failed to register hwmon device: %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = occ_setup_sysfs(occ);
|
|
if (rc)
|
|
dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(occ_setup);
|
|
|
|
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
|
|
MODULE_DESCRIPTION("Common OCC hwmon code");
|
|
MODULE_LICENSE("GPL");
|