From 415f0a02adaea754dc85cde7b50707f7fbc4cf3f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 28 Mar 2012 09:58:07 +0300 Subject: hv: fix return type of hv_post_message() This function returns negative error codes, but because the type is u16 they get truncated into positive numbers. It doesn't look like the callers care, but we should fix it anyway as a cleanup. Signed-off-by: Dan Carpenter Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv.c | 2 +- drivers/hv/hyperv_vmbus.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 15956bd48b48..86f8885aeb45 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -252,7 +252,7 @@ void hv_cleanup(void) * * This involves a hypercall. */ -u16 hv_post_message(union hv_connection_id connection_id, +int hv_post_message(union hv_connection_id connection_id, enum hv_message_type message_type, void *payload, size_t payload_size) { diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 699f0d8e59ed..b9426a6592ee 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -495,7 +495,7 @@ extern int hv_init(void); extern void hv_cleanup(void); -extern u16 hv_post_message(union hv_connection_id connection_id, +extern int hv_post_message(union hv_connection_id connection_id, enum hv_message_type message_type, void *payload, size_t payload_size); -- cgit v1.2.3 From ecf1948985247cf35b5536fa62e02f56476f41f1 Mon Sep 17 00:00:00 2001 From: Dmitry Artamonow Date: Mon, 2 Apr 2012 09:42:22 +0400 Subject: w1: fix slave driver registration error message W1 core prints "Failed to register master driver" if error happens on registering SLAVE driver. Fix it. Signed-off-by: Dmitry Artamonow Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/w1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 9761950697b4..2f2e894ea0c8 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -1027,7 +1027,7 @@ static int __init w1_init(void) retval = driver_register(&w1_slave_driver); if (retval) { printk(KERN_ERR - "Failed to register master driver. err=%d.\n", + "Failed to register slave driver. err=%d.\n", retval); goto err_out_master_unregister; } -- cgit v1.2.3 From f19420c1acb0b573c88a12deb2d42035e22d4a17 Mon Sep 17 00:00:00 2001 From: Markus Franke Date: Thu, 12 Apr 2012 00:40:30 +0200 Subject: w1: Add 1-wire slave device driver for DS28E04-100 This patch adds a 1-wire slave device driver for the DS28E04-100. Signed-off-by: Markus Franke Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/Kconfig | 7 + drivers/w1/slaves/Makefile | 1 + drivers/w1/slaves/w1_ds28e04.c | 462 +++++++++++++++++++++++++++++++++++++++++ drivers/w1/w1_family.h | 1 + 4 files changed, 471 insertions(+) create mode 100644 drivers/w1/slaves/w1_ds28e04.c (limited to 'drivers') diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index eb9e376d6244..605ee36c96f8 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -94,6 +94,13 @@ config W1_SLAVE_DS2781 If you are unsure, say N. +config W1_SLAVE_DS28E04 + tristate "4096-Bit Addressable 1-Wire EEPROM with PIO (DS28E04-100)" + depends on W1 + help + Say Y here if you want to use a 1-wire + 4kb EEPROM with PIO family device (DS28E04). + config W1_SLAVE_BQ27000 tristate "BQ27000 slave support" depends on W1 diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index c4f1859fb520..05188f6aab5a 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o +obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o diff --git a/drivers/w1/slaves/w1_ds28e04.c b/drivers/w1/slaves/w1_ds28e04.c new file mode 100644 index 000000000000..f652db3782bf --- /dev/null +++ b/drivers/w1/slaves/w1_ds28e04.c @@ -0,0 +1,462 @@ +/* + * w1_ds28e04.c - w1 family 1C (DS28E04) driver + * + * Copyright (c) 2012 Markus Franke + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Markus Franke , "); +MODULE_DESCRIPTION("w1 family 1C driver for DS28E04, 4kb EEPROM and PIO"); + +/* Allow the strong pullup to be disabled, but default to enabled. + * If it was disabled a parasite powered device might not get the required + * current to copy the data from the scratchpad to EEPROM. If it is enabled parasite powered + * devices have a better chance of getting the current required. + */ +static int w1_strong_pullup = 1; +module_param_named(strong_pullup, w1_strong_pullup, int, 0); + +/* enable/disable CRC checking on DS28E04-100 memory accesses */ +static char w1_enable_crccheck = 1; + +#define W1_EEPROM_SIZE 512 +#define W1_PAGE_COUNT 16 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F1C_READ_EEPROM 0xF0 +#define W1_F1C_WRITE_SCRATCH 0x0F +#define W1_F1C_READ_SCRATCH 0xAA +#define W1_F1C_COPY_SCRATCH 0x55 +#define W1_F1C_ACCESS_WRITE 0x5A + +#define W1_1C_REG_LOGIC_STATE 0x220 + +struct w1_f1C_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f1C_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return (size - off); + + return count; +} + +static int w1_f1C_refresh_block(struct w1_slave *sl, struct w1_f1C_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} + +static int w1_f1C_read(struct w1_slave *sl, int addr, int len, char *data) +{ + u8 wrbuf[3]; + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) + return -EIO; + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + return w1_read_block(sl->master, data, len); +} + +static ssize_t w1_f1C_read_bin(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + struct w1_f1C_data *data = sl->family_data; + int i, min_page, max_page; + + if ((count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + if(w1_enable_crccheck) { + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f1C_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + } + else { + count = w1_f1C_read(sl, off, count, buf); + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f1C_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + u8 wrbuf[4]; + u8 rdbuf[W1_PAGE_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + unsigned int tm = 10; + int i; + struct w1_f1C_data *f1C = sl->family_data; + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F1C_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) + return -1; + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_COPY_SCRATCH; + wrbuf[3] = es; + + for(i = 0; i < sizeof(wrbuf); ++i) { + /* issue 10ms strong pullup (or delay) on the last byte for writing the data from the scratchpad to EEPROM */ + if(w1_strong_pullup && i == sizeof(wrbuf)-1) + w1_next_pullup(sl->master, tm); + + w1_write_8(sl->master, wrbuf[i]); + } + + if(!w1_strong_pullup) + msleep(tm); + + if(w1_enable_crccheck) { + /* invalidate cached data */ + f1C->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); + } + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t w1_f1C_write_bin(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr, len, idx; + + if ((count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + + if(w1_enable_crccheck) { + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off); + return -EINVAL; + } + } + } + + mutex_lock(&sl->master->mutex); + + /* Can only write data to one page at a time */ + idx = 0; + while (idx < count) { + addr = off + idx; + len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); + if (len > (count - idx)) + len = count - idx; + + if (w1_f1C_write(sl, addr, len, &buf[idx]) < 0) { + count = -EIO; + goto out_up; + } + idx += len; + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static ssize_t w1_f1C_read_pio(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + + /* check arguments */ + if(off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + ret = w1_f1C_read(sl, W1_1C_REG_LOGIC_STATE, count, buf); + mutex_unlock(&sl->master->mutex); + + return ret; +} + +static ssize_t w1_f1C_write_pio(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 wrbuf[3]; + u8 ack; + + /* check arguments */ + if(off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + + /* Write the PIO data */ + if (w1_reset_select_slave(sl)) + return -1; + + /* set bit 7..2 to value '1' */ + *buf = *buf | 0xFC; + + wrbuf[0] = W1_F1C_ACCESS_WRITE; + wrbuf[1] = *buf; + wrbuf[2] = ~(*buf); + w1_write_block(sl->master, wrbuf, 3); + + w1_read_block(sl->master, &ack, sizeof(ack)); + + mutex_unlock(&sl->master->mutex); + + /* check for acknowledgement */ + if(ack != 0xAA) return -EIO; + + return count; +} + +static ssize_t w1_f1C_show_crccheck(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if(put_user(w1_enable_crccheck + 0x30, buf)) + return -EFAULT; + + return sizeof(w1_enable_crccheck); +} + +static ssize_t w1_f1C_store_crccheck(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + char val; + + if(count != 1 || !buf) return -EINVAL; + + if(get_user(val, buf)) + return -EFAULT; + + /* convert to decimal */ + val = val - 0x30; + if(val != 0 && val != 1) return -EINVAL; + + /* set the new value */ + w1_enable_crccheck = val; + + return sizeof(w1_enable_crccheck); +} + +#define NB_SYSFS_BIN_FILES 2 +static struct bin_attribute w1_f1C_bin_attr[NB_SYSFS_BIN_FILES] = { + { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = W1_EEPROM_SIZE, + .read = w1_f1C_read_bin, + .write = w1_f1C_write_bin, + }, + { + .attr = { + .name = "pio", + .mode = S_IRUGO | S_IWUSR, + }, + .size = 1, + .read = w1_f1C_read_pio, + .write = w1_f1C_write_pio, + } +}; + +static DEVICE_ATTR(crccheck, S_IWUSR | S_IRUGO, w1_f1C_show_crccheck, w1_f1C_store_crccheck); + +static int w1_f1C_add_slave(struct w1_slave *sl) +{ + int err = 0; + int i; + struct w1_f1C_data *data = NULL; + + if(w1_enable_crccheck) { + data = kzalloc(sizeof(struct w1_f1C_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + } + + /* create binary sysfs attributes */ + for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) + err = sysfs_create_bin_file(&sl->dev.kobj, &(w1_f1C_bin_attr[i])); + + if(err) + goto out; + + /* create device attributes */ + err = device_create_file(&sl->dev, &dev_attr_crccheck); + + if(err) { + /* remove binary sysfs attributes */ + for (i = 0; i < NB_SYSFS_BIN_FILES; ++i) + sysfs_remove_bin_file(&sl->dev.kobj, &(w1_f1C_bin_attr[i])); + } + +out: + if(err) { + if(w1_enable_crccheck) + kfree(data); + } + + return err; +} + +static void w1_f1C_remove_slave(struct w1_slave *sl) +{ + int i; + + if(w1_enable_crccheck) { + kfree(sl->family_data); + sl->family_data = NULL; + } + + /* remove device attributes */ + device_remove_file(&sl->dev, &dev_attr_crccheck); + + /* remove binary sysfs attributes */ + for (i = 0; i < NB_SYSFS_BIN_FILES; ++i) + sysfs_remove_bin_file(&sl->dev.kobj, &(w1_f1C_bin_attr[i])); +} + +static struct w1_family_ops w1_f1C_fops = { + .add_slave = w1_f1C_add_slave, + .remove_slave = w1_f1C_remove_slave, +}; + +static struct w1_family w1_family_1C = { + .fid = W1_FAMILY_DS28E04, + .fops = &w1_f1C_fops, +}; + +static int __init w1_f1C_init(void) +{ + return w1_register_family(&w1_family_1C); +} + +static void __exit w1_f1C_fini(void) +{ + w1_unregister_family(&w1_family_1C); +} + +module_init(w1_f1C_init); +module_exit(w1_f1C_fini); diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index 874aeb05011b..b00ada44a89b 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -30,6 +30,7 @@ #define W1_FAMILY_SMEM_01 0x01 #define W1_FAMILY_SMEM_81 0x81 #define W1_THERM_DS18S20 0x10 +#define W1_FAMILY_DS28E04 0x1C #define W1_COUNTER_DS2423 0x1D #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 -- cgit v1.2.3 From 8f1e12512e9f1276b68c8b14a60961658c73336f Mon Sep 17 00:00:00 2001 From: Markus Franke Date: Thu, 12 Apr 2012 00:42:03 +0200 Subject: w1: Disable irqs during 1-wire bus operations, extend 1-wire reset pulse This patch offers the possibility to disables irqs during w1_write_bit() and w1_reset_bus() operations as timing requirements are very strict for the 1-wire bus protocol. Per default interrupts are enabled but can be disabled via the module parameter "w1_disable_irqs". Extend 1-wire reset pulse length from 480us to 500us as 480us is the minimum requirement for the 1-wire reset/presence pulse. Signed-off-by: Markus Franke Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/w1_io.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c index 3135b2c63998..e10acc237733 100644 --- a/drivers/w1/w1_io.c +++ b/drivers/w1/w1_io.c @@ -31,6 +31,9 @@ static int w1_delay_parm = 1; module_param_named(delay_coef, w1_delay_parm, int, 0); +static int w1_disable_irqs = 0; +module_param_named(disable_irqs, w1_disable_irqs, int, 0); + static u8 w1_crc8_table[] = { 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, @@ -79,6 +82,10 @@ static u8 w1_touch_bit(struct w1_master *dev, int bit) */ static void w1_write_bit(struct w1_master *dev, int bit) { + unsigned long flags = 0; + + if(w1_disable_irqs) local_irq_save(flags); + if (bit) { dev->bus_master->write_bit(dev->bus_master->data, 0); w1_delay(6); @@ -90,6 +97,8 @@ static void w1_write_bit(struct w1_master *dev, int bit) dev->bus_master->write_bit(dev->bus_master->data, 1); w1_delay(10); } + + if(w1_disable_irqs) local_irq_restore(flags); } /** @@ -158,7 +167,7 @@ EXPORT_SYMBOL_GPL(w1_write_8); static u8 w1_read_bit(struct w1_master *dev) { int result; - unsigned long flags; + unsigned long flags = 0; /* sample timing is critical here */ local_irq_save(flags); @@ -318,6 +327,9 @@ EXPORT_SYMBOL_GPL(w1_read_block); int w1_reset_bus(struct w1_master *dev) { int result; + unsigned long flags = 0; + + if(w1_disable_irqs) local_irq_save(flags); if (dev->bus_master->reset_bus) result = dev->bus_master->reset_bus(dev->bus_master->data) & 0x1; @@ -330,19 +342,21 @@ int w1_reset_bus(struct w1_master *dev) * cpu for such a short amount of time AND get it back in * the maximum amount of time. */ - w1_delay(480); + w1_delay(500); dev->bus_master->write_bit(dev->bus_master->data, 1); w1_delay(70); result = dev->bus_master->read_bit(dev->bus_master->data) & 0x1; - /* minmum 70 (above) + 410 = 480 us + /* minmum 70 (above) + 430 = 500 us * There aren't any timing requirements between a reset and * the following transactions. Sleeping is safe here. */ - /* w1_delay(410); min required time */ + /* w1_delay(430); min required time */ msleep(1); } + if(w1_disable_irqs) local_irq_restore(flags); + return result; } EXPORT_SYMBOL_GPL(w1_reset_bus); -- cgit v1.2.3 From eda70f1dfc9e5165b9413dbf1ccb5c108f26a18c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 13 Apr 2012 16:39:06 +0300 Subject: w1: w1_ds28e04: unlock on error path in w1_f1C_write_pio() We should unlock here before returning. Signed-off-by: Dan Carpenter Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/w1_ds28e04.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/w1/slaves/w1_ds28e04.c b/drivers/w1/slaves/w1_ds28e04.c index f652db3782bf..4aa1aa90480d 100644 --- a/drivers/w1/slaves/w1_ds28e04.c +++ b/drivers/w1/slaves/w1_ds28e04.c @@ -309,8 +309,10 @@ static ssize_t w1_f1C_write_pio(struct file *filp, struct kobject *kobj, mutex_lock(&sl->master->mutex); /* Write the PIO data */ - if (w1_reset_select_slave(sl)) + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->mutex); return -1; + } /* set bit 7..2 to value '1' */ *buf = *buf | 0xFC; -- cgit v1.2.3 From 02fbe5e61df654b2b39a1ddc1ae912c9e14c3ddd Mon Sep 17 00:00:00 2001 From: Peter Korsgaard Date: Tue, 17 Apr 2012 12:13:18 +0200 Subject: devtmpfs: fix 'the the' typo Signed-off-by: Peter Korsgaard Signed-off-by: Greg Kroah-Hartman --- drivers/base/devtmpfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 8493536ea55b..765c3a28077a 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -7,9 +7,9 @@ * devtmpfs, a tmpfs-based filesystem is created. Every driver-core * device which requests a device node, will add a node in this * filesystem. - * By default, all devices are named after the the name of the - * device, owned by root and have a default mode of 0600. Subsystems - * can overwrite the default setting if needed. + * By default, all devices are named after the name of the device, + * owned by root and have a default mode of 0600. Subsystems can + * overwrite the default setting if needed. */ #include -- cgit v1.2.3 From 0d4e293ca8271cc7d7ac1423afe5ceb7d213d0fc Mon Sep 17 00:00:00 2001 From: Peter Korsgaard Date: Tue, 17 Apr 2012 12:12:57 +0200 Subject: core.c: fix 'the the' typo Signed-off-by: Peter Korsgaard Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/core.c b/drivers/base/core.c index e28ce9898af4..6cb25f1511df 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -878,8 +878,8 @@ EXPORT_SYMBOL_GPL(dev_set_name); * to NULL prevents an entry from being created. class->dev_kobj must * be set (or cleared) before any devices are registered to the class * otherwise device_create_sys_dev_entry() and - * device_remove_sys_dev_entry() will disagree about the the presence - * of the link. + * device_remove_sys_dev_entry() will disagree about the presence of + * the link. */ static struct kobject *device_to_dev_kobj(struct device *dev) { -- cgit v1.2.3 From efb4df82ca97b3c1cadd03bb98bce7a7e206fa63 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 17 Apr 2012 17:03:30 -0700 Subject: driver core: fix dma-buf.c kernel-doc warnings Fix kernel-doc warnings in dma-buf.c: Warning(drivers/base/dma-buf.c:305): No description found for parameter 'dmabuf' Warning(drivers/base/dma-buf.c:305): Excess function parameter 'dma_buf' description in 'dma_buf_begin_cpu_access' Warning(drivers/base/dma-buf.c:332): No description found for parameter 'dmabuf' Warning(drivers/base/dma-buf.c:332): Excess function parameter 'dma_buf' description in 'dma_buf_end_cpu_access' Warning(drivers/base/dma-buf.c:350): No description found for parameter 'dmabuf' Warning(drivers/base/dma-buf.c:350): Excess function parameter 'dma_buf' description in 'dma_buf_kmap_atomic' Warning(drivers/base/dma-buf.c:367): No description found for parameter 'dmabuf' Warning(drivers/base/dma-buf.c:367): Excess function parameter 'dma_buf' description in 'dma_buf_kunmap_atomic' Warning(drivers/base/dma-buf.c:385): No description found for parameter 'dmabuf' Warning(drivers/base/dma-buf.c:385): Excess function parameter 'dma_buf' description in 'dma_buf_kmap' Warning(drivers/base/dma-buf.c:402): No description found for parameter 'dmabuf' Warning(drivers/base/dma-buf.c:402): Excess function parameter 'dma_buf' description in 'dma_buf_kunmap' Signed-off-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- drivers/base/dma-buf.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/base/dma-buf.c b/drivers/base/dma-buf.c index 07cbbc6fddb4..05c64c11bad2 100644 --- a/drivers/base/dma-buf.c +++ b/drivers/base/dma-buf.c @@ -293,7 +293,7 @@ EXPORT_SYMBOL_GPL(dma_buf_unmap_attachment); * cpu in the kernel context. Calls begin_cpu_access to allow exporter-specific * preparations. Coherency is only guaranteed in the specified range for the * specified access direction. - * @dma_buf: [in] buffer to prepare cpu access for. + * @dmabuf: [in] buffer to prepare cpu access for. * @start: [in] start of range for cpu access. * @len: [in] length of range for cpu access. * @direction: [in] length of range for cpu access. @@ -320,7 +320,7 @@ EXPORT_SYMBOL_GPL(dma_buf_begin_cpu_access); * cpu in the kernel context. Calls end_cpu_access to allow exporter-specific * actions. Coherency is only guaranteed in the specified range for the * specified access direction. - * @dma_buf: [in] buffer to complete cpu access for. + * @dmabuf: [in] buffer to complete cpu access for. * @start: [in] start of range for cpu access. * @len: [in] length of range for cpu access. * @direction: [in] length of range for cpu access. @@ -340,7 +340,7 @@ EXPORT_SYMBOL_GPL(dma_buf_end_cpu_access); /** * dma_buf_kmap_atomic - Map a page of the buffer object into kernel address * space. The same restrictions as for kmap_atomic and friends apply. - * @dma_buf: [in] buffer to map page from. + * @dmabuf: [in] buffer to map page from. * @page_num: [in] page in PAGE_SIZE units to map. * * This call must always succeed, any necessary preparations that might fail @@ -356,7 +356,7 @@ EXPORT_SYMBOL_GPL(dma_buf_kmap_atomic); /** * dma_buf_kunmap_atomic - Unmap a page obtained by dma_buf_kmap_atomic. - * @dma_buf: [in] buffer to unmap page from. + * @dmabuf: [in] buffer to unmap page from. * @page_num: [in] page in PAGE_SIZE units to unmap. * @vaddr: [in] kernel space pointer obtained from dma_buf_kmap_atomic. * @@ -375,7 +375,7 @@ EXPORT_SYMBOL_GPL(dma_buf_kunmap_atomic); /** * dma_buf_kmap - Map a page of the buffer object into kernel address space. The * same restrictions as for kmap and friends apply. - * @dma_buf: [in] buffer to map page from. + * @dmabuf: [in] buffer to map page from. * @page_num: [in] page in PAGE_SIZE units to map. * * This call must always succeed, any necessary preparations that might fail @@ -391,7 +391,7 @@ EXPORT_SYMBOL_GPL(dma_buf_kmap); /** * dma_buf_kunmap - Unmap a page obtained by dma_buf_kmap. - * @dma_buf: [in] buffer to unmap page from. + * @dmabuf: [in] buffer to unmap page from. * @page_num: [in] page in PAGE_SIZE units to unmap. * @vaddr: [in] kernel space pointer obtained from dma_buf_kmap. * -- cgit v1.2.3 From 97ec448aeadff55234368a89c4a07a7ef290a084 Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Mon, 16 Apr 2012 18:14:10 -0700 Subject: drivers/base/bus.c: local variables should not be exposed globally The variable 'system_kset' is only referenced in this file and should be marked static to prevent it from being exposed globally. This quiets the sparse waring: warning: symbol 'system_kset' was not declared. Should it be static? Also, remove the comment since drivers/base/sys.c has now been deleted. Signed-off-by: H Hartley Sweeten Cc: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 26a06b801b5b..2bcef657a60c 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -21,8 +21,7 @@ #include "power/power.h" /* /sys/devices/system */ -/* FIXME: make static after drivers/base/sys.c is deleted */ -struct kset *system_kset; +static struct kset *system_kset; #define to_bus_attr(_attr) container_of(_attr, struct bus_attribute, attr) -- cgit v1.2.3 From a15d49fd3094cff90e5410ca454a870e0a722fe1 Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Mon, 16 Apr 2012 15:06:25 +0200 Subject: driver core: check start node in klist_iter_init_node klist_iter_init_node() takes a node as a start argument. However, this node might not be valid anymore. This patch updates the klist_iter_init_node() and dependent functions to return an error if so. All calling functions have been audited to check for a return code here. Signed-off-by: Hannes Reinecke Cc: Greg Kroah-Hartmann Cc: Kay Sievers Cc: Stable Kernel Cc: Linux Kernel Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 46 +++++++++++++++++++++++++++++----------------- drivers/base/class.c | 32 ++++++++++++++++++++------------ drivers/base/driver.c | 18 +++++++++++------- 3 files changed, 60 insertions(+), 36 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 2bcef657a60c..76aed01a8b2c 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -296,11 +296,13 @@ int bus_for_each_dev(struct bus_type *bus, struct device *start, if (!bus) return -EINVAL; - klist_iter_init_node(&bus->p->klist_devices, &i, - (start ? &start->p->knode_bus : NULL)); - while ((dev = next_device(&i)) && !error) - error = fn(dev, data); - klist_iter_exit(&i); + error = klist_iter_init_node(&bus->p->klist_devices, &i, + (start ? &start->p->knode_bus : NULL)); + if (!error) { + while ((dev = next_device(&i)) && !error) + error = fn(dev, data); + klist_iter_exit(&i); + } return error; } EXPORT_SYMBOL_GPL(bus_for_each_dev); @@ -330,8 +332,10 @@ struct device *bus_find_device(struct bus_type *bus, if (!bus) return NULL; - klist_iter_init_node(&bus->p->klist_devices, &i, - (start ? &start->p->knode_bus : NULL)); + if (klist_iter_init_node(&bus->p->klist_devices, &i, + (start ? &start->p->knode_bus : NULL)) < 0) + return NULL; + while ((dev = next_device(&i))) if (match(dev, data) && get_device(dev)) break; @@ -384,7 +388,9 @@ struct device *subsys_find_device_by_id(struct bus_type *subsys, unsigned int id return NULL; if (hint) { - klist_iter_init_node(&subsys->p->klist_devices, &i, &hint->p->knode_bus); + if (klist_iter_init_node(&subsys->p->klist_devices, &i, + &hint->p->knode_bus) < 0) + return NULL; dev = next_device(&i); if (dev && dev->id == id && get_device(dev)) { klist_iter_exit(&i); @@ -446,11 +452,13 @@ int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, if (!bus) return -EINVAL; - klist_iter_init_node(&bus->p->klist_drivers, &i, - start ? &start->p->knode_bus : NULL); - while ((drv = next_driver(&i)) && !error) - error = fn(drv, data); - klist_iter_exit(&i); + error = klist_iter_init_node(&bus->p->klist_drivers, &i, + start ? &start->p->knode_bus : NULL); + if (!error) { + while ((drv = next_driver(&i)) && !error) + error = fn(drv, data); + klist_iter_exit(&i); + } return error; } EXPORT_SYMBOL_GPL(bus_for_each_drv); @@ -1111,15 +1119,19 @@ EXPORT_SYMBOL_GPL(bus_sort_breadthfirst); * otherwise if it is NULL, the iteration starts at the beginning of * the list. */ -void subsys_dev_iter_init(struct subsys_dev_iter *iter, struct bus_type *subsys, - struct device *start, const struct device_type *type) +int subsys_dev_iter_init(struct subsys_dev_iter *iter, struct bus_type *subsys, + struct device *start, const struct device_type *type) { struct klist_node *start_knode = NULL; + int error; if (start) start_knode = &start->p->knode_bus; - klist_iter_init_node(&subsys->p->klist_devices, &iter->ki, start_knode); - iter->type = type; + error = klist_iter_init_node(&subsys->p->klist_devices, &iter->ki, + start_knode); + if (!error) + iter->type = type; + return error; } EXPORT_SYMBOL_GPL(subsys_dev_iter_init); diff --git a/drivers/base/class.c b/drivers/base/class.c index 03243d4002fd..23dbc661d4a0 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -301,15 +301,20 @@ void class_destroy(struct class *cls) * otherwise if it is NULL, the iteration starts at the beginning of * the list. */ -void class_dev_iter_init(struct class_dev_iter *iter, struct class *class, - struct device *start, const struct device_type *type) +int class_dev_iter_init(struct class_dev_iter *iter, struct class *class, + struct device *start, const struct device_type *type) { struct klist_node *start_knode = NULL; + int error; if (start) start_knode = &start->knode_class; - klist_iter_init_node(&class->p->klist_devices, &iter->ki, start_knode); - iter->type = type; + error = klist_iter_init_node(&class->p->klist_devices, &iter->ki, + start_knode); + if (!error) + iter->type = type; + + return error; } EXPORT_SYMBOL_GPL(class_dev_iter_init); @@ -387,14 +392,15 @@ int class_for_each_device(struct class *class, struct device *start, return -EINVAL; } - class_dev_iter_init(&iter, class, start, NULL); - while ((dev = class_dev_iter_next(&iter))) { - error = fn(dev, data); - if (error) - break; + error = class_dev_iter_init(&iter, class, start, NULL); + if (!error) { + while ((dev = class_dev_iter_next(&iter))) { + error = fn(dev, data); + if (error) + break; + } + class_dev_iter_exit(&iter); } - class_dev_iter_exit(&iter); - return error; } EXPORT_SYMBOL_GPL(class_for_each_device); @@ -434,7 +440,9 @@ struct device *class_find_device(struct class *class, struct device *start, return NULL; } - class_dev_iter_init(&iter, class, start, NULL); + if (class_dev_iter_init(&iter, class, start, NULL) < 0) + return NULL; + while ((dev = class_dev_iter_next(&iter))) { if (match(dev, data)) { get_device(dev); diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 3ec3896c83a6..16f6dd2c4403 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -49,11 +49,13 @@ int driver_for_each_device(struct device_driver *drv, struct device *start, if (!drv) return -EINVAL; - klist_iter_init_node(&drv->p->klist_devices, &i, - start ? &start->p->knode_driver : NULL); - while ((dev = next_device(&i)) && !error) - error = fn(dev, data); - klist_iter_exit(&i); + error = klist_iter_init_node(&drv->p->klist_devices, &i, + start ? &start->p->knode_driver : NULL); + if (!error) { + while ((dev = next_device(&i)) && !error) + error = fn(dev, data); + klist_iter_exit(&i); + } return error; } EXPORT_SYMBOL_GPL(driver_for_each_device); @@ -83,8 +85,10 @@ struct device *driver_find_device(struct device_driver *drv, if (!drv) return NULL; - klist_iter_init_node(&drv->p->klist_devices, &i, - (start ? &start->p->knode_driver : NULL)); + if (klist_iter_init_node(&drv->p->klist_devices, &i, + (start ? &start->p->knode_driver : NULL)) < 0) + return NULL; + while ((dev = next_device(&i))) if (match(dev, data) && get_device(dev)) break; -- cgit v1.2.3 From 7cd9c9bb57476167e83b7780dbc06d1dd601789d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 19 Apr 2012 19:17:30 -0700 Subject: Revert "driver core: check start node in klist_iter_init_node" This reverts commit a15d49fd3094cff90e5410ca454a870e0a722fe1 as that patch broke the build. Cc: Hannes Reinecke Reported-by: Stephen Rothwell Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 46 +++++++++++++++++----------------------------- drivers/base/class.c | 32 ++++++++++++-------------------- drivers/base/driver.c | 18 +++++++----------- 3 files changed, 36 insertions(+), 60 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 76aed01a8b2c..2bcef657a60c 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -296,13 +296,11 @@ int bus_for_each_dev(struct bus_type *bus, struct device *start, if (!bus) return -EINVAL; - error = klist_iter_init_node(&bus->p->klist_devices, &i, - (start ? &start->p->knode_bus : NULL)); - if (!error) { - while ((dev = next_device(&i)) && !error) - error = fn(dev, data); - klist_iter_exit(&i); - } + klist_iter_init_node(&bus->p->klist_devices, &i, + (start ? &start->p->knode_bus : NULL)); + while ((dev = next_device(&i)) && !error) + error = fn(dev, data); + klist_iter_exit(&i); return error; } EXPORT_SYMBOL_GPL(bus_for_each_dev); @@ -332,10 +330,8 @@ struct device *bus_find_device(struct bus_type *bus, if (!bus) return NULL; - if (klist_iter_init_node(&bus->p->klist_devices, &i, - (start ? &start->p->knode_bus : NULL)) < 0) - return NULL; - + klist_iter_init_node(&bus->p->klist_devices, &i, + (start ? &start->p->knode_bus : NULL)); while ((dev = next_device(&i))) if (match(dev, data) && get_device(dev)) break; @@ -388,9 +384,7 @@ struct device *subsys_find_device_by_id(struct bus_type *subsys, unsigned int id return NULL; if (hint) { - if (klist_iter_init_node(&subsys->p->klist_devices, &i, - &hint->p->knode_bus) < 0) - return NULL; + klist_iter_init_node(&subsys->p->klist_devices, &i, &hint->p->knode_bus); dev = next_device(&i); if (dev && dev->id == id && get_device(dev)) { klist_iter_exit(&i); @@ -452,13 +446,11 @@ int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, if (!bus) return -EINVAL; - error = klist_iter_init_node(&bus->p->klist_drivers, &i, - start ? &start->p->knode_bus : NULL); - if (!error) { - while ((drv = next_driver(&i)) && !error) - error = fn(drv, data); - klist_iter_exit(&i); - } + klist_iter_init_node(&bus->p->klist_drivers, &i, + start ? &start->p->knode_bus : NULL); + while ((drv = next_driver(&i)) && !error) + error = fn(drv, data); + klist_iter_exit(&i); return error; } EXPORT_SYMBOL_GPL(bus_for_each_drv); @@ -1119,19 +1111,15 @@ EXPORT_SYMBOL_GPL(bus_sort_breadthfirst); * otherwise if it is NULL, the iteration starts at the beginning of * the list. */ -int subsys_dev_iter_init(struct subsys_dev_iter *iter, struct bus_type *subsys, - struct device *start, const struct device_type *type) +void subsys_dev_iter_init(struct subsys_dev_iter *iter, struct bus_type *subsys, + struct device *start, const struct device_type *type) { struct klist_node *start_knode = NULL; - int error; if (start) start_knode = &start->p->knode_bus; - error = klist_iter_init_node(&subsys->p->klist_devices, &iter->ki, - start_knode); - if (!error) - iter->type = type; - return error; + klist_iter_init_node(&subsys->p->klist_devices, &iter->ki, start_knode); + iter->type = type; } EXPORT_SYMBOL_GPL(subsys_dev_iter_init); diff --git a/drivers/base/class.c b/drivers/base/class.c index 23dbc661d4a0..03243d4002fd 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -301,20 +301,15 @@ void class_destroy(struct class *cls) * otherwise if it is NULL, the iteration starts at the beginning of * the list. */ -int class_dev_iter_init(struct class_dev_iter *iter, struct class *class, - struct device *start, const struct device_type *type) +void class_dev_iter_init(struct class_dev_iter *iter, struct class *class, + struct device *start, const struct device_type *type) { struct klist_node *start_knode = NULL; - int error; if (start) start_knode = &start->knode_class; - error = klist_iter_init_node(&class->p->klist_devices, &iter->ki, - start_knode); - if (!error) - iter->type = type; - - return error; + klist_iter_init_node(&class->p->klist_devices, &iter->ki, start_knode); + iter->type = type; } EXPORT_SYMBOL_GPL(class_dev_iter_init); @@ -392,15 +387,14 @@ int class_for_each_device(struct class *class, struct device *start, return -EINVAL; } - error = class_dev_iter_init(&iter, class, start, NULL); - if (!error) { - while ((dev = class_dev_iter_next(&iter))) { - error = fn(dev, data); - if (error) - break; - } - class_dev_iter_exit(&iter); + class_dev_iter_init(&iter, class, start, NULL); + while ((dev = class_dev_iter_next(&iter))) { + error = fn(dev, data); + if (error) + break; } + class_dev_iter_exit(&iter); + return error; } EXPORT_SYMBOL_GPL(class_for_each_device); @@ -440,9 +434,7 @@ struct device *class_find_device(struct class *class, struct device *start, return NULL; } - if (class_dev_iter_init(&iter, class, start, NULL) < 0) - return NULL; - + class_dev_iter_init(&iter, class, start, NULL); while ((dev = class_dev_iter_next(&iter))) { if (match(dev, data)) { get_device(dev); diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 16f6dd2c4403..3ec3896c83a6 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -49,13 +49,11 @@ int driver_for_each_device(struct device_driver *drv, struct device *start, if (!drv) return -EINVAL; - error = klist_iter_init_node(&drv->p->klist_devices, &i, - start ? &start->p->knode_driver : NULL); - if (!error) { - while ((dev = next_device(&i)) && !error) - error = fn(dev, data); - klist_iter_exit(&i); - } + klist_iter_init_node(&drv->p->klist_devices, &i, + start ? &start->p->knode_driver : NULL); + while ((dev = next_device(&i)) && !error) + error = fn(dev, data); + klist_iter_exit(&i); return error; } EXPORT_SYMBOL_GPL(driver_for_each_device); @@ -85,10 +83,8 @@ struct device *driver_find_device(struct device_driver *drv, if (!drv) return NULL; - if (klist_iter_init_node(&drv->p->klist_devices, &i, - (start ? &start->p->knode_driver : NULL)) < 0) - return NULL; - + klist_iter_init_node(&drv->p->klist_devices, &i, + (start ? &start->p->knode_driver : NULL)); while ((dev = next_device(&i))) if (match(dev, data) && get_device(dev)) break; -- cgit v1.2.3 From de55d8716ac50a356cea736c29bb7db5ac3d0190 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:22 +0900 Subject: Extcon (external connector): import Android's switch class and modify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit External connector class (extcon) is based on and an extension of Android kernel's switch class located at linux/drivers/switch/. This patch provides the before-extension switch class moved to the location where the extcon will be located (linux/drivers/extcon/) and updates to handle class properly. The before-extension class, switch class of Android kernel, commits imported are: switch: switch class and GPIO drivers. (splitted) Author: Mike Lockwood switch: Use device_create instead of device_create_drvdata. Author: Arve Hjønnevåg In this patch, upon the commits of Android kernel, we have added: - Relocated and renamed for extcon. - Comments, module name, and author information are updated - Code clean for successing patches - Bugfix: enabling write access without write functions - Class/device/sysfs create/remove handling - Added comments about uevents - Format changes for extcon_dev_register() to have a parent dev. Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mark Brown -- Changes from v7 - Compiler error fixed when it is compiled as a module. - Removed out-of-date Kconfig entry Changes from v6 - Updated comment/strings - Revised "Android-compatible" mode. * Automatically activated if CONFIG_ANDROID && !CONFIG_ANDROID_SWITCH * Creates /sys/class/switch/*, which is a copy of /sys/class/extcon/* Changes from v5 - Split the patch - Style fixes - "Android-compatible" mode is enabled by Kconfig option. Changes from v2 - Updated name_show - Sysfs entries are handled by class itself. - Updated the method to add/remove devices for the class - Comments on uevent send - Able to become a module - Compatible with Android platform Changes from RFC - Renamed to extcon (external connector) from multistate switch - Added a seperated directory (drivers/extcon) - Added kerneldoc comments - Removed unused variables from extcon_gpio.c - Added ABI Documentation. Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/extcon/Kconfig | 17 +++ drivers/extcon/Makefile | 5 + drivers/extcon/extcon_class.c | 236 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 261 insertions(+) create mode 100644 drivers/extcon/Kconfig create mode 100644 drivers/extcon/Makefile create mode 100644 drivers/extcon/extcon_class.c (limited to 'drivers') diff --git a/drivers/Kconfig b/drivers/Kconfig index d236aef7e59f..0233ad979b7d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -140,4 +140,6 @@ source "drivers/virt/Kconfig" source "drivers/devfreq/Kconfig" +source "drivers/extcon/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 95952c82bf16..c41dfa92cd79 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -134,3 +134,4 @@ obj-$(CONFIG_VIRT_DRIVERS) += virt/ obj-$(CONFIG_HYPERV) += hv/ obj-$(CONFIG_PM_DEVFREQ) += devfreq/ +obj-$(CONFIG_EXTCON) += extcon/ diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig new file mode 100644 index 000000000000..7932e1bd9b02 --- /dev/null +++ b/drivers/extcon/Kconfig @@ -0,0 +1,17 @@ +menuconfig EXTCON + tristate "External Connector Class (extcon) support" + help + Say Y here to enable external connector class (extcon) support. + This allows monitoring external connectors by userspace + via sysfs and uevent and supports external connectors with + multiple states; i.e., an extcon that may have multiple + cables attached. For example, an external connector of a device + may be used to connect an HDMI cable and a AC adaptor, and to + host USB ports. Many of 30-pin connectors including PDMI are + also good examples. + +if EXTCON + +comment "Extcon Device Drivers" + +endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile new file mode 100644 index 000000000000..6bc69214fcd7 --- /dev/null +++ b/drivers/extcon/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for external connector class (extcon) devices +# + +obj-$(CONFIG_EXTCON) += extcon_class.o diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c new file mode 100644 index 000000000000..3c46368c279a --- /dev/null +++ b/drivers/extcon/extcon_class.c @@ -0,0 +1,236 @@ +/* + * drivers/extcon/extcon_class.c + * + * External connector (extcon) class driver + * + * Copyright (C) 2012 Samsung Electronics + * Author: Donggeun Kim + * Author: MyungJoo Ham + * + * based on android/drivers/switch/switch_class.c + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +struct class *extcon_class; +#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +static struct class_compat *switch_class; +#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); + + if (edev->print_state) { + int ret = edev->print_state(edev, buf); + + if (ret >= 0) + return ret; + /* Use default if failed */ + } + return sprintf(buf, "%u\n", edev->state); +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); + + /* Optional callback given by the user */ + if (edev->print_name) { + int ret = edev->print_name(edev, buf); + if (ret >= 0) + return ret; + } + + return sprintf(buf, "%s\n", dev_name(edev->dev)); +} + +/** + * extcon_set_state() - Set the cable attach states of the extcon device. + * @edev: the extcon device + * @state: new cable attach status for @edev + * + * Changing the state sends uevent with environment variable containing + * the name of extcon device (envp[0]) and the state output (envp[1]). + * Tizen uses this format for extcon device to get events from ports. + * Android uses this format as well. + */ +void extcon_set_state(struct extcon_dev *edev, u32 state) +{ + char name_buf[120]; + char state_buf[120]; + char *prop_buf; + char *envp[3]; + int env_offset = 0; + int length; + + if (edev->state != state) { + edev->state = state; + + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + if (prop_buf) { + length = name_show(edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(name_buf, sizeof(name_buf), + "NAME=%s", prop_buf); + envp[env_offset++] = name_buf; + } + length = state_show(edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(state_buf, sizeof(state_buf), + "STATE=%s", prop_buf); + envp[env_offset++] = state_buf; + } + envp[env_offset] = NULL; + kobject_uevent_env(&edev->dev->kobj, KOBJ_CHANGE, envp); + free_page((unsigned long)prop_buf); + } else { + dev_err(edev->dev, "out of memory in extcon_set_state\n"); + kobject_uevent(&edev->dev->kobj, KOBJ_CHANGE); + } + } +} +EXPORT_SYMBOL_GPL(extcon_set_state); + +static struct device_attribute extcon_attrs[] = { + __ATTR_RO(state), + __ATTR_RO(name), +}; + +static int create_extcon_class(void) +{ + if (!extcon_class) { + extcon_class = class_create(THIS_MODULE, "extcon"); + if (IS_ERR(extcon_class)) + return PTR_ERR(extcon_class); + extcon_class->dev_attrs = extcon_attrs; + +#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) + switch_class = class_compat_register("switch"); + if (WARN(!switch_class, "cannot allocate")) + return -ENOMEM; +#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + } + + return 0; +} + +static void extcon_cleanup(struct extcon_dev *edev, bool skip) +{ + if (!skip && get_device(edev->dev)) { + device_unregister(edev->dev); + put_device(edev->dev); + } + + kfree(edev->dev); +} + +static void extcon_dev_release(struct device *dev) +{ + struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); + + extcon_cleanup(edev, true); +} + +/** + * extcon_dev_register() - Register a new extcon device + * @edev : the new extcon device (should be allocated before calling) + * @dev : the parent device for this extcon device. + * + * Among the members of edev struct, please set the "user initializing data" + * in any case and set the "optional callbacks" if required. However, please + * do not set the values of "internal data", which are initialized by + * this function. + */ +int extcon_dev_register(struct extcon_dev *edev, struct device *dev) +{ + int ret; + + if (!extcon_class) { + ret = create_extcon_class(); + if (ret < 0) + return ret; + } + + edev->dev = kzalloc(sizeof(struct device), GFP_KERNEL); + edev->dev->parent = dev; + edev->dev->class = extcon_class; + edev->dev->release = extcon_dev_release; + + dev_set_name(edev->dev, edev->name ? edev->name : dev_name(dev)); + ret = device_register(edev->dev); + if (ret) { + put_device(edev->dev); + goto err_dev; + } +#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) + if (switch_class) + ret = class_compat_create_link(switch_class, edev->dev, + dev); +#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + + dev_set_drvdata(edev->dev, edev); + edev->state = 0; + return 0; + +err_dev: + kfree(edev->dev); + return ret; +} +EXPORT_SYMBOL_GPL(extcon_dev_register); + +/** + * extcon_dev_unregister() - Unregister the extcon device. + * @edev: the extcon device instance to be unregitered. + * + * Note that this does not call kfree(edev) because edev was not allocated + * by this class. + */ +void extcon_dev_unregister(struct extcon_dev *edev) +{ + extcon_cleanup(edev, false); +} +EXPORT_SYMBOL_GPL(extcon_dev_unregister); + +static int __init extcon_class_init(void) +{ + return create_extcon_class(); +} +module_init(extcon_class_init); + +static void __exit extcon_class_exit(void) +{ + class_destroy(extcon_class); +} +module_exit(extcon_class_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_AUTHOR("Donggeun Kim "); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("External connector (extcon) class driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From be48308a24c7651bf968b561dbd590edb8166d62 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:23 +0900 Subject: Extcon: support generic GPIO extcon driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generic GPIO extcon driver (an external connector device based on GPIO control) and imported from Android kernel. switch: switch class and GPIO drivers. (splitted) Author: Mike Lockwood switch: gpio: Don't call request_irq with interrupts disabled Author: Arve Hjønnevåg switch_gpio: Add missing #include Author: Mike Lockwood Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mark Brown -- Changed from v7: - Style updates mentioned by Stephen Boyd and Mark Brown Changed from v5: - Splitted at v5 from the main extcon patch. - Added debounce time for irq handlers. - Use request_any_context_irq instead of request_irq - User needs to specify irq flags for GPIO interrupts (was fixed to IRQF_TRIGGER_LOW before) - Use module_platform_driver(). Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/Kconfig | 7 ++ drivers/extcon/Makefile | 1 + drivers/extcon/extcon_gpio.c | 169 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 drivers/extcon/extcon_gpio.c (limited to 'drivers') diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 7932e1bd9b02..85cecff36db3 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -14,4 +14,11 @@ if EXTCON comment "Extcon Device Drivers" +config EXTCON_GPIO + tristate "GPIO extcon support" + depends on GENERIC_GPIO + help + Say Y here to enable GPIO based extcon support. Note that GPIO + extcon supports single state per extcon instance. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 6bc69214fcd7..2c46d4176d18 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_EXTCON) += extcon_class.o +obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon_gpio.c new file mode 100644 index 000000000000..5c0f08506f93 --- /dev/null +++ b/drivers/extcon/extcon_gpio.c @@ -0,0 +1,169 @@ +/* + * drivers/extcon/extcon_gpio.c + * + * Single-state GPIO extcon driver based on extcon class + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * Modified by MyungJoo Ham to support extcon + * (originally switch class is supported) + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_extcon_data { + struct extcon_dev edev; + unsigned gpio; + const char *state_on; + const char *state_off; + int irq; + struct delayed_work work; + unsigned long debounce_jiffies; +}; + +static void gpio_extcon_work(struct work_struct *work) +{ + int state; + struct gpio_extcon_data *data = + container_of(to_delayed_work(work), struct gpio_extcon_data, + work); + + state = gpio_get_value(data->gpio); + extcon_set_state(&data->edev, state); +} + +static irqreturn_t gpio_irq_handler(int irq, void *dev_id) +{ + struct gpio_extcon_data *extcon_data = dev_id; + + schedule_delayed_work(&extcon_data->work, + extcon_data->debounce_jiffies); + return IRQ_HANDLED; +} + +static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf) +{ + struct gpio_extcon_data *extcon_data = + container_of(edev, struct gpio_extcon_data, edev); + const char *state; + if (extcon_get_state(edev)) + state = extcon_data->state_on; + else + state = extcon_data->state_off; + + if (state) + return sprintf(buf, "%s\n", state); + return -EINVAL; +} + +static int __devinit gpio_extcon_probe(struct platform_device *pdev) +{ + struct gpio_extcon_platform_data *pdata = pdev->dev.platform_data; + struct gpio_extcon_data *extcon_data; + int ret = 0; + + if (!pdata) + return -EBUSY; + if (!pdata->irq_flags) { + dev_err(&pdev->dev, "IRQ flag is not specified.\n"); + return -EINVAL; + } + + extcon_data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data), + GFP_KERNEL); + if (!extcon_data) + return -ENOMEM; + + extcon_data->edev.name = pdata->name; + extcon_data->gpio = pdata->gpio; + extcon_data->state_on = pdata->state_on; + extcon_data->state_off = pdata->state_off; + if (pdata->state_on && pdata->state_off) + extcon_data->edev.print_state = extcon_gpio_print_state; + extcon_data->debounce_jiffies = msecs_to_jiffies(pdata->debounce); + + ret = extcon_dev_register(&extcon_data->edev, &pdev->dev); + if (ret < 0) + goto err_extcon_dev_register; + + ret = gpio_request_one(extcon_data->gpio, GPIOF_DIR_IN, pdev->name); + if (ret < 0) + goto err_request_gpio; + + INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); + + extcon_data->irq = gpio_to_irq(extcon_data->gpio); + if (extcon_data->irq < 0) { + ret = extcon_data->irq; + goto err_detect_irq_num_failed; + } + + ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, + pdata->irq_flags, pdev->name, + extcon_data); + if (ret < 0) + goto err_request_irq; + + /* Perform initial detection */ + gpio_extcon_work(&extcon_data->work.work); + + return 0; + +err_request_irq: +err_detect_irq_num_failed: + gpio_free(extcon_data->gpio); +err_request_gpio: + extcon_dev_unregister(&extcon_data->edev); +err_extcon_dev_register: + devm_kfree(&pdev->dev, extcon_data); + + return ret; +} + +static int __devexit gpio_extcon_remove(struct platform_device *pdev) +{ + struct gpio_extcon_data *extcon_data = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&extcon_data->work); + gpio_free(extcon_data->gpio); + extcon_dev_unregister(&extcon_data->edev); + devm_kfree(&pdev->dev, extcon_data); + + return 0; +} + +static struct platform_driver gpio_extcon = { + .probe = gpio_extcon_probe, + .remove = __devexit_p(gpio_extcon_remove), + .driver = { + .name = "extcon-gpio", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(gpio_extcon); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("GPIO extcon driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 74c5d09bd562edc220d6e076b8f1e118819c178f Mon Sep 17 00:00:00 2001 From: Donggeun Kim Date: Fri, 20 Apr 2012 14:16:24 +0900 Subject: Extcon: support notification based on the state changes. State changes of extcon devices have been notified via kobjet_uevent. This patch adds notifier interfaces in order to allow device drivers to get notified easily. Along with notifier interface, extcon_get_extcon_dev() function is added so that device drivers may discover a extcon_dev easily. Signed-off-by: Donggeun Kim Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mark Brown -- Changes from RFC - Renamed switch to extcon - Bugfix: extcon_dev_unregister() - Bugfix: "edev->dev" is "internal" data. - Added kerneldoc comments. - Reworded comments. Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) (limited to 'drivers') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 3c46368c279a..83a088f1edc4 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -36,6 +36,9 @@ struct class *extcon_class; static struct class_compat *switch_class; #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +static LIST_HEAD(extcon_dev_list); +static DEFINE_MUTEX(extcon_dev_list_lock); + static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -75,6 +78,9 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, * the name of extcon device (envp[0]) and the state output (envp[1]). * Tizen uses this format for extcon device to get events from ports. * Android uses this format as well. + * + * Note that notifier provides the which bits are changes in the state + * variable with "val" to the callback. */ void extcon_set_state(struct extcon_dev *edev, u32 state) { @@ -84,10 +90,14 @@ void extcon_set_state(struct extcon_dev *edev, u32 state) char *envp[3]; int env_offset = 0; int length; + u32 old_state = edev->state; if (edev->state != state) { edev->state = state; + raw_notifier_call_chain(&edev->nh, old_state ^ edev->state, + edev); + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); if (prop_buf) { length = name_show(edev->dev, NULL, prop_buf); @@ -117,6 +127,51 @@ void extcon_set_state(struct extcon_dev *edev, u32 state) } EXPORT_SYMBOL_GPL(extcon_set_state); +/** + * extcon_get_extcon_dev() - Get the extcon device instance from the name + * @extcon_name: The extcon name provided with extcon_dev_register() + */ +struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) +{ + struct extcon_dev *sd; + + mutex_lock(&extcon_dev_list_lock); + list_for_each_entry(sd, &extcon_dev_list, entry) { + if (!strcmp(sd->name, extcon_name)) + goto out; + } + sd = NULL; +out: + mutex_unlock(&extcon_dev_list_lock); + return sd; +} +EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); + +/** + * extcon_register_notifier() - Register a notifee to get notified by + * any attach status changes from the extcon. + * @edev: the extcon device. + * @nb: a notifier block to be registered. + */ +int extcon_register_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return raw_notifier_chain_register(&edev->nh, nb); +} +EXPORT_SYMBOL_GPL(extcon_register_notifier); + +/** + * extcon_unregister_notifier() - Unregister a notifee from the extcon device. + * @edev: the extcon device. + * @nb: a registered notifier block to be unregistered. + */ +int extcon_unregister_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return raw_notifier_chain_unregister(&edev->nh, nb); +} +EXPORT_SYMBOL_GPL(extcon_unregister_notifier); + static struct device_attribute extcon_attrs[] = { __ATTR_RO(state), __ATTR_RO(name), @@ -142,6 +197,10 @@ static int create_extcon_class(void) static void extcon_cleanup(struct extcon_dev *edev, bool skip) { + mutex_lock(&extcon_dev_list_lock); + list_del(&edev->entry); + mutex_unlock(&extcon_dev_list_lock); + if (!skip && get_device(edev->dev)) { device_unregister(edev->dev); put_device(edev->dev); @@ -194,8 +253,15 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) dev); #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + RAW_INIT_NOTIFIER_HEAD(&edev->nh); + dev_set_drvdata(edev->dev, edev); edev->state = 0; + + mutex_lock(&extcon_dev_list_lock); + list_add(&edev->entry, &extcon_dev_list); + mutex_unlock(&extcon_dev_list_lock); + return 0; err_dev: -- cgit v1.2.3 From 806d9dd71ff52ef09764585baaeec23afbb98560 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:25 +0900 Subject: Extcon: support multiple states at a device. One switch device (e.g., MUIC(MAX8997, MAX77686, ...), and some 30-pin devices) may have multiple cables attached. For example, one 30-pin port may inhabit a USB cable, an HDMI cable, and a mic. Thus, one switch device requires multiple state bits each representing a type of cable. For such purpose, we use the 32bit state variable; thus, up to 32 different type of cables may be defined for a switch device. The list of possible cables is defined by the array of cable names in the switch_dev struct given to the class. Signed-off-by: Chanwoo Choi Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park -- Changes from V7 - Bugfixed in _call_per_cable() (incorrect nb) (Chanwoo Choi) - Compiler error in header for !CONFIG_EXTCON (Chanwoo Choi) Changes from V5 - Sysfs style reformed: subdirectory per cable. - Updated standard cable names - Removed unnecessary printf - Bugfixes after testing Changes from V4 - Bugfixes after more testing at Exynos4412 boards with userspace processses. Changes from V3 - Bugfixes after more testing at Exynos4412 boards. Changes from V2 - State can be stored by user - Documentation updated Changes from RFC - Switch is renamed to extcon - Added kerneldoc comments - Added APIs to support "standard" cable names - Added helper APIs to support notifier block registration with cable name. - Regrouped function list in the header file. Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 439 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 426 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 83a088f1edc4..403933bc3e9c 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -31,6 +31,39 @@ #include #include +/* + * extcon_cable_name suggests the standard cable names for commonly used + * cable types. + * + * However, please do not use extcon_cable_name directly for extcon_dev + * struct's supported_cable pointer unless your device really supports + * every single port-type of the following cable names. Please choose cable + * names that are actually used in your extcon device. + */ +const char *extcon_cable_name[] = { + [EXTCON_USB] = "USB", + [EXTCON_USB_HOST] = "USB-Host", + [EXTCON_TA] = "TA", + [EXTCON_FAST_CHARGER] = "Fast-charger", + [EXTCON_SLOW_CHARGER] = "Slow-charger", + [EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream", + [EXTCON_HDMI] = "HDMI", + [EXTCON_MHL] = "MHL", + [EXTCON_DVI] = "DVI", + [EXTCON_VGA] = "VGA", + [EXTCON_DOCK] = "Dock", + [EXTCON_LINE_IN] = "Line-in", + [EXTCON_LINE_OUT] = "Line-out", + [EXTCON_MIC_IN] = "Microphone", + [EXTCON_HEADPHONE_OUT] = "Headphone", + [EXTCON_SPDIF_IN] = "SPDIF-in", + [EXTCON_SPDIF_OUT] = "SPDIF-out", + [EXTCON_VIDEO_IN] = "Video-in", + [EXTCON_VIDEO_OUT] = "Video-out", + + NULL, +}; + struct class *extcon_class; #if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) static struct class_compat *switch_class; @@ -42,6 +75,7 @@ static DEFINE_MUTEX(extcon_dev_list_lock); static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) { + int i, count = 0; struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); if (edev->print_state) { @@ -51,7 +85,39 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, return ret; /* Use default if failed */ } - return sprintf(buf, "%u\n", edev->state); + + if (edev->max_supported == 0) + return sprintf(buf, "%u\n", edev->state); + + for (i = 0; i < SUPPORTED_CABLE_MAX; i++) { + if (!edev->supported_cable[i]) + break; + count += sprintf(buf + count, "%s=%d\n", + edev->supported_cable[i], + !!(edev->state & (1 << i))); + } + + return count; +} + +void extcon_set_state(struct extcon_dev *edev, u32 state); +static ssize_t state_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 state; + ssize_t ret = 0; + struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); + + ret = sscanf(buf, "0x%x", &state); + if (ret == 0) + ret = -EINVAL; + else + extcon_set_state(edev, state); + + if (ret < 0) + return ret; + + return count; } static ssize_t name_show(struct device *dev, struct device_attribute *attr, @@ -69,9 +135,52 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%s\n", dev_name(edev->dev)); } +static ssize_t cable_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct extcon_cable *cable = container_of(attr, struct extcon_cable, + attr_name); + + return sprintf(buf, "%s\n", + cable->edev->supported_cable[cable->cable_index]); +} + +static ssize_t cable_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct extcon_cable *cable = container_of(attr, struct extcon_cable, + attr_state); + + return sprintf(buf, "%d\n", + extcon_get_cable_state_(cable->edev, + cable->cable_index)); +} + +static ssize_t cable_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct extcon_cable *cable = container_of(attr, struct extcon_cable, + attr_state); + int ret, state; + + ret = sscanf(buf, "%d", &state); + if (ret == 0) + ret = -EINVAL; + else + ret = extcon_set_cable_state_(cable->edev, cable->cable_index, + state); + + if (ret < 0) + return ret; + return count; +} + /** - * extcon_set_state() - Set the cable attach states of the extcon device. + * extcon_update_state() - Update the cable attach states of the extcon device + * only for the masked bits. * @edev: the extcon device + * @mask: the bit mask to designate updated bits. * @state: new cable attach status for @edev * * Changing the state sends uevent with environment variable containing @@ -79,10 +188,10 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, * Tizen uses this format for extcon device to get events from ports. * Android uses this format as well. * - * Note that notifier provides the which bits are changes in the state - * variable with "val" to the callback. + * Note that the notifier provides which bits are changed in the state + * variable with the val parameter (second) to the callback. */ -void extcon_set_state(struct extcon_dev *edev, u32 state) +void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) { char name_buf[120]; char state_buf[120]; @@ -90,15 +199,20 @@ void extcon_set_state(struct extcon_dev *edev, u32 state) char *envp[3]; int env_offset = 0; int length; - u32 old_state = edev->state; + unsigned long flags; - if (edev->state != state) { - edev->state = state; + spin_lock_irqsave(&edev->lock, flags); - raw_notifier_call_chain(&edev->nh, old_state ^ edev->state, - edev); + if (edev->state != ((edev->state & ~mask) | (state & mask))) { + u32 old_state = edev->state; - prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + edev->state &= ~mask; + edev->state |= state & mask; + + raw_notifier_call_chain(&edev->nh, old_state, edev); + + /* This could be in interrupt handler */ + prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); if (prop_buf) { length = name_show(edev->dev, NULL, prop_buf); if (length > 0) { @@ -117,16 +231,131 @@ void extcon_set_state(struct extcon_dev *edev, u32 state) envp[env_offset++] = state_buf; } envp[env_offset] = NULL; + /* Unlock early before uevent */ + spin_unlock_irqrestore(&edev->lock, flags); + kobject_uevent_env(&edev->dev->kobj, KOBJ_CHANGE, envp); free_page((unsigned long)prop_buf); } else { + /* Unlock early before uevent */ + spin_unlock_irqrestore(&edev->lock, flags); + dev_err(edev->dev, "out of memory in extcon_set_state\n"); kobject_uevent(&edev->dev->kobj, KOBJ_CHANGE); } + } else { + /* No changes */ + spin_unlock_irqrestore(&edev->lock, flags); } } +EXPORT_SYMBOL_GPL(extcon_update_state); + +/** + * extcon_set_state() - Set the cable attach states of the extcon device. + * @edev: the extcon device + * @state: new cable attach status for @edev + * + * Note that notifier provides which bits are changed in the state + * variable with the val parameter (second) to the callback. + */ +void extcon_set_state(struct extcon_dev *edev, u32 state) +{ + extcon_update_state(edev, 0xffffffff, state); +} EXPORT_SYMBOL_GPL(extcon_set_state); +/** + * extcon_find_cable_index() - Get the cable index based on the cable name. + * @edev: the extcon device that has the cable. + * @cable_name: cable name to be searched. + * + * Note that accessing a cable state based on cable_index is faster than + * cable_name because using cable_name induces a loop with strncmp(). + * Thus, when get/set_cable_state is repeatedly used, using cable_index + * is recommended. + */ +int extcon_find_cable_index(struct extcon_dev *edev, const char *cable_name) +{ + int i; + + if (edev->supported_cable) { + for (i = 0; edev->supported_cable[i]; i++) { + if (!strncmp(edev->supported_cable[i], + cable_name, CABLE_NAME_MAX)) + return i; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(extcon_find_cable_index); + +/** + * extcon_get_cable_state_() - Get the status of a specific cable. + * @edev: the extcon device that has the cable. + * @index: cable index that can be retrieved by extcon_find_cable_index(). + */ +int extcon_get_cable_state_(struct extcon_dev *edev, int index) +{ + if (index < 0 || (edev->max_supported && edev->max_supported <= index)) + return -EINVAL; + + return !!(edev->state & (1 << index)); +} +EXPORT_SYMBOL_GPL(extcon_get_cable_state_); + +/** + * extcon_get_cable_state() - Get the status of a specific cable. + * @edev: the extcon device that has the cable. + * @cable_name: cable name. + * + * Note that this is slower than extcon_get_cable_state_. + */ +int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name) +{ + return extcon_get_cable_state_(edev, extcon_find_cable_index + (edev, cable_name)); +} +EXPORT_SYMBOL_GPL(extcon_get_cable_state); + +/** + * extcon_get_cable_state_() - Set the status of a specific cable. + * @edev: the extcon device that has the cable. + * @index: cable index that can be retrieved by extcon_find_cable_index(). + * @cable_state: the new cable status. The default semantics is + * true: attached / false: detached. + */ +int extcon_set_cable_state_(struct extcon_dev *edev, + int index, bool cable_state) +{ + u32 state; + + if (index < 0 || (edev->max_supported && edev->max_supported <= index)) + return -EINVAL; + + state = cable_state ? (1 << index) : 0; + extcon_update_state(edev, 1 << index, state); + return 0; +} +EXPORT_SYMBOL_GPL(extcon_set_cable_state_); + +/** + * extcon_get_cable_state() - Set the status of a specific cable. + * @edev: the extcon device that has the cable. + * @cable_name: cable name. + * @cable_state: the new cable status. The default semantics is + * true: attached / false: detached. + * + * Note that this is slower than extcon_set_cable_state_. + */ +int extcon_set_cable_state(struct extcon_dev *edev, + const char *cable_name, bool cable_state) +{ + return extcon_set_cable_state_(edev, extcon_find_cable_index + (edev, cable_name), cable_state); +} +EXPORT_SYMBOL_GPL(extcon_set_cable_state); + /** * extcon_get_extcon_dev() - Get the extcon device instance from the name * @extcon_name: The extcon name provided with extcon_dev_register() @@ -147,11 +376,88 @@ out: } EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); +static int _call_per_cable(struct notifier_block *nb, unsigned long val, + void *ptr) +{ + struct extcon_specific_cable_nb *obj = container_of(nb, + struct extcon_specific_cable_nb, internal_nb); + struct extcon_dev *edev = ptr; + + if ((val & (1 << obj->cable_index)) != + (edev->state & (1 << obj->cable_index))) { + obj->previous_value = val; + return obj->user_nb->notifier_call(obj->user_nb, val, ptr); + } + + return NOTIFY_OK; +} + +/** + * extcon_register_interest() - Register a notifier for a state change of a + * specific cable, not a entier set of cables of a + * extcon device. + * @obj: an empty extcon_specific_cable_nb object to be returned. + * @extcon_name: the name of extcon device. + * @cable_name: the target cable name. + * @nb: the notifier block to get notified. + * + * Provide an empty extcon_specific_cable_nb. extcon_register_interest() sets + * the struct for you. + * + * extcon_register_interest is a helper function for those who want to get + * notification for a single specific cable's status change. If a user wants + * to get notification for any changes of all cables of a extcon device, + * he/she should use the general extcon_register_notifier(). + * + * Note that the second parameter given to the callback of nb (val) is + * "old_state", not the current state. The current state can be retrieved + * by looking at the third pameter (edev pointer)'s state value. + */ +int extcon_register_interest(struct extcon_specific_cable_nb *obj, + const char *extcon_name, const char *cable_name, + struct notifier_block *nb) +{ + if (!obj || !extcon_name || !cable_name || !nb) + return -EINVAL; + + obj->edev = extcon_get_extcon_dev(extcon_name); + if (!obj->edev) + return -ENODEV; + + obj->cable_index = extcon_find_cable_index(obj->edev, cable_name); + if (obj->cable_index < 0) + return -ENODEV; + + obj->user_nb = nb; + + obj->internal_nb.notifier_call = _call_per_cable; + + return raw_notifier_chain_register(&obj->edev->nh, &obj->internal_nb); +} + +/** + * extcon_unregister_interest() - Unregister the notifier registered by + * extcon_register_interest(). + * @obj: the extcon_specific_cable_nb object returned by + * extcon_register_interest(). + */ +int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) +{ + if (!obj) + return -EINVAL; + + return raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); +} + /** * extcon_register_notifier() - Register a notifee to get notified by * any attach status changes from the extcon. * @edev: the extcon device. * @nb: a notifier block to be registered. + * + * Note that the second parameter given to the callback of nb (val) is + * "old_state", not the current state. The current state can be retrieved + * by looking at the third pameter (edev pointer)'s state value. */ int extcon_register_notifier(struct extcon_dev *edev, struct notifier_block *nb) @@ -173,8 +479,9 @@ int extcon_unregister_notifier(struct extcon_dev *edev, EXPORT_SYMBOL_GPL(extcon_unregister_notifier); static struct device_attribute extcon_attrs[] = { - __ATTR_RO(state), + __ATTR(state, S_IRUGO | S_IWUSR, state_show, state_store), __ATTR_RO(name), + __ATTR_NULL, }; static int create_extcon_class(void) @@ -202,6 +509,16 @@ static void extcon_cleanup(struct extcon_dev *edev, bool skip) mutex_unlock(&extcon_dev_list_lock); if (!skip && get_device(edev->dev)) { + int index; + + for (index = 0; index < edev->max_supported; index++) + kfree(edev->cables[index].attr_g.name); + + if (edev->max_supported) { + kfree(edev->extcon_dev_type.groups); + kfree(edev->cables); + } + device_unregister(edev->dev); put_device(edev->dev); } @@ -216,6 +533,10 @@ static void extcon_dev_release(struct device *dev) extcon_cleanup(edev, true); } +static void dummy_sysfs_dev_release(struct device *dev) +{ +} + /** * extcon_dev_register() - Register a new extcon device * @edev : the new extcon device (should be allocated before calling) @@ -228,7 +549,7 @@ static void extcon_dev_release(struct device *dev) */ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) { - int ret; + int ret, index = 0; if (!extcon_class) { ret = create_extcon_class(); @@ -236,12 +557,93 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) return ret; } + if (edev->supported_cable) { + /* Get size of array */ + for (index = 0; edev->supported_cable[index]; index++) + ; + edev->max_supported = index; + } else { + edev->max_supported = 0; + } + + if (index > SUPPORTED_CABLE_MAX) { + dev_err(edev->dev, "extcon: maximum number of supported cables exceeded.\n"); + return -EINVAL; + } + edev->dev = kzalloc(sizeof(struct device), GFP_KERNEL); edev->dev->parent = dev; edev->dev->class = extcon_class; edev->dev->release = extcon_dev_release; dev_set_name(edev->dev, edev->name ? edev->name : dev_name(dev)); + + if (edev->max_supported) { + char buf[10]; + char *str; + struct extcon_cable *cable; + + edev->cables = kzalloc(sizeof(struct extcon_cable) * + edev->max_supported, GFP_KERNEL); + if (!edev->cables) { + ret = -ENOMEM; + goto err_sysfs_alloc; + } + for (index = 0; index < edev->max_supported; index++) { + cable = &edev->cables[index]; + + snprintf(buf, 10, "cable.%d", index); + str = kzalloc(sizeof(char) * (strlen(buf) + 1), + GFP_KERNEL); + if (!str) { + for (index--; index >= 0; index--) { + cable = &edev->cables[index]; + kfree(cable->attr_g.name); + } + ret = -ENOMEM; + + goto err_alloc_cables; + } + strcpy(str, buf); + + cable->edev = edev; + cable->cable_index = index; + cable->attrs[0] = &cable->attr_name.attr; + cable->attrs[1] = &cable->attr_state.attr; + cable->attrs[2] = NULL; + cable->attr_g.name = str; + cable->attr_g.attrs = cable->attrs; + + cable->attr_name.attr.name = "name"; + cable->attr_name.attr.mode = 0444; + cable->attr_name.show = cable_name_show; + + cable->attr_state.attr.name = "state"; + cable->attr_state.attr.mode = 0644; + cable->attr_state.show = cable_state_show; + cable->attr_state.store = cable_state_store; + } + } + + if (edev->max_supported) { + edev->extcon_dev_type.groups = + kzalloc(sizeof(struct attribute_group *) * + (edev->max_supported + 1), GFP_KERNEL); + if (!edev->extcon_dev_type.groups) { + ret = -ENOMEM; + goto err_alloc_groups; + } + + edev->extcon_dev_type.name = dev_name(edev->dev); + edev->extcon_dev_type.release = dummy_sysfs_dev_release; + + for (index = 0; index < edev->max_supported; index++) + edev->extcon_dev_type.groups[index] = + &edev->cables[index].attr_g; + + edev->dev->type = &edev->extcon_dev_type; + } + ret = device_register(edev->dev); if (ret) { put_device(edev->dev); @@ -253,6 +655,8 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) dev); #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + spin_lock_init(&edev->lock); + RAW_INIT_NOTIFIER_HEAD(&edev->nh); dev_set_drvdata(edev->dev, edev); @@ -265,6 +669,15 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) return 0; err_dev: + if (edev->max_supported) + kfree(edev->extcon_dev_type.groups); +err_alloc_groups: + for (index = 0; index < edev->max_supported; index++) + kfree(edev->cables[index].attr_g.name); +err_alloc_cables: + if (edev->max_supported) + kfree(edev->cables); +err_sysfs_alloc: kfree(edev->dev); return ret; } -- cgit v1.2.3 From bde68e60b18208978c50c6fb9bdf29826d2887f3 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:26 +0900 Subject: Extcon: support mutually exclusive relation between cables. There could be cables that t recannot be attaches simulatenously. Extcon device drivers may express such information via mutually_exclusive in struct extcon_dev. For example, for an extcon device with 16 cables (bits 0 to 15 are available), if mutually_exclusive = { 0x7, 0xC0, 0x81, 0 }, then, the following attachments are prohibitted. {0, 1} {0, 2} {1, 2} {6, 7} {0, 7} and every attachment set that are superset of one of the above. For the detail, please refer to linux/include/linux/extcon.h. The concept is suggested by NeilBrown Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park -- Changes from V5: - Updated sysfs format Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 123 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 403933bc3e9c..3bc4b8af46cf 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -72,6 +72,39 @@ static struct class_compat *switch_class; static LIST_HEAD(extcon_dev_list); static DEFINE_MUTEX(extcon_dev_list_lock); +/** + * check_mutually_exclusive - Check if new_state violates mutually_exclusive + * condition. + * @edev: the extcon device + * @new_state: new cable attach status for @edev + * + * Returns 0 if nothing violates. Returns the index + 1 for the first + * violated condition. + */ +static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state) +{ + int i = 0; + + if (!edev->mutually_exclusive) + return 0; + + for (i = 0; edev->mutually_exclusive[i]; i++) { + int count = 0, j; + u32 correspondants = new_state & edev->mutually_exclusive[i]; + u32 exp = 1; + + for (j = 0; j < 32; j++) { + if (exp & correspondants) + count++; + if (count > 1) + return i + 1; + exp <<= 1; + } + } + + return 0; +} + static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -100,7 +133,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, return count; } -void extcon_set_state(struct extcon_dev *edev, u32 state); +int extcon_set_state(struct extcon_dev *edev, u32 state); static ssize_t state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -112,7 +145,7 @@ static ssize_t state_store(struct device *dev, struct device_attribute *attr, if (ret == 0) ret = -EINVAL; else - extcon_set_state(edev, state); + ret = extcon_set_state(edev, state); if (ret < 0) return ret; @@ -191,7 +224,7 @@ static ssize_t cable_state_store(struct device *dev, * Note that the notifier provides which bits are changed in the state * variable with the val parameter (second) to the callback. */ -void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) +int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) { char name_buf[120]; char state_buf[120]; @@ -206,6 +239,12 @@ void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) if (edev->state != ((edev->state & ~mask) | (state & mask))) { u32 old_state = edev->state; + if (check_mutually_exclusive(edev, (edev->state & ~mask) | + (state & mask))) { + spin_unlock_irqrestore(&edev->lock, flags); + return -EPERM; + } + edev->state &= ~mask; edev->state |= state & mask; @@ -247,6 +286,8 @@ void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) /* No changes */ spin_unlock_irqrestore(&edev->lock, flags); } + + return 0; } EXPORT_SYMBOL_GPL(extcon_update_state); @@ -258,9 +299,9 @@ EXPORT_SYMBOL_GPL(extcon_update_state); * Note that notifier provides which bits are changed in the state * variable with the val parameter (second) to the callback. */ -void extcon_set_state(struct extcon_dev *edev, u32 state) +int extcon_set_state(struct extcon_dev *edev, u32 state) { - extcon_update_state(edev, 0xffffffff, state); + return extcon_update_state(edev, 0xffffffff, state); } EXPORT_SYMBOL_GPL(extcon_set_state); @@ -334,8 +375,7 @@ int extcon_set_cable_state_(struct extcon_dev *edev, return -EINVAL; state = cable_state ? (1 << index) : 0; - extcon_update_state(edev, 1 << index, state); - return 0; + return extcon_update_state(edev, 1 << index, state); } EXPORT_SYMBOL_GPL(extcon_set_cable_state_); @@ -511,6 +551,14 @@ static void extcon_cleanup(struct extcon_dev *edev, bool skip) if (!skip && get_device(edev->dev)) { int index; + if (edev->mutually_exclusive && edev->max_supported) { + for (index = 0; edev->mutually_exclusive[index]; + index++) + kfree(edev->d_attrs_muex[index].attr.name); + kfree(edev->d_attrs_muex); + kfree(edev->attrs_muex); + } + for (index = 0; index < edev->max_supported; index++) kfree(edev->cables[index].attr_g.name); @@ -533,6 +581,7 @@ static void extcon_dev_release(struct device *dev) extcon_cleanup(edev, true); } +static const char *muex_name = "mutually_exclusive"; static void dummy_sysfs_dev_release(struct device *dev) { } @@ -625,10 +674,58 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) } } + if (edev->max_supported && edev->mutually_exclusive) { + char buf[80]; + char *name; + + /* Count the size of mutually_exclusive array */ + for (index = 0; edev->mutually_exclusive[index]; index++) + ; + + edev->attrs_muex = kzalloc(sizeof(struct attribute *) * + (index + 1), GFP_KERNEL); + if (!edev->attrs_muex) { + ret = -ENOMEM; + goto err_muex; + } + + edev->d_attrs_muex = kzalloc(sizeof(struct device_attribute) * + index, GFP_KERNEL); + if (!edev->d_attrs_muex) { + ret = -ENOMEM; + kfree(edev->attrs_muex); + goto err_muex; + } + + for (index = 0; edev->mutually_exclusive[index]; index++) { + sprintf(buf, "0x%x", edev->mutually_exclusive[index]); + name = kzalloc(sizeof(char) * (strlen(buf) + 1), + GFP_KERNEL); + if (!name) { + for (index--; index >= 0; index--) { + kfree(edev->d_attrs_muex[index].attr. + name); + } + kfree(edev->d_attrs_muex); + kfree(edev->attrs_muex); + ret = -ENOMEM; + goto err_muex; + } + strcpy(name, buf); + edev->d_attrs_muex[index].attr.name = name; + edev->d_attrs_muex[index].attr.mode = 0000; + edev->attrs_muex[index] = &edev->d_attrs_muex[index] + .attr; + } + edev->attr_g_muex.name = muex_name; + edev->attr_g_muex.attrs = edev->attrs_muex; + + } + if (edev->max_supported) { edev->extcon_dev_type.groups = kzalloc(sizeof(struct attribute_group *) * - (edev->max_supported + 1), GFP_KERNEL); + (edev->max_supported + 2), GFP_KERNEL); if (!edev->extcon_dev_type.groups) { ret = -ENOMEM; goto err_alloc_groups; @@ -640,6 +737,9 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) for (index = 0; index < edev->max_supported; index++) edev->extcon_dev_type.groups[index] = &edev->cables[index].attr_g; + if (edev->mutually_exclusive) + edev->extcon_dev_type.groups[index] = + &edev->attr_g_muex; edev->dev->type = &edev->extcon_dev_type; } @@ -672,6 +772,13 @@ err_dev: if (edev->max_supported) kfree(edev->extcon_dev_type.groups); err_alloc_groups: + if (edev->max_supported && edev->mutually_exclusive) { + for (index = 0; edev->mutually_exclusive[index]; index++) + kfree(edev->d_attrs_muex[index].attr.name); + kfree(edev->d_attrs_muex); + kfree(edev->attrs_muex); + } +err_muex: for (index = 0; index < edev->max_supported; index++) kfree(edev->cables[index].attr_g.name); err_alloc_cables: -- cgit v1.2.3 From 449a2bf5e881b2a00d42a7c0baa67119c8cb5dce Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Mon, 23 Apr 2012 20:19:57 +0900 Subject: Remove "switch" class in drivers/staging/android/switch Because extcon can also be a switch class for legacy userspace (Android) and is a superset of switch class in drivers/staging/android/switch, switch class may be removed. - Remove switch class - Remove switch class consideration in extcon class Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 12 +- drivers/staging/android/Kconfig | 3 - drivers/staging/android/Makefile | 1 - drivers/staging/android/switch/Kconfig | 11 -- drivers/staging/android/switch/Makefile | 4 - drivers/staging/android/switch/switch.h | 53 -------- drivers/staging/android/switch/switch_class.c | 174 -------------------------- drivers/staging/android/switch/switch_gpio.c | 172 ------------------------- 8 files changed, 6 insertions(+), 424 deletions(-) delete mode 100644 drivers/staging/android/switch/Kconfig delete mode 100644 drivers/staging/android/switch/Makefile delete mode 100644 drivers/staging/android/switch/switch.h delete mode 100644 drivers/staging/android/switch/switch_class.c delete mode 100644 drivers/staging/android/switch/switch_gpio.c (limited to 'drivers') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 3bc4b8af46cf..dbd3bfba42da 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -65,9 +65,9 @@ const char *extcon_cable_name[] = { }; struct class *extcon_class; -#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +#if defined(CONFIG_ANDROID) static struct class_compat *switch_class; -#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +#endif /* CONFIG_ANDROID */ static LIST_HEAD(extcon_dev_list); static DEFINE_MUTEX(extcon_dev_list_lock); @@ -532,11 +532,11 @@ static int create_extcon_class(void) return PTR_ERR(extcon_class); extcon_class->dev_attrs = extcon_attrs; -#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +#if defined(CONFIG_ANDROID) switch_class = class_compat_register("switch"); if (WARN(!switch_class, "cannot allocate")) return -ENOMEM; -#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +#endif /* CONFIG_ANDROID */ } return 0; @@ -749,11 +749,11 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) put_device(edev->dev); goto err_dev; } -#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +#if defined(CONFIG_ANDROID) if (switch_class) ret = class_compat_create_link(switch_class, edev->dev, dev); -#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +#endif /* CONFIG_ANDROID */ spin_lock_init(&edev->lock); diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig index 08a3b1133d29..60dc7108c8b3 100644 --- a/drivers/staging/android/Kconfig +++ b/drivers/staging/android/Kconfig @@ -52,8 +52,6 @@ config ANDROID_LOW_MEMORY_KILLER ---help--- Register processes to be killed when memory is low -source "drivers/staging/android/switch/Kconfig" - config ANDROID_INTF_ALARM bool "Android alarm driver" depends on RTC_CLASS @@ -79,7 +77,6 @@ config ANDROID_ALARM_OLDDRV_COMPAT Provides preprocessor alias to aid compatability with older out-of-tree drivers that use the Android Alarm in-kernel API. This will be removed eventually. - endif # if ANDROID endmenu diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile index 9b6c9ed91f69..045d17bde524 100644 --- a/drivers/staging/android/Makefile +++ b/drivers/staging/android/Makefile @@ -6,6 +6,5 @@ obj-$(CONFIG_ANDROID_RAM_CONSOLE) += ram_console.o obj-$(CONFIG_ANDROID_TIMED_OUTPUT) += timed_output.o obj-$(CONFIG_ANDROID_TIMED_GPIO) += timed_gpio.o obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o -obj-$(CONFIG_ANDROID_SWITCH) += switch/ obj-$(CONFIG_ANDROID_INTF_ALARM) += alarm.o obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o diff --git a/drivers/staging/android/switch/Kconfig b/drivers/staging/android/switch/Kconfig deleted file mode 100644 index 36846f62f4bc..000000000000 --- a/drivers/staging/android/switch/Kconfig +++ /dev/null @@ -1,11 +0,0 @@ -menuconfig ANDROID_SWITCH - tristate "Android Switch class support" - help - Say Y here to enable Android switch class support. This allows - monitoring switches by userspace via sysfs and uevent. - -config ANDROID_SWITCH_GPIO - tristate "Android GPIO Switch support" - depends on GENERIC_GPIO && ANDROID_SWITCH - help - Say Y here to enable GPIO based switch support. diff --git a/drivers/staging/android/switch/Makefile b/drivers/staging/android/switch/Makefile deleted file mode 100644 index d76bfdcedfaf..000000000000 --- a/drivers/staging/android/switch/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# Android Switch Class Driver -obj-$(CONFIG_ANDROID_SWITCH) += switch_class.o -obj-$(CONFIG_ANDROID_SWITCH_GPIO) += switch_gpio.o - diff --git a/drivers/staging/android/switch/switch.h b/drivers/staging/android/switch/switch.h deleted file mode 100644 index 4fcb3109875a..000000000000 --- a/drivers/staging/android/switch/switch.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Switch class driver - * - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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. - * -*/ - -#ifndef __LINUX_SWITCH_H__ -#define __LINUX_SWITCH_H__ - -struct switch_dev { - const char *name; - struct device *dev; - int index; - int state; - - ssize_t (*print_name)(struct switch_dev *sdev, char *buf); - ssize_t (*print_state)(struct switch_dev *sdev, char *buf); -}; - -struct gpio_switch_platform_data { - const char *name; - unsigned gpio; - - /* if NULL, switch_dev.name will be printed */ - const char *name_on; - const char *name_off; - /* if NULL, "0" or "1" will be printed */ - const char *state_on; - const char *state_off; -}; - -extern int switch_dev_register(struct switch_dev *sdev); -extern void switch_dev_unregister(struct switch_dev *sdev); - -static inline int switch_get_state(struct switch_dev *sdev) -{ - return sdev->state; -} - -extern void switch_set_state(struct switch_dev *sdev, int state); - -#endif /* __LINUX_SWITCH_H__ */ diff --git a/drivers/staging/android/switch/switch_class.c b/drivers/staging/android/switch/switch_class.c deleted file mode 100644 index 74680446fc66..000000000000 --- a/drivers/staging/android/switch/switch_class.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * switch_class.c - * - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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 -#include -#include -#include -#include -#include -#include "switch.h" - -struct class *switch_class; -static atomic_t device_count; - -static ssize_t state_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct switch_dev *sdev = (struct switch_dev *) - dev_get_drvdata(dev); - - if (sdev->print_state) { - int ret = sdev->print_state(sdev, buf); - if (ret >= 0) - return ret; - } - return sprintf(buf, "%d\n", sdev->state); -} - -static ssize_t name_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct switch_dev *sdev = (struct switch_dev *) - dev_get_drvdata(dev); - - if (sdev->print_name) { - int ret = sdev->print_name(sdev, buf); - if (ret >= 0) - return ret; - } - return sprintf(buf, "%s\n", sdev->name); -} - -static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, state_show, NULL); -static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, name_show, NULL); - -void switch_set_state(struct switch_dev *sdev, int state) -{ - char name_buf[120]; - char state_buf[120]; - char *prop_buf; - char *envp[3]; - int env_offset = 0; - int length; - - if (sdev->state != state) { - sdev->state = state; - - prop_buf = (char *)get_zeroed_page(GFP_KERNEL); - if (prop_buf) { - length = name_show(sdev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(name_buf, sizeof(name_buf), - "SWITCH_NAME=%s", prop_buf); - envp[env_offset++] = name_buf; - } - length = state_show(sdev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(state_buf, sizeof(state_buf), - "SWITCH_STATE=%s", prop_buf); - envp[env_offset++] = state_buf; - } - envp[env_offset] = NULL; - kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp); - free_page((unsigned long)prop_buf); - } else { - printk(KERN_ERR "out of memory in switch_set_state\n"); - kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE); - } - } -} -EXPORT_SYMBOL_GPL(switch_set_state); - -static int create_switch_class(void) -{ - if (!switch_class) { - switch_class = class_create(THIS_MODULE, "switch"); - if (IS_ERR(switch_class)) - return PTR_ERR(switch_class); - atomic_set(&device_count, 0); - } - - return 0; -} - -int switch_dev_register(struct switch_dev *sdev) -{ - int ret; - - if (!switch_class) { - ret = create_switch_class(); - if (ret < 0) - return ret; - } - - sdev->index = atomic_inc_return(&device_count); - sdev->dev = device_create(switch_class, NULL, - MKDEV(0, sdev->index), NULL, sdev->name); - if (IS_ERR(sdev->dev)) - return PTR_ERR(sdev->dev); - - ret = device_create_file(sdev->dev, &dev_attr_state); - if (ret < 0) - goto err_create_file_1; - ret = device_create_file(sdev->dev, &dev_attr_name); - if (ret < 0) - goto err_create_file_2; - - dev_set_drvdata(sdev->dev, sdev); - sdev->state = 0; - return 0; - -err_create_file_2: - device_remove_file(sdev->dev, &dev_attr_state); -err_create_file_1: - device_destroy(switch_class, MKDEV(0, sdev->index)); - printk(KERN_ERR "switch: Failed to register driver %s\n", sdev->name); - - return ret; -} -EXPORT_SYMBOL_GPL(switch_dev_register); - -void switch_dev_unregister(struct switch_dev *sdev) -{ - device_remove_file(sdev->dev, &dev_attr_name); - device_remove_file(sdev->dev, &dev_attr_state); - device_destroy(switch_class, MKDEV(0, sdev->index)); - dev_set_drvdata(sdev->dev, NULL); -} -EXPORT_SYMBOL_GPL(switch_dev_unregister); - -static int __init switch_class_init(void) -{ - return create_switch_class(); -} - -static void __exit switch_class_exit(void) -{ - class_destroy(switch_class); -} - -module_init(switch_class_init); -module_exit(switch_class_exit); - -MODULE_AUTHOR("Mike Lockwood "); -MODULE_DESCRIPTION("Switch class driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/switch/switch_gpio.c b/drivers/staging/android/switch/switch_gpio.c deleted file mode 100644 index 38b2c2f6004e..000000000000 --- a/drivers/staging/android/switch/switch_gpio.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * switch_gpio.c - * - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include "switch.h" - -struct gpio_switch_data { - struct switch_dev sdev; - unsigned gpio; - const char *name_on; - const char *name_off; - const char *state_on; - const char *state_off; - int irq; - struct work_struct work; -}; - -static void gpio_switch_work(struct work_struct *work) -{ - int state; - struct gpio_switch_data *data = - container_of(work, struct gpio_switch_data, work); - - state = gpio_get_value(data->gpio); - switch_set_state(&data->sdev, state); -} - -static irqreturn_t gpio_irq_handler(int irq, void *dev_id) -{ - struct gpio_switch_data *switch_data = - (struct gpio_switch_data *)dev_id; - - schedule_work(&switch_data->work); - return IRQ_HANDLED; -} - -static ssize_t switch_gpio_print_state(struct switch_dev *sdev, char *buf) -{ - struct gpio_switch_data *switch_data = - container_of(sdev, struct gpio_switch_data, sdev); - const char *state; - if (switch_get_state(sdev)) - state = switch_data->state_on; - else - state = switch_data->state_off; - - if (state) - return sprintf(buf, "%s\n", state); - return -1; -} - -static int gpio_switch_probe(struct platform_device *pdev) -{ - struct gpio_switch_platform_data *pdata = pdev->dev.platform_data; - struct gpio_switch_data *switch_data; - int ret = 0; - - if (!pdata) - return -EBUSY; - - switch_data = kzalloc(sizeof(struct gpio_switch_data), GFP_KERNEL); - if (!switch_data) - return -ENOMEM; - - switch_data->sdev.name = pdata->name; - switch_data->gpio = pdata->gpio; - switch_data->name_on = pdata->name_on; - switch_data->name_off = pdata->name_off; - switch_data->state_on = pdata->state_on; - switch_data->state_off = pdata->state_off; - switch_data->sdev.print_state = switch_gpio_print_state; - - ret = switch_dev_register(&switch_data->sdev); - if (ret < 0) - goto err_switch_dev_register; - - ret = gpio_request(switch_data->gpio, pdev->name); - if (ret < 0) - goto err_request_gpio; - - ret = gpio_direction_input(switch_data->gpio); - if (ret < 0) - goto err_set_gpio_input; - - INIT_WORK(&switch_data->work, gpio_switch_work); - - switch_data->irq = gpio_to_irq(switch_data->gpio); - if (switch_data->irq < 0) { - ret = switch_data->irq; - goto err_detect_irq_num_failed; - } - - ret = request_irq(switch_data->irq, gpio_irq_handler, - IRQF_TRIGGER_LOW, pdev->name, switch_data); - if (ret < 0) - goto err_request_irq; - - /* Perform initial detection */ - gpio_switch_work(&switch_data->work); - - return 0; - -err_request_irq: -err_detect_irq_num_failed: -err_set_gpio_input: - gpio_free(switch_data->gpio); -err_request_gpio: - switch_dev_unregister(&switch_data->sdev); -err_switch_dev_register: - kfree(switch_data); - - return ret; -} - -static int __devexit gpio_switch_remove(struct platform_device *pdev) -{ - struct gpio_switch_data *switch_data = platform_get_drvdata(pdev); - - cancel_work_sync(&switch_data->work); - gpio_free(switch_data->gpio); - switch_dev_unregister(&switch_data->sdev); - kfree(switch_data); - - return 0; -} - -static struct platform_driver gpio_switch_driver = { - .probe = gpio_switch_probe, - .remove = __devexit_p(gpio_switch_remove), - .driver = { - .name = "switch-gpio", - .owner = THIS_MODULE, - }, -}; - -static int __init gpio_switch_init(void) -{ - return platform_driver_register(&gpio_switch_driver); -} - -static void __exit gpio_switch_exit(void) -{ - platform_driver_unregister(&gpio_switch_driver); -} - -module_init(gpio_switch_init); -module_exit(gpio_switch_exit); - -MODULE_AUTHOR("Mike Lockwood "); -MODULE_DESCRIPTION("GPIO Switch driver"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 9169c01236ab29ce55c93aaf22ec6ecc65c46d1a Mon Sep 17 00:00:00 2001 From: yan Date: Fri, 20 Apr 2012 21:08:45 +0800 Subject: drivers/base/core.c: Fix a typo in comment Signed-off-by: YanHong Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/base/core.c b/drivers/base/core.c index 6cb25f1511df..33461ec35d8f 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -65,7 +65,7 @@ static inline int device_is_not_partition(struct device *dev) * @dev: struct device to get the name of * * Will return the device's driver's name if it is bound to a device. If - * the device is not bound to a device, it will return the name of the bus + * the device is not bound to a driver, it will return the name of the bus * it is attached to. If it is not attached to a bus either, an empty * string will be returned. */ -- cgit v1.2.3 From a1d26ac0ddc4ea561e17a75dd3b5f9d3c1812f16 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 25 Apr 2012 11:47:02 +0300 Subject: Extcon: check for allocation failure Return -ENOMEM if the kmalloc() fails. Signed-off-by: Dan Carpenter Acked-by: MyungJoo Ham Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index dbd3bfba42da..53c64a98b0be 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -621,6 +621,8 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) } edev->dev = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!edev->dev) + return -ENOMEM; edev->dev->parent = dev; edev->dev->class = extcon_class; edev->dev->release = extcon_dev_release; -- cgit v1.2.3 From f4cce69611ee941bac0729c6069795f106905ef9 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 27 Apr 2012 15:17:28 +0900 Subject: Extcon: Notify changed state for only one cable to notifee This patch inform the state of only one cable instead of previous data including the state of 32 cables to notifee which use extcon_register_interest() function to monitor whether the specific cable is attachd or detached. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 53c64a98b0be..4657ad38164b 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -425,8 +425,15 @@ static int _call_per_cable(struct notifier_block *nb, unsigned long val, if ((val & (1 << obj->cable_index)) != (edev->state & (1 << obj->cable_index))) { + bool cable_state = true; + obj->previous_value = val; - return obj->user_nb->notifier_call(obj->user_nb, val, ptr); + + if (val & (1 << obj->cable_index)) + cable_state = false; + + return obj->user_nb->notifier_call(obj->user_nb, + cable_state, ptr); } return NOTIFY_OK; -- cgit v1.2.3 From c0a6720977f89c50473366af965ae28cd8fce923 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sun, 29 Apr 2012 22:12:08 -0400 Subject: Revert "w1: Add 1-wire slave device driver for DS28E04-100" This reverts commit f19420c1acb0b573c88a12deb2d42035e22d4a17. It contained lots of errors and warnings and shouldn't have ever been applied, that was my fault, sorry. Cc: Markus Franke Cc: Evgeniy Polyakov , Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/Kconfig | 7 - drivers/w1/slaves/Makefile | 1 - drivers/w1/slaves/w1_ds28e04.c | 464 ----------------------------------------- drivers/w1/w1_family.h | 1 - 4 files changed, 473 deletions(-) delete mode 100644 drivers/w1/slaves/w1_ds28e04.c (limited to 'drivers') diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index 605ee36c96f8..eb9e376d6244 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -94,13 +94,6 @@ config W1_SLAVE_DS2781 If you are unsure, say N. -config W1_SLAVE_DS28E04 - tristate "4096-Bit Addressable 1-Wire EEPROM with PIO (DS28E04-100)" - depends on W1 - help - Say Y here if you want to use a 1-wire - 4kb EEPROM with PIO family device (DS28E04). - config W1_SLAVE_BQ27000 tristate "BQ27000 slave support" depends on W1 diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index 05188f6aab5a..c4f1859fb520 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -12,4 +12,3 @@ obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o -obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o diff --git a/drivers/w1/slaves/w1_ds28e04.c b/drivers/w1/slaves/w1_ds28e04.c deleted file mode 100644 index 4aa1aa90480d..000000000000 --- a/drivers/w1/slaves/w1_ds28e04.c +++ /dev/null @@ -1,464 +0,0 @@ -/* - * w1_ds28e04.c - w1 family 1C (DS28E04) driver - * - * Copyright (c) 2012 Markus Franke - * - * This source code is licensed under the GNU General Public License, - * Version 2. See the file COPYING for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define CRC16_INIT 0 -#define CRC16_VALID 0xb001 - -#include "../w1.h" -#include "../w1_int.h" -#include "../w1_family.h" - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Markus Franke , "); -MODULE_DESCRIPTION("w1 family 1C driver for DS28E04, 4kb EEPROM and PIO"); - -/* Allow the strong pullup to be disabled, but default to enabled. - * If it was disabled a parasite powered device might not get the required - * current to copy the data from the scratchpad to EEPROM. If it is enabled parasite powered - * devices have a better chance of getting the current required. - */ -static int w1_strong_pullup = 1; -module_param_named(strong_pullup, w1_strong_pullup, int, 0); - -/* enable/disable CRC checking on DS28E04-100 memory accesses */ -static char w1_enable_crccheck = 1; - -#define W1_EEPROM_SIZE 512 -#define W1_PAGE_COUNT 16 -#define W1_PAGE_SIZE 32 -#define W1_PAGE_BITS 5 -#define W1_PAGE_MASK 0x1F - -#define W1_F1C_READ_EEPROM 0xF0 -#define W1_F1C_WRITE_SCRATCH 0x0F -#define W1_F1C_READ_SCRATCH 0xAA -#define W1_F1C_COPY_SCRATCH 0x55 -#define W1_F1C_ACCESS_WRITE 0x5A - -#define W1_1C_REG_LOGIC_STATE 0x220 - -struct w1_f1C_data { - u8 memory[W1_EEPROM_SIZE]; - u32 validcrc; -}; - -/** - * Check the file size bounds and adjusts count as needed. - * This would not be needed if the file size didn't reset to 0 after a write. - */ -static inline size_t w1_f1C_fix_count(loff_t off, size_t count, size_t size) -{ - if (off > size) - return 0; - - if ((off + count) > size) - return (size - off); - - return count; -} - -static int w1_f1C_refresh_block(struct w1_slave *sl, struct w1_f1C_data *data, - int block) -{ - u8 wrbuf[3]; - int off = block * W1_PAGE_SIZE; - - if (data->validcrc & (1 << block)) - return 0; - - if (w1_reset_select_slave(sl)) { - data->validcrc = 0; - return -EIO; - } - - wrbuf[0] = W1_F1C_READ_EEPROM; - wrbuf[1] = off & 0xff; - wrbuf[2] = off >> 8; - w1_write_block(sl->master, wrbuf, 3); - w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); - - /* cache the block if the CRC is valid */ - if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) - data->validcrc |= (1 << block); - - return 0; -} - -static int w1_f1C_read(struct w1_slave *sl, int addr, int len, char *data) -{ - u8 wrbuf[3]; - - /* read directly from the EEPROM */ - if (w1_reset_select_slave(sl)) - return -EIO; - - wrbuf[0] = W1_F1C_READ_EEPROM; - wrbuf[1] = addr & 0xff; - wrbuf[2] = addr >> 8; - - w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); - return w1_read_block(sl->master, data, len); -} - -static ssize_t w1_f1C_read_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct w1_slave *sl = kobj_to_w1_slave(kobj); - struct w1_f1C_data *data = sl->family_data; - int i, min_page, max_page; - - if ((count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE)) == 0) - return 0; - - mutex_lock(&sl->master->mutex); - - if(w1_enable_crccheck) { - min_page = (off >> W1_PAGE_BITS); - max_page = (off + count - 1) >> W1_PAGE_BITS; - for (i = min_page; i <= max_page; i++) { - if (w1_f1C_refresh_block(sl, data, i)) { - count = -EIO; - goto out_up; - } - } - memcpy(buf, &data->memory[off], count); - } - else { - count = w1_f1C_read(sl, off, count, buf); - } - -out_up: - mutex_unlock(&sl->master->mutex); - - return count; -} - -/** - * Writes to the scratchpad and reads it back for verification. - * Then copies the scratchpad to EEPROM. - * The data must be on one page. - * The master must be locked. - * - * @param sl The slave structure - * @param addr Address for the write - * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) - * @param data The data to write - * @return 0=Success -1=failure - */ -static int w1_f1C_write(struct w1_slave *sl, int addr, int len, const u8 *data) -{ - u8 wrbuf[4]; - u8 rdbuf[W1_PAGE_SIZE + 3]; - u8 es = (addr + len - 1) & 0x1f; - unsigned int tm = 10; - int i; - struct w1_f1C_data *f1C = sl->family_data; - - /* Write the data to the scratchpad */ - if (w1_reset_select_slave(sl)) - return -1; - - wrbuf[0] = W1_F1C_WRITE_SCRATCH; - wrbuf[1] = addr & 0xff; - wrbuf[2] = addr >> 8; - - w1_write_block(sl->master, wrbuf, 3); - w1_write_block(sl->master, data, len); - - /* Read the scratchpad and verify */ - if (w1_reset_select_slave(sl)) - return -1; - - w1_write_8(sl->master, W1_F1C_READ_SCRATCH); - w1_read_block(sl->master, rdbuf, len + 3); - - /* Compare what was read against the data written */ - if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || - (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) - return -1; - - /* Copy the scratchpad to EEPROM */ - if (w1_reset_select_slave(sl)) - return -1; - - wrbuf[0] = W1_F1C_COPY_SCRATCH; - wrbuf[3] = es; - - for(i = 0; i < sizeof(wrbuf); ++i) { - /* issue 10ms strong pullup (or delay) on the last byte for writing the data from the scratchpad to EEPROM */ - if(w1_strong_pullup && i == sizeof(wrbuf)-1) - w1_next_pullup(sl->master, tm); - - w1_write_8(sl->master, wrbuf[i]); - } - - if(!w1_strong_pullup) - msleep(tm); - - if(w1_enable_crccheck) { - /* invalidate cached data */ - f1C->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); - } - - /* Reset the bus to wake up the EEPROM (this may not be needed) */ - w1_reset_bus(sl->master); - - return 0; -} - -static ssize_t w1_f1C_write_bin(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) - -{ - struct w1_slave *sl = kobj_to_w1_slave(kobj); - int addr, len, idx; - - if ((count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE)) == 0) - return 0; - - if(w1_enable_crccheck) { - /* can only write full blocks in cached mode */ - if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { - dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", - (int)off, count); - return -EINVAL; - } - - /* make sure the block CRCs are valid */ - for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { - if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) { - dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off); - return -EINVAL; - } - } - } - - mutex_lock(&sl->master->mutex); - - /* Can only write data to one page at a time */ - idx = 0; - while (idx < count) { - addr = off + idx; - len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); - if (len > (count - idx)) - len = count - idx; - - if (w1_f1C_write(sl, addr, len, &buf[idx]) < 0) { - count = -EIO; - goto out_up; - } - idx += len; - } - -out_up: - mutex_unlock(&sl->master->mutex); - - return count; -} - -static ssize_t w1_f1C_read_pio(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) - -{ - struct w1_slave *sl = kobj_to_w1_slave(kobj); - int ret; - - /* check arguments */ - if(off != 0 || count != 1 || buf == NULL) - return -EINVAL; - - mutex_lock(&sl->master->mutex); - ret = w1_f1C_read(sl, W1_1C_REG_LOGIC_STATE, count, buf); - mutex_unlock(&sl->master->mutex); - - return ret; -} - -static ssize_t w1_f1C_write_pio(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) - -{ - struct w1_slave *sl = kobj_to_w1_slave(kobj); - u8 wrbuf[3]; - u8 ack; - - /* check arguments */ - if(off != 0 || count != 1 || buf == NULL) - return -EINVAL; - - mutex_lock(&sl->master->mutex); - - /* Write the PIO data */ - if (w1_reset_select_slave(sl)) { - mutex_unlock(&sl->master->mutex); - return -1; - } - - /* set bit 7..2 to value '1' */ - *buf = *buf | 0xFC; - - wrbuf[0] = W1_F1C_ACCESS_WRITE; - wrbuf[1] = *buf; - wrbuf[2] = ~(*buf); - w1_write_block(sl->master, wrbuf, 3); - - w1_read_block(sl->master, &ack, sizeof(ack)); - - mutex_unlock(&sl->master->mutex); - - /* check for acknowledgement */ - if(ack != 0xAA) return -EIO; - - return count; -} - -static ssize_t w1_f1C_show_crccheck(struct device *dev, struct device_attribute *attr, - char *buf) -{ - if(put_user(w1_enable_crccheck + 0x30, buf)) - return -EFAULT; - - return sizeof(w1_enable_crccheck); -} - -static ssize_t w1_f1C_store_crccheck(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - char val; - - if(count != 1 || !buf) return -EINVAL; - - if(get_user(val, buf)) - return -EFAULT; - - /* convert to decimal */ - val = val - 0x30; - if(val != 0 && val != 1) return -EINVAL; - - /* set the new value */ - w1_enable_crccheck = val; - - return sizeof(w1_enable_crccheck); -} - -#define NB_SYSFS_BIN_FILES 2 -static struct bin_attribute w1_f1C_bin_attr[NB_SYSFS_BIN_FILES] = { - { - .attr = { - .name = "eeprom", - .mode = S_IRUGO | S_IWUSR, - }, - .size = W1_EEPROM_SIZE, - .read = w1_f1C_read_bin, - .write = w1_f1C_write_bin, - }, - { - .attr = { - .name = "pio", - .mode = S_IRUGO | S_IWUSR, - }, - .size = 1, - .read = w1_f1C_read_pio, - .write = w1_f1C_write_pio, - } -}; - -static DEVICE_ATTR(crccheck, S_IWUSR | S_IRUGO, w1_f1C_show_crccheck, w1_f1C_store_crccheck); - -static int w1_f1C_add_slave(struct w1_slave *sl) -{ - int err = 0; - int i; - struct w1_f1C_data *data = NULL; - - if(w1_enable_crccheck) { - data = kzalloc(sizeof(struct w1_f1C_data), GFP_KERNEL); - if (!data) - return -ENOMEM; - sl->family_data = data; - } - - /* create binary sysfs attributes */ - for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) - err = sysfs_create_bin_file(&sl->dev.kobj, &(w1_f1C_bin_attr[i])); - - if(err) - goto out; - - /* create device attributes */ - err = device_create_file(&sl->dev, &dev_attr_crccheck); - - if(err) { - /* remove binary sysfs attributes */ - for (i = 0; i < NB_SYSFS_BIN_FILES; ++i) - sysfs_remove_bin_file(&sl->dev.kobj, &(w1_f1C_bin_attr[i])); - } - -out: - if(err) { - if(w1_enable_crccheck) - kfree(data); - } - - return err; -} - -static void w1_f1C_remove_slave(struct w1_slave *sl) -{ - int i; - - if(w1_enable_crccheck) { - kfree(sl->family_data); - sl->family_data = NULL; - } - - /* remove device attributes */ - device_remove_file(&sl->dev, &dev_attr_crccheck); - - /* remove binary sysfs attributes */ - for (i = 0; i < NB_SYSFS_BIN_FILES; ++i) - sysfs_remove_bin_file(&sl->dev.kobj, &(w1_f1C_bin_attr[i])); -} - -static struct w1_family_ops w1_f1C_fops = { - .add_slave = w1_f1C_add_slave, - .remove_slave = w1_f1C_remove_slave, -}; - -static struct w1_family w1_family_1C = { - .fid = W1_FAMILY_DS28E04, - .fops = &w1_f1C_fops, -}; - -static int __init w1_f1C_init(void) -{ - return w1_register_family(&w1_family_1C); -} - -static void __exit w1_f1C_fini(void) -{ - w1_unregister_family(&w1_family_1C); -} - -module_init(w1_f1C_init); -module_exit(w1_f1C_fini); diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index b00ada44a89b..874aeb05011b 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -30,7 +30,6 @@ #define W1_FAMILY_SMEM_01 0x01 #define W1_FAMILY_SMEM_81 0x81 #define W1_THERM_DS18S20 0x10 -#define W1_FAMILY_DS28E04 0x1C #define W1_COUNTER_DS2423 0x1D #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 -- cgit v1.2.3 From 6c8b0906cf447adf2aeeed3d79eb5cec7f362d1f Mon Sep 17 00:00:00 2001 From: Aneesh V Date: Fri, 27 Apr 2012 17:54:04 +0530 Subject: memory: emif: add register definitions for EMIF Add register offsets and bit field definitions for EMIF module in TI SoCs Signed-off-by: Aneesh V Reviewed-by: Santosh Shilimkar Reviewed-by: Benoit Cousson [santosh.shilimkar@ti.com: Moved to drivers/memory from drivers/misc] Signed-off-by: Santosh Shilimkar Tested-by: Lokesh Vutla Signed-off-by: Greg Kroah-Hartman --- drivers/memory/emif.h | 454 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 drivers/memory/emif.h (limited to 'drivers') diff --git a/drivers/memory/emif.h b/drivers/memory/emif.h new file mode 100644 index 000000000000..44b97dfe95b4 --- /dev/null +++ b/drivers/memory/emif.h @@ -0,0 +1,454 @@ +/* + * Defines for the EMIF driver + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Benoit Cousson (b-cousson@ti.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __EMIF_H +#define __EMIF_H + +/* Registers offset */ +#define EMIF_MODULE_ID_AND_REVISION 0x0000 +#define EMIF_STATUS 0x0004 +#define EMIF_SDRAM_CONFIG 0x0008 +#define EMIF_SDRAM_CONFIG_2 0x000c +#define EMIF_SDRAM_REFRESH_CONTROL 0x0010 +#define EMIF_SDRAM_REFRESH_CTRL_SHDW 0x0014 +#define EMIF_SDRAM_TIMING_1 0x0018 +#define EMIF_SDRAM_TIMING_1_SHDW 0x001c +#define EMIF_SDRAM_TIMING_2 0x0020 +#define EMIF_SDRAM_TIMING_2_SHDW 0x0024 +#define EMIF_SDRAM_TIMING_3 0x0028 +#define EMIF_SDRAM_TIMING_3_SHDW 0x002c +#define EMIF_LPDDR2_NVM_TIMING 0x0030 +#define EMIF_LPDDR2_NVM_TIMING_SHDW 0x0034 +#define EMIF_POWER_MANAGEMENT_CONTROL 0x0038 +#define EMIF_POWER_MANAGEMENT_CTRL_SHDW 0x003c +#define EMIF_LPDDR2_MODE_REG_DATA 0x0040 +#define EMIF_LPDDR2_MODE_REG_CONFIG 0x0050 +#define EMIF_OCP_CONFIG 0x0054 +#define EMIF_OCP_CONFIG_VALUE_1 0x0058 +#define EMIF_OCP_CONFIG_VALUE_2 0x005c +#define EMIF_IODFT_TEST_LOGIC_GLOBAL_CONTROL 0x0060 +#define EMIF_IODFT_TEST_LOGIC_CTRL_MISR_RESULT 0x0064 +#define EMIF_IODFT_TEST_LOGIC_ADDRESS_MISR_RESULT 0x0068 +#define EMIF_IODFT_TEST_LOGIC_DATA_MISR_RESULT_1 0x006c +#define EMIF_IODFT_TEST_LOGIC_DATA_MISR_RESULT_2 0x0070 +#define EMIF_IODFT_TEST_LOGIC_DATA_MISR_RESULT_3 0x0074 +#define EMIF_PERFORMANCE_COUNTER_1 0x0080 +#define EMIF_PERFORMANCE_COUNTER_2 0x0084 +#define EMIF_PERFORMANCE_COUNTER_CONFIG 0x0088 +#define EMIF_PERFORMANCE_COUNTER_MASTER_REGION_SELECT 0x008c +#define EMIF_PERFORMANCE_COUNTER_TIME 0x0090 +#define EMIF_MISC_REG 0x0094 +#define EMIF_DLL_CALIB_CTRL 0x0098 +#define EMIF_DLL_CALIB_CTRL_SHDW 0x009c +#define EMIF_END_OF_INTERRUPT 0x00a0 +#define EMIF_SYSTEM_OCP_INTERRUPT_RAW_STATUS 0x00a4 +#define EMIF_LL_OCP_INTERRUPT_RAW_STATUS 0x00a8 +#define EMIF_SYSTEM_OCP_INTERRUPT_STATUS 0x00ac +#define EMIF_LL_OCP_INTERRUPT_STATUS 0x00b0 +#define EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET 0x00b4 +#define EMIF_LL_OCP_INTERRUPT_ENABLE_SET 0x00b8 +#define EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_CLEAR 0x00bc +#define EMIF_LL_OCP_INTERRUPT_ENABLE_CLEAR 0x00c0 +#define EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG 0x00c8 +#define EMIF_TEMPERATURE_ALERT_CONFIG 0x00cc +#define EMIF_OCP_ERROR_LOG 0x00d0 +#define EMIF_READ_WRITE_LEVELING_RAMP_WINDOW 0x00d4 +#define EMIF_READ_WRITE_LEVELING_RAMP_CONTROL 0x00d8 +#define EMIF_READ_WRITE_LEVELING_CONTROL 0x00dc +#define EMIF_DDR_PHY_CTRL_1 0x00e4 +#define EMIF_DDR_PHY_CTRL_1_SHDW 0x00e8 +#define EMIF_DDR_PHY_CTRL_2 0x00ec +#define EMIF_PRIORITY_TO_CLASS_OF_SERVICE_MAPPING 0x0100 +#define EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_1_MAPPING 0x0104 +#define EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_2_MAPPING 0x0108 +#define EMIF_READ_WRITE_EXECUTION_THRESHOLD 0x0120 +#define EMIF_COS_CONFIG 0x0124 +#define EMIF_PHY_STATUS_1 0x0140 +#define EMIF_PHY_STATUS_2 0x0144 +#define EMIF_PHY_STATUS_3 0x0148 +#define EMIF_PHY_STATUS_4 0x014c +#define EMIF_PHY_STATUS_5 0x0150 +#define EMIF_PHY_STATUS_6 0x0154 +#define EMIF_PHY_STATUS_7 0x0158 +#define EMIF_PHY_STATUS_8 0x015c +#define EMIF_PHY_STATUS_9 0x0160 +#define EMIF_PHY_STATUS_10 0x0164 +#define EMIF_PHY_STATUS_11 0x0168 +#define EMIF_PHY_STATUS_12 0x016c +#define EMIF_PHY_STATUS_13 0x0170 +#define EMIF_PHY_STATUS_14 0x0174 +#define EMIF_PHY_STATUS_15 0x0178 +#define EMIF_PHY_STATUS_16 0x017c +#define EMIF_PHY_STATUS_17 0x0180 +#define EMIF_PHY_STATUS_18 0x0184 +#define EMIF_PHY_STATUS_19 0x0188 +#define EMIF_PHY_STATUS_20 0x018c +#define EMIF_PHY_STATUS_21 0x0190 +#define EMIF_EXT_PHY_CTRL_1 0x0200 +#define EMIF_EXT_PHY_CTRL_1_SHDW 0x0204 +#define EMIF_EXT_PHY_CTRL_2 0x0208 +#define EMIF_EXT_PHY_CTRL_2_SHDW 0x020c +#define EMIF_EXT_PHY_CTRL_3 0x0210 +#define EMIF_EXT_PHY_CTRL_3_SHDW 0x0214 +#define EMIF_EXT_PHY_CTRL_4 0x0218 +#define EMIF_EXT_PHY_CTRL_4_SHDW 0x021c +#define EMIF_EXT_PHY_CTRL_5 0x0220 +#define EMIF_EXT_PHY_CTRL_5_SHDW 0x0224 +#define EMIF_EXT_PHY_CTRL_6 0x0228 +#define EMIF_EXT_PHY_CTRL_6_SHDW 0x022c +#define EMIF_EXT_PHY_CTRL_7 0x0230 +#define EMIF_EXT_PHY_CTRL_7_SHDW 0x0234 +#define EMIF_EXT_PHY_CTRL_8 0x0238 +#define EMIF_EXT_PHY_CTRL_8_SHDW 0x023c +#define EMIF_EXT_PHY_CTRL_9 0x0240 +#define EMIF_EXT_PHY_CTRL_9_SHDW 0x0244 +#define EMIF_EXT_PHY_CTRL_10 0x0248 +#define EMIF_EXT_PHY_CTRL_10_SHDW 0x024c +#define EMIF_EXT_PHY_CTRL_11 0x0250 +#define EMIF_EXT_PHY_CTRL_11_SHDW 0x0254 +#define EMIF_EXT_PHY_CTRL_12 0x0258 +#define EMIF_EXT_PHY_CTRL_12_SHDW 0x025c +#define EMIF_EXT_PHY_CTRL_13 0x0260 +#define EMIF_EXT_PHY_CTRL_13_SHDW 0x0264 +#define EMIF_EXT_PHY_CTRL_14 0x0268 +#define EMIF_EXT_PHY_CTRL_14_SHDW 0x026c +#define EMIF_EXT_PHY_CTRL_15 0x0270 +#define EMIF_EXT_PHY_CTRL_15_SHDW 0x0274 +#define EMIF_EXT_PHY_CTRL_16 0x0278 +#define EMIF_EXT_PHY_CTRL_16_SHDW 0x027c +#define EMIF_EXT_PHY_CTRL_17 0x0280 +#define EMIF_EXT_PHY_CTRL_17_SHDW 0x0284 +#define EMIF_EXT_PHY_CTRL_18 0x0288 +#define EMIF_EXT_PHY_CTRL_18_SHDW 0x028c +#define EMIF_EXT_PHY_CTRL_19 0x0290 +#define EMIF_EXT_PHY_CTRL_19_SHDW 0x0294 +#define EMIF_EXT_PHY_CTRL_20 0x0298 +#define EMIF_EXT_PHY_CTRL_20_SHDW 0x029c +#define EMIF_EXT_PHY_CTRL_21 0x02a0 +#define EMIF_EXT_PHY_CTRL_21_SHDW 0x02a4 +#define EMIF_EXT_PHY_CTRL_22 0x02a8 +#define EMIF_EXT_PHY_CTRL_22_SHDW 0x02ac +#define EMIF_EXT_PHY_CTRL_23 0x02b0 +#define EMIF_EXT_PHY_CTRL_23_SHDW 0x02b4 +#define EMIF_EXT_PHY_CTRL_24 0x02b8 +#define EMIF_EXT_PHY_CTRL_24_SHDW 0x02bc +#define EMIF_EXT_PHY_CTRL_25 0x02c0 +#define EMIF_EXT_PHY_CTRL_25_SHDW 0x02c4 +#define EMIF_EXT_PHY_CTRL_26 0x02c8 +#define EMIF_EXT_PHY_CTRL_26_SHDW 0x02cc +#define EMIF_EXT_PHY_CTRL_27 0x02d0 +#define EMIF_EXT_PHY_CTRL_27_SHDW 0x02d4 +#define EMIF_EXT_PHY_CTRL_28 0x02d8 +#define EMIF_EXT_PHY_CTRL_28_SHDW 0x02dc +#define EMIF_EXT_PHY_CTRL_29 0x02e0 +#define EMIF_EXT_PHY_CTRL_29_SHDW 0x02e4 +#define EMIF_EXT_PHY_CTRL_30 0x02e8 +#define EMIF_EXT_PHY_CTRL_30_SHDW 0x02ec + +/* Registers shifts and masks */ + +/* EMIF_MODULE_ID_AND_REVISION */ +#define SCHEME_SHIFT 30 +#define SCHEME_MASK (0x3 << 30) +#define MODULE_ID_SHIFT 16 +#define MODULE_ID_MASK (0xfff << 16) +#define RTL_VERSION_SHIFT 11 +#define RTL_VERSION_MASK (0x1f << 11) +#define MAJOR_REVISION_SHIFT 8 +#define MAJOR_REVISION_MASK (0x7 << 8) +#define MINOR_REVISION_SHIFT 0 +#define MINOR_REVISION_MASK (0x3f << 0) + +/* STATUS */ +#define BE_SHIFT 31 +#define BE_MASK (1 << 31) +#define DUAL_CLK_MODE_SHIFT 30 +#define DUAL_CLK_MODE_MASK (1 << 30) +#define FAST_INIT_SHIFT 29 +#define FAST_INIT_MASK (1 << 29) +#define RDLVLGATETO_SHIFT 6 +#define RDLVLGATETO_MASK (1 << 6) +#define RDLVLTO_SHIFT 5 +#define RDLVLTO_MASK (1 << 5) +#define WRLVLTO_SHIFT 4 +#define WRLVLTO_MASK (1 << 4) +#define PHY_DLL_READY_SHIFT 2 +#define PHY_DLL_READY_MASK (1 << 2) + +/* SDRAM_CONFIG */ +#define SDRAM_TYPE_SHIFT 29 +#define SDRAM_TYPE_MASK (0x7 << 29) +#define IBANK_POS_SHIFT 27 +#define IBANK_POS_MASK (0x3 << 27) +#define DDR_TERM_SHIFT 24 +#define DDR_TERM_MASK (0x7 << 24) +#define DDR2_DDQS_SHIFT 23 +#define DDR2_DDQS_MASK (1 << 23) +#define DYN_ODT_SHIFT 21 +#define DYN_ODT_MASK (0x3 << 21) +#define DDR_DISABLE_DLL_SHIFT 20 +#define DDR_DISABLE_DLL_MASK (1 << 20) +#define SDRAM_DRIVE_SHIFT 18 +#define SDRAM_DRIVE_MASK (0x3 << 18) +#define CWL_SHIFT 16 +#define CWL_MASK (0x3 << 16) +#define NARROW_MODE_SHIFT 14 +#define NARROW_MODE_MASK (0x3 << 14) +#define CL_SHIFT 10 +#define CL_MASK (0xf << 10) +#define ROWSIZE_SHIFT 7 +#define ROWSIZE_MASK (0x7 << 7) +#define IBANK_SHIFT 4 +#define IBANK_MASK (0x7 << 4) +#define EBANK_SHIFT 3 +#define EBANK_MASK (1 << 3) +#define PAGESIZE_SHIFT 0 +#define PAGESIZE_MASK (0x7 << 0) + +/* SDRAM_CONFIG_2 */ +#define CS1NVMEN_SHIFT 30 +#define CS1NVMEN_MASK (1 << 30) +#define EBANK_POS_SHIFT 27 +#define EBANK_POS_MASK (1 << 27) +#define RDBNUM_SHIFT 4 +#define RDBNUM_MASK (0x3 << 4) +#define RDBSIZE_SHIFT 0 +#define RDBSIZE_MASK (0x7 << 0) + +/* SDRAM_REFRESH_CONTROL */ +#define INITREF_DIS_SHIFT 31 +#define INITREF_DIS_MASK (1 << 31) +#define SRT_SHIFT 29 +#define SRT_MASK (1 << 29) +#define ASR_SHIFT 28 +#define ASR_MASK (1 << 28) +#define PASR_SHIFT 24 +#define PASR_MASK (0x7 << 24) +#define REFRESH_RATE_SHIFT 0 +#define REFRESH_RATE_MASK (0xffff << 0) + +/* SDRAM_TIMING_1 */ +#define T_RTW_SHIFT 29 +#define T_RTW_MASK (0x7 << 29) +#define T_RP_SHIFT 25 +#define T_RP_MASK (0xf << 25) +#define T_RCD_SHIFT 21 +#define T_RCD_MASK (0xf << 21) +#define T_WR_SHIFT 17 +#define T_WR_MASK (0xf << 17) +#define T_RAS_SHIFT 12 +#define T_RAS_MASK (0x1f << 12) +#define T_RC_SHIFT 6 +#define T_RC_MASK (0x3f << 6) +#define T_RRD_SHIFT 3 +#define T_RRD_MASK (0x7 << 3) +#define T_WTR_SHIFT 0 +#define T_WTR_MASK (0x7 << 0) + +/* SDRAM_TIMING_2 */ +#define T_XP_SHIFT 28 +#define T_XP_MASK (0x7 << 28) +#define T_ODT_SHIFT 25 +#define T_ODT_MASK (0x7 << 25) +#define T_XSNR_SHIFT 16 +#define T_XSNR_MASK (0x1ff << 16) +#define T_XSRD_SHIFT 6 +#define T_XSRD_MASK (0x3ff << 6) +#define T_RTP_SHIFT 3 +#define T_RTP_MASK (0x7 << 3) +#define T_CKE_SHIFT 0 +#define T_CKE_MASK (0x7 << 0) + +/* SDRAM_TIMING_3 */ +#define T_PDLL_UL_SHIFT 28 +#define T_PDLL_UL_MASK (0xf << 28) +#define T_CSTA_SHIFT 24 +#define T_CSTA_MASK (0xf << 24) +#define T_CKESR_SHIFT 21 +#define T_CKESR_MASK (0x7 << 21) +#define ZQ_ZQCS_SHIFT 15 +#define ZQ_ZQCS_MASK (0x3f << 15) +#define T_TDQSCKMAX_SHIFT 13 +#define T_TDQSCKMAX_MASK (0x3 << 13) +#define T_RFC_SHIFT 4 +#define T_RFC_MASK (0x1ff << 4) +#define T_RAS_MAX_SHIFT 0 +#define T_RAS_MAX_MASK (0xf << 0) + +/* POWER_MANAGEMENT_CONTROL */ +#define PD_TIM_SHIFT 12 +#define PD_TIM_MASK (0xf << 12) +#define DPD_EN_SHIFT 11 +#define DPD_EN_MASK (1 << 11) +#define LP_MODE_SHIFT 8 +#define LP_MODE_MASK (0x7 << 8) +#define SR_TIM_SHIFT 4 +#define SR_TIM_MASK (0xf << 4) +#define CS_TIM_SHIFT 0 +#define CS_TIM_MASK (0xf << 0) + +/* LPDDR2_MODE_REG_DATA */ +#define VALUE_0_SHIFT 0 +#define VALUE_0_MASK (0x7f << 0) + +/* LPDDR2_MODE_REG_CONFIG */ +#define CS_SHIFT 31 +#define CS_MASK (1 << 31) +#define REFRESH_EN_SHIFT 30 +#define REFRESH_EN_MASK (1 << 30) +#define ADDRESS_SHIFT 0 +#define ADDRESS_MASK (0xff << 0) + +/* OCP_CONFIG */ +#define SYS_THRESH_MAX_SHIFT 24 +#define SYS_THRESH_MAX_MASK (0xf << 24) +#define MPU_THRESH_MAX_SHIFT 20 +#define MPU_THRESH_MAX_MASK (0xf << 20) +#define LL_THRESH_MAX_SHIFT 16 +#define LL_THRESH_MAX_MASK (0xf << 16) + +/* PERFORMANCE_COUNTER_1 */ +#define COUNTER1_SHIFT 0 +#define COUNTER1_MASK (0xffffffff << 0) + +/* PERFORMANCE_COUNTER_2 */ +#define COUNTER2_SHIFT 0 +#define COUNTER2_MASK (0xffffffff << 0) + +/* PERFORMANCE_COUNTER_CONFIG */ +#define CNTR2_MCONNID_EN_SHIFT 31 +#define CNTR2_MCONNID_EN_MASK (1 << 31) +#define CNTR2_REGION_EN_SHIFT 30 +#define CNTR2_REGION_EN_MASK (1 << 30) +#define CNTR2_CFG_SHIFT 16 +#define CNTR2_CFG_MASK (0xf << 16) +#define CNTR1_MCONNID_EN_SHIFT 15 +#define CNTR1_MCONNID_EN_MASK (1 << 15) +#define CNTR1_REGION_EN_SHIFT 14 +#define CNTR1_REGION_EN_MASK (1 << 14) +#define CNTR1_CFG_SHIFT 0 +#define CNTR1_CFG_MASK (0xf << 0) + +/* PERFORMANCE_COUNTER_MASTER_REGION_SELECT */ +#define MCONNID2_SHIFT 24 +#define MCONNID2_MASK (0xff << 24) +#define REGION_SEL2_SHIFT 16 +#define REGION_SEL2_MASK (0x3 << 16) +#define MCONNID1_SHIFT 8 +#define MCONNID1_MASK (0xff << 8) +#define REGION_SEL1_SHIFT 0 +#define REGION_SEL1_MASK (0x3 << 0) + +/* PERFORMANCE_COUNTER_TIME */ +#define TOTAL_TIME_SHIFT 0 +#define TOTAL_TIME_MASK (0xffffffff << 0) + +/* DLL_CALIB_CTRL */ +#define ACK_WAIT_SHIFT 16 +#define ACK_WAIT_MASK (0xf << 16) +#define DLL_CALIB_INTERVAL_SHIFT 0 +#define DLL_CALIB_INTERVAL_MASK (0x1ff << 0) + +/* END_OF_INTERRUPT */ +#define EOI_SHIFT 0 +#define EOI_MASK (1 << 0) + +/* SYSTEM_OCP_INTERRUPT_RAW_STATUS */ +#define DNV_SYS_SHIFT 2 +#define DNV_SYS_MASK (1 << 2) +#define TA_SYS_SHIFT 1 +#define TA_SYS_MASK (1 << 1) +#define ERR_SYS_SHIFT 0 +#define ERR_SYS_MASK (1 << 0) + +/* LOW_LATENCY_OCP_INTERRUPT_RAW_STATUS */ +#define DNV_LL_SHIFT 2 +#define DNV_LL_MASK (1 << 2) +#define TA_LL_SHIFT 1 +#define TA_LL_MASK (1 << 1) +#define ERR_LL_SHIFT 0 +#define ERR_LL_MASK (1 << 0) + +/* SYSTEM_OCP_INTERRUPT_ENABLE_SET */ +#define EN_DNV_SYS_SHIFT 2 +#define EN_DNV_SYS_MASK (1 << 2) +#define EN_TA_SYS_SHIFT 1 +#define EN_TA_SYS_MASK (1 << 1) +#define EN_ERR_SYS_SHIFT 0 +#define EN_ERR_SYS_MASK (1 << 0) + +/* LOW_LATENCY_OCP_INTERRUPT_ENABLE_SET */ +#define EN_DNV_LL_SHIFT 2 +#define EN_DNV_LL_MASK (1 << 2) +#define EN_TA_LL_SHIFT 1 +#define EN_TA_LL_MASK (1 << 1) +#define EN_ERR_LL_SHIFT 0 +#define EN_ERR_LL_MASK (1 << 0) + +/* SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG */ +#define ZQ_CS1EN_SHIFT 31 +#define ZQ_CS1EN_MASK (1 << 31) +#define ZQ_CS0EN_SHIFT 30 +#define ZQ_CS0EN_MASK (1 << 30) +#define ZQ_DUALCALEN_SHIFT 29 +#define ZQ_DUALCALEN_MASK (1 << 29) +#define ZQ_SFEXITEN_SHIFT 28 +#define ZQ_SFEXITEN_MASK (1 << 28) +#define ZQ_ZQINIT_MULT_SHIFT 18 +#define ZQ_ZQINIT_MULT_MASK (0x3 << 18) +#define ZQ_ZQCL_MULT_SHIFT 16 +#define ZQ_ZQCL_MULT_MASK (0x3 << 16) +#define ZQ_REFINTERVAL_SHIFT 0 +#define ZQ_REFINTERVAL_MASK (0xffff << 0) + +/* TEMPERATURE_ALERT_CONFIG */ +#define TA_CS1EN_SHIFT 31 +#define TA_CS1EN_MASK (1 << 31) +#define TA_CS0EN_SHIFT 30 +#define TA_CS0EN_MASK (1 << 30) +#define TA_SFEXITEN_SHIFT 28 +#define TA_SFEXITEN_MASK (1 << 28) +#define TA_DEVWDT_SHIFT 26 +#define TA_DEVWDT_MASK (0x3 << 26) +#define TA_DEVCNT_SHIFT 24 +#define TA_DEVCNT_MASK (0x3 << 24) +#define TA_REFINTERVAL_SHIFT 0 +#define TA_REFINTERVAL_MASK (0x3fffff << 0) + +/* OCP_ERROR_LOG */ +#define MADDRSPACE_SHIFT 14 +#define MADDRSPACE_MASK (0x3 << 14) +#define MBURSTSEQ_SHIFT 11 +#define MBURSTSEQ_MASK (0x7 << 11) +#define MCMD_SHIFT 8 +#define MCMD_MASK (0x7 << 8) +#define MCONNID_SHIFT 0 +#define MCONNID_MASK (0xff << 0) + +/* DDR_PHY_CTRL_1 - EMIF4D */ +#define DLL_SLAVE_DLY_CTRL_SHIFT_4D 4 +#define DLL_SLAVE_DLY_CTRL_MASK_4D (0xFF << 4) +#define READ_LATENCY_SHIFT_4D 0 +#define READ_LATENCY_MASK_4D (0xf << 0) + +/* DDR_PHY_CTRL_1 - EMIF4D5 */ +#define DLL_HALF_DELAY_SHIFT_4D5 21 +#define DLL_HALF_DELAY_MASK_4D5 (1 << 21) +#define READ_LATENCY_SHIFT_4D5 0 +#define READ_LATENCY_MASK_4D5 (0x1f << 0) + +/* DDR_PHY_CTRL_1_SHDW */ +#define DDR_PHY_CTRL_1_SHDW_SHIFT 5 +#define DDR_PHY_CTRL_1_SHDW_MASK (0x7ffffff << 5) +#define READ_LATENCY_SHDW_SHIFT 0 +#define READ_LATENCY_SHDW_MASK (0x1f << 0) + +#endif -- cgit v1.2.3 From 7ec944538dde3d7f490bd4d2619051789db5c3c3 Mon Sep 17 00:00:00 2001 From: Aneesh V Date: Fri, 27 Apr 2012 17:54:05 +0530 Subject: memory: emif: add basic infrastructure for EMIF driver EMIF is an SDRAM controller used in various Texas Instruments SoCs. EMIF supports, based on its revision, one or more of LPDDR2/DDR2/DDR3 protocols. Add the basic infrastructure for EMIF driver that includes driver registration, probe, parsing of platform data etc. Signed-off-by: Aneesh V Reviewed-by: Santosh Shilimkar Reviewed-by: Benoit Cousson [santosh.shilimkar@ti.com: Moved to drivers/memory from drivers/misc] Signed-off-by: Santosh Shilimkar Tested-by: Lokesh Vutla Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/memory/Kconfig | 22 ++++ drivers/memory/Makefile | 5 + drivers/memory/emif.c | 289 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/memory/emif.h | 7 ++ 6 files changed, 326 insertions(+) create mode 100644 drivers/memory/Kconfig create mode 100644 drivers/memory/Makefile create mode 100644 drivers/memory/emif.c (limited to 'drivers') diff --git a/drivers/Kconfig b/drivers/Kconfig index 0233ad979b7d..63b81826cb55 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -142,4 +142,6 @@ source "drivers/devfreq/Kconfig" source "drivers/extcon/Kconfig" +source "drivers/memory/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index c41dfa92cd79..265b506a15be 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -135,3 +135,4 @@ obj-$(CONFIG_HYPERV) += hv/ obj-$(CONFIG_PM_DEVFREQ) += devfreq/ obj-$(CONFIG_EXTCON) += extcon/ +obj-$(CONFIG_MEMORY) += memory/ diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig new file mode 100644 index 000000000000..b08327cca0e5 --- /dev/null +++ b/drivers/memory/Kconfig @@ -0,0 +1,22 @@ +# +# Memory devices +# + +menuconfig MEMORY + bool "Memory Controller drivers" + +if MEMORY + +config TI_EMIF + tristate "Texas Instruments EMIF driver" + select DDR + help + This driver is for the EMIF module available in Texas Instruments + SoCs. EMIF is an SDRAM controller that, based on its revision, + supports one or more of DDR2, DDR3, and LPDDR2 SDRAM protocols. + This driver takes care of only LPDDR2 memories presently. The + functions of the driver includes re-configuring AC timing + parameters and other settings during frequency, voltage and + temperature changes + +endif diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile new file mode 100644 index 000000000000..e27f80b28859 --- /dev/null +++ b/drivers/memory/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for memory devices +# + +obj-$(CONFIG_TI_EMIF) += emif.o diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c new file mode 100644 index 000000000000..7486d7ef0826 --- /dev/null +++ b/drivers/memory/emif.c @@ -0,0 +1,289 @@ +/* + * EMIF driver + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Aneesh V + * Santosh Shilimkar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "emif.h" + +/** + * struct emif_data - Per device static data for driver's use + * @duplicate: Whether the DDR devices attached to this EMIF + * instance are exactly same as that on EMIF1. In + * this case we can save some memory and processing + * @temperature_level: Maximum temperature of LPDDR2 devices attached + * to this EMIF - read from MR4 register. If there + * are two devices attached to this EMIF, this + * value is the maximum of the two temperature + * levels. + * @node: node in the device list + * @base: base address of memory-mapped IO registers. + * @dev: device pointer. + * @plat_data: Pointer to saved platform data. + */ +struct emif_data { + u8 duplicate; + u8 temperature_level; + struct list_head node; + void __iomem *base; + struct device *dev; + struct emif_platform_data *plat_data; +}; + +static struct emif_data *emif1; +static LIST_HEAD(device_list); + +static void get_default_timings(struct emif_data *emif) +{ + struct emif_platform_data *pd = emif->plat_data; + + pd->timings = lpddr2_jedec_timings; + pd->timings_arr_size = ARRAY_SIZE(lpddr2_jedec_timings); + + dev_warn(emif->dev, "%s: using default timings\n", __func__); +} + +static int is_dev_data_valid(u32 type, u32 density, u32 io_width, u32 phy_type, + u32 ip_rev, struct device *dev) +{ + int valid; + + valid = (type == DDR_TYPE_LPDDR2_S4 || + type == DDR_TYPE_LPDDR2_S2) + && (density >= DDR_DENSITY_64Mb + && density <= DDR_DENSITY_8Gb) + && (io_width >= DDR_IO_WIDTH_8 + && io_width <= DDR_IO_WIDTH_32); + + /* Combinations of EMIF and PHY revisions that we support today */ + switch (ip_rev) { + case EMIF_4D: + valid = valid && (phy_type == EMIF_PHY_TYPE_ATTILAPHY); + break; + case EMIF_4D5: + valid = valid && (phy_type == EMIF_PHY_TYPE_INTELLIPHY); + break; + default: + valid = 0; + } + + if (!valid) + dev_err(dev, "%s: invalid DDR details\n", __func__); + return valid; +} + +static int is_custom_config_valid(struct emif_custom_configs *cust_cfgs, + struct device *dev) +{ + int valid = 1; + + if ((cust_cfgs->mask & EMIF_CUSTOM_CONFIG_LPMODE) && + (cust_cfgs->lpmode != EMIF_LP_MODE_DISABLE)) + valid = cust_cfgs->lpmode_freq_threshold && + cust_cfgs->lpmode_timeout_performance && + cust_cfgs->lpmode_timeout_power; + + if (cust_cfgs->mask & EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL) + valid = valid && cust_cfgs->temp_alert_poll_interval_ms; + + if (!valid) + dev_warn(dev, "%s: invalid custom configs\n", __func__); + + return valid; +} + +static struct emif_data *__init_or_module get_device_details( + struct platform_device *pdev) +{ + u32 size; + struct emif_data *emif = NULL; + struct ddr_device_info *dev_info; + struct emif_custom_configs *cust_cfgs; + struct emif_platform_data *pd; + struct device *dev; + void *temp; + + pd = pdev->dev.platform_data; + dev = &pdev->dev; + + if (!(pd && pd->device_info && is_dev_data_valid(pd->device_info->type, + pd->device_info->density, pd->device_info->io_width, + pd->phy_type, pd->ip_rev, dev))) { + dev_err(dev, "%s: invalid device data\n", __func__); + goto error; + } + + emif = devm_kzalloc(dev, sizeof(*emif), GFP_KERNEL); + temp = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + dev_info = devm_kzalloc(dev, sizeof(*dev_info), GFP_KERNEL); + + if (!emif || !pd || !dev_info) { + dev_err(dev, "%s:%d: allocation error\n", __func__, __LINE__); + goto error; + } + + memcpy(temp, pd, sizeof(*pd)); + pd = temp; + memcpy(dev_info, pd->device_info, sizeof(*dev_info)); + + pd->device_info = dev_info; + emif->plat_data = pd; + emif->dev = dev; + emif->temperature_level = SDRAM_TEMP_NOMINAL; + + /* + * For EMIF instances other than EMIF1 see if the devices connected + * are exactly same as on EMIF1(which is typically the case). If so, + * mark it as a duplicate of EMIF1 and skip copying timings data. + * This will save some memory and some computation later. + */ + emif->duplicate = emif1 && (memcmp(dev_info, + emif1->plat_data->device_info, + sizeof(struct ddr_device_info)) == 0); + + if (emif->duplicate) { + pd->timings = NULL; + pd->min_tck = NULL; + goto out; + } else if (emif1) { + dev_warn(emif->dev, "%s: Non-symmetric DDR geometry\n", + __func__); + } + + /* + * Copy custom configs - ignore allocation error, if any, as + * custom_configs is not very critical + */ + cust_cfgs = pd->custom_configs; + if (cust_cfgs && is_custom_config_valid(cust_cfgs, dev)) { + temp = devm_kzalloc(dev, sizeof(*cust_cfgs), GFP_KERNEL); + if (temp) + memcpy(temp, cust_cfgs, sizeof(*cust_cfgs)); + else + dev_warn(dev, "%s:%d: allocation error\n", __func__, + __LINE__); + pd->custom_configs = temp; + } + + /* + * Copy timings and min-tck values from platform data. If it is not + * available or if memory allocation fails, use JEDEC defaults + */ + size = sizeof(struct lpddr2_timings) * pd->timings_arr_size; + if (pd->timings) { + temp = devm_kzalloc(dev, size, GFP_KERNEL); + if (temp) { + memcpy(temp, pd->timings, sizeof(*pd->timings)); + pd->timings = temp; + } else { + dev_warn(dev, "%s:%d: allocation error\n", __func__, + __LINE__); + get_default_timings(emif); + } + } else { + get_default_timings(emif); + } + + if (pd->min_tck) { + temp = devm_kzalloc(dev, sizeof(*pd->min_tck), GFP_KERNEL); + if (temp) { + memcpy(temp, pd->min_tck, sizeof(*pd->min_tck)); + pd->min_tck = temp; + } else { + dev_warn(dev, "%s:%d: allocation error\n", __func__, + __LINE__); + pd->min_tck = &lpddr2_jedec_min_tck; + } + } else { + pd->min_tck = &lpddr2_jedec_min_tck; + } + +out: + return emif; + +error: + return NULL; +} + +static int __init_or_module emif_probe(struct platform_device *pdev) +{ + struct emif_data *emif; + struct resource *res; + + emif = get_device_details(pdev); + if (!emif) { + pr_err("%s: error getting device data\n", __func__); + goto error; + } + + if (!emif1) + emif1 = emif; + + list_add(&emif->node, &device_list); + + /* Save pointers to each other in emif and device structures */ + emif->dev = &pdev->dev; + platform_set_drvdata(pdev, emif); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(emif->dev, "%s: error getting memory resource\n", + __func__); + goto error; + } + + emif->base = devm_request_and_ioremap(emif->dev, res); + if (!emif->base) { + dev_err(emif->dev, "%s: devm_request_and_ioremap() failed\n", + __func__); + goto error; + } + + dev_info(&pdev->dev, "%s: device configured with addr = %p\n", + __func__, emif->base); + + return 0; +error: + return -ENODEV; +} + +static struct platform_driver emif_driver = { + .driver = { + .name = "emif", + }, +}; + +static int __init_or_module emif_register(void) +{ + return platform_driver_probe(&emif_driver, emif_probe); +} + +static void __exit emif_unregister(void) +{ + platform_driver_unregister(&emif_driver); +} + +module_init(emif_register); +module_exit(emif_unregister); +MODULE_DESCRIPTION("TI EMIF SDRAM Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:emif"); +MODULE_AUTHOR("Texas Instruments Inc"); diff --git a/drivers/memory/emif.h b/drivers/memory/emif.h index 44b97dfe95b4..692b2a864e7b 100644 --- a/drivers/memory/emif.h +++ b/drivers/memory/emif.h @@ -12,6 +12,13 @@ #ifndef __EMIF_H #define __EMIF_H +/* + * Maximum number of different frequencies supported by EMIF driver + * Determines the number of entries in the pointer array for register + * cache + */ +#define EMIF_MAX_NUM_FREQUENCIES 6 + /* Registers offset */ #define EMIF_MODULE_ID_AND_REVISION 0x0000 #define EMIF_STATUS 0x0004 -- cgit v1.2.3 From a93de288aad3b046935d626065d4bcbb7d93b093 Mon Sep 17 00:00:00 2001 From: Aneesh V Date: Fri, 27 Apr 2012 17:54:06 +0530 Subject: memory: emif: handle frequency and voltage change events Change SDRAM timings and other settings as necessary on voltage and frequency changes. We calculate these register settings based on data from the device data sheet and inputs such a frequency, voltage state(stable or ramping), temperature level etc. TODO: frequency and voltage change handling needs to be integrated with clock framework and regulator framework respectively. This is not done today due to missing pieces in the kernel. Signed-off-by: Aneesh V Reviewed-by: Santosh Shilimkar Reviewed-by: Benoit Cousson [santosh.shilimkar@ti.com: Moved to drivers/memory from drivers/misc] Signed-off-by: Santosh Shilimkar Tested-by: Lokesh Vutla Signed-off-by: Greg Kroah-Hartman --- drivers/memory/emif.c | 894 +++++++++++++++++++++++++++++++++++++++++++++++++- drivers/memory/emif.h | 130 +++++++- 2 files changed, 1020 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index 7486d7ef0826..bd116eb8c738 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "emif.h" @@ -37,20 +38,595 @@ * @node: node in the device list * @base: base address of memory-mapped IO registers. * @dev: device pointer. + * @addressing table with addressing information from the spec + * @regs_cache: An array of 'struct emif_regs' that stores + * calculated register values for different + * frequencies, to avoid re-calculating them on + * each DVFS transition. + * @curr_regs: The set of register values used in the last + * frequency change (i.e. corresponding to the + * frequency in effect at the moment) * @plat_data: Pointer to saved platform data. */ struct emif_data { u8 duplicate; u8 temperature_level; + u8 lpmode; struct list_head node; + unsigned long irq_state; void __iomem *base; struct device *dev; + const struct lpddr2_addressing *addressing; + struct emif_regs *regs_cache[EMIF_MAX_NUM_FREQUENCIES]; + struct emif_regs *curr_regs; struct emif_platform_data *plat_data; }; static struct emif_data *emif1; +static spinlock_t emif_lock; +static unsigned long irq_state; +static u32 t_ck; /* DDR clock period in ps */ static LIST_HEAD(device_list); +/* + * Calculate the period of DDR clock from frequency value + */ +static void set_ddr_clk_period(u32 freq) +{ + /* Divide 10^12 by frequency to get period in ps */ + t_ck = (u32)DIV_ROUND_UP_ULL(1000000000000ull, freq); +} + +/* + * Get the CL from SDRAM_CONFIG register + */ +static u32 get_cl(struct emif_data *emif) +{ + u32 cl; + void __iomem *base = emif->base; + + cl = (readl(base + EMIF_SDRAM_CONFIG) & CL_MASK) >> CL_SHIFT; + + return cl; +} + +static void set_lpmode(struct emif_data *emif, u8 lpmode) +{ + u32 temp; + void __iomem *base = emif->base; + + temp = readl(base + EMIF_POWER_MANAGEMENT_CONTROL); + temp &= ~LP_MODE_MASK; + temp |= (lpmode << LP_MODE_SHIFT); + writel(temp, base + EMIF_POWER_MANAGEMENT_CONTROL); +} + +static void do_freq_update(void) +{ + struct emif_data *emif; + + /* + * Workaround for errata i728: Disable LPMODE during FREQ_UPDATE + * + * i728 DESCRIPTION: + * The EMIF automatically puts the SDRAM into self-refresh mode + * after the EMIF has not performed accesses during + * EMIF_PWR_MGMT_CTRL[7:4] REG_SR_TIM number of DDR clock cycles + * and the EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE bit field is set + * to 0x2. If during a small window the following three events + * occur: + * - The SR_TIMING counter expires + * - And frequency change is requested + * - And OCP access is requested + * Then it causes instable clock on the DDR interface. + * + * WORKAROUND + * To avoid the occurrence of the three events, the workaround + * is to disable the self-refresh when requesting a frequency + * change. Before requesting a frequency change the software must + * program EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE to 0x0. When the + * frequency change has been done, the software can reprogram + * EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE to 0x2 + */ + list_for_each_entry(emif, &device_list, node) { + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_DISABLE); + } + + /* + * TODO: Do FREQ_UPDATE here when an API + * is available for this as part of the new + * clock framework + */ + + list_for_each_entry(emif, &device_list, node) { + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_SELF_REFRESH); + } +} + +/* Find addressing table entry based on the device's type and density */ +static const struct lpddr2_addressing *get_addressing_table( + const struct ddr_device_info *device_info) +{ + u32 index, type, density; + + type = device_info->type; + density = device_info->density; + + switch (type) { + case DDR_TYPE_LPDDR2_S4: + index = density - 1; + break; + case DDR_TYPE_LPDDR2_S2: + switch (density) { + case DDR_DENSITY_1Gb: + case DDR_DENSITY_2Gb: + index = density + 3; + break; + default: + index = density - 1; + } + break; + default: + return NULL; + } + + return &lpddr2_jedec_addressing_table[index]; +} + +/* + * Find the the right timing table from the array of timing + * tables of the device using DDR clock frequency + */ +static const struct lpddr2_timings *get_timings_table(struct emif_data *emif, + u32 freq) +{ + u32 i, min, max, freq_nearest; + const struct lpddr2_timings *timings = NULL; + const struct lpddr2_timings *timings_arr = emif->plat_data->timings; + struct device *dev = emif->dev; + + /* Start with a very high frequency - 1GHz */ + freq_nearest = 1000000000; + + /* + * Find the timings table such that: + * 1. the frequency range covers the required frequency(safe) AND + * 2. the max_freq is closest to the required frequency(optimal) + */ + for (i = 0; i < emif->plat_data->timings_arr_size; i++) { + max = timings_arr[i].max_freq; + min = timings_arr[i].min_freq; + if ((freq >= min) && (freq <= max) && (max < freq_nearest)) { + freq_nearest = max; + timings = &timings_arr[i]; + } + } + + if (!timings) + dev_err(dev, "%s: couldn't find timings for - %dHz\n", + __func__, freq); + + dev_dbg(dev, "%s: timings table: freq %d, speed bin freq %d\n", + __func__, freq, freq_nearest); + + return timings; +} + +static u32 get_sdram_ref_ctrl_shdw(u32 freq, + const struct lpddr2_addressing *addressing) +{ + u32 ref_ctrl_shdw = 0, val = 0, freq_khz, t_refi; + + /* Scale down frequency and t_refi to avoid overflow */ + freq_khz = freq / 1000; + t_refi = addressing->tREFI_ns / 100; + + /* + * refresh rate to be set is 'tREFI(in us) * freq in MHz + * division by 10000 to account for change in units + */ + val = t_refi * freq_khz / 10000; + ref_ctrl_shdw |= val << REFRESH_RATE_SHIFT; + + return ref_ctrl_shdw; +} + +static u32 get_sdram_tim_1_shdw(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing) +{ + u32 tim1 = 0, val = 0; + + val = max(min_tck->tWTR, DIV_ROUND_UP(timings->tWTR, t_ck)) - 1; + tim1 |= val << T_WTR_SHIFT; + + if (addressing->num_banks == B8) + val = DIV_ROUND_UP(timings->tFAW, t_ck*4); + else + val = max(min_tck->tRRD, DIV_ROUND_UP(timings->tRRD, t_ck)); + tim1 |= (val - 1) << T_RRD_SHIFT; + + val = DIV_ROUND_UP(timings->tRAS_min + timings->tRPab, t_ck) - 1; + tim1 |= val << T_RC_SHIFT; + + val = max(min_tck->tRASmin, DIV_ROUND_UP(timings->tRAS_min, t_ck)); + tim1 |= (val - 1) << T_RAS_SHIFT; + + val = max(min_tck->tWR, DIV_ROUND_UP(timings->tWR, t_ck)) - 1; + tim1 |= val << T_WR_SHIFT; + + val = max(min_tck->tRCD, DIV_ROUND_UP(timings->tRCD, t_ck)) - 1; + tim1 |= val << T_RCD_SHIFT; + + val = max(min_tck->tRPab, DIV_ROUND_UP(timings->tRPab, t_ck)) - 1; + tim1 |= val << T_RP_SHIFT; + + return tim1; +} + +static u32 get_sdram_tim_1_shdw_derated(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing) +{ + u32 tim1 = 0, val = 0; + + val = max(min_tck->tWTR, DIV_ROUND_UP(timings->tWTR, t_ck)) - 1; + tim1 = val << T_WTR_SHIFT; + + /* + * tFAW is approximately 4 times tRRD. So add 1875*4 = 7500ps + * to tFAW for de-rating + */ + if (addressing->num_banks == B8) { + val = DIV_ROUND_UP(timings->tFAW + 7500, 4 * t_ck) - 1; + } else { + val = DIV_ROUND_UP(timings->tRRD + 1875, t_ck); + val = max(min_tck->tRRD, val) - 1; + } + tim1 |= val << T_RRD_SHIFT; + + val = DIV_ROUND_UP(timings->tRAS_min + timings->tRPab + 1875, t_ck); + tim1 |= (val - 1) << T_RC_SHIFT; + + val = DIV_ROUND_UP(timings->tRAS_min + 1875, t_ck); + val = max(min_tck->tRASmin, val) - 1; + tim1 |= val << T_RAS_SHIFT; + + val = max(min_tck->tWR, DIV_ROUND_UP(timings->tWR, t_ck)) - 1; + tim1 |= val << T_WR_SHIFT; + + val = max(min_tck->tRCD, DIV_ROUND_UP(timings->tRCD + 1875, t_ck)); + tim1 |= (val - 1) << T_RCD_SHIFT; + + val = max(min_tck->tRPab, DIV_ROUND_UP(timings->tRPab + 1875, t_ck)); + tim1 |= (val - 1) << T_RP_SHIFT; + + return tim1; +} + +static u32 get_sdram_tim_2_shdw(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing, + u32 type) +{ + u32 tim2 = 0, val = 0; + + val = min_tck->tCKE - 1; + tim2 |= val << T_CKE_SHIFT; + + val = max(min_tck->tRTP, DIV_ROUND_UP(timings->tRTP, t_ck)) - 1; + tim2 |= val << T_RTP_SHIFT; + + /* tXSNR = tRFCab_ps + 10 ns(tRFCab_ps for LPDDR2). */ + val = DIV_ROUND_UP(addressing->tRFCab_ps + 10000, t_ck) - 1; + tim2 |= val << T_XSNR_SHIFT; + + /* XSRD same as XSNR for LPDDR2 */ + tim2 |= val << T_XSRD_SHIFT; + + val = max(min_tck->tXP, DIV_ROUND_UP(timings->tXP, t_ck)) - 1; + tim2 |= val << T_XP_SHIFT; + + return tim2; +} + +static u32 get_sdram_tim_3_shdw(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing, + u32 type, u32 ip_rev, u32 derated) +{ + u32 tim3 = 0, val = 0, t_dqsck; + + val = timings->tRAS_max_ns / addressing->tREFI_ns - 1; + val = val > 0xF ? 0xF : val; + tim3 |= val << T_RAS_MAX_SHIFT; + + val = DIV_ROUND_UP(addressing->tRFCab_ps, t_ck) - 1; + tim3 |= val << T_RFC_SHIFT; + + t_dqsck = (derated == EMIF_DERATED_TIMINGS) ? + timings->tDQSCK_max_derated : timings->tDQSCK_max; + if (ip_rev == EMIF_4D5) + val = DIV_ROUND_UP(t_dqsck + 1000, t_ck) - 1; + else + val = DIV_ROUND_UP(t_dqsck, t_ck) - 1; + + tim3 |= val << T_TDQSCKMAX_SHIFT; + + val = DIV_ROUND_UP(timings->tZQCS, t_ck) - 1; + tim3 |= val << ZQ_ZQCS_SHIFT; + + val = DIV_ROUND_UP(timings->tCKESR, t_ck); + val = max(min_tck->tCKESR, val) - 1; + tim3 |= val << T_CKESR_SHIFT; + + if (ip_rev == EMIF_4D5) { + tim3 |= (EMIF_T_CSTA - 1) << T_CSTA_SHIFT; + + val = DIV_ROUND_UP(EMIF_T_PDLL_UL, 128) - 1; + tim3 |= val << T_PDLL_UL_SHIFT; + } + + return tim3; +} + +static u32 get_read_idle_ctrl_shdw(u8 volt_ramp) +{ + u32 idle = 0, val = 0; + + /* + * Maximum value in normal conditions and increased frequency + * when voltage is ramping + */ + if (volt_ramp) + val = READ_IDLE_INTERVAL_DVFS / t_ck / 64 - 1; + else + val = 0x1FF; + + /* + * READ_IDLE_CTRL register in EMIF4D has same offset and fields + * as DLL_CALIB_CTRL in EMIF4D5, so use the same shifts + */ + idle |= val << DLL_CALIB_INTERVAL_SHIFT; + idle |= EMIF_READ_IDLE_LEN_VAL << ACK_WAIT_SHIFT; + + return idle; +} + +static u32 get_dll_calib_ctrl_shdw(u8 volt_ramp) +{ + u32 calib = 0, val = 0; + + if (volt_ramp == DDR_VOLTAGE_RAMPING) + val = DLL_CALIB_INTERVAL_DVFS / t_ck / 16 - 1; + else + val = 0; /* Disabled when voltage is stable */ + + calib |= val << DLL_CALIB_INTERVAL_SHIFT; + calib |= DLL_CALIB_ACK_WAIT_VAL << ACK_WAIT_SHIFT; + + return calib; +} + +static u32 get_ddr_phy_ctrl_1_attilaphy_4d(const struct lpddr2_timings *timings, + u32 freq, u8 RL) +{ + u32 phy = EMIF_DDR_PHY_CTRL_1_BASE_VAL_ATTILAPHY, val = 0; + + val = RL + DIV_ROUND_UP(timings->tDQSCK_max, t_ck) - 1; + phy |= val << READ_LATENCY_SHIFT_4D; + + if (freq <= 100000000) + val = EMIF_DLL_SLAVE_DLY_CTRL_100_MHZ_AND_LESS_ATTILAPHY; + else if (freq <= 200000000) + val = EMIF_DLL_SLAVE_DLY_CTRL_200_MHZ_ATTILAPHY; + else + val = EMIF_DLL_SLAVE_DLY_CTRL_400_MHZ_ATTILAPHY; + + phy |= val << DLL_SLAVE_DLY_CTRL_SHIFT_4D; + + return phy; +} + +static u32 get_phy_ctrl_1_intelliphy_4d5(u32 freq, u8 cl) +{ + u32 phy = EMIF_DDR_PHY_CTRL_1_BASE_VAL_INTELLIPHY, half_delay; + + /* + * DLL operates at 266 MHz. If DDR frequency is near 266 MHz, + * half-delay is not needed else set half-delay + */ + if (freq >= 265000000 && freq < 267000000) + half_delay = 0; + else + half_delay = 1; + + phy |= half_delay << DLL_HALF_DELAY_SHIFT_4D5; + phy |= ((cl + DIV_ROUND_UP(EMIF_PHY_TOTAL_READ_LATENCY_INTELLIPHY_PS, + t_ck) - 1) << READ_LATENCY_SHIFT_4D5); + + return phy; +} + +static u32 get_ext_phy_ctrl_2_intelliphy_4d5(void) +{ + u32 fifo_we_slave_ratio; + + fifo_we_slave_ratio = DIV_ROUND_CLOSEST( + EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS * 256 , t_ck); + + return fifo_we_slave_ratio | fifo_we_slave_ratio << 11 | + fifo_we_slave_ratio << 22; +} + +static u32 get_ext_phy_ctrl_3_intelliphy_4d5(void) +{ + u32 fifo_we_slave_ratio; + + fifo_we_slave_ratio = DIV_ROUND_CLOSEST( + EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS * 256 , t_ck); + + return fifo_we_slave_ratio >> 10 | fifo_we_slave_ratio << 1 | + fifo_we_slave_ratio << 12 | fifo_we_slave_ratio << 23; +} + +static u32 get_ext_phy_ctrl_4_intelliphy_4d5(void) +{ + u32 fifo_we_slave_ratio; + + fifo_we_slave_ratio = DIV_ROUND_CLOSEST( + EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS * 256 , t_ck); + + return fifo_we_slave_ratio >> 9 | fifo_we_slave_ratio << 2 | + fifo_we_slave_ratio << 13; +} + +static u32 get_pwr_mgmt_ctrl(u32 freq, struct emif_data *emif, u32 ip_rev) +{ + u32 pwr_mgmt_ctrl = 0, timeout; + u32 lpmode = EMIF_LP_MODE_SELF_REFRESH; + u32 timeout_perf = EMIF_LP_MODE_TIMEOUT_PERFORMANCE; + u32 timeout_pwr = EMIF_LP_MODE_TIMEOUT_POWER; + u32 freq_threshold = EMIF_LP_MODE_FREQ_THRESHOLD; + + struct emif_custom_configs *cust_cfgs = emif->plat_data->custom_configs; + + if (cust_cfgs && (cust_cfgs->mask & EMIF_CUSTOM_CONFIG_LPMODE)) { + lpmode = cust_cfgs->lpmode; + timeout_perf = cust_cfgs->lpmode_timeout_performance; + timeout_pwr = cust_cfgs->lpmode_timeout_power; + freq_threshold = cust_cfgs->lpmode_freq_threshold; + } + + /* Timeout based on DDR frequency */ + timeout = freq >= freq_threshold ? timeout_perf : timeout_pwr; + + /* The value to be set in register is "log2(timeout) - 3" */ + if (timeout < 16) { + timeout = 0; + } else { + timeout = __fls(timeout) - 3; + if (timeout & (timeout - 1)) + timeout++; + } + + switch (lpmode) { + case EMIF_LP_MODE_CLOCK_STOP: + pwr_mgmt_ctrl = (timeout << CS_TIM_SHIFT) | + SR_TIM_MASK | PD_TIM_MASK; + break; + case EMIF_LP_MODE_SELF_REFRESH: + /* Workaround for errata i735 */ + if (timeout < 6) + timeout = 6; + + pwr_mgmt_ctrl = (timeout << SR_TIM_SHIFT) | + CS_TIM_MASK | PD_TIM_MASK; + break; + case EMIF_LP_MODE_PWR_DN: + pwr_mgmt_ctrl = (timeout << PD_TIM_SHIFT) | + CS_TIM_MASK | SR_TIM_MASK; + break; + case EMIF_LP_MODE_DISABLE: + default: + pwr_mgmt_ctrl = CS_TIM_MASK | + PD_TIM_MASK | SR_TIM_MASK; + } + + /* No CS_TIM in EMIF_4D5 */ + if (ip_rev == EMIF_4D5) + pwr_mgmt_ctrl &= ~CS_TIM_MASK; + + pwr_mgmt_ctrl |= lpmode << LP_MODE_SHIFT; + + return pwr_mgmt_ctrl; +} + +/* + * Program EMIF shadow registers that are not dependent on temperature + * or voltage + */ +static void setup_registers(struct emif_data *emif, struct emif_regs *regs) +{ + void __iomem *base = emif->base; + + writel(regs->sdram_tim2_shdw, base + EMIF_SDRAM_TIMING_2_SHDW); + writel(regs->phy_ctrl_1_shdw, base + EMIF_DDR_PHY_CTRL_1_SHDW); + + /* Settings specific for EMIF4D5 */ + if (emif->plat_data->ip_rev != EMIF_4D5) + return; + writel(regs->ext_phy_ctrl_2_shdw, base + EMIF_EXT_PHY_CTRL_2_SHDW); + writel(regs->ext_phy_ctrl_3_shdw, base + EMIF_EXT_PHY_CTRL_3_SHDW); + writel(regs->ext_phy_ctrl_4_shdw, base + EMIF_EXT_PHY_CTRL_4_SHDW); +} + +/* + * When voltage ramps dll calibration and forced read idle should + * happen more often + */ +static void setup_volt_sensitive_regs(struct emif_data *emif, + struct emif_regs *regs, u32 volt_state) +{ + u32 calib_ctrl; + void __iomem *base = emif->base; + + /* + * EMIF_READ_IDLE_CTRL in EMIF4D refers to the same register as + * EMIF_DLL_CALIB_CTRL in EMIF4D5 and dll_calib_ctrl_shadow_* + * is an alias of the respective read_idle_ctrl_shdw_* (members of + * a union). So, the below code takes care of both cases + */ + if (volt_state == DDR_VOLTAGE_RAMPING) + calib_ctrl = regs->dll_calib_ctrl_shdw_volt_ramp; + else + calib_ctrl = regs->dll_calib_ctrl_shdw_normal; + + writel(calib_ctrl, base + EMIF_DLL_CALIB_CTRL_SHDW); +} + +/* + * setup_temperature_sensitive_regs() - set the timings for temperature + * sensitive registers. This happens once at initialisation time based + * on the temperature at boot time and subsequently based on the temperature + * alert interrupt. Temperature alert can happen when the temperature + * increases or drops. So this function can have the effect of either + * derating the timings or going back to nominal values. + */ +static void setup_temperature_sensitive_regs(struct emif_data *emif, + struct emif_regs *regs) +{ + u32 tim1, tim3, ref_ctrl, type; + void __iomem *base = emif->base; + u32 temperature; + + type = emif->plat_data->device_info->type; + + tim1 = regs->sdram_tim1_shdw; + tim3 = regs->sdram_tim3_shdw; + ref_ctrl = regs->ref_ctrl_shdw; + + /* No de-rating for non-lpddr2 devices */ + if (type != DDR_TYPE_LPDDR2_S2 && type != DDR_TYPE_LPDDR2_S4) + goto out; + + temperature = emif->temperature_level; + if (temperature == SDRAM_TEMP_HIGH_DERATE_REFRESH) { + ref_ctrl = regs->ref_ctrl_shdw_derated; + } else if (temperature == SDRAM_TEMP_HIGH_DERATE_REFRESH_AND_TIMINGS) { + tim1 = regs->sdram_tim1_shdw_derated; + tim3 = regs->sdram_tim3_shdw_derated; + ref_ctrl = regs->ref_ctrl_shdw_derated; + } + +out: + writel(tim1, base + EMIF_SDRAM_TIMING_1_SHDW); + writel(tim3, base + EMIF_SDRAM_TIMING_3_SHDW); + writel(ref_ctrl, base + EMIF_SDRAM_REFRESH_CTRL_SHDW); +} + static void get_default_timings(struct emif_data *emif) { struct emif_platform_data *pd = emif->plat_data; @@ -234,10 +810,8 @@ static int __init_or_module emif_probe(struct platform_device *pdev) goto error; } - if (!emif1) - emif1 = emif; - list_add(&emif->node, &device_list); + emif->addressing = get_addressing_table(emif->plat_data->device_info); /* Save pointers to each other in emif and device structures */ emif->dev = &pdev->dev; @@ -257,6 +831,18 @@ static int __init_or_module emif_probe(struct platform_device *pdev) goto error; } + /* One-time actions taken on probing the first device */ + if (!emif1) { + emif1 = emif; + spin_lock_init(&emif_lock); + + /* + * TODO: register notifiers for frequency and voltage + * change here once the respective frameworks are + * available + */ + } + dev_info(&pdev->dev, "%s: device configured with addr = %p\n", __func__, emif->base); @@ -265,6 +851,308 @@ error: return -ENODEV; } +static int get_emif_reg_values(struct emif_data *emif, u32 freq, + struct emif_regs *regs) +{ + u32 cs1_used, ip_rev, phy_type; + u32 cl, type; + const struct lpddr2_timings *timings; + const struct lpddr2_min_tck *min_tck; + const struct ddr_device_info *device_info; + const struct lpddr2_addressing *addressing; + struct emif_data *emif_for_calc; + struct device *dev; + const struct emif_custom_configs *custom_configs; + + dev = emif->dev; + /* + * If the devices on this EMIF instance is duplicate of EMIF1, + * use EMIF1 details for the calculation + */ + emif_for_calc = emif->duplicate ? emif1 : emif; + timings = get_timings_table(emif_for_calc, freq); + addressing = emif_for_calc->addressing; + if (!timings || !addressing) { + dev_err(dev, "%s: not enough data available for %dHz", + __func__, freq); + return -1; + } + + device_info = emif_for_calc->plat_data->device_info; + type = device_info->type; + cs1_used = device_info->cs1_used; + ip_rev = emif_for_calc->plat_data->ip_rev; + phy_type = emif_for_calc->plat_data->phy_type; + + min_tck = emif_for_calc->plat_data->min_tck; + custom_configs = emif_for_calc->plat_data->custom_configs; + + set_ddr_clk_period(freq); + + regs->ref_ctrl_shdw = get_sdram_ref_ctrl_shdw(freq, addressing); + regs->sdram_tim1_shdw = get_sdram_tim_1_shdw(timings, min_tck, + addressing); + regs->sdram_tim2_shdw = get_sdram_tim_2_shdw(timings, min_tck, + addressing, type); + regs->sdram_tim3_shdw = get_sdram_tim_3_shdw(timings, min_tck, + addressing, type, ip_rev, EMIF_NORMAL_TIMINGS); + + cl = get_cl(emif); + + if (phy_type == EMIF_PHY_TYPE_ATTILAPHY && ip_rev == EMIF_4D) { + regs->phy_ctrl_1_shdw = get_ddr_phy_ctrl_1_attilaphy_4d( + timings, freq, cl); + } else if (phy_type == EMIF_PHY_TYPE_INTELLIPHY && ip_rev == EMIF_4D5) { + regs->phy_ctrl_1_shdw = get_phy_ctrl_1_intelliphy_4d5(freq, cl); + regs->ext_phy_ctrl_2_shdw = get_ext_phy_ctrl_2_intelliphy_4d5(); + regs->ext_phy_ctrl_3_shdw = get_ext_phy_ctrl_3_intelliphy_4d5(); + regs->ext_phy_ctrl_4_shdw = get_ext_phy_ctrl_4_intelliphy_4d5(); + } else { + return -1; + } + + /* Only timeout values in pwr_mgmt_ctrl_shdw register */ + regs->pwr_mgmt_ctrl_shdw = + get_pwr_mgmt_ctrl(freq, emif_for_calc, ip_rev) & + (CS_TIM_MASK | SR_TIM_MASK | PD_TIM_MASK); + + if (ip_rev & EMIF_4D) { + regs->read_idle_ctrl_shdw_normal = + get_read_idle_ctrl_shdw(DDR_VOLTAGE_STABLE); + + regs->read_idle_ctrl_shdw_volt_ramp = + get_read_idle_ctrl_shdw(DDR_VOLTAGE_RAMPING); + } else if (ip_rev & EMIF_4D5) { + regs->dll_calib_ctrl_shdw_normal = + get_dll_calib_ctrl_shdw(DDR_VOLTAGE_STABLE); + + regs->dll_calib_ctrl_shdw_volt_ramp = + get_dll_calib_ctrl_shdw(DDR_VOLTAGE_RAMPING); + } + + if (type == DDR_TYPE_LPDDR2_S2 || type == DDR_TYPE_LPDDR2_S4) { + regs->ref_ctrl_shdw_derated = get_sdram_ref_ctrl_shdw(freq / 4, + addressing); + + regs->sdram_tim1_shdw_derated = + get_sdram_tim_1_shdw_derated(timings, min_tck, + addressing); + + regs->sdram_tim3_shdw_derated = get_sdram_tim_3_shdw(timings, + min_tck, addressing, type, ip_rev, + EMIF_DERATED_TIMINGS); + } + + regs->freq = freq; + + return 0; +} + +/* + * get_regs() - gets the cached emif_regs structure for a given EMIF instance + * given frequency(freq): + * + * As an optimisation, every EMIF instance other than EMIF1 shares the + * register cache with EMIF1 if the devices connected on this instance + * are same as that on EMIF1(indicated by the duplicate flag) + * + * If we do not have an entry corresponding to the frequency given, we + * allocate a new entry and calculate the values + * + * Upon finding the right reg dump, save it in curr_regs. It can be + * directly used for thermal de-rating and voltage ramping changes. + */ +static struct emif_regs *get_regs(struct emif_data *emif, u32 freq) +{ + int i; + struct emif_regs **regs_cache; + struct emif_regs *regs = NULL; + struct device *dev; + + dev = emif->dev; + if (emif->curr_regs && emif->curr_regs->freq == freq) { + dev_dbg(dev, "%s: using curr_regs - %u Hz", __func__, freq); + return emif->curr_regs; + } + + if (emif->duplicate) + regs_cache = emif1->regs_cache; + else + regs_cache = emif->regs_cache; + + for (i = 0; i < EMIF_MAX_NUM_FREQUENCIES && regs_cache[i]; i++) { + if (regs_cache[i]->freq == freq) { + regs = regs_cache[i]; + dev_dbg(dev, + "%s: reg dump found in reg cache for %u Hz\n", + __func__, freq); + break; + } + } + + /* + * If we don't have an entry for this frequency in the cache create one + * and calculate the values + */ + if (!regs) { + regs = devm_kzalloc(emif->dev, sizeof(*regs), GFP_ATOMIC); + if (!regs) + return NULL; + + if (get_emif_reg_values(emif, freq, regs)) { + devm_kfree(emif->dev, regs); + return NULL; + } + + /* + * Now look for an un-used entry in the cache and save the + * newly created struct. If there are no free entries + * over-write the last entry + */ + for (i = 0; i < EMIF_MAX_NUM_FREQUENCIES && regs_cache[i]; i++) + ; + + if (i >= EMIF_MAX_NUM_FREQUENCIES) { + dev_warn(dev, "%s: regs_cache full - reusing a slot!!\n", + __func__); + i = EMIF_MAX_NUM_FREQUENCIES - 1; + devm_kfree(emif->dev, regs_cache[i]); + } + regs_cache[i] = regs; + } + + return regs; +} + +static void do_volt_notify_handling(struct emif_data *emif, u32 volt_state) +{ + dev_dbg(emif->dev, "%s: voltage notification : %d", __func__, + volt_state); + + if (!emif->curr_regs) { + dev_err(emif->dev, + "%s: volt-notify before registers are ready: %d\n", + __func__, volt_state); + return; + } + + setup_volt_sensitive_regs(emif, emif->curr_regs, volt_state); +} + +/* + * TODO: voltage notify handling should be hooked up to + * regulator framework as soon as the necessary support + * is available in mainline kernel. This function is un-used + * right now. + */ +static void __attribute__((unused)) volt_notify_handling(u32 volt_state) +{ + struct emif_data *emif; + + spin_lock_irqsave(&emif_lock, irq_state); + + list_for_each_entry(emif, &device_list, node) + do_volt_notify_handling(emif, volt_state); + do_freq_update(); + + spin_unlock_irqrestore(&emif_lock, irq_state); +} + +static void do_freq_pre_notify_handling(struct emif_data *emif, u32 new_freq) +{ + struct emif_regs *regs; + + regs = get_regs(emif, new_freq); + if (!regs) + return; + + emif->curr_regs = regs; + + /* + * Update the shadow registers: + * Temperature and voltage-ramp sensitive settings are also configured + * in terms of DDR cycles. So, we need to update them too when there + * is a freq change + */ + dev_dbg(emif->dev, "%s: setting up shadow registers for %uHz", + __func__, new_freq); + setup_registers(emif, regs); + setup_temperature_sensitive_regs(emif, regs); + setup_volt_sensitive_regs(emif, regs, DDR_VOLTAGE_STABLE); + + /* + * Part of workaround for errata i728. See do_freq_update() + * for more details + */ + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_DISABLE); +} + +/* + * TODO: frequency notify handling should be hooked up to + * clock framework as soon as the necessary support is + * available in mainline kernel. This function is un-used + * right now. + */ +static void __attribute__((unused)) freq_pre_notify_handling(u32 new_freq) +{ + struct emif_data *emif; + + /* + * NOTE: we are taking the spin-lock here and releases it + * only in post-notifier. This doesn't look good and + * Sparse complains about it, but this seems to be + * un-avoidable. We need to lock a sequence of events + * that is split between EMIF and clock framework. + * + * 1. EMIF driver updates EMIF timings in shadow registers in the + * frequency pre-notify callback from clock framework + * 2. clock framework sets up the registers for the new frequency + * 3. clock framework initiates a hw-sequence that updates + * the frequency EMIF timings synchronously. + * + * All these 3 steps should be performed as an atomic operation + * vis-a-vis similar sequence in the EMIF interrupt handler + * for temperature events. Otherwise, there could be race + * conditions that could result in incorrect EMIF timings for + * a given frequency + */ + spin_lock_irqsave(&emif_lock, irq_state); + + list_for_each_entry(emif, &device_list, node) + do_freq_pre_notify_handling(emif, new_freq); +} + +static void do_freq_post_notify_handling(struct emif_data *emif) +{ + /* + * Part of workaround for errata i728. See do_freq_update() + * for more details + */ + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_SELF_REFRESH); +} + +/* + * TODO: frequency notify handling should be hooked up to + * clock framework as soon as the necessary support is + * available in mainline kernel. This function is un-used + * right now. + */ +static void __attribute__((unused)) freq_post_notify_handling(void) +{ + struct emif_data *emif; + + list_for_each_entry(emif, &device_list, node) + do_freq_post_notify_handling(emif); + + /* + * Lock is done in pre-notify handler. See freq_pre_notify_handling() + * for more details + */ + spin_unlock_irqrestore(&emif_lock, irq_state); +} + static struct platform_driver emif_driver = { .driver = { .name = "emif", diff --git a/drivers/memory/emif.h b/drivers/memory/emif.h index 692b2a864e7b..bfe08bae961a 100644 --- a/drivers/memory/emif.h +++ b/drivers/memory/emif.h @@ -19,6 +19,103 @@ */ #define EMIF_MAX_NUM_FREQUENCIES 6 +/* State of the core voltage */ +#define DDR_VOLTAGE_STABLE 0 +#define DDR_VOLTAGE_RAMPING 1 + +/* Defines for timing De-rating */ +#define EMIF_NORMAL_TIMINGS 0 +#define EMIF_DERATED_TIMINGS 1 + +/* Length of the forced read idle period in terms of cycles */ +#define EMIF_READ_IDLE_LEN_VAL 5 + +/* + * forced read idle interval to be used when voltage + * is changed as part of DVFS/DPS - 1ms + */ +#define READ_IDLE_INTERVAL_DVFS (1*1000000) + +/* + * Forced read idle interval to be used when voltage is stable + * 50us - or maximum value will do + */ +#define READ_IDLE_INTERVAL_NORMAL (50*1000000) + +/* DLL calibration interval when voltage is NOT stable - 1us */ +#define DLL_CALIB_INTERVAL_DVFS (1*1000000) + +#define DLL_CALIB_ACK_WAIT_VAL 5 + +/* Interval between ZQCS commands - hw team recommended value */ +#define EMIF_ZQCS_INTERVAL_US (50*1000) +/* Enable ZQ Calibration on exiting Self-refresh */ +#define ZQ_SFEXITEN_ENABLE 1 +/* + * ZQ Calibration simultaneously on both chip-selects: + * Needs one calibration resistor per CS + */ +#define ZQ_DUALCALEN_DISABLE 0 +#define ZQ_DUALCALEN_ENABLE 1 + +#define T_ZQCS_DEFAULT_NS 90 +#define T_ZQCL_DEFAULT_NS 360 +#define T_ZQINIT_DEFAULT_NS 1000 + +/* DPD_EN */ +#define DPD_DISABLE 0 +#define DPD_ENABLE 1 + +/* + * Default values for the low-power entry to be used if not provided by user. + * OMAP4/5 has a hw bug(i735) due to which this value can not be less than 512 + * Timeout values are in DDR clock 'cycles' and frequency threshold in Hz + */ +#define EMIF_LP_MODE_TIMEOUT_PERFORMANCE 2048 +#define EMIF_LP_MODE_TIMEOUT_POWER 512 +#define EMIF_LP_MODE_FREQ_THRESHOLD 400000000 + +/* DDR_PHY_CTRL_1 values for EMIF4D - ATTILA PHY combination */ +#define EMIF_DDR_PHY_CTRL_1_BASE_VAL_ATTILAPHY 0x049FF000 +#define EMIF_DLL_SLAVE_DLY_CTRL_400_MHZ_ATTILAPHY 0x41 +#define EMIF_DLL_SLAVE_DLY_CTRL_200_MHZ_ATTILAPHY 0x80 +#define EMIF_DLL_SLAVE_DLY_CTRL_100_MHZ_AND_LESS_ATTILAPHY 0xFF + +/* DDR_PHY_CTRL_1 values for EMIF4D5 INTELLIPHY combination */ +#define EMIF_DDR_PHY_CTRL_1_BASE_VAL_INTELLIPHY 0x0E084200 +#define EMIF_PHY_TOTAL_READ_LATENCY_INTELLIPHY_PS 10000 + +/* TEMP_ALERT_CONFIG - corresponding to temp gradient 5 C/s */ +#define TEMP_ALERT_POLL_INTERVAL_DEFAULT_MS 360 + +#define EMIF_T_CSTA 3 +#define EMIF_T_PDLL_UL 128 + +/* External PHY control registers magic values */ +#define EMIF_EXT_PHY_CTRL_1_VAL 0x04020080 +#define EMIF_EXT_PHY_CTRL_5_VAL 0x04010040 +#define EMIF_EXT_PHY_CTRL_6_VAL 0x01004010 +#define EMIF_EXT_PHY_CTRL_7_VAL 0x00001004 +#define EMIF_EXT_PHY_CTRL_8_VAL 0x04010040 +#define EMIF_EXT_PHY_CTRL_9_VAL 0x01004010 +#define EMIF_EXT_PHY_CTRL_10_VAL 0x00001004 +#define EMIF_EXT_PHY_CTRL_11_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_12_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_13_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_14_VAL 0x80080080 +#define EMIF_EXT_PHY_CTRL_15_VAL 0x00800800 +#define EMIF_EXT_PHY_CTRL_16_VAL 0x08102040 +#define EMIF_EXT_PHY_CTRL_17_VAL 0x00000001 +#define EMIF_EXT_PHY_CTRL_18_VAL 0x540A8150 +#define EMIF_EXT_PHY_CTRL_19_VAL 0xA81502A0 +#define EMIF_EXT_PHY_CTRL_20_VAL 0x002A0540 +#define EMIF_EXT_PHY_CTRL_21_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_22_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_23_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_24_VAL 0x00000077 + +#define EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS 1200 + /* Registers offset */ #define EMIF_MODULE_ID_AND_REVISION 0x0000 #define EMIF_STATUS 0x0004 @@ -458,4 +555,35 @@ #define READ_LATENCY_SHDW_SHIFT 0 #define READ_LATENCY_SHDW_MASK (0x1f << 0) -#endif +#ifndef __ASSEMBLY__ +/* + * Structure containing shadow of important registers in EMIF + * The calculation function fills in this structure to be later used for + * initialisation and DVFS + */ +struct emif_regs { + u32 freq; + u32 ref_ctrl_shdw; + u32 ref_ctrl_shdw_derated; + u32 sdram_tim1_shdw; + u32 sdram_tim1_shdw_derated; + u32 sdram_tim2_shdw; + u32 sdram_tim3_shdw; + u32 sdram_tim3_shdw_derated; + u32 pwr_mgmt_ctrl_shdw; + union { + u32 read_idle_ctrl_shdw_normal; + u32 dll_calib_ctrl_shdw_normal; + }; + union { + u32 read_idle_ctrl_shdw_volt_ramp; + u32 dll_calib_ctrl_shdw_volt_ramp; + }; + + u32 phy_ctrl_1_shdw; + u32 ext_phy_ctrl_2_shdw; + u32 ext_phy_ctrl_3_shdw; + u32 ext_phy_ctrl_4_shdw; +}; +#endif /* __ASSEMBLY__ */ +#endif /* __EMIF_H */ -- cgit v1.2.3 From 68b4aee35d1fb8399068aa6e6c908584690d0b06 Mon Sep 17 00:00:00 2001 From: Aneesh V Date: Fri, 27 Apr 2012 17:54:07 +0530 Subject: memory: emif: add interrupt and temperature handling Add an ISR for EMIF that: 1. reports details of access errors 2. takes action on thermal events Also clear all interrupts on shut-down. Pending IRQs may casue problems during warm-reset. Temperature handling: EMIF can be configured to poll the temperature level of an LPDDR2 device from the MR4 mode register in the device. EMIF generates an interrupt whenever it identifies a temperature level change between two consecutive pollings. Some of the timing parameters need to be de-rated at high temperatures. The interrupt handler takes care of doing this and also takes care of going back to nominal settings when temperature falls back to nominal levels. Signed-off-by: Aneesh V Reviewed-by: Santosh Shilimkar Reviewed-by: Benoit Cousson [santosh.shilimkar@ti.com: Moved to drivers/memory from drivers/misc] Signed-off-by: Santosh Shilimkar Signed-off-by: Greg Kroah-Hartman --- drivers/memory/emif.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index bd116eb8c738..a8dcf3515573 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -544,6 +544,42 @@ static u32 get_pwr_mgmt_ctrl(u32 freq, struct emif_data *emif, u32 ip_rev) return pwr_mgmt_ctrl; } +/* + * Get the temperature level of the EMIF instance: + * Reads the MR4 register of attached SDRAM parts to find out the temperature + * level. If there are two parts attached(one on each CS), then the temperature + * level for the EMIF instance is the higher of the two temperatures. + */ +static void get_temperature_level(struct emif_data *emif) +{ + u32 temp, temperature_level; + void __iomem *base; + + base = emif->base; + + /* Read mode register 4 */ + writel(DDR_MR4, base + EMIF_LPDDR2_MODE_REG_CONFIG); + temperature_level = readl(base + EMIF_LPDDR2_MODE_REG_DATA); + temperature_level = (temperature_level & MR4_SDRAM_REF_RATE_MASK) >> + MR4_SDRAM_REF_RATE_SHIFT; + + if (emif->plat_data->device_info->cs1_used) { + writel(DDR_MR4 | CS_MASK, base + EMIF_LPDDR2_MODE_REG_CONFIG); + temp = readl(base + EMIF_LPDDR2_MODE_REG_DATA); + temp = (temp & MR4_SDRAM_REF_RATE_MASK) + >> MR4_SDRAM_REF_RATE_SHIFT; + temperature_level = max(temp, temperature_level); + } + + /* treat everything less than nominal(3) in MR4 as nominal */ + if (unlikely(temperature_level < SDRAM_TEMP_NOMINAL)) + temperature_level = SDRAM_TEMP_NOMINAL; + + /* if we get reserved value in MR4 persist with the existing value */ + if (likely(temperature_level != SDRAM_TEMP_RESERVED_4)) + emif->temperature_level = temperature_level; +} + /* * Program EMIF shadow registers that are not dependent on temperature * or voltage @@ -627,6 +663,158 @@ out: writel(ref_ctrl, base + EMIF_SDRAM_REFRESH_CTRL_SHDW); } +static irqreturn_t handle_temp_alert(void __iomem *base, struct emif_data *emif) +{ + u32 old_temp_level; + irqreturn_t ret = IRQ_HANDLED; + + spin_lock_irqsave(&emif_lock, irq_state); + old_temp_level = emif->temperature_level; + get_temperature_level(emif); + + if (unlikely(emif->temperature_level == old_temp_level)) { + goto out; + } else if (!emif->curr_regs) { + dev_err(emif->dev, "temperature alert before registers are calculated, not de-rating timings\n"); + goto out; + } + + if (emif->temperature_level < old_temp_level || + emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) { + /* + * Temperature coming down - defer handling to thread OR + * Temperature far too high - do kernel_power_off() from + * thread context + */ + ret = IRQ_WAKE_THREAD; + } else { + /* Temperature is going up - handle immediately */ + setup_temperature_sensitive_regs(emif, emif->curr_regs); + do_freq_update(); + } + +out: + spin_unlock_irqrestore(&emif_lock, irq_state); + return ret; +} + +static irqreturn_t emif_interrupt_handler(int irq, void *dev_id) +{ + u32 interrupts; + struct emif_data *emif = dev_id; + void __iomem *base = emif->base; + struct device *dev = emif->dev; + irqreturn_t ret = IRQ_HANDLED; + + /* Save the status and clear it */ + interrupts = readl(base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS); + writel(interrupts, base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS); + + /* + * Handle temperature alert + * Temperature alert should be same for all ports + * So, it's enough to process it only for one of the ports + */ + if (interrupts & TA_SYS_MASK) + ret = handle_temp_alert(base, emif); + + if (interrupts & ERR_SYS_MASK) + dev_err(dev, "Access error from SYS port - %x\n", interrupts); + + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) { + /* Save the status and clear it */ + interrupts = readl(base + EMIF_LL_OCP_INTERRUPT_STATUS); + writel(interrupts, base + EMIF_LL_OCP_INTERRUPT_STATUS); + + if (interrupts & ERR_LL_MASK) + dev_err(dev, "Access error from LL port - %x\n", + interrupts); + } + + return ret; +} + +static irqreturn_t emif_threaded_isr(int irq, void *dev_id) +{ + struct emif_data *emif = dev_id; + + if (emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) { + dev_emerg(emif->dev, "SDRAM temperature exceeds operating limit.. Needs shut down!!!\n"); + kernel_power_off(); + return IRQ_HANDLED; + } + + spin_lock_irqsave(&emif_lock, irq_state); + + if (emif->curr_regs) { + setup_temperature_sensitive_regs(emif, emif->curr_regs); + do_freq_update(); + } else { + dev_err(emif->dev, "temperature alert before registers are calculated, not de-rating timings\n"); + } + + spin_unlock_irqrestore(&emif_lock, irq_state); + + return IRQ_HANDLED; +} + +static void clear_all_interrupts(struct emif_data *emif) +{ + void __iomem *base = emif->base; + + writel(readl(base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS), + base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS); + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) + writel(readl(base + EMIF_LL_OCP_INTERRUPT_STATUS), + base + EMIF_LL_OCP_INTERRUPT_STATUS); +} + +static void disable_and_clear_all_interrupts(struct emif_data *emif) +{ + void __iomem *base = emif->base; + + /* Disable all interrupts */ + writel(readl(base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET), + base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_CLEAR); + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) + writel(readl(base + EMIF_LL_OCP_INTERRUPT_ENABLE_SET), + base + EMIF_LL_OCP_INTERRUPT_ENABLE_CLEAR); + + /* Clear all interrupts */ + clear_all_interrupts(emif); +} + +static int __init_or_module setup_interrupts(struct emif_data *emif, u32 irq) +{ + u32 interrupts, type; + void __iomem *base = emif->base; + + type = emif->plat_data->device_info->type; + + clear_all_interrupts(emif); + + /* Enable interrupts for SYS interface */ + interrupts = EN_ERR_SYS_MASK; + if (type == DDR_TYPE_LPDDR2_S2 || type == DDR_TYPE_LPDDR2_S4) + interrupts |= EN_TA_SYS_MASK; + writel(interrupts, base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET); + + /* Enable interrupts for LL interface */ + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) { + /* TA need not be enabled for LL */ + interrupts = EN_ERR_LL_MASK; + writel(interrupts, base + EMIF_LL_OCP_INTERRUPT_ENABLE_SET); + } + + /* setup IRQ handlers */ + return devm_request_threaded_irq(emif->dev, irq, + emif_interrupt_handler, + emif_threaded_isr, + 0, dev_name(emif->dev), + emif); + +} + static void get_default_timings(struct emif_data *emif) { struct emif_platform_data *pd = emif->plat_data; @@ -803,6 +991,7 @@ static int __init_or_module emif_probe(struct platform_device *pdev) { struct emif_data *emif; struct resource *res; + int irq; emif = get_device_details(pdev); if (!emif) { @@ -831,6 +1020,16 @@ static int __init_or_module emif_probe(struct platform_device *pdev) goto error; } + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(emif->dev, "%s: error getting IRQ resource - %d\n", + __func__, irq); + goto error; + } + + disable_and_clear_all_interrupts(emif); + setup_interrupts(emif, irq); + /* One-time actions taken on probing the first device */ if (!emif1) { emif1 = emif; @@ -843,14 +1042,21 @@ static int __init_or_module emif_probe(struct platform_device *pdev) */ } - dev_info(&pdev->dev, "%s: device configured with addr = %p\n", - __func__, emif->base); + dev_info(&pdev->dev, "%s: device configured with addr = %p and IRQ%d\n", + __func__, emif->base, irq); return 0; error: return -ENODEV; } +static void emif_shutdown(struct platform_device *pdev) +{ + struct emif_data *emif = platform_get_drvdata(pdev); + + disable_and_clear_all_interrupts(emif); +} + static int get_emif_reg_values(struct emif_data *emif, u32 freq, struct emif_regs *regs) { @@ -1154,6 +1360,7 @@ static void __attribute__((unused)) freq_post_notify_handling(void) } static struct platform_driver emif_driver = { + .shutdown = emif_shutdown, .driver = { .name = "emif", }, -- cgit v1.2.3 From 98231c4fc3b95a0e9d37ba3711ad993bb27605c8 Mon Sep 17 00:00:00 2001 From: Aneesh V Date: Fri, 27 Apr 2012 17:54:08 +0530 Subject: memory: emif: add one-time settings Add settings that are not dependent on frequency or any other transient parameters. This includes - power managment control init - impedence calibration control - frequency independent phy configuration registers - initialization of temperature polling Signed-off-by: Aneesh V Reviewed-by: Santosh Shilimkar Reviewed-by: Benoit Cousson [santosh.shilimkar@ti.com: Moved to drivers/memory from drivers/misc] Signed-off-by: Santosh Shilimkar Tested-by: Lokesh Vutla Signed-off-by: Greg Kroah-Hartman --- drivers/memory/emif.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) (limited to 'drivers') diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index a8dcf3515573..3dfffbb8ab40 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -77,6 +77,24 @@ static void set_ddr_clk_period(u32 freq) t_ck = (u32)DIV_ROUND_UP_ULL(1000000000000ull, freq); } +/* + * Get bus width used by EMIF. Note that this may be different from the + * bus width of the DDR devices used. For instance two 16-bit DDR devices + * may be connected to a given CS of EMIF. In this case bus width as far + * as EMIF is concerned is 32, where as the DDR bus width is 16 bits. + */ +static u32 get_emif_bus_width(struct emif_data *emif) +{ + u32 width; + void __iomem *base = emif->base; + + width = (readl(base + EMIF_SDRAM_CONFIG) & NARROW_MODE_MASK) + >> NARROW_MODE_SHIFT; + width = width == 0 ? 32 : 16; + + return width; +} + /* * Get the CL from SDRAM_CONFIG register */ @@ -372,6 +390,70 @@ static u32 get_sdram_tim_3_shdw(const struct lpddr2_timings *timings, return tim3; } +static u32 get_zq_config_reg(const struct lpddr2_addressing *addressing, + bool cs1_used, bool cal_resistors_per_cs) +{ + u32 zq = 0, val = 0; + + val = EMIF_ZQCS_INTERVAL_US * 1000 / addressing->tREFI_ns; + zq |= val << ZQ_REFINTERVAL_SHIFT; + + val = DIV_ROUND_UP(T_ZQCL_DEFAULT_NS, T_ZQCS_DEFAULT_NS) - 1; + zq |= val << ZQ_ZQCL_MULT_SHIFT; + + val = DIV_ROUND_UP(T_ZQINIT_DEFAULT_NS, T_ZQCL_DEFAULT_NS) - 1; + zq |= val << ZQ_ZQINIT_MULT_SHIFT; + + zq |= ZQ_SFEXITEN_ENABLE << ZQ_SFEXITEN_SHIFT; + + if (cal_resistors_per_cs) + zq |= ZQ_DUALCALEN_ENABLE << ZQ_DUALCALEN_SHIFT; + else + zq |= ZQ_DUALCALEN_DISABLE << ZQ_DUALCALEN_SHIFT; + + zq |= ZQ_CS0EN_MASK; /* CS0 is used for sure */ + + val = cs1_used ? 1 : 0; + zq |= val << ZQ_CS1EN_SHIFT; + + return zq; +} + +static u32 get_temp_alert_config(const struct lpddr2_addressing *addressing, + const struct emif_custom_configs *custom_configs, bool cs1_used, + u32 sdram_io_width, u32 emif_bus_width) +{ + u32 alert = 0, interval, devcnt; + + if (custom_configs && (custom_configs->mask & + EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL)) + interval = custom_configs->temp_alert_poll_interval_ms; + else + interval = TEMP_ALERT_POLL_INTERVAL_DEFAULT_MS; + + interval *= 1000000; /* Convert to ns */ + interval /= addressing->tREFI_ns; /* Convert to refresh cycles */ + alert |= (interval << TA_REFINTERVAL_SHIFT); + + /* + * sdram_io_width is in 'log2(x) - 1' form. Convert emif_bus_width + * also to this form and subtract to get TA_DEVCNT, which is + * in log2(x) form. + */ + emif_bus_width = __fls(emif_bus_width) - 1; + devcnt = emif_bus_width - sdram_io_width; + alert |= devcnt << TA_DEVCNT_SHIFT; + + /* DEVWDT is in 'log2(x) - 3' form */ + alert |= (sdram_io_width - 2) << TA_DEVWDT_SHIFT; + + alert |= 1 << TA_SFEXITEN_SHIFT; + alert |= 1 << TA_CS0EN_SHIFT; + alert |= (cs1_used ? 1 : 0) << TA_CS1EN_SHIFT; + + return alert; +} + static u32 get_read_idle_ctrl_shdw(u8 volt_ramp) { u32 idle = 0, val = 0; @@ -815,6 +897,71 @@ static int __init_or_module setup_interrupts(struct emif_data *emif, u32 irq) } +static void __init_or_module emif_onetime_settings(struct emif_data *emif) +{ + u32 pwr_mgmt_ctrl, zq, temp_alert_cfg; + void __iomem *base = emif->base; + const struct lpddr2_addressing *addressing; + const struct ddr_device_info *device_info; + + device_info = emif->plat_data->device_info; + addressing = get_addressing_table(device_info); + + /* + * Init power management settings + * We don't know the frequency yet. Use a high frequency + * value for a conservative timeout setting + */ + pwr_mgmt_ctrl = get_pwr_mgmt_ctrl(1000000000, emif, + emif->plat_data->ip_rev); + emif->lpmode = (pwr_mgmt_ctrl & LP_MODE_MASK) >> LP_MODE_SHIFT; + writel(pwr_mgmt_ctrl, base + EMIF_POWER_MANAGEMENT_CONTROL); + + /* Init ZQ calibration settings */ + zq = get_zq_config_reg(addressing, device_info->cs1_used, + device_info->cal_resistors_per_cs); + writel(zq, base + EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG); + + /* Check temperature level temperature level*/ + get_temperature_level(emif); + if (emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) + dev_emerg(emif->dev, "SDRAM temperature exceeds operating limit.. Needs shut down!!!\n"); + + /* Init temperature polling */ + temp_alert_cfg = get_temp_alert_config(addressing, + emif->plat_data->custom_configs, device_info->cs1_used, + device_info->io_width, get_emif_bus_width(emif)); + writel(temp_alert_cfg, base + EMIF_TEMPERATURE_ALERT_CONFIG); + + /* + * Program external PHY control registers that are not frequency + * dependent + */ + if (emif->plat_data->phy_type != EMIF_PHY_TYPE_INTELLIPHY) + return; + writel(EMIF_EXT_PHY_CTRL_1_VAL, base + EMIF_EXT_PHY_CTRL_1_SHDW); + writel(EMIF_EXT_PHY_CTRL_5_VAL, base + EMIF_EXT_PHY_CTRL_5_SHDW); + writel(EMIF_EXT_PHY_CTRL_6_VAL, base + EMIF_EXT_PHY_CTRL_6_SHDW); + writel(EMIF_EXT_PHY_CTRL_7_VAL, base + EMIF_EXT_PHY_CTRL_7_SHDW); + writel(EMIF_EXT_PHY_CTRL_8_VAL, base + EMIF_EXT_PHY_CTRL_8_SHDW); + writel(EMIF_EXT_PHY_CTRL_9_VAL, base + EMIF_EXT_PHY_CTRL_9_SHDW); + writel(EMIF_EXT_PHY_CTRL_10_VAL, base + EMIF_EXT_PHY_CTRL_10_SHDW); + writel(EMIF_EXT_PHY_CTRL_11_VAL, base + EMIF_EXT_PHY_CTRL_11_SHDW); + writel(EMIF_EXT_PHY_CTRL_12_VAL, base + EMIF_EXT_PHY_CTRL_12_SHDW); + writel(EMIF_EXT_PHY_CTRL_13_VAL, base + EMIF_EXT_PHY_CTRL_13_SHDW); + writel(EMIF_EXT_PHY_CTRL_14_VAL, base + EMIF_EXT_PHY_CTRL_14_SHDW); + writel(EMIF_EXT_PHY_CTRL_15_VAL, base + EMIF_EXT_PHY_CTRL_15_SHDW); + writel(EMIF_EXT_PHY_CTRL_16_VAL, base + EMIF_EXT_PHY_CTRL_16_SHDW); + writel(EMIF_EXT_PHY_CTRL_17_VAL, base + EMIF_EXT_PHY_CTRL_17_SHDW); + writel(EMIF_EXT_PHY_CTRL_18_VAL, base + EMIF_EXT_PHY_CTRL_18_SHDW); + writel(EMIF_EXT_PHY_CTRL_19_VAL, base + EMIF_EXT_PHY_CTRL_19_SHDW); + writel(EMIF_EXT_PHY_CTRL_20_VAL, base + EMIF_EXT_PHY_CTRL_20_SHDW); + writel(EMIF_EXT_PHY_CTRL_21_VAL, base + EMIF_EXT_PHY_CTRL_21_SHDW); + writel(EMIF_EXT_PHY_CTRL_22_VAL, base + EMIF_EXT_PHY_CTRL_22_SHDW); + writel(EMIF_EXT_PHY_CTRL_23_VAL, base + EMIF_EXT_PHY_CTRL_23_SHDW); + writel(EMIF_EXT_PHY_CTRL_24_VAL, base + EMIF_EXT_PHY_CTRL_24_SHDW); +} + static void get_default_timings(struct emif_data *emif) { struct emif_platform_data *pd = emif->plat_data; @@ -1027,6 +1174,7 @@ static int __init_or_module emif_probe(struct platform_device *pdev) goto error; } + emif_onetime_settings(emif); disable_and_clear_all_interrupts(emif); setup_interrupts(emif, irq); -- cgit v1.2.3 From aac10aaa8cc65a6fef6f5bc7d0b96035b0225a61 Mon Sep 17 00:00:00 2001 From: Aneesh V Date: Fri, 27 Apr 2012 17:54:09 +0530 Subject: memory: emif: add debugfs entries for emif Add debug entries for: 1. calculated registers per frequency 2. last polled value of MR4(temperature level of LPDDR2 memory) Signed-off-by: Aneesh V Reviewed-by: Santosh Shilimkar Reviewed-by: Benoit Cousson [santosh.shilimkar@ti.com: Moved to drivers/memory from drivers/misc] Signed-off-by: Santosh Shilimkar Tested-by: Lokesh Vutla Signed-off-by: Greg Kroah-Hartman --- drivers/memory/emif.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) (limited to 'drivers') diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c index 3dfffbb8ab40..33a4396b24cb 100644 --- a/drivers/memory/emif.c +++ b/drivers/memory/emif.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ * frequency change (i.e. corresponding to the * frequency in effect at the moment) * @plat_data: Pointer to saved platform data. + * @debugfs_root: dentry to the root folder for EMIF in debugfs */ struct emif_data { u8 duplicate; @@ -60,6 +62,7 @@ struct emif_data { struct emif_regs *regs_cache[EMIF_MAX_NUM_FREQUENCIES]; struct emif_regs *curr_regs; struct emif_platform_data *plat_data; + struct dentry *debugfs_root; }; static struct emif_data *emif1; @@ -68,6 +71,130 @@ static unsigned long irq_state; static u32 t_ck; /* DDR clock period in ps */ static LIST_HEAD(device_list); +static void do_emif_regdump_show(struct seq_file *s, struct emif_data *emif, + struct emif_regs *regs) +{ + u32 type = emif->plat_data->device_info->type; + u32 ip_rev = emif->plat_data->ip_rev; + + seq_printf(s, "EMIF register cache dump for %dMHz\n", + regs->freq/1000000); + + seq_printf(s, "ref_ctrl_shdw\t: 0x%08x\n", regs->ref_ctrl_shdw); + seq_printf(s, "sdram_tim1_shdw\t: 0x%08x\n", regs->sdram_tim1_shdw); + seq_printf(s, "sdram_tim2_shdw\t: 0x%08x\n", regs->sdram_tim2_shdw); + seq_printf(s, "sdram_tim3_shdw\t: 0x%08x\n", regs->sdram_tim3_shdw); + + if (ip_rev == EMIF_4D) { + seq_printf(s, "read_idle_ctrl_shdw_normal\t: 0x%08x\n", + regs->read_idle_ctrl_shdw_normal); + seq_printf(s, "read_idle_ctrl_shdw_volt_ramp\t: 0x%08x\n", + regs->read_idle_ctrl_shdw_volt_ramp); + } else if (ip_rev == EMIF_4D5) { + seq_printf(s, "dll_calib_ctrl_shdw_normal\t: 0x%08x\n", + regs->dll_calib_ctrl_shdw_normal); + seq_printf(s, "dll_calib_ctrl_shdw_volt_ramp\t: 0x%08x\n", + regs->dll_calib_ctrl_shdw_volt_ramp); + } + + if (type == DDR_TYPE_LPDDR2_S2 || type == DDR_TYPE_LPDDR2_S4) { + seq_printf(s, "ref_ctrl_shdw_derated\t: 0x%08x\n", + regs->ref_ctrl_shdw_derated); + seq_printf(s, "sdram_tim1_shdw_derated\t: 0x%08x\n", + regs->sdram_tim1_shdw_derated); + seq_printf(s, "sdram_tim3_shdw_derated\t: 0x%08x\n", + regs->sdram_tim3_shdw_derated); + } +} + +static int emif_regdump_show(struct seq_file *s, void *unused) +{ + struct emif_data *emif = s->private; + struct emif_regs **regs_cache; + int i; + + if (emif->duplicate) + regs_cache = emif1->regs_cache; + else + regs_cache = emif->regs_cache; + + for (i = 0; i < EMIF_MAX_NUM_FREQUENCIES && regs_cache[i]; i++) { + do_emif_regdump_show(s, emif, regs_cache[i]); + seq_printf(s, "\n"); + } + + return 0; +} + +static int emif_regdump_open(struct inode *inode, struct file *file) +{ + return single_open(file, emif_regdump_show, inode->i_private); +} + +static const struct file_operations emif_regdump_fops = { + .open = emif_regdump_open, + .read = seq_read, + .release = single_release, +}; + +static int emif_mr4_show(struct seq_file *s, void *unused) +{ + struct emif_data *emif = s->private; + + seq_printf(s, "MR4=%d\n", emif->temperature_level); + return 0; +} + +static int emif_mr4_open(struct inode *inode, struct file *file) +{ + return single_open(file, emif_mr4_show, inode->i_private); +} + +static const struct file_operations emif_mr4_fops = { + .open = emif_mr4_open, + .read = seq_read, + .release = single_release, +}; + +static int __init_or_module emif_debugfs_init(struct emif_data *emif) +{ + struct dentry *dentry; + int ret; + + dentry = debugfs_create_dir(dev_name(emif->dev), NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto err0; + } + emif->debugfs_root = dentry; + + dentry = debugfs_create_file("regcache_dump", S_IRUGO, + emif->debugfs_root, emif, &emif_regdump_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto err1; + } + + dentry = debugfs_create_file("mr4", S_IRUGO, + emif->debugfs_root, emif, &emif_mr4_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto err1; + } + + return 0; +err1: + debugfs_remove_recursive(emif->debugfs_root); +err0: + return ret; +} + +static void __exit emif_debugfs_exit(struct emif_data *emif) +{ + debugfs_remove_recursive(emif->debugfs_root); + emif->debugfs_root = NULL; +} + /* * Calculate the period of DDR clock from frequency value */ @@ -1175,6 +1302,7 @@ static int __init_or_module emif_probe(struct platform_device *pdev) } emif_onetime_settings(emif); + emif_debugfs_init(emif); disable_and_clear_all_interrupts(emif); setup_interrupts(emif, irq); @@ -1198,6 +1326,15 @@ error: return -ENODEV; } +static int __exit emif_remove(struct platform_device *pdev) +{ + struct emif_data *emif = platform_get_drvdata(pdev); + + emif_debugfs_exit(emif); + + return 0; +} + static void emif_shutdown(struct platform_device *pdev) { struct emif_data *emif = platform_get_drvdata(pdev); @@ -1508,6 +1645,7 @@ static void __attribute__((unused)) freq_post_notify_handling(void) } static struct platform_driver emif_driver = { + .remove = __exit_p(emif_remove), .shutdown = emif_shutdown, .driver = { .name = "emif", -- cgit v1.2.3 From 2878bda864c53db599c4a3e3b136ed3e2af68ca0 Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Wed, 2 May 2012 15:38:44 -0700 Subject: Extcon: fix section mismatch in extcon_gpio.c Fix the section mismatch be renaming the struct platform_driver variable. Signed-off-by: H Hartley Sweeten Cc: MyungJoo Ham Cc: Mark Brown Cc: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_gpio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon_gpio.c index 5c0f08506f93..fe7a07b47336 100644 --- a/drivers/extcon/extcon_gpio.c +++ b/drivers/extcon/extcon_gpio.c @@ -153,7 +153,7 @@ static int __devexit gpio_extcon_remove(struct platform_device *pdev) return 0; } -static struct platform_driver gpio_extcon = { +static struct platform_driver gpio_extcon_driver = { .probe = gpio_extcon_probe, .remove = __devexit_p(gpio_extcon_remove), .driver = { @@ -162,7 +162,7 @@ static struct platform_driver gpio_extcon = { }, }; -module_platform_driver(gpio_extcon); +module_platform_driver(gpio_extcon_driver); MODULE_AUTHOR("Mike Lockwood "); MODULE_DESCRIPTION("GPIO extcon driver"); -- cgit v1.2.3 From 18e9a971c7fb17dab079305a23af5bb57b0706b1 Mon Sep 17 00:00:00 2001 From: Santosh Shilimkar Date: Fri, 4 May 2012 11:38:11 +0530 Subject: memory: emif: Add Kconfig dependency for TI EMIF controller Make TI_EMIF depends on ARCH_OMAP2PLUS to avoid build breaks on other architectures. In future if other TI non OMAP socs start using it, the dependency can be extended. Signed-off-by: Santosh Shilimkar Reported-by: Paul Gortmaker Cc: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/memory/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index b08327cca0e5..e0b3156d65b5 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -9,6 +9,7 @@ if MEMORY config TI_EMIF tristate "Texas Instruments EMIF driver" + depends on ARCH_OMAP2PLUS select DDR help This driver is for the EMIF module available in Texas Instruments -- cgit v1.2.3 From 0e1507c8453081c9a6a515b92f89dd00b68f5c09 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 2 May 2012 10:38:51 +0100 Subject: extcon: Add EXTCON_MECHANICAL cable type for physical presence Some accessory detection mechanisms are able to detect that something is physically present in the socket separately to identifying what is present in the socket. This information can be useful to applications, for example allowing them to indicate that a potentially broken accessory is present, so provide a standard way to report it to userspace. Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 4657ad38164b..f598a700ec15 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -60,6 +60,7 @@ const char *extcon_cable_name[] = { [EXTCON_SPDIF_OUT] = "SPDIF-out", [EXTCON_VIDEO_IN] = "Video-in", [EXTCON_VIDEO_OUT] = "Video-out", + [EXTCON_MECHANICAL] = "Mechanical", NULL, }; -- cgit v1.2.3 From 4ae68e7345b0d5d49e1c2c7583d15a3c177f8a2f Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Wed, 2 May 2012 17:02:07 -0700 Subject: w1: w1_ds2408.c: quite sparse noise about using plaing integer as NULL pointer NULL not 0 should be used with pointers. Just remove the offending lines since they will default to NULL anyway. Signed-off-by: H Hartley Sweeten Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/w1_ds2408.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/w1/slaves/w1_ds2408.c b/drivers/w1/slaves/w1_ds2408.c index 7c8cdb8aed26..8e813eed0f0a 100644 --- a/drivers/w1/slaves/w1_ds2408.c +++ b/drivers/w1/slaves/w1_ds2408.c @@ -332,7 +332,6 @@ static struct bin_attribute w1_f29_sysfs_bin_files[NB_SYSFS_BIN_FILES] = { }, .size = 1, .read = w1_f29_read_cond_search_mask, - .write = 0, }, { .attr = { @@ -341,7 +340,6 @@ static struct bin_attribute w1_f29_sysfs_bin_files[NB_SYSFS_BIN_FILES] = { }, .size = 1, .read = w1_f29_read_cond_search_polarity, - .write = 0, }, { .attr = { -- cgit v1.2.3 From 698cd2ddd851b34e7200b4f846ae68306e11bae4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 3 May 2012 18:15:12 +0100 Subject: devres: Clarify documentation for devres_destroy() It's not massively obvious (at least to me) that removing and freeing a resource does not involve calling the release function for the resource but rather only removes the management of it. Make the documentation more explicit. Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/base/devres.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/base/devres.c b/drivers/base/devres.c index 524bf96c289f..1741a60de117 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -309,6 +309,10 @@ EXPORT_SYMBOL_GPL(devres_remove); * which @match returns 1. If @match is NULL, it's considered to * match all. If found, the resource is removed atomically and freed. * + * Note that the release function for the resource will not be called, + * only the devres-allocated data will be freed. The caller becomes + * responsible for freeing any other data. + * * RETURNS: * 0 if devres is found and freed, -ENOENT if not found. */ -- cgit v1.2.3 From d926d0e4c74cfcb42a05e91d1cdf698b41e1e118 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 3 May 2012 18:15:13 +0100 Subject: devres: Add devres_release() APIs using devres frequently want to implement a "remove and free the resource" operation so it seems sensible that they should be able to just have devres do the freeing for them since that's a big part of what devres is all about. Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/base/devres.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'drivers') diff --git a/drivers/base/devres.c b/drivers/base/devres.c index 1741a60de117..2360adb7a58f 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -330,6 +330,37 @@ int devres_destroy(struct device *dev, dr_release_t release, } EXPORT_SYMBOL_GPL(devres_destroy); + +/** + * devres_release - Find a device resource and destroy it, calling release + * @dev: Device to find resource from + * @release: Look for resources associated with this release function + * @match: Match function (optional) + * @match_data: Data for the match function + * + * Find the latest devres of @dev associated with @release and for + * which @match returns 1. If @match is NULL, it's considered to + * match all. If found, the resource is removed atomically, the + * release function called and the resource freed. + * + * RETURNS: + * 0 if devres is found and freed, -ENOENT if not found. + */ +int devres_release(struct device *dev, dr_release_t release, + dr_match_t match, void *match_data) +{ + void *res; + + res = devres_remove(dev, release, match, match_data); + if (unlikely(!res)) + return -ENOENT; + + (*release)(dev, res); + devres_free(res); + return 0; +} +EXPORT_SYMBOL_GPL(devres_release); + static int remove_nodes(struct device *dev, struct list_head *first, struct list_head *end, struct list_head *todo) -- cgit v1.2.3 From a85990b3b126e4fa443f1cbc18250a92c144ac27 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 3 May 2012 18:15:14 +0100 Subject: gpiolib: Convert to devres_release() Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/devres.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/devres.c b/drivers/gpio/devres.c index 3dd29399cef5..8950f6261bbb 100644 --- a/drivers/gpio/devres.c +++ b/drivers/gpio/devres.c @@ -83,8 +83,7 @@ EXPORT_SYMBOL(devm_gpio_request); void devm_gpio_free(struct device *dev, unsigned int gpio) { - WARN_ON(devres_destroy(dev, devm_gpio_release, devm_gpio_match, + WARN_ON(devres_release(dev, devm_gpio_release, devm_gpio_match, &gpio)); - gpio_free(gpio); } EXPORT_SYMBOL(devm_gpio_free); -- cgit v1.2.3 From 7ff9554bb578ba02166071d2d487b7fc7d860d62 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Thu, 3 May 2012 02:29:13 +0200 Subject: printk: convert byte-buffer to variable-length record buffer - Record-based stream instead of the traditional byte stream buffer. All records carry a 64 bit timestamp, the syslog facility and priority in the record header. - Records consume almost the same amount, sometimes less memory than the traditional byte stream buffer (if printk_time is enabled). The record header is 16 bytes long, plus some padding bytes at the end if needed. The byte-stream buffer needed 3 chars for the syslog prefix, 15 char for the timestamp and a newline. - Buffer management is based on message sequence numbers. When records need to be discarded, the reading heads move on to the next full record. Unlike the byte-stream buffer, no old logged lines get truncated or partly overwritten by new ones. Sequence numbers also allow consumers of the log stream to get notified if any message in the stream they are about to read gets discarded during the time of reading. - Better buffered IO support for KERN_CONT continuation lines, when printk() is called multiple times for a single line. The use of KERN_CONT is now mandatory to use continuation; a few places in the kernel need trivial fixes here. The buffering could possibly be extended to per-cpu variables to allow better thread-safety for multiple printk() invocations for a single line. - Full-featured syslog facility value support. Different facilities can tag their messages. All userspace-injected messages enforce a facility value > 0 now, to be able to reliably distinguish them from the kernel-generated messages. Independent subsystems like a baseband processor running its own firmware, or a kernel-related userspace process can use their own unique facility values. Multiple independent log streams can co-exist that way in the same buffer. All share the same global sequence number counter to ensure proper ordering (and interleaving) and to allow the consumers of the log to reliably correlate the events from different facilities. Tested-by: William Douglas Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/char/mem.c | 55 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/char/mem.c b/drivers/char/mem.c index d6e9d081c8b1..cf56614479ef 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -810,33 +810,54 @@ static const struct file_operations oldmem_fops = { static ssize_t kmsg_writev(struct kiocb *iocb, const struct iovec *iv, unsigned long count, loff_t pos) { - char *line, *p; + char *buf, *line; int i; - ssize_t ret = -EFAULT; + int level = default_message_loglevel; + int facility = 1; /* LOG_USER */ size_t len = iov_length(iv, count); + ssize_t ret = len; - line = kmalloc(len + 1, GFP_KERNEL); - if (line == NULL) + if (len > 1024) + return -EINVAL; + buf = kmalloc(len+1, GFP_KERNEL); + if (buf == NULL) return -ENOMEM; - /* - * copy all vectors into a single string, to ensure we do - * not interleave our log line with other printk calls - */ - p = line; + line = buf; for (i = 0; i < count; i++) { - if (copy_from_user(p, iv[i].iov_base, iv[i].iov_len)) + if (copy_from_user(line, iv[i].iov_base, iv[i].iov_len)) goto out; - p += iv[i].iov_len; + line += iv[i].iov_len; + } + + /* + * Extract and skip the syslog prefix <[0-9]*>. Coming from userspace + * the decimal value represents 32bit, the lower 3 bit are the log + * level, the rest are the log facility. + * + * If no prefix or no userspace facility is specified, we + * enforce LOG_USER, to be able to reliably distinguish + * kernel-generated messages from userspace-injected ones. + */ + line = buf; + if (line[0] == '<') { + char *endp = NULL; + + i = simple_strtoul(line+1, &endp, 10); + if (endp && endp[0] == '>') { + level = i & 7; + if (i >> 3) + facility = i >> 3; + endp++; + len -= endp - line; + line = endp; + } } - p[0] = '\0'; + line[len] = '\0'; - ret = printk("%s", line); - /* printk can add a prefix */ - if (ret > len) - ret = len; + printk_emit(facility, level, NULL, 0, "%s", line); out: - kfree(line); + kfree(buf); return ret; } -- cgit v1.2.3 From e11fea92e13fb91c50bacca799a6131c81929986 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Thu, 3 May 2012 02:29:41 +0200 Subject: kmsg: export printk records to the /dev/kmsg interface Support for multiple concurrent readers of /dev/kmsg, with read(), seek(), poll() support. Output of message sequence numbers, to allow userspace log consumers to reliably reconnect and reconstruct their state at any given time. After open("/dev/kmsg"), read() always returns *all* buffered records. If only future messages should be read, SEEK_END can be used. In case records get overwritten while /dev/kmsg is held open, or records get faster overwritten than they are read, the next read() will return -EPIPE and the current reading position gets updated to the next available record. The passed sequence numbers allow the log consumer to calculate the amount of lost messages. [root@mop ~]# cat /dev/kmsg 5,0,0;Linux version 3.4.0-rc1+ (kay@mop) (gcc version 4.7.0 20120315 ... 6,159,423091;ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-ff]) 7,160,424069;pci_root PNP0A03:00: host bridge window [io 0x0000-0x0cf7] (ignored) SUBSYSTEM=acpi DEVICE=+acpi:PNP0A03:00 6,339,5140900;NET: Registered protocol family 10 30,340,5690716;udevd[80]: starting version 181 6,341,6081421;FDC 0 is a S82078B 6,345,6154686;microcode: CPU0 sig=0x623, pf=0x0, revision=0x0 7,346,6156968;sr 1:0:0:0: Attached scsi CD-ROM sr0 SUBSYSTEM=scsi DEVICE=+scsi:1:0:0:0 6,347,6289375;microcode: CPU1 sig=0x623, pf=0x0, revision=0x0 Cc: Karel Zak Tested-by: William Douglas Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/char/mem.c | 61 +----------------------------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) (limited to 'drivers') diff --git a/drivers/char/mem.c b/drivers/char/mem.c index cf56614479ef..0e7fbfcbd6e6 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -807,65 +807,6 @@ static const struct file_operations oldmem_fops = { }; #endif -static ssize_t kmsg_writev(struct kiocb *iocb, const struct iovec *iv, - unsigned long count, loff_t pos) -{ - char *buf, *line; - int i; - int level = default_message_loglevel; - int facility = 1; /* LOG_USER */ - size_t len = iov_length(iv, count); - ssize_t ret = len; - - if (len > 1024) - return -EINVAL; - buf = kmalloc(len+1, GFP_KERNEL); - if (buf == NULL) - return -ENOMEM; - - line = buf; - for (i = 0; i < count; i++) { - if (copy_from_user(line, iv[i].iov_base, iv[i].iov_len)) - goto out; - line += iv[i].iov_len; - } - - /* - * Extract and skip the syslog prefix <[0-9]*>. Coming from userspace - * the decimal value represents 32bit, the lower 3 bit are the log - * level, the rest are the log facility. - * - * If no prefix or no userspace facility is specified, we - * enforce LOG_USER, to be able to reliably distinguish - * kernel-generated messages from userspace-injected ones. - */ - line = buf; - if (line[0] == '<') { - char *endp = NULL; - - i = simple_strtoul(line+1, &endp, 10); - if (endp && endp[0] == '>') { - level = i & 7; - if (i >> 3) - facility = i >> 3; - endp++; - len -= endp - line; - line = endp; - } - } - line[len] = '\0'; - - printk_emit(facility, level, NULL, 0, "%s", line); -out: - kfree(buf); - return ret; -} - -static const struct file_operations kmsg_fops = { - .aio_write = kmsg_writev, - .llseek = noop_llseek, -}; - static const struct memdev { const char *name; umode_t mode; @@ -884,7 +825,7 @@ static const struct memdev { [7] = { "full", 0666, &full_fops, NULL }, [8] = { "random", 0666, &random_fops, NULL }, [9] = { "urandom", 0666, &urandom_fops, NULL }, - [11] = { "kmsg", 0, &kmsg_fops, NULL }, + [11] = { "kmsg", 0644, &kmsg_fops, NULL }, #ifdef CONFIG_CRASH_DUMP [12] = { "oldmem", 0, &oldmem_fops, NULL }, #endif -- cgit v1.2.3 From c4e00daaa96d3a0786f1f4fe6456281c60ef9a16 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Thu, 3 May 2012 02:29:59 +0200 Subject: driver-core: extend dev_printk() to pass structured data Extends dev_printk() to attach a dictionary with a device identifier and the driver core subsystem name to logged messages, which makes dev_prink() reliable machine-readable. In addition to the printed plain text message, it creates these properties: SUBSYSTEM= - the driver-core subsytem name DEVICE= b12:8 - block dev_t c127:3 - char dev_t n8 - netdev ifindex +sound:card0 - subsystem:devname Tested-by: William Douglas Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/base/core.c b/drivers/base/core.c index 33461ec35d8f..346be8b78b24 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "base.h" #include "power/power.h" @@ -1843,15 +1844,60 @@ void device_shutdown(void) */ #ifdef CONFIG_PRINTK - int __dev_printk(const char *level, const struct device *dev, struct va_format *vaf) { + char dict[128]; + size_t dictlen = 0; + const char *subsys; + if (!dev) return printk("%s(NULL device *): %pV", level, vaf); - return printk("%s%s %s: %pV", - level, dev_driver_string(dev), dev_name(dev), vaf); + if (dev->class) + subsys = dev->class->name; + else if (dev->bus) + subsys = dev->bus->name; + else + goto skip; + + dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, + "SUBSYSTEM=%s", subsys); + + /* + * Add device identifier DEVICE=: + * b12:8 block dev_t + * c127:3 char dev_t + * n8 netdev ifindex + * +sound:card0 subsystem:devname + */ + if (MAJOR(dev->devt)) { + char c; + + if (strcmp(subsys, "block") == 0) + c = 'b'; + else + c = 'c'; + dictlen++; + dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, + "DEVICE=%c%u:%u", + c, MAJOR(dev->devt), MINOR(dev->devt)); + } else if (strcmp(subsys, "net") == 0) { + struct net_device *net = to_net_dev(dev); + + dictlen++; + dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, + "DEVICE=n%u", net->ifindex); + } else { + dictlen++; + dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen, + "DEVICE=+%s:%s", subsys, dev_name(dev)); + } +skip: + return printk_emit(0, level[1] - '0', + dictlen ? dict : NULL, dictlen, + "%s %s: %pV", + dev_driver_string(dev), dev_name(dev), vaf); } EXPORT_SYMBOL(__dev_printk); -- cgit v1.2.3 From 2c03ead66a2f0f39c38a455891b749ad48a2e1a7 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Tue, 8 May 2012 13:14:49 +0200 Subject: parport: use KERN_CONT in printk() continuation lines On Tue, May 8, 2012 at 10:48 AM, Sasha Levin wrote: > Before: > [ 10.110626] parport0: PC-style at 0x378, irq 7 [PCSPP,TRISTATE] > > After: > parport0: PC-style at 0x378 > , irq 7 > [ > PCSPP > ,TRISTATE > ] Reported-By: Sasha Levin Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/parport/parport_pc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/parport/parport_pc.c b/drivers/parport/parport_pc.c index 0cb64f50cecd..937981a3a77f 100644 --- a/drivers/parport/parport_pc.c +++ b/drivers/parport/parport_pc.c @@ -2351,7 +2351,7 @@ struct parport *parport_pc_probe_port(unsigned long int base, printk(KERN_INFO "%s: PC-style at 0x%lx", p->name, p->base); if (p->base_hi && priv->ecr) - printk(" (0x%lx)", p->base_hi); + printk(KERN_CONT " (0x%lx)", p->base_hi); if (p->irq == PARPORT_IRQ_AUTO) { p->irq = PARPORT_IRQ_NONE; parport_irq_probe(p); @@ -2362,7 +2362,7 @@ struct parport *parport_pc_probe_port(unsigned long int base, p->irq = PARPORT_IRQ_NONE; } if (p->irq != PARPORT_IRQ_NONE) { - printk(", irq %d", p->irq); + printk(KERN_CONT ", irq %d", p->irq); priv->ctr_writable |= 0x10; if (p->dma == PARPORT_DMA_AUTO) { @@ -2386,21 +2386,21 @@ struct parport *parport_pc_probe_port(unsigned long int base, /* p->ops->ecp_read_data = parport_pc_ecp_read_block_pio; */ #endif /* IEEE 1284 support */ if (p->dma != PARPORT_DMA_NONE) { - printk(", dma %d", p->dma); + printk(KERN_CONT ", dma %d", p->dma); p->modes |= PARPORT_MODE_DMA; } else - printk(", using FIFO"); + printk(KERN_CONT ", using FIFO"); } else /* We can't use the DMA channel after all. */ p->dma = PARPORT_DMA_NONE; #endif /* Allowed to use FIFO/DMA */ - printk(" ["); + printk(KERN_CONT " ["); #define printmode(x) \ {\ if (p->modes & PARPORT_MODE_##x) {\ - printk("%s%s", f ? "," : "", #x);\ + printk(KERN_CONT "%s%s", f ? "," : "", #x);\ f++;\ } \ } @@ -2416,9 +2416,9 @@ struct parport *parport_pc_probe_port(unsigned long int base, } #undef printmode #ifndef CONFIG_PARPORT_1284 - printk("(,...)"); + printk(KERN_CONT "(,...)"); #endif /* CONFIG_PARPORT_1284 */ - printk("]\n"); + printk(KERN_CONT "]\n"); if (probedirq != PARPORT_IRQ_NONE) printk(KERN_INFO "%s: irq %d detected\n", p->name, probedirq); -- cgit v1.2.3 From be96447e0d49622fe00b07474f9a86805d389ca7 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Tue, 8 May 2012 17:24:20 +0200 Subject: acpi: use KERN_CONT in printk() continuation lines Cc: Len Brown Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/acpi/pci_link.c | 12 ++++++------ drivers/acpi/sleep.c | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index 4a29763b8eb4..a12808259dfb 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -720,21 +720,21 @@ static int acpi_pci_link_add(struct acpi_device *device) acpi_device_bid(device)); for (i = 0; i < link->irq.possible_count; i++) { if (link->irq.active == link->irq.possible[i]) { - printk(" *%d", link->irq.possible[i]); + printk(KERN_CONT " *%d", link->irq.possible[i]); found = 1; } else - printk(" %d", link->irq.possible[i]); + printk(KERN_CONT " %d", link->irq.possible[i]); } - printk(")"); + printk(KERN_CONT ")"); if (!found) - printk(" *%d", link->irq.active); + printk(KERN_CONT " *%d", link->irq.active); if (!link->device->status.enabled) - printk(", disabled."); + printk(KERN_CONT ", disabled."); - printk("\n"); + printk(KERN_CONT "\n"); list_add_tail(&link->list, &acpi_link_list); diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index eb6fd233764b..06527c526618 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -887,7 +887,7 @@ int __init acpi_sleep_init(void) status = acpi_get_sleep_type_data(i, &type_a, &type_b); if (ACPI_SUCCESS(status)) { sleep_states[i] = 1; - printk(" S%d", i); + printk(KERN_CONT " S%d", i); } } @@ -901,7 +901,7 @@ int __init acpi_sleep_init(void) hibernation_set_ops(old_suspend_ordering ? &acpi_hibernation_ops_old : &acpi_hibernation_ops); sleep_states[ACPI_STATE_S4] = 1; - printk(" S4"); + printk(KERN_CONT " S4"); if (!nosigcheck) { acpi_get_table(ACPI_SIG_FACS, 1, (struct acpi_table_header **)&facs); @@ -914,11 +914,11 @@ int __init acpi_sleep_init(void) status = acpi_get_sleep_type_data(ACPI_STATE_S5, &type_a, &type_b); if (ACPI_SUCCESS(status)) { sleep_states[ACPI_STATE_S5] = 1; - printk(" S5"); + printk(KERN_CONT " S5"); pm_power_off_prepare = acpi_power_off_prepare; pm_power_off = acpi_power_off; } - printk(")\n"); + printk(KERN_CONT ")\n"); /* * Register the tts_notifier to reboot notifier list so that the _TTS * object can also be evaluated when the system enters S5. -- cgit v1.2.3 From b76668ba8a7722f589af2e13a340f3629430a35a Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 9 May 2012 12:31:58 +0900 Subject: Extcon: add MAX8997 extcon driver This patch add extcon-max8997 driver to support the muic feature of Maxim max8997 by using Extcon framework. The extcon-max8997 driver is implemented based on 'drivers/misc/ max8997-muic.c' and then use Extcon interface instead of callback function in struct max8997_muic_platform_data to notify cable state of notifee which want to know always newly cable state when external connector(e.g., USB, TA, JIG) is attached or detached. v1 - Use Extcon interface to notify cable state of notifee instead of callback function when external connector is attached or detached. - Bug fix of getting platform_data for irq_base value. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/Kconfig | 8 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-max8997.c | 537 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 546 insertions(+) create mode 100644 drivers/extcon/extcon-max8997.c (limited to 'drivers') diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 85cecff36db3..29c5cf852efc 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -21,4 +21,12 @@ config EXTCON_GPIO Say Y here to enable GPIO based extcon support. Note that GPIO extcon supports single state per extcon instance. +config EXTCON_MAX8997 + tristate "MAX8997 EXTCON Support" + depends on MFD_MAX8997 + help + If you say yes here you get support for the MUIC device of + Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory + detector and switch. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 2c46d4176d18..86020bdb6da0 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_EXTCON) += extcon_class.o obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o +obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c new file mode 100644 index 000000000000..2b1aa04a27dd --- /dev/null +++ b/drivers/extcon/extcon-max8997.c @@ -0,0 +1,537 @@ +/* + * extcon-max8997.c - MAX8997 extcon driver to support MAX8997 MUIC + * + * Copyright (C) 2012 Samsung Electrnoics + * Donggeun Kim + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEV_NAME "max8997-muic" + +/* MAX8997-MUIC STATUS1 register */ +#define STATUS1_ADC_SHIFT 0 +#define STATUS1_ADCLOW_SHIFT 5 +#define STATUS1_ADCERR_SHIFT 6 +#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) +#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) + +/* MAX8997-MUIC STATUS2 register */ +#define STATUS2_CHGTYP_SHIFT 0 +#define STATUS2_CHGDETRUN_SHIFT 3 +#define STATUS2_DCDTMR_SHIFT 4 +#define STATUS2_DBCHG_SHIFT 5 +#define STATUS2_VBVOLT_SHIFT 6 +#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) +#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) +#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) +#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) + +/* MAX8997-MUIC STATUS3 register */ +#define STATUS3_OVP_SHIFT 2 +#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) + +/* MAX8997-MUIC CONTROL1 register */ +#define COMN1SW_SHIFT 0 +#define COMP2SW_SHIFT 3 +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define SW_MASK (COMP2SW_MASK | COMN1SW_MASK) + +#define MAX8997_SW_USB ((1 << COMP2SW_SHIFT) | (1 << COMN1SW_SHIFT)) +#define MAX8997_SW_AUDIO ((2 << COMP2SW_SHIFT) | (2 << COMN1SW_SHIFT)) +#define MAX8997_SW_UART ((3 << COMP2SW_SHIFT) | (3 << COMN1SW_SHIFT)) +#define MAX8997_SW_OPEN ((0 << COMP2SW_SHIFT) | (0 << COMN1SW_SHIFT)) + +#define MAX8997_ADC_GROUND 0x00 +#define MAX8997_ADC_MHL 0x01 +#define MAX8997_ADC_JIG_USB_1 0x18 +#define MAX8997_ADC_JIG_USB_2 0x19 +#define MAX8997_ADC_DESKDOCK 0x1a +#define MAX8997_ADC_JIG_UART 0x1c +#define MAX8997_ADC_CARDOCK 0x1d +#define MAX8997_ADC_OPEN 0x1f + +struct max8997_muic_irq { + unsigned int irq; + const char *name; +}; + +static struct max8997_muic_irq muic_irqs[] = { + { MAX8997_MUICIRQ_ADCError, "muic-ADC_error" }, + { MAX8997_MUICIRQ_ADCLow, "muic-ADC_low" }, + { MAX8997_MUICIRQ_ADC, "muic-ADC" }, + { MAX8997_MUICIRQ_VBVolt, "muic-VB_voltage" }, + { MAX8997_MUICIRQ_DBChg, "muic-DB_charger" }, + { MAX8997_MUICIRQ_DCDTmr, "muic-DCD_timer" }, + { MAX8997_MUICIRQ_ChgDetRun, "muic-CDR_status" }, + { MAX8997_MUICIRQ_ChgTyp, "muic-charger_type" }, + { MAX8997_MUICIRQ_OVP, "muic-over_voltage" }, +}; + +struct max8997_muic_info { + struct device *dev; + struct i2c_client *muic; + struct max8997_muic_platform_data *muic_pdata; + + int irq; + struct work_struct irq_work; + + enum max8997_muic_charger_type pre_charger_type; + int pre_adc; + + struct mutex mutex; + + struct extcon_dev *edev; +}; + +const char *max8997_extcon_cable[] = { + [0] = "USB", + [1] = "USB-Host", + [2] = "TA", + [3] = "Fast-charger", + [4] = "Slow-charger", + [5] = "Charge-downstream", + [6] = "MHL", + [7] = "Dock-desk", + [7] = "Dock-card", + [8] = "JIG", + + NULL, +}; + +static int max8997_muic_handle_usb(struct max8997_muic_info *info, + enum max8997_muic_usb_type usb_type, bool attached) +{ + int ret = 0; + + if (usb_type == MAX8997_USB_HOST) { + /* switch to USB */ + ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, + attached ? MAX8997_SW_USB : MAX8997_SW_OPEN, + SW_MASK); + if (ret) { + dev_err(info->dev, "failed to update muic register\n"); + goto out; + } + } + + switch (usb_type) { + case MAX8997_USB_HOST: + extcon_set_cable_state(info->edev, "USB-Host", attached); + break; + case MAX8997_USB_DEVICE: + extcon_set_cable_state(info->edev, "USB", attached); + break; + default: + ret = -EINVAL; + break; + } + +out: + return ret; +} + +static int max8997_muic_handle_dock(struct max8997_muic_info *info, + int adc, bool attached) +{ + int ret = 0; + + /* switch to AUDIO */ + ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, + attached ? MAX8997_SW_AUDIO : MAX8997_SW_OPEN, + SW_MASK); + if (ret) { + dev_err(info->dev, "failed to update muic register\n"); + goto out; + } + + switch (adc) { + case MAX8997_ADC_DESKDOCK: + extcon_set_cable_state(info->edev, "Dock-desk", attached); + break; + case MAX8997_ADC_CARDOCK: + extcon_set_cable_state(info->edev, "Dock-card", attached); + break; + default: + ret = -EINVAL; + break; + } +out: + return ret; +} + +static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, + bool attached) +{ + int ret = 0; + + /* switch to UART */ + ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, + attached ? MAX8997_SW_UART : MAX8997_SW_OPEN, + SW_MASK); + if (ret) { + dev_err(info->dev, "failed to update muic register\n"); + goto out; + } + + extcon_set_cable_state(info->edev, "JIG", attached); +out: + return ret; +} + +static int max8997_muic_handle_adc_detach(struct max8997_muic_info *info) +{ + int ret = 0; + + switch (info->pre_adc) { + case MAX8997_ADC_GROUND: + ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, false); + break; + case MAX8997_ADC_MHL: + extcon_set_cable_state(info->edev, "MHL", false); + break; + case MAX8997_ADC_JIG_USB_1: + case MAX8997_ADC_JIG_USB_2: + ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, false); + break; + case MAX8997_ADC_DESKDOCK: + case MAX8997_ADC_CARDOCK: + ret = max8997_muic_handle_dock(info, info->pre_adc, false); + break; + case MAX8997_ADC_JIG_UART: + ret = max8997_muic_handle_jig_uart(info, false); + break; + default: + break; + } + + return ret; +} + +static int max8997_muic_handle_adc(struct max8997_muic_info *info, int adc) +{ + int ret = 0; + + switch (adc) { + case MAX8997_ADC_GROUND: + ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, true); + break; + case MAX8997_ADC_MHL: + extcon_set_cable_state(info->edev, "MHL", true); + break; + case MAX8997_ADC_JIG_USB_1: + case MAX8997_ADC_JIG_USB_2: + ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, true); + break; + case MAX8997_ADC_DESKDOCK: + case MAX8997_ADC_CARDOCK: + ret = max8997_muic_handle_dock(info, adc, true); + break; + case MAX8997_ADC_JIG_UART: + ret = max8997_muic_handle_jig_uart(info, true); + break; + case MAX8997_ADC_OPEN: + ret = max8997_muic_handle_adc_detach(info); + break; + default: + ret = -EINVAL; + goto out; + } + + info->pre_adc = adc; +out: + return ret; +} + +static int max8997_muic_handle_charger_type_detach( + struct max8997_muic_info *info) +{ + int ret = 0; + + switch (info->pre_charger_type) { + case MAX8997_CHARGER_TYPE_USB: + extcon_set_cable_state(info->edev, "USB", false); + break; + case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: + extcon_set_cable_state(info->edev, "Charge-downstream", false); + break; + case MAX8997_CHARGER_TYPE_DEDICATED_CHG: + extcon_set_cable_state(info->edev, "TA", false); + break; + case MAX8997_CHARGER_TYPE_500MA: + extcon_set_cable_state(info->edev, "Slow-charger", false); + break; + case MAX8997_CHARGER_TYPE_1A: + extcon_set_cable_state(info->edev, "Fast-charger", false); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int max8997_muic_handle_charger_type(struct max8997_muic_info *info, + enum max8997_muic_charger_type charger_type) +{ + u8 adc; + int ret; + + ret = max8997_read_reg(info->muic, MAX8997_MUIC_REG_STATUS1, &adc); + if (ret) { + dev_err(info->dev, "failed to read muic register\n"); + goto out; + } + + switch (charger_type) { + case MAX8997_CHARGER_TYPE_NONE: + ret = max8997_muic_handle_charger_type_detach(info); + break; + case MAX8997_CHARGER_TYPE_USB: + if ((adc & STATUS1_ADC_MASK) == MAX8997_ADC_OPEN) { + max8997_muic_handle_usb(info, + MAX8997_USB_DEVICE, true); + } + break; + case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: + extcon_set_cable_state(info->edev, "Charge-downstream", true); + break; + case MAX8997_CHARGER_TYPE_DEDICATED_CHG: + extcon_set_cable_state(info->edev, "TA", true); + break; + case MAX8997_CHARGER_TYPE_500MA: + extcon_set_cable_state(info->edev, "Slow-charger", true); + break; + case MAX8997_CHARGER_TYPE_1A: + extcon_set_cable_state(info->edev, "Fast-charger", true); + break; + default: + ret = -EINVAL; + goto out; + } + + info->pre_charger_type = charger_type; +out: + return ret; +} + +static void max8997_muic_irq_work(struct work_struct *work) +{ + struct max8997_muic_info *info = container_of(work, + struct max8997_muic_info, irq_work); + struct max8997_dev *max8997 = i2c_get_clientdata(info->muic); + u8 status[2]; + u8 adc, chg_type; + + int irq_type = info->irq - max8997->irq_base; + int ret; + + mutex_lock(&info->mutex); + + ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, + 2, status); + if (ret) { + dev_err(info->dev, "failed to read muic register\n"); + mutex_unlock(&info->mutex); + return; + } + + dev_dbg(info->dev, "%s: STATUS1:0x%x, 2:0x%x\n", __func__, + status[0], status[1]); + + switch (irq_type) { + case MAX8997_MUICIRQ_ADC: + adc = status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + max8997_muic_handle_adc(info, adc); + break; + case MAX8997_MUICIRQ_ChgTyp: + chg_type = status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + max8997_muic_handle_charger_type(info, chg_type); + break; + default: + dev_info(info->dev, "misc interrupt: irq %d occurred\n", + irq_type); + break; + } + + mutex_unlock(&info->mutex); + + return; +} + +static irqreturn_t max8997_muic_irq_handler(int irq, void *data) +{ + struct max8997_muic_info *info = data; + + dev_dbg(info->dev, "irq:%d\n", irq); + info->irq = irq; + + schedule_work(&info->irq_work); + + return IRQ_HANDLED; +} + +static void max8997_muic_detect_dev(struct max8997_muic_info *info) +{ + int ret; + u8 status[2], adc, chg_type; + + ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, + 2, status); + if (ret) { + dev_err(info->dev, "failed to read muic register\n"); + return; + } + + dev_info(info->dev, "STATUS1:0x%x, STATUS2:0x%x\n", + status[0], status[1]); + + adc = status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + chg_type = status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + max8997_muic_handle_adc(info, adc); + max8997_muic_handle_charger_type(info, chg_type); +} + +static int __devinit max8997_muic_probe(struct platform_device *pdev) +{ + struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev); + struct max8997_muic_info *info; + int ret, i; + + info = kzalloc(sizeof(struct max8997_muic_info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kfree; + } + + info->dev = &pdev->dev; + info->muic = max8997->muic; + + platform_set_drvdata(pdev, info); + mutex_init(&info->mutex); + + INIT_WORK(&info->irq_work, max8997_muic_irq_work); + + for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { + struct max8997_muic_irq *muic_irq = &muic_irqs[i]; + + ret = request_threaded_irq(pdata->irq_base + muic_irq->irq, + NULL, max8997_muic_irq_handler, + 0, muic_irq->name, + info); + if (ret) { + dev_err(&pdev->dev, + "failed: irq request (IRQ: %d," + " error :%d)\n", + muic_irq->irq, ret); + + for (i = i - 1; i >= 0; i--) + free_irq(muic_irq->irq, info); + + goto err_irq; + } + } + + /* External connector */ + info->edev = kzalloc(sizeof(struct extcon_dev), GFP_KERNEL); + if (!info->edev) { + dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); + ret = -ENOMEM; + goto err_irq; + } + info->edev->name = DEV_NAME; + info->edev->supported_cable = max8997_extcon_cable; + ret = extcon_dev_register(info->edev, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to register extcon device\n"); + goto err_extcon; + } + + /* Initialize registers according to platform data */ + if (pdata->muic_pdata) { + struct max8997_muic_platform_data *mdata = info->muic_pdata; + + for (i = 0; i < mdata->num_init_data; i++) { + max8997_write_reg(info->muic, mdata->init_data[i].addr, + mdata->init_data[i].data); + } + } + + /* Initial device detection */ + max8997_muic_detect_dev(info); + + return ret; + +err_extcon: + kfree(info->edev); +err_irq: + kfree(info); +err_kfree: + return ret; +} + +static int __devexit max8997_muic_remove(struct platform_device *pdev) +{ + struct max8997_muic_info *info = platform_get_drvdata(pdev); + struct max8997_dev *max8997 = i2c_get_clientdata(info->muic); + int i; + + for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) + free_irq(max8997->irq_base + muic_irqs[i].irq, info); + cancel_work_sync(&info->irq_work); + + extcon_dev_unregister(info->edev); + + kfree(info); + + return 0; +} + +static struct platform_driver max8997_muic_driver = { + .driver = { + .name = DEV_NAME, + .owner = THIS_MODULE, + }, + .probe = max8997_muic_probe, + .remove = __devexit_p(max8997_muic_remove), +}; + +module_platform_driver(max8997_muic_driver); + +MODULE_DESCRIPTION("Maxim MAX8997 Extcon driver"); +MODULE_AUTHOR("Donggeun Kim "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 6a7e2618b3dbfbf1e8ab2b4be102b2944738fb68 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 9 May 2012 12:32:04 +0900 Subject: misc: MAX8997: Remove max8997-muic driver This patch remove old max8997-muic drvier because of newly Extcon framework. Extcon framework manages the external connector, so add extcon-max8997 driver by using Extcon interface to support MUIC feature of Maxim 8997 PMIC instead of max8997-muic driver(drivers/misc/max8997-muic.c). Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 8 - drivers/misc/Makefile | 1 - drivers/misc/max8997-muic.c | 495 -------------------------------------------- 3 files changed, 504 deletions(-) delete mode 100644 drivers/misc/max8997-muic.c (limited to 'drivers') diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c7795096d43b..5e86d8820ff6 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -490,14 +490,6 @@ config USB_SWITCH_FSA9480 stereo and mono audio, video, microphone and UART data to use a common connector port. -config MAX8997_MUIC - tristate "MAX8997 MUIC Support" - depends on MFD_MAX8997 - help - If you say yes here you get support for the MUIC device of - Maxim MAX8997 PMIC. - The MAX8997 MUIC is a USB port accessory detector and switch. - source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 3e1d80106f04..b26495a02554 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -48,4 +48,3 @@ obj-y += lis3lv02d/ obj-y += carma/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ -obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o diff --git a/drivers/misc/max8997-muic.c b/drivers/misc/max8997-muic.c deleted file mode 100644 index 19591eaa492a..000000000000 --- a/drivers/misc/max8997-muic.c +++ /dev/null @@ -1,495 +0,0 @@ -/* - * max8997-muic.c - MAX8997 muic driver for the Maxim 8997 - * - * Copyright (C) 2011 Samsung Electrnoics - * Donggeun Kim - * - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* MAX8997-MUIC STATUS1 register */ -#define STATUS1_ADC_SHIFT 0 -#define STATUS1_ADCLOW_SHIFT 5 -#define STATUS1_ADCERR_SHIFT 6 -#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) - -/* MAX8997-MUIC STATUS2 register */ -#define STATUS2_CHGTYP_SHIFT 0 -#define STATUS2_CHGDETRUN_SHIFT 3 -#define STATUS2_DCDTMR_SHIFT 4 -#define STATUS2_DBCHG_SHIFT 5 -#define STATUS2_VBVOLT_SHIFT 6 -#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) -#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) - -/* MAX8997-MUIC STATUS3 register */ -#define STATUS3_OVP_SHIFT 2 -#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) - -/* MAX8997-MUIC CONTROL1 register */ -#define COMN1SW_SHIFT 0 -#define COMP2SW_SHIFT 3 -#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) -#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define SW_MASK (COMP2SW_MASK | COMN1SW_MASK) - -#define MAX8997_SW_USB ((1 << COMP2SW_SHIFT) | (1 << COMN1SW_SHIFT)) -#define MAX8997_SW_AUDIO ((2 << COMP2SW_SHIFT) | (2 << COMN1SW_SHIFT)) -#define MAX8997_SW_UART ((3 << COMP2SW_SHIFT) | (3 << COMN1SW_SHIFT)) -#define MAX8997_SW_OPEN ((0 << COMP2SW_SHIFT) | (0 << COMN1SW_SHIFT)) - -#define MAX8997_ADC_GROUND 0x00 -#define MAX8997_ADC_MHL 0x01 -#define MAX8997_ADC_JIG_USB_1 0x18 -#define MAX8997_ADC_JIG_USB_2 0x19 -#define MAX8997_ADC_DESKDOCK 0x1a -#define MAX8997_ADC_JIG_UART 0x1c -#define MAX8997_ADC_CARDOCK 0x1d -#define MAX8997_ADC_OPEN 0x1f - -struct max8997_muic_irq { - unsigned int irq; - const char *name; -}; - -static struct max8997_muic_irq muic_irqs[] = { - { MAX8997_MUICIRQ_ADCError, "muic-ADC_error" }, - { MAX8997_MUICIRQ_ADCLow, "muic-ADC_low" }, - { MAX8997_MUICIRQ_ADC, "muic-ADC" }, - { MAX8997_MUICIRQ_VBVolt, "muic-VB_voltage" }, - { MAX8997_MUICIRQ_DBChg, "muic-DB_charger" }, - { MAX8997_MUICIRQ_DCDTmr, "muic-DCD_timer" }, - { MAX8997_MUICIRQ_ChgDetRun, "muic-CDR_status" }, - { MAX8997_MUICIRQ_ChgTyp, "muic-charger_type" }, - { MAX8997_MUICIRQ_OVP, "muic-over_voltage" }, -}; - -struct max8997_muic_info { - struct device *dev; - struct max8997_dev *iodev; - struct i2c_client *muic; - struct max8997_muic_platform_data *muic_pdata; - - int irq; - struct work_struct irq_work; - - enum max8997_muic_charger_type pre_charger_type; - int pre_adc; - - struct mutex mutex; -}; - -static int max8997_muic_handle_usb(struct max8997_muic_info *info, - enum max8997_muic_usb_type usb_type, bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int ret = 0; - - if (usb_type == MAX8997_USB_HOST) { - /* switch to USB */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_USB : MAX8997_SW_OPEN, - SW_MASK); - if (ret) { - dev_err(info->dev, "failed to update muic register\n"); - goto out; - } - } - - if (mdata->usb_callback) - mdata->usb_callback(usb_type, attached); -out: - return ret; -} - -static void max8997_muic_handle_mhl(struct max8997_muic_info *info, - bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - - if (mdata->mhl_callback) - mdata->mhl_callback(attached); -} - -static int max8997_muic_handle_dock(struct max8997_muic_info *info, - int adc, bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int ret = 0; - - /* switch to AUDIO */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_AUDIO : MAX8997_SW_OPEN, - SW_MASK); - if (ret) { - dev_err(info->dev, "failed to update muic register\n"); - goto out; - } - - switch (adc) { - case MAX8997_ADC_DESKDOCK: - if (mdata->deskdock_callback) - mdata->deskdock_callback(attached); - break; - case MAX8997_ADC_CARDOCK: - if (mdata->cardock_callback) - mdata->cardock_callback(attached); - break; - default: - break; - } -out: - return ret; -} - -static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, - bool attached) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int ret = 0; - - /* switch to UART */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_UART : MAX8997_SW_OPEN, - SW_MASK); - if (ret) { - dev_err(info->dev, "failed to update muic register\n"); - goto out; - } - - if (mdata->uart_callback) - mdata->uart_callback(attached); -out: - return ret; -} - -static int max8997_muic_handle_adc_detach(struct max8997_muic_info *info) -{ - int ret = 0; - - switch (info->pre_adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, false); - break; - case MAX8997_ADC_MHL: - max8997_muic_handle_mhl(info, false); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, false); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, info->pre_adc, false); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, false); - break; - default: - break; - } - - return ret; -} - -static int max8997_muic_handle_adc(struct max8997_muic_info *info, int adc) -{ - int ret = 0; - - switch (adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, true); - break; - case MAX8997_ADC_MHL: - max8997_muic_handle_mhl(info, true); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, true); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, adc, true); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, true); - break; - case MAX8997_ADC_OPEN: - ret = max8997_muic_handle_adc_detach(info); - break; - default: - break; - } - - info->pre_adc = adc; - - return ret; -} - -static int max8997_muic_handle_charger_type(struct max8997_muic_info *info, - enum max8997_muic_charger_type charger_type) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - u8 adc; - int ret; - - ret = max8997_read_reg(info->muic, MAX8997_MUIC_REG_STATUS1, &adc); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - goto out; - } - - switch (charger_type) { - case MAX8997_CHARGER_TYPE_NONE: - if (mdata->charger_callback) - mdata->charger_callback(false, charger_type); - if (info->pre_charger_type == MAX8997_CHARGER_TYPE_USB) { - max8997_muic_handle_usb(info, - MAX8997_USB_DEVICE, false); - } - break; - case MAX8997_CHARGER_TYPE_USB: - if ((adc & STATUS1_ADC_MASK) == MAX8997_ADC_OPEN) { - max8997_muic_handle_usb(info, - MAX8997_USB_DEVICE, true); - } - if (mdata->charger_callback) - mdata->charger_callback(true, charger_type); - break; - case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: - case MAX8997_CHARGER_TYPE_DEDICATED_CHG: - case MAX8997_CHARGER_TYPE_500MA: - case MAX8997_CHARGER_TYPE_1A: - if (mdata->charger_callback) - mdata->charger_callback(true, charger_type); - break; - default: - break; - } - - info->pre_charger_type = charger_type; -out: - return ret; -} - -static void max8997_muic_irq_work(struct work_struct *work) -{ - struct max8997_muic_info *info = container_of(work, - struct max8997_muic_info, irq_work); - struct max8997_platform_data *pdata = - dev_get_platdata(info->iodev->dev); - u8 status[3]; - u8 adc, chg_type; - - int irq_type = info->irq - pdata->irq_base; - int ret; - - mutex_lock(&info->mutex); - - ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 3, status); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - mutex_unlock(&info->mutex); - return; - } - - dev_dbg(info->dev, "%s: STATUS1:0x%x, 2:0x%x\n", __func__, - status[0], status[1]); - - switch (irq_type) { - case MAX8997_MUICIRQ_ADC: - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - max8997_muic_handle_adc(info, adc); - break; - case MAX8997_MUICIRQ_ChgTyp: - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - max8997_muic_handle_charger_type(info, chg_type); - break; - default: - dev_info(info->dev, "misc interrupt: %s occurred\n", - muic_irqs[irq_type].name); - break; - } - - mutex_unlock(&info->mutex); - - return; -} - -static irqreturn_t max8997_muic_irq_handler(int irq, void *data) -{ - struct max8997_muic_info *info = data; - - dev_dbg(info->dev, "irq:%d\n", irq); - info->irq = irq; - - schedule_work(&info->irq_work); - - return IRQ_HANDLED; -} - -static void max8997_muic_detect_dev(struct max8997_muic_info *info) -{ - int ret; - u8 status[2], adc, chg_type; - - ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 2, status); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - return; - } - - dev_info(info->dev, "STATUS1:0x%x, STATUS2:0x%x\n", - status[0], status[1]); - - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - max8997_muic_handle_adc(info, adc); - max8997_muic_handle_charger_type(info, chg_type); -} - -static void max8997_initialize_device(struct max8997_muic_info *info) -{ - struct max8997_muic_platform_data *mdata = info->muic_pdata; - int i; - - for (i = 0; i < mdata->num_init_data; i++) { - max8997_write_reg(info->muic, mdata->init_data[i].addr, - mdata->init_data[i].data); - } -} - -static int __devinit max8997_muic_probe(struct platform_device *pdev) -{ - struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); - struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); - struct max8997_muic_info *info; - int ret, i; - - info = kzalloc(sizeof(struct max8997_muic_info), GFP_KERNEL); - if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - ret = -ENOMEM; - goto err_kfree; - } - - if (!pdata->muic_pdata) { - dev_err(&pdev->dev, "failed to get platform_data\n"); - ret = -EINVAL; - goto err_pdata; - } - info->muic_pdata = pdata->muic_pdata; - - info->dev = &pdev->dev; - info->iodev = iodev; - info->muic = iodev->muic; - - platform_set_drvdata(pdev, info); - mutex_init(&info->mutex); - - INIT_WORK(&info->irq_work, max8997_muic_irq_work); - - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { - struct max8997_muic_irq *muic_irq = &muic_irqs[i]; - - ret = request_threaded_irq(pdata->irq_base + muic_irq->irq, - NULL, max8997_muic_irq_handler, - 0, muic_irq->name, - info); - if (ret) { - dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", - muic_irq->irq, ret); - - for (i = i - 1; i >= 0; i--) - free_irq(muic_irq->irq, info); - - goto err_irq; - } - } - - /* Initialize registers according to platform data */ - max8997_initialize_device(info); - - /* Initial device detection */ - max8997_muic_detect_dev(info); - - return ret; - -err_irq: -err_pdata: - kfree(info); -err_kfree: - return ret; -} - -static int __devexit max8997_muic_remove(struct platform_device *pdev) -{ - struct max8997_muic_info *info = platform_get_drvdata(pdev); - struct max8997_platform_data *pdata = - dev_get_platdata(info->iodev->dev); - int i; - - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) - free_irq(pdata->irq_base + muic_irqs[i].irq, info); - cancel_work_sync(&info->irq_work); - - kfree(info); - - return 0; -} - -static struct platform_driver max8997_muic_driver = { - .driver = { - .name = "max8997-muic", - .owner = THIS_MODULE, - }, - .probe = max8997_muic_probe, - .remove = __devexit_p(max8997_muic_remove), -}; - -module_platform_driver(max8997_muic_driver); - -MODULE_DESCRIPTION("Maxim MAX8997 MUIC driver"); -MODULE_AUTHOR("Donggeun Kim "); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 7f3a781d6fd81e397c3928c9af33f1fc63232db6 Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Wed, 9 May 2012 01:37:51 +0200 Subject: printk - fix compilation for CONFIG_PRINTK=n Reported-by: Randy Dunlap Signed-off-by: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/char/mem.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 0e7fbfcbd6e6..67c3371723cc 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -825,7 +825,9 @@ static const struct memdev { [7] = { "full", 0666, &full_fops, NULL }, [8] = { "random", 0666, &random_fops, NULL }, [9] = { "urandom", 0666, &urandom_fops, NULL }, +#ifdef CONFIG_PRINTK [11] = { "kmsg", 0644, &kmsg_fops, NULL }, +#endif #ifdef CONFIG_CRASH_DUMP [12] = { "oldmem", 0, &oldmem_fops, NULL }, #endif -- cgit v1.2.3 From c542fb79fba4c63aa6e2a27f90373b0516614eca Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Thu, 10 May 2012 10:42:30 +0300 Subject: ARM: tegra20: Add Tegra Memory Controller(MC) driver Tegra Memory Controller(MC) driver for Tegra20 Added to support MC General interrupts, mainly for IOMMU(GART). Signed-off-by: Hiroshi DOYU Acked-by: Stephen Warren Signed-off-by: Greg Kroah-Hartman --- drivers/memory/Kconfig | 4 + drivers/memory/Makefile | 1 + drivers/memory/tegra20-mc.c | 262 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 drivers/memory/tegra20-mc.c (limited to 'drivers') diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index e0b3156d65b5..ebade1647f9e 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -20,4 +20,8 @@ config TI_EMIF parameters and other settings during frequency, voltage and temperature changes +config TEGRA20_MC + bool + depends on ARCH_TEGRA_2x_SOC + endif diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index e27f80b28859..1f585184d88b 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_TI_EMIF) += emif.o +obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c new file mode 100644 index 000000000000..c0bfffacdc64 --- /dev/null +++ b/drivers/memory/tegra20-mc.c @@ -0,0 +1,262 @@ +/* + * Tegra20 Memory Controller + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "tegra20-mc" + +#define MC_INTSTATUS 0x0 +#define MC_INTMASK 0x4 + +#define MC_INT_ERR_SHIFT 6 +#define MC_INT_ERR_MASK (0x1f << MC_INT_ERR_SHIFT) +#define MC_INT_DECERR_EMEM BIT(MC_INT_ERR_SHIFT) +#define MC_INT_INVALID_GART_PAGE BIT(MC_INT_ERR_SHIFT + 1) +#define MC_INT_SECURITY_VIOLATION BIT(MC_INT_ERR_SHIFT + 2) +#define MC_INT_ARBITRATION_EMEM BIT(MC_INT_ERR_SHIFT + 3) + +#define MC_GART_ERROR_REQ 0x30 +#define MC_DECERR_EMEM_OTHERS_STATUS 0x58 +#define MC_SECURITY_VIOLATION_STATUS 0x74 + +#define SECURITY_VIOLATION_TYPE BIT(30) /* 0=TRUSTZONE, 1=CARVEOUT */ + +#define MC_CLIENT_ID_MASK 0x3f + +#define NUM_MC_REG_BANKS 2 + +struct tegra20_mc { + void __iomem *regs[NUM_MC_REG_BANKS]; + struct device *dev; +}; + +static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) +{ + if (offs < 0x24) + return readl(mc->regs[0] + offs); + BUG_ON(offs < 0x3c); + if (offs < 0x400) + return readl(mc->regs[1] + offs - 0x3c); + BUG(); +} + +static inline void mc_writel(struct tegra20_mc *mc, u32 val, u32 offs) +{ + if (offs < 0x24) { + writel(val, mc->regs[0] + offs); + return; + } + BUG_ON(offs < 0x3c); + if (offs < 0x400) { + writel(val, mc->regs[1] + offs - 0x3c); + return; + } + BUG(); +} + +static const char * const tegra20_mc_client[] = { + "cbr_display0a", + "cbr_display0ab", + "cbr_display0b", + "cbr_display0bb", + "cbr_display0c", + "cbr_display0cb", + "cbr_display1b", + "cbr_display1bb", + "cbr_eppup", + "cbr_g2pr", + "cbr_g2sr", + "cbr_mpeunifbr", + "cbr_viruv", + "csr_avpcarm7r", + "csr_displayhc", + "csr_displayhcb", + "csr_fdcdrd", + "csr_g2dr", + "csr_host1xdmar", + "csr_host1xr", + "csr_idxsrd", + "csr_mpcorer", + "csr_mpe_ipred", + "csr_mpeamemrd", + "csr_mpecsrd", + "csr_ppcsahbdmar", + "csr_ppcsahbslvr", + "csr_texsrd", + "csr_vdebsevr", + "csr_vdember", + "csr_vdemcer", + "csr_vdetper", + "cbw_eppu", + "cbw_eppv", + "cbw_eppy", + "cbw_mpeunifbw", + "cbw_viwsb", + "cbw_viwu", + "cbw_viwv", + "cbw_viwy", + "ccw_g2dw", + "csw_avpcarm7w", + "csw_fdcdwr", + "csw_host1xw", + "csw_ispw", + "csw_mpcorew", + "csw_mpecswr", + "csw_ppcsahbdmaw", + "csw_ppcsahbslvw", + "csw_vdebsevw", + "csw_vdembew", + "csw_vdetpmw", +}; + +static void tegra20_mc_decode(struct tegra20_mc *mc, int n) +{ + u32 addr, req; + const char *client = "Unknown"; + int idx, cid; + const struct reg_info { + u32 offset; + u32 write_bit; /* 0=READ, 1=WRITE */ + int cid_shift; + char *message; + } reg[] = { + { + .offset = MC_DECERR_EMEM_OTHERS_STATUS, + .write_bit = 31, + .message = "MC_DECERR", + }, + { + .offset = MC_GART_ERROR_REQ, + .cid_shift = 1, + .message = "MC_GART_ERR", + + }, + { + .offset = MC_SECURITY_VIOLATION_STATUS, + .write_bit = 31, + .message = "MC_SECURITY_ERR", + }, + }; + + idx = n - MC_INT_ERR_SHIFT; + if ((idx < 0) || (idx >= ARRAY_SIZE(reg))) { + pr_err_ratelimited("Unknown interrupt status %08lx\n", BIT(n)); + return; + } + + req = mc_readl(mc, reg[idx].offset); + cid = (req >> reg[idx].cid_shift) & MC_CLIENT_ID_MASK; + if (cid < ARRAY_SIZE(tegra20_mc_client)) + client = tegra20_mc_client[cid]; + + addr = mc_readl(mc, reg[idx].offset + sizeof(u32)); + + pr_err_ratelimited("%s (0x%08x): 0x%08x %s (%s %s)\n", + reg[idx].message, req, addr, client, + (req & BIT(reg[idx].write_bit)) ? "write" : "read", + (reg[idx].offset == MC_SECURITY_VIOLATION_STATUS) ? + ((req & SECURITY_VIOLATION_TYPE) ? + "carveout" : "trustzone") : ""); +} + +static const struct of_device_id tegra20_mc_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra20-mc", }, + {}, +}; + +static irqreturn_t tegra20_mc_isr(int irq, void *data) +{ + u32 stat, mask, bit; + struct tegra20_mc *mc = data; + + stat = mc_readl(mc, MC_INTSTATUS); + mask = mc_readl(mc, MC_INTMASK); + mask &= stat; + if (!mask) + return IRQ_NONE; + while ((bit = ffs(mask)) != 0) + tegra20_mc_decode(mc, bit - 1); + mc_writel(mc, stat, MC_INTSTATUS); + return IRQ_HANDLED; +} + +static int __devinit tegra20_mc_probe(struct platform_device *pdev) +{ + struct resource *irq; + struct tegra20_mc *mc; + int i, err; + u32 intmask; + + mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); + if (!mc) + return -ENOMEM; + mc->dev = &pdev->dev; + + for (i = 0; i < ARRAY_SIZE(mc->regs); i++) { + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) + return -ENODEV; + mc->regs[i] = devm_request_and_ioremap(&pdev->dev, res); + if (!mc->regs[i]) + return -EBUSY; + } + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) + return -ENODEV; + err = devm_request_irq(&pdev->dev, irq->start, tegra20_mc_isr, + IRQF_SHARED, dev_name(&pdev->dev), mc); + if (err) + return -ENODEV; + + platform_set_drvdata(pdev, mc); + + intmask = MC_INT_INVALID_GART_PAGE | + MC_INT_DECERR_EMEM | MC_INT_SECURITY_VIOLATION; + mc_writel(mc, intmask, MC_INTMASK); + return 0; +} + +static int __devexit tegra20_mc_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver tegra20_mc_driver = { + .probe = tegra20_mc_probe, + .remove = __devexit_p(tegra20_mc_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra20_mc_of_match, + }, +}; +module_platform_driver(tegra20_mc_driver); + +MODULE_AUTHOR("Hiroshi DOYU "); +MODULE_DESCRIPTION("Tegra20 MC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); -- cgit v1.2.3 From af4681097b23fe9c63a03d774de7c742fa3a920e Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Thu, 10 May 2012 10:42:32 +0300 Subject: ARM: tegra30: Add Tegra Memory Controller(MC) driver Tegra Memory Controller(MC) driver for Tegra30 Added to support MC General interrupts, mainly for IOMMU(SMMU). Signed-off-by: Hiroshi DOYU Acked-by: Stephen Warren Signed-off-by: Greg Kroah-Hartman --- drivers/memory/Kconfig | 4 + drivers/memory/Makefile | 1 + drivers/memory/tegra30-mc.c | 391 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+) create mode 100644 drivers/memory/tegra30-mc.c (limited to 'drivers') diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index ebade1647f9e..42e6d66513b2 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -24,4 +24,8 @@ config TEGRA20_MC bool depends on ARCH_TEGRA_2x_SOC +config TEGRA30_MC + bool + depends on ARCH_TEGRA_3x_SOC + endif diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 1f585184d88b..42b3ce9d80fc 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_TI_EMIF) += emif.o obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o +obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o diff --git a/drivers/memory/tegra30-mc.c b/drivers/memory/tegra30-mc.c new file mode 100644 index 000000000000..c9821258117a --- /dev/null +++ b/drivers/memory/tegra30-mc.c @@ -0,0 +1,391 @@ +/* + * Tegra30 Memory Controller + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "tegra30-mc" + +#define MC_INTSTATUS 0x0 +#define MC_INTMASK 0x4 + +#define MC_INT_ERR_SHIFT 6 +#define MC_INT_ERR_MASK (0x1f << MC_INT_ERR_SHIFT) +#define MC_INT_DECERR_EMEM BIT(MC_INT_ERR_SHIFT) +#define MC_INT_SECURITY_VIOLATION BIT(MC_INT_ERR_SHIFT + 2) +#define MC_INT_ARBITRATION_EMEM BIT(MC_INT_ERR_SHIFT + 3) +#define MC_INT_INVALID_SMMU_PAGE BIT(MC_INT_ERR_SHIFT + 4) + +#define MC_ERR_STATUS 0x8 +#define MC_ERR_ADR 0xc + +#define MC_ERR_TYPE_SHIFT 28 +#define MC_ERR_TYPE_MASK (7 << MC_ERR_TYPE_SHIFT) +#define MC_ERR_TYPE_DECERR_EMEM 2 +#define MC_ERR_TYPE_SECURITY_TRUSTZONE 3 +#define MC_ERR_TYPE_SECURITY_CARVEOUT 4 +#define MC_ERR_TYPE_INVALID_SMMU_PAGE 6 + +#define MC_ERR_INVALID_SMMU_PAGE_SHIFT 25 +#define MC_ERR_INVALID_SMMU_PAGE_MASK (7 << MC_ERR_INVALID_SMMU_PAGE_SHIFT) +#define MC_ERR_RW_SHIFT 16 +#define MC_ERR_RW BIT(MC_ERR_RW_SHIFT) +#define MC_ERR_SECURITY BIT(MC_ERR_RW_SHIFT + 1) + +#define SECURITY_VIOLATION_TYPE BIT(30) /* 0=TRUSTZONE, 1=CARVEOUT */ + +#define MC_EMEM_ARB_CFG 0x90 +#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94 +#define MC_EMEM_ARB_TIMING_RCD 0x98 +#define MC_EMEM_ARB_TIMING_RP 0x9c +#define MC_EMEM_ARB_TIMING_RC 0xa0 +#define MC_EMEM_ARB_TIMING_RAS 0xa4 +#define MC_EMEM_ARB_TIMING_FAW 0xa8 +#define MC_EMEM_ARB_TIMING_RRD 0xac +#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0 +#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4 +#define MC_EMEM_ARB_TIMING_R2R 0xb8 +#define MC_EMEM_ARB_TIMING_W2W 0xbc +#define MC_EMEM_ARB_TIMING_R2W 0xc0 +#define MC_EMEM_ARB_TIMING_W2R 0xc4 + +#define MC_EMEM_ARB_DA_TURNS 0xd0 +#define MC_EMEM_ARB_DA_COVERS 0xd4 +#define MC_EMEM_ARB_MISC0 0xd8 +#define MC_EMEM_ARB_MISC1 0xdc + +#define MC_EMEM_ARB_RING3_THROTTLE 0xe4 +#define MC_EMEM_ARB_OVERRIDE 0xe8 + +#define MC_TIMING_CONTROL 0xfc + +#define MC_CLIENT_ID_MASK 0x7f + +#define NUM_MC_REG_BANKS 4 + +struct tegra30_mc { + void __iomem *regs[NUM_MC_REG_BANKS]; + struct device *dev; + u32 ctx[0]; +}; + +static inline u32 mc_readl(struct tegra30_mc *mc, u32 offs) +{ + if (offs < 0x10) + return readl(mc->regs[0] + offs); + BUG_ON(offs < 0x3c); + if (offs < 0x1f0) + return readl(mc->regs[1] + offs - 0x3c); + BUG_ON(offs < 0x200); + if (offs < 0x228) + return readl(mc->regs[2] + offs - 0x200); + BUG_ON(offs < 0x284); + if (offs < 0x400) + return readl(mc->regs[3] + offs - 0x284); + BUG(); +} + +static inline void mc_writel(struct tegra30_mc *mc, u32 val, u32 offs) +{ + if (offs < 0x10) { + writel(val, mc->regs[0] + offs); + return; + } + BUG_ON(offs < 0x3c); + if (offs < 0x1f0) { + writel(val, mc->regs[1] + offs - 0x3c); + return; + } + BUG_ON(offs < 0x200); + if (offs < 0x228) { + writel(val, mc->regs[2] + offs - 0x200); + return; + } + BUG_ON(offs < 0x284); + if (offs < 0x400) { + writel(val, mc->regs[3] + offs - 0x284); + return; + } + BUG(); +} + +static const char * const tegra30_mc_client[] = { + "csr_ptcr", + "cbr_display0a", + "cbr_display0ab", + "cbr_display0b", + "cbr_display0bb", + "cbr_display0c", + "cbr_display0cb", + "cbr_display1b", + "cbr_display1bb", + "cbr_eppup", + "cbr_g2pr", + "cbr_g2sr", + "cbr_mpeunifbr", + "cbr_viruv", + "csr_afir", + "csr_avpcarm7r", + "csr_displayhc", + "csr_displayhcb", + "csr_fdcdrd", + "csr_fdcdrd2", + "csr_g2dr", + "csr_hdar", + "csr_host1xdmar", + "csr_host1xr", + "csr_idxsrd", + "csr_idxsrd2", + "csr_mpe_ipred", + "csr_mpeamemrd", + "csr_mpecsrd", + "csr_ppcsahbdmar", + "csr_ppcsahbslvr", + "csr_satar", + "csr_texsrd", + "csr_texsrd2", + "csr_vdebsevr", + "csr_vdember", + "csr_vdemcer", + "csr_vdetper", + "csr_mpcorelpr", + "csr_mpcorer", + "cbw_eppu", + "cbw_eppv", + "cbw_eppy", + "cbw_mpeunifbw", + "cbw_viwsb", + "cbw_viwu", + "cbw_viwv", + "cbw_viwy", + "ccw_g2dw", + "csw_afiw", + "csw_avpcarm7w", + "csw_fdcdwr", + "csw_fdcdwr2", + "csw_hdaw", + "csw_host1xw", + "csw_ispw", + "csw_mpcorelpw", + "csw_mpcorew", + "csw_mpecswr", + "csw_ppcsahbdmaw", + "csw_ppcsahbslvw", + "csw_sataw", + "csw_vdebsevw", + "csw_vdedbgw", + "csw_vdembew", + "csw_vdetpmw", +}; + +static void tegra30_mc_decode(struct tegra30_mc *mc, int n) +{ + u32 err, addr; + const char * const mc_int_err[] = { + "MC_DECERR", + "Unknown", + "MC_SECURITY_ERR", + "MC_ARBITRATION_EMEM", + "MC_SMMU_ERR", + }; + const char * const err_type[] = { + "Unknown", + "Unknown", + "DECERR_EMEM", + "SECURITY_TRUSTZONE", + "SECURITY_CARVEOUT", + "Unknown", + "INVALID_SMMU_PAGE", + "Unknown", + }; + char attr[6]; + int cid, perm, type, idx; + const char *client = "Unknown"; + + idx = n - MC_INT_ERR_SHIFT; + if ((idx < 0) || (idx >= ARRAY_SIZE(mc_int_err)) || (idx == 1)) { + pr_err_ratelimited("Unknown interrupt status %08lx\n", BIT(n)); + return; + } + + err = readl(mc + MC_ERR_STATUS); + + type = (err & MC_ERR_TYPE_MASK) >> MC_ERR_TYPE_SHIFT; + perm = (err & MC_ERR_INVALID_SMMU_PAGE_MASK) >> + MC_ERR_INVALID_SMMU_PAGE_SHIFT; + if (type == MC_ERR_TYPE_INVALID_SMMU_PAGE) + sprintf(attr, "%c-%c-%c", + (perm & BIT(2)) ? 'R' : '-', + (perm & BIT(1)) ? 'W' : '-', + (perm & BIT(0)) ? 'S' : '-'); + else + attr[0] = '\0'; + + cid = err & MC_CLIENT_ID_MASK; + if (cid < ARRAY_SIZE(tegra30_mc_client)) + client = tegra30_mc_client[cid]; + + addr = readl(mc + MC_ERR_ADR); + + pr_err_ratelimited("%s (0x%08x): 0x%08x %s (%s %s %s %s)\n", + mc_int_err[idx], err, addr, client, + (err & MC_ERR_SECURITY) ? "secure" : "non-secure", + (err & MC_ERR_RW) ? "write" : "read", + err_type[type], attr); +} + +static const u32 tegra30_mc_ctx[] = { + MC_EMEM_ARB_CFG, + MC_EMEM_ARB_OUTSTANDING_REQ, + MC_EMEM_ARB_TIMING_RCD, + MC_EMEM_ARB_TIMING_RP, + MC_EMEM_ARB_TIMING_RC, + MC_EMEM_ARB_TIMING_RAS, + MC_EMEM_ARB_TIMING_FAW, + MC_EMEM_ARB_TIMING_RRD, + MC_EMEM_ARB_TIMING_RAP2PRE, + MC_EMEM_ARB_TIMING_WAP2PRE, + MC_EMEM_ARB_TIMING_R2R, + MC_EMEM_ARB_TIMING_W2W, + MC_EMEM_ARB_TIMING_R2W, + MC_EMEM_ARB_TIMING_W2R, + MC_EMEM_ARB_DA_TURNS, + MC_EMEM_ARB_DA_COVERS, + MC_EMEM_ARB_MISC0, + MC_EMEM_ARB_MISC1, + MC_EMEM_ARB_RING3_THROTTLE, + MC_EMEM_ARB_OVERRIDE, + MC_INTMASK, +}; + +static int tegra30_mc_suspend(struct device *dev) +{ + int i; + struct tegra30_mc *mc = dev_get_drvdata(dev); + + for (i = 0; i < ARRAY_SIZE(tegra30_mc_ctx); i++) + mc->ctx[i] = mc_readl(mc, tegra30_mc_ctx[i]); + return 0; +} + +static int tegra30_mc_resume(struct device *dev) +{ + int i; + struct tegra30_mc *mc = dev_get_drvdata(dev); + + for (i = 0; i < ARRAY_SIZE(tegra30_mc_ctx); i++) + mc_writel(mc, mc->ctx[i], tegra30_mc_ctx[i]); + + mc_writel(mc, 1, MC_TIMING_CONTROL); + /* Read-back to ensure that write reached */ + mc_readl(mc, MC_TIMING_CONTROL); + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(tegra30_mc_pm, + tegra30_mc_suspend, + tegra30_mc_resume, NULL); + +static const struct of_device_id tegra30_mc_of_match[] __devinitconst = { + { .compatible = "nvidia,tegra30-mc", }, + {}, +}; + +static irqreturn_t tegra30_mc_isr(int irq, void *data) +{ + u32 stat, mask, bit; + struct tegra30_mc *mc = data; + + stat = mc_readl(mc, MC_INTSTATUS); + mask = mc_readl(mc, MC_INTMASK); + mask &= stat; + if (!mask) + return IRQ_NONE; + while ((bit = ffs(mask)) != 0) + tegra30_mc_decode(mc, bit - 1); + mc_writel(mc, stat, MC_INTSTATUS); + return IRQ_HANDLED; +} + +static int __devinit tegra30_mc_probe(struct platform_device *pdev) +{ + struct resource *irq; + struct tegra30_mc *mc; + size_t bytes; + int err, i; + u32 intmask; + + bytes = sizeof(*mc) + sizeof(u32) * ARRAY_SIZE(tegra30_mc_ctx); + mc = devm_kzalloc(&pdev->dev, bytes, GFP_KERNEL); + if (!mc) + return -ENOMEM; + mc->dev = &pdev->dev; + + for (i = 0; i < ARRAY_SIZE(mc->regs); i++) { + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) + return -ENODEV; + mc->regs[i] = devm_request_and_ioremap(&pdev->dev, res); + if (!mc->regs[i]) + return -EBUSY; + } + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) + return -ENODEV; + err = devm_request_irq(&pdev->dev, irq->start, tegra30_mc_isr, + IRQF_SHARED, dev_name(&pdev->dev), mc); + if (err) + return -ENODEV; + + platform_set_drvdata(pdev, mc); + + intmask = MC_INT_INVALID_SMMU_PAGE | + MC_INT_DECERR_EMEM | MC_INT_SECURITY_VIOLATION; + mc_writel(mc, intmask, MC_INTMASK); + return 0; +} + +static int __devexit tegra30_mc_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver tegra30_mc_driver = { + .probe = tegra30_mc_probe, + .remove = __devexit_p(tegra30_mc_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra30_mc_of_match, + .pm = &tegra30_mc_pm, + }, +}; +module_platform_driver(tegra30_mc_driver); + +MODULE_AUTHOR("Hiroshi DOYU "); +MODULE_DESCRIPTION("Tegra30 MC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); -- cgit v1.2.3 From 0c733a427c430b54a6fa574fbc6e96baf7b9202f Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Fri, 11 May 2012 13:04:44 +0300 Subject: ARM: tegra20: MC: Remove unnecessary BUG*() Accessing interleaved MC register offsets/ranges are verified. BUG*()s in accessors can be removed. Signed-off-by: Hiroshi DOYU Signed-off-by: Greg Kroah-Hartman --- drivers/memory/tegra20-mc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c index c0bfffacdc64..2a437e92756c 100644 --- a/drivers/memory/tegra20-mc.c +++ b/drivers/memory/tegra20-mc.c @@ -53,12 +53,14 @@ struct tegra20_mc { static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) { + u32 val = 0; + if (offs < 0x24) - return readl(mc->regs[0] + offs); - BUG_ON(offs < 0x3c); + val = readl(mc->regs[0] + offs); if (offs < 0x400) - return readl(mc->regs[1] + offs - 0x3c); - BUG(); + val = readl(mc->regs[1] + offs - 0x3c); + + return val; } static inline void mc_writel(struct tegra20_mc *mc, u32 val, u32 offs) @@ -67,12 +69,10 @@ static inline void mc_writel(struct tegra20_mc *mc, u32 val, u32 offs) writel(val, mc->regs[0] + offs); return; } - BUG_ON(offs < 0x3c); if (offs < 0x400) { writel(val, mc->regs[1] + offs - 0x3c); return; } - BUG(); } static const char * const tegra20_mc_client[] = { -- cgit v1.2.3 From b37fd4154e758c7226fda663e2ea3a97c6e8c732 Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Fri, 11 May 2012 13:04:45 +0300 Subject: ARM: tegra30: MC: Remove unnecessary BUG*() Accessing interleaved MC register offsets/ranges are verified. BUG*()s in accessors can be removed. Signed-off-by: Hiroshi DOYU Signed-off-by: Greg Kroah-Hartman --- drivers/memory/tegra30-mc.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/memory/tegra30-mc.c b/drivers/memory/tegra30-mc.c index c9821258117a..ec9fc7894801 100644 --- a/drivers/memory/tegra30-mc.c +++ b/drivers/memory/tegra30-mc.c @@ -91,18 +91,18 @@ struct tegra30_mc { static inline u32 mc_readl(struct tegra30_mc *mc, u32 offs) { + u32 val = 0; + if (offs < 0x10) - return readl(mc->regs[0] + offs); - BUG_ON(offs < 0x3c); + val = readl(mc->regs[0] + offs); if (offs < 0x1f0) - return readl(mc->regs[1] + offs - 0x3c); - BUG_ON(offs < 0x200); + val = readl(mc->regs[1] + offs - 0x3c); if (offs < 0x228) - return readl(mc->regs[2] + offs - 0x200); - BUG_ON(offs < 0x284); + val = readl(mc->regs[2] + offs - 0x200); if (offs < 0x400) - return readl(mc->regs[3] + offs - 0x284); - BUG(); + val = readl(mc->regs[3] + offs - 0x284); + + return val; } static inline void mc_writel(struct tegra30_mc *mc, u32 val, u32 offs) @@ -111,22 +111,18 @@ static inline void mc_writel(struct tegra30_mc *mc, u32 val, u32 offs) writel(val, mc->regs[0] + offs); return; } - BUG_ON(offs < 0x3c); if (offs < 0x1f0) { writel(val, mc->regs[1] + offs - 0x3c); return; } - BUG_ON(offs < 0x200); if (offs < 0x228) { writel(val, mc->regs[2] + offs - 0x200); return; } - BUG_ON(offs < 0x284); if (offs < 0x400) { writel(val, mc->regs[3] + offs - 0x284); return; } - BUG(); } static const char * const tegra30_mc_client[] = { -- cgit v1.2.3 From f0e33f9805625d60dc81f34740f16a6db67e8427 Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Fri, 11 May 2012 09:56:24 +0300 Subject: ARM: tegra20: Make MC optional in Kconfig For bare minimal system. Signed-off-by: Hiroshi DOYU Acked-by: Stephen Warren Signed-off-by: Greg Kroah-Hartman --- drivers/memory/Kconfig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 42e6d66513b2..efc6b36f28c8 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -21,8 +21,14 @@ config TI_EMIF temperature changes config TEGRA20_MC - bool + bool "Tegra20 Memory Controller(MC) driver" + default y depends on ARCH_TEGRA_2x_SOC + help + This driver is for the Memory Controller(MC) module available + in Tegra20 SoCs, mainly for a address translation fault + analysis, especially for IOMMU/GART(Graphics Address + Relocation Table) module. config TEGRA30_MC bool -- cgit v1.2.3 From 42d1149f75a3d78033f66853796f20340d7b3ee2 Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Fri, 11 May 2012 09:56:25 +0300 Subject: ARM: tegra30: Make MC optional in Kconfig For bare minimal system. Signed-off-by: Hiroshi DOYU Acked-by: Stephen Warren Signed-off-by: Greg Kroah-Hartman --- drivers/memory/Kconfig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index efc6b36f28c8..067f31174a0e 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -31,7 +31,13 @@ config TEGRA20_MC Relocation Table) module. config TEGRA30_MC - bool + bool "Tegra30 Memory Controller(MC) driver" + default y depends on ARCH_TEGRA_3x_SOC + help + This driver is for the Memory Controller(MC) module available + in Tegra30 SoCs, mainly for a address translation fault + analysis, especially for IOMMU/SMMU(System Memory Management + Unit) module. endif -- cgit v1.2.3 From 094e47e9fa79e28f0e51e37400ea6eea35a4ee1f Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Fri, 11 May 2012 11:03:09 +0300 Subject: Driver Core: don't oops with unregistered driver in driver_find_device() driver_find_device() can be called with an unregistered driver. Need to check driver_private to see if it's populated or not, especially under deferrable probe. In the case that there are 2 drivers, one depends on the other. With -EPROBE_DEFER, two drivers can use deferred probe to ensure that their relative probe order doesn't matter. If dependee driver is probed first, then the dependant's driver_find_device('dependee') succeeds. If the dependant is probed first, then the dependant's driver_find_device('dependee') should return NULL, and the dependant should get -EPROBE_DEFER. driver_find_device() needs to return NULL if it's not populated. In [PATCHv5 2/3] ARM: tegra: Add SMMU enabler in AHB: http://article.gmane.org/gmane.linux.ports.tegra/4658 "tegra_ahb_driver" may not be populated when it's called. For more SMMU/AHB specific discussion, refer to the following thread: https://lkml.org/lkml/2012/5/10/21 Signed-off-by: Hiroshi DOYU Cc: Stephen Warren Signed-off-by: Greg Kroah-Hartman --- drivers/base/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 3ec3896c83a6..207c27ddf828 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -80,7 +80,7 @@ struct device *driver_find_device(struct device_driver *drv, struct klist_iter i; struct device *dev; - if (!drv) + if (!drv || !drv->p) return NULL; klist_iter_init_node(&drv->p->klist_devices, &i, -- cgit v1.2.3 From 90394482b807cb0a474fb387ed020603df14cfd0 Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Mon, 14 May 2012 13:07:03 +0300 Subject: memory: tegra{20,30}-mc: Use dev_err_ratelimited() Introduce a new dev_*_ratelimited() instead of pr_*_ratelimited() for better info to print. Signed-off-by: Hiroshi DOYU Signed-off-by: Greg Kroah-Hartman --- drivers/memory/tegra20-mc.c | 5 +++-- drivers/memory/tegra30-mc.c | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c index 2a437e92756c..a38d9d3de231 100644 --- a/drivers/memory/tegra20-mc.c +++ b/drivers/memory/tegra20-mc.c @@ -161,7 +161,8 @@ static void tegra20_mc_decode(struct tegra20_mc *mc, int n) idx = n - MC_INT_ERR_SHIFT; if ((idx < 0) || (idx >= ARRAY_SIZE(reg))) { - pr_err_ratelimited("Unknown interrupt status %08lx\n", BIT(n)); + dev_err_ratelimited(mc->dev, "Unknown interrupt status %08lx\n", + BIT(n)); return; } @@ -172,7 +173,7 @@ static void tegra20_mc_decode(struct tegra20_mc *mc, int n) addr = mc_readl(mc, reg[idx].offset + sizeof(u32)); - pr_err_ratelimited("%s (0x%08x): 0x%08x %s (%s %s)\n", + dev_err_ratelimited(mc->dev, "%s (0x%08x): 0x%08x %s (%s %s)\n", reg[idx].message, req, addr, client, (req & BIT(reg[idx].write_bit)) ? "write" : "read", (reg[idx].offset == MC_SECURITY_VIOLATION_STATUS) ? diff --git a/drivers/memory/tegra30-mc.c b/drivers/memory/tegra30-mc.c index ec9fc7894801..cb639b1cd5e9 100644 --- a/drivers/memory/tegra30-mc.c +++ b/drivers/memory/tegra30-mc.c @@ -220,7 +220,8 @@ static void tegra30_mc_decode(struct tegra30_mc *mc, int n) idx = n - MC_INT_ERR_SHIFT; if ((idx < 0) || (idx >= ARRAY_SIZE(mc_int_err)) || (idx == 1)) { - pr_err_ratelimited("Unknown interrupt status %08lx\n", BIT(n)); + dev_err_ratelimited(mc->dev, "Unknown interrupt status %08lx\n", + BIT(n)); return; } @@ -243,7 +244,7 @@ static void tegra30_mc_decode(struct tegra30_mc *mc, int n) addr = readl(mc + MC_ERR_ADR); - pr_err_ratelimited("%s (0x%08x): 0x%08x %s (%s %s %s %s)\n", + dev_err_ratelimited(mc->dev, "%s (0x%08x): 0x%08x %s (%s %s %s %s)\n", mc_int_err[idx], err, addr, client, (err & MC_ERR_SECURITY) ? "secure" : "non-secure", (err & MC_ERR_RW) ? "write" : "read", -- cgit v1.2.3 From a360530012766e5fd752bd6538c8cc6349846781 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 12 May 2012 13:44:57 -0700 Subject: Drivers: hv: Get rid of an unnecessary check in vmbus_prep_negotiate_resp() The vmbus_prep_negotiate_resp() is only invoked when we are negotiating the version; so the current check in vmbus_prep_negotiate_resp() is unnecessary. Get rid of it. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 9ffbfc575a0c..6c8c4d340930 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -56,30 +56,29 @@ struct vmbus_channel_message_table_entry { void vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, struct icmsg_negotiate *negop, u8 *buf) { - if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - icmsghdrp->icmsgsize = 0x10; - - negop = (struct icmsg_negotiate *)&buf[ - sizeof(struct vmbuspipe_hdr) + - sizeof(struct icmsg_hdr)]; - - if (negop->icframe_vercnt == 2 && - negop->icversion_data[1].major == 3) { - negop->icversion_data[0].major = 3; - negop->icversion_data[0].minor = 0; - negop->icversion_data[1].major = 3; - negop->icversion_data[1].minor = 0; - } else { - negop->icversion_data[0].major = 1; - negop->icversion_data[0].minor = 0; - negop->icversion_data[1].major = 1; - negop->icversion_data[1].minor = 0; - } - - negop->icframe_vercnt = 1; - negop->icmsg_vercnt = 1; + icmsghdrp->icmsgsize = 0x10; + + negop = (struct icmsg_negotiate *)&buf[ + sizeof(struct vmbuspipe_hdr) + + sizeof(struct icmsg_hdr)]; + + if (negop->icframe_vercnt == 2 && + negop->icversion_data[1].major == 3) { + negop->icversion_data[0].major = 3; + negop->icversion_data[0].minor = 0; + negop->icversion_data[1].major = 3; + negop->icversion_data[1].minor = 0; + } else { + negop->icversion_data[0].major = 1; + negop->icversion_data[0].minor = 0; + negop->icversion_data[1].major = 1; + negop->icversion_data[1].minor = 0; } + + negop->icframe_vercnt = 1; + negop->icmsg_vercnt = 1; } + EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); /* -- cgit v1.2.3 From c836d0ab70acf7b7bd2b698278e8abae9e6d9978 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 12 May 2012 13:44:58 -0700 Subject: Drivers: hv: util: Properly handle version negotiations. The current version negotiation code is not "future proof". Fix this by allowing each service the flexibility to either specify the highest version it can support or it can support the highest version number the host is offering. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 54 +++++++++++++++++++++++++++++++++-------------- drivers/hv/hv_kvp.c | 3 ++- drivers/hv/hv_util.c | 9 +++++--- 3 files changed, 46 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 6c8c4d340930..2b8b8d4558d2 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -46,37 +46,59 @@ struct vmbus_channel_message_table_entry { * * @icmsghdrp is of type &struct icmsg_hdr. * @negop is of type &struct icmsg_negotiate. - * Set up and fill in default negotiate response message. This response can - * come from both the vmbus driver and the hv_utils driver. The current api - * will respond properly to both Windows 2008 and Windows 2008-R2 operating - * systems. + * Set up and fill in default negotiate response message. + * + * The max_fw_version specifies the maximum framework version that + * we can support and max _srv_version specifies the maximum service + * version we can support. A special value MAX_SRV_VER can be + * specified to indicate that we can handle the maximum version + * exposed by the host. * * Mainly used by Hyper-V drivers. */ void vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, - struct icmsg_negotiate *negop, u8 *buf) + struct icmsg_negotiate *negop, u8 *buf, + int max_fw_version, int max_srv_version) { + int icframe_vercnt; + int icmsg_vercnt; + int i; + icmsghdrp->icmsgsize = 0x10; negop = (struct icmsg_negotiate *)&buf[ sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr)]; - if (negop->icframe_vercnt == 2 && - negop->icversion_data[1].major == 3) { - negop->icversion_data[0].major = 3; - negop->icversion_data[0].minor = 0; - negop->icversion_data[1].major = 3; - negop->icversion_data[1].minor = 0; - } else { - negop->icversion_data[0].major = 1; - negop->icversion_data[0].minor = 0; - negop->icversion_data[1].major = 1; - negop->icversion_data[1].minor = 0; + icframe_vercnt = negop->icframe_vercnt; + icmsg_vercnt = negop->icmsg_vercnt; + + /* + * Select the framework version number we will + * support. + */ + + for (i = 0; i < negop->icframe_vercnt; i++) { + if (negop->icversion_data[i].major <= max_fw_version) + icframe_vercnt = negop->icversion_data[i].major; + } + + for (i = negop->icframe_vercnt; + (i < negop->icframe_vercnt + negop->icmsg_vercnt); i++) { + if (negop->icversion_data[i].major <= max_srv_version) + icmsg_vercnt = negop->icversion_data[i].major; } + /* + * Respond with the maximum framework and service + * version numbers we can support. + */ negop->icframe_vercnt = 1; negop->icmsg_vercnt = 1; + negop->icversion_data[0].major = icframe_vercnt; + negop->icversion_data[0].minor = 0; + negop->icversion_data[1].major = icmsg_vercnt; + negop->icversion_data[1].minor = 0; } EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index 6186025209ce..0012eed6d872 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -394,7 +394,8 @@ void hv_kvp_onchannelcallback(void *context) sizeof(struct vmbuspipe_hdr)]; if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - vmbus_prep_negotiate_resp(icmsghdrp, negop, recv_buffer); + vmbus_prep_negotiate_resp(icmsghdrp, negop, + recv_buffer, MAX_SRV_VER, MAX_SRV_VER); } else { kvp_msg = (struct hv_kvp_msg *)&recv_buffer[ sizeof(struct vmbuspipe_hdr) + diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index dbb8b8eec210..d3ac6a40118b 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -70,7 +70,8 @@ static void shutdown_onchannelcallback(void *context) sizeof(struct vmbuspipe_hdr)]; if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - vmbus_prep_negotiate_resp(icmsghdrp, negop, shut_txf_buf); + vmbus_prep_negotiate_resp(icmsghdrp, negop, + shut_txf_buf, MAX_SRV_VER, MAX_SRV_VER); } else { shutdown_msg = (struct shutdown_msg_data *)&shut_txf_buf[ @@ -195,7 +196,8 @@ static void timesync_onchannelcallback(void *context) sizeof(struct vmbuspipe_hdr)]; if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - vmbus_prep_negotiate_resp(icmsghdrp, NULL, time_txf_buf); + vmbus_prep_negotiate_resp(icmsghdrp, NULL, time_txf_buf, + MAX_SRV_VER, MAX_SRV_VER); } else { timedatap = (struct ictimesync_data *)&time_txf_buf[ sizeof(struct vmbuspipe_hdr) + @@ -234,7 +236,8 @@ static void heartbeat_onchannelcallback(void *context) sizeof(struct vmbuspipe_hdr)]; if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) { - vmbus_prep_negotiate_resp(icmsghdrp, NULL, hbeat_txf_buf); + vmbus_prep_negotiate_resp(icmsghdrp, NULL, + hbeat_txf_buf, MAX_SRV_VER, MAX_SRV_VER); } else { heartbeat_msg = (struct heartbeat_msg_data *)&hbeat_txf_buf[ -- cgit v1.2.3 From 356c05d58af05d582e634b54b40050c73609617b Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 14 May 2012 13:30:03 -0400 Subject: sysfs: get rid of some lockdep false positives This patch (as1554) fixes a lockdep false-positive report. The problem arises because lockdep is unable to deal with the tree-structured locks created by the device core and sysfs. This particular problem involves a sysfs attribute method that unregisters itself, not from the device it was called for, but from a descendant device. Lockdep doesn't understand the distinction and reports a possible deadlock, even though the operation is safe. This is the sort of thing that would normally be handled by using a nested lock annotation; unfortunately it's not feasible to do that here. There's no sensible way to tell sysfs when attribute removal occurs in the context of a parent attribute method. As a workaround, the patch adds a new flag to struct attribute telling sysfs not to inform lockdep when it acquires a readlock on a sysfs_dirent instance for the attribute. The readlock is still acquired, but lockdep doesn't know about it and hence does not complain about impossible deadlock scenarios. Also added are macros for static initialization of attribute structures with the ignore_lockdep flag set. The three offending attributes in the USB subsystem are converted to use the new macros. Signed-off-by: Alan Stern Acked-by: Tejun Heo CC: Eric W. Biederman CC: Peter Zijlstra Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/sysfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 566d9f94f735..9a56e3adf476 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -73,7 +73,7 @@ set_bConfigurationValue(struct device *dev, struct device_attribute *attr, return (value < 0) ? value : count; } -static DEVICE_ATTR(bConfigurationValue, S_IRUGO | S_IWUSR, +static DEVICE_ATTR_IGNORE_LOCKDEP(bConfigurationValue, S_IRUGO | S_IWUSR, show_bConfigurationValue, set_bConfigurationValue); /* String fields */ @@ -595,7 +595,7 @@ static ssize_t usb_dev_authorized_store(struct device *dev, return result < 0? result : size; } -static DEVICE_ATTR(authorized, 0644, +static DEVICE_ATTR_IGNORE_LOCKDEP(authorized, 0644, usb_dev_authorized_show, usb_dev_authorized_store); /* "Safely remove a device" */ @@ -618,7 +618,7 @@ static ssize_t usb_remove_store(struct device *dev, usb_unlock_device(udev); return rc; } -static DEVICE_ATTR(remove, 0200, NULL, usb_remove_store); +static DEVICE_ATTR_IGNORE_LOCKDEP(remove, 0200, NULL, usb_remove_store); static struct attribute *dev_attrs[] = { -- cgit v1.2.3 From 7458eab6f4527bce719703eb15fef1cff9e8d9e0 Mon Sep 17 00:00:00 2001 From: Hiroshi DOYU Date: Mon, 14 May 2012 19:20:03 +0300 Subject: memory: tegra{20,30}-mc: Remove empty *_remove() Remove unnecessary empty functions. Signed-off-by: Hiroshi DOYU Signed-off-by: Greg Kroah-Hartman --- drivers/memory/tegra20-mc.c | 6 ------ drivers/memory/tegra30-mc.c | 6 ------ 2 files changed, 12 deletions(-) (limited to 'drivers') diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c index a38d9d3de231..3ed49c1c2b91 100644 --- a/drivers/memory/tegra20-mc.c +++ b/drivers/memory/tegra20-mc.c @@ -241,14 +241,8 @@ static int __devinit tegra20_mc_probe(struct platform_device *pdev) return 0; } -static int __devexit tegra20_mc_remove(struct platform_device *pdev) -{ - return 0; -} - static struct platform_driver tegra20_mc_driver = { .probe = tegra20_mc_probe, - .remove = __devexit_p(tegra20_mc_remove), .driver = { .name = DRV_NAME, .owner = THIS_MODULE, diff --git a/drivers/memory/tegra30-mc.c b/drivers/memory/tegra30-mc.c index cb639b1cd5e9..e56ff04eb5cc 100644 --- a/drivers/memory/tegra30-mc.c +++ b/drivers/memory/tegra30-mc.c @@ -365,14 +365,8 @@ static int __devinit tegra30_mc_probe(struct platform_device *pdev) return 0; } -static int __devexit tegra30_mc_remove(struct platform_device *pdev) -{ - return 0; -} - static struct platform_driver tegra30_mc_driver = { .probe = tegra30_mc_probe, - .remove = __devexit_p(tegra30_mc_remove), .driver = { .name = DRV_NAME, .owner = THIS_MODULE, -- cgit v1.2.3 From 94ca629e40eb7e997be21d8065c25e4f3797b03f Mon Sep 17 00:00:00 2001 From: Benedikt Spranger Date: Mon, 14 May 2012 18:48:17 +0200 Subject: uio_pdrv_genirq: get irq through platform resource if not set otherwise Platform devices are configured through platform resources. The interrupt in the driver uio_pdrv_genirq is instead configured through a side channel i.e. the platform data structure. Make it possible to use the generic configuration scheme via platform resource. Signed-off-by: Benedikt Spranger Acked-by: Magnus Damm Signed-off-by: "Hans J. Koch" Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_pdrv_genirq.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index b98371d93a92..42202cd83158 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -146,6 +146,14 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) priv->flags = 0; /* interrupt is enabled to begin with */ priv->pdev = pdev; + if (!uioinfo->irq) { + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + goto bad0; + } + uioinfo->irq = ret; + } uiomem = &uioinfo->mem[0]; for (i = 0; i < pdev->num_resources; ++i) { -- cgit v1.2.3