Commit fe91f281 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'hwmon-for-linus-v4.14' of...

Merge tag 'hwmon-for-linus-v4.14' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull hwmon updates from Guenter Roeck:

 - new drivers:
   - Lantiq CPU temperature sensor
   - IBM CFF power supply
   - TPS53679 PMBus driver

 - new support:
   - LM5066I (lm25066 PMBus driver)
   - Intel VID protocol VR13 (PMBus drivers)
   - CAT34TS02C, GT30TS00, GT34TS02, and CAT34TS04 (jc42 driver)

 - cleanup and minor improvements in several drivers

* tag 'hwmon-for-linus-v4.14' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (36 commits)
  hwmon: (ltq-cputemp) add cpu temp sensor driver
  hwmon: (ltq-cputemp) add devicetree bindings documentation
  hwmon: (pmbus) Add support for Texas Instruments tps53679 device
  hwmon: (asc7621) make several arrays static const
  hwmon: (pmbus/lm25066) Add support for TI LM5066I
  hwmon: (pmbus/lm25066) Offset coefficient depends on CL
  hwmon: (pmbus) Add support for Intel VID protocol VR13
  Documentation: hwmon: Document the IBM CFF power supply
  hwmon: (pmbus) Add IBM Common Form Factor (CFF) power supply driver
  dt-bindings: hwmon: Document the IBM CCF power supply version 1
  hwmon: (ftsteutates) constify i2c_device_id
  hwmon: da9052: Add support for TSI channel
  mfd: da9052: Make touchscreen registration optional
  hwmon: da9052: Replace S_IRUGO with 0444
  mfd: da9052: Add register details for TSI
  hwmon: (aspeed-pwm) add THERMAL dependency
  hwmon: (pmbus) Add debugfs for status registers
  hwmon: (aspeed-pwm-tacho) cooling device support.
  Documentation: dt-bindings: aspeed-pwm-tacho cooling device.
  hwmon: (pmbus): Add generic alarm bit for iin and pin
  ...
parents aa9d4648 7074d0a9
......@@ -11,6 +11,8 @@ Required properties for pwm-tacho node:
- #size-cells : should be 1.
- #cooling-cells: should be 2.
- reg : address and length of the register set for the device.
- pinctrl-names : a pinctrl state named "default" must be defined.
......@@ -28,12 +30,17 @@ fan subnode format:
Under fan subnode there can upto 8 child nodes, with each child node
representing a fan. If there are 8 fans each fan can have one PWM port and
one/two Fan tach inputs.
For PWM port can be configured cooling-levels to create cooling device.
Cooling device could be bound to a thermal zone for the thermal control.
Required properties for each child node:
- reg : should specify PWM source port.
integer value in the range 0 to 7 with 0 indicating PWM port A and
7 indicating PWM port H.
- cooling-levels: PWM duty cycle values in a range from 0 to 255
which correspond to thermal cooling states.
- aspeed,fan-tach-ch : should specify the Fan tach input channel.
integer value in the range 0 through 15, with 0 indicating
Fan tach channel 0 and 15 indicating Fan tach channel 15.
......@@ -50,6 +57,7 @@ pwm_tacho_fixed_clk: fixedclk {
pwm_tacho: pwmtachocontroller@1e786000 {
#address-cells = <1>;
#size-cells = <1>;
#cooling-cells = <2>;
reg = <0x1E786000 0x1000>;
compatible = "aspeed,ast2500-pwm-tacho";
clocks = <&pwm_tacho_fixed_clk>;
......@@ -58,6 +66,7 @@ pwm_tacho: pwmtachocontroller@1e786000 {
fan@0 {
reg = <0x00>;
cooling-levels = /bits/ 8 <125 151 177 203 229 255>;
aspeed,fan-tach-ch = /bits/ 8 <0x00>;
};
......
Device-tree bindings for IBM Common Form Factor Power Supply Version 1
----------------------------------------------------------------------
Required properties:
- compatible = "ibm,cffps1";
- reg = < I2C bus address >; : Address of the power supply on the
I2C bus.
Example:
i2c-bus@100 {
#address-cells = <1>;
#size-cells = <0>;
#interrupt-cells = <1>;
< more properties >
power-supply@68 {
compatible = "ibm,cffps1";
reg = <0x68>;
};
};
Lantiq cpu temperatur sensor
Requires node properties:
- compatible value :
"lantiq,cputemp"
Example:
cputemp@0 {
compatible = "lantiq,cputemp";
};
......@@ -18,6 +18,10 @@ enhancements. It can monitor up to 4 voltages, 16 temperatures and
8 fans. It also contains an integrated watchdog which is currently
implemented in this driver.
To clear a temperature or fan alarm, execute the following command with the
correct path to the alarm file:
echo 0 >XXXX_alarm
Specification of the chip can be found here:
ftp://ftp.ts.fujitsu.com/pub/Mainboard-OEM-Sales/Services/Software&Tools/Linux_SystemMonitoring&Watchdog&GPIO/BMC-Teutates_Specification_V1.21.pdf
ftp://ftp.ts.fujitsu.com/pub/Mainboard-OEM-Sales/Services/Software&Tools/Linux_SystemMonitoring&Watchdog&GPIO/Fujitsu_mainboards-1-Sensors_HowTo-en-US.pdf
Kernel driver ibm-cffps
=======================
Supported chips:
* IBM Common Form Factor power supply
Author: Eddie James <eajames@us.ibm.com>
Description
-----------
This driver supports IBM Common Form Factor (CFF) power supplies. This driver
is a client to the core PMBus driver.
Usage Notes
-----------
This driver does not auto-detect devices. You will have to instantiate the
devices explicitly. Please see Documentation/i2c/instantiating-devices for
details.
Sysfs entries
-------------
The following attributes are supported:
curr1_alarm Output current over-current alarm.
curr1_input Measured output current in mA.
curr1_label "iout1"
fan1_alarm Fan 1 warning.
fan1_fault Fan 1 fault.
fan1_input Fan 1 speed in RPM.
fan2_alarm Fan 2 warning.
fan2_fault Fan 2 fault.
fan2_input Fan 2 speed in RPM.
in1_alarm Input voltage under-voltage alarm.
in1_input Measured input voltage in mV.
in1_label "vin"
in2_alarm Output voltage over-voltage alarm.
in2_input Measured output voltage in mV.
in2_label "vout1"
power1_alarm Input fault or alarm.
power1_input Measured input power in uW.
power1_label "pin"
temp1_alarm PSU inlet ambient temperature over-temperature alarm.
temp1_input Measured PSU inlet ambient temp in millidegrees C.
temp2_alarm Secondary rectifier temp over-temperature alarm.
temp2_input Measured secondary rectifier temp in millidegrees C.
temp3_alarm ORing FET temperature over-temperature alarm.
temp3_input Measured ORing FET temperature in millidegrees C.
......@@ -29,6 +29,11 @@ Supported chips:
Addresses scanned: -
Datasheet:
http://www.national.com/pf/LM/LM5066.html
* Texas Instruments LM5066I
Prefix: 'lm5066i'
Addresses scanned: -
Datasheet:
http://www.ti.com/product/LM5066I
Author: Guenter Roeck <linux@roeck-us.net>
......@@ -37,8 +42,8 @@ Description
-----------
This driver supports hardware monitoring for National Semiconductor / TI LM25056,
LM25063, LM25066, LM5064, and LM5066 Power Management, Monitoring, Control, and
Protection ICs.
LM25063, LM25066, LM5064, and LM5066/LM5066I Power Management, Monitoring,
Control, and Protection ICs.
The driver is a client driver to the core PMBus driver. Please see
Documentation/hwmon/pmbus for details on PMBus client drivers.
......
......@@ -343,6 +343,7 @@ config SENSORS_ASB100
config SENSORS_ASPEED
tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver"
depends on THERMAL || THERMAL=n
select REGMAP
help
This driver provides support for ASPEED AST2400/AST2500 PWM
......@@ -790,6 +791,13 @@ config SENSORS_LTC4261
This driver can also be built as a module. If so, the module will
be called ltc4261.
config SENSORS_LTQ_CPUTEMP
bool "Lantiq cpu temperature sensor driver"
depends on LANTIQ
help
If you say yes here you get support for the temperature
sensor inside your CPU.
config SENSORS_MAX1111
tristate "Maxim MAX1111 Serial 8-bit ADC chip and compatibles"
depends on SPI_MASTER
......
......@@ -110,6 +110,7 @@ obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
obj-$(CONFIG_SENSORS_MAX16065) += max16065.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
......
......@@ -384,7 +384,7 @@ static struct attribute *adc128_attrs[] = {
NULL
};
static struct attribute_group adc128_group = {
static const struct attribute_group adc128_group = {
.attrs = adc128_attrs,
.is_visible = adc128_is_visible,
};
......
......@@ -191,24 +191,23 @@ static int ads1015_get_channels_config_of(struct i2c_client *client)
unsigned int data_rate = ADS1015_DEFAULT_DATA_RATE;
if (of_property_read_u32(node, "reg", &pval)) {
dev_err(&client->dev, "invalid reg on %s\n",
node->full_name);
dev_err(&client->dev, "invalid reg on %pOF\n", node);
continue;
}
channel = pval;
if (channel >= ADS1015_CHANNELS) {
dev_err(&client->dev,
"invalid channel index %d on %s\n",
channel, node->full_name);
"invalid channel index %d on %pOF\n",
channel, node);
continue;
}
if (!of_property_read_u32(node, "ti,gain", &pval)) {
pga = pval;
if (pga > 6) {
dev_err(&client->dev, "invalid gain on %s\n",
node->full_name);
dev_err(&client->dev, "invalid gain on %pOF\n",
node);
return -EINVAL;
}
}
......@@ -217,8 +216,7 @@ static int ads1015_get_channels_config_of(struct i2c_client *client)
data_rate = pval;
if (data_rate > 7) {
dev_err(&client->dev,
"invalid data_rate on %s\n",
node->full_name);
"invalid data_rate on %pOF\n", node);
return -EINVAL;
}
}
......
......@@ -1319,14 +1319,14 @@ static struct attribute *vid_attrs[] = {
NULL
};
static struct attribute_group adt7475_attr_group = { .attrs = adt7475_attrs };
static struct attribute_group fan4_attr_group = { .attrs = fan4_attrs };
static struct attribute_group pwm2_attr_group = { .attrs = pwm2_attrs };
static struct attribute_group in0_attr_group = { .attrs = in0_attrs };
static struct attribute_group in3_attr_group = { .attrs = in3_attrs };
static struct attribute_group in4_attr_group = { .attrs = in4_attrs };
static struct attribute_group in5_attr_group = { .attrs = in5_attrs };
static struct attribute_group vid_attr_group = { .attrs = vid_attrs };
static const struct attribute_group adt7475_attr_group = { .attrs = adt7475_attrs };
static const struct attribute_group fan4_attr_group = { .attrs = fan4_attrs };
static const struct attribute_group pwm2_attr_group = { .attrs = pwm2_attrs };
static const struct attribute_group in0_attr_group = { .attrs = in0_attrs };
static const struct attribute_group in3_attr_group = { .attrs = in3_attrs };
static const struct attribute_group in4_attr_group = { .attrs = in4_attrs };
static const struct attribute_group in5_attr_group = { .attrs = in5_attrs };
static const struct attribute_group vid_attr_group = { .attrs = vid_attrs };
static int adt7475_detect(struct i2c_client *client,
struct i2c_board_info *info)
......
......@@ -512,7 +512,7 @@ static ssize_t show_pwm_ac(struct device *dev,
{
SETUP_SHOW_DATA_PARAM(dev, attr);
u8 config, altbit, regval;
const u8 map[] = {
static const u8 map[] = {
0x01, 0x02, 0x04, 0x1f, 0x00, 0x06, 0x07, 0x10,
0x08, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f
};
......@@ -533,7 +533,7 @@ static ssize_t store_pwm_ac(struct device *dev,
SETUP_STORE_DATA_PARAM(dev, attr);
unsigned long reqval;
u8 currval, config, altbit, newval;
const u16 map[] = {
static const u16 map[] = {
0x04, 0x00, 0x01, 0xff, 0x02, 0xff, 0x05, 0x06,
0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f,
0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
......
......@@ -20,6 +20,7 @@
#include <linux/platform_device.h>
#include <linux/sysfs.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
/* ASPEED PWM & FAN Tach Register Definition */
#define ASPEED_PTCR_CTRL 0x00
......@@ -166,6 +167,18 @@
/* How long we sleep in us while waiting for an RPM result. */
#define ASPEED_RPM_STATUS_SLEEP_USEC 500
#define MAX_CDEV_NAME_LEN 16
struct aspeed_cooling_device {
char name[16];
struct aspeed_pwm_tacho_data *priv;
struct thermal_cooling_device *tcdev;
int pwm_port;
u8 *cooling_levels;
u8 max_state;
u8 cur_state;
};
struct aspeed_pwm_tacho_data {
struct regmap *regmap;
unsigned long clk_freq;
......@@ -180,6 +193,7 @@ struct aspeed_pwm_tacho_data {
u8 pwm_port_type[8];
u8 pwm_port_fan_ctrl[8];
u8 fan_tach_ch_source[16];
struct aspeed_cooling_device *cdev[8];
const struct attribute_group *groups[3];
};
......@@ -765,6 +779,94 @@ static void aspeed_create_fan_tach_channel(struct aspeed_pwm_tacho_data *priv,
}
}
static int
aspeed_pwm_cz_get_max_state(struct thermal_cooling_device *tcdev,
unsigned long *state)
{
struct aspeed_cooling_device *cdev = tcdev->devdata;
*state = cdev->max_state;
return 0;
}
static int
aspeed_pwm_cz_get_cur_state(struct thermal_cooling_device *tcdev,
unsigned long *state)
{
struct aspeed_cooling_device *cdev = tcdev->devdata;
*state = cdev->cur_state;
return 0;
}
static int
aspeed_pwm_cz_set_cur_state(struct thermal_cooling_device *tcdev,
unsigned long state)
{
struct aspeed_cooling_device *cdev = tcdev->devdata;
if (state > cdev->max_state)
return -EINVAL;
cdev->cur_state = state;
cdev->priv->pwm_port_fan_ctrl[cdev->pwm_port] =
cdev->cooling_levels[cdev->cur_state];
aspeed_set_pwm_port_fan_ctrl(cdev->priv, cdev->pwm_port,
cdev->cooling_levels[cdev->cur_state]);
return 0;
}
static const struct thermal_cooling_device_ops aspeed_pwm_cool_ops = {
.get_max_state = aspeed_pwm_cz_get_max_state,
.get_cur_state = aspeed_pwm_cz_get_cur_state,
.set_cur_state = aspeed_pwm_cz_set_cur_state,
};
static int aspeed_create_pwm_cooling(struct device *dev,
struct device_node *child,
struct aspeed_pwm_tacho_data *priv,
u32 pwm_port, u8 num_levels)
{
int ret;
struct aspeed_cooling_device *cdev;
cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
if (!cdev)
return -ENOMEM;
cdev->cooling_levels = devm_kzalloc(dev, num_levels, GFP_KERNEL);
if (!cdev->cooling_levels)
return -ENOMEM;
cdev->max_state = num_levels - 1;
ret = of_property_read_u8_array(child, "cooling-levels",
cdev->cooling_levels,
num_levels);
if (ret) {
dev_err(dev, "Property 'cooling-levels' cannot be read.\n");
return ret;
}
snprintf(cdev->name, MAX_CDEV_NAME_LEN, "%s%d", child->name, pwm_port);
cdev->tcdev = thermal_of_cooling_device_register(child,
cdev->name,
cdev,
&aspeed_pwm_cool_ops);
if (IS_ERR(cdev->tcdev))
return PTR_ERR(cdev->tcdev);
cdev->priv = priv;
cdev->pwm_port = pwm_port;
priv->cdev[pwm_port] = cdev;
return 0;
}
static int aspeed_create_fan(struct device *dev,
struct device_node *child,
struct aspeed_pwm_tacho_data *priv)
......@@ -778,6 +880,15 @@ static int aspeed_create_fan(struct device *dev,
return ret;
aspeed_create_pwm_port(priv, (u8)pwm_port);
ret = of_property_count_u8_elems(child, "cooling-levels");
if (ret > 0) {
ret = aspeed_create_pwm_cooling(dev, child, priv, pwm_port,
ret);
if (ret)
return ret;
}
count = of_property_count_u8_elems(child, "aspeed,fan-tach-ch");
if (count < 1)
return -EINVAL;
......@@ -834,9 +945,10 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
for_each_child_of_node(np, child) {
ret = aspeed_create_fan(dev, child, priv);
of_node_put(child);
if (ret)
if (ret) {
of_node_put(child);
return ret;
}
}
priv->groups[0] = &pwm_dev_group;
......
This diff is collapsed.
......@@ -60,7 +60,7 @@
static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
static struct i2c_device_id fts_id[] = {
static const struct i2c_device_id fts_id[] = {
{ "ftsteutates", 0 },
{ }
};
......@@ -435,6 +435,7 @@ clear_temp_alarm(struct device *dev, struct device_attribute *devattr,
goto error;
data->valid = false;
ret = count;
error:
mutex_unlock(&data->update_lock);
return ret;
......@@ -508,6 +509,7 @@ clear_fan_alarm(struct device *dev, struct device_attribute *devattr,
goto error;
data->valid = false;
ret = count;
error:
mutex_unlock(&data->update_lock);
return ret;
......
......@@ -85,7 +85,7 @@ static umode_t hwmon_dev_name_is_visible(struct kobject *kobj,
return attr->mode;
}
static struct attribute_group hwmon_dev_attr_group = {
static const struct attribute_group hwmon_dev_attr_group = {
.attrs = hwmon_dev_attrs,
.is_visible = hwmon_dev_name_is_visible,
};
......@@ -135,7 +135,7 @@ static int hwmon_thermal_get_temp(void *data, int *temp)
return 0;
}
static struct thermal_zone_of_device_ops hwmon_thermal_ops = {
static const struct thermal_zone_of_device_ops hwmon_thermal_ops = {
.get_temp = hwmon_thermal_get_temp,
};
......
......@@ -495,7 +495,7 @@ static struct {
};
#ifdef MODULE
static struct pci_device_id i5k_amb_ids[] = {
static const struct pci_device_id i5k_amb_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR) },
{ 0, }
......
This diff is collapsed.
......@@ -72,6 +72,8 @@ static const unsigned short normal_i2c[] = {
#define NXP_MANID 0x1131 /* NXP Semiconductors */
#define ONS_MANID 0x1b09 /* ON Semiconductor */
#define STM_MANID 0x104a /* ST Microelectronics */
#define GT_MANID 0x1c68 /* Giantec */
#define GT_MANID2 0x132d /* Giantec, 2nd mfg ID */
/* Supported chips */
......@@ -86,6 +88,13 @@ static const unsigned short normal_i2c[] = {
#define AT30TSE004_DEVID 0x2200
#define AT30TSE004_DEVID_MASK 0xffff
/* Giantec */
#define GT30TS00_DEVID 0x2200
#define GT30TS00_DEVID_MASK 0xff00
#define GT34TS02_DEVID 0x3300
#define GT34TS02_DEVID_MASK 0xff00
/* IDT */
#define TSE2004_DEVID 0x2200
#define TSE2004_DEVID_MASK 0xff00
......@@ -130,6 +139,12 @@ static const unsigned short normal_i2c[] = {
#define CAT6095_DEVID 0x0800 /* Also matches CAT34TS02 */
#define CAT6095_DEVID_MASK 0xffe0
#define CAT34TS02C_DEVID 0x0a00
#define CAT34TS02C_DEVID_MASK 0xfff0
#define CAT34TS04_DEVID 0x2200
#define CAT34TS04_DEVID_MASK 0xfff0
/* ST Microelectronics */
#define STTS424_DEVID 0x0101
#define STTS424_DEVID_MASK 0xffff
......@@ -158,6 +173,8 @@ static struct jc42_chips jc42_chips[] = {
{ ADT_MANID, ADT7408_DEVID, ADT7408_DEVID_MASK },
{ ATMEL_MANID, AT30TS00_DEVID, AT30TS00_DEVID_MASK },
{ ATMEL_MANID2, AT30TSE004_DEVID, AT30TSE004_DEVID_MASK },
{ GT_MANID, GT30TS00_DEVID, GT30TS00_DEVID_MASK },
{ GT_MANID2, GT34TS02_DEVID, GT34TS02_DEVID_MASK },
{ IDT_MANID, TSE2004_DEVID, TSE2004_DEVID_MASK },
{ IDT_MANID, TS3000_DEVID, TS3000_DEVID_MASK },
{ IDT_MANID, TS3001_DEVID, TS3001_DEVID_MASK },
......@@ -170,6 +187,8 @@ static struct jc42_chips jc42_chips[] = {
{ MCP_MANID, MCP9843_DEVID, MCP9843_DEVID_MASK },
{ NXP_MANID, SE97_DEVID, SE97_DEVID_MASK },
{ ONS_MANID, CAT6095_DEVID, CAT6095_DEVID_MASK },
{ ONS_MANID, CAT34TS02C_DEVID, CAT34TS02C_DEVID_MASK },
{ ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK },
{ NXP_MANID, SE98_DEVID, SE98_DEVID_MASK },
{ STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK },
{ STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK },
......
/* Lantiq cpu temperature sensor driver
*
* Copyright (C) 2017 Florian Eckert <fe@dev.tdt.de>
*
* 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
*
* This program is distributed in the hope that it will be useful
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>
*/
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <lantiq_soc.h>
/* gphy1 configuration register contains cpu temperature */
#define CGU_GPHY1_CR 0x0040
#define CGU_TEMP_PD BIT(19)
static void ltq_cputemp_enable(void)
{
ltq_cgu_w32(ltq_cgu_r32(CGU_GPHY1_CR) | CGU_TEMP_PD, CGU_GPHY1_CR);
}
static void ltq_cputemp_disable(void *data)
{
ltq_cgu_w32(ltq_cgu_r32(CGU_GPHY1_CR) & ~CGU_TEMP_PD, CGU_GPHY1_CR);
}
static int ltq_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *temp)
{
int value;
switch (attr) {
case hwmon_temp_input:
/* get the temperature including one decimal place */
value = (ltq_cgu_r32(CGU_GPHY1_CR) >> 9) & 0x01FF;
value = value * 5;
/* range -38 to +154 °C, register value zero is -38.0 °C */
value -= 380;
/* scale temp to millidegree */
value = value * 100;
break;
default:
return -EOPNOTSUPP;
}
*temp = value;
return 0;
}
static umode_t ltq_is_visible(const void *_data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type != hwmon_temp)
return 0;
switch (attr) {
case hwmon_temp_input:
return 0444;
default:
return 0;
}
}
static const u32 ltq_chip_config[] = {
HWMON_C_REGISTER_TZ,
0
};
static const struct hwmon_channel_info ltq_chip = {
.type = hwmon_chip,
.config = ltq_chip_config,
};
static const u32 ltq_temp_config[] = {
HWMON_T_INPUT,
0
};
static const struct hwmon_channel_info ltq_temp = {
.type = hwmon_temp,
.config = ltq_temp_config,
};
static const struct hwmon_channel_info *ltq_info[] = {
&ltq_chip,
&ltq_temp,
NULL
};
static const struct hwmon_ops ltq_hwmon_ops = {
.is_visible = ltq_is_visible,
.read = ltq_read,
};
static const struct hwmon_chip_info ltq_chip_info = {
.ops = &ltq_hwmon_ops,
.info = ltq_info,
};
static int ltq_cputemp_probe(struct platform_device *pdev)
{
struct device *hwmon_dev;
int err = 0;
/* available on vr9 v1.2 SoCs only */
if (ltq_soc_type() != SOC_TYPE_VR9_2)
return -ENODEV;
err = devm_add_action(&pdev->dev, ltq_cputemp_disable, NULL);
if (err)
return err;
ltq_cputemp_enable();
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
"ltq_cputemp",
NULL,
&ltq_chip_info,
NULL);
if (IS_ERR(hwmon_dev)) {
dev_err(&pdev->dev, "Failed to register as hwmon device");
return PTR_ERR(hwmon_dev);
}
return 0;
}
const struct of_device_id ltq_cputemp_match[] = {
{ .compatible = "lantiq,cputemp" },
{},
};
MODULE_DEVICE_TABLE(of, ltq_cputemp_match);
static struct platform_driver ltq_cputemp_driver = {
.probe = ltq_cputemp_probe,
.driver = {
.name = "ltq-cputemp",
.of_match_table = ltq_cputemp_match,
},
};
module_platform_driver(ltq_cputemp_driver);
MODULE_AUTHOR("Florian Eckert <fe@dev.tdt.de>");
MODULE_DESCRIPTION("Lantiq cpu temperature sensor driver");
MODULE_LICENSE("GPL");
......@@ -704,7 +704,7 @@ static umode_t nct7802_temp_is_visible(struct kobject *kobj,
return attr->mode;
}
static struct attribute_group nct7802_temp_group = {
static const struct attribute_group nct7802_temp_group = {
.attrs = nct7802_temp_attrs,
.is_visible = nct7802_temp_is_visible,
};
......@@ -802,7 +802,7 @@ static umode_t nct7802_in_is_visible(struct kobject *kobj,
return attr->mode;
}
static struct attribute_group nct7802_in_group = {
static const struct attribute_group nct7802_in_group = {
.attrs = nct7802_in_attrs,
.is_visible = nct7802_in_is_visible,
};
......@@ -880,7 +880,7 @@ static umode_t nct7802_fan_is_visible(struct kobject *kobj,
return attr->mode;
}
static struct attribute_group nct7802_fan_group = {
static const struct attribute_group nct7802_fan_group = {
.attrs = nct7802_fan_attrs,
.is_visible = nct7802_fan_is_visible,
};
......@@ -898,7 +898,7 @@ static struct attribute *nct7802_pwm_attrs[] = {
NULL
};
static struct attribute_group nct7802_pwm_group = {
static const struct attribute_group nct7802_pwm_group = {
.attrs = nct7802_pwm_attrs,
};
......@@ -1011,7 +1011,7 @@ static struct attribute *nct7802_auto_point_attrs[] = {
NULL
};
static struct attribute_group nct7802_auto_point_group = {
static const struct attribute_group nct7802_auto_point_group = {
.attrs = nct7802_auto_point_attrs,
};
......
......@@ -37,6 +37,15 @@ config SENSORS_ADM1275
This driver can also be built as a module. If so, the module will
be called adm1275.
config SENSORS_IBM_CFFPS
tristate "IBM Common Form Factor Power Supply"
help
If you say yes here you get hardware monitoring support for the IBM
Common Form Factor power supply.
This driver can also be built as a module. If so, the module will
be called ibm-cffps.
config SENSORS_IR35221
tristate "Infineon IR35221"
default n
......@@ -135,6 +144,15 @@ config SENSORS_TPS40422
This driver can also be built as a module. If so, the module will
be called tps40422.
config SENSORS_TPS53679
tristate "TI TPS53679"
help
If you say yes here you get hardware monitoring support for TI
TPS53679.
This driver can also be built as a module. If so, the module will
be called tps53679.
config SENSORS_UCD9000
tristate "TI UCD90120, UCD90124, UCD90160, UCD9090, UCD90910"
default n
......
......@@ -5,6 +5,7 @@
obj-$(CONFIG_PMBUS) += pmbus_core.o
obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o
obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
obj-$(CONFIG_SENSORS_IR35221) += ir35221.o
obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
......@@ -14,6 +15,7 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o
obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
/*
* Copyright 2017 IBM Corp.
*
* 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 <linux/bitops.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include "pmbus.h"
/* STATUS_MFR_SPECIFIC bits */
#define CFFPS_MFR_FAN_FAULT BIT(0)
#define CFFPS_MFR_THERMAL_FAULT BIT(1)
#define CFFPS_MFR_OV_FAULT BIT(2)
#define CFFPS_MFR_UV_FAULT BIT(3)
#define CFFPS_MFR_PS_KILL BIT(4)
#define CFFPS_MFR_OC_FAULT BIT(5)
#define CFFPS_MFR_VAUX_FAULT BIT(6)
#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7)
static int ibm_cffps_read_byte_data(struct i2c_client *client, int page,
int reg)
{
int rc, mfr;
switch (reg) {
case PMBUS_STATUS_VOUT:
case PMBUS_STATUS_IOUT:
case PMBUS_STATUS_TEMPERATURE:
case PMBUS_STATUS_FAN_12:
rc = pmbus_read_byte_data(client, page, reg);
if (rc < 0)
return rc;
mfr = pmbus_read_byte_data(client, page,
PMBUS_STATUS_MFR_SPECIFIC);
if (mfr < 0)
/*
* Return the status register instead of an error,
* since we successfully read status.
*/
return rc;
/* Add MFR_SPECIFIC bits to the standard pmbus status regs. */
if (reg == PMBUS_STATUS_FAN_12) {
if (mfr & CFFPS_MFR_FAN_FAULT)
rc |= PB_FAN_FAN1_FAULT;
} else if (reg == PMBUS_STATUS_TEMPERATURE) {
if (mfr & CFFPS_MFR_THERMAL_FAULT)
rc |= PB_TEMP_OT_FAULT;
} else if (reg == PMBUS_STATUS_VOUT) {
if (mfr & (CFFPS_MFR_OV_FAULT | CFFPS_MFR_VAUX_FAULT))
rc |= PB_VOLTAGE_OV_FAULT;
if (mfr & CFFPS_MFR_UV_FAULT)
rc |= PB_VOLTAGE_UV_FAULT;
} else if (reg == PMBUS_STATUS_IOUT) {
if (mfr & CFFPS_MFR_OC_FAULT)
rc |= PB_IOUT_OC_FAULT;
if (mfr & CFFPS_MFR_CURRENT_SHARE_WARNING)
rc |= PB_CURRENT_SHARE_FAULT;
}
break;
default:
rc = -ENODATA;
break;
}
return rc;
}
static int ibm_cffps_read_word_data(struct i2c_client *client, int page,
int reg)
{
int rc, mfr;
switch (reg) {
case PMBUS_STATUS_WORD:
rc = pmbus_read_word_data(client, page, reg);
if (rc < 0)
return rc;
mfr = pmbus_read_byte_data(client, page,
PMBUS_STATUS_MFR_SPECIFIC);
if (mfr < 0)
/*
* Return the status register instead of an error,
* since we successfully read status.
*/
return rc;
if (mfr & CFFPS_MFR_PS_KILL)
rc |= PB_STATUS_OFF;
break;
default:
rc = -ENODATA;
break;
}
return 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 | PMBUS_HAVE_TEMP |
PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT |
PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_FAN12,
.read_byte_data = ibm_cffps_read_byte_data,
.read_word_data = ibm_cffps_read_word_data,
};
static int ibm_cffps_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
return pmbus_do_probe(client, id, &ibm_cffps_info);
}
static const struct i2c_device_id ibm_cffps_id[] = {
{ "ibm_cffps1", 1 },
{}
};
MODULE_DEVICE_TABLE(i2c, ibm_cffps_id);
static const struct of_device_id ibm_cffps_of_match[] = {
{ .compatible = "ibm,cffps1" },
{}
};
MODULE_DEVICE_TABLE(of, ibm_cffps_of_match);
static struct i2c_driver ibm_cffps_driver = {
.driver = {
.name = "ibm-cffps",
.of_match_table = ibm_cffps_of_match,
},
.probe = ibm_cffps_probe,
.remove = pmbus_do_remove,
.id_table = ibm_cffps_id,
};
module_i2c_driver(ibm_cffps_driver);
MODULE_AUTHOR("Eddie James");
MODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies");
MODULE_LICENSE("GPL");
......@@ -28,7 +28,7 @@
#include <linux/i2c.h>
#include "pmbus.h"
enum chips { lm25056, lm25063, lm25066, lm5064, lm5066 };
enum chips { lm25056, lm25063, lm25066, lm5064, lm5066, lm5066i };
#define LM25066_READ_VAUX 0xd0
#define LM25066_MFR_READ_IIN 0xd1
......@@ -65,7 +65,7 @@ struct __coeff {
#define PSC_CURRENT_IN_L (PSC_NUM_CLASSES)
#define PSC_POWER_L (PSC_NUM_CLASSES + 1)
static struct __coeff lm25066_coeff[5][PSC_NUM_CLASSES + 2] = {
static struct __coeff lm25066_coeff[6][PSC_NUM_CLASSES + 2] = {
[lm25056] = {
[PSC_VOLTAGE_IN] = {
.m = 16296,
......@@ -210,6 +210,41 @@ static struct __coeff lm25066_coeff[5][PSC_NUM_CLASSES + 2] = {
.m = 16,
},
},
[lm5066i] = {
[PSC_VOLTAGE_IN] = {
.m = 4617,
.b = -140,
.R = -2,
},
[PSC_VOLTAGE_OUT] = {
.m = 4602,
.b = 500,
.R = -2,
},
[PSC_CURRENT_IN] = {
.m = 15076,
.b = -504,
.R = -2,
},
[PSC_CURRENT_IN_L] = {
.m = 7645,
.b = 100,
.R = -2,
},
[PSC_POWER] = {
.m = 1701,
.b = -4000,
.R = -3,
},
[PSC_POWER_L] = {
.m = 861,
.b = -965,
.R = -3,
},
[PSC_TEMPERATURE] = {
.m = 16,
},
},
};
struct lm25066_data {
......@@ -250,6 +285,7 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg)
ret = DIV_ROUND_CLOSEST(ret * 70, 453);
break;
case lm5066:
case lm5066i:
/* VIN: 2.18 mV VAUX: 725 uV LSB */
ret = DIV_ROUND_CLOSEST(ret * 725, 2180);
break;
......@@ -488,16 +524,18 @@ static int lm25066_probe(struct i2c_client *client,
info->m[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].m;
info->b[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].b;
info->R[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].R;
info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].b;
info->R[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].R;
info->b[PSC_POWER] = coeff[PSC_POWER].b;
info->R[PSC_POWER] = coeff[PSC_POWER].R;
if (config & LM25066_DEV_SETUP_CL) {
info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].m;
info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].b;
info->m[PSC_POWER] = coeff[PSC_POWER_L].m;
info->b[PSC_POWER] = coeff[PSC_POWER_L].b;
} else {
info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].m;
info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].b;
info->m[PSC_POWER] = coeff[PSC_POWER].m;
info->b[PSC_POWER] = coeff[PSC_POWER].b;
}
return pmbus_do_probe(client, id, info);
......@@ -509,6 +547,7 @@ static const struct i2c_device_id lm25066_id[] = {
{"lm25066", lm25066},
{"lm5064", lm5064},
{"lm5066", lm5066},
{"lm5066i", lm5066i},
{ }
};
......
......@@ -341,7 +341,7 @@ enum pmbus_sensor_classes {
#define PMBUS_HAVE_STATUS_VMON BIT(19)
enum pmbus_data_format { linear = 0, direct, vid };
enum vrm_version { vr11 = 0, vr12 };
enum vrm_version { vr11 = 0, vr12, vr13 };
struct pmbus_driver_info {
int pages; /* Total number of pages */
......
This diff is collapsed.
/*
* Hardware monitoring driver for Texas Instruments TPS53679
*
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Vadim Pasternak <vadimp@mellanox.com>
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "pmbus.h"
#define TPS53679_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */
#define TPS53679_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */
#define TPS53679_PROT_VR13_10MV 0x04 /* VR13.0 mode, 10-mV DAC */
#define TPS53679_PROT_IMVP8_5MV 0x05 /* IMVP8 mode, 5-mV DAC */
#define TPS53679_PROT_VR13_5MV 0x07 /* VR13.0 mode, 5-mV DAC */
#define TPS53679_PAGE_NUM 2
static int tps53679_identify(struct i2c_client *client,
struct pmbus_driver_info *info)
{
u8 vout_params;
int ret;
/* Read the register with VOUT scaling value.*/
ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
if (ret < 0)
return ret;
vout_params = ret & GENMASK(4, 0);
switch (vout_params) {
case TPS53679_PROT_VR13_10MV:
case TPS53679_PROT_VR12_5_10MV:
info->vrm_version = vr13;
break;
case TPS53679_PROT_VR13_5MV:
case TPS53679_PROT_VR12_5MV:
case TPS53679_PROT_IMVP8_5MV:
info->vrm_version = vr12;
break;
default:
return -EINVAL;
}
return 0;
}
static struct pmbus_driver_info tps53679_info = {
.pages = TPS53679_PAGE_NUM,
.format[PSC_VOLTAGE_IN] = linear,
.format[PSC_VOLTAGE_OUT] = vid,
.format[PSC_TEMPERATURE] = linear,
.format[PSC_CURRENT_OUT] = linear,
.format[PSC_POWER] = linear,
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
PMBUS_HAVE_POUT,
.func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
PMBUS_HAVE_POUT,
.identify = tps53679_identify,
};
static int tps53679_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
return pmbus_do_probe(client, id, &tps53679_info);
}
static const struct i2c_device_id tps53679_id[] = {
{"tps53679", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, tps53679_id);
static const struct of_device_id tps53679_of_match[] = {
{.compatible = "ti,tps53679"},
{}
};
MODULE_DEVICE_TABLE(of, tps53679_of_match);
static struct i2c_driver tps53679_driver = {
.driver = {
.name = "tps53679",
.of_match_table = of_match_ptr(tps53679_of_match),
},
.probe = tps53679_probe,
.remove = pmbus_do_remove,
.id_table = tps53679_id,
};
module_i2c_driver(tps53679_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
MODULE_DESCRIPTION("PMBus driver for Texas Instruments TPS53679");
MODULE_LICENSE("GPL");
......@@ -120,7 +120,7 @@ scpi_show_label(struct device *dev, struct device_attribute *attr, char *buf)
return sprintf(buf, "%s\n", sensor->info.name);
}
static struct thermal_zone_of_device_ops scpi_sensor_ops = {
static const struct thermal_zone_of_device_ops scpi_sensor_ops = {
.get_temp = scpi_read_temp,
};
......
......@@ -718,6 +718,10 @@ static int stts751_read_chip_config(struct stts751_priv *priv)
ret = i2c_smbus_read_byte_data(priv->client, STTS751_REG_RATE);
if (ret < 0)
return ret;
if (ret >= ARRAY_SIZE(stts751_intervals)) {
dev_err(priv->dev, "Unrecognized conversion rate 0x%x\n", ret);
return -ENODEV;
}
priv->interval = ret;
ret = stts751_read_reg16(priv, &priv->event_max,
......
......@@ -18,6 +18,7 @@
#include <linux/mfd/core.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/mfd/da9052/da9052.h>
#include <linux/mfd/da9052/pdata.h>
......@@ -518,9 +519,6 @@ static const struct mfd_cell da9052_subdev_info[] = {
{
.name = "da9052-wled3",
},
{
.name = "da9052-tsi",
},
{
.name = "da9052-bat",
},
......@@ -529,6 +527,10 @@ static const struct mfd_cell da9052_subdev_info[] = {
},
};
static const struct mfd_cell da9052_tsi_subdev_info[] = {
{ .name = "da9052-tsi" },
};
const struct regmap_config da9052_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
......@@ -619,9 +621,27 @@ int da9052_device_init(struct da9052 *da9052, u8 chip_id)
goto err;
}
/*
* Check if touchscreen pins are used are analogue input instead
* of having a touchscreen connected to them. The analogue input
* functionality will be provided by hwmon driver (if enabled).
*/
if (!device_property_read_bool(da9052->dev, "dlg,tsi-as-adc")) {
ret = mfd_add_devices(da9052->dev, PLATFORM_DEVID_AUTO,
da9052_tsi_subdev_info,
ARRAY_SIZE(da9052_tsi_subdev_info),
NULL, 0, NULL);
if (ret) {
dev_err(da9052->dev, "failed to add TSI subdev: %d\n",
ret);
goto err;
}
}
return 0;
err:
mfd_remove_devices(da9052->dev);
da9052_irq_exit(da9052);
return ret;
......
......@@ -45,6 +45,12 @@
#define DA9052_ADC_TJUNC 8
#define DA9052_ADC_VBBAT 9
/* TSI channel has its own 4 channel mux */
#define DA9052_ADC_TSI_XP 70
#define DA9052_ADC_TSI_XN 71
#define DA9052_ADC_TSI_YP 72
#define DA9052_ADC_TSI_YN 73
#define DA9052_IRQ_DCIN 0
#define DA9052_IRQ_VBUS 1
#define DA9052_IRQ_DCINREM 2
......
......@@ -690,7 +690,10 @@
/* TSI CONTROL REGISTER B BITS */
#define DA9052_TSICONTB_ADCREF 0X80
#define DA9052_TSICONTB_TSIMAN 0X40
#define DA9052_TSICONTB_TSIMUX 0X30
#define DA9052_TSICONTB_TSIMUX_XP 0X00
#define DA9052_TSICONTB_TSIMUX_YP 0X10
#define DA9052_TSICONTB_TSIMUX_XN 0X20
#define DA9052_TSICONTB_TSIMUX_YN 0X30
#define DA9052_TSICONTB_TSISEL3 0X08
#define DA9052_TSICONTB_TSISEL2 0X04
#define DA9052_TSICONTB_TSISEL1 0X02
......@@ -705,8 +708,14 @@
/* TSI CO-ORDINATE LSB RESULT REGISTER BITS */
#define DA9052_TSILSB_PENDOWN 0X40
#define DA9052_TSILSB_TSIZL 0X30
#define DA9052_TSILSB_TSIZL_SHIFT 4
#define DA9052_TSILSB_TSIZL_BITS 2
#define DA9052_TSILSB_TSIYL 0X0C
#define DA9052_TSILSB_TSIYL_SHIFT 2
#define DA9052_TSILSB_TSIYL_BITS 2
#define DA9052_TSILSB_TSIXL 0X03
#define DA9052_TSILSB_TSIXL_SHIFT 0
#define DA9052_TSILSB_TSIXL_BITS 2
/* TSI Z MEASUREMENT MSB RESULT REGISTER BIT */
#define DA9052_TSIZMSB_TSIZM 0XFF
......
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