| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include <linux/i2c.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #define EE1004_ADDR_SET_PAGE 0x36 |
| #define EE1004_EEPROM_SIZE 512 |
| #define EE1004_PAGE_SIZE 256 |
| #define EE1004_PAGE_SHIFT 8 |
| |
| |
| |
| |
| |
| static DEFINE_MUTEX(ee1004_bus_lock); |
| static struct i2c_client *ee1004_set_page[2]; |
| static unsigned int ee1004_dev_count; |
| static int ee1004_current_page; |
| |
| static const struct i2c_device_id ee1004_ids[] = { |
| <------>{ "ee1004", 0 }, |
| <------>{ } |
| }; |
| MODULE_DEVICE_TABLE(i2c, ee1004_ids); |
| |
| |
| |
| static int ee1004_get_current_page(void) |
| { |
| <------>int err; |
| |
| <------>err = i2c_smbus_read_byte(ee1004_set_page[0]); |
| <------>if (err == -ENXIO) { |
| <------><------> |
| <------><------>return 1; |
| <------>} |
| <------>if (err < 0) { |
| <------><------> |
| <------><------>return err; |
| <------>} |
| |
| <------> |
| <------>return 0; |
| } |
| |
| static ssize_t ee1004_eeprom_read(struct i2c_client *client, char *buf, |
| <------><------><------><------> unsigned int offset, size_t count) |
| { |
| <------>int status; |
| |
| <------>if (count > I2C_SMBUS_BLOCK_MAX) |
| <------><------>count = I2C_SMBUS_BLOCK_MAX; |
| <------> |
| <------>if (unlikely(offset + count > EE1004_PAGE_SIZE)) |
| <------><------>count = EE1004_PAGE_SIZE - offset; |
| |
| <------>if (count > I2C_SMBUS_BLOCK_MAX) |
| <------><------>count = I2C_SMBUS_BLOCK_MAX; |
| |
| <------>status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset, |
| <------><------><------><------><------><------><------> count, buf); |
| <------>dev_dbg(&client->dev, "read %zu@%d --> %d\n", count, offset, status); |
| |
| <------>return status; |
| } |
| |
| static ssize_t ee1004_read(struct file *filp, struct kobject *kobj, |
| <------><------><------> struct bin_attribute *bin_attr, |
| <------><------><------> char *buf, loff_t off, size_t count) |
| { |
| <------>struct device *dev = kobj_to_dev(kobj); |
| <------>struct i2c_client *client = to_i2c_client(dev); |
| <------>size_t requested = count; |
| <------>int page; |
| |
| <------>if (unlikely(!count)) |
| <------><------>return count; |
| |
| <------>page = off >> EE1004_PAGE_SHIFT; |
| <------>if (unlikely(page > 1)) |
| <------><------>return 0; |
| <------>off &= (1 << EE1004_PAGE_SHIFT) - 1; |
| |
| <------> |
| <------> * Read data from chip, protecting against concurrent access to |
| <------> * other EE1004 SPD EEPROMs on the same adapter. |
| <------> */ |
| <------>mutex_lock(&ee1004_bus_lock); |
| |
| <------>while (count) { |
| <------><------>int status; |
| |
| <------><------> |
| <------><------>if (page != ee1004_current_page) { |
| <------><------><------> |
| <------><------><------>status = i2c_smbus_write_byte(ee1004_set_page[page], |
| <------><------><------><------><------><------> 0x00); |
| <------><------><------>if (status == -ENXIO) { |
| <------><------><------><------> |
| <------><------><------><------> * Don't give up just yet. Some memory |
| <------><------><------><------> * modules will select the page but not |
| <------><------><------><------> * ack the command. Check which page is |
| <------><------><------><------> * selected now. |
| <------><------><------><------> */ |
| <------><------><------><------>if (ee1004_get_current_page() == page) |
| <------><------><------><------><------>status = 0; |
| <------><------><------>} |
| <------><------><------>if (status < 0) { |
| <------><------><------><------>dev_err(dev, "Failed to select page %d (%d)\n", |
| <------><------><------><------><------>page, status); |
| <------><------><------><------>mutex_unlock(&ee1004_bus_lock); |
| <------><------><------><------>return status; |
| <------><------><------>} |
| <------><------><------>dev_dbg(dev, "Selected page %d\n", page); |
| <------><------><------>ee1004_current_page = page; |
| <------><------>} |
| |
| <------><------>status = ee1004_eeprom_read(client, buf, off, count); |
| <------><------>if (status < 0) { |
| <------><------><------>mutex_unlock(&ee1004_bus_lock); |
| <------><------><------>return status; |
| <------><------>} |
| <------><------>buf += status; |
| <------><------>off += status; |
| <------><------>count -= status; |
| |
| <------><------>if (off == EE1004_PAGE_SIZE) { |
| <------><------><------>page++; |
| <------><------><------>off = 0; |
| <------><------>} |
| <------>} |
| |
| <------>mutex_unlock(&ee1004_bus_lock); |
| |
| <------>return requested; |
| } |
| |
| static const struct bin_attribute eeprom_attr = { |
| <------>.attr = { |
| <------><------>.name = "eeprom", |
| <------><------>.mode = 0444, |
| <------>}, |
| <------>.size = EE1004_EEPROM_SIZE, |
| <------>.read = ee1004_read, |
| }; |
| |
| static int ee1004_probe(struct i2c_client *client, |
| <------><------><------>const struct i2c_device_id *id) |
| { |
| <------>int err, cnr = 0; |
| <------>const char *slow = NULL; |
| |
| <------> |
| <------>if (!i2c_check_functionality(client->adapter, |
| <------><------><------><------> I2C_FUNC_SMBUS_READ_BYTE | |
| <------><------><------><------> I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { |
| <------><------>if (i2c_check_functionality(client->adapter, |
| <------><------><------><------> I2C_FUNC_SMBUS_READ_BYTE | |
| <------><------><------><------> I2C_FUNC_SMBUS_READ_WORD_DATA)) |
| <------><------><------>slow = "word"; |
| <------><------>else if (i2c_check_functionality(client->adapter, |
| <------><------><------><------> I2C_FUNC_SMBUS_READ_BYTE | |
| <------><------><------><------> I2C_FUNC_SMBUS_READ_BYTE_DATA)) |
| <------><------><------>slow = "byte"; |
| <------><------>else |
| <------><------><------>return -EPFNOSUPPORT; |
| <------>} |
| |
| <------> |
| <------>mutex_lock(&ee1004_bus_lock); |
| <------>if (++ee1004_dev_count == 1) { |
| <------><------>for (cnr = 0; cnr < 2; cnr++) { |
| <------><------><------>ee1004_set_page[cnr] = i2c_new_dummy_device(client->adapter, |
| <------><------><------><------><------><------>EE1004_ADDR_SET_PAGE + cnr); |
| <------><------><------>if (IS_ERR(ee1004_set_page[cnr])) { |
| <------><------><------><------>dev_err(&client->dev, |
| <------><------><------><------><------>"address 0x%02x unavailable\n", |
| <------><------><------><------><------>EE1004_ADDR_SET_PAGE + cnr); |
| <------><------><------><------>err = PTR_ERR(ee1004_set_page[cnr]); |
| <------><------><------><------>goto err_clients; |
| <------><------><------>} |
| <------><------>} |
| <------>} else if (i2c_adapter_id(client->adapter) != |
| <------><------> i2c_adapter_id(ee1004_set_page[0]->adapter)) { |
| <------><------>dev_err(&client->dev, |
| <------><------><------>"Driver only supports devices on a single I2C bus\n"); |
| <------><------>err = -EOPNOTSUPP; |
| <------><------>goto err_clients; |
| <------>} |
| |
| <------> |
| <------>err = ee1004_get_current_page(); |
| <------>if (err < 0) |
| <------><------>goto err_clients; |
| <------>ee1004_current_page = err; |
| <------>dev_dbg(&client->dev, "Currently selected page: %d\n", |
| <------><------>ee1004_current_page); |
| <------>mutex_unlock(&ee1004_bus_lock); |
| |
| <------> |
| <------>err = sysfs_create_bin_file(&client->dev.kobj, &eeprom_attr); |
| <------>if (err) |
| <------><------>goto err_clients_lock; |
| |
| <------>dev_info(&client->dev, |
| <------><------> "%u byte EE1004-compliant SPD EEPROM, read-only\n", |
| <------><------> EE1004_EEPROM_SIZE); |
| <------>if (slow) |
| <------><------>dev_notice(&client->dev, |
| <------><------><------> "Falling back to %s reads, performance will suffer\n", |
| <------><------><------> slow); |
| |
| <------>return 0; |
| |
| err_clients_lock: |
| <------>mutex_lock(&ee1004_bus_lock); |
| err_clients: |
| <------>if (--ee1004_dev_count == 0) { |
| <------><------>for (cnr--; cnr >= 0; cnr--) { |
| <------><------><------>i2c_unregister_device(ee1004_set_page[cnr]); |
| <------><------><------>ee1004_set_page[cnr] = NULL; |
| <------><------>} |
| <------>} |
| <------>mutex_unlock(&ee1004_bus_lock); |
| |
| <------>return err; |
| } |
| |
| static int ee1004_remove(struct i2c_client *client) |
| { |
| <------>int i; |
| |
| <------>sysfs_remove_bin_file(&client->dev.kobj, &eeprom_attr); |
| |
| <------> |
| <------>mutex_lock(&ee1004_bus_lock); |
| <------>if (--ee1004_dev_count == 0) { |
| <------><------>for (i = 0; i < 2; i++) { |
| <------><------><------>i2c_unregister_device(ee1004_set_page[i]); |
| <------><------><------>ee1004_set_page[i] = NULL; |
| <------><------>} |
| <------>} |
| <------>mutex_unlock(&ee1004_bus_lock); |
| |
| <------>return 0; |
| } |
| |
| |
| |
| static struct i2c_driver ee1004_driver = { |
| <------>.driver = { |
| <------><------>.name = "ee1004", |
| <------>}, |
| <------>.probe = ee1004_probe, |
| <------>.remove = ee1004_remove, |
| <------>.id_table = ee1004_ids, |
| }; |
| module_i2c_driver(ee1004_driver); |
| |
| MODULE_DESCRIPTION("Driver for EE1004-compliant DDR4 SPD EEPROMs"); |
| MODULE_AUTHOR("Jean Delvare"); |
| MODULE_LICENSE("GPL"); |
| |