# --- T2-COPYRIGHT-NOTE-BEGIN --- # T2 SDE: package/*/linux/spd-5118.patch # Copyright (C) 2023 The T2 SDE Project # # This Copyright note is generated by scripts/Create-CopyPatch, # more information can be found in the files COPYING and README. # # This patch file is dual-licensed. It is available under the license the # patched project is licensed under, as long as it is an OpenSource license # as defined at http://www.opensource.org/ (e.g. BSD, X11) or under the terms # of the GNU General Public License version 2 as used by the T2 SDE. # --- T2-COPYRIGHT-NOTE-END --- Add a JEDEC SPD-5118 compliant memory SPD hub eeprom and hwmon temperature sensor driver. Signed-off-by: René Rebe --- linux-6.2/drivers/hwmon/Makefile.vanilla 2023-03-26 20:08:00.724240065 +0200 +++ linux-6.2/drivers/hwmon/Makefile 2023-03-26 20:10:17.800237355 +0200 @@ -98,6 +98,7 @@ obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) += intel-m10-bmc-hwmon.o obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o +obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o --- linux-6.2/drivers/hwmon/Kconfig.vanilla 2023-03-26 20:08:00.729240064 +0200 +++ linux-6.2/drivers/hwmon/Kconfig 2023-03-26 20:24:02.545221056 +0200 @@ -819,6 +819,16 @@ This driver can also be built as a module. If so, the module will be called jc42. +config SENSORS_SPD5118 + tristate "JEDEC SPD5118 compliant memory SPD hub" + depends on I2C + select REGMAP_I2C + help + If you say yes here, you get support for JEDEC JC42.4 compliant + + This driver can also be built as a module. If so, the module + will be called spd5118. + config SENSORS_POWR1220 tristate "Lattice POWR1220 Power Monitoring" depends on I2C --- linux-6.6/drivers/hwmon/spd5118.c.vanilla 2023-10-26 22:07:06.816531830 +0200 +++ linux-6.6/drivers/hwmon/spd5118.c 2023-10-28 20:31:50.848798609 +0200 @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * spd5118.c - driver for Jedec 5118 compliant temperature sensors + * + * Copyright (c) 2023 René Rebe, ExactCODE GmbH; Germany. + * + * Inspired by ee1004.c and jc42.c. + * + * SPD5118 compliant temperature sensors are typically used on memory modules. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_SPD_PAGE_SIZE_SPD5 128 +#define MAX_SPD_SIZE (SPD_PAGE_LEN * SPD_SN_LEN) +#define SPD_HUB_MEMREG(addr) ((u8)(0x80 | (addr))) +#define SPD5_MR0 0x00 +#define SPD5_MR11 0x0B +#define SPD5_MEMREG_REG(addr) ((u8)((~0x80) & (addr))) +#define SPD5_MR0_SPD5_HUB_DEV 0x51 + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, I2C_CLIENT_END }; + +/* SPD5118 registers. All registers are 16 bit. */ +#define SPD5118_REG_TYPE (0x00) +#define SPD5118_REG_VENDOR (0x03) +#define SPD5118_REG_TEMP (0x31) + +#define SPD5118_NUM_PAGES 8 +#define SPD5118_PAGE_SIZE 128 +#define SPD5118_PAGE_SHIFT 7 +#define SPD5118_EEPROM_SIZE (SPD5118_PAGE_SIZE * SPD5118_NUM_PAGES) + + +/* Each client has this additional data */ +struct spd5118_data { + struct mutex update_lock; /* protect register access */ + struct regmap *regmap; + int current_page; +}; + +static long spd5118_temp_from_reg(s32 reg) +{ + reg = sign_extend32(reg >> 2, 11); + + return reg * 1000 / 4; +} + +static int spd5118_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct spd5118_data *data = dev_get_drvdata(dev); + int ret, regval; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_temp_input: + ret = regmap_read(data->regmap, SPD5118_REG_TEMP, ®val); + if (ret) + break; + + *val = spd5118_temp_from_reg(regval); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return ret; +} + +static int spd5118_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct spd5118_data *data = dev_get_drvdata(dev); + int ret; + + mutex_lock(&data->update_lock); + + switch (attr) { + default: + ret = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return ret; +} + +static umode_t spd5118_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + umode_t mode = 0444; + + switch (attr) { + case hwmon_temp_input: + break; + default: + mode = 0; + break; + } + return mode; +} + +static int spd5118_set_current_page(struct i2c_client *client, int page) +{ + struct device *dev = &client->dev; + struct spd5118_data *data = dev_get_drvdata(dev); + int ret; + + if (page == data->current_page) + return 0; + + ret = i2c_smbus_write_byte_data(client, SPD5_MEMREG_REG(SPD5_MR11), page); + if (ret < 0) { + dev_err(dev, "Failed to select page %d (%d)\n", page, ret); + return ret; + } + + dev_dbg(dev, "Selected page %d\n", page); + data->current_page = page; + + return 0; +} + +static ssize_t spd5118_eeprom_read(struct i2c_client *client, char *buf, + unsigned int offset, size_t count) +{ + int status, page; + + page = offset >> SPD5118_PAGE_SHIFT; + offset &= (1 << SPD5118_PAGE_SHIFT) - 1; + + status = spd5118_set_current_page(client, page); + if (status) + return status; + + /* Can't cross page boundaries */ + if (offset + count > SPD5118_PAGE_SIZE) + count = SPD5118_PAGE_SIZE - offset; + + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + + return i2c_smbus_read_i2c_block_data_or_emulated(client, 0x80 + offset, count, buf); +} + +static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = kobj_to_i2c_client(kobj); + struct device *dev = &client->dev; + struct spd5118_data *data = dev_get_drvdata(dev); + size_t requested = count; + int ret = 0; + + mutex_lock(&data->update_lock); + + while (count) { + ret = spd5118_eeprom_read(client, buf, off, count); + if (ret < 0) + goto out; + + buf += ret; + off += ret; + count -= ret; + } +out: + + mutex_unlock(&data->update_lock); + + return ret < 0 ? ret : requested; +} + +static BIN_ATTR_RO(eeprom, SPD5118_EEPROM_SIZE); + +static struct bin_attribute *spd5118_attrs[] = { + &bin_attr_eeprom, + NULL +}; + +BIN_ATTRIBUTE_GROUPS(spd5118); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int spd5118_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int cap, vendor; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + cap = i2c_smbus_read_byte_data(client, SPD5118_REG_TYPE); + vendor = i2c_smbus_read_byte_data(client, SPD5118_REG_VENDOR); + + if (cap <= 0 || vendor <= 0) + return -ENODEV; + + strscpy(info->type, "spd5118", I2C_NAME_SIZE); + return 0; +} + +static const struct hwmon_channel_info *spd5118_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_ops spd5118_hwmon_ops = { + .is_visible = spd5118_is_visible, + .read = spd5118_read, + .write = spd5118_write, +}; + +static const struct hwmon_chip_info spd5118_chip_info = { + .ops = &spd5118_hwmon_ops, + .info = spd5118_info, +}; + +static bool spd5118_readable_reg(struct device *dev, unsigned int reg) +{ + return (reg >= SPD5118_REG_TYPE && reg <= SPD5118_REG_TEMP + 1); +} + +static bool spd5118_writeable_reg(struct device *dev, unsigned int reg) +{ + return 0; +} + +static bool spd5118_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == SPD5118_REG_TEMP; +} + +static const struct regmap_config spd5118_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .max_register = SPD5118_REG_TEMP, + .writeable_reg = spd5118_writeable_reg, + .readable_reg = spd5118_readable_reg, + .volatile_reg = spd5118_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static int spd5118_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + unsigned int typ, cap, vendor, temp, conf; + struct spd5118_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(struct spd5118_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + + data->current_page = -1; + + ret = spd5118_set_current_page(client, 0); + + typ = i2c_smbus_read_word_swapped(client, SPD5_MEMREG_REG(SPD5_MR0)); + if (typ != 0x5118) { + return -ENODEV; + } + vendor = i2c_smbus_read_word_swapped(client, SPD5_MEMREG_REG(0x3)); + cap = i2c_smbus_read_byte_data(client, SPD5_MEMREG_REG(0x5)); + conf = i2c_smbus_read_byte_data(client, SPD5_MEMREG_REG(0x1a)); + temp = i2c_smbus_read_word_data(client, SPD5_MEMREG_REG(0x31)); + + ret = i2c_smbus_write_byte_data(client, SPD5_MEMREG_REG(SPD5_MR11), 0); + + data->regmap = devm_regmap_init_i2c(client, &spd5118_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + mutex_init(&data->update_lock); + + ret = regmap_read(data->regmap, SPD5118_REG_TYPE, &cap); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "spd5118", + data, &spd5118_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static void spd5118_remove(struct i2c_client *client) +{ +} + +static const struct i2c_device_id spd5118_id[] = { + { "spd5118", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, spd5118_id); + +#ifdef CONFIG_OF +static const struct of_device_id spd5118_of_ids[] = { + { .compatible = "jedec,spd5118", }, + { } +}; +MODULE_DEVICE_TABLE(of, spd5118_of_ids); +#endif + +static struct i2c_driver spd5118_driver = { + .class = I2C_CLASS_SPD | I2C_CLASS_HWMON, + .driver = { + .name = "spd5118", + .dev_groups = spd5118_groups, + .of_match_table = of_match_ptr(spd5118_of_ids), + }, + .probe = spd5118_probe, + .remove = spd5118_remove, + .id_table = spd5118_id, + .detect = spd5118_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(spd5118_driver); + +MODULE_AUTHOR("René Rebe "); +MODULE_DESCRIPTION("SPD 5118 driver"); +MODULE_LICENSE("GPL");