// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2019 CERN * Author: Federico Vaga <federico.vaga@cern.ch> */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/memory.h> #include <linux/fmc.h> #include <linux/ipmi/fru.h> #include "fmc-internal.h" #include "fmc-compat.h" #define FRU_EEPROM_NAME "fru_eeprom" /** * Default EEPROM type according to the standard. */ #define FMC_EEPROM_TYPE_DEFAULT "24c02" /** * Setup function for the AT24C02 EEPROM. What we need to do here is to * quickly validate the EEPROM content. The EEPROM should contain a valid * FRU. */ static void fmc_slot_eeprom_setup(struct memory_accessor *macc, void *context) { struct fmc_slot *slot = context; slot->macc = macc; } /** * Default configuration for AT24C02 EEPROM type */ static const struct at24_platform_data at24_24c02 = { .byte_len = 256, .page_size = 8, .flags = 0, .setup = fmc_slot_eeprom_setup, }; /** * Initialize I2C EEPROM info with standad values */ static void fmc_slot_eeprom_init(struct fmc_slot *slot, struct i2c_board_info *info, const char *name) { strncpy(info->type, name, I2C_NAME_SIZE); info->addr = FMC_EEPROM_ADDR_SPACE; info->platform_data = &slot->at24_data; } static void fmc_slot_eeprom_init_default(struct fmc_slot *slot, struct i2c_board_info *info) { memset(info, 0, sizeof(*info)); fmc_slot_eeprom_init(slot, info, FMC_EEPROM_TYPE_DEFAULT); memcpy(&slot->at24_data, &at24_24c02, sizeof(slot->at24_data)); slot->at24_data.context = slot; } /** * Read from EEPROM */ ssize_t fmc_slot_eeprom_read(struct fmc_slot *slot, void *buf, off_t offset, size_t count) { /* * TODO if we export this function, do we have to lock it when we * use it? Think about it */ if (!slot->macc || !slot->macc->read) return -ENODEV; return slot->macc->read(slot->macc, buf, offset, count); } EXPORT_SYMBOL(fmc_slot_eeprom_read); /** * Add EEPROM which will be associated to the given FMC slot * @slot: FMC slot instance * @info: I2C EEPROM information * * This creates also a symlink to the EEPROM device in the FMC slot sysfs * directory. If this fails it will be reported on dmesg but this does not * prevent the system from running; for this reason the function will * succeed anyway. * * This should be used only by the FMC framework itself. But, this is also * exported to other users in order to be able to support corner cases * where the EEPROM type is not supported by the AT24 driver (in which case * the sysfs attribute eeprom_type can be easily used). * * Return: 0 on success, otherwise a negative error number */ static int __fmc_slot_eeprom_add(struct fmc_slot *slot, struct i2c_board_info *info) { struct i2c_board_info info_l = *info; int err; if (!fmc_slot_present(slot)) return -ENODEV; if (!slot->adapter) { dev_err(&slot->dev, "missing I2C adapter\n"); return -ENODEV; } info_l.addr = fmc_slot_i2c_address(info_l.addr, slot->ga); slot->eeprom = i2c_new_device(slot->adapter, &info_l); if (!slot->eeprom) return -ENODEV; err = sysfs_create_link(&slot->dev.kobj, &slot->eeprom->dev.kobj, FRU_EEPROM_NAME); if (err) dev_err(&slot->dev, "Failed to create eeprom symlink to %s\n", dev_name(&slot->eeprom->dev)); return 0; } /** * Add EEPROM to a given FMC slot * @slot: FMC slot instance * * Return: 0 on success, otherwise a negative error number */ int fmc_slot_eeprom_add(struct fmc_slot *slot) { struct i2c_board_info i2c_info; fmc_slot_eeprom_init_default(slot, &i2c_info); return __fmc_slot_eeprom_add(slot, &i2c_info); } /** * Remove EEPROM associated to the given FMC slot * @slot: FMC slot instance * * This should be used only by the FMC framework itself. But, this is also * exported to other users in order to be able to support corner cases * where the EEPROM type is not supported by the AT24 driver (in which case * the sysfs attribute eeprom_type can be easily used). */ void fmc_slot_eeprom_del(struct fmc_slot *slot) { if (!slot || !slot->eeprom) return; sysfs_remove_link(&slot->dev.kobj, FRU_EEPROM_NAME); i2c_unregister_device(slot->eeprom); slot->eeprom = NULL; slot->macc = NULL; } /** * Replace current EEPROM instance with a given one * @slot: FMC slot instance * @info: I2C EEPROM information * * Return: 0 on success, otherwise a negative error number */ static int fmc_slot_eeprom_replace(struct fmc_slot *slot, struct i2c_board_info *info) { fmc_slot_eeprom_del(slot); return __fmc_slot_eeprom_add(slot, info); } int fmc_slot_eeprom_type_set(struct fmc_slot *slot, const char *type) { #define FMC_EEPROM_SIZE_OFF 3 struct i2c_board_info i2c_info; unsigned int len; int ret; if (strncmp(type, "24c", 3)) { if (strncmp(type, "at24c", 5)) { dev_err(&slot->dev, "Invalid EEPROM type. Expected 'at24cXX' or '24cXX' (got %s)\n", type); return -EINVAL; } type += 2; /* skip 'at' */ } ret = kstrtouint(type + FMC_EEPROM_SIZE_OFF, 10, &len); if (ret < 0) { dev_err(&slot->dev, "Failed to get EEPROM size %d ('%s' %d)\n", ret, type + FMC_EEPROM_SIZE_OFF, len); return -EINVAL; } memset(&i2c_info, 0, sizeof(i2c_info)); memset(&slot->at24_data, 0, sizeof(slot->at24_data)); len = (len * 1024) / 8; /* * For sizes between 1K and 16K the EEPROM uses part of the device * address as internal memory address */ if (len > 4096) /* 32K 4KiB */ slot->at24_data.flags = AT24_FLAG_ADDR16; else if (len > 131072) /* 1024K 128KiB */ return -EINVAL; fmc_slot_eeprom_init(slot, &i2c_info, type); slot->at24_data.byte_len = len; slot->at24_data.page_size = 1; /* 1Byte page to play safe */ slot->at24_data.setup = fmc_slot_eeprom_setup; slot->at24_data.context = slot; dev_dbg(&slot->dev, "%s 0x%x %d %d 0x%x\n", i2c_info.type, i2c_info.addr, slot->at24_data.byte_len, slot->at24_data.page_size, slot->at24_data.flags); return fmc_slot_eeprom_replace(slot, &i2c_info); } EXPORT_SYMBOL(fmc_slot_eeprom_type_set); /** * Check if an FMC mezzanine in the FMC slot has a valid FRU * @slot: FMC slot to verify * * Return: 1 if the slot is present, otherwise 0 */ int fmc_slot_fru_valid(struct fmc_slot *slot) { struct fru_common_header fru_h; ssize_t ret; ret = fmc_slot_eeprom_read(slot, (void *)&fru_h, 0x0, sizeof(struct fru_common_header)); if (ret != sizeof(struct fru_common_header)) { dev_err(&slot->dev, "Failed while reading mezzanine's EEPROM (ret: %zd)\n", ret); return 0; } if (!fru_header_cksum_ok(&fru_h)) { dev_warn(&slot->dev, "Invalid FRU: checksum failure\n"); return 0; } return 1; } EXPORT_SYMBOL(fmc_slot_fru_valid); static ssize_t fru_valid_show(struct device *dev, struct device_attribute *attr, char *buf) { struct fmc_slot *slot = to_fmc_slot(dev); return sprintf(buf, "%d\n", fmc_slot_fru_valid(slot)); } static DEVICE_ATTR_RO(fru_valid); const char *fmc_slot_eeprom_type_get(struct fmc_slot *slot) { return slot && slot->eeprom ? slot->eeprom->name : "none"; } EXPORT_SYMBOL(fmc_slot_eeprom_type_get); static ssize_t eeprom_type_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", fmc_slot_eeprom_type_get(to_fmc_slot(dev))); } static ssize_t eeprom_type_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct fmc_slot *slot = to_fmc_slot(dev); int ret; if (!fmc_slot_present(slot)) return -ENODEV; ret = fmc_slot_eeprom_type_set(slot, buf); return ret ? ret : count; } static DEVICE_ATTR_RW(eeprom_type); static struct attribute *fmc_slot_eeprom_attrs[] = { &dev_attr_fru_valid.attr, &dev_attr_eeprom_type.attr, NULL, }; const struct attribute_group fmc_slot_eeprom_group = { .attrs = fmc_slot_eeprom_attrs, };