Commit e7c1bbcf authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'hwmon-for-v5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull hwmon updates from Guenter Roeck:
 "New drivers for:

   - Aquacomputer D5 Next

   - SB-RMI power module

  Added chip support to existing drivers:

   - Support for various Zen2 and Zen3 APUs and for Yellow Carp (SMU
     v13) added to k10temp driver

   - Support for Silicom n5010 PAC added to intel-m10-bmc driver

   - Support for BPD-RS600 added to pmbus/bpa-rs600 driver

  Other notable changes:

   - In k10temp, do not display Tdie on Zen CPUs if there is no
     difference between Tdie and Tctl

   - Converted adt7470 and dell-smm drivers to use
     devm_hwmon_device_register_with_info API

   - Support for temperature/pwm tables added to axi-fan-control driver

   - Enabled fan control for Dell Precision 7510 in dell-smm driver

  Various other minor improvements and fixes in several drivers"

* tag 'hwmon-for-v5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (41 commits)
  hwmon: add driver for Aquacomputer D5 Next
  hwmon: (adt7470) Convert to devm_hwmon_device_register_with_info API
  hwmon: (adt7470) Convert to use regmap
  hwmon: (adt7470) Fix some style issues
  hwmon: (k10temp) Add support for yellow carp
  hwmon: (k10temp) Rework the temperature offset calculation
  hwmon: (k10temp) Don't show Tdie for all Zen/Zen2/Zen3 CPU/APU
  hwmon: (k10temp) Add additional missing Zen2 and Zen3 APUs
  hwmon: remove amd_energy driver in Makefile
  hwmon: (dell-smm) Rework SMM function debugging
  hwmon: (dell-smm) Mark i8k_get_fan_nominal_speed as __init
  hwmon: (dell-smm) Mark tables as __initconst
  hwmon: (pmbus/bpa-rs600) Add workaround for incorrect Pin max
  hwmon: (pmbus/bpa-rs600) Don't use rated limits as warn limits
  hwmon: (axi-fan-control) Support temperature vs pwm points
  hwmon: (axi-fan-control) Handle irqs in natural order
  hwmon: (axi-fan-control) Make sure the clock is enabled
  hwmon: (pmbus/ibm-cffps) Fix write bits for LED control
  hwmon: (w83781d) Match on device tree compatibles
  dt-bindings: hwmon: Add bindings for Winbond W83781D
  ...
