18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ee1004 - driver for DDR4 SPD EEPROMs 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2017-2019 Jean Delvare 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Based on the at24 driver: 88c2ecf20Sopenharmony_ci * Copyright (C) 2005-2007 David Brownell 98c2ecf20Sopenharmony_ci * Copyright (C) 2008 Wolfram Sang, Pengutronix 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/i2c.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/mutex.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* 208c2ecf20Sopenharmony_ci * DDR4 memory modules use special EEPROMs following the Jedec EE1004 218c2ecf20Sopenharmony_ci * specification. These are 512-byte EEPROMs using a single I2C address 228c2ecf20Sopenharmony_ci * in the 0x50-0x57 range for data. One of two 256-byte page is selected 238c2ecf20Sopenharmony_ci * by writing a command to I2C address 0x36 or 0x37 on the same I2C bus. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * Therefore we need to request these 2 additional addresses, and serialize 268c2ecf20Sopenharmony_ci * access to all such EEPROMs with a single mutex. 278c2ecf20Sopenharmony_ci * 288c2ecf20Sopenharmony_ci * We assume it is safe to read up to 32 bytes at once from these EEPROMs. 298c2ecf20Sopenharmony_ci * We use SMBus access even if I2C is available, these EEPROMs are small 308c2ecf20Sopenharmony_ci * enough, and reading from them infrequent enough, that we favor simplicity 318c2ecf20Sopenharmony_ci * over performance. 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define EE1004_ADDR_SET_PAGE 0x36 358c2ecf20Sopenharmony_ci#define EE1004_EEPROM_SIZE 512 368c2ecf20Sopenharmony_ci#define EE1004_PAGE_SIZE 256 378c2ecf20Sopenharmony_ci#define EE1004_PAGE_SHIFT 8 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci/* 408c2ecf20Sopenharmony_ci * Mutex protects ee1004_set_page and ee1004_dev_count, and must be held 418c2ecf20Sopenharmony_ci * from page selection to end of read. 428c2ecf20Sopenharmony_ci */ 438c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(ee1004_bus_lock); 448c2ecf20Sopenharmony_cistatic struct i2c_client *ee1004_set_page[2]; 458c2ecf20Sopenharmony_cistatic unsigned int ee1004_dev_count; 468c2ecf20Sopenharmony_cistatic int ee1004_current_page; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic const struct i2c_device_id ee1004_ids[] = { 498c2ecf20Sopenharmony_ci { "ee1004", 0 }, 508c2ecf20Sopenharmony_ci { } 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ee1004_ids); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/*-------------------------------------------------------------------------*/ 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic int ee1004_get_current_page(void) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci int err; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci err = i2c_smbus_read_byte(ee1004_set_page[0]); 618c2ecf20Sopenharmony_ci if (err == -ENXIO) { 628c2ecf20Sopenharmony_ci /* Nack means page 1 is selected */ 638c2ecf20Sopenharmony_ci return 1; 648c2ecf20Sopenharmony_ci } 658c2ecf20Sopenharmony_ci if (err < 0) { 668c2ecf20Sopenharmony_ci /* Anything else is a real error, bail out */ 678c2ecf20Sopenharmony_ci return err; 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci /* Ack means page 0 is selected, returned value meaningless */ 718c2ecf20Sopenharmony_ci return 0; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic ssize_t ee1004_eeprom_read(struct i2c_client *client, char *buf, 758c2ecf20Sopenharmony_ci unsigned int offset, size_t count) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci int status; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci if (count > I2C_SMBUS_BLOCK_MAX) 808c2ecf20Sopenharmony_ci count = I2C_SMBUS_BLOCK_MAX; 818c2ecf20Sopenharmony_ci /* Can't cross page boundaries */ 828c2ecf20Sopenharmony_ci if (unlikely(offset + count > EE1004_PAGE_SIZE)) 838c2ecf20Sopenharmony_ci count = EE1004_PAGE_SIZE - offset; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset, 868c2ecf20Sopenharmony_ci count, buf); 878c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "read %zu@%d --> %d\n", count, offset, status); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return status; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic ssize_t ee1004_read(struct file *filp, struct kobject *kobj, 938c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, 948c2ecf20Sopenharmony_ci char *buf, loff_t off, size_t count) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct device *dev = kobj_to_dev(kobj); 978c2ecf20Sopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 988c2ecf20Sopenharmony_ci size_t requested = count; 998c2ecf20Sopenharmony_ci int page; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci if (unlikely(!count)) 1028c2ecf20Sopenharmony_ci return count; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci page = off >> EE1004_PAGE_SHIFT; 1058c2ecf20Sopenharmony_ci if (unlikely(page > 1)) 1068c2ecf20Sopenharmony_ci return 0; 1078c2ecf20Sopenharmony_ci off &= (1 << EE1004_PAGE_SHIFT) - 1; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci /* 1108c2ecf20Sopenharmony_ci * Read data from chip, protecting against concurrent access to 1118c2ecf20Sopenharmony_ci * other EE1004 SPD EEPROMs on the same adapter. 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_ci mutex_lock(&ee1004_bus_lock); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci while (count) { 1168c2ecf20Sopenharmony_ci int status; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci /* Select page */ 1198c2ecf20Sopenharmony_ci if (page != ee1004_current_page) { 1208c2ecf20Sopenharmony_ci /* Data is ignored */ 1218c2ecf20Sopenharmony_ci status = i2c_smbus_write_byte(ee1004_set_page[page], 1228c2ecf20Sopenharmony_ci 0x00); 1238c2ecf20Sopenharmony_ci if (status == -ENXIO) { 1248c2ecf20Sopenharmony_ci /* 1258c2ecf20Sopenharmony_ci * Don't give up just yet. Some memory 1268c2ecf20Sopenharmony_ci * modules will select the page but not 1278c2ecf20Sopenharmony_ci * ack the command. Check which page is 1288c2ecf20Sopenharmony_ci * selected now. 1298c2ecf20Sopenharmony_ci */ 1308c2ecf20Sopenharmony_ci if (ee1004_get_current_page() == page) 1318c2ecf20Sopenharmony_ci status = 0; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci if (status < 0) { 1348c2ecf20Sopenharmony_ci dev_err(dev, "Failed to select page %d (%d)\n", 1358c2ecf20Sopenharmony_ci page, status); 1368c2ecf20Sopenharmony_ci mutex_unlock(&ee1004_bus_lock); 1378c2ecf20Sopenharmony_ci return status; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci dev_dbg(dev, "Selected page %d\n", page); 1408c2ecf20Sopenharmony_ci ee1004_current_page = page; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci status = ee1004_eeprom_read(client, buf, off, count); 1448c2ecf20Sopenharmony_ci if (status < 0) { 1458c2ecf20Sopenharmony_ci mutex_unlock(&ee1004_bus_lock); 1468c2ecf20Sopenharmony_ci return status; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci buf += status; 1498c2ecf20Sopenharmony_ci off += status; 1508c2ecf20Sopenharmony_ci count -= status; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (off == EE1004_PAGE_SIZE) { 1538c2ecf20Sopenharmony_ci page++; 1548c2ecf20Sopenharmony_ci off = 0; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci mutex_unlock(&ee1004_bus_lock); 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci return requested; 1618c2ecf20Sopenharmony_ci} 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic const struct bin_attribute eeprom_attr = { 1648c2ecf20Sopenharmony_ci .attr = { 1658c2ecf20Sopenharmony_ci .name = "eeprom", 1668c2ecf20Sopenharmony_ci .mode = 0444, 1678c2ecf20Sopenharmony_ci }, 1688c2ecf20Sopenharmony_ci .size = EE1004_EEPROM_SIZE, 1698c2ecf20Sopenharmony_ci .read = ee1004_read, 1708c2ecf20Sopenharmony_ci}; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int ee1004_probe(struct i2c_client *client, 1738c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci int err, cnr = 0; 1768c2ecf20Sopenharmony_ci const char *slow = NULL; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci /* Make sure we can operate on this adapter */ 1798c2ecf20Sopenharmony_ci if (!i2c_check_functionality(client->adapter, 1808c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_BYTE | 1818c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { 1828c2ecf20Sopenharmony_ci if (i2c_check_functionality(client->adapter, 1838c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_BYTE | 1848c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_WORD_DATA)) 1858c2ecf20Sopenharmony_ci slow = "word"; 1868c2ecf20Sopenharmony_ci else if (i2c_check_functionality(client->adapter, 1878c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_BYTE | 1888c2ecf20Sopenharmony_ci I2C_FUNC_SMBUS_READ_BYTE_DATA)) 1898c2ecf20Sopenharmony_ci slow = "byte"; 1908c2ecf20Sopenharmony_ci else 1918c2ecf20Sopenharmony_ci return -EPFNOSUPPORT; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* Use 2 dummy devices for page select command */ 1958c2ecf20Sopenharmony_ci mutex_lock(&ee1004_bus_lock); 1968c2ecf20Sopenharmony_ci if (++ee1004_dev_count == 1) { 1978c2ecf20Sopenharmony_ci for (cnr = 0; cnr < 2; cnr++) { 1988c2ecf20Sopenharmony_ci ee1004_set_page[cnr] = i2c_new_dummy_device(client->adapter, 1998c2ecf20Sopenharmony_ci EE1004_ADDR_SET_PAGE + cnr); 2008c2ecf20Sopenharmony_ci if (IS_ERR(ee1004_set_page[cnr])) { 2018c2ecf20Sopenharmony_ci dev_err(&client->dev, 2028c2ecf20Sopenharmony_ci "address 0x%02x unavailable\n", 2038c2ecf20Sopenharmony_ci EE1004_ADDR_SET_PAGE + cnr); 2048c2ecf20Sopenharmony_ci err = PTR_ERR(ee1004_set_page[cnr]); 2058c2ecf20Sopenharmony_ci goto err_clients; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci } else if (i2c_adapter_id(client->adapter) != 2098c2ecf20Sopenharmony_ci i2c_adapter_id(ee1004_set_page[0]->adapter)) { 2108c2ecf20Sopenharmony_ci dev_err(&client->dev, 2118c2ecf20Sopenharmony_ci "Driver only supports devices on a single I2C bus\n"); 2128c2ecf20Sopenharmony_ci err = -EOPNOTSUPP; 2138c2ecf20Sopenharmony_ci goto err_clients; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci /* Remember current page to avoid unneeded page select */ 2178c2ecf20Sopenharmony_ci err = ee1004_get_current_page(); 2188c2ecf20Sopenharmony_ci if (err < 0) 2198c2ecf20Sopenharmony_ci goto err_clients; 2208c2ecf20Sopenharmony_ci ee1004_current_page = err; 2218c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "Currently selected page: %d\n", 2228c2ecf20Sopenharmony_ci ee1004_current_page); 2238c2ecf20Sopenharmony_ci mutex_unlock(&ee1004_bus_lock); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* Create the sysfs eeprom file */ 2268c2ecf20Sopenharmony_ci err = sysfs_create_bin_file(&client->dev.kobj, &eeprom_attr); 2278c2ecf20Sopenharmony_ci if (err) 2288c2ecf20Sopenharmony_ci goto err_clients_lock; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci dev_info(&client->dev, 2318c2ecf20Sopenharmony_ci "%u byte EE1004-compliant SPD EEPROM, read-only\n", 2328c2ecf20Sopenharmony_ci EE1004_EEPROM_SIZE); 2338c2ecf20Sopenharmony_ci if (slow) 2348c2ecf20Sopenharmony_ci dev_notice(&client->dev, 2358c2ecf20Sopenharmony_ci "Falling back to %s reads, performance will suffer\n", 2368c2ecf20Sopenharmony_ci slow); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci err_clients_lock: 2418c2ecf20Sopenharmony_ci mutex_lock(&ee1004_bus_lock); 2428c2ecf20Sopenharmony_ci err_clients: 2438c2ecf20Sopenharmony_ci if (--ee1004_dev_count == 0) { 2448c2ecf20Sopenharmony_ci for (cnr--; cnr >= 0; cnr--) { 2458c2ecf20Sopenharmony_ci i2c_unregister_device(ee1004_set_page[cnr]); 2468c2ecf20Sopenharmony_ci ee1004_set_page[cnr] = NULL; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci mutex_unlock(&ee1004_bus_lock); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci return err; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int ee1004_remove(struct i2c_client *client) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci int i; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci sysfs_remove_bin_file(&client->dev.kobj, &eeprom_attr); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* Remove page select clients if this is the last device */ 2618c2ecf20Sopenharmony_ci mutex_lock(&ee1004_bus_lock); 2628c2ecf20Sopenharmony_ci if (--ee1004_dev_count == 0) { 2638c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++) { 2648c2ecf20Sopenharmony_ci i2c_unregister_device(ee1004_set_page[i]); 2658c2ecf20Sopenharmony_ci ee1004_set_page[i] = NULL; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci mutex_unlock(&ee1004_bus_lock); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci return 0; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci/*-------------------------------------------------------------------------*/ 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic struct i2c_driver ee1004_driver = { 2768c2ecf20Sopenharmony_ci .driver = { 2778c2ecf20Sopenharmony_ci .name = "ee1004", 2788c2ecf20Sopenharmony_ci }, 2798c2ecf20Sopenharmony_ci .probe = ee1004_probe, 2808c2ecf20Sopenharmony_ci .remove = ee1004_remove, 2818c2ecf20Sopenharmony_ci .id_table = ee1004_ids, 2828c2ecf20Sopenharmony_ci}; 2838c2ecf20Sopenharmony_cimodule_i2c_driver(ee1004_driver); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Driver for EE1004-compliant DDR4 SPD EEPROMs"); 2868c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jean Delvare"); 2878c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 288