parents 871dda46 0e35f63f
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/amd,sbrmi.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: >
Sideband Remote Management Interface (SB-RMI) compliant
AMD SoC power device.
maintainers:
- Akshay Gupta <Akshay.Gupta@amd.com>
description: |
SB Remote Management Interface (SB-RMI) is an SMBus compatible
interface that reports AMD SoC's Power (normalized Power) using,
Mailbox Service Request and resembles a typical 8-pin remote power
sensor's I2C interface to BMC. The power attributes in hwmon
reports power in microwatts.
properties:
compatible:
enum:
- amd,sbrmi
reg:
maxItems: 1
description: |
I2C bus address of the device as specified in Section SBI SMBus Address
of the SoC register reference. The SB-RMI address is normally 78h for
socket 0 and 70h for socket 1, but it could vary based on hardware
address select pins.
\[open source SoC register reference\]
https://www.amd.com/en/support/tech-docs?keyword=55898
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
sbrmi@3c {
compatible = "amd,sbrmi";
reg = <0x3c>;
};
};
...
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/hwmon/winbond,w83781d.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Winbond W83781 and compatible hardware monitor IC
maintainers:
- Linus Walleij <linus.walleij@linaro.org>
properties:
compatible:
enum:
- winbond,w83781d
- winbond,w83781g
- winbond,w83782d
- winbond,w83783s
- asus,as99127f
reg:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
temperature-sensor@28 {
compatible = "winbond,w83781d";
reg = <0x28>;
};
};
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver aquacomputer-d5next
=================================
Supported devices:
* Aquacomputer D5 Next watercooling pump
Author: Aleksa Savic
Description
-----------
This driver exposes hardware sensors of the Aquacomputer D5 Next watercooling
pump, which communicates through a proprietary USB HID protocol.
Available sensors are pump and fan speed, power, voltage and current, as
well as coolant temperature. Also available through debugfs are the serial
number, firmware version and power-on count.
Attaching a fan is optional and allows it to be controlled using temperature
curves directly from the pump. If it's not connected, the fan-related sensors
will report zeroes.
The pump can be configured either through software or via its physical
interface. Configuring the pump through this driver is not implemented, as it
seems to require sending it a complete configuration. That includes addressable
RGB LEDs, for which there is no standard sysfs interface. Thus, that task is
better suited for userspace tools.
Usage notes
-----------
The pump communicates via HID reports. The driver is loaded automatically by
the kernel and supports hotswapping.
Sysfs entries
-------------
============ =============================================
temp1_input Coolant temperature (in millidegrees Celsius)
fan1_input Pump speed (in RPM)
fan2_input Fan speed (in RPM)
power1_input Pump power (in micro Watts)
power2_input Fan power (in micro Watts)
in0_input Pump voltage (in milli Volts)
in1_input Fan voltage (in milli Volts)
in2_input +5V rail voltage (in milli Volts)
curr1_input Pump current (in milli Amperes)
curr2_input Fan current (in milli Amperes)
============ =============================================
Debugfs entries
---------------
================ ===============================================
serial_number Serial number of the pump
firmware_version Version of installed firmware
power_cycles Count of how many times the pump was powered on
================ ===============================================
......@@ -39,6 +39,7 @@ Hardware Monitoring Kernel Drivers
adt7475
aht10
amc6821
aquacomputer_d5next
asb100
asc7621
aspeed-pwm-tacho
......@@ -160,6 +161,7 @@ Hardware Monitoring Kernel Drivers
pwm-fan
q54sj108a2
raspberrypi-hwmon
sbrmi
sbtsi_temp
sch5627
sch5636
......
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver sbrmi
===================
Supported hardware:
* Sideband Remote Management Interface (SB-RMI) compliant AMD SoC
device connected to the BMC via the APML.
Prefix: 'sbrmi'
Addresses scanned: This driver doesn't support address scanning.
To instantiate this driver on an AMD CPU with SB-RMI
support, the i2c bus number would be the bus connected from the board
management controller (BMC) to the CPU.
The SMBus address is really 7 bits. Some vendors and the SMBus
specification show the address as 8 bits, left justified with the R/W
bit as a write (0) making bit 0. Some vendors use only the 7 bits
to describe the address.
As mentioned in AMD's APML specification, The SB-RMI address is
normally 78h(0111 100W) or 3Ch(011 1100) for socket 0 and 70h(0111 000W)
or 38h(011 1000) for socket 1, but it could vary based on hardware
address select pins.
Datasheet: The SB-RMI interface and protocol along with the Advanced
Platform Management Link (APML) Specification is available
as part of the open source SoC register reference at:
https://www.amd.com/en/support/tech-docs?keyword=55898
Author: Akshay Gupta <akshay.gupta@amd.com>
Description
-----------
The APML provides a way to communicate with the SB Remote Management interface
(SB-RMI) module from the external SMBus master that can be used to report socket
power on AMD platforms using mailbox command and resembles a typical 8-pin remote
power sensor's I2C interface to BMC.
This driver implements current power with power cap and power cap max.
sysfs-Interface
---------------
Power sensors can be queried and set via the standard ``hwmon`` interface
on ``sysfs``, under the directory ``/sys/class/hwmon/hwmonX`` for some value
of ``X`` (search for the ``X`` such that ``/sys/class/hwmon/hwmonX/name`` has
content ``sbrmi``)
================ ===== ========================================================
Name Perm Description
================ ===== ========================================================
power1_input RO Current Power consumed
power1_cap RW Power limit can be set between 0 and power1_cap_max
power1_cap_max RO Maximum powerlimit calculated and reported by the SMU FW
================ ===== ========================================================
The following example show how the 'Power' attribute from the i2c-addresses
can be monitored using the userspace utilities like ``sensors`` binary::
# sensors
sbrmi-i2c-1-38
Adapter: bcm2835 I2C adapter
power1: 61.00 W (cap = 225.00 W)
sbrmi-i2c-1-3c
Adapter: bcm2835 I2C adapter
power1: 28.39 W (cap = 224.77 W)
#
Also, Below shows how get and set the values from sysfs entries individually::
# cat /sys/class/hwmon/hwmon1/power1_cap_max
225000000
# echo 180000000 > /sys/class/hwmon/hwmon1/power1_cap
# cat /sys/class/hwmon/hwmon1/power1_cap
180000000
......@@ -32,5 +32,5 @@ Usage Notes
The driver relies on device tree node to indicate the presence of SCPI
support in the kernel. See
Documentation/devicetree/bindings/arm/arm,scpi.txt for details of the
Documentation/devicetree/bindings/firmware/arm,scpi.yaml for details of the
devicetree node.
......@@ -42,4 +42,4 @@ humidity1_input Measured humidity in %H
update_interval The minimum interval for polling the sensor,
in milliseconds. Writable. Must be at least
2000.
============== =============================================
=============== ============================================
......@@ -1316,6 +1316,13 @@ L: linux-media@vger.kernel.org
S: Maintained
F: drivers/media/i2c/aptina-pll.*
AQUACOMPUTER D5 NEXT PUMP SENSOR DRIVER
M: Aleksa Savic <savicaleksa83@gmail.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/aquacomputer_d5next.rst
F: drivers/hwmon/aquacomputer_d5next.c
AQUANTIA ETHERNET DRIVER (atlantic)
M: Igor Russkikh <irusskikh@marvell.com>
L: netdev@vger.kernel.org
......
......@@ -25,6 +25,8 @@
#define PCI_DEVICE_ID_AMD_17H_M60H_DF_F4 0x144c
#define PCI_DEVICE_ID_AMD_17H_M70H_DF_F4 0x1444
#define PCI_DEVICE_ID_AMD_19H_DF_F4 0x1654
#define PCI_DEVICE_ID_AMD_19H_M40H_ROOT 0x14b5
#define PCI_DEVICE_ID_AMD_19H_M40H_DF_F4 0x167d
#define PCI_DEVICE_ID_AMD_19H_M50H_DF_F4 0x166e
/* Protect the PCI config register pairs used for SMN and DF indirect access. */
......@@ -37,6 +39,7 @@ static const struct pci_device_id amd_root_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M10H_ROOT) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M30H_ROOT) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_ROOT) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_ROOT) },
{}
};
......@@ -58,6 +61,7 @@ static const struct pci_device_id amd_nb_misc_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F3) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) },
{}
};
......@@ -74,6 +78,7 @@ static const struct pci_device_id amd_nb_link_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F4) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F4) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F4) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F4) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F4) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F4) },
{}
......
......@@ -254,6 +254,16 @@ config SENSORS_AHT10
This driver can also be built as a module. If so, the module
will be called aht10.
config SENSORS_AQUACOMPUTER_D5NEXT
tristate "Aquacomputer D5 Next watercooling pump"
depends on USB_HID
help
If you say yes here you get support for the Aquacomputer D5 Next
watercooling pump sensors.
This driver can also be built as a module. If so, the module
will be called aquacomputer_d5next.
config SENSORS_AS370
tristate "Synaptics AS370 SoC hardware monitoring driver"
help
......@@ -1551,6 +1561,16 @@ config SENSORS_SBTSI
This driver can also be built as a module. If so, the module will
be called sbtsi_temp.
config SENSORS_SBRMI
tristate "Emulated SB-RMI sensor"
depends on I2C
help
If you say yes here you get support for emulated RMI
sensors on AMD SoCs with APML interface connected to a BMC device.
This driver can also be built as a module. If so, the module will
be called sbrmi.
config SENSORS_SHT15
tristate "Sensiron humidity and temperature sensors. SHT15 and compat."
depends on GPIOLIB || COMPILE_TEST
......
......@@ -45,8 +45,8 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
obj-$(CONFIG_SENSORS_AHT10) += aht10.o
obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
obj-$(CONFIG_SENSORS_AQUACOMPUTER_D5NEXT) += aquacomputer_d5next.o
obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o
......@@ -164,6 +164,7 @@ obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o
obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o
......
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0+
/*
* hwmon driver for Aquacomputer D5 Next watercooling pump
*
* The D5 Next sends HID reports (with ID 0x01) every second to report sensor values
* (coolant temperature, pump and fan speed, voltage, current and power). It responds to
* Get_Report requests, but returns a dummy value of no use.
*
* Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
*/
#include <asm/unaligned.h>
#include <linux/debugfs.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#define DRIVER_NAME "aquacomputer-d5next"
#define D5NEXT_STATUS_REPORT_ID 0x01
#define D5NEXT_STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */
/* Register offsets for the D5 Next pump */
#define D5NEXT_SERIAL_FIRST_PART 3
#define D5NEXT_SERIAL_SECOND_PART 5
#define D5NEXT_FIRMWARE_VERSION 13
#define D5NEXT_POWER_CYCLES 24
#define D5NEXT_COOLANT_TEMP 87
#define D5NEXT_PUMP_SPEED 116
#define D5NEXT_FAN_SPEED 103
#define D5NEXT_PUMP_POWER 114
#define D5NEXT_FAN_POWER 101
#define D5NEXT_PUMP_VOLTAGE 110
#define D5NEXT_FAN_VOLTAGE 97
#define D5NEXT_5V_VOLTAGE 57
#define D5NEXT_PUMP_CURRENT 112
#define D5NEXT_FAN_CURRENT 99
/* Labels for provided values */
#define L_COOLANT_TEMP "Coolant temp"
#define L_PUMP_SPEED "Pump speed"
#define L_FAN_SPEED "Fan speed"
#define L_PUMP_POWER "Pump power"
#define L_FAN_POWER "Fan power"
#define L_PUMP_VOLTAGE "Pump voltage"
#define L_FAN_VOLTAGE "Fan voltage"
#define L_5V_VOLTAGE "+5V voltage"
#define L_PUMP_CURRENT "Pump current"
#define L_FAN_CURRENT "Fan current"
static const char *const label_speeds[] = {
L_PUMP_SPEED,
L_FAN_SPEED,
};
static const char *const label_power[] = {
L_PUMP_POWER,
L_FAN_POWER,
};
static const char *const label_voltages[] = {
L_PUMP_VOLTAGE,
L_FAN_VOLTAGE,
L_5V_VOLTAGE,
};
static const char *const label_current[] = {
L_PUMP_CURRENT,
L_FAN_CURRENT,
};
struct d5next_data {
struct hid_device *hdev;
struct device *hwmon_dev;
struct dentry *debugfs;
s32 temp_input;
u16 speed_input[2];
u32 power_input[2];
u16 voltage_input[3];
u16 current_input[2];
u32 serial_number[2];
u16 firmware_version;
u32 power_cycles; /* How many times the device was powered on */
unsigned long updated;
};
static umode_t d5next_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
int channel)
{
return 0444;
}
static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
long *val)
{
struct d5next_data *priv = dev_get_drvdata(dev);
if (time_after(jiffies, priv->updated + D5NEXT_STATUS_UPDATE_INTERVAL))
return -ENODATA;
switch (type) {
case hwmon_temp:
*val = priv->temp_input;
break;
case hwmon_fan:
*val = priv->speed_input[channel];
break;
case hwmon_power:
*val = priv->power_input[channel];
break;
case hwmon_in:
*val = priv->voltage_input[channel];
break;
case hwmon_curr:
*val = priv->current_input[channel];
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
switch (type) {
case hwmon_temp:
*str = L_COOLANT_TEMP;
break;
case hwmon_fan:
*str = label_speeds[channel];
break;
case hwmon_power:
*str = label_power[channel];
break;
case hwmon_in:
*str = label_voltages[channel];
break;
case hwmon_curr:
*str = label_current[channel];
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static const struct hwmon_ops d5next_hwmon_ops = {
.is_visible = d5next_is_visible,
.read = d5next_read,
.read_string = d5next_read_string,
};
static const struct hwmon_channel_info *d5next_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL),
HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL),
HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL),
HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL),
NULL
};
static const struct hwmon_chip_info d5next_chip_info = {
.ops = &d5next_hwmon_ops,
.info = d5next_info,
};
static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
{
struct d5next_data *priv;
if (report->id != D5NEXT_STATUS_REPORT_ID)
return 0;
priv = hid_get_drvdata(hdev);
/* Info provided with every report */
priv->serial_number[0] = get_unaligned_be16(data + D5NEXT_SERIAL_FIRST_PART);
priv->serial_number[1] = get_unaligned_be16(data + D5NEXT_SERIAL_SECOND_PART);
priv->firmware_version = get_unaligned_be16(data + D5NEXT_FIRMWARE_VERSION);
priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES);
/* Sensor readings */
priv->temp_input = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10;
priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED);
priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED);
priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000;
priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000;
priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10;
priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10;
priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10;
priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT);
priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT);
priv->updated = jiffies;
return 0;
}
#ifdef CONFIG_DEBUG_FS
static int serial_number_show(struct seq_file *seqf, void *unused)
{
struct d5next_data *priv = seqf->private;
seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(serial_number);
static int firmware_version_show(struct seq_file *seqf, void *unused)
{
struct d5next_data *priv = seqf->private;
seq_printf(seqf, "%u\n", priv->firmware_version);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(firmware_version);
static int power_cycles_show(struct seq_file *seqf, void *unused)
{
struct d5next_data *priv = seqf->private;
seq_printf(seqf, "%u\n", priv->power_cycles);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(power_cycles);
static void d5next_debugfs_init(struct d5next_data *priv)
{
char name[32];
scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));
priv->debugfs = debugfs_create_dir(name, NULL);
debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops);
debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
}
#else
static void d5next_debugfs_init(struct d5next_data *priv)
{
}
#endif
static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct d5next_data *priv;
int ret;
priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->hdev = hdev;
hid_set_drvdata(hdev, priv);
priv->updated = jiffies - D5NEXT_STATUS_UPDATE_INTERVAL;
ret = hid_parse(hdev);
if (ret)
return ret;
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret)
return ret;
ret = hid_hw_open(hdev);
if (ret)
goto fail_and_stop;
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "d5next", priv,
&d5next_chip_info, NULL);
if (IS_ERR(priv->hwmon_dev)) {
ret = PTR_ERR(priv->hwmon_dev);
goto fail_and_close;
}
d5next_debugfs_init(priv);
return 0;
fail_and_close:
hid_hw_close(hdev);
fail_and_stop:
hid_hw_stop(hdev);
return ret;
}
static void d5next_remove(struct hid_device *hdev)
{
struct d5next_data *priv = hid_get_drvdata(hdev);
debugfs_remove_recursive(priv->debugfs);
hwmon_device_unregister(priv->hwmon_dev);
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
static const struct hid_device_id d5next_table[] = {
{ HID_USB_DEVICE(0x0c70, 0xf00e) }, /* Aquacomputer D5 Next */
{},
};
MODULE_DEVICE_TABLE(hid, d5next_table);
static struct hid_driver d5next_driver = {
.name = DRIVER_NAME,
.id_table = d5next_table,
.probe = d5next_probe,
.remove = d5next_remove,
.raw_event = d5next_raw_event,
};
static int __init d5next_init(void)
{
return hid_register_driver(&d5next_driver);
}
static void __exit d5next_exit(void)
{
hid_unregister_driver(&d5next_driver);
}
/* Request to initialize after the HID bus to ensure it's not being loaded before */
late_initcall(d5next_init);
module_exit(d5next_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
MODULE_DESCRIPTION("Hwmon driver for Aquacomputer D5 Next pump");
......@@ -8,6 +8,7 @@
#include <linux/clk.h>
#include <linux/fpga/adi-axi-common.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
......@@ -23,6 +24,14 @@
#define ADI_REG_PWM_PERIOD 0x00c0
#define ADI_REG_TACH_MEASUR 0x00c4
#define ADI_REG_TEMPERATURE 0x00c8
#define ADI_REG_TEMP_00_H 0x0100
#define ADI_REG_TEMP_25_L 0x0104
#define ADI_REG_TEMP_25_H 0x0108
#define ADI_REG_TEMP_50_L 0x010c
#define ADI_REG_TEMP_50_H 0x0110
#define ADI_REG_TEMP_75_L 0x0114
#define ADI_REG_TEMP_75_H 0x0118
#define ADI_REG_TEMP_100_L 0x011c
#define ADI_REG_IRQ_MASK 0x0040
#define ADI_REG_IRQ_PENDING 0x0044
......@@ -62,6 +71,39 @@ static inline u32 axi_ioread(const u32 reg,
return ioread32(ctl->base + reg);
}
/*
* The core calculates the temperature as:
* T = /raw * 509.3140064 / 65535) - 280.2308787
*/
static ssize_t axi_fan_control_show(struct device *dev, struct device_attribute *da, char *buf)
{
struct axi_fan_control_data *ctl = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
u32 temp = axi_ioread(attr->index, ctl);
temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230;
return sprintf(buf, "%u\n", temp);
}
static ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
struct axi_fan_control_data *ctl = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
u32 temp;
int ret;
ret = kstrtou32(buf, 10, &temp);
if (ret)
return ret;
temp = DIV_ROUND_CLOSEST_ULL((temp + 280230) * 65535ULL, 509314);
axi_iowrite(temp, attr->index, ctl);
return count;
}
static long axi_fan_control_get_pwm_duty(const struct axi_fan_control_data *ctl)
{
u32 pwm_width = axi_ioread(ADI_REG_PWM_WIDTH, ctl);
......@@ -283,18 +325,9 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
u32 irq_pending = axi_ioread(ADI_REG_IRQ_PENDING, ctl);
u32 clear_mask;
if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) {
if (ctl->update_tacho_params) {
u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl);
/* get 25% tolerance */
u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100);
/* set new tacho parameters */
axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl);
axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl);
ctl->update_tacho_params = false;
}
}
if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE)
/* hardware requested a new pwm */
ctl->hw_pwm_req = true;
if (irq_pending & ADI_IRQ_SRC_PWM_CHANGED) {
/*
......@@ -310,9 +343,18 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
}
}
if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE)
/* hardware requested a new pwm */
ctl->hw_pwm_req = true;
if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) {
if (ctl->update_tacho_params) {
u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl);
/* get 25% tolerance */
u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100);
/* set new tacho parameters */
axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl);
axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl);
ctl->update_tacho_params = false;
}
}
if (irq_pending & ADI_IRQ_SRC_TACH_ERR)
ctl->fan_fault = 1;
......@@ -351,6 +393,11 @@ static int axi_fan_control_init(struct axi_fan_control_data *ctl,
return ret;
}
static void axi_fan_control_clk_disable(void *clk)
{
clk_disable_unprepare(clk);
}
static const struct hwmon_channel_info *axi_fan_control_info[] = {
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_LABEL),
......@@ -370,6 +417,36 @@ static const struct hwmon_chip_info axi_chip_info = {
.info = axi_fan_control_info,
};
/* temperature threshold below which PWM should be 0% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp_hyst, axi_fan_control, ADI_REG_TEMP_00_H);
/* temperature threshold above which PWM should be 25% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, axi_fan_control, ADI_REG_TEMP_25_L);
/* temperature threshold below which PWM should be 25% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp_hyst, axi_fan_control, ADI_REG_TEMP_25_H);
/* temperature threshold above which PWM should be 50% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, axi_fan_control, ADI_REG_TEMP_50_L);
/* temperature threshold below which PWM should be 50% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp_hyst, axi_fan_control, ADI_REG_TEMP_50_H);
/* temperature threshold above which PWM should be 75% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, axi_fan_control, ADI_REG_TEMP_75_L);
/* temperature threshold below which PWM should be 75% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp_hyst, axi_fan_control, ADI_REG_TEMP_75_H);
/* temperature threshold above which PWM should be 100% */
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, axi_fan_control, ADI_REG_TEMP_100_L);
static struct attribute *axi_fan_control_attrs[] = {
&sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(axi_fan_control);
static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
static const struct of_device_id axi_fan_control_of_match[] = {
......@@ -406,6 +483,14 @@ static int axi_fan_control_probe(struct platform_device *pdev)
return PTR_ERR(clk);
}
ret = clk_prepare_enable(clk);
if (ret)
return ret;
ret = devm_add_action_or_reset(&pdev->dev, axi_fan_control_clk_disable, clk);
if (ret)
return ret;
ctl->clk_rate = clk_get_rate(clk);
if (!ctl->clk_rate)
return -EINVAL;
......@@ -446,7 +531,7 @@ static int axi_fan_control_probe(struct platform_device *pdev)
name,
ctl,
&axi_chip_info,
NULL);
axi_fan_control_groups);
return PTR_ERR_OR_ZERO(ctl->hdev);
}
......
This diff is collapsed.
......@@ -166,7 +166,7 @@ static int read_registers(struct fam15h_power_data *data)
memset(data->cu_on, 0, sizeof(int) * MAX_CUS);
get_online_cpus();
cpus_read_lock();
/*
* Choose the first online core of each compute unit, and then
......@@ -190,7 +190,7 @@ static int read_registers(struct fam15h_power_data *data)
on_each_cpu_mask(mask, do_read_registers_on_cu, data, true);
put_online_cpus();
cpus_read_unlock();
free_cpumask_var(mask);
return 0;
......
......@@ -228,6 +228,118 @@ static const struct m10bmc_hwmon_board_data d5005bmc_hwmon_bdata = {
.hinfo = d5005bmc_hinfo,
};
static const struct m10bmc_sdata n5010bmc_temp_tbl[] = {
{ 0x100, 0x0, 0x104, 0x0, 0x0, 1000, "Board Local Temperature" },
{ 0x108, 0x0, 0x10c, 0x0, 0x0, 1000, "FPGA 1 Temperature" },
{ 0x110, 0x0, 0x114, 0x0, 0x0, 1000, "FPGA 2 Temperature" },
{ 0x118, 0x0, 0x0, 0x0, 0x0, 1000, "Card Top Temperature" },
{ 0x11c, 0x0, 0x0, 0x0, 0x0, 1000, "Card Bottom Temperature" },
{ 0x128, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 1.2V Temperature" },
{ 0x134, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 5V Temperature" },
{ 0x140, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 0.9V Temperature" },
{ 0x14c, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 0.85V Temperature" },
{ 0x158, 0x0, 0x0, 0x0, 0x0, 1000, "AUX 12V Temperature" },
{ 0x164, 0x0, 0x0, 0x0, 0x0, 1000, "Backplane 12V Temperature" },
{ 0x1a8, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-1 Temperature" },
{ 0x1ac, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-2 Temperature" },
{ 0x1b0, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-3 Temperature" },
{ 0x1b4, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-4 Temperature" },
{ 0x1b8, 0x0, 0x0, 0x0, 0x0, 1000, "CVL1 Internal Temperature" },
{ 0x1bc, 0x0, 0x0, 0x0, 0x0, 1000, "CVL2 Internal Temperature" },
};
static const struct m10bmc_sdata n5010bmc_in_tbl[] = {
{ 0x120, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.2V Voltage" },
{ 0x12c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 5V Voltage" },
{ 0x138, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.9V Voltage" },
{ 0x144, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.85V Voltage" },
{ 0x150, 0x0, 0x0, 0x0, 0x0, 1, "AUX 12V Voltage" },
{ 0x15c, 0x0, 0x0, 0x0, 0x0, 1, "Backplane 12V Voltage" },
{ 0x16c, 0x0, 0x0, 0x0, 0x0, 1, "DDR4 1.2V Voltage" },
{ 0x17c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.8V Voltage" },
{ 0x184, 0x0, 0x0, 0x0, 0x0, 1, "QDR 1.3V Voltage" },
{ 0x18c, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 0.8V Voltage" },
{ 0x194, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 1.05V Voltage" },
{ 0x19c, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 1.05V Voltage" },
{ 0x1a4, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 0.8V Voltage" },
};
static const struct m10bmc_sdata n5010bmc_curr_tbl[] = {
{ 0x124, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.2V Current" },
{ 0x130, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 5V Current" },
{ 0x13c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.9V Current" },
{ 0x148, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.85V Current" },
{ 0x154, 0x0, 0x0, 0x0, 0x0, 1, "AUX 12V Current" },
{ 0x160, 0x0, 0x0, 0x0, 0x0, 1, "Backplane 12V Current" },
{ 0x168, 0x0, 0x0, 0x0, 0x0, 1, "DDR4 1.2V Current" },
{ 0x178, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.8V Current" },
{ 0x180, 0x0, 0x0, 0x0, 0x0, 1, "QDR 1.3V Current" },
{ 0x188, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 0.8V Current" },
{ 0x190, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 1.05V Current" },
{ 0x198, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 1.05V Current" },
{ 0x1a0, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 0.8V Current" },
};
static const struct hwmon_channel_info *n5010bmc_hinfo[] = {
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL),
HWMON_CHANNEL_INFO(curr,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL,
HWMON_C_INPUT | HWMON_C_LABEL),
NULL
};
static const struct m10bmc_hwmon_board_data n5010bmc_hwmon_bdata = {
.tables = {
[hwmon_temp] = n5010bmc_temp_tbl,
[hwmon_in] = n5010bmc_in_tbl,
[hwmon_curr] = n5010bmc_curr_tbl,
},
.hinfo = n5010bmc_hinfo,
};
static umode_t
m10bmc_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
......@@ -438,6 +550,10 @@ static const struct platform_device_id intel_m10bmc_hwmon_ids[] = {
.name = "d5005bmc-hwmon",
.driver_data = (unsigned long)&d5005bmc_hwmon_bdata,
},
{
.name = "n5010bmc-hwmon",
.driver_data = (unsigned long)&n5010bmc_hwmon_bdata,
},
{ }
};
......
......@@ -65,10 +65,11 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
#define F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET 0xd8200c64
#define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET 0xd8200ca4
/* Common for Zen CPU families (Family 17h and 18h) */
#define ZEN_REPORTED_TEMP_CTRL_OFFSET 0x00059800
/* Common for Zen CPU families (Family 17h and 18h and 19h) */
#define ZEN_REPORTED_TEMP_CTRL_BASE 0x00059800
#define ZEN_CCD_TEMP(x) (0x00059954 + ((x) * 4))
#define ZEN_CCD_TEMP(offset, x) (ZEN_REPORTED_TEMP_CTRL_BASE + \
(offset) + ((x) * 4))
#define ZEN_CCD_TEMP_VALID BIT(11)
#define ZEN_CCD_TEMP_MASK GENMASK(10, 0)
......@@ -103,6 +104,7 @@ struct k10temp_data {
u32 temp_adjust_mask;
u32 show_temp;
bool is_zen;
u32 ccd_offset;
};
#define TCTL_BIT 0
......@@ -163,7 +165,7 @@ static void read_tempreg_nb_f15(struct pci_dev *pdev, u32 *regval)
static void read_tempreg_nb_zen(struct pci_dev *pdev, u32 *regval)
{
amd_smn_read(amd_pci_dev_to_node_id(pdev),
ZEN_REPORTED_TEMP_CTRL_OFFSET, regval);
ZEN_REPORTED_TEMP_CTRL_BASE, regval);
}
static long get_raw_temp(struct k10temp_data *data)
......@@ -226,7 +228,8 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
break;
case 2 ... 9: /* Tccd{1-8} */
amd_smn_read(amd_pci_dev_to_node_id(data->pdev),
ZEN_CCD_TEMP(channel - 2), &regval);
ZEN_CCD_TEMP(data->ccd_offset, channel - 2),
&regval);
*val = (regval & ZEN_CCD_TEMP_MASK) * 125 - 49000;
break;
default:
......@@ -387,7 +390,7 @@ static void k10temp_get_ccd_support(struct pci_dev *pdev,
for (i = 0; i < limit; i++) {
amd_smn_read(amd_pci_dev_to_node_id(pdev),
ZEN_CCD_TEMP(i), &regval);
ZEN_CCD_TEMP(data->ccd_offset, i), &regval);
if (regval & ZEN_CCD_TEMP_VALID)
data->show_temp |= BIT(TCCD_BIT(i));
}
......@@ -426,7 +429,6 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
} else if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) {
data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
data->read_tempreg = read_tempreg_nb_zen;
data->show_temp |= BIT(TDIE_BIT); /* show Tdie */
data->is_zen = true;
switch (boot_cpu_data.x86_model) {
......@@ -434,22 +436,31 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
case 0x8: /* Zen+ */
case 0x11: /* Zen APU */
case 0x18: /* Zen+ APU */
data->ccd_offset = 0x154;
k10temp_get_ccd_support(pdev, data, 4);
break;
case 0x31: /* Zen2 Threadripper */
case 0x60: /* Renoir */
case 0x68: /* Lucienne */
case 0x71: /* Zen2 */
data->ccd_offset = 0x154;
k10temp_get_ccd_support(pdev, data, 8);
break;
}
} else if (boot_cpu_data.x86 == 0x19) {
data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
data->read_tempreg = read_tempreg_nb_zen;
data->show_temp |= BIT(TDIE_BIT);
data->is_zen = true;
switch (boot_cpu_data.x86_model) {
case 0x0 ... 0x1: /* Zen3 SP3/TR */
case 0x21: /* Zen3 Ryzen Desktop */
case 0x50 ... 0x5f: /* Green Sardine */
data->ccd_offset = 0x154;
k10temp_get_ccd_support(pdev, data, 8);
break;
case 0x40 ... 0x4f: /* Yellow Carp */
data->ccd_offset = 0x300;
k10temp_get_ccd_support(pdev, data, 8);
break;
}
......@@ -463,6 +474,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (boot_cpu_data.x86 == entry->model &&
strstr(boot_cpu_data.x86_model_id, entry->id)) {
data->show_temp |= BIT(TDIE_BIT); /* show Tdie */
data->temp_offset = entry->offset;
break;
}
......@@ -491,6 +503,8 @@ static const struct pci_device_id k10temp_id_table[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) },
{ PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) },
{}
};
......
......@@ -13,6 +13,7 @@
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/fixp-arith.h>
#include <linux/platform_data/ntc_thermistor.h>
......@@ -549,15 +550,16 @@ static int get_temp_mc(struct ntc_data *data, unsigned int ohm)
int temp;
lookup_comp(data, ohm, &low, &high);
if (low == high) {
/* Unable to use linear approximation */
temp = data->comp[low].temp_c * 1000;
} else {
temp = data->comp[low].temp_c * 1000 +
((data->comp[high].temp_c - data->comp[low].temp_c) *
1000 * ((int)ohm - (int)data->comp[low].ohm)) /
((int)data->comp[high].ohm - (int)data->comp[low].ohm);
}
/*
* First multiplying the table temperatures with 1000 to get to
* millicentigrades (which is what we want) and then interpolating
* will give the best precision.
*/
temp = fixp_linear_interpolate(data->comp[low].ohm,
data->comp[low].temp_c * 1000,
data->comp[high].ohm,
data->comp[high].temp_c * 1000,
ohm);
return temp;
}
......
......@@ -12,14 +12,7 @@
#include <linux/pmbus.h>
#include "pmbus.h"
#define BPARS600_MFR_VIN_MIN 0xa0
#define BPARS600_MFR_VIN_MAX 0xa1
#define BPARS600_MFR_IIN_MAX 0xa2
#define BPARS600_MFR_PIN_MAX 0xa3
#define BPARS600_MFR_VOUT_MIN 0xa4
#define BPARS600_MFR_VOUT_MAX 0xa5
#define BPARS600_MFR_IOUT_MAX 0xa6
#define BPARS600_MFR_POUT_MAX 0xa7
enum chips { bpa_rs600, bpd_rs600 };
static int bpa_rs600_read_byte_data(struct i2c_client *client, int page, int reg)
{
......@@ -72,6 +65,26 @@ static int bpa_rs600_read_vin(struct i2c_client *client)
return ret;
}
/*
* Firmware V5.70 incorrectly reports 1640W for MFR_PIN_MAX.
* Deal with this by returning a sensible value.
*/
static int bpa_rs600_read_pin_max(struct i2c_client *client)
{
int ret;
ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_MFR_PIN_MAX);
if (ret < 0)
return ret;
/* Detect invalid 1640W (linear encoding) */
if (ret == 0x0b34)
/* Report 700W (linear encoding) */
return 0x095e;
return ret;
}
static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int phase, int reg)
{
int ret;
......@@ -81,29 +94,13 @@ static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int pha
switch (reg) {
case PMBUS_VIN_UV_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MIN);
break;
case PMBUS_VIN_OV_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MAX);
break;
case PMBUS_VOUT_UV_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MIN);
break;
case PMBUS_VOUT_OV_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MAX);
break;
case PMBUS_IIN_OC_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IIN_MAX);
break;
case PMBUS_IOUT_OC_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IOUT_MAX);
break;
case PMBUS_PIN_OP_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_PIN_MAX);
break;
case PMBUS_POUT_OP_WARN_LIMIT:
ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_POUT_MAX);
break;
case PMBUS_VIN_UV_FAULT_LIMIT:
case PMBUS_VIN_OV_FAULT_LIMIT:
case PMBUS_VOUT_UV_FAULT_LIMIT:
......@@ -114,6 +111,9 @@ static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int pha
case PMBUS_READ_VIN:
ret = bpa_rs600_read_vin(client);
break;
case PMBUS_MFR_PIN_MAX:
ret = bpa_rs600_read_pin_max(client);
break;
default:
if (reg >= PMBUS_VIRT_BASE)
ret = -ENXIO;
......@@ -146,11 +146,19 @@ static struct pmbus_driver_info bpa_rs600_info = {
.read_word_data = bpa_rs600_read_word_data,
};
static const struct i2c_device_id bpa_rs600_id[] = {
{ "bpa-rs600", bpa_rs600 },
{ "bpd-rs600", bpd_rs600 },
{},
};
MODULE_DEVICE_TABLE(i2c, bpa_rs600_id);
static int bpa_rs600_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
int ret;
const struct i2c_device_id *mid;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA
......@@ -164,7 +172,11 @@ static int bpa_rs600_probe(struct i2c_client *client)
return ret;
}
if (strncmp(buf, "BPA-RS600", 8)) {
for (mid = bpa_rs600_id; mid->name[0]; mid++) {
if (!strncasecmp(buf, mid->name, strlen(mid->name)))
break;
}
if (!mid->name[0]) {
buf[ret] = '\0';
dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf);
return -ENODEV;
......@@ -173,12 +185,6 @@ static int bpa_rs600_probe(struct i2c_client *client)
return pmbus_do_probe(client, &bpa_rs600_info);
}
static const struct i2c_device_id bpa_rs600_id[] = {
{ "bpars600", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, bpa_rs600_id);
static const struct of_device_id __maybe_unused bpa_rs600_of_match[] = {
{ .compatible = "blutek,bpa-rs600" },
{},
......
......@@ -50,9 +50,9 @@
#define CFFPS_MFR_VAUX_FAULT BIT(6)
#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7)
#define CFFPS_LED_BLINK BIT(0)
#define CFFPS_LED_ON BIT(1)
#define CFFPS_LED_OFF BIT(2)
#define CFFPS_LED_BLINK (BIT(0) | BIT(6))
#define CFFPS_LED_ON (BIT(1) | BIT(6))
#define CFFPS_LED_OFF (BIT(2) | BIT(6))
#define CFFPS_BLINK_RATE_MS 250
enum {
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* sbrmi.c - hwmon driver for a SB-RMI mailbox
* compliant AMD SoC device.
*
* Copyright (C) 2020-2021 Advanced Micro Devices, Inc.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
/* Do not allow setting negative power limit */
#define SBRMI_PWR_MIN 0
/* Mask for Status Register bit[1] */
#define SW_ALERT_MASK 0x2
/* Software Interrupt for triggering */
#define START_CMD 0x80
#define TRIGGER_MAILBOX 0x01
/*
* SB-RMI supports soft mailbox service request to MP1 (power management
* firmware) through SBRMI inbound/outbound message registers.
* SB-RMI message IDs
*/
enum sbrmi_msg_id {
SBRMI_READ_PKG_PWR_CONSUMPTION = 0x1,
SBRMI_WRITE_PKG_PWR_LIMIT,
SBRMI_READ_PKG_PWR_LIMIT,
SBRMI_READ_PKG_MAX_PWR_LIMIT,
};
/* SB-RMI registers */
enum sbrmi_reg {
SBRMI_CTRL = 0x01,
SBRMI_STATUS,
SBRMI_OUTBNDMSG0 = 0x30,
SBRMI_OUTBNDMSG1,
SBRMI_OUTBNDMSG2,
SBRMI_OUTBNDMSG3,
SBRMI_OUTBNDMSG4,
SBRMI_OUTBNDMSG5,
SBRMI_OUTBNDMSG6,
SBRMI_OUTBNDMSG7,
SBRMI_INBNDMSG0,
SBRMI_INBNDMSG1,
SBRMI_INBNDMSG2,
SBRMI_INBNDMSG3,
SBRMI_INBNDMSG4,
SBRMI_INBNDMSG5,
SBRMI_INBNDMSG6,
SBRMI_INBNDMSG7,
SBRMI_SW_INTERRUPT,
};
/* Each client has this additional data */
struct sbrmi_data {
struct i2c_client *client;
struct mutex lock;
u32 pwr_limit_max;
};
struct sbrmi_mailbox_msg {
u8 cmd;
bool read;
u32 data_in;
u32 data_out;
};
static int sbrmi_enable_alert(struct i2c_client *client)
{
int ctrl;
/*
* Enable the SB-RMI Software alert status
* by writing 0 to bit 4 of Control register(0x1)
*/
ctrl = i2c_smbus_read_byte_data(client, SBRMI_CTRL);
if (ctrl < 0)
return ctrl;
if (ctrl & 0x10) {
ctrl &= ~0x10;
return i2c_smbus_write_byte_data(client,
SBRMI_CTRL, ctrl);
}
return 0;
}
static int rmi_mailbox_xfer(struct sbrmi_data *data,
struct sbrmi_mailbox_msg *msg)
{
int i, ret, retry = 10;
int sw_status;
u8 byte;
mutex_lock(&data->lock);
/* Indicate firmware a command is to be serviced */
ret = i2c_smbus_write_byte_data(data->client,
SBRMI_INBNDMSG7, START_CMD);
if (ret < 0)
goto exit_unlock;
/* Write the command to SBRMI::InBndMsg_inst0 */
ret = i2c_smbus_write_byte_data(data->client,
SBRMI_INBNDMSG0, msg->cmd);
if (ret < 0)
goto exit_unlock;
/*
* For both read and write the initiator (BMC) writes
* Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1]
* SBRMI_x3C(MSB):SBRMI_x39(LSB)
*/
for (i = 0; i < 4; i++) {
byte = (msg->data_in >> i * 8) & 0xff;
ret = i2c_smbus_write_byte_data(data->client,
SBRMI_INBNDMSG1 + i, byte);
if (ret < 0)
goto exit_unlock;
}
/*
* Write 0x01 to SBRMI::SoftwareInterrupt to notify firmware to
* perform the requested read or write command
*/
ret = i2c_smbus_write_byte_data(data->client,
SBRMI_SW_INTERRUPT, TRIGGER_MAILBOX);
if (ret < 0)
goto exit_unlock;
/*
* Firmware will write SBRMI::Status[SwAlertSts]=1 to generate
* an ALERT (if enabled) to initiator (BMC) to indicate completion
* of the requested command
*/
do {
sw_status = i2c_smbus_read_byte_data(data->client,
SBRMI_STATUS);
if (sw_status < 0) {
ret = sw_status;
goto exit_unlock;
}
if (sw_status & SW_ALERT_MASK)
break;
usleep_range(50, 100);
} while (retry--);
if (retry < 0) {
dev_err(&data->client->dev,
"Firmware fail to indicate command completion\n");
ret = -EIO;
goto exit_unlock;
}
/*
* For a read operation, the initiator (BMC) reads the firmware
* response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1]
* {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
*/
if (msg->read) {
for (i = 0; i < 4; i++) {
ret = i2c_smbus_read_byte_data(data->client,
SBRMI_OUTBNDMSG1 + i);
if (ret < 0)
goto exit_unlock;
msg->data_out |= ret << i * 8;
}
}
/*
* BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the
* ALERT to initiator
*/
ret = i2c_smbus_write_byte_data(data->client, SBRMI_STATUS,
sw_status | SW_ALERT_MASK);
exit_unlock:
mutex_unlock(&data->lock);
return ret;
}
static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct sbrmi_data *data = dev_get_drvdata(dev);
struct sbrmi_mailbox_msg msg = { 0 };
int ret;
if (type != hwmon_power)
return -EINVAL;
msg.read = true;
switch (attr) {
case hwmon_power_input:
msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION;
ret = rmi_mailbox_xfer(data, &msg);
break;
case hwmon_power_cap:
msg.cmd = SBRMI_READ_PKG_PWR_LIMIT;
ret = rmi_mailbox_xfer(data, &msg);
break;
case hwmon_power_cap_max:
msg.data_out = data->pwr_limit_max;
ret = 0;
break;
default:
return -EINVAL;
}
if (ret < 0)
return ret;
/* hwmon power attributes are in microWatt */
*val = (long)msg.data_out * 1000;
return ret;
}
static int sbrmi_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct sbrmi_data *data = dev_get_drvdata(dev);
struct sbrmi_mailbox_msg msg = { 0 };
if (type != hwmon_power && attr != hwmon_power_cap)
return -EINVAL;
/*
* hwmon power attributes are in microWatt
* mailbox read/write is in mWatt
*/
val /= 1000;
val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max);
msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT;
msg.data_in = val;
msg.read = false;
return rmi_mailbox_xfer(data, &msg);
}
static umode_t sbrmi_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_power:
switch (attr) {
case hwmon_power_input:
case hwmon_power_cap_max:
return 0444;
case hwmon_power_cap:
return 0644;
}
break;
default:
break;
}
return 0;
}
static const struct hwmon_channel_info *sbrmi_info[] = {
HWMON_CHANNEL_INFO(power,
HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX),
NULL
};
static const struct hwmon_ops sbrmi_hwmon_ops = {
.is_visible = sbrmi_is_visible,
.read = sbrmi_read,
.write = sbrmi_write,
};
static const struct hwmon_chip_info sbrmi_chip_info = {
.ops = &sbrmi_hwmon_ops,
.info = sbrmi_info,
};
static int sbrmi_get_max_pwr_limit(struct sbrmi_data *data)
{
struct sbrmi_mailbox_msg msg = { 0 };
int ret;
msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT;
msg.read = true;
ret = rmi_mailbox_xfer(data, &msg);
if (ret < 0)
return ret;
data->pwr_limit_max = msg.data_out;
return ret;
}
static int sbrmi_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device *hwmon_dev;
struct sbrmi_data *data;
int ret;
data = devm_kzalloc(dev, sizeof(struct sbrmi_data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
/* Enable alert for SB-RMI sequence */
ret = sbrmi_enable_alert(client);
if (ret < 0)
return ret;
/* Cache maximum power limit */
ret = sbrmi_get_max_pwr_limit(data);
if (ret < 0)
return ret;
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
&sbrmi_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static const struct i2c_device_id sbrmi_id[] = {
{"sbrmi", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, sbrmi_id);
static const struct of_device_id __maybe_unused sbrmi_of_match[] = {
{
.compatible = "amd,sbrmi",
},
{ },
};
MODULE_DEVICE_TABLE(of, sbrmi_of_match);
static struct i2c_driver sbrmi_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "sbrmi",
.of_match_table = of_match_ptr(sbrmi_of_match),
},
.probe = sbrmi_probe,
.id_table = sbrmi_id,
};
module_i2c_driver(sbrmi_driver);
MODULE_AUTHOR("Akshay Gupta <akshay.gupta@amd.com>");
MODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor");
MODULE_LICENSE("GPL");
......@@ -372,12 +372,10 @@ struct w83627ehf_data {
u8 temp3_val_only:1;
u8 have_vid:1;
#ifdef CONFIG_PM
/* Remember extra register values over suspend/resume */
u8 vbat;
u8 fandiv1;
u8 fandiv2;
#endif
};
struct w83627ehf_sio_data {
......@@ -1083,7 +1081,7 @@ cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf)
struct w83627ehf_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm));
}
DEVICE_ATTR_RO(cpu0_vid);
static DEVICE_ATTR_RO(cpu0_vid);
/* Case open detection */
......@@ -1694,7 +1692,7 @@ static const struct hwmon_chip_info w83627ehf_chip_info = {
.info = w83627ehf_info,
};
static int w83627ehf_probe(struct platform_device *pdev)
static int __init w83627ehf_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev);
......@@ -1705,20 +1703,12 @@ static int w83627ehf_probe(struct platform_device *pdev)
struct device *hwmon_dev;
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!request_region(res->start, IOREGION_LENGTH, DRVNAME)) {
err = -EBUSY;
dev_err(dev, "Failed to request region 0x%lx-0x%lx\n",
(unsigned long)res->start,
(unsigned long)res->start + IOREGION_LENGTH - 1);
goto exit;
}
if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME))
return -EBUSY;
data = devm_kzalloc(&pdev->dev, sizeof(struct w83627ehf_data),
GFP_KERNEL);
if (!data) {
err = -ENOMEM;
goto exit_release;
}
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->addr = res->start;
mutex_init(&data->lock);
......@@ -1882,7 +1872,7 @@ static int w83627ehf_probe(struct platform_device *pdev)
err = superio_enter(sio_data->sioreg);
if (err)
goto exit_release;
return err;
/* Read VID value */
if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
......@@ -1951,30 +1941,10 @@ static int w83627ehf_probe(struct platform_device *pdev)
data,
&w83627ehf_chip_info,
w83627ehf_groups);
if (IS_ERR(hwmon_dev)) {
err = PTR_ERR(hwmon_dev);
goto exit_release;
}
return 0;
exit_release:
release_region(res->start, IOREGION_LENGTH);
exit:
return err;
return PTR_ERR_OR_ZERO(hwmon_dev);
}
static int w83627ehf_remove(struct platform_device *pdev)
{
struct w83627ehf_data *data = platform_get_drvdata(pdev);
release_region(data->addr, IOREGION_LENGTH);
return 0;
}
#ifdef CONFIG_PM
static int w83627ehf_suspend(struct device *dev)
static int __maybe_unused w83627ehf_suspend(struct device *dev)
{
struct w83627ehf_data *data = w83627ehf_update_device(dev);
......@@ -1985,7 +1955,7 @@ static int w83627ehf_suspend(struct device *dev)
return 0;
}
static int w83627ehf_resume(struct device *dev)
static int __maybe_unused w83627ehf_resume(struct device *dev)
{
struct w83627ehf_data *data = dev_get_drvdata(dev);
int i;
......@@ -2040,25 +2010,13 @@ static int w83627ehf_resume(struct device *dev)
return 0;
}
static const struct dev_pm_ops w83627ehf_dev_pm_ops = {
.suspend = w83627ehf_suspend,
.resume = w83627ehf_resume,
.freeze = w83627ehf_suspend,
.restore = w83627ehf_resume,
};
#define W83627EHF_DEV_PM_OPS (&w83627ehf_dev_pm_ops)
#else
#define W83627EHF_DEV_PM_OPS NULL
#endif /* CONFIG_PM */
static SIMPLE_DEV_PM_OPS(w83627ehf_dev_pm_ops, w83627ehf_suspend, w83627ehf_resume);
static struct platform_driver w83627ehf_driver = {
.driver = {
.name = DRVNAME,
.pm = W83627EHF_DEV_PM_OPS,
.pm = &w83627ehf_dev_pm_ops,
},
.probe = w83627ehf_probe,
.remove = w83627ehf_remove,
};
/* w83627ehf_find() looks for a '627 in the Super-I/O config space */
......@@ -2150,8 +2108,7 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr,
/*
* when Super-I/O functions move to a separate file, the Super-I/O
* bus will manage the lifetime of the device and this module will only keep
* track of the w83627ehf driver. But since we platform_device_alloc(), we
* must keep track of the device
* track of the w83627ehf driver.
*/
static struct platform_device *pdev;
......@@ -2159,7 +2116,10 @@ static int __init sensors_w83627ehf_init(void)
{
int err;
unsigned short address;
struct resource res;
struct resource res = {
.name = DRVNAME,
.flags = IORESOURCE_IO,
};
struct w83627ehf_sio_data sio_data;
/*
......@@ -2173,55 +2133,17 @@ static int __init sensors_w83627ehf_init(void)
w83627ehf_find(0x4e, &address, &sio_data))
return -ENODEV;
err = platform_driver_register(&w83627ehf_driver);
if (err)
goto exit;
pdev = platform_device_alloc(DRVNAME, address);
if (!pdev) {
err = -ENOMEM;
pr_err("Device allocation failed\n");
goto exit_unregister;
}
err = platform_device_add_data(pdev, &sio_data,
sizeof(struct w83627ehf_sio_data));
if (err) {
pr_err("Platform data allocation failed\n");
goto exit_device_put;
}
memset(&res, 0, sizeof(res));
res.name = DRVNAME;
res.start = address + IOREGION_OFFSET;
res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1;
res.flags = IORESOURCE_IO;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit_device_put;
err = platform_device_add_resources(pdev, &res, 1);
if (err) {
pr_err("Device resource addition failed (%d)\n", err);
goto exit_device_put;
}
/* platform_device_add calls probe() */
err = platform_device_add(pdev);
if (err) {
pr_err("Device addition failed (%d)\n", err);
goto exit_device_put;
}
return err;
return 0;
pdev = platform_create_bundle(&w83627ehf_driver, w83627ehf_probe, &res, 1, &sio_data,
sizeof(struct w83627ehf_sio_data));
exit_device_put:
platform_device_put(pdev);
exit_unregister:
platform_driver_unregister(&w83627ehf_driver);
exit:
return err;
return PTR_ERR_OR_ZERO(pdev);
}
static void __exit sensors_w83627ehf_exit(void)
......
......@@ -1571,10 +1571,21 @@ static const struct i2c_device_id w83781d_ids[] = {
};
MODULE_DEVICE_TABLE(i2c, w83781d_ids);
static const struct of_device_id w83781d_of_match[] = {
{ .compatible = "winbond,w83781d" },
{ .compatible = "winbond,w83781g" },
{ .compatible = "winbond,w83782d" },
{ .compatible = "winbond,w83783s" },
{ .compatible = "asus,as99127f" },
{ },
};
MODULE_DEVICE_TABLE(of, w83781d_of_match);
static struct i2c_driver w83781d_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "w83781d",
.of_match_table = w83781d_of_match,
},
.probe_new = w83781d_probe,
.remove = w83781d_remove,
......
......@@ -555,6 +555,7 @@
#define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b
#define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443
#define PCI_DEVICE_ID_AMD_19H_DF_F3 0x1653
#define PCI_DEVICE_ID_AMD_19H_M40H_DF_F3 0x167c
#define PCI_DEVICE_ID_AMD_19H_M50H_DF_F3 0x166d
#define PCI_DEVICE_ID_AMD_CNB17H_F3 0x1703
#define PCI_DEVICE_ID_AMD_LANCE 0x2000
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment