diff options
Diffstat (limited to 'drivers')
238 files changed, 15056 insertions, 1899 deletions
diff --git a/drivers/bcma/host_pci.c b/drivers/bcma/host_pci.c index ffd8797faf4..471a04013fe 100644 --- a/drivers/bcma/host_pci.c +++ b/drivers/bcma/host_pci.c @@ -6,6 +6,7 @@ */ #include "bcma_private.h" +#include <linux/slab.h> #include <linux/bcma/bcma.h> #include <linux/pci.h> diff --git a/drivers/block/brd.c b/drivers/block/brd.c index b7f51e4594f..dba1c32e1dd 100644 --- a/drivers/block/brd.c +++ b/drivers/block/brd.c @@ -35,10 +35,6 @@ */ struct brd_device { int brd_number; - int brd_refcnt; - loff_t brd_offset; - loff_t brd_sizelimit; - unsigned brd_blocksize; struct request_queue *brd_queue; struct gendisk *brd_disk; @@ -440,11 +436,11 @@ static int rd_nr; int rd_size = CONFIG_BLK_DEV_RAM_SIZE; static int max_part; static int part_shift; -module_param(rd_nr, int, 0); +module_param(rd_nr, int, S_IRUGO); MODULE_PARM_DESC(rd_nr, "Maximum number of brd devices"); -module_param(rd_size, int, 0); +module_param(rd_size, int, S_IRUGO); MODULE_PARM_DESC(rd_size, "Size of each RAM disk in kbytes."); -module_param(max_part, int, 0); +module_param(max_part, int, S_IRUGO); MODULE_PARM_DESC(max_part, "Maximum number of partitions per RAM disk"); MODULE_LICENSE("GPL"); MODULE_ALIAS_BLOCKDEV_MAJOR(RAMDISK_MAJOR); @@ -552,7 +548,7 @@ static struct kobject *brd_probe(dev_t dev, int *part, void *data) struct kobject *kobj; mutex_lock(&brd_devices_mutex); - brd = brd_init_one(dev & MINORMASK); + brd = brd_init_one(MINOR(dev) >> part_shift); kobj = brd ? get_disk(brd->brd_disk) : ERR_PTR(-ENOMEM); mutex_unlock(&brd_devices_mutex); @@ -575,25 +571,39 @@ static int __init brd_init(void) * * (1) if rd_nr is specified, create that many upfront, and this * also becomes a hard limit. - * (2) if rd_nr is not specified, create 1 rd device on module - * load, user can further extend brd device by create dev node - * themselves and have kernel automatically instantiate actual - * device on-demand. + * (2) if rd_nr is not specified, create CONFIG_BLK_DEV_RAM_COUNT + * (default 16) rd device on module load, user can further + * extend brd device by create dev node themselves and have + * kernel automatically instantiate actual device on-demand. */ part_shift = 0; - if (max_part > 0) + if (max_part > 0) { part_shift = fls(max_part); + /* + * Adjust max_part according to part_shift as it is exported + * to user space so that user can decide correct minor number + * if [s]he want to create more devices. + * + * Note that -1 is required because partition 0 is reserved + * for the whole disk. + */ + max_part = (1UL << part_shift) - 1; + } + + if ((1UL << part_shift) > DISK_MAX_PARTS) + return -EINVAL; + if (rd_nr > 1UL << (MINORBITS - part_shift)) return -EINVAL; if (rd_nr) { nr = rd_nr; - range = rd_nr; + range = rd_nr << part_shift; } else { nr = CONFIG_BLK_DEV_RAM_COUNT; - range = 1UL << (MINORBITS - part_shift); + range = 1UL << MINORBITS; } if (register_blkdev(RAMDISK_MAJOR, "ramdisk")) @@ -632,7 +642,7 @@ static void __exit brd_exit(void) unsigned long range; struct brd_device *brd, *next; - range = rd_nr ? rd_nr : 1UL << (MINORBITS - part_shift); + range = rd_nr ? rd_nr << part_shift : 1UL << MINORBITS; list_for_each_entry_safe(brd, next, &brd_devices, brd_list) brd_del_one(brd); diff --git a/drivers/block/loop.c b/drivers/block/loop.c index c59a672a3de..76c8da78212 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -1540,9 +1540,9 @@ static const struct block_device_operations lo_fops = { * And now the modules code and kernel interface. */ static int max_loop; -module_param(max_loop, int, 0); +module_param(max_loop, int, S_IRUGO); MODULE_PARM_DESC(max_loop, "Maximum number of loop devices"); -module_param(max_part, int, 0); +module_param(max_part, int, S_IRUGO); MODULE_PARM_DESC(max_part, "Maximum number of partitions per loop device"); MODULE_LICENSE("GPL"); MODULE_ALIAS_BLOCKDEV_MAJOR(LOOP_MAJOR); @@ -1688,9 +1688,20 @@ static int __init loop_init(void) */ part_shift = 0; - if (max_part > 0) + if (max_part > 0) { part_shift = fls(max_part); + /* + * Adjust max_part according to part_shift as it is exported + * to user space so that user can decide correct minor number + * if [s]he want to create more devices. + * + * Note that -1 is required because partition 0 is reserved + * for the whole disk. + */ + max_part = (1UL << part_shift) - 1; + } + if ((1UL << part_shift) > DISK_MAX_PARTS) return -EINVAL; diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index 38223e93aa9..58c0e6387cf 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -36,6 +36,7 @@ #include <asm/system.h> #include <linux/poll.h> #include <linux/sched.h> +#include <linux/seq_file.h> #include <linux/spinlock.h> #include <linux/mutex.h> #include <linux/slab.h> @@ -1896,102 +1897,128 @@ int ipmi_request_supply_msgs(ipmi_user_t user, EXPORT_SYMBOL(ipmi_request_supply_msgs); #ifdef CONFIG_PROC_FS -static int ipmb_file_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int smi_ipmb_proc_show(struct seq_file *m, void *v) { - char *out = (char *) page; - ipmi_smi_t intf = data; + ipmi_smi_t intf = m->private; int i; - int rv = 0; - for (i = 0; i < IPMI_MAX_CHANNELS; i++) - rv += sprintf(out+rv, "%x ", intf->channels[i].address); - out[rv-1] = '\n'; /* Replace the final space with a newline */ - out[rv] = '\0'; - rv++; - return rv; + seq_printf(m, "%x", intf->channels[0].address); + for (i = 1; i < IPMI_MAX_CHANNELS; i++) + seq_printf(m, " %x", intf->channels[i].address); + return seq_putc(m, '\n'); } -static int version_file_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int smi_ipmb_proc_open(struct inode *inode, struct file *file) { - char *out = (char *) page; - ipmi_smi_t intf = data; + return single_open(file, smi_ipmb_proc_show, PDE(inode)->data); +} - return sprintf(out, "%u.%u\n", +static const struct file_operations smi_ipmb_proc_ops = { + .open = smi_ipmb_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int smi_version_proc_show(struct seq_file *m, void *v) +{ + ipmi_smi_t intf = m->private; + + return seq_printf(m, "%u.%u\n", ipmi_version_major(&intf->bmc->id), ipmi_version_minor(&intf->bmc->id)); } -static int stat_file_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int smi_version_proc_open(struct inode *inode, struct file *file) { - char *out = (char *) page; - ipmi_smi_t intf = data; + return single_open(file, smi_version_proc_show, PDE(inode)->data); +} + +static const struct file_operations smi_version_proc_ops = { + .open = smi_version_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; - out += sprintf(out, "sent_invalid_commands: %u\n", +static int smi_stats_proc_show(struct seq_file *m, void *v) +{ + ipmi_smi_t intf = m->private; + + seq_printf(m, "sent_invalid_commands: %u\n", ipmi_get_stat(intf, sent_invalid_commands)); - out += sprintf(out, "sent_local_commands: %u\n", + seq_printf(m, "sent_local_commands: %u\n", ipmi_get_stat(intf, sent_local_commands)); - out += sprintf(out, "handled_local_responses: %u\n", + seq_printf(m, "handled_local_responses: %u\n", ipmi_get_stat(intf, handled_local_responses)); - out += sprintf(out, "unhandled_local_responses: %u\n", + seq_printf(m, "unhandled_local_responses: %u\n", ipmi_get_stat(intf, unhandled_local_responses)); - out += sprintf(out, "sent_ipmb_commands: %u\n", + seq_printf(m, "sent_ipmb_commands: %u\n", ipmi_get_stat(intf, sent_ipmb_commands)); - out += sprintf(out, "sent_ipmb_command_errs: %u\n", + seq_printf(m, "sent_ipmb_command_errs: %u\n", ipmi_get_stat(intf, sent_ipmb_command_errs)); - out += sprintf(out, "retransmitted_ipmb_commands: %u\n", + seq_printf(m, "retransmitted_ipmb_commands: %u\n", ipmi_get_stat(intf, retransmitted_ipmb_commands)); - out += sprintf(out, "timed_out_ipmb_commands: %u\n", + seq_printf(m, "timed_out_ipmb_commands: %u\n", ipmi_get_stat(intf, timed_out_ipmb_commands)); - out += sprintf(out, "timed_out_ipmb_broadcasts: %u\n", + seq_printf(m, "timed_out_ipmb_broadcasts: %u\n", ipmi_get_stat(intf, timed_out_ipmb_broadcasts)); - out += sprintf(out, "sent_ipmb_responses: %u\n", + seq_printf(m, "sent_ipmb_responses: %u\n", ipmi_get_stat(intf, sent_ipmb_responses)); - out += sprintf(out, "handled_ipmb_responses: %u\n", + seq_printf(m, "handled_ipmb_responses: %u\n", ipmi_get_stat(intf, handled_ipmb_responses)); - out += sprintf(out, "invalid_ipmb_responses: %u\n", + seq_printf(m, "invalid_ipmb_responses: %u\n", ipmi_get_stat(intf, invalid_ipmb_responses)); - out += sprintf(out, "unhandled_ipmb_responses: %u\n", + seq_printf(m, "unhandled_ipmb_responses: %u\n", ipmi_get_stat(intf, unhandled_ipmb_responses)); - out += sprintf(out, "sent_lan_commands: %u\n", + seq_printf(m, "sent_lan_commands: %u\n", ipmi_get_stat(intf, sent_lan_commands)); - out += sprintf(out, "sent_lan_command_errs: %u\n", + seq_printf(m, "sent_lan_command_errs: %u\n", ipmi_get_stat(intf, sent_lan_command_errs)); - out += sprintf(out, "retransmitted_lan_commands: %u\n", + seq_printf(m, "retransmitted_lan_commands: %u\n", ipmi_get_stat(intf, retransmitted_lan_commands)); - out += sprintf(out, "timed_out_lan_commands: %u\n", + seq_printf(m, "timed_out_lan_commands: %u\n", ipmi_get_stat(intf, timed_out_lan_commands)); - out += sprintf(out, "sent_lan_responses: %u\n", + seq_printf(m, "sent_lan_responses: %u\n", ipmi_get_stat(intf, sent_lan_responses)); - out += sprintf(out, "handled_lan_responses: %u\n", + seq_printf(m, "handled_lan_responses: %u\n", ipmi_get_stat(intf, handled_lan_responses)); - out += sprintf(out, "invalid_lan_responses: %u\n", + seq_printf(m, "invalid_lan_responses: %u\n", ipmi_get_stat(intf, invalid_lan_responses)); - out += sprintf(out, "unhandled_lan_responses: %u\n", + seq_printf(m, "unhandled_lan_responses: %u\n", ipmi_get_stat(intf, unhandled_lan_responses)); - out += sprintf(out, "handled_commands: %u\n", + seq_printf(m, "handled_commands: %u\n", ipmi_get_stat(intf, handled_commands)); - out += sprintf(out, "invalid_commands: %u\n", + seq_printf(m, "invalid_commands: %u\n", ipmi_get_stat(intf, invalid_commands)); - out += sprintf(out, "unhandled_commands: %u\n", + seq_printf(m, "unhandled_commands: %u\n", ipmi_get_stat(intf, unhandled_commands)); - out += sprintf(out, "invalid_events: %u\n", + seq_printf(m, "invalid_events: %u\n", ipmi_get_stat(intf, invalid_events)); - out += sprintf(out, "events: %u\n", + seq_printf(m, "events: %u\n", ipmi_get_stat(intf, events)); - out += sprintf(out, "failed rexmit LAN msgs: %u\n", + seq_printf(m, "failed rexmit LAN msgs: %u\n", ipmi_get_stat(intf, dropped_rexmit_lan_commands)); - out += sprintf(out, "failed rexmit IPMB msgs: %u\n", + seq_printf(m, "failed rexmit IPMB msgs: %u\n", ipmi_get_stat(intf, dropped_rexmit_ipmb_commands)); + return 0; +} - return (out - ((char *) page)); +static int smi_stats_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, smi_stats_proc_show, PDE(inode)->data); } + +static const struct file_operations smi_stats_proc_ops = { + .open = smi_stats_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; #endif /* CONFIG_PROC_FS */ int ipmi_smi_add_proc_entry(ipmi_smi_t smi, char *name, - read_proc_t *read_proc, + const struct file_operations *proc_ops, void *data) { int rv = 0; @@ -2010,15 +2037,12 @@ int ipmi_smi_add_proc_entry(ipmi_smi_t smi, char *name, } strcpy(entry->name, name); - file = create_proc_entry(name, 0, smi->proc_dir); + file = proc_create_data(name, 0, smi->proc_dir, proc_ops, data); if (!file) { kfree(entry->name); kfree(entry); rv = -ENOMEM; } else { - file->data = data; - file->read_proc = read_proc; - mutex_lock(&smi->proc_entry_lock); /* Stick it on the list. */ entry->next = smi->proc_entries; @@ -2043,17 +2067,17 @@ static int add_proc_entries(ipmi_smi_t smi, int num) if (rv == 0) rv = ipmi_smi_add_proc_entry(smi, "stats", - stat_file_read_proc, + &smi_stats_proc_ops, smi); if (rv == 0) rv = ipmi_smi_add_proc_entry(smi, "ipmb", - ipmb_file_read_proc, + &smi_ipmb_proc_ops, smi); if (rv == 0) rv = ipmi_smi_add_proc_entry(smi, "version", - version_file_read_proc, + &smi_version_proc_ops, smi); #endif /* CONFIG_PROC_FS */ diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 64c6b853061..9397ab49b72 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -43,6 +43,7 @@ #include <linux/moduleparam.h> #include <asm/system.h> #include <linux/sched.h> +#include <linux/seq_file.h> #include <linux/timer.h> #include <linux/errno.h> #include <linux/spinlock.h> @@ -2805,54 +2806,73 @@ static int try_enable_event_buffer(struct smi_info *smi_info) return rv; } -static int type_file_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int smi_type_proc_show(struct seq_file *m, void *v) { - struct smi_info *smi = data; + struct smi_info *smi = m->private; - return sprintf(page, "%s\n", si_to_str[smi->si_type]); + return seq_printf(m, "%s\n", si_to_str[smi->si_type]); } -static int stat_file_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int smi_type_proc_open(struct inode *inode, struct file *file) { - char *out = (char *) page; - struct smi_info *smi = data; + return single_open(file, smi_type_proc_show, PDE(inode)->data); +} + +static const struct file_operations smi_type_proc_ops = { + .open = smi_type_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int smi_si_stats_proc_show(struct seq_file *m, void *v) +{ + struct smi_info *smi = m->private; - out += sprintf(out, "interrupts_enabled: %d\n", + seq_printf(m, "interrupts_enabled: %d\n", smi->irq && !smi->interrupt_disabled); - out += sprintf(out, "short_timeouts: %u\n", + seq_printf(m, "short_timeouts: %u\n", smi_get_stat(smi, short_timeouts)); - out += sprintf(out, "long_timeouts: %u\n", + seq_printf(m, "long_timeouts: %u\n", smi_get_stat(smi, long_timeouts)); - out += sprintf(out, "idles: %u\n", + seq_printf(m, "idles: %u\n", smi_get_stat(smi, idles)); - out += sprintf(out, "interrupts: %u\n", + seq_printf(m, "interrupts: %u\n", smi_get_stat(smi, interrupts)); - out += sprintf(out, "attentions: %u\n", + seq_printf(m, "attentions: %u\n", smi_get_stat(smi, attentions)); - out += sprintf(out, "flag_fetches: %u\n", + seq_printf(m, "flag_fetches: %u\n", smi_get_stat(smi, flag_fetches)); - out += sprintf(out, "hosed_count: %u\n", + seq_printf(m, "hosed_count: %u\n", smi_get_stat(smi, hosed_count)); - out += sprintf(out, "complete_transactions: %u\n", + seq_printf(m, "complete_transactions: %u\n", smi_get_stat(smi, complete_transactions)); - out += sprintf(out, "events: %u\n", + seq_printf(m, "events: %u\n", smi_get_stat(smi, events)); - out += sprintf(out, "watchdog_pretimeouts: %u\n", + seq_printf(m, "watchdog_pretimeouts: %u\n", smi_get_stat(smi, watchdog_pretimeouts)); - out += sprintf(out, "incoming_messages: %u\n", + seq_printf(m, "incoming_messages: %u\n", smi_get_stat(smi, incoming_messages)); + return 0; +} - return out - page; +static int smi_si_stats_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, smi_si_stats_proc_show, PDE(inode)->data); } -static int param_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static const struct file_operations smi_si_stats_proc_ops = { + .open = smi_si_stats_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int smi_params_proc_show(struct seq_file *m, void *v) { - struct smi_info *smi = data; + struct smi_info *smi = m->private; - return sprintf(page, + return seq_printf(m, "%s,%s,0x%lx,rsp=%d,rsi=%d,rsh=%d,irq=%d,ipmb=%d\n", si_to_str[smi->si_type], addr_space_to_str[smi->io.addr_type], @@ -2864,6 +2884,18 @@ static int param_read_proc(char *page, char **start, off_t off, smi->slave_addr); } +static int smi_params_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, smi_params_proc_show, PDE(inode)->data); +} + +static const struct file_operations smi_params_proc_ops = { + .open = smi_params_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + /* * oem_data_avail_to_receive_msg_avail * @info - smi_info structure with msg_flags set @@ -3257,7 +3289,7 @@ static int try_smi_init(struct smi_info *new_smi) } rv = ipmi_smi_add_proc_entry(new_smi->intf, "type", - type_file_read_proc, + &smi_type_proc_ops, new_smi); if (rv) { dev_err(new_smi->dev, "Unable to create proc entry: %d\n", rv); @@ -3265,7 +3297,7 @@ static int try_smi_init(struct smi_info *new_smi) } rv = ipmi_smi_add_proc_entry(new_smi->intf, "si_stats", - stat_file_read_proc, + &smi_si_stats_proc_ops, new_smi); if (rv) { dev_err(new_smi->dev, "Unable to create proc entry: %d\n", rv); @@ -3273,7 +3305,7 @@ static int try_smi_init(struct smi_info *new_smi) } rv = ipmi_smi_add_proc_entry(new_smi->intf, "params", - param_read_proc, + &smi_params_proc_ops, new_smi); if (rv) { dev_err(new_smi->dev, "Unable to create proc entry: %d\n", rv); diff --git a/drivers/char/mspec.c b/drivers/char/mspec.c index 966a95bc974..25d139c9dbe 100644 --- a/drivers/char/mspec.c +++ b/drivers/char/mspec.c @@ -271,14 +271,13 @@ mspec_mmap(struct file *file, struct vm_area_struct *vma, pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; vdata_size = sizeof(struct vma_data) + pages * sizeof(long); if (vdata_size <= PAGE_SIZE) - vdata = kmalloc(vdata_size, GFP_KERNEL); + vdata = kzalloc(vdata_size, GFP_KERNEL); else { - vdata = vmalloc(vdata_size); + vdata = vzalloc(vdata_size); flags = VMD_VMALLOCED; } if (!vdata) return -ENOMEM; - memset(vdata, 0, vdata_size); vdata->vm_start = vma->vm_start; vdata->vm_end = vma->vm_end; diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c index f176dbaeb15..3fcf80ff12f 100644 --- a/drivers/char/ppdev.c +++ b/drivers/char/ppdev.c @@ -457,6 +457,7 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return -ENODEV; modes = port->modes; + parport_put_port(port); if (copy_to_user (argp, &modes, sizeof (modes))) { return -EFAULT; } diff --git a/drivers/dma/timb_dma.c b/drivers/dma/timb_dma.c index d2c75feff7d..f69f90a6187 100644 --- a/drivers/dma/timb_dma.c +++ b/drivers/dma/timb_dma.c @@ -27,7 +27,6 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/slab.h> #include <linux/timb_dma.h> @@ -685,7 +684,7 @@ static irqreturn_t td_irq(int irq, void *devid) static int __devinit td_probe(struct platform_device *pdev) { - struct timb_dma_platform_data *pdata = mfd_get_data(pdev); + struct timb_dma_platform_data *pdata = pdev->dev.platform_data; struct timb_dma *td; struct resource *iomem; int irq; diff --git a/drivers/edac/amd76x_edac.c b/drivers/edac/amd76x_edac.c index cace0a7b707..e47e73bbbcc 100644 --- a/drivers/edac/amd76x_edac.c +++ b/drivers/edac/amd76x_edac.c @@ -19,7 +19,7 @@ #include <linux/edac.h> #include "edac_core.h" -#define AMD76X_REVISION " Ver: 2.0.2 " __DATE__ +#define AMD76X_REVISION " Ver: 2.0.2" #define EDAC_MOD_STR "amd76x_edac" #define amd76x_printk(level, fmt, arg...) \ diff --git a/drivers/edac/amd8111_edac.c b/drivers/edac/amd8111_edac.c index 35b78d04bbf..ddd890052ce 100644 --- a/drivers/edac/amd8111_edac.c +++ b/drivers/edac/amd8111_edac.c @@ -33,7 +33,7 @@ #include "edac_module.h" #include "amd8111_edac.h" -#define AMD8111_EDAC_REVISION " Ver: 1.0.0 " __DATE__ +#define AMD8111_EDAC_REVISION " Ver: 1.0.0" #define AMD8111_EDAC_MOD_STR "amd8111_edac" #define PCI_DEVICE_ID_AMD_8111_PCI 0x7460 diff --git a/drivers/edac/amd8131_edac.c b/drivers/edac/amd8131_edac.c index b432d60c622..a5c680561c7 100644 --- a/drivers/edac/amd8131_edac.c +++ b/drivers/edac/amd8131_edac.c @@ -33,7 +33,7 @@ #include "edac_module.h" #include "amd8131_edac.h" -#define AMD8131_EDAC_REVISION " Ver: 1.0.0 " __DATE__ +#define AMD8131_EDAC_REVISION " Ver: 1.0.0" #define AMD8131_EDAC_MOD_STR "amd8131_edac" /* Wrapper functions for accessing PCI configuration space */ diff --git a/drivers/edac/cpc925_edac.c b/drivers/edac/cpc925_edac.c index 837ad8f85b4..a687a0d1696 100644 --- a/drivers/edac/cpc925_edac.c +++ b/drivers/edac/cpc925_edac.c @@ -30,7 +30,7 @@ #include "edac_core.h" #include "edac_module.h" -#define CPC925_EDAC_REVISION " Ver: 1.0.0 " __DATE__ +#define CPC925_EDAC_REVISION " Ver: 1.0.0" #define CPC925_EDAC_MOD_STR "cpc925_edac" #define cpc925_printk(level, fmt, arg...) \ diff --git a/drivers/edac/e752x_edac.c b/drivers/edac/e752x_edac.c index ec302d42658..1af531a11d2 100644 --- a/drivers/edac/e752x_edac.c +++ b/drivers/edac/e752x_edac.c @@ -24,7 +24,7 @@ #include <linux/edac.h> #include "edac_core.h" -#define E752X_REVISION " Ver: 2.0.2 " __DATE__ +#define E752X_REVISION " Ver: 2.0.2" #define EDAC_MOD_STR "e752x_edac" static int report_non_memory_errors; diff --git a/drivers/edac/e7xxx_edac.c b/drivers/edac/e7xxx_edac.c index 1731d724581..6ffb6d23281 100644 --- a/drivers/edac/e7xxx_edac.c +++ b/drivers/edac/e7xxx_edac.c @@ -29,7 +29,7 @@ #include <linux/edac.h> #include "edac_core.h" -#define E7XXX_REVISION " Ver: 2.0.2 " __DATE__ +#define E7XXX_REVISION " Ver: 2.0.2" #define EDAC_MOD_STR "e7xxx_edac" #define e7xxx_printk(level, fmt, arg...) \ diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h index eefa3501916..55b8278bb17 100644 --- a/drivers/edac/edac_core.h +++ b/drivers/edac/edac_core.h @@ -421,10 +421,6 @@ struct mem_ctl_info { u32 ce_count; /* Total Correctable Errors for this MC */ unsigned long start_time; /* mci load start time (in jiffies) */ - /* this stuff is for safe removal of mc devices from global list while - * NMI handlers may be traversing list - */ - struct rcu_head rcu; struct completion complete; /* edac sysfs device control */ @@ -620,10 +616,6 @@ struct edac_device_ctl_info { unsigned long start_time; /* edac_device load start time (jiffies) */ - /* these are for safe removal of mc devices from global list while - * NMI handlers may be traversing list - */ - struct rcu_head rcu; struct completion removal_complete; /* sysfs top name under 'edac' directory @@ -722,10 +714,6 @@ struct edac_pci_ctl_info { unsigned long start_time; /* edac_pci load start time (jiffies) */ - /* these are for safe removal of devices from global list while - * NMI handlers may be traversing list - */ - struct rcu_head rcu; struct completion complete; /* sysfs top name under 'edac' directory diff --git a/drivers/edac/edac_device.c b/drivers/edac/edac_device.c index a7408cf86f3..c3f67437afb 100644 --- a/drivers/edac/edac_device.c +++ b/drivers/edac/edac_device.c @@ -346,30 +346,18 @@ fail1: } /* - * complete_edac_device_list_del - * - * callback function when reference count is zero - */ -static void complete_edac_device_list_del(struct rcu_head *head) -{ - struct edac_device_ctl_info *edac_dev; - - edac_dev = container_of(head, struct edac_device_ctl_info, rcu); - INIT_LIST_HEAD(&edac_dev->link); -} - -/* * del_edac_device_from_global_list - * - * remove the RCU, setup for a callback call, - * then wait for the callback to occur */ static void del_edac_device_from_global_list(struct edac_device_ctl_info *edac_device) { list_del_rcu(&edac_device->link); - call_rcu(&edac_device->rcu, complete_edac_device_list_del); - rcu_barrier(); + + /* these are for safe removal of devices from global list while + * NMI handlers may be traversing list + */ + synchronize_rcu(); + INIT_LIST_HEAD(&edac_device->link); } /* diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 1d805604907..d69144a0904 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -447,20 +447,16 @@ fail1: return 1; } -static void complete_mc_list_del(struct rcu_head *head) -{ - struct mem_ctl_info *mci; - - mci = container_of(head, struct mem_ctl_info, rcu); - INIT_LIST_HEAD(&mci->link); -} - static void del_mc_from_global_list(struct mem_ctl_info *mci) { atomic_dec(&edac_handlers); list_del_rcu(&mci->link); - call_rcu(&mci->rcu, complete_mc_list_del); - rcu_barrier(); + + /* these are for safe removal of devices from global list while + * NMI handlers may be traversing list + */ + synchronize_rcu(); + INIT_LIST_HEAD(&mci->link); } /** diff --git a/drivers/edac/edac_module.c b/drivers/edac/edac_module.c index be4b075c309..5ddaa86d6a6 100644 --- a/drivers/edac/edac_module.c +++ b/drivers/edac/edac_module.c @@ -15,7 +15,7 @@ #include "edac_core.h" #include "edac_module.h" -#define EDAC_VERSION "Ver: 2.1.0 " __DATE__ +#define EDAC_VERSION "Ver: 2.1.0" #ifdef CONFIG_EDAC_DEBUG /* Values of 0 to 4 will generate output */ diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c index efb5d565078..2b378207d57 100644 --- a/drivers/edac/edac_pci.c +++ b/drivers/edac/edac_pci.c @@ -164,19 +164,6 @@ fail1: } /* - * complete_edac_pci_list_del - * - * RCU completion callback to indicate item is deleted - */ -static void complete_edac_pci_list_del(struct rcu_head *head) -{ - struct edac_pci_ctl_info *pci; - - pci = container_of(head, struct edac_pci_ctl_info, rcu); - INIT_LIST_HEAD(&pci->link); -} - -/* * del_edac_pci_from_global_list * * remove the PCI control struct from the global list @@ -184,8 +171,12 @@ static void complete_edac_pci_list_del(struct rcu_head *head) static void del_edac_pci_from_global_list(struct edac_pci_ctl_info *pci) { list_del_rcu(&pci->link); - call_rcu(&pci->rcu, complete_edac_pci_list_del); - rcu_barrier(); + + /* these are for safe removal of devices from global list while + * NMI handlers may be traversing list + */ + synchronize_rcu(); + INIT_LIST_HEAD(&pci->link); } #if 0 diff --git a/drivers/edac/i5000_edac.c b/drivers/edac/i5000_edac.c index 87f427c2ce5..4dc3ac25a42 100644 --- a/drivers/edac/i5000_edac.c +++ b/drivers/edac/i5000_edac.c @@ -27,7 +27,7 @@ /* * Alter this version for the I5000 module when modifications are made */ -#define I5000_REVISION " Ver: 2.0.12 " __DATE__ +#define I5000_REVISION " Ver: 2.0.12" #define EDAC_MOD_STR "i5000_edac" #define i5000_printk(level, fmt, arg...) \ diff --git a/drivers/edac/i5400_edac.c b/drivers/edac/i5400_edac.c index 80a465efbae..74d6ec342af 100644 --- a/drivers/edac/i5400_edac.c +++ b/drivers/edac/i5400_edac.c @@ -33,7 +33,7 @@ /* * Alter this version for the I5400 module when modifications are made */ -#define I5400_REVISION " Ver: 1.0.0 " __DATE__ +#define I5400_REVISION " Ver: 1.0.0" #define EDAC_MOD_STR "i5400_edac" diff --git a/drivers/edac/i7300_edac.c b/drivers/edac/i7300_edac.c index 363cc160294..a76fe8366b6 100644 --- a/drivers/edac/i7300_edac.c +++ b/drivers/edac/i7300_edac.c @@ -31,7 +31,7 @@ /* * Alter this version for the I7300 module when modifications are made */ -#define I7300_REVISION " Ver: 1.0.0 " __DATE__ +#define I7300_REVISION " Ver: 1.0.0" #define EDAC_MOD_STR "i7300_edac" diff --git a/drivers/edac/i7core_edac.c b/drivers/edac/i7core_edac.c index 465cbc25149..04f1e7ce02b 100644 --- a/drivers/edac/i7core_edac.c +++ b/drivers/edac/i7core_edac.c @@ -59,7 +59,7 @@ MODULE_PARM_DESC(use_pci_fixup, "Enable PCI fixup to seek for hidden devices"); /* * Alter this version for the module when modifications are made */ -#define I7CORE_REVISION " Ver: 1.0.0 " __DATE__ +#define I7CORE_REVISION " Ver: 1.0.0" #define EDAC_MOD_STR "i7core_edac" /* diff --git a/drivers/edac/i82860_edac.c b/drivers/edac/i82860_edac.c index b8a95cf5071..931a0577504 100644 --- a/drivers/edac/i82860_edac.c +++ b/drivers/edac/i82860_edac.c @@ -16,7 +16,7 @@ #include <linux/edac.h> #include "edac_core.h" -#define I82860_REVISION " Ver: 2.0.2 " __DATE__ +#define I82860_REVISION " Ver: 2.0.2" #define EDAC_MOD_STR "i82860_edac" #define i82860_printk(level, fmt, arg...) \ diff --git a/drivers/edac/i82875p_edac.c b/drivers/edac/i82875p_edac.c index b2fd1e89914..33864c63c68 100644 --- a/drivers/edac/i82875p_edac.c +++ b/drivers/edac/i82875p_edac.c @@ -20,7 +20,7 @@ #include <linux/edac.h> #include "edac_core.h" -#define I82875P_REVISION " Ver: 2.0.2 " __DATE__ +#define I82875P_REVISION " Ver: 2.0.2" #define EDAC_MOD_STR "i82875p_edac" #define i82875p_printk(level, fmt, arg...) \ diff --git a/drivers/edac/i82975x_edac.c b/drivers/edac/i82975x_edac.c index 92e65e7038e..a5da732fe5b 100644 --- a/drivers/edac/i82975x_edac.c +++ b/drivers/edac/i82975x_edac.c @@ -16,7 +16,7 @@ #include <linux/edac.h> #include "edac_core.h" -#define I82975X_REVISION " Ver: 1.0.0 " __DATE__ +#define I82975X_REVISION " Ver: 1.0.0" #define EDAC_MOD_STR "i82975x_edac" #define i82975x_printk(level, fmt, arg...) \ diff --git a/drivers/edac/mpc85xx_edac.h b/drivers/edac/mpc85xx_edac.h index cb24df83946..932016f2cf0 100644 --- a/drivers/edac/mpc85xx_edac.h +++ b/drivers/edac/mpc85xx_edac.h @@ -11,7 +11,7 @@ #ifndef _MPC85XX_EDAC_H_ #define _MPC85XX_EDAC_H_ -#define MPC85XX_REVISION " Ver: 2.0.0 " __DATE__ +#define MPC85XX_REVISION " Ver: 2.0.0" #define EDAC_MOD_STR "MPC85xx_edac" #define mpc85xx_printk(level, fmt, arg...) \ diff --git a/drivers/edac/mv64x60_edac.h b/drivers/edac/mv64x60_edac.h index e042e2daa8f..c7f209c92a1 100644 --- a/drivers/edac/mv64x60_edac.h +++ b/drivers/edac/mv64x60_edac.h @@ -12,7 +12,7 @@ #ifndef _MV64X60_EDAC_H_ #define _MV64X60_EDAC_H_ -#define MV64x60_REVISION " Ver: 2.0.0 " __DATE__ +#define MV64x60_REVISION " Ver: 2.0.0" #define EDAC_MOD_STR "MV64x60_edac" #define mv64x60_printk(level, fmt, arg...) \ diff --git a/drivers/edac/ppc4xx_edac.c b/drivers/edac/ppc4xx_edac.c index af8e7b1aa29..0de7d877089 100644 --- a/drivers/edac/ppc4xx_edac.c +++ b/drivers/edac/ppc4xx_edac.c @@ -113,7 +113,7 @@ #define EDAC_OPSTATE_UNKNOWN_STR "unknown" #define PPC4XX_EDAC_MODULE_NAME "ppc4xx_edac" -#define PPC4XX_EDAC_MODULE_REVISION "v1.0.0 " __DATE__ +#define PPC4XX_EDAC_MODULE_REVISION "v1.0.0" #define PPC4XX_EDAC_MESSAGE_SIZE 256 diff --git a/drivers/edac/r82600_edac.c b/drivers/edac/r82600_edac.c index 678513738c3..b153674431f 100644 --- a/drivers/edac/r82600_edac.c +++ b/drivers/edac/r82600_edac.c @@ -22,7 +22,7 @@ #include <linux/edac.h> #include "edac_core.h" -#define R82600_REVISION " Ver: 2.0.2 " __DATE__ +#define R82600_REVISION " Ver: 2.0.2" #define EDAC_MOD_STR "r82600_edac" #define r82600_printk(level, fmt, arg...) \ diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d3b29530554..d2136460375 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1,5 +1,5 @@ # -# platform-neutral GPIO infrastructure and expanders +# GPIO infrastructure and drivers # config ARCH_WANT_OPTIONAL_GPIOLIB @@ -31,7 +31,7 @@ menuconfig GPIOLIB help This enables GPIO support through the generic GPIO library. You only need to enable this, if you also want to enable - one or more of the GPIO expansion card drivers below. + one or more of the GPIO drivers below. If unsure, say N. @@ -63,21 +63,26 @@ config GPIO_SYSFS Kernel drivers may also request that a particular GPIO be exported to userspace; this can be useful when debugging. -# put expanders in the right section, in alphabetical order +# put drivers in the right section, in alphabetical order config GPIO_MAX730X tristate -comment "Memory mapped GPIO expanders:" +comment "Memory mapped GPIO drivers:" + +config GPIO_BASIC_MMIO_CORE + tristate + help + Provides core functionality for basic memory-mapped GPIO controllers. config GPIO_BASIC_MMIO tristate "Basic memory-mapped GPIO controllers support" + select GPIO_BASIC_MMIO_CORE help Say yes here to support basic memory-mapped GPIO controllers. config GPIO_IT8761E tristate "IT8761E GPIO support" - depends on GPIOLIB help Say yes here to support GPIO functionality of IT8761E super I/O chip. @@ -101,7 +106,7 @@ config GPIO_VR41XX config GPIO_SCH tristate "Intel SCH/TunnelCreek GPIO" - depends on GPIOLIB && PCI && X86 + depends on PCI && X86 select MFD_CORE select LPC_SCH help @@ -121,7 +126,7 @@ config GPIO_SCH config GPIO_VX855 tristate "VIA VX855/VX875 GPIO" - depends on GPIOLIB && MFD_SUPPORT && PCI + depends on MFD_SUPPORT && PCI select MFD_CORE select MFD_VX855 help @@ -347,13 +352,13 @@ config GPIO_ML_IOH config GPIO_TIMBERDALE bool "Support for timberdale GPIO IP" - depends on MFD_TIMBERDALE && GPIOLIB && HAS_IOMEM + depends on MFD_TIMBERDALE && HAS_IOMEM ---help--- Add support for the GPIO IP in the timberdale FPGA. config GPIO_RDC321X tristate "RDC R-321x GPIO support" - depends on PCI && GPIOLIB + depends on PCI select MFD_SUPPORT select MFD_CORE select MFD_RDC321X @@ -419,4 +424,11 @@ config AB8500_GPIO depends on AB8500_CORE && BROKEN help Select this to enable the AB8500 IC GPIO driver + +config GPIO_TPS65910 + bool "TPS65910 GPIO" + depends on MFD_TPS65910 + help + Select this option to enable GPIO driver for the TPS65910 + chip family. endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index becef595435..6a3387acc0e 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -1,8 +1,4 @@ -# generic gpio support: dedicated expander chips, etc -# -# NOTE: platform-specific GPIO drivers don't belong in the -# drivers/gpio directory; put them with other platform setup -# code, IRQ controllers, board init, etc. +# generic gpio support: platform drivers, dedicated expander chips, etc ccflags-$(CONFIG_DEBUG_GPIO) += -DDEBUG @@ -10,6 +6,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib.o obj-$(CONFIG_GPIO_ADP5520) += adp5520-gpio.o obj-$(CONFIG_GPIO_ADP5588) += adp5588-gpio.o +obj-$(CONFIG_GPIO_BASIC_MMIO_CORE) += basic_mmio_gpio.o obj-$(CONFIG_GPIO_BASIC_MMIO) += basic_mmio_gpio.o obj-$(CONFIG_GPIO_LANGWELL) += langwell_gpio.o obj-$(CONFIG_GPIO_MAX730X) += max730x.o @@ -43,3 +40,4 @@ obj-$(CONFIG_GPIO_SX150X) += sx150x.o obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o obj-$(CONFIG_GPIO_ML_IOH) += ml_ioh_gpio.o obj-$(CONFIG_AB8500_GPIO) += ab8500-gpio.o +obj-$(CONFIG_GPIO_TPS65910) += tps65910-gpio.o diff --git a/drivers/gpio/basic_mmio_gpio.c b/drivers/gpio/basic_mmio_gpio.c index 3addea65894..8152e9f516b 100644 --- a/drivers/gpio/basic_mmio_gpio.c +++ b/drivers/gpio/basic_mmio_gpio.c @@ -45,6 +45,7 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` */ #include <linux/init.h> +#include <linux/err.h> #include <linux/bug.h> #include <linux/kernel.h> #include <linux/module.h> @@ -61,102 +62,101 @@ o ` ~~~~\___/~~~~ ` controller in FPGA is ,.` #include <linux/mod_devicetable.h> #include <linux/basic_mmio_gpio.h> -struct bgpio_chip { - struct gpio_chip gc; - void __iomem *reg_dat; - void __iomem *reg_set; - void __iomem *reg_clr; - - /* Number of bits (GPIOs): <register width> * 8. */ - int bits; - - /* - * Some GPIO controllers work with the big-endian bits notation, - * e.g. in a 8-bits register, GPIO7 is the least significant bit. - */ - int big_endian_bits; - - /* - * Used to lock bgpio_chip->data. Also, this is needed to keep - * shadowed and real data registers writes together. - */ - spinlock_t lock; - - /* Shadowed data register to clear/set bits safely. */ - unsigned long data; -}; +static void bgpio_write8(void __iomem *reg, unsigned long data) +{ + writeb(data, reg); +} -static struct bgpio_chip *to_bgpio_chip(struct gpio_chip *gc) +static unsigned long bgpio_read8(void __iomem *reg) { - return container_of(gc, struct bgpio_chip, gc); + return readb(reg); } -static unsigned long bgpio_in(struct bgpio_chip *bgc) +static void bgpio_write16(void __iomem *reg, unsigned long data) { - switch (bgc->bits) { - case 8: - return __raw_readb(bgc->reg_dat); - case 16: - return __raw_readw(bgc->reg_dat); - case 32: - return __raw_readl(bgc->reg_dat); -#if BITS_PER_LONG >= 64 - case 64: - return __raw_readq(bgc->reg_dat); -#endif - } - return -EINVAL; + writew(data, reg); } -static void bgpio_out(struct bgpio_chip *bgc, void __iomem *reg, - unsigned long data) +static unsigned long bgpio_read16(void __iomem *reg) { - switch (bgc->bits) { - case 8: - __raw_writeb(data, reg); - return; - case 16: - __raw_writew(data, reg); - return; - case 32: - __raw_writel(data, reg); - return; + return readw(reg); +} + +static void bgpio_write32(void __iomem *reg, unsigned long data) +{ + writel(data, reg); +} + +static unsigned long bgpio_read32(void __iomem *reg) +{ + return readl(reg); +} + #if BITS_PER_LONG >= 64 - case 64: - __raw_writeq(data, reg); - return; -#endif - } +static void bgpio_write64(void __iomem *reg, unsigned long data) +{ + writeq(data, reg); } +static unsigned long bgpio_read64(void __iomem *reg) +{ + return readq(reg); +} +#endif /* BITS_PER_LONG >= 64 */ + static unsigned long bgpio_pin2mask(struct bgpio_chip *bgc, unsigned int pin) { - if (bgc->big_endian_bits) - return 1 << (bgc->bits - 1 - pin); - else - return 1 << pin; + return 1 << pin; +} + +static unsigned long bgpio_pin2mask_be(struct bgpio_chip *bgc, + unsigned int pin) +{ + return 1 << (bgc->bits - 1 - pin); } static int bgpio_get(struct gpio_chip *gc, unsigned int gpio) { struct bgpio_chip *bgc = to_bgpio_chip(gc); - return bgpio_in(bgc) & bgpio_pin2mask(bgc, gpio); + return bgc->read_reg(bgc->reg_dat) & bgc->pin2mask(bgc, gpio); } static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct bgpio_chip *bgc = to_bgpio_chip(gc); - unsigned long mask = bgpio_pin2mask(bgc, gpio); + unsigned long mask = bgc->pin2mask(bgc, gpio); unsigned long flags; - if (bgc->reg_set) { - if (val) - bgpio_out(bgc, bgc->reg_set, mask); - else - bgpio_out(bgc, bgc->reg_clr, mask); - return; - } + spin_lock_irqsave(&bgc->lock, flags); + + if (val) + bgc->data |= mask; + else + bgc->data &= ~mask; + + bgc->write_reg(bgc->reg_dat, bgc->data); + + spin_unlock_irqrestore(&bgc->lock, flags); +} + +static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long mask = bgc->pin2mask(bgc, gpio); + + if (val) + bgc->write_reg(bgc->reg_set, mask); + else + bgc->write_reg(bgc->reg_clr, mask); +} + +static void bgpio_set_set(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long mask = bgc->pin2mask(bgc, gpio); + unsigned long flags; spin_lock_irqsave(&bgc->lock, flags); @@ -165,103 +165,352 @@ static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) else bgc->data &= ~mask; - bgpio_out(bgc, bgc->reg_dat, bgc->data); + bgc->write_reg(bgc->reg_set, bgc->data); spin_unlock_irqrestore(&bgc->lock, flags); } +static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + return 0; +} + +static int bgpio_simple_dir_out(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + gc->set(gc, gpio, val); + + return 0; +} + static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) { + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; + + spin_lock_irqsave(&bgc->lock, flags); + + bgc->dir &= ~bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + return 0; } static int bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) { - bgpio_set(gc, gpio, val); + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; + + gc->set(gc, gpio, val); + + spin_lock_irqsave(&bgc->lock, flags); + + bgc->dir |= bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + return 0; } -static int __devinit bgpio_probe(struct platform_device *pdev) +static int bgpio_dir_in_inv(struct gpio_chip *gc, unsigned int gpio) { - const struct platform_device_id *platid = platform_get_device_id(pdev); - struct device *dev = &pdev->dev; - struct bgpio_pdata *pdata = dev_get_platdata(dev); - struct bgpio_chip *bgc; - struct resource *res_dat; - struct resource *res_set; - struct resource *res_clr; - resource_size_t dat_sz; - int bits; - int ret; + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; - res_dat = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat"); - if (!res_dat) - return -EINVAL; + spin_lock_irqsave(&bgc->lock, flags); - dat_sz = resource_size(res_dat); - if (!is_power_of_2(dat_sz)) - return -EINVAL; + bgc->dir |= bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + + return 0; +} - bits = dat_sz * 8; - if (bits > BITS_PER_LONG) +static int bgpio_dir_out_inv(struct gpio_chip *gc, unsigned int gpio, int val) +{ + struct bgpio_chip *bgc = to_bgpio_chip(gc); + unsigned long flags; + + gc->set(gc, gpio, val); + + spin_lock_irqsave(&bgc->lock, flags); + + bgc->dir &= ~bgc->pin2mask(bgc, gpio); + bgc->write_reg(bgc->reg_dir, bgc->dir); + + spin_unlock_irqrestore(&bgc->lock, flags); + + return 0; +} + +static int bgpio_setup_accessors(struct device *dev, + struct bgpio_chip *bgc, + bool be) +{ + + switch (bgc->bits) { + case 8: + bgc->read_reg = bgpio_read8; + bgc->write_reg = bgpio_write8; + break; + case 16: + bgc->read_reg = bgpio_read16; + bgc->write_reg = bgpio_write16; + break; + case 32: + bgc->read_reg = bgpio_read32; + bgc->write_reg = bgpio_write32; + break; +#if BITS_PER_LONG >= 64 + case 64: + bgc->read_reg = bgpio_read64; + bgc->write_reg = bgpio_write64; + break; +#endif /* BITS_PER_LONG >= 64 */ + default: + dev_err(dev, "unsupported data width %u bits\n", bgc->bits); return -EINVAL; + } - bgc = devm_kzalloc(dev, sizeof(*bgc), GFP_KERNEL); - if (!bgc) - return -ENOMEM; + bgc->pin2mask = be ? bgpio_pin2mask_be : bgpio_pin2mask; + + return 0; +} + +/* + * Create the device and allocate the resources. For setting GPIO's there are + * three supported configurations: + * + * - single input/output register resource (named "dat"). + * - set/clear pair (named "set" and "clr"). + * - single output register resource and single input resource ("set" and + * dat"). + * + * For the single output register, this drives a 1 by setting a bit and a zero + * by clearing a bit. For the set clr pair, this drives a 1 by setting a bit + * in the set register and clears it by setting a bit in the clear register. + * The configuration is detected by which resources are present. + * + * For setting the GPIO direction, there are three supported configurations: + * + * - simple bidirection GPIO that requires no configuration. + * - an output direction register (named "dirout") where a 1 bit + * indicates the GPIO is an output. + * - an input direction register (named "dirin") where a 1 bit indicates + * the GPIO is an input. + */ +static int bgpio_setup_io(struct bgpio_chip *bgc, + void __iomem *dat, + void __iomem *set, + void __iomem *clr) +{ - bgc->reg_dat = devm_ioremap(dev, res_dat->start, dat_sz); + bgc->reg_dat = dat; if (!bgc->reg_dat) - return -ENOMEM; + return -EINVAL; + + if (set && clr) { + bgc->reg_set = set; + bgc->reg_clr = clr; + bgc->gc.set = bgpio_set_with_clear; + } else if (set && !clr) { + bgc->reg_set = set; + bgc->gc.set = bgpio_set_set; + } else { + bgc->gc.set = bgpio_set; + } + + bgc->gc.get = bgpio_get; + + return 0; +} - res_set = platform_get_resource_byname(pdev, IORESOURCE_MEM, "set"); - res_clr = platform_get_resource_byname(pdev, IORESOURCE_MEM, "clr"); - if (res_set && res_clr) { - if (resource_size(res_set) != resource_size(res_clr) || - resource_size(res_set) != dat_sz) - return -EINVAL; - - bgc->reg_set = devm_ioremap(dev, res_set->start, dat_sz); - bgc->reg_clr = devm_ioremap(dev, res_clr->start, dat_sz); - if (!bgc->reg_set || !bgc->reg_clr) - return -ENOMEM; - } else if (res_set || res_clr) { +static int bgpio_setup_direction(struct bgpio_chip *bgc, + void __iomem *dirout, + void __iomem *dirin) +{ + if (dirout && dirin) { return -EINVAL; + } else if (dirout) { + bgc->reg_dir = dirout; + bgc->gc.direction_output = bgpio_dir_out; + bgc->gc.direction_input = bgpio_dir_in; + } else if (dirin) { + bgc->reg_dir = dirin; + bgc->gc.direction_output = bgpio_dir_out_inv; + bgc->gc.direction_input = bgpio_dir_in_inv; + } else { + bgc->gc.direction_output = bgpio_simple_dir_out; + bgc->gc.direction_input = bgpio_simple_dir_in; } - spin_lock_init(&bgc->lock); + return 0; +} - bgc->bits = bits; - bgc->big_endian_bits = !strcmp(platid->name, "basic-mmio-gpio-be"); - bgc->data = bgpio_in(bgc); +int __devexit bgpio_remove(struct bgpio_chip *bgc) +{ + int err = gpiochip_remove(&bgc->gc); - bgc->gc.ngpio = bits; - bgc->gc.direction_input = bgpio_dir_in; - bgc->gc.direction_output = bgpio_dir_out; - bgc->gc.get = bgpio_get; - bgc->gc.set = bgpio_set; + kfree(bgc); + + return err; +} +EXPORT_SYMBOL_GPL(bgpio_remove); + +int __devinit bgpio_init(struct bgpio_chip *bgc, + struct device *dev, + unsigned long sz, + void __iomem *dat, + void __iomem *set, + void __iomem *clr, + void __iomem *dirout, + void __iomem *dirin, + bool big_endian) +{ + int ret; + + if (!is_power_of_2(sz)) + return -EINVAL; + + bgc->bits = sz * 8; + if (bgc->bits > BITS_PER_LONG) + return -EINVAL; + + spin_lock_init(&bgc->lock); bgc->gc.dev = dev; bgc->gc.label = dev_name(dev); + bgc->gc.base = -1; + bgc->gc.ngpio = bgc->bits; - if (pdata) - bgc->gc.base = pdata->base; - else - bgc->gc.base = -1; + ret = bgpio_setup_io(bgc, dat, set, clr); + if (ret) + return ret; - dev_set_drvdata(dev, bgc); + ret = bgpio_setup_accessors(dev, bgc, big_endian); + if (ret) + return ret; - ret = gpiochip_add(&bgc->gc); + ret = bgpio_setup_direction(bgc, dirout, dirin); if (ret) - dev_err(dev, "gpiochip_add() failed: %d\n", ret); + return ret; + + bgc->data = bgc->read_reg(bgc->reg_dat); return ret; } +EXPORT_SYMBOL_GPL(bgpio_init); + +#ifdef CONFIG_GPIO_BASIC_MMIO -static int __devexit bgpio_remove(struct platform_device *pdev) +static void __iomem *bgpio_map(struct platform_device *pdev, + const char *name, + resource_size_t sane_sz, + int *err) { - struct bgpio_chip *bgc = dev_get_drvdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct resource *r; + resource_size_t start; + resource_size_t sz; + void __iomem *ret; + + *err = 0; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!r) + return NULL; - return gpiochip_remove(&bgc->gc); + sz = resource_size(r); + if (sz != sane_sz) { + *err = -EINVAL; + return NULL; + } + + start = r->start; + if (!devm_request_mem_region(dev, start, sz, r->name)) { + *err = -EBUSY; + return NULL; + } + + ret = devm_ioremap(dev, start, sz); + if (!ret) { + *err = -ENOMEM; + return NULL; + } + + return ret; +} + +static int __devinit bgpio_pdev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *r; + void __iomem *dat; + void __iomem *set; + void __iomem *clr; + void __iomem *dirout; + void __iomem *dirin; + unsigned long sz; + bool be; + int err; + struct bgpio_chip *bgc; + struct bgpio_pdata *pdata = dev_get_platdata(dev); + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat"); + if (!r) + return -EINVAL; + + sz = resource_size(r); + + dat = bgpio_map(pdev, "dat", sz, &err); + if (!dat) + return err ? err : -EINVAL; + + set = bgpio_map(pdev, "set", sz, &err); + if (err) + return err; + + clr = bgpio_map(pdev, "clr", sz, &err); + if (err) + return err; + + dirout = bgpio_map(pdev, "dirout", sz, &err); + if (err) + return err; + + dirin = bgpio_map(pdev, "dirin", sz, &err); + if (err) + return err; + + be = !strcmp(platform_get_device_id(pdev)->name, "basic-mmio-gpio-be"); + + bgc = devm_kzalloc(&pdev->dev, sizeof(*bgc), GFP_KERNEL); + if (!bgc) + return -ENOMEM; + + err = bgpio_init(bgc, dev, sz, dat, set, clr, dirout, dirin, be); + if (err) + return err; + + if (pdata) { + bgc->gc.base = pdata->base; + if (pdata->ngpio > 0) + bgc->gc.ngpio = pdata->ngpio; + } + + platform_set_drvdata(pdev, bgc); + + return gpiochip_add(&bgc->gc); +} + +static int __devexit bgpio_pdev_remove(struct platform_device *pdev) +{ + struct bgpio_chip *bgc = platform_get_drvdata(pdev); + + return bgpio_remove(bgc); } static const struct platform_device_id bgpio_id_table[] = { @@ -276,21 +525,23 @@ static struct platform_driver bgpio_driver = { .name = "basic-mmio-gpio", }, .id_table = bgpio_id_table, - .probe = bgpio_probe, - .remove = __devexit_p(bgpio_remove), + .probe = bgpio_pdev_probe, + .remove = __devexit_p(bgpio_pdev_remove), }; -static int __init bgpio_init(void) +static int __init bgpio_platform_init(void) { return platform_driver_register(&bgpio_driver); } -module_init(bgpio_init); +module_init(bgpio_platform_init); -static void __exit bgpio_exit(void) +static void __exit bgpio_platform_exit(void) { platform_driver_unregister(&bgpio_driver); } -module_exit(bgpio_exit); +module_exit(bgpio_platform_exit); + +#endif /* CONFIG_GPIO_BASIC_MMIO */ MODULE_DESCRIPTION("Driver for basic memory-mapped GPIO controllers"); MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>"); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 36a2974815b..137a8ca6782 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -12,6 +12,8 @@ #include <linux/idr.h> #include <linux/slab.h> +#define CREATE_TRACE_POINTS +#include <trace/events/gpio.h> /* Optional implementation infrastructure for GPIO interfaces. * @@ -1165,6 +1167,7 @@ struct gpio_chip *gpiochip_find(void *data, return chip; } +EXPORT_SYMBOL_GPL(gpiochip_find); /* These "optional" allocation calls help prevent drivers from stomping * on each other, and help provide better diagnostics in debugfs. @@ -1404,6 +1407,8 @@ int gpio_direction_input(unsigned gpio) status = chip->direction_input(chip, gpio); if (status == 0) clear_bit(FLAG_IS_OUT, &desc->flags); + + trace_gpio_direction(chip->base + gpio, 1, status); lose: return status; fail: @@ -1457,6 +1462,8 @@ int gpio_direction_output(unsigned gpio, int value) status = chip->direction_output(chip, gpio, value); if (status == 0) set_bit(FLAG_IS_OUT, &desc->flags); + trace_gpio_value(chip->base + gpio, 0, value); + trace_gpio_direction(chip->base + gpio, 0, status); lose: return status; fail: @@ -1546,10 +1553,13 @@ EXPORT_SYMBOL_GPL(gpio_set_debounce); int __gpio_get_value(unsigned gpio) { struct gpio_chip *chip; + int value; chip = gpio_to_chip(gpio); WARN_ON(chip->can_sleep); - return chip->get ? chip->get(chip, gpio - chip->base) : 0; + value = chip->get ? chip->get(chip, gpio - chip->base) : 0; + trace_gpio_value(gpio, 1, value); + return value; } EXPORT_SYMBOL_GPL(__gpio_get_value); @@ -1568,6 +1578,7 @@ void __gpio_set_value(unsigned gpio, int value) chip = gpio_to_chip(gpio); WARN_ON(chip->can_sleep); + trace_gpio_value(gpio, 0, value); chip->set(chip, gpio - chip->base, value); } EXPORT_SYMBOL_GPL(__gpio_set_value); @@ -1618,10 +1629,13 @@ EXPORT_SYMBOL_GPL(__gpio_to_irq); int gpio_get_value_cansleep(unsigned gpio) { struct gpio_chip *chip; + int value; might_sleep_if(extra_checks); chip = gpio_to_chip(gpio); - return chip->get ? chip->get(chip, gpio - chip->base) : 0; + value = chip->get ? chip->get(chip, gpio - chip->base) : 0; + trace_gpio_value(gpio, 1, value); + return value; } EXPORT_SYMBOL_GPL(gpio_get_value_cansleep); @@ -1631,6 +1645,7 @@ void gpio_set_value_cansleep(unsigned gpio, int value) might_sleep_if(extra_checks); chip = gpio_to_chip(gpio); + trace_gpio_value(gpio, 0, value); chip->set(chip, gpio - chip->base, value); } EXPORT_SYMBOL_GPL(gpio_set_value_cansleep); diff --git a/drivers/gpio/janz-ttl.c b/drivers/gpio/janz-ttl.c index 2514fb075f4..813ac077e5d 100644 --- a/drivers/gpio/janz-ttl.c +++ b/drivers/gpio/janz-ttl.c @@ -15,7 +15,6 @@ #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/io.h> #include <linux/gpio.h> #include <linux/slab.h> @@ -150,7 +149,7 @@ static int __devinit ttl_probe(struct platform_device *pdev) struct resource *res; int ret; - pdata = mfd_get_data(pdev); + pdata = pdev->dev.platform_data; if (!pdata) { dev_err(dev, "no platform data\n"); ret = -ENXIO; diff --git a/drivers/gpio/pca953x.c b/drivers/gpio/pca953x.c index 7630ab7b9be..78a843947d8 100644 --- a/drivers/gpio/pca953x.c +++ b/drivers/gpio/pca953x.c @@ -397,7 +397,7 @@ static int pca953x_irq_setup(struct pca953x_chip *chip, irq_set_chip_data(irq, chip); irq_set_chip_and_handler(irq, &pca953x_irq_chip, - handle_edge_irq); + handle_simple_irq); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else diff --git a/drivers/gpio/rdc321x-gpio.c b/drivers/gpio/rdc321x-gpio.c index a9bda881935..2762698e020 100644 --- a/drivers/gpio/rdc321x-gpio.c +++ b/drivers/gpio/rdc321x-gpio.c @@ -27,7 +27,6 @@ #include <linux/pci.h> #include <linux/gpio.h> #include <linux/mfd/rdc321x.h> -#include <linux/mfd/core.h> #include <linux/slab.h> struct rdc321x_gpio { @@ -136,7 +135,7 @@ static int __devinit rdc321x_gpio_probe(struct platform_device *pdev) struct rdc321x_gpio *rdc321x_gpio_dev; struct rdc321x_gpio_pdata *pdata; - pdata = mfd_get_data(pdev); + pdata = pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "no platform data supplied\n"); return -ENODEV; diff --git a/drivers/gpio/timbgpio.c b/drivers/gpio/timbgpio.c index edbe1eae531..0265872e57d 100644 --- a/drivers/gpio/timbgpio.c +++ b/drivers/gpio/timbgpio.c @@ -23,7 +23,6 @@ #include <linux/module.h> #include <linux/gpio.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/timb_gpio.h> @@ -229,7 +228,7 @@ static int __devinit timbgpio_probe(struct platform_device *pdev) struct gpio_chip *gc; struct timbgpio *tgpio; struct resource *iomem; - struct timbgpio_platform_data *pdata = mfd_get_data(pdev); + struct timbgpio_platform_data *pdata = pdev->dev.platform_data; int irq = platform_get_irq(pdev, 0); if (!pdata || pdata->nr_pins > 32) { @@ -320,13 +319,14 @@ err_mem: static int __devexit timbgpio_remove(struct platform_device *pdev) { int err; + struct timbgpio_platform_data *pdata = pdev->dev.platform_data; struct timbgpio *tgpio = platform_get_drvdata(pdev); struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); int irq = platform_get_irq(pdev, 0); if (irq >= 0 && tgpio->irq_base > 0) { int i; - for (i = 0; i < tgpio->gpio.ngpio; i++) { + for (i = 0; i < pdata->nr_pins; i++) { irq_set_chip(tgpio->irq_base + i, NULL); irq_set_chip_data(tgpio->irq_base + i, NULL); } diff --git a/drivers/gpio/tps65910-gpio.c b/drivers/gpio/tps65910-gpio.c new file mode 100644 index 00000000000..8d1ddfdd63e --- /dev/null +++ b/drivers/gpio/tps65910-gpio.c @@ -0,0 +1,100 @@ +/* + * tps65910-gpio.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria jedu@slimlogic.co.uk> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/mfd/tps65910.h> + +static int tps65910_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + uint8_t val; + + tps65910->read(tps65910, TPS65910_GPIO0 + offset, 1, &val); + + if (val & GPIO_STS_MASK) + return 1; + + return 0; +} + +static void tps65910_gpio_set(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + + if (value) + tps65910_set_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_SET_MASK); + else + tps65910_clear_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_SET_MASK); +} + +static int tps65910_gpio_output(struct gpio_chip *gc, unsigned offset, + int value) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + + /* Set the initial value */ + tps65910_gpio_set(gc, 0, value); + + return tps65910_set_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_CFG_MASK); +} + +static int tps65910_gpio_input(struct gpio_chip *gc, unsigned offset) +{ + struct tps65910 *tps65910 = container_of(gc, struct tps65910, gpio); + + return tps65910_clear_bits(tps65910, TPS65910_GPIO0 + offset, + GPIO_CFG_MASK); +} + +void tps65910_gpio_init(struct tps65910 *tps65910, int gpio_base) +{ + int ret; + + if (!gpio_base) + return; + + tps65910->gpio.owner = THIS_MODULE; + tps65910->gpio.label = tps65910->i2c_client->name; + tps65910->gpio.dev = tps65910->dev; + tps65910->gpio.base = gpio_base; + + switch(tps65910_chip_id(tps65910)) { + case TPS65910: + tps65910->gpio.ngpio = 6; + case TPS65911: + tps65910->gpio.ngpio = 9; + default: + return; + } + tps65910->gpio.can_sleep = 1; + + tps65910->gpio.direction_input = tps65910_gpio_input; + tps65910->gpio.direction_output = tps65910_gpio_output; + tps65910->gpio.set = tps65910_gpio_set; + tps65910->gpio.get = tps65910_gpio_get; + + ret = gpiochip_add(&tps65910->gpio); + + if (ret) + dev_warn(tps65910->dev, "GPIO registration failed: %d\n", ret); +} diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 9577c432e77..de3d2465fe2 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -350,6 +350,7 @@ static int get_pkg_tjmax(unsigned int cpu, struct device *dev) static int create_name_attr(struct platform_data *pdata, struct device *dev) { + sysfs_attr_init(&pdata->name_attr.attr); pdata->name_attr.attr.name = "name"; pdata->name_attr.attr.mode = S_IRUGO; pdata->name_attr.show = show_name; @@ -372,6 +373,7 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev, for (i = 0; i < MAX_ATTRS; i++) { snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH, names[i], attr_no); + sysfs_attr_init(&tdata->sd_attrs[i].dev_attr.attr); tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i]; tdata->sd_attrs[i].dev_attr.attr.mode = S_IRUGO; tdata->sd_attrs[i].dev_attr.show = rd_ptr[i]; @@ -422,7 +424,7 @@ static void update_ttarget(__u8 cpu_model, struct temp_data *tdata, } } -static int chk_ucode_version(struct platform_device *pdev) +static int __devinit chk_ucode_version(struct platform_device *pdev) { struct cpuinfo_x86 *c = &cpu_data(pdev->id); int err; @@ -509,8 +511,8 @@ static int create_core_data(struct platform_data *pdata, /* * Provide a single set of attributes for all HT siblings of a core * to avoid duplicate sensors (the processor ID and core ID of all - * HT siblings of a core is the same). - * Skip if a HT sibling of this core is already online. + * HT siblings of a core are the same). + * Skip if a HT sibling of this core is already registered. * This is not an error. */ if (pdata->core_data[attr_no] != NULL) @@ -770,10 +772,10 @@ static void __cpuinit put_core_offline(unsigned int cpu) coretemp_remove_core(pdata, &pdev->dev, indx); /* - * If a core is taken offline, but a HT sibling of the same core is - * still online, register the alternate sibling. This ensures that - * exactly one set of attributes is provided as long as at least one - * HT sibling of a core is online. + * If a HT sibling of a core is taken offline, but another HT sibling + * of the same core is still online, register the alternate sibling. + * This ensures that exactly one set of attributes is provided as long + * as at least one HT sibling of a core is online. */ for_each_sibling(i, cpu) { if (i != cpu) { diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c index 98799bab69c..354770ed318 100644 --- a/drivers/hwmon/pmbus_core.c +++ b/drivers/hwmon/pmbus_core.c @@ -707,6 +707,7 @@ do { \ struct sensor_device_attribute *a \ = &data->_type##s[data->num_##_type##s].attribute; \ BUG_ON(data->num_attributes >= data->max_attributes); \ + sysfs_attr_init(&a->dev_attr.attr); \ a->dev_attr.attr.name = _name; \ a->dev_attr.attr.mode = _mode; \ a->dev_attr.show = _show; \ diff --git a/drivers/i2c/busses/i2c-ocores.c b/drivers/i2c/busses/i2c-ocores.c index fee1a261386..1b46a9d9f90 100644 --- a/drivers/i2c/busses/i2c-ocores.c +++ b/drivers/i2c/busses/i2c-ocores.c @@ -49,7 +49,6 @@ #include <linux/init.h> #include <linux/errno.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/wait.h> @@ -306,7 +305,7 @@ static int __devinit ocores_i2c_probe(struct platform_device *pdev) return -EIO; } - pdata = mfd_get_data(pdev); + pdata = pdev->dev.platform_data; if (pdata) { i2c->regstep = pdata->regstep; i2c->clock_khz = pdata->clock_khz; diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index e9d5ff4d149..4bb68f35caf 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -34,7 +34,6 @@ #include <linux/errno.h> #include <linux/delay.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/wait.h> @@ -705,7 +704,7 @@ static int __devinit xiic_i2c_probe(struct platform_device *pdev) if (irq < 0) goto resource_missing; - pdata = mfd_get_data(pdev); + pdata = (struct xiic_i2c_platform_data *) pdev->dev.platform_data; if (!pdata) return -EINVAL; diff --git a/drivers/infiniband/Kconfig b/drivers/infiniband/Kconfig index 6e35eccc9ca..0f9a84c1046 100644 --- a/drivers/infiniband/Kconfig +++ b/drivers/infiniband/Kconfig @@ -2,6 +2,7 @@ menuconfig INFINIBAND tristate "InfiniBand support" depends on PCI || BROKEN depends on HAS_IOMEM + depends on NET ---help--- Core support for InfiniBand (IB). Make sure to also select any protocols you wish to use as well as drivers for your diff --git a/drivers/infiniband/core/Makefile b/drivers/infiniband/core/Makefile index cb1ab3ea499..c8bbaef1bec 100644 --- a/drivers/infiniband/core/Makefile +++ b/drivers/infiniband/core/Makefile @@ -8,7 +8,7 @@ obj-$(CONFIG_INFINIBAND_USER_ACCESS) += ib_uverbs.o ib_ucm.o \ $(user_access-y) ib_core-y := packer.o ud_header.o verbs.o sysfs.o \ - device.o fmr_pool.o cache.o + device.o fmr_pool.o cache.o netlink.o ib_core-$(CONFIG_INFINIBAND_USER_MEM) += umem.o ib_mad-y := mad.o smi.o agent.o mad_rmpp.o diff --git a/drivers/infiniband/core/cm.c b/drivers/infiniband/core/cm.c index f804e28e1eb..f62f52fb9ec 100644 --- a/drivers/infiniband/core/cm.c +++ b/drivers/infiniband/core/cm.c @@ -3639,8 +3639,16 @@ static struct kobj_type cm_port_obj_type = { .release = cm_release_port_obj }; +static char *cm_devnode(struct device *dev, mode_t *mode) +{ + *mode = 0666; + return kasprintf(GFP_KERNEL, "infiniband/%s", dev_name(dev)); +} + struct class cm_class = { + .owner = THIS_MODULE, .name = "infiniband_cm", + .devnode = cm_devnode, }; EXPORT_SYMBOL(cm_class); diff --git a/drivers/infiniband/core/cma.c b/drivers/infiniband/core/cma.c index 99dde874fbb..b6a33b3c516 100644 --- a/drivers/infiniband/core/cma.c +++ b/drivers/infiniband/core/cma.c @@ -47,6 +47,7 @@ #include <rdma/rdma_cm.h> #include <rdma/rdma_cm_ib.h> +#include <rdma/rdma_netlink.h> #include <rdma/ib_cache.h> #include <rdma/ib_cm.h> #include <rdma/ib_sa.h> @@ -89,20 +90,6 @@ struct cma_device { struct list_head id_list; }; -enum cma_state { - CMA_IDLE, - CMA_ADDR_QUERY, - CMA_ADDR_RESOLVED, - CMA_ROUTE_QUERY, - CMA_ROUTE_RESOLVED, - CMA_CONNECT, - CMA_DISCONNECT, - CMA_ADDR_BOUND, - CMA_LISTEN, - CMA_DEVICE_REMOVAL, - CMA_DESTROYING -}; - struct rdma_bind_list { struct idr *ps; struct hlist_head owners; @@ -126,7 +113,7 @@ struct rdma_id_private { struct list_head mc_list; int internal_id; - enum cma_state state; + enum rdma_cm_state state; spinlock_t lock; struct mutex qp_mutex; @@ -146,6 +133,7 @@ struct rdma_id_private { u32 seq_num; u32 qkey; u32 qp_num; + pid_t owner; u8 srq; u8 tos; u8 reuseaddr; @@ -165,8 +153,8 @@ struct cma_multicast { struct cma_work { struct work_struct work; struct rdma_id_private *id; - enum cma_state old_state; - enum cma_state new_state; + enum rdma_cm_state old_state; + enum rdma_cm_state new_state; struct rdma_cm_event event; }; @@ -217,7 +205,7 @@ struct sdp_hah { #define CMA_VERSION 0x00 #define SDP_MAJ_VERSION 0x2 -static int cma_comp(struct rdma_id_private *id_priv, enum cma_state comp) +static int cma_comp(struct rdma_id_private *id_priv, enum rdma_cm_state comp) { unsigned long flags; int ret; @@ -229,7 +217,7 @@ static int cma_comp(struct rdma_id_private *id_priv, enum cma_state comp) } static int cma_comp_exch(struct rdma_id_private *id_priv, - enum cma_state comp, enum cma_state exch) + enum rdma_cm_state comp, enum rdma_cm_state exch) { unsigned long flags; int ret; @@ -241,11 +229,11 @@ static int cma_comp_exch(struct rdma_id_private *id_priv, return ret; } -static enum cma_state cma_exch(struct rdma_id_private *id_priv, - enum cma_state exch) +static enum rdma_cm_state cma_exch(struct rdma_id_private *id_priv, + enum rdma_cm_state exch) { unsigned long flags; - enum cma_state old; + enum rdma_cm_state old; spin_lock_irqsave(&id_priv->lock, flags); old = id_priv->state; @@ -279,11 +267,6 @@ static inline void sdp_set_ip_ver(struct sdp_hh *hh, u8 ip_ver) hh->ip_version = (ip_ver << 4) | (hh->ip_version & 0xF); } -static inline int cma_is_ud_ps(enum rdma_port_space ps) -{ - return (ps == RDMA_PS_UDP || ps == RDMA_PS_IPOIB); -} - static void cma_attach_to_dev(struct rdma_id_private *id_priv, struct cma_device *cma_dev) { @@ -413,7 +396,7 @@ static void cma_deref_id(struct rdma_id_private *id_priv) } static int cma_disable_callback(struct rdma_id_private *id_priv, - enum cma_state state) + enum rdma_cm_state state) { mutex_lock(&id_priv->handler_mutex); if (id_priv->state != state) { @@ -429,7 +412,8 @@ static int cma_has_cm_dev(struct rdma_id_private *id_priv) } struct rdma_cm_id *rdma_create_id(rdma_cm_event_handler event_handler, - void *context, enum rdma_port_space ps) + void *context, enum rdma_port_space ps, + enum ib_qp_type qp_type) { struct rdma_id_private *id_priv; @@ -437,10 +421,12 @@ struct rdma_cm_id *rdma_create_id(rdma_cm_event_handler event_handler, if (!id_priv) return ERR_PTR(-ENOMEM); - id_priv->state = CMA_IDLE; + id_priv->owner = task_pid_nr(current); + id_priv->state = RDMA_CM_IDLE; id_priv->id.context = context; id_priv->id.event_handler = event_handler; id_priv->id.ps = ps; + id_priv->id.qp_type = qp_type; spin_lock_init(&id_priv->lock); mutex_init(&id_priv->qp_mutex); init_completion(&id_priv->comp); @@ -508,7 +494,7 @@ int rdma_create_qp(struct rdma_cm_id *id, struct ib_pd *pd, if (IS_ERR(qp)) return PTR_ERR(qp); - if (cma_is_ud_ps(id_priv->id.ps)) + if (id->qp_type == IB_QPT_UD) ret = cma_init_ud_qp(id_priv, qp); else ret = cma_init_conn_qp(id_priv, qp); @@ -636,7 +622,7 @@ static int cma_ib_init_qp_attr(struct rdma_id_private *id_priv, qp_attr->port_num = id_priv->id.port_num; *qp_attr_mask = IB_QP_STATE | IB_QP_PKEY_INDEX | IB_QP_PORT; - if (cma_is_ud_ps(id_priv->id.ps)) { + if (id_priv->id.qp_type == IB_QPT_UD) { ret = cma_set_qkey(id_priv); if (ret) return ret; @@ -659,7 +645,7 @@ int rdma_init_qp_attr(struct rdma_cm_id *id, struct ib_qp_attr *qp_attr, id_priv = container_of(id, struct rdma_id_private, id); switch (rdma_node_get_transport(id_priv->id.device->node_type)) { case RDMA_TRANSPORT_IB: - if (!id_priv->cm_id.ib || cma_is_ud_ps(id_priv->id.ps)) + if (!id_priv->cm_id.ib || (id_priv->id.qp_type == IB_QPT_UD)) ret = cma_ib_init_qp_attr(id_priv, qp_attr, qp_attr_mask); else ret = ib_cm_init_qp_attr(id_priv->cm_id.ib, qp_attr, @@ -858,16 +844,16 @@ static void cma_cancel_listens(struct rdma_id_private *id_priv) } static void cma_cancel_operation(struct rdma_id_private *id_priv, - enum cma_state state) + enum rdma_cm_state state) { switch (state) { - case CMA_ADDR_QUERY: + case RDMA_CM_ADDR_QUERY: rdma_addr_cancel(&id_priv->id.route.addr.dev_addr); break; - case CMA_ROUTE_QUERY: + case RDMA_CM_ROUTE_QUERY: cma_cancel_route(id_priv); break; - case CMA_LISTEN: + case RDMA_CM_LISTEN: if (cma_any_addr((struct sockaddr *) &id_priv->id.route.addr.src_addr) && !id_priv->cma_dev) cma_cancel_listens(id_priv); @@ -918,10 +904,10 @@ static void cma_leave_mc_groups(struct rdma_id_private *id_priv) void rdma_destroy_id(struct rdma_cm_id *id) { struct rdma_id_private *id_priv; - enum cma_state state; + enum rdma_cm_state state; id_priv = container_of(id, struct rdma_id_private, id); - state = cma_exch(id_priv, CMA_DESTROYING); + state = cma_exch(id_priv, RDMA_CM_DESTROYING); cma_cancel_operation(id_priv, state); /* @@ -1015,9 +1001,9 @@ static int cma_ib_handler(struct ib_cm_id *cm_id, struct ib_cm_event *ib_event) int ret = 0; if ((ib_event->event != IB_CM_TIMEWAIT_EXIT && - cma_disable_callback(id_priv, CMA_CONNECT)) || + cma_disable_callback(id_priv, RDMA_CM_CONNECT)) || (ib_event->event == IB_CM_TIMEWAIT_EXIT && - cma_disable_callback(id_priv, CMA_DISCONNECT))) + cma_disable_callback(id_priv, RDMA_CM_DISCONNECT))) return 0; memset(&event, 0, sizeof event); @@ -1048,7 +1034,8 @@ static int cma_ib_handler(struct ib_cm_id *cm_id, struct ib_cm_event *ib_event) event.status = -ETIMEDOUT; /* fall through */ case IB_CM_DREQ_RECEIVED: case IB_CM_DREP_RECEIVED: - if (!cma_comp_exch(id_priv, CMA_CONNECT, CMA_DISCONNECT)) + if (!cma_comp_exch(id_priv, RDMA_CM_CONNECT, + RDMA_CM_DISCONNECT)) goto out; event.event = RDMA_CM_EVENT_DISCONNECTED; break; @@ -1075,7 +1062,7 @@ static int cma_ib_handler(struct ib_cm_id *cm_id, struct ib_cm_event *ib_event) if (ret) { /* Destroy the CM ID by returning a non-zero value. */ id_priv->cm_id.ib = NULL; - cma_exch(id_priv, CMA_DESTROYING); + cma_exch(id_priv, RDMA_CM_DESTROYING); mutex_unlock(&id_priv->handler_mutex); rdma_destroy_id(&id_priv->id); return ret; @@ -1101,7 +1088,7 @@ static struct rdma_id_private *cma_new_conn_id(struct rdma_cm_id *listen_id, goto err; id = rdma_create_id(listen_id->event_handler, listen_id->context, - listen_id->ps); + listen_id->ps, ib_event->param.req_rcvd.qp_type); if (IS_ERR(id)) goto err; @@ -1132,7 +1119,7 @@ static struct rdma_id_private *cma_new_conn_id(struct rdma_cm_id *listen_id, rdma_addr_set_dgid(&rt->addr.dev_addr, &rt->path_rec[0].dgid); id_priv = container_of(id, struct rdma_id_private, id); - id_priv->state = CMA_CONNECT; + id_priv->state = RDMA_CM_CONNECT; return id_priv; destroy_id: @@ -1152,7 +1139,7 @@ static struct rdma_id_private *cma_new_udp_id(struct rdma_cm_id *listen_id, int ret; id = rdma_create_id(listen_id->event_handler, listen_id->context, - listen_id->ps); + listen_id->ps, IB_QPT_UD); if (IS_ERR(id)) return NULL; @@ -1172,7 +1159,7 @@ static struct rdma_id_private *cma_new_udp_id(struct rdma_cm_id *listen_id, } id_priv = container_of(id, struct rdma_id_private, id); - id_priv->state = CMA_CONNECT; + id_priv->state = RDMA_CM_CONNECT; return id_priv; err: rdma_destroy_id(id); @@ -1201,13 +1188,13 @@ static int cma_req_handler(struct ib_cm_id *cm_id, struct ib_cm_event *ib_event) int offset, ret; listen_id = cm_id->context; - if (cma_disable_callback(listen_id, CMA_LISTEN)) + if (cma_disable_callback(listen_id, RDMA_CM_LISTEN)) return -ECONNABORTED; memset(&event, 0, sizeof event); offset = cma_user_data_offset(listen_id->id.ps); event.event = RDMA_CM_EVENT_CONNECT_REQUEST; - if (cma_is_ud_ps(listen_id->id.ps)) { + if (listen_id->id.qp_type == IB_QPT_UD) { conn_id = cma_new_udp_id(&listen_id->id, ib_event); event.param.ud.private_data = ib_event->private_data + offset; event.param.ud.private_data_len = @@ -1243,8 +1230,7 @@ static int cma_req_handler(struct ib_cm_id *cm_id, struct ib_cm_event *ib_event) * while we're accessing the cm_id. */ mutex_lock(&lock); - if (cma_comp(conn_id, CMA_CONNECT) && - !cma_is_ud_ps(conn_id->id.ps)) + if (cma_comp(conn_id, RDMA_CM_CONNECT) && (conn_id->id.qp_type != IB_QPT_UD)) ib_send_cm_mra(cm_id, CMA_CM_MRA_SETTING, NULL, 0); mutex_unlock(&lock); mutex_unlock(&conn_id->handler_mutex); @@ -1257,7 +1243,7 @@ static int cma_req_handler(struct ib_cm_id *cm_id, struct ib_cm_event *ib_event) conn_id->cm_id.ib = NULL; release_conn_id: - cma_exch(conn_id, CMA_DESTROYING); + cma_exch(conn_id, RDMA_CM_DESTROYING); mutex_unlock(&conn_id->handler_mutex); rdma_destroy_id(&conn_id->id); @@ -1328,7 +1314,7 @@ static int cma_iw_handler(struct iw_cm_id *iw_id, struct iw_cm_event *iw_event) struct sockaddr_in *sin; int ret = 0; - if (cma_disable_callback(id_priv, CMA_CONNECT)) + if (cma_disable_callback(id_priv, RDMA_CM_CONNECT)) return 0; memset(&event, 0, sizeof event); @@ -1371,7 +1357,7 @@ static int cma_iw_handler(struct iw_cm_id *iw_id, struct iw_cm_event *iw_event) if (ret) { /* Destroy the CM ID by returning a non-zero value. */ id_priv->cm_id.iw = NULL; - cma_exch(id_priv, CMA_DESTROYING); + cma_exch(id_priv, RDMA_CM_DESTROYING); mutex_unlock(&id_priv->handler_mutex); rdma_destroy_id(&id_priv->id); return ret; @@ -1393,20 +1379,20 @@ static int iw_conn_req_handler(struct iw_cm_id *cm_id, struct ib_device_attr attr; listen_id = cm_id->context; - if (cma_disable_callback(listen_id, CMA_LISTEN)) + if (cma_disable_callback(listen_id, RDMA_CM_LISTEN)) return -ECONNABORTED; /* Create a new RDMA id for the new IW CM ID */ new_cm_id = rdma_create_id(listen_id->id.event_handler, listen_id->id.context, - RDMA_PS_TCP); + RDMA_PS_TCP, IB_QPT_RC); if (IS_ERR(new_cm_id)) { ret = -ENOMEM; goto out; } conn_id = container_of(new_cm_id, struct rdma_id_private, id); mutex_lock_nested(&conn_id->handler_mutex, SINGLE_DEPTH_NESTING); - conn_id->state = CMA_CONNECT; + conn_id->state = RDMA_CM_CONNECT; dev = ip_dev_find(&init_net, iw_event->local_addr.sin_addr.s_addr); if (!dev) { @@ -1461,7 +1447,7 @@ static int iw_conn_req_handler(struct iw_cm_id *cm_id, if (ret) { /* User wants to destroy the CM ID */ conn_id->cm_id.iw = NULL; - cma_exch(conn_id, CMA_DESTROYING); + cma_exch(conn_id, RDMA_CM_DESTROYING); mutex_unlock(&conn_id->handler_mutex); cma_deref_id(conn_id); rdma_destroy_id(&conn_id->id); @@ -1548,13 +1534,14 @@ static void cma_listen_on_dev(struct rdma_id_private *id_priv, struct rdma_cm_id *id; int ret; - id = rdma_create_id(cma_listen_handler, id_priv, id_priv->id.ps); + id = rdma_create_id(cma_listen_handler, id_priv, id_priv->id.ps, + id_priv->id.qp_type); if (IS_ERR(id)) return; dev_id_priv = container_of(id, struct rdma_id_private, id); - dev_id_priv->state = CMA_ADDR_BOUND; + dev_id_priv->state = RDMA_CM_ADDR_BOUND; memcpy(&id->route.addr.src_addr, &id_priv->id.route.addr.src_addr, ip_addr_size((struct sockaddr *) &id_priv->id.route.addr.src_addr)); @@ -1601,8 +1588,8 @@ static void cma_query_handler(int status, struct ib_sa_path_rec *path_rec, route->num_paths = 1; *route->path_rec = *path_rec; } else { - work->old_state = CMA_ROUTE_QUERY; - work->new_state = CMA_ADDR_RESOLVED; + work->old_state = RDMA_CM_ROUTE_QUERY; + work->new_state = RDMA_CM_ADDR_RESOLVED; work->event.event = RDMA_CM_EVENT_ROUTE_ERROR; work->event.status = status; } @@ -1660,7 +1647,7 @@ static void cma_work_handler(struct work_struct *_work) goto out; if (id_priv->id.event_handler(&id_priv->id, &work->event)) { - cma_exch(id_priv, CMA_DESTROYING); + cma_exch(id_priv, RDMA_CM_DESTROYING); destroy = 1; } out: @@ -1678,12 +1665,12 @@ static void cma_ndev_work_handler(struct work_struct *_work) int destroy = 0; mutex_lock(&id_priv->handler_mutex); - if (id_priv->state == CMA_DESTROYING || - id_priv->state == CMA_DEVICE_REMOVAL) + if (id_priv->state == RDMA_CM_DESTROYING || + id_priv->state == RDMA_CM_DEVICE_REMOVAL) goto out; if (id_priv->id.event_handler(&id_priv->id, &work->event)) { - cma_exch(id_priv, CMA_DESTROYING); + cma_exch(id_priv, RDMA_CM_DESTROYING); destroy = 1; } @@ -1707,8 +1694,8 @@ static int cma_resolve_ib_route(struct rdma_id_private *id_priv, int timeout_ms) work->id = id_priv; INIT_WORK(&work->work, cma_work_handler); - work->old_state = CMA_ROUTE_QUERY; - work->new_state = CMA_ROUTE_RESOLVED; + work->old_state = RDMA_CM_ROUTE_QUERY; + work->new_state = RDMA_CM_ROUTE_RESOLVED; work->event.event = RDMA_CM_EVENT_ROUTE_RESOLVED; route->path_rec = kmalloc(sizeof *route->path_rec, GFP_KERNEL); @@ -1737,7 +1724,8 @@ int rdma_set_ib_paths(struct rdma_cm_id *id, int ret; id_priv = container_of(id, struct rdma_id_private, id); - if (!cma_comp_exch(id_priv, CMA_ADDR_RESOLVED, CMA_ROUTE_RESOLVED)) + if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_RESOLVED, + RDMA_CM_ROUTE_RESOLVED)) return -EINVAL; id->route.path_rec = kmemdup(path_rec, sizeof *path_rec * num_paths, @@ -1750,7 +1738,7 @@ int rdma_set_ib_paths(struct rdma_cm_id *id, id->route.num_paths = num_paths; return 0; err: - cma_comp_exch(id_priv, CMA_ROUTE_RESOLVED, CMA_ADDR_RESOLVED); + cma_comp_exch(id_priv, RDMA_CM_ROUTE_RESOLVED, RDMA_CM_ADDR_RESOLVED); return ret; } EXPORT_SYMBOL(rdma_set_ib_paths); @@ -1765,8 +1753,8 @@ static int cma_resolve_iw_route(struct rdma_id_private *id_priv, int timeout_ms) work->id = id_priv; INIT_WORK(&work->work, cma_work_handler); - work->old_state = CMA_ROUTE_QUERY; - work->new_state = CMA_ROUTE_RESOLVED; + work->old_state = RDMA_CM_ROUTE_QUERY; + work->new_state = RDMA_CM_ROUTE_RESOLVED; work->event.event = RDMA_CM_EVENT_ROUTE_RESOLVED; queue_work(cma_wq, &work->work); return 0; @@ -1830,8 +1818,8 @@ static int cma_resolve_iboe_route(struct rdma_id_private *id_priv) goto err2; } - work->old_state = CMA_ROUTE_QUERY; - work->new_state = CMA_ROUTE_RESOLVED; + work->old_state = RDMA_CM_ROUTE_QUERY; + work->new_state = RDMA_CM_ROUTE_RESOLVED; work->event.event = RDMA_CM_EVENT_ROUTE_RESOLVED; work->event.status = 0; @@ -1853,7 +1841,7 @@ int rdma_resolve_route(struct rdma_cm_id *id, int timeout_ms) int ret; id_priv = container_of(id, struct rdma_id_private, id); - if (!cma_comp_exch(id_priv, CMA_ADDR_RESOLVED, CMA_ROUTE_QUERY)) + if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_RESOLVED, RDMA_CM_ROUTE_QUERY)) return -EINVAL; atomic_inc(&id_priv->refcount); @@ -1882,7 +1870,7 @@ int rdma_resolve_route(struct rdma_cm_id *id, int timeout_ms) return 0; err: - cma_comp_exch(id_priv, CMA_ROUTE_QUERY, CMA_ADDR_RESOLVED); + cma_comp_exch(id_priv, RDMA_CM_ROUTE_QUERY, RDMA_CM_ADDR_RESOLVED); cma_deref_id(id_priv); return ret; } @@ -1941,14 +1929,16 @@ static void addr_handler(int status, struct sockaddr *src_addr, memset(&event, 0, sizeof event); mutex_lock(&id_priv->handler_mutex); - if (!cma_comp_exch(id_priv, CMA_ADDR_QUERY, CMA_ADDR_RESOLVED)) + if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_QUERY, + RDMA_CM_ADDR_RESOLVED)) goto out; if (!status && !id_priv->cma_dev) status = cma_acquire_dev(id_priv); if (status) { - if (!cma_comp_exch(id_priv, CMA_ADDR_RESOLVED, CMA_ADDR_BOUND)) + if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_RESOLVED, + RDMA_CM_ADDR_BOUND)) goto out; event.event = RDMA_CM_EVENT_ADDR_ERROR; event.status = status; @@ -1959,7 +1949,7 @@ static void addr_handler(int status, struct sockaddr *src_addr, } if (id_priv->id.event_handler(&id_priv->id, &event)) { - cma_exch(id_priv, CMA_DESTROYING); + cma_exch(id_priv, RDMA_CM_DESTROYING); mutex_unlock(&id_priv->handler_mutex); cma_deref_id(id_priv); rdma_destroy_id(&id_priv->id); @@ -2004,8 +1994,8 @@ static int cma_resolve_loopback(struct rdma_id_private *id_priv) work->id = id_priv; INIT_WORK(&work->work, cma_work_handler); - work->old_state = CMA_ADDR_QUERY; - work->new_state = CMA_ADDR_RESOLVED; + work->old_state = RDMA_CM_ADDR_QUERY; + work->new_state = RDMA_CM_ADDR_RESOLVED; work->event.event = RDMA_CM_EVENT_ADDR_RESOLVED; queue_work(cma_wq, &work->work); return 0; @@ -2034,13 +2024,13 @@ int rdma_resolve_addr(struct rdma_cm_id *id, struct sockaddr *src_addr, int ret; id_priv = container_of(id, struct rdma_id_private, id); - if (id_priv->state == CMA_IDLE) { + if (id_priv->state == RDMA_CM_IDLE) { ret = cma_bind_addr(id, src_addr, dst_addr); if (ret) return ret; } - if (!cma_comp_exch(id_priv, CMA_ADDR_BOUND, CMA_ADDR_QUERY)) + if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_BOUND, RDMA_CM_ADDR_QUERY)) return -EINVAL; atomic_inc(&id_priv->refcount); @@ -2056,7 +2046,7 @@ int rdma_resolve_addr(struct rdma_cm_id *id, struct sockaddr *src_addr, return 0; err: - cma_comp_exch(id_priv, CMA_ADDR_QUERY, CMA_ADDR_BOUND); + cma_comp_exch(id_priv, RDMA_CM_ADDR_QUERY, RDMA_CM_ADDR_BOUND); cma_deref_id(id_priv); return ret; } @@ -2070,7 +2060,7 @@ int rdma_set_reuseaddr(struct rdma_cm_id *id, int reuse) id_priv = container_of(id, struct rdma_id_private, id); spin_lock_irqsave(&id_priv->lock, flags); - if (id_priv->state == CMA_IDLE) { + if (id_priv->state == RDMA_CM_IDLE) { id_priv->reuseaddr = reuse; ret = 0; } else { @@ -2177,7 +2167,7 @@ static int cma_check_port(struct rdma_bind_list *bind_list, if (id_priv == cur_id) continue; - if ((cur_id->state == CMA_LISTEN) || + if ((cur_id->state == RDMA_CM_LISTEN) || !reuseaddr || !cur_id->reuseaddr) { cur_addr = (struct sockaddr *) &cur_id->id.route.addr.src_addr; if (cma_any_addr(cur_addr)) @@ -2280,14 +2270,14 @@ int rdma_listen(struct rdma_cm_id *id, int backlog) int ret; id_priv = container_of(id, struct rdma_id_private, id); - if (id_priv->state == CMA_IDLE) { + if (id_priv->state == RDMA_CM_IDLE) { ((struct sockaddr *) &id->route.addr.src_addr)->sa_family = AF_INET; ret = rdma_bind_addr(id, (struct sockaddr *) &id->route.addr.src_addr); if (ret) return ret; } - if (!cma_comp_exch(id_priv, CMA_ADDR_BOUND, CMA_LISTEN)) + if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_BOUND, RDMA_CM_LISTEN)) return -EINVAL; if (id_priv->reuseaddr) { @@ -2319,7 +2309,7 @@ int rdma_listen(struct rdma_cm_id *id, int backlog) return 0; err: id_priv->backlog = 0; - cma_comp_exch(id_priv, CMA_LISTEN, CMA_ADDR_BOUND); + cma_comp_exch(id_priv, RDMA_CM_LISTEN, RDMA_CM_ADDR_BOUND); return ret; } EXPORT_SYMBOL(rdma_listen); @@ -2333,7 +2323,7 @@ int rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr) return -EAFNOSUPPORT; id_priv = container_of(id, struct rdma_id_private, id); - if (!cma_comp_exch(id_priv, CMA_IDLE, CMA_ADDR_BOUND)) + if (!cma_comp_exch(id_priv, RDMA_CM_IDLE, RDMA_CM_ADDR_BOUND)) return -EINVAL; ret = cma_check_linklocal(&id->route.addr.dev_addr, addr); @@ -2360,7 +2350,7 @@ err2: if (id_priv->cma_dev) cma_release_dev(id_priv); err1: - cma_comp_exch(id_priv, CMA_ADDR_BOUND, CMA_IDLE); + cma_comp_exch(id_priv, RDMA_CM_ADDR_BOUND, RDMA_CM_IDLE); return ret; } EXPORT_SYMBOL(rdma_bind_addr); @@ -2433,7 +2423,7 @@ static int cma_sidr_rep_handler(struct ib_cm_id *cm_id, struct ib_cm_sidr_rep_event_param *rep = &ib_event->param.sidr_rep_rcvd; int ret = 0; - if (cma_disable_callback(id_priv, CMA_CONNECT)) + if (cma_disable_callback(id_priv, RDMA_CM_CONNECT)) return 0; memset(&event, 0, sizeof event); @@ -2479,7 +2469,7 @@ static int cma_sidr_rep_handler(struct ib_cm_id *cm_id, if (ret) { /* Destroy the CM ID by returning a non-zero value. */ id_priv->cm_id.ib = NULL; - cma_exch(id_priv, CMA_DESTROYING); + cma_exch(id_priv, RDMA_CM_DESTROYING); mutex_unlock(&id_priv->handler_mutex); rdma_destroy_id(&id_priv->id); return ret; @@ -2645,7 +2635,7 @@ int rdma_connect(struct rdma_cm_id *id, struct rdma_conn_param *conn_param) int ret; id_priv = container_of(id, struct rdma_id_private, id); - if (!cma_comp_exch(id_priv, CMA_ROUTE_RESOLVED, CMA_CONNECT)) + if (!cma_comp_exch(id_priv, RDMA_CM_ROUTE_RESOLVED, RDMA_CM_CONNECT)) return -EINVAL; if (!id->qp) { @@ -2655,7 +2645,7 @@ int rdma_connect(struct rdma_cm_id *id, struct rdma_conn_param *conn_param) switch (rdma_node_get_transport(id->device->node_type)) { case RDMA_TRANSPORT_IB: - if (cma_is_ud_ps(id->ps)) + if (id->qp_type == IB_QPT_UD) ret = cma_resolve_ib_udp(id_priv, conn_param); else ret = cma_connect_ib(id_priv, conn_param); @@ -2672,7 +2662,7 @@ int rdma_connect(struct rdma_cm_id *id, struct rdma_conn_param *conn_param) return 0; err: - cma_comp_exch(id_priv, CMA_CONNECT, CMA_ROUTE_RESOLVED); + cma_comp_exch(id_priv, RDMA_CM_CONNECT, RDMA_CM_ROUTE_RESOLVED); return ret; } EXPORT_SYMBOL(rdma_connect); @@ -2758,7 +2748,10 @@ int rdma_accept(struct rdma_cm_id *id, struct rdma_conn_param *conn_param) int ret; id_priv = container_of(id, struct rdma_id_private, id); - if (!cma_comp(id_priv, CMA_CONNECT)) + + id_priv->owner = task_pid_nr(current); + + if (!cma_comp(id_priv, RDMA_CM_CONNECT)) return -EINVAL; if (!id->qp && conn_param) { @@ -2768,7 +2761,7 @@ int rdma_accept(struct rdma_cm_id *id, struct rdma_conn_param *conn_param) switch (rdma_node_get_transport(id->device->node_type)) { case RDMA_TRANSPORT_IB: - if (cma_is_ud_ps(id->ps)) + if (id->qp_type == IB_QPT_UD) ret = cma_send_sidr_rep(id_priv, IB_SIDR_SUCCESS, conn_param->private_data, conn_param->private_data_len); @@ -2829,7 +2822,7 @@ int rdma_reject(struct rdma_cm_id *id, const void *private_data, switch (rdma_node_get_transport(id->device->node_type)) { case RDMA_TRANSPORT_IB: - if (cma_is_ud_ps(id->ps)) + if (id->qp_type == IB_QPT_UD) ret = cma_send_sidr_rep(id_priv, IB_SIDR_REJECT, private_data, private_data_len); else @@ -2887,8 +2880,8 @@ static int cma_ib_mc_handler(int status, struct ib_sa_multicast *multicast) int ret; id_priv = mc->id_priv; - if (cma_disable_callback(id_priv, CMA_ADDR_BOUND) && - cma_disable_callback(id_priv, CMA_ADDR_RESOLVED)) + if (cma_disable_callback(id_priv, RDMA_CM_ADDR_BOUND) && + cma_disable_callback(id_priv, RDMA_CM_ADDR_RESOLVED)) return 0; mutex_lock(&id_priv->qp_mutex); @@ -2912,7 +2905,7 @@ static int cma_ib_mc_handler(int status, struct ib_sa_multicast *multicast) ret = id_priv->id.event_handler(&id_priv->id, &event); if (ret) { - cma_exch(id_priv, CMA_DESTROYING); + cma_exch(id_priv, RDMA_CM_DESTROYING); mutex_unlock(&id_priv->handler_mutex); rdma_destroy_id(&id_priv->id); return 0; @@ -3095,8 +3088,8 @@ int rdma_join_multicast(struct rdma_cm_id *id, struct sockaddr *addr, int ret; id_priv = container_of(id, struct rdma_id_private, id); - if (!cma_comp(id_priv, CMA_ADDR_BOUND) && - !cma_comp(id_priv, CMA_ADDR_RESOLVED)) + if (!cma_comp(id_priv, RDMA_CM_ADDR_BOUND) && + !cma_comp(id_priv, RDMA_CM_ADDR_RESOLVED)) return -EINVAL; mc = kmalloc(sizeof *mc, GFP_KERNEL); @@ -3261,19 +3254,19 @@ static void cma_add_one(struct ib_device *device) static int cma_remove_id_dev(struct rdma_id_private *id_priv) { struct rdma_cm_event event; - enum cma_state state; + enum rdma_cm_state state; int ret = 0; /* Record that we want to remove the device */ - state = cma_exch(id_priv, CMA_DEVICE_REMOVAL); - if (state == CMA_DESTROYING) + state = cma_exch(id_priv, RDMA_CM_DEVICE_REMOVAL); + if (state == RDMA_CM_DESTROYING) return 0; cma_cancel_operation(id_priv, state); mutex_lock(&id_priv->handler_mutex); /* Check for destruction from another callback. */ - if (!cma_comp(id_priv, CMA_DEVICE_REMOVAL)) + if (!cma_comp(id_priv, RDMA_CM_DEVICE_REMOVAL)) goto out; memset(&event, 0, sizeof event); @@ -3328,6 +3321,100 @@ static void cma_remove_one(struct ib_device *device) kfree(cma_dev); } +static int cma_get_id_stats(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct nlmsghdr *nlh; + struct rdma_cm_id_stats *id_stats; + struct rdma_id_private *id_priv; + struct rdma_cm_id *id = NULL; + struct cma_device *cma_dev; + int i_dev = 0, i_id = 0; + + /* + * We export all of the IDs as a sequence of messages. Each + * ID gets its own netlink message. + */ + mutex_lock(&lock); + + list_for_each_entry(cma_dev, &dev_list, list) { + if (i_dev < cb->args[0]) { + i_dev++; + continue; + } + + i_id = 0; + list_for_each_entry(id_priv, &cma_dev->id_list, list) { + if (i_id < cb->args[1]) { + i_id++; + continue; + } + + id_stats = ibnl_put_msg(skb, &nlh, cb->nlh->nlmsg_seq, + sizeof *id_stats, RDMA_NL_RDMA_CM, + RDMA_NL_RDMA_CM_ID_STATS); + if (!id_stats) + goto out; + + memset(id_stats, 0, sizeof *id_stats); + id = &id_priv->id; + id_stats->node_type = id->route.addr.dev_addr.dev_type; + id_stats->port_num = id->port_num; + id_stats->bound_dev_if = + id->route.addr.dev_addr.bound_dev_if; + + if (id->route.addr.src_addr.ss_family == AF_INET) { + if (ibnl_put_attr(skb, nlh, + sizeof(struct sockaddr_in), + &id->route.addr.src_addr, + RDMA_NL_RDMA_CM_ATTR_SRC_ADDR)) { + goto out; + } + if (ibnl_put_attr(skb, nlh, + sizeof(struct sockaddr_in), + &id->route.addr.dst_addr, + RDMA_NL_RDMA_CM_ATTR_DST_ADDR)) { + goto out; + } + } else if (id->route.addr.src_addr.ss_family == AF_INET6) { + if (ibnl_put_attr(skb, nlh, + sizeof(struct sockaddr_in6), + &id->route.addr.src_addr, + RDMA_NL_RDMA_CM_ATTR_SRC_ADDR)) { + goto out; + } + if (ibnl_put_attr(skb, nlh, + sizeof(struct sockaddr_in6), + &id->route.addr.dst_addr, + RDMA_NL_RDMA_CM_ATTR_DST_ADDR)) { + goto out; + } + } + + id_stats->pid = id_priv->owner; + id_stats->port_space = id->ps; + id_stats->cm_state = id_priv->state; + id_stats->qp_num = id_priv->qp_num; + id_stats->qp_type = id->qp_type; + + i_id++; + } + + cb->args[1] = 0; + i_dev++; + } + +out: + mutex_unlock(&lock); + cb->args[0] = i_dev; + cb->args[1] = i_id; + + return skb->len; +} + +static const struct ibnl_client_cbs cma_cb_table[] = { + [RDMA_NL_RDMA_CM_ID_STATS] = { .dump = cma_get_id_stats }, +}; + static int __init cma_init(void) { int ret; @@ -3343,6 +3430,10 @@ static int __init cma_init(void) ret = ib_register_client(&cma_client); if (ret) goto err; + + if (ibnl_add_client(RDMA_NL_RDMA_CM, RDMA_NL_RDMA_CM_NUM_OPS, cma_cb_table)) + printk(KERN_WARNING "RDMA CMA: failed to add netlink callback\n"); + return 0; err: @@ -3355,6 +3446,7 @@ err: static void __exit cma_cleanup(void) { + ibnl_remove_client(RDMA_NL_RDMA_CM); ib_unregister_client(&cma_client); unregister_netdevice_notifier(&cma_nb); rdma_addr_unregister_client(&addr_client); diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c index f793bf2f5da..4007f721d25 100644 --- a/drivers/infiniband/core/device.c +++ b/drivers/infiniband/core/device.c @@ -38,6 +38,7 @@ #include <linux/slab.h> #include <linux/init.h> #include <linux/mutex.h> +#include <rdma/rdma_netlink.h> #include "core_priv.h" @@ -725,22 +726,40 @@ static int __init ib_core_init(void) return -ENOMEM; ret = ib_sysfs_setup(); - if (ret) + if (ret) { printk(KERN_WARNING "Couldn't create InfiniBand device class\n"); + goto err; + } + + ret = ibnl_init(); + if (ret) { + printk(KERN_WARNING "Couldn't init IB netlink interface\n"); + goto err_sysfs; + } ret = ib_cache_setup(); if (ret) { printk(KERN_WARNING "Couldn't set up InfiniBand P_Key/GID cache\n"); - ib_sysfs_cleanup(); - destroy_workqueue(ib_wq); + goto err_nl; } + return 0; + +err_nl: + ibnl_cleanup(); + +err_sysfs: + ib_sysfs_cleanup(); + +err: + destroy_workqueue(ib_wq); return ret; } static void __exit ib_core_cleanup(void) { ib_cache_cleanup(); + ibnl_cleanup(); ib_sysfs_cleanup(); /* Make sure that any pending umem accounting work is done. */ destroy_workqueue(ib_wq); diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index 822cfdcd9f7..b4d8672a3e4 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c @@ -276,6 +276,13 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, goto error1; } + /* Verify the QP requested is supported. For example, Ethernet devices + * will not have QP0 */ + if (!port_priv->qp_info[qpn].qp) { + ret = ERR_PTR(-EPROTONOSUPPORT); + goto error1; + } + /* Allocate structures */ mad_agent_priv = kzalloc(sizeof *mad_agent_priv, GFP_KERNEL); if (!mad_agent_priv) { diff --git a/drivers/infiniband/core/netlink.c b/drivers/infiniband/core/netlink.c new file mode 100644 index 00000000000..4a5abaf0a25 --- /dev/null +++ b/drivers/infiniband/core/netlink.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2010 Voltaire Inc. All rights reserved. + * + * This software is available to you under a choice of one of two + * licenses. You may choose to be licensed under the terms of the GNU + * General Public License (GPL) Version 2, available from the file + * COPYING in the main directory of this source tree, or the + * OpenIB.org BSD license below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ + +#include <net/netlink.h> +#include <net/net_namespace.h> +#include <net/sock.h> +#include <rdma/rdma_netlink.h> + +struct ibnl_client { + struct list_head list; + int index; + int nops; + const struct ibnl_client_cbs *cb_table; +}; + +static DEFINE_MUTEX(ibnl_mutex); +static struct sock *nls; +static LIST_HEAD(client_list); + +int ibnl_add_client(int index, int nops, + const struct ibnl_client_cbs cb_table[]) +{ + struct ibnl_client *cur; + struct ibnl_client *nl_client; + + nl_client = kmalloc(sizeof *nl_client, GFP_KERNEL); + if (!nl_client) + return -ENOMEM; + + nl_client->index = index; + nl_client->nops = nops; + nl_client->cb_table = cb_table; + + mutex_lock(&ibnl_mutex); + + list_for_each_entry(cur, &client_list, list) { + if (cur->index == index) { + pr_warn("Client for %d already exists\n", index); + mutex_unlock(&ibnl_mutex); + kfree(nl_client); + return -EINVAL; + } + } + + list_add_tail(&nl_client->list, &client_list); + + mutex_unlock(&ibnl_mutex); + + return 0; +} +EXPORT_SYMBOL(ibnl_add_client); + +int ibnl_remove_client(int index) +{ + struct ibnl_client *cur, *next; + + mutex_lock(&ibnl_mutex); + list_for_each_entry_safe(cur, next, &client_list, list) { + if (cur->index == index) { + list_del(&(cur->list)); + mutex_unlock(&ibnl_mutex); + kfree(cur); + return 0; + } + } + pr_warn("Can't remove callback for client idx %d. Not found\n", index); + mutex_unlock(&ibnl_mutex); + + return -EINVAL; +} +EXPORT_SYMBOL(ibnl_remove_client); + +void *ibnl_put_msg(struct sk_buff *skb, struct nlmsghdr **nlh, int seq, + int len, int client, int op) +{ + unsigned char *prev_tail; + + prev_tail = skb_tail_pointer(skb); + *nlh = NLMSG_NEW(skb, 0, seq, RDMA_NL_GET_TYPE(client, op), + len, NLM_F_MULTI); + (*nlh)->nlmsg_len = skb_tail_pointer(skb) - prev_tail; + return NLMSG_DATA(*nlh); + +nlmsg_failure: + nlmsg_trim(skb, prev_tail); + return NULL; +} +EXPORT_SYMBOL(ibnl_put_msg); + +int ibnl_put_attr(struct sk_buff *skb, struct nlmsghdr *nlh, + int len, void *data, int type) +{ + unsigned char *prev_tail; + + prev_tail = skb_tail_pointer(skb); + NLA_PUT(skb, type, len, data); + nlh->nlmsg_len += skb_tail_pointer(skb) - prev_tail; + return 0; + +nla_put_failure: + nlmsg_trim(skb, prev_tail - nlh->nlmsg_len); + return -EMSGSIZE; +} +EXPORT_SYMBOL(ibnl_put_attr); + +static int ibnl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct ibnl_client *client; + int type = nlh->nlmsg_type; + int index = RDMA_NL_GET_CLIENT(type); + int op = RDMA_NL_GET_OP(type); + + list_for_each_entry(client, &client_list, list) { + if (client->index == index) { + if (op < 0 || op >= client->nops || + !client->cb_table[RDMA_NL_GET_OP(op)].dump) + return -EINVAL; + return netlink_dump_start(nls, skb, nlh, + client->cb_table[op].dump, + NULL); + } + } + + pr_info("Index %d wasn't found in client list\n", index); + return -EINVAL; +} + +static void ibnl_rcv(struct sk_buff *skb) +{ + mutex_lock(&ibnl_mutex); + netlink_rcv_skb(skb, &ibnl_rcv_msg); + mutex_unlock(&ibnl_mutex); +} + +int __init ibnl_init(void) +{ + nls = netlink_kernel_create(&init_net, NETLINK_RDMA, 0, ibnl_rcv, + NULL, THIS_MODULE); + if (!nls) { + pr_warn("Failed to create netlink socket\n"); + return -ENOMEM; + } + + return 0; +} + +void ibnl_cleanup(void) +{ + struct ibnl_client *cur, *next; + + mutex_lock(&ibnl_mutex); + list_for_each_entry_safe(cur, next, &client_list, list) { + list_del(&(cur->list)); + kfree(cur); + } + mutex_unlock(&ibnl_mutex); + + netlink_kernel_release(nls); +} diff --git a/drivers/infiniband/core/ucma.c b/drivers/infiniband/core/ucma.c index b3fa798525b..71be5eebd68 100644 --- a/drivers/infiniband/core/ucma.c +++ b/drivers/infiniband/core/ucma.c @@ -367,13 +367,28 @@ done: return ret; } -static ssize_t ucma_create_id(struct ucma_file *file, - const char __user *inbuf, - int in_len, int out_len) +static int ucma_get_qp_type(struct rdma_ucm_create_id *cmd, enum ib_qp_type *qp_type) +{ + switch (cmd->ps) { + case RDMA_PS_TCP: + *qp_type = IB_QPT_RC; + return 0; + case RDMA_PS_UDP: + case RDMA_PS_IPOIB: + *qp_type = IB_QPT_UD; + return 0; + default: + return -EINVAL; + } +} + +static ssize_t ucma_create_id(struct ucma_file *file, const char __user *inbuf, + int in_len, int out_len) { struct rdma_ucm_create_id cmd; struct rdma_ucm_create_id_resp resp; struct ucma_context *ctx; + enum ib_qp_type qp_type; int ret; if (out_len < sizeof(resp)) @@ -382,6 +397,10 @@ static ssize_t ucma_create_id(struct ucma_file *file, if (copy_from_user(&cmd, inbuf, sizeof(cmd))) return -EFAULT; + ret = ucma_get_qp_type(&cmd, &qp_type); + if (ret) + return ret; + mutex_lock(&file->mut); ctx = ucma_alloc_ctx(file); mutex_unlock(&file->mut); @@ -389,7 +408,7 @@ static ssize_t ucma_create_id(struct ucma_file *file, return -ENOMEM; ctx->uid = cmd.uid; - ctx->cm_id = rdma_create_id(ucma_event_handler, ctx, cmd.ps); + ctx->cm_id = rdma_create_id(ucma_event_handler, ctx, cmd.ps, qp_type); if (IS_ERR(ctx->cm_id)) { ret = PTR_ERR(ctx->cm_id); goto err1; @@ -1338,9 +1357,11 @@ static const struct file_operations ucma_fops = { }; static struct miscdevice ucma_misc = { - .minor = MISC_DYNAMIC_MINOR, - .name = "rdma_cm", - .fops = &ucma_fops, + .minor = MISC_DYNAMIC_MINOR, + .name = "rdma_cm", + .nodename = "infiniband/rdma_cm", + .mode = 0666, + .fops = &ucma_fops, }; static ssize_t show_abi_version(struct device *dev, diff --git a/drivers/infiniband/core/user_mad.c b/drivers/infiniband/core/user_mad.c index cd1996d0ad0..8d261b6ea5f 100644 --- a/drivers/infiniband/core/user_mad.c +++ b/drivers/infiniband/core/user_mad.c @@ -1176,6 +1176,11 @@ static void ib_umad_remove_one(struct ib_device *device) kref_put(&umad_dev->ref, ib_umad_release_dev); } +static char *umad_devnode(struct device *dev, mode_t *mode) +{ + return kasprintf(GFP_KERNEL, "infiniband/%s", dev_name(dev)); +} + static int __init ib_umad_init(void) { int ret; @@ -1194,6 +1199,8 @@ static int __init ib_umad_init(void) goto out_chrdev; } + umad_class->devnode = umad_devnode; + ret = class_create_file(umad_class, &class_attr_abi_version.attr); if (ret) { printk(KERN_ERR "user_mad: couldn't create abi_version attribute\n"); diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c index ec83e9fe387..e49a85f8a44 100644 --- a/drivers/infiniband/core/uverbs_main.c +++ b/drivers/infiniband/core/uverbs_main.c @@ -824,6 +824,12 @@ static void ib_uverbs_remove_one(struct ib_device *device) kfree(uverbs_dev); } +static char *uverbs_devnode(struct device *dev, mode_t *mode) +{ + *mode = 0666; + return kasprintf(GFP_KERNEL, "infiniband/%s", dev_name(dev)); +} + static int __init ib_uverbs_init(void) { int ret; @@ -842,6 +848,8 @@ static int __init ib_uverbs_init(void) goto out_chrdev; } + uverbs_class->devnode = uverbs_devnode; + ret = class_create_file(uverbs_class, &class_attr_abi_version.attr); if (ret) { printk(KERN_ERR "user_verbs: couldn't create abi_version attribute\n"); diff --git a/drivers/infiniband/hw/cxgb3/iwch_cm.c b/drivers/infiniband/hw/cxgb3/iwch_cm.c index 23918413899..0a5008fbeba 100644 --- a/drivers/infiniband/hw/cxgb3/iwch_cm.c +++ b/drivers/infiniband/hw/cxgb3/iwch_cm.c @@ -914,7 +914,7 @@ static void process_mpa_reply(struct iwch_ep *ep, struct sk_buff *skb) goto err; if (peer2peer && iwch_rqes_posted(ep->com.qp) == 0) { - iwch_post_zb_read(ep->com.qp); + iwch_post_zb_read(ep); } goto out; @@ -1078,6 +1078,8 @@ static int tx_ack(struct t3cdev *tdev, struct sk_buff *skb, void *ctx) struct iwch_ep *ep = ctx; struct cpl_wr_ack *hdr = cplhdr(skb); unsigned int credits = ntohs(hdr->credits); + unsigned long flags; + int post_zb = 0; PDBG("%s ep %p credits %u\n", __func__, ep, credits); @@ -1087,28 +1089,34 @@ static int tx_ack(struct t3cdev *tdev, struct sk_buff *skb, void *ctx) return CPL_RET_BUF_DONE; } + spin_lock_irqsave(&ep->com.lock, flags); BUG_ON(credits != 1); dst_confirm(ep->dst); if (!ep->mpa_skb) { PDBG("%s rdma_init wr_ack ep %p state %u\n", - __func__, ep, state_read(&ep->com)); + __func__, ep, ep->com.state); if (ep->mpa_attr.initiator) { PDBG("%s initiator ep %p state %u\n", - __func__, ep, state_read(&ep->com)); - if (peer2peer) - iwch_post_zb_read(ep->com.qp); + __func__, ep, ep->com.state); + if (peer2peer && ep->com.state == FPDU_MODE) + post_zb = 1; } else { PDBG("%s responder ep %p state %u\n", - __func__, ep, state_read(&ep->com)); - ep->com.rpl_done = 1; - wake_up(&ep->com.waitq); + __func__, ep, ep->com.state); + if (ep->com.state == MPA_REQ_RCVD) { + ep->com.rpl_done = 1; + wake_up(&ep->com.waitq); + } } } else { PDBG("%s lsm ack ep %p state %u freeing skb\n", - __func__, ep, state_read(&ep->com)); + __func__, ep, ep->com.state); kfree_skb(ep->mpa_skb); ep->mpa_skb = NULL; } + spin_unlock_irqrestore(&ep->com.lock, flags); + if (post_zb) + iwch_post_zb_read(ep); return CPL_RET_BUF_DONE; } diff --git a/drivers/infiniband/hw/cxgb3/iwch_provider.h b/drivers/infiniband/hw/cxgb3/iwch_provider.h index c5406da3f4c..9a342c9b220 100644 --- a/drivers/infiniband/hw/cxgb3/iwch_provider.h +++ b/drivers/infiniband/hw/cxgb3/iwch_provider.h @@ -332,7 +332,7 @@ int iwch_bind_mw(struct ib_qp *qp, struct ib_mw_bind *mw_bind); int iwch_poll_cq(struct ib_cq *ibcq, int num_entries, struct ib_wc *wc); int iwch_post_terminate(struct iwch_qp *qhp, struct respQ_msg_t *rsp_msg); -int iwch_post_zb_read(struct iwch_qp *qhp); +int iwch_post_zb_read(struct iwch_ep *ep); int iwch_register_device(struct iwch_dev *dev); void iwch_unregister_device(struct iwch_dev *dev); void stop_read_rep_timer(struct iwch_qp *qhp); diff --git a/drivers/infiniband/hw/cxgb3/iwch_qp.c b/drivers/infiniband/hw/cxgb3/iwch_qp.c index 1b4cd09f74d..ecd313f359a 100644 --- a/drivers/infiniband/hw/cxgb3/iwch_qp.c +++ b/drivers/infiniband/hw/cxgb3/iwch_qp.c @@ -738,7 +738,7 @@ static inline void build_term_codes(struct respQ_msg_t *rsp_msg, } } -int iwch_post_zb_read(struct iwch_qp *qhp) +int iwch_post_zb_read(struct iwch_ep *ep) { union t3_wr *wqe; struct sk_buff *skb; @@ -761,10 +761,10 @@ int iwch_post_zb_read(struct iwch_qp *qhp) wqe->read.local_len = cpu_to_be32(0); wqe->read.local_to = cpu_to_be64(1); wqe->send.wrh.op_seop_flags = cpu_to_be32(V_FW_RIWR_OP(T3_WR_READ)); - wqe->send.wrh.gen_tid_len = cpu_to_be32(V_FW_RIWR_TID(qhp->ep->hwtid)| + wqe->send.wrh.gen_tid_len = cpu_to_be32(V_FW_RIWR_TID(ep->hwtid)| V_FW_RIWR_LEN(flit_cnt)); skb->priority = CPL_PRIORITY_DATA; - return iwch_cxgb3_ofld_send(qhp->rhp->rdev.t3cdev_p, skb); + return iwch_cxgb3_ofld_send(ep->com.qp->rhp->rdev.t3cdev_p, skb); } /* diff --git a/drivers/infiniband/hw/cxgb4/iw_cxgb4.h b/drivers/infiniband/hw/cxgb4/iw_cxgb4.h index 35d2a5dd9bb..4f045375c8e 100644 --- a/drivers/infiniband/hw/cxgb4/iw_cxgb4.h +++ b/drivers/infiniband/hw/cxgb4/iw_cxgb4.h @@ -35,7 +35,7 @@ #include <linux/list.h> #include <linux/spinlock.h> #include <linux/idr.h> -#include <linux/workqueue.h> +#include <linux/completion.h> #include <linux/netdevice.h> #include <linux/sched.h> #include <linux/pci.h> @@ -131,28 +131,21 @@ static inline int c4iw_num_stags(struct c4iw_rdev *rdev) #define C4IW_WR_TO (10*HZ) -enum { - REPLY_READY = 0, -}; - struct c4iw_wr_wait { - wait_queue_head_t wait; - unsigned long status; + struct completion completion; int ret; }; static inline void c4iw_init_wr_wait(struct c4iw_wr_wait *wr_waitp) { wr_waitp->ret = 0; - wr_waitp->status = 0; - init_waitqueue_head(&wr_waitp->wait); + init_completion(&wr_waitp->completion); } static inline void c4iw_wake_up(struct c4iw_wr_wait *wr_waitp, int ret) { wr_waitp->ret = ret; - set_bit(REPLY_READY, &wr_waitp->status); - wake_up(&wr_waitp->wait); + complete(&wr_waitp->completion); } static inline int c4iw_wait_for_reply(struct c4iw_rdev *rdev, @@ -164,8 +157,7 @@ static inline int c4iw_wait_for_reply(struct c4iw_rdev *rdev, int ret; do { - ret = wait_event_timeout(wr_waitp->wait, - test_and_clear_bit(REPLY_READY, &wr_waitp->status), to); + ret = wait_for_completion_timeout(&wr_waitp->completion, to); if (!ret) { printk(KERN_ERR MOD "%s - Device %s not responding - " "tid %u qpid %u\n", func, diff --git a/drivers/infiniband/hw/nes/nes.c b/drivers/infiniband/hw/nes/nes.c index 13de1192927..2d668c69f6d 100644 --- a/drivers/infiniband/hw/nes/nes.c +++ b/drivers/infiniband/hw/nes/nes.c @@ -1138,7 +1138,9 @@ static ssize_t nes_store_wqm_quanta(struct device_driver *ddp, u32 i = 0; struct nes_device *nesdev; - strict_strtoul(buf, 0, &wqm_quanta_value); + if (kstrtoul(buf, 0, &wqm_quanta_value) < 0) + return -EINVAL; + list_for_each_entry(nesdev, &nes_dev_list, list) { if (i == ee_flsh_adapter) { nesdev->nesadapter->wqm_quanta = wqm_quanta_value; diff --git a/drivers/infiniband/hw/qib/Kconfig b/drivers/infiniband/hw/qib/Kconfig index 7c03a70c55a..8349f9c5064 100644 --- a/drivers/infiniband/hw/qib/Kconfig +++ b/drivers/infiniband/hw/qib/Kconfig @@ -1,6 +1,6 @@ config INFINIBAND_QIB tristate "QLogic PCIe HCA support" - depends on 64BIT && NET + depends on 64BIT ---help--- This is a low-level driver for QLogic PCIe QLE InfiniBand host channel adapters. This driver does not support the QLogic diff --git a/drivers/infiniband/ulp/iser/iser_verbs.c b/drivers/infiniband/ulp/iser/iser_verbs.c index 9876865732f..ede1475bee0 100644 --- a/drivers/infiniband/ulp/iser/iser_verbs.c +++ b/drivers/infiniband/ulp/iser/iser_verbs.c @@ -548,7 +548,7 @@ int iser_connect(struct iser_conn *ib_conn, iser_conn_get(ib_conn); /* ref ib conn's cma id */ ib_conn->cma_id = rdma_create_id(iser_cma_handler, (void *)ib_conn, - RDMA_PS_TCP); + RDMA_PS_TCP, IB_QPT_RC); if (IS_ERR(ib_conn->cma_id)) { err = PTR_ERR(ib_conn->cma_id); iser_err("rdma_create_id failed: %d\n", err); diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c index 376d640487d..ee165fdcb59 100644 --- a/drivers/infiniband/ulp/srp/ib_srp.c +++ b/drivers/infiniband/ulp/srp/ib_srp.c @@ -1147,7 +1147,7 @@ static void srp_process_aer_req(struct srp_target_port *target, static void srp_handle_recv(struct srp_target_port *target, struct ib_wc *wc) { struct ib_device *dev = target->srp_host->srp_dev->dev; - struct srp_iu *iu = (struct srp_iu *) wc->wr_id; + struct srp_iu *iu = (struct srp_iu *) (uintptr_t) wc->wr_id; int res; u8 opcode; @@ -1231,7 +1231,7 @@ static void srp_send_completion(struct ib_cq *cq, void *target_ptr) break; } - iu = (struct srp_iu *) wc.wr_id; + iu = (struct srp_iu *) (uintptr_t) wc.wr_id; list_add(&iu->list, &target->free_tx); } } diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 69badb4e06a..b4dee9d5a05 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -412,6 +412,17 @@ config KEYBOARD_PXA930_ROTARY To compile this driver as a module, choose M here: the module will be called pxa930_rotary. +config KEYBOARD_PMIC8XXX + tristate "Qualcomm PMIC8XXX keypad support" + depends on MFD_PM8XXX + help + Say Y here if you want to enable the driver for the PMIC8XXX + keypad provided as a reference design from Qualcomm. This is intended + to support upto 18x8 matrix based keypad design. + + To compile this driver as a module, choose M here: the module will + be called pmic8xxx-keypad. + config KEYBOARD_SAMSUNG tristate "Samsung keypad support" depends on SAMSUNG_DEV_KEYPAD diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index c49cf8e04cd..ddde0fd476f 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_KEYBOARD_NOMADIK) += nomadik-ske-keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_OMAP4) += omap4-keypad.o obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o +obj-$(CONFIG_KEYBOARD_PMIC8XXX) += pmic8xxx-keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o obj-$(CONFIG_KEYBOARD_QT1070) += qt1070.o diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c new file mode 100644 index 00000000000..40b02ae96f8 --- /dev/null +++ b/drivers/input/keyboard/pmic8xxx-keypad.c @@ -0,0 +1,799 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/mutex.h> + +#include <linux/mfd/pm8xxx/core.h> +#include <linux/mfd/pm8xxx/gpio.h> +#include <linux/input/pmic8xxx-keypad.h> + +#define PM8XXX_MAX_ROWS 18 +#define PM8XXX_MAX_COLS 8 +#define PM8XXX_ROW_SHIFT 3 +#define PM8XXX_MATRIX_MAX_SIZE (PM8XXX_MAX_ROWS * PM8XXX_MAX_COLS) + +#define PM8XXX_MIN_ROWS 5 +#define PM8XXX_MIN_COLS 5 + +#define MAX_SCAN_DELAY 128 +#define MIN_SCAN_DELAY 1 + +/* in nanoseconds */ +#define MAX_ROW_HOLD_DELAY 122000 +#define MIN_ROW_HOLD_DELAY 30500 + +#define MAX_DEBOUNCE_TIME 20 +#define MIN_DEBOUNCE_TIME 5 + +#define KEYP_CTRL 0x148 + +#define KEYP_CTRL_EVNTS BIT(0) +#define KEYP_CTRL_EVNTS_MASK 0x3 + +#define KEYP_CTRL_SCAN_COLS_SHIFT 5 +#define KEYP_CTRL_SCAN_COLS_MIN 5 +#define KEYP_CTRL_SCAN_COLS_BITS 0x3 + +#define KEYP_CTRL_SCAN_ROWS_SHIFT 2 +#define KEYP_CTRL_SCAN_ROWS_MIN 5 +#define KEYP_CTRL_SCAN_ROWS_BITS 0x7 + +#define KEYP_CTRL_KEYP_EN BIT(7) + +#define KEYP_SCAN 0x149 + +#define KEYP_SCAN_READ_STATE BIT(0) +#define KEYP_SCAN_DBOUNCE_SHIFT 1 +#define KEYP_SCAN_PAUSE_SHIFT 3 +#define KEYP_SCAN_ROW_HOLD_SHIFT 6 + +#define KEYP_TEST 0x14A + +#define KEYP_TEST_CLEAR_RECENT_SCAN BIT(6) +#define KEYP_TEST_CLEAR_OLD_SCAN BIT(5) +#define KEYP_TEST_READ_RESET BIT(4) +#define KEYP_TEST_DTEST_EN BIT(3) +#define KEYP_TEST_ABORT_READ BIT(0) + +#define KEYP_TEST_DBG_SELECT_SHIFT 1 + +/* bits of these registers represent + * '0' for key press + * '1' for key release + */ +#define KEYP_RECENT_DATA 0x14B +#define KEYP_OLD_DATA 0x14C + +#define KEYP_CLOCK_FREQ 32768 + +/** + * struct pmic8xxx_kp - internal keypad data structure + * @pdata - keypad platform data pointer + * @input - input device pointer for keypad + * @key_sense_irq - key press/release irq number + * @key_stuck_irq - key stuck notification irq number + * @keycodes - array to hold the key codes + * @dev - parent device pointer + * @keystate - present key press/release state + * @stuckstate - present state when key stuck irq + * @ctrl_reg - control register value + */ +struct pmic8xxx_kp { + const struct pm8xxx_keypad_platform_data *pdata; + struct input_dev *input; + int key_sense_irq; + int key_stuck_irq; + + unsigned short keycodes[PM8XXX_MATRIX_MAX_SIZE]; + + struct device *dev; + u16 keystate[PM8XXX_MAX_ROWS]; + u16 stuckstate[PM8XXX_MAX_ROWS]; + + u8 ctrl_reg; +}; + +static int pmic8xxx_kp_write_u8(struct pmic8xxx_kp *kp, + u8 data, u16 reg) +{ + int rc; + + rc = pm8xxx_writeb(kp->dev->parent, reg, data); + return rc; +} + +static int pmic8xxx_kp_read(struct pmic8xxx_kp *kp, + u8 *data, u16 reg, unsigned num_bytes) +{ + int rc; + + rc = pm8xxx_read_buf(kp->dev->parent, reg, data, num_bytes); + return rc; +} + +static int pmic8xxx_kp_read_u8(struct pmic8xxx_kp *kp, + u8 *data, u16 reg) +{ + int rc; + + rc = pmic8xxx_kp_read(kp, data, reg, 1); + return rc; +} + +static u8 pmic8xxx_col_state(struct pmic8xxx_kp *kp, u8 col) +{ + /* all keys pressed on that particular row? */ + if (col == 0x00) + return 1 << kp->pdata->num_cols; + else + return col & ((1 << kp->pdata->num_cols) - 1); +} + +/* + * Synchronous read protocol for RevB0 onwards: + * + * 1. Write '1' to ReadState bit in KEYP_SCAN register + * 2. Wait 2*32KHz clocks, so that HW can successfully enter read mode + * synchronously + * 3. Read rows in old array first if events are more than one + * 4. Read rows in recent array + * 5. Wait 4*32KHz clocks + * 6. Write '0' to ReadState bit of KEYP_SCAN register so that hw can + * synchronously exit read mode. + */ +static int pmic8xxx_chk_sync_read(struct pmic8xxx_kp *kp) +{ + int rc; + u8 scan_val; + + rc = pmic8xxx_kp_read_u8(kp, &scan_val, KEYP_SCAN); + if (rc < 0) { + dev_err(kp->dev, "Error reading KEYP_SCAN reg, rc=%d\n", rc); + return rc; + } + + scan_val |= 0x1; + + rc = pmic8xxx_kp_write_u8(kp, scan_val, KEYP_SCAN); + if (rc < 0) { + dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc); + return rc; + } + + /* 2 * 32KHz clocks */ + udelay((2 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1); + + return rc; +} + +static int pmic8xxx_kp_read_data(struct pmic8xxx_kp *kp, u16 *state, + u16 data_reg, int read_rows) +{ + int rc, row; + u8 new_data[PM8XXX_MAX_ROWS]; + + rc = pmic8xxx_kp_read(kp, new_data, data_reg, read_rows); + if (rc) + return rc; + + for (row = 0; row < kp->pdata->num_rows; row++) { + dev_dbg(kp->dev, "new_data[%d] = %d\n", row, + new_data[row]); + state[row] = pmic8xxx_col_state(kp, new_data[row]); + } + + return rc; +} + +static int pmic8xxx_kp_read_matrix(struct pmic8xxx_kp *kp, u16 *new_state, + u16 *old_state) +{ + int rc, read_rows; + u8 scan_val; + + if (kp->pdata->num_rows < PM8XXX_MIN_ROWS) + read_rows = PM8XXX_MIN_ROWS; + else + read_rows = kp->pdata->num_rows; + + pmic8xxx_chk_sync_read(kp); + + if (old_state) { + rc = pmic8xxx_kp_read_data(kp, old_state, KEYP_OLD_DATA, + read_rows); + if (rc < 0) { + dev_err(kp->dev, + "Error reading KEYP_OLD_DATA, rc=%d\n", rc); + return rc; + } + } + + rc = pmic8xxx_kp_read_data(kp, new_state, KEYP_RECENT_DATA, + read_rows); + if (rc < 0) { + dev_err(kp->dev, + "Error reading KEYP_RECENT_DATA, rc=%d\n", rc); + return rc; + } + + /* 4 * 32KHz clocks */ + udelay((4 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1); + + rc = pmic8xxx_kp_read_u8(kp, &scan_val, KEYP_SCAN); + if (rc < 0) { + dev_err(kp->dev, "Error reading KEYP_SCAN reg, rc=%d\n", rc); + return rc; + } + + scan_val &= 0xFE; + rc = pmic8xxx_kp_write_u8(kp, scan_val, KEYP_SCAN); + if (rc < 0) + dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc); + + return rc; +} + +static void __pmic8xxx_kp_scan_matrix(struct pmic8xxx_kp *kp, u16 *new_state, + u16 *old_state) +{ + int row, col, code; + + for (row = 0; row < kp->pdata->num_rows; row++) { + int bits_changed = new_state[row] ^ old_state[row]; + + if (!bits_changed) + continue; + + for (col = 0; col < kp->pdata->num_cols; col++) { + if (!(bits_changed & (1 << col))) + continue; + + dev_dbg(kp->dev, "key [%d:%d] %s\n", row, col, + !(new_state[row] & (1 << col)) ? + "pressed" : "released"); + + code = MATRIX_SCAN_CODE(row, col, PM8XXX_ROW_SHIFT); + + input_event(kp->input, EV_MSC, MSC_SCAN, code); + input_report_key(kp->input, + kp->keycodes[code], + !(new_state[row] & (1 << col))); + + input_sync(kp->input); + } + } +} + +static bool pmic8xxx_detect_ghost_keys(struct pmic8xxx_kp *kp, u16 *new_state) +{ + int row, found_first = -1; + u16 check, row_state; + + check = 0; + for (row = 0; row < kp->pdata->num_rows; row++) { + row_state = (~new_state[row]) & + ((1 << kp->pdata->num_cols) - 1); + + if (hweight16(row_state) > 1) { + if (found_first == -1) + found_first = row; + if (check & row_state) { + dev_dbg(kp->dev, "detected ghost key on row[%d]" + " and row[%d]\n", found_first, row); + return true; + } + } + check |= row_state; + } + return false; +} + +static int pmic8xxx_kp_scan_matrix(struct pmic8xxx_kp *kp, unsigned int events) +{ + u16 new_state[PM8XXX_MAX_ROWS]; + u16 old_state[PM8XXX_MAX_ROWS]; + int rc; + + switch (events) { + case 0x1: + rc = pmic8xxx_kp_read_matrix(kp, new_state, NULL); + if (rc < 0) + return rc; + + /* detecting ghost key is not an error */ + if (pmic8xxx_detect_ghost_keys(kp, new_state)) + return 0; + __pmic8xxx_kp_scan_matrix(kp, new_state, kp->keystate); + memcpy(kp->keystate, new_state, sizeof(new_state)); + break; + case 0x3: /* two events - eventcounter is gray-coded */ + rc = pmic8xxx_kp_read_matrix(kp, new_state, old_state); + if (rc < 0) + return rc; + + __pmic8xxx_kp_scan_matrix(kp, old_state, kp->keystate); + __pmic8xxx_kp_scan_matrix(kp, new_state, old_state); + memcpy(kp->keystate, new_state, sizeof(new_state)); + break; + case 0x2: + dev_dbg(kp->dev, "Some key events were lost\n"); + rc = pmic8xxx_kp_read_matrix(kp, new_state, old_state); + if (rc < 0) + return rc; + __pmic8xxx_kp_scan_matrix(kp, old_state, kp->keystate); + __pmic8xxx_kp_scan_matrix(kp, new_state, old_state); + memcpy(kp->keystate, new_state, sizeof(new_state)); + break; + default: + rc = -EINVAL; + } + return rc; +} + +/* + * NOTE: We are reading recent and old data registers blindly + * whenever key-stuck interrupt happens, because events counter doesn't + * get updated when this interrupt happens due to key stuck doesn't get + * considered as key state change. + * + * We are not using old data register contents after they are being read + * because it might report the key which was pressed before the key being stuck + * as stuck key because it's pressed status is stored in the old data + * register. + */ +static irqreturn_t pmic8xxx_kp_stuck_irq(int irq, void *data) +{ + u16 new_state[PM8XXX_MAX_ROWS]; + u16 old_state[PM8XXX_MAX_ROWS]; + int rc; + struct pmic8xxx_kp *kp = data; + + rc = pmic8xxx_kp_read_matrix(kp, new_state, old_state); + if (rc < 0) { + dev_err(kp->dev, "failed to read keypad matrix\n"); + return IRQ_HANDLED; + } + + __pmic8xxx_kp_scan_matrix(kp, new_state, kp->stuckstate); + + return IRQ_HANDLED; +} + +static irqreturn_t pmic8xxx_kp_irq(int irq, void *data) +{ + struct pmic8xxx_kp *kp = data; + u8 ctrl_val, events; + int rc; + + rc = pmic8xxx_kp_read(kp, &ctrl_val, KEYP_CTRL, 1); + if (rc < 0) { + dev_err(kp->dev, "failed to read keyp_ctrl register\n"); + return IRQ_HANDLED; + } + + events = ctrl_val & KEYP_CTRL_EVNTS_MASK; + + rc = pmic8xxx_kp_scan_matrix(kp, events); + if (rc < 0) + dev_err(kp->dev, "failed to scan matrix\n"); + + return IRQ_HANDLED; +} + +static int __devinit pmic8xxx_kpd_init(struct pmic8xxx_kp *kp) +{ + int bits, rc, cycles; + u8 scan_val = 0, ctrl_val = 0; + static const u8 row_bits[] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, + }; + + /* Find column bits */ + if (kp->pdata->num_cols < KEYP_CTRL_SCAN_COLS_MIN) + bits = 0; + else + bits = kp->pdata->num_cols - KEYP_CTRL_SCAN_COLS_MIN; + ctrl_val = (bits & KEYP_CTRL_SCAN_COLS_BITS) << + KEYP_CTRL_SCAN_COLS_SHIFT; + + /* Find row bits */ + if (kp->pdata->num_rows < KEYP_CTRL_SCAN_ROWS_MIN) + bits = 0; + else + bits = row_bits[kp->pdata->num_rows - KEYP_CTRL_SCAN_ROWS_MIN]; + + ctrl_val |= (bits << KEYP_CTRL_SCAN_ROWS_SHIFT); + + rc = pmic8xxx_kp_write_u8(kp, ctrl_val, KEYP_CTRL); + if (rc < 0) { + dev_err(kp->dev, "Error writing KEYP_CTRL reg, rc=%d\n", rc); + return rc; + } + + bits = (kp->pdata->debounce_ms / 5) - 1; + + scan_val |= (bits << KEYP_SCAN_DBOUNCE_SHIFT); + + bits = fls(kp->pdata->scan_delay_ms) - 1; + scan_val |= (bits << KEYP_SCAN_PAUSE_SHIFT); + + /* Row hold time is a multiple of 32KHz cycles. */ + cycles = (kp->pdata->row_hold_ns * KEYP_CLOCK_FREQ) / NSEC_PER_SEC; + + scan_val |= (cycles << KEYP_SCAN_ROW_HOLD_SHIFT); + + rc = pmic8xxx_kp_write_u8(kp, scan_val, KEYP_SCAN); + if (rc) + dev_err(kp->dev, "Error writing KEYP_SCAN reg, rc=%d\n", rc); + + return rc; + +} + +static int __devinit pmic8xxx_kp_config_gpio(int gpio_start, int num_gpios, + struct pmic8xxx_kp *kp, struct pm_gpio *gpio_config) +{ + int rc, i; + + if (gpio_start < 0 || num_gpios < 0) + return -EINVAL; + + for (i = 0; i < num_gpios; i++) { + rc = pm8xxx_gpio_config(gpio_start + i, gpio_config); + if (rc) { + dev_err(kp->dev, "%s: FAIL pm8xxx_gpio_config():" + "for PM GPIO [%d] rc=%d.\n", + __func__, gpio_start + i, rc); + return rc; + } + } + + return 0; +} + +static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp) +{ + int rc; + + kp->ctrl_reg |= KEYP_CTRL_KEYP_EN; + + rc = pmic8xxx_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL); + if (rc < 0) + dev_err(kp->dev, "Error writing KEYP_CTRL reg, rc=%d\n", rc); + + return rc; +} + +static int pmic8xxx_kp_disable(struct pmic8xxx_kp *kp) +{ + int rc; + + kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN; + + rc = pmic8xxx_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL); + if (rc < 0) + return rc; + + return rc; +} + +static int pmic8xxx_kp_open(struct input_dev *dev) +{ + struct pmic8xxx_kp *kp = input_get_drvdata(dev); + + return pmic8xxx_kp_enable(kp); +} + +static void pmic8xxx_kp_close(struct input_dev *dev) +{ + struct pmic8xxx_kp *kp = input_get_drvdata(dev); + + pmic8xxx_kp_disable(kp); +} + +/* + * keypad controller should be initialized in the following sequence + * only, otherwise it might get into FSM stuck state. + * + * - Initialize keypad control parameters, like no. of rows, columns, + * timing values etc., + * - configure rows and column gpios pull up/down. + * - set irq edge type. + * - enable the keypad controller. + */ +static int __devinit pmic8xxx_kp_probe(struct platform_device *pdev) +{ + const struct pm8xxx_keypad_platform_data *pdata = mfd_get_data(pdev); + const struct matrix_keymap_data *keymap_data; + struct pmic8xxx_kp *kp; + int rc; + u8 ctrl_val; + + struct pm_gpio kypd_drv = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_OPEN_DRAIN, + .output_value = 0, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .out_strength = PM_GPIO_STRENGTH_LOW, + .function = PM_GPIO_FUNC_1, + .inv_int_pol = 1, + }; + + struct pm_gpio kypd_sns = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_31P5, + .vin_sel = PM_GPIO_VIN_S3, + .out_strength = PM_GPIO_STRENGTH_NO, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 1, + }; + + + if (!pdata || !pdata->num_cols || !pdata->num_rows || + pdata->num_cols > PM8XXX_MAX_COLS || + pdata->num_rows > PM8XXX_MAX_ROWS || + pdata->num_cols < PM8XXX_MIN_COLS) { + dev_err(&pdev->dev, "invalid platform data\n"); + return -EINVAL; + } + + if (!pdata->scan_delay_ms || + pdata->scan_delay_ms > MAX_SCAN_DELAY || + pdata->scan_delay_ms < MIN_SCAN_DELAY || + !is_power_of_2(pdata->scan_delay_ms)) { + dev_err(&pdev->dev, "invalid keypad scan time supplied\n"); + return -EINVAL; + } + + if (!pdata->row_hold_ns || + pdata->row_hold_ns > MAX_ROW_HOLD_DELAY || + pdata->row_hold_ns < MIN_ROW_HOLD_DELAY || + ((pdata->row_hold_ns % MIN_ROW_HOLD_DELAY) != 0)) { + dev_err(&pdev->dev, "invalid keypad row hold time supplied\n"); + return -EINVAL; + } + + if (!pdata->debounce_ms || + ((pdata->debounce_ms % 5) != 0) || + pdata->debounce_ms > MAX_DEBOUNCE_TIME || + pdata->debounce_ms < MIN_DEBOUNCE_TIME) { + dev_err(&pdev->dev, "invalid debounce time supplied\n"); + return -EINVAL; + } + + keymap_data = pdata->keymap_data; + if (!keymap_data) { + dev_err(&pdev->dev, "no keymap data supplied\n"); + return -EINVAL; + } + + kp = kzalloc(sizeof(*kp), GFP_KERNEL); + if (!kp) + return -ENOMEM; + + platform_set_drvdata(pdev, kp); + + kp->pdata = pdata; + kp->dev = &pdev->dev; + + kp->input = input_allocate_device(); + if (!kp->input) { + dev_err(&pdev->dev, "unable to allocate input device\n"); + rc = -ENOMEM; + goto err_alloc_device; + } + + kp->key_sense_irq = platform_get_irq(pdev, 0); + if (kp->key_sense_irq < 0) { + dev_err(&pdev->dev, "unable to get keypad sense irq\n"); + rc = -ENXIO; + goto err_get_irq; + } + + kp->key_stuck_irq = platform_get_irq(pdev, 1); + if (kp->key_stuck_irq < 0) { + dev_err(&pdev->dev, "unable to get keypad stuck irq\n"); + rc = -ENXIO; + goto err_get_irq; + } + + kp->input->name = pdata->input_name ? : "PMIC8XXX keypad"; + kp->input->phys = pdata->input_phys_device ? : "pmic8xxx_keypad/input0"; + + kp->input->dev.parent = &pdev->dev; + + kp->input->id.bustype = BUS_I2C; + kp->input->id.version = 0x0001; + kp->input->id.product = 0x0001; + kp->input->id.vendor = 0x0001; + + kp->input->evbit[0] = BIT_MASK(EV_KEY); + + if (pdata->rep) + __set_bit(EV_REP, kp->input->evbit); + + kp->input->keycode = kp->keycodes; + kp->input->keycodemax = PM8XXX_MATRIX_MAX_SIZE; + kp->input->keycodesize = sizeof(kp->keycodes); + kp->input->open = pmic8xxx_kp_open; + kp->input->close = pmic8xxx_kp_close; + + matrix_keypad_build_keymap(keymap_data, PM8XXX_ROW_SHIFT, + kp->input->keycode, kp->input->keybit); + + input_set_capability(kp->input, EV_MSC, MSC_SCAN); + input_set_drvdata(kp->input, kp); + + /* initialize keypad state */ + memset(kp->keystate, 0xff, sizeof(kp->keystate)); + memset(kp->stuckstate, 0xff, sizeof(kp->stuckstate)); + + rc = pmic8xxx_kpd_init(kp); + if (rc < 0) { + dev_err(&pdev->dev, "unable to initialize keypad controller\n"); + goto err_get_irq; + } + + rc = pmic8xxx_kp_config_gpio(pdata->cols_gpio_start, + pdata->num_cols, kp, &kypd_sns); + if (rc < 0) { + dev_err(&pdev->dev, "unable to configure keypad sense lines\n"); + goto err_gpio_config; + } + + rc = pmic8xxx_kp_config_gpio(pdata->rows_gpio_start, + pdata->num_rows, kp, &kypd_drv); + if (rc < 0) { + dev_err(&pdev->dev, "unable to configure keypad drive lines\n"); + goto err_gpio_config; + } + + rc = request_any_context_irq(kp->key_sense_irq, pmic8xxx_kp_irq, + IRQF_TRIGGER_RISING, "pmic-keypad", kp); + if (rc < 0) { + dev_err(&pdev->dev, "failed to request keypad sense irq\n"); + goto err_get_irq; + } + + rc = request_any_context_irq(kp->key_stuck_irq, pmic8xxx_kp_stuck_irq, + IRQF_TRIGGER_RISING, "pmic-keypad-stuck", kp); + if (rc < 0) { + dev_err(&pdev->dev, "failed to request keypad stuck irq\n"); + goto err_req_stuck_irq; + } + + rc = pmic8xxx_kp_read_u8(kp, &ctrl_val, KEYP_CTRL); + if (rc < 0) { + dev_err(&pdev->dev, "failed to read KEYP_CTRL register\n"); + goto err_pmic_reg_read; + } + + kp->ctrl_reg = ctrl_val; + + rc = input_register_device(kp->input); + if (rc < 0) { + dev_err(&pdev->dev, "unable to register keypad input device\n"); + goto err_pmic_reg_read; + } + + device_init_wakeup(&pdev->dev, pdata->wakeup); + + return 0; + +err_pmic_reg_read: + free_irq(kp->key_stuck_irq, NULL); +err_req_stuck_irq: + free_irq(kp->key_sense_irq, NULL); +err_gpio_config: +err_get_irq: + input_free_device(kp->input); +err_alloc_device: + platform_set_drvdata(pdev, NULL); + kfree(kp); + return rc; +} + +static int __devexit pmic8xxx_kp_remove(struct platform_device *pdev) +{ + struct pmic8xxx_kp *kp = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + free_irq(kp->key_stuck_irq, NULL); + free_irq(kp->key_sense_irq, NULL); + input_unregister_device(kp->input); + kfree(kp); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pmic8xxx_kp_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pmic8xxx_kp *kp = platform_get_drvdata(pdev); + struct input_dev *input_dev = kp->input; + + if (device_may_wakeup(dev)) { + enable_irq_wake(kp->key_sense_irq); + } else { + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + pmic8xxx_kp_disable(kp); + + mutex_unlock(&input_dev->mutex); + } + + return 0; +} + +static int pmic8xxx_kp_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pmic8xxx_kp *kp = platform_get_drvdata(pdev); + struct input_dev *input_dev = kp->input; + + if (device_may_wakeup(dev)) { + disable_irq_wake(kp->key_sense_irq); + } else { + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + pmic8xxx_kp_enable(kp); + + mutex_unlock(&input_dev->mutex); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pm8xxx_kp_pm_ops, + pmic8xxx_kp_suspend, pmic8xxx_kp_resume); + +static struct platform_driver pmic8xxx_kp_driver = { + .probe = pmic8xxx_kp_probe, + .remove = __devexit_p(pmic8xxx_kp_remove), + .driver = { + .name = PM8XXX_KEYPAD_DEV_NAME, + .owner = THIS_MODULE, + .pm = &pm8xxx_kp_pm_ops, + }, +}; + +static int __init pmic8xxx_kp_init(void) +{ + return platform_driver_register(&pmic8xxx_kp_driver); +} +module_init(pmic8xxx_kp_init); + +static void __exit pmic8xxx_kp_exit(void) +{ + platform_driver_unregister(&pmic8xxx_kp_driver); +} +module_exit(pmic8xxx_kp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8XXX keypad driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8xxx_keypad"); +MODULE_AUTHOR("Trilok Soni <tsoni@codeaurora.org>"); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index f9cf0881b0e..45dc6aa62ba 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -330,6 +330,17 @@ config INPUT_PWM_BEEPER To compile this driver as a module, choose M here: the module will be called pwm-beeper. +config INPUT_PMIC8XXX_PWRKEY + tristate "PMIC8XXX power key support" + depends on MFD_PM8XXX + help + Say Y here if you want support for the PMIC8XXX power key. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called pmic8xxx-pwrkey. + config INPUT_GPIO_ROTARY_ENCODER tristate "Rotary encoders connected to GPIO pins" depends on GPIOLIB && GENERIC_GPIO diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index e3f7984e627..38efb2cb182 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o obj-$(CONFIG_INPUT_POWERMATE) += powermate.o obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o +obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o diff --git a/drivers/input/misc/pmic8xxx-pwrkey.c b/drivers/input/misc/pmic8xxx-pwrkey.c new file mode 100644 index 00000000000..97e07e786e4 --- /dev/null +++ b/drivers/input/misc/pmic8xxx-pwrkey.c @@ -0,0 +1,231 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/log2.h> + +#include <linux/mfd/pm8xxx/core.h> +#include <linux/input/pmic8xxx-pwrkey.h> + +#define PON_CNTL_1 0x1C +#define PON_CNTL_PULL_UP BIT(7) +#define PON_CNTL_TRIG_DELAY_MASK (0x7) + +/** + * struct pmic8xxx_pwrkey - pmic8xxx pwrkey information + * @key_press_irq: key press irq number + */ +struct pmic8xxx_pwrkey { + struct input_dev *pwr; + int key_press_irq; +}; + +static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey) +{ + struct pmic8xxx_pwrkey *pwrkey = _pwrkey; + + input_report_key(pwrkey->pwr, KEY_POWER, 1); + input_sync(pwrkey->pwr); + + return IRQ_HANDLED; +} + +static irqreturn_t pwrkey_release_irq(int irq, void *_pwrkey) +{ + struct pmic8xxx_pwrkey *pwrkey = _pwrkey; + + input_report_key(pwrkey->pwr, KEY_POWER, 0); + input_sync(pwrkey->pwr); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM_SLEEP +static int pmic8xxx_pwrkey_suspend(struct device *dev) +{ + struct pmic8xxx_pwrkey *pwrkey = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(pwrkey->key_press_irq); + + return 0; +} + +static int pmic8xxx_pwrkey_resume(struct device *dev) +{ + struct pmic8xxx_pwrkey *pwrkey = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(pwrkey->key_press_irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pm8xxx_pwr_key_pm_ops, + pmic8xxx_pwrkey_suspend, pmic8xxx_pwrkey_resume); + +static int __devinit pmic8xxx_pwrkey_probe(struct platform_device *pdev) +{ + struct input_dev *pwr; + int key_release_irq = platform_get_irq(pdev, 0); + int key_press_irq = platform_get_irq(pdev, 1); + int err; + unsigned int delay; + u8 pon_cntl; + struct pmic8xxx_pwrkey *pwrkey; + const struct pm8xxx_pwrkey_platform_data *pdata = mfd_get_data(pdev); + + if (!pdata) { + dev_err(&pdev->dev, "power key platform data not supplied\n"); + return -EINVAL; + } + + if (pdata->kpd_trigger_delay_us > 62500) { + dev_err(&pdev->dev, "invalid power key trigger delay\n"); + return -EINVAL; + } + + pwrkey = kzalloc(sizeof(*pwrkey), GFP_KERNEL); + if (!pwrkey) + return -ENOMEM; + + pwr = input_allocate_device(); + if (!pwr) { + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + err = -ENOMEM; + goto free_pwrkey; + } + + input_set_capability(pwr, EV_KEY, KEY_POWER); + + pwr->name = "pmic8xxx_pwrkey"; + pwr->phys = "pmic8xxx_pwrkey/input0"; + pwr->dev.parent = &pdev->dev; + + delay = (pdata->kpd_trigger_delay_us << 10) / USEC_PER_SEC; + delay = 1 + ilog2(delay); + + err = pm8xxx_readb(pdev->dev.parent, PON_CNTL_1, &pon_cntl); + if (err < 0) { + dev_err(&pdev->dev, "failed reading PON_CNTL_1 err=%d\n", err); + goto free_input_dev; + } + + pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK; + pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK); + if (pdata->pull_up) + pon_cntl |= PON_CNTL_PULL_UP; + else + pon_cntl &= ~PON_CNTL_PULL_UP; + + err = pm8xxx_writeb(pdev->dev.parent, PON_CNTL_1, pon_cntl); + if (err < 0) { + dev_err(&pdev->dev, "failed writing PON_CNTL_1 err=%d\n", err); + goto free_input_dev; + } + + err = input_register_device(pwr); + if (err) { + dev_dbg(&pdev->dev, "Can't register power key: %d\n", err); + goto free_input_dev; + } + + pwrkey->key_press_irq = key_press_irq; + pwrkey->pwr = pwr; + + platform_set_drvdata(pdev, pwrkey); + + err = request_irq(key_press_irq, pwrkey_press_irq, + IRQF_TRIGGER_RISING, "pmic8xxx_pwrkey_press", pwrkey); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", + key_press_irq, err); + goto unreg_input_dev; + } + + err = request_irq(key_release_irq, pwrkey_release_irq, + IRQF_TRIGGER_RISING, "pmic8xxx_pwrkey_release", pwrkey); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", + key_release_irq, err); + + goto free_press_irq; + } + + device_init_wakeup(&pdev->dev, pdata->wakeup); + + return 0; + +free_press_irq: + free_irq(key_press_irq, NULL); +unreg_input_dev: + platform_set_drvdata(pdev, NULL); + input_unregister_device(pwr); + pwr = NULL; +free_input_dev: + input_free_device(pwr); +free_pwrkey: + kfree(pwrkey); + return err; +} + +static int __devexit pmic8xxx_pwrkey_remove(struct platform_device *pdev) +{ + struct pmic8xxx_pwrkey *pwrkey = platform_get_drvdata(pdev); + int key_release_irq = platform_get_irq(pdev, 0); + int key_press_irq = platform_get_irq(pdev, 1); + + device_init_wakeup(&pdev->dev, 0); + + free_irq(key_press_irq, pwrkey); + free_irq(key_release_irq, pwrkey); + input_unregister_device(pwrkey->pwr); + platform_set_drvdata(pdev, NULL); + kfree(pwrkey); + + return 0; +} + +static struct platform_driver pmic8xxx_pwrkey_driver = { + .probe = pmic8xxx_pwrkey_probe, + .remove = __devexit_p(pmic8xxx_pwrkey_remove), + .driver = { + .name = PM8XXX_PWRKEY_DEV_NAME, + .owner = THIS_MODULE, + .pm = &pm8xxx_pwr_key_pm_ops, + }, +}; + +static int __init pmic8xxx_pwrkey_init(void) +{ + return platform_driver_register(&pmic8xxx_pwrkey_driver); +} +module_init(pmic8xxx_pwrkey_init); + +static void __exit pmic8xxx_pwrkey_exit(void) +{ + platform_driver_unregister(&pmic8xxx_pwrkey_driver); +} +module_exit(pmic8xxx_pwrkey_exit); + +MODULE_ALIAS("platform:pmic8xxx_pwrkey"); +MODULE_DESCRIPTION("PMIC8XXX Power Key driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Trilok Soni <tsoni@codeaurora.org>"); diff --git a/drivers/input/misc/twl4030-vibra.c b/drivers/input/misc/twl4030-vibra.c index 6a11694e3fc..014dd4ad0d4 100644 --- a/drivers/input/misc/twl4030-vibra.c +++ b/drivers/input/misc/twl4030-vibra.c @@ -29,7 +29,6 @@ #include <linux/workqueue.h> #include <linux/i2c/twl.h> #include <linux/mfd/twl4030-codec.h> -#include <linux/mfd/core.h> #include <linux/input.h> #include <linux/slab.h> @@ -197,7 +196,7 @@ static SIMPLE_DEV_PM_OPS(twl4030_vibra_pm_ops, static int __devinit twl4030_vibra_probe(struct platform_device *pdev) { - struct twl4030_codec_vibra_data *pdata = mfd_get_data(pdev); + struct twl4030_codec_vibra_data *pdata = pdev->dev.platform_data; struct vibra_info *info; int ret; diff --git a/drivers/isdn/hardware/eicon/divasfunc.c b/drivers/isdn/hardware/eicon/divasfunc.c index d36a4c09e25..0bbee7824d7 100644 --- a/drivers/isdn/hardware/eicon/divasfunc.c +++ b/drivers/isdn/hardware/eicon/divasfunc.c @@ -113,9 +113,8 @@ void diva_xdi_didd_remove_adapter(int card) static void start_dbg(void) { DbgRegister("DIVAS", DRIVERRELEASE_DIVAS, (debugmask) ? debugmask : DBG_DEFAULT); - DBG_LOG(("DIVA ISDNXDI BUILD (%s[%s]-%s-%s)", - DIVA_BUILD, diva_xdi_common_code_build, __DATE__, - __TIME__)) + DBG_LOG(("DIVA ISDNXDI BUILD (%s[%s])", + DIVA_BUILD, diva_xdi_common_code_build)) } /* diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 1d027b475b2..23f0d5e99f3 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -389,6 +389,16 @@ config LEDS_NETXBIG and 5Big Network v2 boards. The LEDs are wired to a CPLD and are controlled through a GPIO extension bus. +config LEDS_ASIC3 + bool "LED support for the HTC ASIC3" + depends on MFD_ASIC3 + default y + help + This option enables support for the LEDs on the HTC ASIC3. The HTC + ASIC3 LED GPIOs are inputs, not outputs, thus the leds-gpio driver + cannot be used. This driver supports hardware blinking with an on+off + period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700. + config LEDS_TRIGGERS bool "LED Trigger support" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index bccb96c9bb4..bbfd2e367dc 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o +obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c index 416def84d04..0d4c16678ac 100644 --- a/drivers/leds/leds-88pm860x.c +++ b/drivers/leds/leds-88pm860x.c @@ -17,7 +17,6 @@ #include <linux/leds.h> #include <linux/slab.h> #include <linux/workqueue.h> -#include <linux/mfd/core.h> #include <linux/mfd/88pm860x.h> #define LED_PWM_SHIFT (3) @@ -171,7 +170,6 @@ static int pm860x_led_probe(struct platform_device *pdev) struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); struct pm860x_led_pdata *pdata; struct pm860x_led *data; - struct mfd_cell *cell; struct resource *res; int ret; @@ -181,10 +179,7 @@ static int pm860x_led_probe(struct platform_device *pdev) return -EINVAL; } - cell = pdev->dev.platform_data; - if (cell == NULL) - return -ENODEV; - pdata = cell->mfd_data; + pdata = pdev->dev.platform_data; if (pdata == NULL) { dev_err(&pdev->dev, "No platform data!\n"); return -EINVAL; diff --git a/drivers/leds/leds-asic3.c b/drivers/leds/leds-asic3.c new file mode 100644 index 00000000000..22f847c890c --- /dev/null +++ b/drivers/leds/leds-asic3.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2011 Paul Parsons <lost.distance@yahoo.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. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/slab.h> + +#include <linux/mfd/asic3.h> +#include <linux/mfd/core.h> + +/* + * The HTC ASIC3 LED GPIOs are inputs, not outputs. + * Hence we turn the LEDs on/off via the TimeBase register. + */ + +/* + * When TimeBase is 4 the clock resolution is about 32Hz. + * This driver supports hardware blinking with an on+off + * period from 62ms (2 clocks) to 125s (4000 clocks). + */ +#define MS_TO_CLK(ms) DIV_ROUND_CLOSEST(((ms)*1024), 32000) +#define CLK_TO_MS(clk) (((clk)*32000)/1024) +#define MAX_CLK 4000 /* Fits into 12-bit Time registers */ +#define MAX_MS CLK_TO_MS(MAX_CLK) + +static const unsigned int led_n_base[ASIC3_NUM_LEDS] = { + [0] = ASIC3_LED_0_Base, + [1] = ASIC3_LED_1_Base, + [2] = ASIC3_LED_2_Base, +}; + +static void brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct platform_device *pdev = to_platform_device(cdev->dev->parent); + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + u32 timebase; + unsigned int base; + + timebase = (value == LED_OFF) ? 0 : (LED_EN|0x4); + + base = led_n_base[cell->id]; + asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), 32); + asic3_write_register(asic, (base + ASIC3_LED_DutyTime), 32); + asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0); + asic3_write_register(asic, (base + ASIC3_LED_TimeBase), timebase); +} + +static int blink_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct platform_device *pdev = to_platform_device(cdev->dev->parent); + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + u32 on; + u32 off; + unsigned int base; + + if (*delay_on > MAX_MS || *delay_off > MAX_MS) + return -EINVAL; + + if (*delay_on == 0 && *delay_off == 0) { + /* If both are zero then a sensible default should be chosen */ + on = MS_TO_CLK(500); + off = MS_TO_CLK(500); + } else { + on = MS_TO_CLK(*delay_on); + off = MS_TO_CLK(*delay_off); + if ((on + off) > MAX_CLK) + return -EINVAL; + } + + base = led_n_base[cell->id]; + asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), (on + off)); + asic3_write_register(asic, (base + ASIC3_LED_DutyTime), on); + asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0); + asic3_write_register(asic, (base + ASIC3_LED_TimeBase), (LED_EN|0x4)); + + *delay_on = CLK_TO_MS(on); + *delay_off = CLK_TO_MS(off); + + return 0; +} + +static int __devinit asic3_led_probe(struct platform_device *pdev) +{ + struct asic3_led *led = pdev->dev.platform_data; + int ret; + + ret = mfd_cell_enable(pdev); + if (ret < 0) + goto ret0; + + led->cdev = kzalloc(sizeof(struct led_classdev), GFP_KERNEL); + if (!led->cdev) { + ret = -ENOMEM; + goto ret1; + } + + led->cdev->name = led->name; + led->cdev->default_trigger = led->default_trigger; + led->cdev->brightness_set = brightness_set; + led->cdev->blink_set = blink_set; + + ret = led_classdev_register(&pdev->dev, led->cdev); + if (ret < 0) + goto ret2; + + return 0; + +ret2: + kfree(led->cdev); +ret1: + (void) mfd_cell_disable(pdev); +ret0: + return ret; +} + +static int __devexit asic3_led_remove(struct platform_device *pdev) +{ + struct asic3_led *led = pdev->dev.platform_data; + + led_classdev_unregister(led->cdev); + + kfree(led->cdev); + + return mfd_cell_disable(pdev); +} + +static struct platform_driver asic3_led_driver = { + .probe = asic3_led_probe, + .remove = __devexit_p(asic3_led_remove), + .driver = { + .name = "leds-asic3", + .owner = THIS_MODULE, + }, +}; + +MODULE_ALIAS("platform:leds-asic3"); + +static int __init asic3_led_init(void) +{ + return platform_driver_register(&asic3_led_driver); +} + +static void __exit asic3_led_exit(void) +{ + platform_driver_unregister(&asic3_led_driver); +} + +module_init(asic3_led_init); +module_exit(asic3_led_exit); + +MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>"); +MODULE_DESCRIPTION("HTC ASIC3 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c index 126ca7955f6..f369e56d654 100644 --- a/drivers/leds/leds-mc13783.c +++ b/drivers/leds/leds-mc13783.c @@ -22,7 +22,6 @@ #include <linux/leds.h> #include <linux/workqueue.h> #include <linux/mfd/mc13783.h> -#include <linux/mfd/core.h> #include <linux/slab.h> struct mc13783_led { @@ -184,7 +183,7 @@ static int __devinit mc13783_led_setup(struct mc13783_led *led, int max_current) static int __devinit mc13783_leds_prepare(struct platform_device *pdev) { - struct mc13783_leds_platform_data *pdata = mfd_get_data(pdev); + struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent); int ret = 0; int reg = 0; @@ -265,7 +264,7 @@ out: static int __devinit mc13783_led_probe(struct platform_device *pdev) { - struct mc13783_leds_platform_data *pdata = mfd_get_data(pdev); + struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct mc13783_led_platform_data *led_cur; struct mc13783_led *led, *led_dat; int ret, i; @@ -352,7 +351,7 @@ err_free: static int __devexit mc13783_led_remove(struct platform_device *pdev) { - struct mc13783_leds_platform_data *pdata = mfd_get_data(pdev); + struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct mc13783_led *led = platform_get_drvdata(pdev); struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent); int i; diff --git a/drivers/media/dvb/dm1105/dm1105.c b/drivers/media/dvb/dm1105/dm1105.c index 2d8b4044be3..b2b0c45f32a 100644 --- a/drivers/media/dvb/dm1105/dm1105.c +++ b/drivers/media/dvb/dm1105/dm1105.c @@ -20,6 +20,7 @@ */ #include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> @@ -49,11 +50,12 @@ #define UNSET (-1U) -#define DM1105_BOARD_NOAUTO UNSET -#define DM1105_BOARD_UNKNOWN 0 -#define DM1105_BOARD_DVBWORLD_2002 1 -#define DM1105_BOARD_DVBWORLD_2004 2 -#define DM1105_BOARD_AXESS_DM05 3 +#define DM1105_BOARD_NOAUTO UNSET +#define DM1105_BOARD_UNKNOWN 0 +#define DM1105_BOARD_DVBWORLD_2002 1 +#define DM1105_BOARD_DVBWORLD_2004 2 +#define DM1105_BOARD_AXESS_DM05 3 +#define DM1105_BOARD_UNBRANDED_I2C_ON_GPIO 4 /* ----------------------------------------------- */ /* @@ -157,22 +159,38 @@ #define DM1105_MAX 0x04 #define DRIVER_NAME "dm1105" +#define DM1105_I2C_GPIO_NAME "dm1105-gpio" #define DM1105_DMA_PACKETS 47 #define DM1105_DMA_PACKET_LENGTH (128*4) #define DM1105_DMA_BYTES (128 * 4 * DM1105_DMA_PACKETS) +/* */ +#define GPIO08 (1 << 8) +#define GPIO13 (1 << 13) +#define GPIO14 (1 << 14) +#define GPIO15 (1 << 15) +#define GPIO16 (1 << 16) +#define GPIO17 (1 << 17) +#define GPIO_ALL 0x03ffff + /* GPIO's for LNB power control */ -#define DM1105_LNB_MASK 0x00000000 -#define DM1105_LNB_OFF 0x00020000 -#define DM1105_LNB_13V 0x00010100 -#define DM1105_LNB_18V 0x00000100 +#define DM1105_LNB_MASK (GPIO_ALL & ~(GPIO14 | GPIO13)) +#define DM1105_LNB_OFF GPIO17 +#define DM1105_LNB_13V (GPIO16 | GPIO08) +#define DM1105_LNB_18V GPIO08 /* GPIO's for LNB power control for Axess DM05 */ -#define DM05_LNB_MASK 0x00000000 -#define DM05_LNB_OFF 0x00020000/* actually 13v */ -#define DM05_LNB_13V 0x00020000 -#define DM05_LNB_18V 0x00030000 +#define DM05_LNB_MASK (GPIO_ALL & ~(GPIO14 | GPIO13)) +#define DM05_LNB_OFF GPIO17/* actually 13v */ +#define DM05_LNB_13V GPIO17 +#define DM05_LNB_18V (GPIO17 | GPIO16) + +/* GPIO's for LNB power control for unbranded with I2C on GPIO */ +#define UNBR_LNB_MASK (GPIO17 | GPIO16) +#define UNBR_LNB_OFF 0 +#define UNBR_LNB_13V GPIO17 +#define UNBR_LNB_18V (GPIO17 | GPIO16) static unsigned int card[] = {[0 ... 3] = UNSET }; module_param_array(card, int, NULL, 0444); @@ -187,7 +205,11 @@ static unsigned int dm1105_devcount; DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); struct dm1105_board { - char *name; + char *name; + struct { + u32 mask, off, v13, v18; + } lnb; + u32 gpio_scl, gpio_sda; }; struct dm1105_subid { @@ -199,15 +221,50 @@ struct dm1105_subid { static const struct dm1105_board dm1105_boards[] = { [DM1105_BOARD_UNKNOWN] = { .name = "UNKNOWN/GENERIC", + .lnb = { + .mask = DM1105_LNB_MASK, + .off = DM1105_LNB_OFF, + .v13 = DM1105_LNB_13V, + .v18 = DM1105_LNB_18V, + }, }, [DM1105_BOARD_DVBWORLD_2002] = { .name = "DVBWorld PCI 2002", + .lnb = { + .mask = DM1105_LNB_MASK, + .off = DM1105_LNB_OFF, + .v13 = DM1105_LNB_13V, + .v18 = DM1105_LNB_18V, + }, }, [DM1105_BOARD_DVBWORLD_2004] = { .name = "DVBWorld PCI 2004", + .lnb = { + .mask = DM1105_LNB_MASK, + .off = DM1105_LNB_OFF, + .v13 = DM1105_LNB_13V, + .v18 = DM1105_LNB_18V, + }, }, [DM1105_BOARD_AXESS_DM05] = { .name = "Axess/EasyTv DM05", + .lnb = { + .mask = DM05_LNB_MASK, + .off = DM05_LNB_OFF, + .v13 = DM05_LNB_13V, + .v18 = DM05_LNB_18V, + }, + }, + [DM1105_BOARD_UNBRANDED_I2C_ON_GPIO] = { + .name = "Unbranded DM1105 with i2c on GPIOs", + .lnb = { + .mask = UNBR_LNB_MASK, + .off = UNBR_LNB_OFF, + .v13 = UNBR_LNB_13V, + .v18 = UNBR_LNB_18V, + }, + .gpio_scl = GPIO14, + .gpio_sda = GPIO13, }, }; @@ -293,6 +350,8 @@ struct dm1105_dev { /* i2c */ struct i2c_adapter i2c_adap; + struct i2c_adapter i2c_bb_adap; + struct i2c_algo_bit_data i2c_bit; /* irq */ struct work_struct work; @@ -328,6 +387,103 @@ struct dm1105_dev { #define dm_setl(reg, bit) dm_andorl((reg), (bit), (bit)) #define dm_clearl(reg, bit) dm_andorl((reg), (bit), 0) +/* The chip has 18 GPIOs. In HOST mode GPIO's used as 15 bit address lines, + so we can use only 3 GPIO's from GPIO15 to GPIO17. + Here I don't check whether HOST is enebled as it is not implemented yet. + */ +static void dm1105_gpio_set(struct dm1105_dev *dev, u32 mask) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + dm_setl(DM1105_GPIOVAL, mask & 0x0003ffff); + +} + +static void dm1105_gpio_clear(struct dm1105_dev *dev, u32 mask) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + dm_clearl(DM1105_GPIOVAL, mask & 0x0003ffff); + +} + +static void dm1105_gpio_andor(struct dm1105_dev *dev, u32 mask, u32 val) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + dm_andorl(DM1105_GPIOVAL, mask & 0x0003ffff, val); + +} + +static u32 dm1105_gpio_get(struct dm1105_dev *dev, u32 mask) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if (mask & 0x0003ffff) + return dm_readl(DM1105_GPIOVAL) & mask & 0x0003ffff; + + return 0; +} + +static void dm1105_gpio_enable(struct dm1105_dev *dev, u32 mask, int asoutput) +{ + if (mask & 0xfffc0000) + printk(KERN_ERR "%s: Only 18 GPIO's are allowed\n", __func__); + + if ((mask & 0x0003ffff) && asoutput) + dm_clearl(DM1105_GPIOCTR, mask & 0x0003ffff); + else if ((mask & 0x0003ffff) && !asoutput) + dm_setl(DM1105_GPIOCTR, mask & 0x0003ffff); + +} + +static void dm1105_setline(struct dm1105_dev *dev, u32 line, int state) +{ + if (state) + dm1105_gpio_enable(dev, line, 0); + else { + dm1105_gpio_enable(dev, line, 1); + dm1105_gpio_clear(dev, line); + } +} + +static void dm1105_setsda(void *data, int state) +{ + struct dm1105_dev *dev = data; + + dm1105_setline(dev, dm1105_boards[dev->boardnr].gpio_sda, state); +} + +static void dm1105_setscl(void *data, int state) +{ + struct dm1105_dev *dev = data; + + dm1105_setline(dev, dm1105_boards[dev->boardnr].gpio_scl, state); +} + +static int dm1105_getsda(void *data) +{ + struct dm1105_dev *dev = data; + + return dm1105_gpio_get(dev, dm1105_boards[dev->boardnr].gpio_sda) + ? 1 : 0; +} + +static int dm1105_getscl(void *data) +{ + struct dm1105_dev *dev = data; + + return dm1105_gpio_get(dev, dm1105_boards[dev->boardnr].gpio_scl) + ? 1 : 0; +} + static int dm1105_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) { @@ -436,31 +592,20 @@ static inline struct dm1105_dev *frontend_to_dm1105_dev(struct dvb_frontend *fe) static int dm1105_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) { struct dm1105_dev *dev = frontend_to_dm1105_dev(fe); - u32 lnb_mask, lnb_13v, lnb_18v, lnb_off; - switch (dev->boardnr) { - case DM1105_BOARD_AXESS_DM05: - lnb_mask = DM05_LNB_MASK; - lnb_off = DM05_LNB_OFF; - lnb_13v = DM05_LNB_13V; - lnb_18v = DM05_LNB_18V; - break; - case DM1105_BOARD_DVBWORLD_2002: - case DM1105_BOARD_DVBWORLD_2004: - default: - lnb_mask = DM1105_LNB_MASK; - lnb_off = DM1105_LNB_OFF; - lnb_13v = DM1105_LNB_13V; - lnb_18v = DM1105_LNB_18V; - } - - dm_writel(DM1105_GPIOCTR, lnb_mask); + dm1105_gpio_enable(dev, dm1105_boards[dev->boardnr].lnb.mask, 1); if (voltage == SEC_VOLTAGE_18) - dm_writel(DM1105_GPIOVAL, lnb_18v); + dm1105_gpio_andor(dev, + dm1105_boards[dev->boardnr].lnb.mask, + dm1105_boards[dev->boardnr].lnb.v18); else if (voltage == SEC_VOLTAGE_13) - dm_writel(DM1105_GPIOVAL, lnb_13v); + dm1105_gpio_andor(dev, + dm1105_boards[dev->boardnr].lnb.mask, + dm1105_boards[dev->boardnr].lnb.v13); else - dm_writel(DM1105_GPIOVAL, lnb_off); + dm1105_gpio_andor(dev, + dm1105_boards[dev->boardnr].lnb.mask, + dm1105_boards[dev->boardnr].lnb.off); return 0; } @@ -708,6 +853,38 @@ static int __devinit frontend_init(struct dm1105_dev *dev) int ret; switch (dev->boardnr) { + case DM1105_BOARD_UNBRANDED_I2C_ON_GPIO: + dm1105_gpio_enable(dev, GPIO15, 1); + dm1105_gpio_clear(dev, GPIO15); + msleep(100); + dm1105_gpio_set(dev, GPIO15); + msleep(200); + dev->fe = dvb_attach( + stv0299_attach, &sharp_z0194a_config, + &dev->i2c_bb_adap); + if (dev->fe) { + dev->fe->ops.set_voltage = dm1105_set_voltage; + dvb_attach(dvb_pll_attach, dev->fe, 0x60, + &dev->i2c_bb_adap, DVB_PLL_OPERA1); + break; + } + + dev->fe = dvb_attach( + stv0288_attach, &earda_config, + &dev->i2c_bb_adap); + if (dev->fe) { + dev->fe->ops.set_voltage = dm1105_set_voltage; + dvb_attach(stb6000_attach, dev->fe, 0x61, + &dev->i2c_bb_adap); + break; + } + + dev->fe = dvb_attach( + si21xx_attach, &serit_config, + &dev->i2c_bb_adap); + if (dev->fe) + dev->fe->ops.set_voltage = dm1105_set_voltage; + break; case DM1105_BOARD_DVBWORLD_2004: dev->fe = dvb_attach( cx24116_attach, &serit_sp2633_config, @@ -870,11 +1047,32 @@ static int __devinit dm1105_probe(struct pci_dev *pdev, if (ret < 0) goto err_dm1105_hw_exit; + i2c_set_adapdata(&dev->i2c_bb_adap, dev); + strcpy(dev->i2c_bb_adap.name, DM1105_I2C_GPIO_NAME); + dev->i2c_bb_adap.owner = THIS_MODULE; + dev->i2c_bb_adap.dev.parent = &pdev->dev; + dev->i2c_bb_adap.algo_data = &dev->i2c_bit; + dev->i2c_bit.data = dev; + dev->i2c_bit.setsda = dm1105_setsda; + dev->i2c_bit.setscl = dm1105_setscl; + dev->i2c_bit.getsda = dm1105_getsda; + dev->i2c_bit.getscl = dm1105_getscl; + dev->i2c_bit.udelay = 10; + dev->i2c_bit.timeout = 10; + + /* Raise SCL and SDA */ + dm1105_setsda(dev, 1); + dm1105_setscl(dev, 1); + + ret = i2c_bit_add_bus(&dev->i2c_bb_adap); + if (ret < 0) + goto err_i2c_del_adapter; + /* dvb */ ret = dvb_register_adapter(&dev->dvb_adapter, DRIVER_NAME, THIS_MODULE, &pdev->dev, adapter_nr); if (ret < 0) - goto err_i2c_del_adapter; + goto err_i2c_del_adapters; dvb_adapter = &dev->dvb_adapter; @@ -952,6 +1150,8 @@ err_dvb_dmx_release: dvb_dmx_release(dvbdemux); err_dvb_unregister_adapter: dvb_unregister_adapter(dvb_adapter); +err_i2c_del_adapters: + i2c_del_adapter(&dev->i2c_bb_adap); err_i2c_del_adapter: i2c_del_adapter(&dev->i2c_adap); err_dm1105_hw_exit: diff --git a/drivers/media/dvb/dvb-usb/lmedm04.c b/drivers/media/dvb/dvb-usb/lmedm04.c index f36f471deae..37b146961ae 100644 --- a/drivers/media/dvb/dvb-usb/lmedm04.c +++ b/drivers/media/dvb/dvb-usb/lmedm04.c @@ -207,17 +207,6 @@ static int lme2510_stream_restart(struct dvb_usb_device *d) rbuff, sizeof(rbuff)); return ret; } -static int lme2510_remote_keypress(struct dvb_usb_adapter *adap, u32 keypress) -{ - struct dvb_usb_device *d = adap->dev; - - deb_info(1, "INT Key Keypress =%04x", keypress); - - if (keypress > 0) - rc_keydown(d->rc_dev, keypress, 0); - - return 0; -} static int lme2510_enable_pid(struct dvb_usb_device *d, u8 index, u16 pid_out) { @@ -256,6 +245,7 @@ static void lme2510_int_response(struct urb *lme_urb) struct lme2510_state *st = adap->dev->priv; static u8 *ibuf, *rbuf; int i = 0, offset; + u32 key; switch (lme_urb->status) { case 0: @@ -282,10 +272,16 @@ static void lme2510_int_response(struct urb *lme_urb) switch (ibuf[0]) { case 0xaa: - debug_data_snipet(1, "INT Remote data snipet in", ibuf); - lme2510_remote_keypress(adap, - (u32)(ibuf[2] << 24) + (ibuf[3] << 16) + - (ibuf[4] << 8) + ibuf[5]); + debug_data_snipet(1, "INT Remote data snipet", ibuf); + if ((ibuf[4] + ibuf[5]) == 0xff) { + key = ibuf[5]; + key += (ibuf[3] > 0) + ? (ibuf[3] ^ 0xff) << 8 : 0; + key += (ibuf[2] ^ 0xff) << 16; + deb_info(1, "INT Key =%08x", key); + if (adap->dev->rc_dev != NULL) + rc_keydown(adap->dev->rc_dev, key, 0); + } break; case 0xbb: switch (st->tuner_config) { @@ -691,45 +687,6 @@ static int lme2510_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) return (ret < 0) ? -ENODEV : 0; } -static int lme2510_int_service(struct dvb_usb_adapter *adap) -{ - struct dvb_usb_device *d = adap->dev; - struct rc_dev *rc; - int ret; - - info("STA Configuring Remote"); - - rc = rc_allocate_device(); - if (!rc) - return -ENOMEM; - - usb_make_path(d->udev, d->rc_phys, sizeof(d->rc_phys)); - strlcat(d->rc_phys, "/ir0", sizeof(d->rc_phys)); - - rc->input_name = "LME2510 Remote Control"; - rc->input_phys = d->rc_phys; - rc->map_name = RC_MAP_LME2510; - rc->driver_name = "LME 2510"; - usb_to_input_id(d->udev, &rc->input_id); - - ret = rc_register_device(rc); - if (ret) { - rc_free_device(rc); - return ret; - } - d->rc_dev = rc; - - /* Start the Interrupt */ - ret = lme2510_int_read(adap); - if (ret < 0) { - rc_unregister_device(rc); - info("INT Unable to start Interrupt Service"); - return -ENODEV; - } - - return 0; -} - static u8 check_sum(u8 *p, u8 len) { u8 sum = 0; @@ -831,7 +788,7 @@ static int lme_firmware_switch(struct usb_device *udev, int cold) cold_fw = !cold; - if (udev->descriptor.idProduct == 0x1122) { + if (le16_to_cpu(udev->descriptor.idProduct) == 0x1122) { switch (dvb_usb_lme2510_firmware) { default: dvb_usb_lme2510_firmware = TUNER_S0194; @@ -1053,8 +1010,11 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap) end: if (ret) { - kfree(adap->fe); - adap->fe = NULL; + if (adap->fe) { + dvb_frontend_detach(adap->fe); + adap->fe = NULL; + } + adap->dev->props.rc.core.rc_codes = NULL; return -ENODEV; } @@ -1097,8 +1057,12 @@ static int dm04_lme2510_tuner(struct dvb_usb_adapter *adap) return -ENODEV; } - /* Start the Interrupt & Remote*/ - ret = lme2510_int_service(adap); + /* Start the Interrupt*/ + ret = lme2510_int_read(adap); + if (ret < 0) { + info("INT Unable to start Interrupt Service"); + return -ENODEV; + } return ret; } @@ -1204,6 +1168,12 @@ static struct dvb_usb_device_properties lme2510_properties = { } } }, + .rc.core = { + .protocol = RC_TYPE_NEC, + .module_name = "LME2510 Remote Control", + .allowed_protos = RC_TYPE_NEC, + .rc_codes = RC_MAP_LME2510, + }, .power_ctrl = lme2510_powerup, .identify_state = lme2510_identify_state, .i2c_algo = &lme2510_i2c_algo, @@ -1246,6 +1216,12 @@ static struct dvb_usb_device_properties lme2510c_properties = { } } }, + .rc.core = { + .protocol = RC_TYPE_NEC, + .module_name = "LME2510 Remote Control", + .allowed_protos = RC_TYPE_NEC, + .rc_codes = RC_MAP_LME2510, + }, .power_ctrl = lme2510_powerup, .identify_state = lme2510_identify_state, .i2c_algo = &lme2510_i2c_algo, @@ -1269,19 +1245,21 @@ static void *lme2510_exit_int(struct dvb_usb_device *d) adap->feedcount = 0; } - if (st->lme_urb != NULL) { + if (st->usb_buffer != NULL) { st->i2c_talk_onoff = 1; st->signal_lock = 0; st->signal_level = 0; st->signal_sn = 0; buffer = st->usb_buffer; + } + + if (st->lme_urb != NULL) { usb_kill_urb(st->lme_urb); usb_free_coherent(d->udev, 5000, st->buffer, st->lme_urb->transfer_dma); info("Interrupt Service Stopped"); - rc_unregister_device(d->rc_dev); - info("Remote Stopped"); } + return buffer; } @@ -1293,7 +1271,8 @@ static void lme2510_exit(struct usb_interface *intf) if (d != NULL) { usb_buffer = lme2510_exit_int(d); dvb_usb_device_exit(intf); - kfree(usb_buffer); + if (usb_buffer != NULL) + kfree(usb_buffer); } } @@ -1327,5 +1306,5 @@ module_exit(lme2510_module_exit); MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>"); MODULE_DESCRIPTION("LME2510(C) DVB-S USB2.0"); -MODULE_VERSION("1.86"); +MODULE_VERSION("1.88"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/stb0899_algo.c b/drivers/media/dvb/frontends/stb0899_algo.c index 2da55ec2039..d70eee00f33 100644 --- a/drivers/media/dvb/frontends/stb0899_algo.c +++ b/drivers/media/dvb/frontends/stb0899_algo.c @@ -23,7 +23,7 @@ #include "stb0899_priv.h" #include "stb0899_reg.h" -inline u32 stb0899_do_div(u64 n, u32 d) +static inline u32 stb0899_do_div(u64 n, u32 d) { /* wrap do_div() for ease of use */ diff --git a/drivers/media/dvb/frontends/tda8261.c b/drivers/media/dvb/frontends/tda8261.c index 1742056a34e..53c7d8f1df2 100644 --- a/drivers/media/dvb/frontends/tda8261.c +++ b/drivers/media/dvb/frontends/tda8261.c @@ -224,7 +224,6 @@ exit: } EXPORT_SYMBOL(tda8261_attach); -MODULE_PARM_DESC(verbose, "Set verbosity level"); MODULE_AUTHOR("Manu Abraham"); MODULE_DESCRIPTION("TDA8261 8PSK/QPSK Tuner"); diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c index 5c2a9058c09..e83e8400302 100644 --- a/drivers/media/radio/radio-maxiradio.c +++ b/drivers/media/radio/radio-maxiradio.c @@ -412,8 +412,7 @@ static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_d goto err_out_free_region; } - v4l2_info(v4l2_dev, "version " DRIVER_VERSION - " time " __TIME__ " " __DATE__ "\n"); + v4l2_info(v4l2_dev, "version " DRIVER_VERSION "\n"); v4l2_info(v4l2_dev, "found Guillemot MAXI Radio device (io = 0x%x)\n", dev->io); diff --git a/drivers/media/radio/radio-timb.c b/drivers/media/radio/radio-timb.c index 1e3a8dd820a..a185610b376 100644 --- a/drivers/media/radio/radio-timb.c +++ b/drivers/media/radio/radio-timb.c @@ -21,7 +21,6 @@ #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/i2c.h> @@ -149,7 +148,7 @@ static const struct v4l2_file_operations timbradio_fops = { static int __devinit timbradio_probe(struct platform_device *pdev) { - struct timb_radio_platform_data *pdata = mfd_get_data(pdev); + struct timb_radio_platform_data *pdata = pdev->dev.platform_data; struct timbradio *tr; int err; diff --git a/drivers/media/radio/radio-wl1273.c b/drivers/media/radio/radio-wl1273.c index e2550dc2944..459f7272d32 100644 --- a/drivers/media/radio/radio-wl1273.c +++ b/drivers/media/radio/radio-wl1273.c @@ -1382,7 +1382,7 @@ static int wl1273_fm_g_volatile_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - ctrl->val = wl1273_fm_get_tx_ctune(radio); + ctrl->cur.val = wl1273_fm_get_tx_ctune(radio); break; default: @@ -1990,7 +1990,7 @@ static int wl1273_fm_radio_remove(struct platform_device *pdev) static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) { - struct wl1273_core **core = mfd_get_data(pdev); + struct wl1273_core **core = pdev->dev.platform_data; struct wl1273_device *radio; struct v4l2_ctrl *ctrl; int r = 0; diff --git a/drivers/media/radio/wl128x/fmdrv_v4l2.c b/drivers/media/radio/wl128x/fmdrv_v4l2.c index d50e5ac75ab..87010724f91 100644 --- a/drivers/media/radio/wl128x/fmdrv_v4l2.c +++ b/drivers/media/radio/wl128x/fmdrv_v4l2.c @@ -191,7 +191,7 @@ static int fm_g_volatile_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - ctrl->val = fm_tx_get_tune_cap_val(fmdev); + ctrl->cur.val = fm_tx_get_tune_cap_val(fmdev); break; default: fmwarn("%s: Unknown IOCTL: %d\n", __func__, ctrl->id); diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index 154c337f00f..7d4bbc226d0 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -148,6 +148,18 @@ config IR_ITE_CIR To compile this driver as a module, choose M here: the module will be called ite-cir. +config IR_FINTEK + tristate "Fintek Consumer Infrared Transceiver" + depends on PNP + depends on RC_CORE + ---help--- + Say Y here to enable support for integrated infrared receiver + /transciever made by Fintek. This chip is found on assorted + Jetway motherboards (and of course, possibly others). + + To compile this driver as a module, choose M here: the + module will be called fintek-cir. + config IR_NUVOTON tristate "Nuvoton w836x7hg Consumer Infrared Transceiver" depends on PNP diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index 1f90a219a16..52830e5f4ea 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_IR_LIRC_CODEC) += ir-lirc-codec.o obj-$(CONFIG_IR_IMON) += imon.o obj-$(CONFIG_IR_ITE_CIR) += ite-cir.o obj-$(CONFIG_IR_MCEUSB) += mceusb.o +obj-$(CONFIG_IR_FINTEK) += fintek-cir.o obj-$(CONFIG_IR_NUVOTON) += nuvoton-cir.o obj-$(CONFIG_IR_ENE) += ene_ir.o obj-$(CONFIG_IR_REDRAT3) += redrat3.o diff --git a/drivers/media/rc/fintek-cir.c b/drivers/media/rc/fintek-cir.c new file mode 100644 index 00000000000..8fa539dde1b --- /dev/null +++ b/drivers/media/rc/fintek-cir.c @@ -0,0 +1,684 @@ +/* + * Driver for Feature Integration Technology Inc. (aka Fintek) LPC CIR + * + * Copyright (C) 2011 Jarod Wilson <jarod@redhat.com> + * + * Special thanks to Fintek for providing hardware and spec sheets. + * This driver is based upon the nuvoton, ite and ene drivers for + * similar hardware. + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/pnp.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <media/rc-core.h> +#include <linux/pci_ids.h> + +#include "fintek-cir.h" + +/* write val to config reg */ +static inline void fintek_cr_write(struct fintek_dev *fintek, u8 val, u8 reg) +{ + fit_dbg("%s: reg 0x%02x, val 0x%02x (ip/dp: %02x/%02x)", + __func__, reg, val, fintek->cr_ip, fintek->cr_dp); + outb(reg, fintek->cr_ip); + outb(val, fintek->cr_dp); +} + +/* read val from config reg */ +static inline u8 fintek_cr_read(struct fintek_dev *fintek, u8 reg) +{ + u8 val; + + outb(reg, fintek->cr_ip); + val = inb(fintek->cr_dp); + + fit_dbg("%s: reg 0x%02x, val 0x%02x (ip/dp: %02x/%02x)", + __func__, reg, val, fintek->cr_ip, fintek->cr_dp); + return val; +} + +/* update config register bit without changing other bits */ +static inline void fintek_set_reg_bit(struct fintek_dev *fintek, u8 val, u8 reg) +{ + u8 tmp = fintek_cr_read(fintek, reg) | val; + fintek_cr_write(fintek, tmp, reg); +} + +/* clear config register bit without changing other bits */ +static inline void fintek_clear_reg_bit(struct fintek_dev *fintek, u8 val, u8 reg) +{ + u8 tmp = fintek_cr_read(fintek, reg) & ~val; + fintek_cr_write(fintek, tmp, reg); +} + +/* enter config mode */ +static inline void fintek_config_mode_enable(struct fintek_dev *fintek) +{ + /* Enabling Config Mode explicitly requires writing 2x */ + outb(CONFIG_REG_ENABLE, fintek->cr_ip); + outb(CONFIG_REG_ENABLE, fintek->cr_ip); +} + +/* exit config mode */ +static inline void fintek_config_mode_disable(struct fintek_dev *fintek) +{ + outb(CONFIG_REG_DISABLE, fintek->cr_ip); +} + +/* + * When you want to address a specific logical device, write its logical + * device number to GCR_LOGICAL_DEV_NO + */ +static inline void fintek_select_logical_dev(struct fintek_dev *fintek, u8 ldev) +{ + fintek_cr_write(fintek, ldev, GCR_LOGICAL_DEV_NO); +} + +/* write val to cir config register */ +static inline void fintek_cir_reg_write(struct fintek_dev *fintek, u8 val, u8 offset) +{ + outb(val, fintek->cir_addr + offset); +} + +/* read val from cir config register */ +static u8 fintek_cir_reg_read(struct fintek_dev *fintek, u8 offset) +{ + u8 val; + + val = inb(fintek->cir_addr + offset); + + return val; +} + +#define pr_reg(text, ...) \ + printk(KERN_INFO KBUILD_MODNAME ": " text, ## __VA_ARGS__) + +/* dump current cir register contents */ +static void cir_dump_regs(struct fintek_dev *fintek) +{ + fintek_config_mode_enable(fintek); + fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + + pr_reg("%s: Dump CIR logical device registers:\n", FINTEK_DRIVER_NAME); + pr_reg(" * CR CIR BASE ADDR: 0x%x\n", + (fintek_cr_read(fintek, CIR_CR_BASE_ADDR_HI) << 8) | + fintek_cr_read(fintek, CIR_CR_BASE_ADDR_LO)); + pr_reg(" * CR CIR IRQ NUM: 0x%x\n", + fintek_cr_read(fintek, CIR_CR_IRQ_SEL)); + + fintek_config_mode_disable(fintek); + + pr_reg("%s: Dump CIR registers:\n", FINTEK_DRIVER_NAME); + pr_reg(" * STATUS: 0x%x\n", fintek_cir_reg_read(fintek, CIR_STATUS)); + pr_reg(" * CONTROL: 0x%x\n", fintek_cir_reg_read(fintek, CIR_CONTROL)); + pr_reg(" * RX_DATA: 0x%x\n", fintek_cir_reg_read(fintek, CIR_RX_DATA)); + pr_reg(" * TX_CONTROL: 0x%x\n", fintek_cir_reg_read(fintek, CIR_TX_CONTROL)); + pr_reg(" * TX_DATA: 0x%x\n", fintek_cir_reg_read(fintek, CIR_TX_DATA)); +} + +/* detect hardware features */ +static int fintek_hw_detect(struct fintek_dev *fintek) +{ + unsigned long flags; + u8 chip_major, chip_minor; + u8 vendor_major, vendor_minor; + u8 portsel, ir_class; + u16 vendor; + int ret = 0; + + fintek_config_mode_enable(fintek); + + /* Check if we're using config port 0x4e or 0x2e */ + portsel = fintek_cr_read(fintek, GCR_CONFIG_PORT_SEL); + if (portsel == 0xff) { + fit_pr(KERN_INFO, "first portsel read was bunk, trying alt"); + fintek_config_mode_disable(fintek); + fintek->cr_ip = CR_INDEX_PORT2; + fintek->cr_dp = CR_DATA_PORT2; + fintek_config_mode_enable(fintek); + portsel = fintek_cr_read(fintek, GCR_CONFIG_PORT_SEL); + } + fit_dbg("portsel reg: 0x%02x", portsel); + + ir_class = fintek_cir_reg_read(fintek, CIR_CR_CLASS); + fit_dbg("ir_class reg: 0x%02x", ir_class); + + switch (ir_class) { + case CLASS_RX_2TX: + case CLASS_RX_1TX: + fintek->hw_tx_capable = true; + break; + case CLASS_RX_ONLY: + default: + fintek->hw_tx_capable = false; + break; + } + + chip_major = fintek_cr_read(fintek, GCR_CHIP_ID_HI); + chip_minor = fintek_cr_read(fintek, GCR_CHIP_ID_LO); + + vendor_major = fintek_cr_read(fintek, GCR_VENDOR_ID_HI); + vendor_minor = fintek_cr_read(fintek, GCR_VENDOR_ID_LO); + vendor = vendor_major << 8 | vendor_minor; + + if (vendor != VENDOR_ID_FINTEK) + fit_pr(KERN_WARNING, "Unknown vendor ID: 0x%04x", vendor); + else + fit_dbg("Read Fintek vendor ID from chip"); + + fintek_config_mode_disable(fintek); + + spin_lock_irqsave(&fintek->fintek_lock, flags); + fintek->chip_major = chip_major; + fintek->chip_minor = chip_minor; + fintek->chip_vendor = vendor; + spin_unlock_irqrestore(&fintek->fintek_lock, flags); + + return ret; +} + +static void fintek_cir_ldev_init(struct fintek_dev *fintek) +{ + /* Select CIR logical device and enable */ + fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN); + + /* Write allocated CIR address and IRQ information to hardware */ + fintek_cr_write(fintek, fintek->cir_addr >> 8, CIR_CR_BASE_ADDR_HI); + fintek_cr_write(fintek, fintek->cir_addr & 0xff, CIR_CR_BASE_ADDR_LO); + + fintek_cr_write(fintek, fintek->cir_irq, CIR_CR_IRQ_SEL); + + fit_dbg("CIR initialized, base io address: 0x%lx, irq: %d (len: %d)", + fintek->cir_addr, fintek->cir_irq, fintek->cir_port_len); +} + +/* enable CIR interrupts */ +static void fintek_enable_cir_irq(struct fintek_dev *fintek) +{ + fintek_cir_reg_write(fintek, CIR_STATUS_IRQ_EN, CIR_STATUS); +} + +static void fintek_cir_regs_init(struct fintek_dev *fintek) +{ + /* clear any and all stray interrupts */ + fintek_cir_reg_write(fintek, CIR_STATUS_IRQ_MASK, CIR_STATUS); + + /* and finally, enable interrupts */ + fintek_enable_cir_irq(fintek); +} + +static void fintek_enable_wake(struct fintek_dev *fintek) +{ + fintek_config_mode_enable(fintek); + fintek_select_logical_dev(fintek, LOGICAL_DEV_ACPI); + + /* Allow CIR PME's to wake system */ + fintek_set_reg_bit(fintek, ACPI_WAKE_EN_CIR_BIT, LDEV_ACPI_WAKE_EN_REG); + /* Enable CIR PME's */ + fintek_set_reg_bit(fintek, ACPI_PME_CIR_BIT, LDEV_ACPI_PME_EN_REG); + /* Clear CIR PME status register */ + fintek_set_reg_bit(fintek, ACPI_PME_CIR_BIT, LDEV_ACPI_PME_CLR_REG); + /* Save state */ + fintek_set_reg_bit(fintek, ACPI_STATE_CIR_BIT, LDEV_ACPI_STATE_REG); + + fintek_config_mode_disable(fintek); +} + +static int fintek_cmdsize(u8 cmd, u8 subcmd) +{ + int datasize = 0; + + switch (cmd) { + case BUF_COMMAND_NULL: + if (subcmd == BUF_HW_CMD_HEADER) + datasize = 1; + break; + case BUF_HW_CMD_HEADER: + if (subcmd == BUF_CMD_G_REVISION) + datasize = 2; + break; + case BUF_COMMAND_HEADER: + switch (subcmd) { + case BUF_CMD_S_CARRIER: + case BUF_CMD_S_TIMEOUT: + case BUF_RSP_PULSE_COUNT: + datasize = 2; + break; + case BUF_CMD_SIG_END: + case BUF_CMD_S_TXMASK: + case BUF_CMD_S_RXSENSOR: + datasize = 1; + break; + } + } + + return datasize; +} + +/* process ir data stored in driver buffer */ +static void fintek_process_rx_ir_data(struct fintek_dev *fintek) +{ + DEFINE_IR_RAW_EVENT(rawir); + u8 sample; + int i; + + for (i = 0; i < fintek->pkts; i++) { + sample = fintek->buf[i]; + switch (fintek->parser_state) { + case CMD_HEADER: + fintek->cmd = sample; + if ((fintek->cmd == BUF_COMMAND_HEADER) || + ((fintek->cmd & BUF_COMMAND_MASK) != + BUF_PULSE_BIT)) { + fintek->parser_state = SUBCMD; + continue; + } + fintek->rem = (fintek->cmd & BUF_LEN_MASK); + fit_dbg("%s: rem: 0x%02x", __func__, fintek->rem); + if (fintek->rem) + fintek->parser_state = PARSE_IRDATA; + else + ir_raw_event_reset(fintek->rdev); + break; + case SUBCMD: + fintek->rem = fintek_cmdsize(fintek->cmd, sample); + fintek->parser_state = CMD_DATA; + break; + case CMD_DATA: + fintek->rem--; + break; + case PARSE_IRDATA: + fintek->rem--; + init_ir_raw_event(&rawir); + rawir.pulse = ((sample & BUF_PULSE_BIT) != 0); + rawir.duration = US_TO_NS((sample & BUF_SAMPLE_MASK) + * CIR_SAMPLE_PERIOD); + + fit_dbg("Storing %s with duration %d", + rawir.pulse ? "pulse" : "space", + rawir.duration); + ir_raw_event_store_with_filter(fintek->rdev, &rawir); + break; + } + + if ((fintek->parser_state != CMD_HEADER) && !fintek->rem) + fintek->parser_state = CMD_HEADER; + } + + fintek->pkts = 0; + + fit_dbg("Calling ir_raw_event_handle"); + ir_raw_event_handle(fintek->rdev); +} + +/* copy data from hardware rx register into driver buffer */ +static void fintek_get_rx_ir_data(struct fintek_dev *fintek, u8 rx_irqs) +{ + unsigned long flags; + u8 sample, status; + + spin_lock_irqsave(&fintek->fintek_lock, flags); + + /* + * We must read data from CIR_RX_DATA until the hardware IR buffer + * is empty and clears the RX_TIMEOUT and/or RX_RECEIVE flags in + * the CIR_STATUS register + */ + do { + sample = fintek_cir_reg_read(fintek, CIR_RX_DATA); + fit_dbg("%s: sample: 0x%02x", __func__, sample); + + fintek->buf[fintek->pkts] = sample; + fintek->pkts++; + + status = fintek_cir_reg_read(fintek, CIR_STATUS); + if (!(status & CIR_STATUS_IRQ_EN)) + break; + } while (status & rx_irqs); + + fintek_process_rx_ir_data(fintek); + + spin_unlock_irqrestore(&fintek->fintek_lock, flags); +} + +static void fintek_cir_log_irqs(u8 status) +{ + fit_pr(KERN_INFO, "IRQ 0x%02x:%s%s%s%s%s", status, + status & CIR_STATUS_IRQ_EN ? " IRQEN" : "", + status & CIR_STATUS_TX_FINISH ? " TXF" : "", + status & CIR_STATUS_TX_UNDERRUN ? " TXU" : "", + status & CIR_STATUS_RX_TIMEOUT ? " RXTO" : "", + status & CIR_STATUS_RX_RECEIVE ? " RXOK" : ""); +} + +/* interrupt service routine for incoming and outgoing CIR data */ +static irqreturn_t fintek_cir_isr(int irq, void *data) +{ + struct fintek_dev *fintek = data; + u8 status, rx_irqs; + + fit_dbg_verbose("%s firing", __func__); + + fintek_config_mode_enable(fintek); + fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_config_mode_disable(fintek); + + /* + * Get IR Status register contents. Write 1 to ack/clear + * + * bit: reg name - description + * 3: TX_FINISH - TX is finished + * 2: TX_UNDERRUN - TX underrun + * 1: RX_TIMEOUT - RX data timeout + * 0: RX_RECEIVE - RX data received + */ + status = fintek_cir_reg_read(fintek, CIR_STATUS); + if (!(status & CIR_STATUS_IRQ_MASK) || status == 0xff) { + fit_dbg_verbose("%s exiting, IRSTS 0x%02x", __func__, status); + fintek_cir_reg_write(fintek, CIR_STATUS_IRQ_MASK, CIR_STATUS); + return IRQ_RETVAL(IRQ_NONE); + } + + if (debug) + fintek_cir_log_irqs(status); + + rx_irqs = status & (CIR_STATUS_RX_RECEIVE | CIR_STATUS_RX_TIMEOUT); + if (rx_irqs) + fintek_get_rx_ir_data(fintek, rx_irqs); + + /* ack/clear all irq flags we've got */ + fintek_cir_reg_write(fintek, status, CIR_STATUS); + + fit_dbg_verbose("%s done", __func__); + return IRQ_RETVAL(IRQ_HANDLED); +} + +static void fintek_enable_cir(struct fintek_dev *fintek) +{ + /* set IRQ enabled */ + fintek_cir_reg_write(fintek, CIR_STATUS_IRQ_EN, CIR_STATUS); + + fintek_config_mode_enable(fintek); + + /* enable the CIR logical device */ + fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN); + + fintek_config_mode_disable(fintek); + + /* clear all pending interrupts */ + fintek_cir_reg_write(fintek, CIR_STATUS_IRQ_MASK, CIR_STATUS); + + /* enable interrupts */ + fintek_enable_cir_irq(fintek); +} + +static void fintek_disable_cir(struct fintek_dev *fintek) +{ + fintek_config_mode_enable(fintek); + + /* disable the CIR logical device */ + fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_cr_write(fintek, LOGICAL_DEV_DISABLE, CIR_CR_DEV_EN); + + fintek_config_mode_disable(fintek); +} + +static int fintek_open(struct rc_dev *dev) +{ + struct fintek_dev *fintek = dev->priv; + unsigned long flags; + + spin_lock_irqsave(&fintek->fintek_lock, flags); + fintek_enable_cir(fintek); + spin_unlock_irqrestore(&fintek->fintek_lock, flags); + + return 0; +} + +static void fintek_close(struct rc_dev *dev) +{ + struct fintek_dev *fintek = dev->priv; + unsigned long flags; + + spin_lock_irqsave(&fintek->fintek_lock, flags); + fintek_disable_cir(fintek); + spin_unlock_irqrestore(&fintek->fintek_lock, flags); +} + +/* Allocate memory, probe hardware, and initialize everything */ +static int fintek_probe(struct pnp_dev *pdev, const struct pnp_device_id *dev_id) +{ + struct fintek_dev *fintek; + struct rc_dev *rdev; + int ret = -ENOMEM; + + fintek = kzalloc(sizeof(struct fintek_dev), GFP_KERNEL); + if (!fintek) + return ret; + + /* input device for IR remote (and tx) */ + rdev = rc_allocate_device(); + if (!rdev) + goto failure; + + ret = -ENODEV; + /* validate pnp resources */ + if (!pnp_port_valid(pdev, 0)) { + dev_err(&pdev->dev, "IR PNP Port not valid!\n"); + goto failure; + } + + if (!pnp_irq_valid(pdev, 0)) { + dev_err(&pdev->dev, "IR PNP IRQ not valid!\n"); + goto failure; + } + + fintek->cir_addr = pnp_port_start(pdev, 0); + fintek->cir_irq = pnp_irq(pdev, 0); + fintek->cir_port_len = pnp_port_len(pdev, 0); + + fintek->cr_ip = CR_INDEX_PORT; + fintek->cr_dp = CR_DATA_PORT; + + spin_lock_init(&fintek->fintek_lock); + + ret = -EBUSY; + /* now claim resources */ + if (!request_region(fintek->cir_addr, + fintek->cir_port_len, FINTEK_DRIVER_NAME)) + goto failure; + + if (request_irq(fintek->cir_irq, fintek_cir_isr, IRQF_SHARED, + FINTEK_DRIVER_NAME, (void *)fintek)) + goto failure; + + pnp_set_drvdata(pdev, fintek); + fintek->pdev = pdev; + + ret = fintek_hw_detect(fintek); + if (ret) + goto failure; + + /* Initialize CIR & CIR Wake Logical Devices */ + fintek_config_mode_enable(fintek); + fintek_cir_ldev_init(fintek); + fintek_config_mode_disable(fintek); + + /* Initialize CIR & CIR Wake Config Registers */ + fintek_cir_regs_init(fintek); + + /* Set up the rc device */ + rdev->priv = fintek; + rdev->driver_type = RC_DRIVER_IR_RAW; + rdev->allowed_protos = RC_TYPE_ALL; + rdev->open = fintek_open; + rdev->close = fintek_close; + rdev->input_name = FINTEK_DESCRIPTION; + rdev->input_phys = "fintek/cir0"; + rdev->input_id.bustype = BUS_HOST; + rdev->input_id.vendor = VENDOR_ID_FINTEK; + rdev->input_id.product = fintek->chip_major; + rdev->input_id.version = fintek->chip_minor; + rdev->dev.parent = &pdev->dev; + rdev->driver_name = FINTEK_DRIVER_NAME; + rdev->map_name = RC_MAP_RC6_MCE; + rdev->timeout = US_TO_NS(1000); + /* rx resolution is hardwired to 50us atm, 1, 25, 100 also possible */ + rdev->rx_resolution = US_TO_NS(CIR_SAMPLE_PERIOD); + + ret = rc_register_device(rdev); + if (ret) + goto failure; + + device_init_wakeup(&pdev->dev, true); + fintek->rdev = rdev; + fit_pr(KERN_NOTICE, "driver has been successfully loaded\n"); + if (debug) + cir_dump_regs(fintek); + + return 0; + +failure: + if (fintek->cir_irq) + free_irq(fintek->cir_irq, fintek); + if (fintek->cir_addr) + release_region(fintek->cir_addr, fintek->cir_port_len); + + rc_free_device(rdev); + kfree(fintek); + + return ret; +} + +static void __devexit fintek_remove(struct pnp_dev *pdev) +{ + struct fintek_dev *fintek = pnp_get_drvdata(pdev); + unsigned long flags; + + spin_lock_irqsave(&fintek->fintek_lock, flags); + /* disable CIR */ + fintek_disable_cir(fintek); + fintek_cir_reg_write(fintek, CIR_STATUS_IRQ_MASK, CIR_STATUS); + /* enable CIR Wake (for IR power-on) */ + fintek_enable_wake(fintek); + spin_unlock_irqrestore(&fintek->fintek_lock, flags); + + /* free resources */ + free_irq(fintek->cir_irq, fintek); + release_region(fintek->cir_addr, fintek->cir_port_len); + + rc_unregister_device(fintek->rdev); + + kfree(fintek); +} + +static int fintek_suspend(struct pnp_dev *pdev, pm_message_t state) +{ + struct fintek_dev *fintek = pnp_get_drvdata(pdev); + + fit_dbg("%s called", __func__); + + /* disable all CIR interrupts */ + fintek_cir_reg_write(fintek, CIR_STATUS_IRQ_MASK, CIR_STATUS); + + fintek_config_mode_enable(fintek); + + /* disable cir logical dev */ + fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_cr_write(fintek, LOGICAL_DEV_DISABLE, CIR_CR_DEV_EN); + + fintek_config_mode_disable(fintek); + + /* make sure wake is enabled */ + fintek_enable_wake(fintek); + + return 0; +} + +static int fintek_resume(struct pnp_dev *pdev) +{ + int ret = 0; + struct fintek_dev *fintek = pnp_get_drvdata(pdev); + + fit_dbg("%s called", __func__); + + /* open interrupt */ + fintek_enable_cir_irq(fintek); + + /* Enable CIR logical device */ + fintek_config_mode_enable(fintek); + fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN); + + fintek_config_mode_disable(fintek); + + fintek_cir_regs_init(fintek); + + return ret; +} + +static void fintek_shutdown(struct pnp_dev *pdev) +{ + struct fintek_dev *fintek = pnp_get_drvdata(pdev); + fintek_enable_wake(fintek); +} + +static const struct pnp_device_id fintek_ids[] = { + { "FIT0002", 0 }, /* CIR */ + { "", 0 }, +}; + +static struct pnp_driver fintek_driver = { + .name = FINTEK_DRIVER_NAME, + .id_table = fintek_ids, + .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, + .probe = fintek_probe, + .remove = __devexit_p(fintek_remove), + .suspend = fintek_suspend, + .resume = fintek_resume, + .shutdown = fintek_shutdown, +}; + +int fintek_init(void) +{ + return pnp_register_driver(&fintek_driver); +} + +void fintek_exit(void) +{ + pnp_unregister_driver(&fintek_driver); +} + +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging output"); + +MODULE_DEVICE_TABLE(pnp, fintek_ids); +MODULE_DESCRIPTION(FINTEK_DESCRIPTION " driver"); + +MODULE_AUTHOR("Jarod Wilson <jarod@redhat.com>"); +MODULE_LICENSE("GPL"); + +module_init(fintek_init); +module_exit(fintek_exit); diff --git a/drivers/media/rc/fintek-cir.h b/drivers/media/rc/fintek-cir.h new file mode 100644 index 00000000000..1b10b2011f5 --- /dev/null +++ b/drivers/media/rc/fintek-cir.h @@ -0,0 +1,243 @@ +/* + * Driver for Feature Integration Technology Inc. (aka Fintek) LPC CIR + * + * Copyright (C) 2011 Jarod Wilson <jarod@redhat.com> + * + * Special thanks to Fintek for providing hardware and spec sheets. + * This driver is based upon the nuvoton, ite and ene drivers for + * similar hardware. + * + * 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 <linux/spinlock.h> +#include <linux/ioctl.h> + +/* platform driver name to register */ +#define FINTEK_DRIVER_NAME "fintek-cir" +#define FINTEK_DESCRIPTION "Fintek LPC SuperIO Consumer IR Transceiver" +#define VENDOR_ID_FINTEK 0x1934 + + +/* debugging module parameter */ +static int debug; + +#define fit_pr(level, text, ...) \ + printk(level KBUILD_MODNAME ": " text, ## __VA_ARGS__) + +#define fit_dbg(text, ...) \ + if (debug) \ + printk(KERN_DEBUG \ + KBUILD_MODNAME ": " text "\n" , ## __VA_ARGS__) + +#define fit_dbg_verbose(text, ...) \ + if (debug > 1) \ + printk(KERN_DEBUG \ + KBUILD_MODNAME ": " text "\n" , ## __VA_ARGS__) + +#define fit_dbg_wake(text, ...) \ + if (debug > 2) \ + printk(KERN_DEBUG \ + KBUILD_MODNAME ": " text "\n" , ## __VA_ARGS__) + + +#define TX_BUF_LEN 256 +#define RX_BUF_LEN 32 + +struct fintek_dev { + struct pnp_dev *pdev; + struct rc_dev *rdev; + + spinlock_t fintek_lock; + + /* for rx */ + u8 buf[RX_BUF_LEN]; + unsigned int pkts; + + struct { + spinlock_t lock; + u8 buf[TX_BUF_LEN]; + unsigned int buf_count; + unsigned int cur_buf_num; + wait_queue_head_t queue; + } tx; + + /* Config register index/data port pair */ + u8 cr_ip; + u8 cr_dp; + + /* hardware I/O settings */ + unsigned long cir_addr; + int cir_irq; + int cir_port_len; + + /* hardware id */ + u8 chip_major; + u8 chip_minor; + u16 chip_vendor; + + /* hardware features */ + bool hw_learning_capable; + bool hw_tx_capable; + + /* rx settings */ + bool learning_enabled; + bool carrier_detect_enabled; + + enum { + CMD_HEADER = 0, + SUBCMD, + CMD_DATA, + PARSE_IRDATA, + } parser_state; + + u8 cmd, rem; + + /* carrier period = 1 / frequency */ + u32 carrier; +}; + +/* buffer packet constants, largely identical to mceusb.c */ +#define BUF_PULSE_BIT 0x80 +#define BUF_LEN_MASK 0x1f +#define BUF_SAMPLE_MASK 0x7f + +#define BUF_COMMAND_HEADER 0x9f +#define BUF_COMMAND_MASK 0xe0 +#define BUF_COMMAND_NULL 0x00 +#define BUF_HW_CMD_HEADER 0xff +#define BUF_CMD_G_REVISION 0x0b +#define BUF_CMD_S_CARRIER 0x06 +#define BUF_CMD_S_TIMEOUT 0x0c +#define BUF_CMD_SIG_END 0x01 +#define BUF_CMD_S_TXMASK 0x08 +#define BUF_CMD_S_RXSENSOR 0x14 +#define BUF_RSP_PULSE_COUNT 0x15 + +#define CIR_SAMPLE_PERIOD 50 + +/* + * Configuration Register: + * Index Port + * Data Port + */ +#define CR_INDEX_PORT 0x2e +#define CR_DATA_PORT 0x2f + +/* Possible alternate values, depends on how the chip is wired */ +#define CR_INDEX_PORT2 0x4e +#define CR_DATA_PORT2 0x4f + +/* + * GCR_CONFIG_PORT_SEL bit 4 specifies which Index Port value is + * active. 1 = 0x4e, 0 = 0x2e + */ +#define PORT_SEL_PORT_4E_EN 0x10 + +/* Extended Function Mode enable/disable magic values */ +#define CONFIG_REG_ENABLE 0x87 +#define CONFIG_REG_DISABLE 0xaa + +/* Chip IDs found in CR_CHIP_ID_{HI,LO} */ +#define CHIP_ID_HIGH_F71809U 0x04 +#define CHIP_ID_LOW_F71809U 0x08 + +/* + * Global control regs we need to care about: + * Global Control def. + * Register name addr val. */ +#define GCR_SOFTWARE_RESET 0x02 /* 0x00 */ +#define GCR_LOGICAL_DEV_NO 0x07 /* 0x00 */ +#define GCR_CHIP_ID_HI 0x20 /* 0x04 */ +#define GCR_CHIP_ID_LO 0x21 /* 0x08 */ +#define GCR_VENDOR_ID_HI 0x23 /* 0x19 */ +#define GCR_VENDOR_ID_LO 0x24 /* 0x34 */ +#define GCR_CONFIG_PORT_SEL 0x25 /* 0x01 */ +#define GCR_KBMOUSE_WAKEUP 0x27 + +#define LOGICAL_DEV_DISABLE 0x00 +#define LOGICAL_DEV_ENABLE 0x01 + +/* Logical device number of the CIR function */ +#define LOGICAL_DEV_CIR 0x05 + +/* CIR Logical Device (LDN 0x08) config registers */ +#define CIR_CR_COMMAND_INDEX 0x04 +#define CIR_CR_IRCS 0x05 /* Before host writes command to IR, host + must set to 1. When host finshes write + command to IR, host must clear to 0. */ +#define CIR_CR_COMMAND_DATA 0x06 /* Host read or write comand data */ +#define CIR_CR_CLASS 0x07 /* 0xff = rx-only, 0x66 = rx + 2 tx, + 0x33 = rx + 1 tx */ +#define CIR_CR_DEV_EN 0x30 /* bit0 = 1 enables CIR */ +#define CIR_CR_BASE_ADDR_HI 0x60 /* MSB of CIR IO base addr */ +#define CIR_CR_BASE_ADDR_LO 0x61 /* LSB of CIR IO base addr */ +#define CIR_CR_IRQ_SEL 0x70 /* bits3-0 store CIR IRQ */ +#define CIR_CR_PSOUT_STATUS 0xf1 +#define CIR_CR_WAKE_KEY3_ADDR 0xf8 +#define CIR_CR_WAKE_KEY3_CODE 0xf9 +#define CIR_CR_WAKE_KEY3_DC 0xfa +#define CIR_CR_WAKE_CONTROL 0xfb +#define CIR_CR_WAKE_KEY12_ADDR 0xfc +#define CIR_CR_WAKE_KEY4_ADDR 0xfd +#define CIR_CR_WAKE_KEY5_ADDR 0xfe + +#define CLASS_RX_ONLY 0xff +#define CLASS_RX_2TX 0x66 +#define CLASS_RX_1TX 0x33 + +/* CIR device registers */ +#define CIR_STATUS 0x00 +#define CIR_RX_DATA 0x01 +#define CIR_TX_CONTROL 0x02 +#define CIR_TX_DATA 0x03 +#define CIR_CONTROL 0x04 + +/* Bits to enable CIR wake */ +#define LOGICAL_DEV_ACPI 0x01 +#define LDEV_ACPI_WAKE_EN_REG 0xe8 +#define ACPI_WAKE_EN_CIR_BIT 0x04 + +#define LDEV_ACPI_PME_EN_REG 0xf0 +#define LDEV_ACPI_PME_CLR_REG 0xf1 +#define ACPI_PME_CIR_BIT 0x02 + +#define LDEV_ACPI_STATE_REG 0xf4 +#define ACPI_STATE_CIR_BIT 0x20 + +/* + * CIR status register (0x00): + * 7 - CIR_IRQ_EN (1 = enable CIR IRQ, 0 = disable) + * 3 - TX_FINISH (1 when TX finished, write 1 to clear) + * 2 - TX_UNDERRUN (1 on TX underrun, write 1 to clear) + * 1 - RX_TIMEOUT (1 on RX timeout, write 1 to clear) + * 0 - RX_RECEIVE (1 on RX receive, write 1 to clear) + */ +#define CIR_STATUS_IRQ_EN 0x80 +#define CIR_STATUS_TX_FINISH 0x08 +#define CIR_STATUS_TX_UNDERRUN 0x04 +#define CIR_STATUS_RX_TIMEOUT 0x02 +#define CIR_STATUS_RX_RECEIVE 0x01 +#define CIR_STATUS_IRQ_MASK 0x0f + +/* + * CIR TX control register (0x02): + * 7 - TX_START (1 to indicate TX start, auto-cleared when done) + * 6 - TX_END (1 to indicate TX data written to TX fifo) + */ +#define CIR_TX_CONTROL_TX_START 0x80 +#define CIR_TX_CONTROL_TX_END 0x40 + diff --git a/drivers/media/rc/keymaps/rc-lme2510.c b/drivers/media/rc/keymaps/rc-lme2510.c index afae14fd152..129d3f9a461 100644 --- a/drivers/media/rc/keymaps/rc-lme2510.c +++ b/drivers/media/rc/keymaps/rc-lme2510.c @@ -14,81 +14,81 @@ static struct rc_map_table lme2510_rc[] = { /* Type 1 - 26 buttons */ - { 0xef12ba45, KEY_0 }, - { 0xef12a05f, KEY_1 }, - { 0xef12af50, KEY_2 }, - { 0xef12a25d, KEY_3 }, - { 0xef12be41, KEY_4 }, - { 0xef12f50a, KEY_5 }, - { 0xef12bd42, KEY_6 }, - { 0xef12b847, KEY_7 }, - { 0xef12b649, KEY_8 }, - { 0xef12fa05, KEY_9 }, - { 0xef12bc43, KEY_POWER }, - { 0xef12b946, KEY_SUBTITLE }, - { 0xef12f906, KEY_PAUSE }, - { 0xef12fc03, KEY_MEDIA_REPEAT}, - { 0xef12fd02, KEY_PAUSE }, - { 0xef12a15e, KEY_VOLUMEUP }, - { 0xef12a35c, KEY_VOLUMEDOWN }, - { 0xef12f609, KEY_CHANNELUP }, - { 0xef12e51a, KEY_CHANNELDOWN }, - { 0xef12e11e, KEY_PLAY }, - { 0xef12e41b, KEY_ZOOM }, - { 0xef12a659, KEY_MUTE }, - { 0xef12a55a, KEY_TV }, - { 0xef12e718, KEY_RECORD }, - { 0xef12f807, KEY_EPG }, - { 0xef12fe01, KEY_STOP }, + { 0x10ed45, KEY_0 }, + { 0x10ed5f, KEY_1 }, + { 0x10ed50, KEY_2 }, + { 0x10ed5d, KEY_3 }, + { 0x10ed41, KEY_4 }, + { 0x10ed0a, KEY_5 }, + { 0x10ed42, KEY_6 }, + { 0x10ed47, KEY_7 }, + { 0x10ed49, KEY_8 }, + { 0x10ed05, KEY_9 }, + { 0x10ed43, KEY_POWER }, + { 0x10ed46, KEY_SUBTITLE }, + { 0x10ed06, KEY_PAUSE }, + { 0x10ed03, KEY_MEDIA_REPEAT}, + { 0x10ed02, KEY_PAUSE }, + { 0x10ed5e, KEY_VOLUMEUP }, + { 0x10ed5c, KEY_VOLUMEDOWN }, + { 0x10ed09, KEY_CHANNELUP }, + { 0x10ed1a, KEY_CHANNELDOWN }, + { 0x10ed1e, KEY_PLAY }, + { 0x10ed1b, KEY_ZOOM }, + { 0x10ed59, KEY_MUTE }, + { 0x10ed5a, KEY_TV }, + { 0x10ed18, KEY_RECORD }, + { 0x10ed07, KEY_EPG }, + { 0x10ed01, KEY_STOP }, /* Type 2 - 20 buttons */ - { 0xff40ea15, KEY_0 }, - { 0xff40f708, KEY_1 }, - { 0xff40f609, KEY_2 }, - { 0xff40f50a, KEY_3 }, - { 0xff40f30c, KEY_4 }, - { 0xff40f20d, KEY_5 }, - { 0xff40f10e, KEY_6 }, - { 0xff40ef10, KEY_7 }, - { 0xff40ee11, KEY_8 }, - { 0xff40ed12, KEY_9 }, - { 0xff40ff00, KEY_POWER }, - { 0xff40fb04, KEY_MEDIA_REPEAT}, /* Recall */ - { 0xff40e51a, KEY_PAUSE }, /* Timeshift */ - { 0xff40fd02, KEY_VOLUMEUP }, /* 2 x -/+ Keys not marked */ - { 0xff40f906, KEY_VOLUMEDOWN }, /* Volume defined as right hand*/ - { 0xff40fe01, KEY_CHANNELUP }, - { 0xff40fa05, KEY_CHANNELDOWN }, - { 0xff40eb14, KEY_ZOOM }, - { 0xff40e718, KEY_RECORD }, - { 0xff40e916, KEY_STOP }, + { 0xbf15, KEY_0 }, + { 0xbf08, KEY_1 }, + { 0xbf09, KEY_2 }, + { 0xbf0a, KEY_3 }, + { 0xbf0c, KEY_4 }, + { 0xbf0d, KEY_5 }, + { 0xbf0e, KEY_6 }, + { 0xbf10, KEY_7 }, + { 0xbf11, KEY_8 }, + { 0xbf12, KEY_9 }, + { 0xbf00, KEY_POWER }, + { 0xbf04, KEY_MEDIA_REPEAT}, /* Recall */ + { 0xbf1a, KEY_PAUSE }, /* Timeshift */ + { 0xbf02, KEY_VOLUMEUP }, /* 2 x -/+ Keys not marked */ + { 0xbf06, KEY_VOLUMEDOWN }, /* Volume defined as right hand*/ + { 0xbf01, KEY_CHANNELUP }, + { 0xbf05, KEY_CHANNELDOWN }, + { 0xbf14, KEY_ZOOM }, + { 0xbf18, KEY_RECORD }, + { 0xbf16, KEY_STOP }, /* Type 3 - 20 buttons */ - { 0xff00e31c, KEY_0 }, - { 0xff00f807, KEY_1 }, - { 0xff00ea15, KEY_2 }, - { 0xff00f609, KEY_3 }, - { 0xff00e916, KEY_4 }, - { 0xff00e619, KEY_5 }, - { 0xff00f20d, KEY_6 }, - { 0xff00f30c, KEY_7 }, - { 0xff00e718, KEY_8 }, - { 0xff00a15e, KEY_9 }, - { 0xff00ba45, KEY_POWER }, - { 0xff00bb44, KEY_MEDIA_REPEAT}, /* Recall */ - { 0xff00b54a, KEY_PAUSE }, /* Timeshift */ - { 0xff00b847, KEY_VOLUMEUP }, /* 2 x -/+ Keys not marked */ - { 0xff00bc43, KEY_VOLUMEDOWN }, /* Volume defined as right hand*/ - { 0xff00b946, KEY_CHANNELUP }, - { 0xff00bf40, KEY_CHANNELDOWN }, - { 0xff00f708, KEY_ZOOM }, - { 0xff00bd42, KEY_RECORD }, - { 0xff00a55a, KEY_STOP }, + { 0x1c, KEY_0 }, + { 0x07, KEY_1 }, + { 0x15, KEY_2 }, + { 0x09, KEY_3 }, + { 0x16, KEY_4 }, + { 0x19, KEY_5 }, + { 0x0d, KEY_6 }, + { 0x0c, KEY_7 }, + { 0x18, KEY_8 }, + { 0x5e, KEY_9 }, + { 0x45, KEY_POWER }, + { 0x44, KEY_MEDIA_REPEAT}, /* Recall */ + { 0x4a, KEY_PAUSE }, /* Timeshift */ + { 0x47, KEY_VOLUMEUP }, /* 2 x -/+ Keys not marked */ + { 0x43, KEY_VOLUMEDOWN }, /* Volume defined as right hand*/ + { 0x46, KEY_CHANNELUP }, + { 0x40, KEY_CHANNELDOWN }, + { 0x08, KEY_ZOOM }, + { 0x42, KEY_RECORD }, + { 0x5a, KEY_STOP }, }; static struct rc_map_list lme2510_map = { .map = { .scan = lme2510_rc, .size = ARRAY_SIZE(lme2510_rc), - .rc_type = RC_TYPE_UNKNOWN, + .rc_type = RC_TYPE_NEC, .name = RC_MAP_LME2510, } }; diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 3be180b3ba2..bb53de7fe40 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -687,7 +687,7 @@ config VIDEO_HEXIUM_GEMINI config VIDEO_TIMBERDALE tristate "Support for timberdale Video In/LogiWIN" - depends on VIDEO_V4L2 && I2C + depends on VIDEO_V4L2 && I2C && DMADEVICES select DMA_ENGINE select TIMB_DMA select VIDEO_ADV7180 @@ -757,6 +757,8 @@ config VIDEO_NOON010PC30 ---help--- This driver supports NOON010PC30 CIF camera from Siliconfile +source "drivers/media/video/m5mols/Kconfig" + config VIDEO_OMAP3 tristate "OMAP 3 Camera support (EXPERIMENTAL)" select OMAP_IOMMU @@ -952,7 +954,7 @@ config VIDEO_SAMSUNG_S5P_FIMC config VIDEO_S5P_MIPI_CSIS tristate "Samsung S5P and EXYNOS4 MIPI CSI receiver driver" - depends on VIDEO_V4L2 && PM_RUNTIME && VIDEO_V4L2_SUBDEV_API + depends on VIDEO_V4L2 && PM_RUNTIME && PLAT_S5P && VIDEO_V4L2_SUBDEV_API ---help--- This is a v4l2 driver for Samsung S5P/EXYNOS4 MIPI-CSI receiver. diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 9519160c2e0..f0fecd6f6a3 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o +obj-$(CONFIG_VIDEO_M5MOLS) += m5mols/ obj-$(CONFIG_SOC_CAMERA_IMX074) += imx074.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o diff --git a/drivers/media/video/cpia2/cpia2_v4l.c b/drivers/media/video/cpia2/cpia2_v4l.c index 0073a8c5533..40eb6326e48 100644 --- a/drivers/media/video/cpia2/cpia2_v4l.c +++ b/drivers/media/video/cpia2/cpia2_v4l.c @@ -438,7 +438,7 @@ static int cpia2_querycap(struct file *file, void *fh, struct v4l2_capability *v strcat(vc->card, " (676/"); break; default: - strcat(vc->card, " (???/"); + strcat(vc->card, " (XXX/"); break; } switch (cam->params.version.sensor_flags) { @@ -458,7 +458,7 @@ static int cpia2_querycap(struct file *file, void *fh, struct v4l2_capability *v strcat(vc->card, "500)"); break; default: - strcat(vc->card, "???)"); + strcat(vc->card, "XXX)"); break; } diff --git a/drivers/media/video/cx231xx/cx231xx-avcore.c b/drivers/media/video/cx231xx/cx231xx-avcore.c index 280df43ca44..8d781341576 100644 --- a/drivers/media/video/cx231xx/cx231xx-avcore.c +++ b/drivers/media/video/cx231xx/cx231xx-avcore.c @@ -1354,7 +1354,7 @@ void cx231xx_dump_SC_reg(struct cx231xx *dev) { u8 value[4] = { 0, 0, 0, 0 }; int status = 0; - cx231xx_info("cx231xx_dump_SC_reg %s!\n", __TIME__); + cx231xx_info("cx231xx_dump_SC_reg!\n"); status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, BOARD_CFG_STAT, value, 4); diff --git a/drivers/media/video/gspca/kinect.c b/drivers/media/video/gspca/kinect.c index 66671a4092e..26fc206f095 100644 --- a/drivers/media/video/gspca/kinect.c +++ b/drivers/media/video/gspca/kinect.c @@ -34,7 +34,7 @@ MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>"); MODULE_DESCRIPTION("GSPCA/Kinect Sensor Device USB Camera Driver"); MODULE_LICENSE("GPL"); -#ifdef DEBUG +#ifdef GSPCA_DEBUG int gspca_debug = D_ERR | D_PROBE | D_CONF | D_STREAM | D_FRAM | D_PACK | D_USBI | D_USBO | D_V4L2; #endif diff --git a/drivers/media/video/m5mols/Kconfig b/drivers/media/video/m5mols/Kconfig new file mode 100644 index 00000000000..302dc3d7019 --- /dev/null +++ b/drivers/media/video/m5mols/Kconfig @@ -0,0 +1,5 @@ +config VIDEO_M5MOLS + tristate "Fujitsu M-5MOLS 8MP sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + ---help--- + This driver supports Fujitsu M-5MOLS camera sensor with ISP diff --git a/drivers/media/video/m5mols/Makefile b/drivers/media/video/m5mols/Makefile new file mode 100644 index 00000000000..0a44e028edc --- /dev/null +++ b/drivers/media/video/m5mols/Makefile @@ -0,0 +1,3 @@ +m5mols-objs := m5mols_core.o m5mols_controls.o m5mols_capture.o + +obj-$(CONFIG_VIDEO_M5MOLS) += m5mols.o diff --git a/drivers/media/video/m5mols/m5mols.h b/drivers/media/video/m5mols/m5mols.h new file mode 100644 index 00000000000..10b55c85448 --- /dev/null +++ b/drivers/media/video/m5mols/m5mols.h @@ -0,0 +1,296 @@ +/* + * Header for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@samsung.com + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@samsung.com + * + * 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. + */ + +#ifndef M5MOLS_H +#define M5MOLS_H + +#include <media/v4l2-subdev.h> +#include "m5mols_reg.h" + +extern int m5mols_debug; + +#define to_m5mols(__sd) container_of(__sd, struct m5mols_info, sd) + +#define to_sd(__ctrl) \ + (&container_of(__ctrl->handler, struct m5mols_info, handle)->sd) + +enum m5mols_restype { + M5MOLS_RESTYPE_MONITOR, + M5MOLS_RESTYPE_CAPTURE, + M5MOLS_RESTYPE_MAX, +}; + +/** + * struct m5mols_resolution - structure for the resolution + * @type: resolution type according to the pixel code + * @width: width of the resolution + * @height: height of the resolution + * @reg: resolution preset register value + */ +struct m5mols_resolution { + u8 reg; + enum m5mols_restype type; + u16 width; + u16 height; +}; + +/** + * struct m5mols_exif - structure for the EXIF information of M-5MOLS + * @exposure_time: exposure time register value + * @shutter_speed: speed of the shutter register value + * @aperture: aperture register value + * @exposure_bias: it calls also EV bias + * @iso_speed: ISO register value + * @flash: status register value of the flash + * @sdr: status register value of the Subject Distance Range + * @qval: not written exact meaning in document + */ +struct m5mols_exif { + u32 exposure_time; + u32 shutter_speed; + u32 aperture; + u32 brightness; + u32 exposure_bias; + u16 iso_speed; + u16 flash; + u16 sdr; + u16 qval; +}; + +/** + * struct m5mols_capture - Structure for the capture capability + * @exif: EXIF information + * @main: size in bytes of the main image + * @thumb: size in bytes of the thumb image, if it was accompanied + * @total: total size in bytes of the produced image + */ +struct m5mols_capture { + struct m5mols_exif exif; + u32 main; + u32 thumb; + u32 total; +}; + +/** + * struct m5mols_scenemode - structure for the scenemode capability + * @metering: metering light register value + * @ev_bias: EV bias register value + * @wb_mode: mode which means the WhiteBalance is Auto or Manual + * @wb_preset: whitebalance preset register value in the Manual mode + * @chroma_en: register value whether the Chroma capability is enabled or not + * @chroma_lvl: chroma's level register value + * @edge_en: register value Whether the Edge capability is enabled or not + * @edge_lvl: edge's level register value + * @af_range: Auto Focus's range + * @fd_mode: Face Detection mode + * @mcc: Multi-axis Color Conversion which means emotion color + * @light: status of the Light + * @flash: status of the Flash + * @tone: Tone color which means Contrast + * @iso: ISO register value + * @capt_mode: Mode of the Image Stabilization while the camera capturing + * @wdr: Wide Dynamic Range register value + * + * The each value according to each scenemode is recommended in the documents. + */ +struct m5mols_scenemode { + u32 metering; + u32 ev_bias; + u32 wb_mode; + u32 wb_preset; + u32 chroma_en; + u32 chroma_lvl; + u32 edge_en; + u32 edge_lvl; + u32 af_range; + u32 fd_mode; + u32 mcc; + u32 light; + u32 flash; + u32 tone; + u32 iso; + u32 capt_mode; + u32 wdr; +}; + +/** + * struct m5mols_version - firmware version information + * @customer: customer information + * @project: version of project information according to customer + * @fw: firmware revision + * @hw: hardware revision + * @param: version of the parameter + * @awb: Auto WhiteBalance algorithm version + * @str: information about manufacturer and packaging vendor + * @af: Auto Focus version + * + * The register offset starts the customer version at 0x0, and it ends + * the awb version at 0x09. The customer, project information occupies 1 bytes + * each. And also the fw, hw, param, awb each requires 2 bytes. The str is + * unique string associated with firmware's version. It includes information + * about manufacturer and the vendor of the sensor's packaging. The least + * significant 2 bytes of the string indicate packaging manufacturer. + */ +#define VERSION_STRING_SIZE 22 +struct m5mols_version { + u8 customer; + u8 project; + u16 fw; + u16 hw; + u16 param; + u16 awb; + u8 str[VERSION_STRING_SIZE]; + u8 af; +}; +#define VERSION_SIZE sizeof(struct m5mols_version) + +/** + * struct m5mols_info - M-5MOLS driver data structure + * @pdata: platform data + * @sd: v4l-subdev instance + * @pad: media pad + * @ffmt: current fmt according to resolution type + * @res_type: current resolution type + * @code: current code + * @irq_waitq: waitqueue for the capture + * @work_irq: workqueue for the IRQ + * @flags: state variable for the interrupt handler + * @handle: control handler + * @autoexposure: Auto Exposure control + * @exposure: Exposure control + * @autowb: Auto White Balance control + * @colorfx: Color effect control + * @saturation: Saturation control + * @zoom: Zoom control + * @ver: information of the version + * @cap: the capture mode attributes + * @power: current sensor's power status + * @ctrl_sync: true means all controls of the sensor are initialized + * @int_capture: true means the capture interrupt is issued once + * @lock_ae: true means the Auto Exposure is locked + * @lock_awb: true means the Aut WhiteBalance is locked + * @resolution: register value for current resolution + * @interrupt: register value for current interrupt status + * @mode: register value for current operation mode + * @mode_save: register value for current operation mode for saving + * @set_power: optional power callback to the board code + */ +struct m5mols_info { + const struct m5mols_platform_data *pdata; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt ffmt[M5MOLS_RESTYPE_MAX]; + int res_type; + enum v4l2_mbus_pixelcode code; + wait_queue_head_t irq_waitq; + struct work_struct work_irq; + unsigned long flags; + + struct v4l2_ctrl_handler handle; + /* Autoexposure/exposure control cluster */ + struct { + struct v4l2_ctrl *autoexposure; + struct v4l2_ctrl *exposure; + }; + struct v4l2_ctrl *autowb; + struct v4l2_ctrl *colorfx; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *zoom; + + struct m5mols_version ver; + struct m5mols_capture cap; + bool power; + bool ctrl_sync; + bool lock_ae; + bool lock_awb; + u8 resolution; + u32 interrupt; + u32 mode; + u32 mode_save; + int (*set_power)(struct device *dev, int on); +}; + +#define ST_CAPT_IRQ 0 + +#define is_powered(__info) (__info->power) +#define is_ctrl_synced(__info) (__info->ctrl_sync) +#define is_available_af(__info) (__info->ver.af) +#define is_code(__code, __type) (__code == m5mols_default_ffmt[__type].code) +#define is_manufacturer(__info, __manufacturer) \ + (__info->ver.str[0] == __manufacturer[0] && \ + __info->ver.str[1] == __manufacturer[1]) +/* + * I2C operation of the M-5MOLS + * + * The I2C read operation of the M-5MOLS requires 2 messages. The first + * message sends the information about the command, command category, and total + * message size. The second message is used to retrieve the data specifed in + * the first message + * + * 1st message 2nd message + * +-------+---+----------+-----+-------+ +------+------+------+------+ + * | size1 | R | category | cmd | size2 | | d[0] | d[1] | d[2] | d[3] | + * +-------+---+----------+-----+-------+ +------+------+------+------+ + * - size1: message data size(5 in this case) + * - size2: desired buffer size of the 2nd message + * - d[0..3]: according to size2 + * + * The I2C write operation needs just one message. The message includes + * category, command, total size, and desired data. + * + * 1st message + * +-------+---+----------+-----+------+------+------+------+ + * | size1 | W | category | cmd | d[0] | d[1] | d[2] | d[3] | + * +-------+---+----------+-----+------+------+------+------+ + * - d[0..3]: according to size1 + */ +int m5mols_read(struct v4l2_subdev *sd, u32 reg_comb, u32 *val); +int m5mols_write(struct v4l2_subdev *sd, u32 reg_comb, u32 val); +int m5mols_busy(struct v4l2_subdev *sd, u8 category, u8 cmd, u32 value); + +/* + * Mode operation of the M-5MOLS + * + * Changing the mode of the M-5MOLS is needed right executing order. + * There are three modes(PARAMETER, MONITOR, CAPTURE) which can be changed + * by user. There are various categories associated with each mode. + * + * +============================================================+ + * | mode | category | + * +============================================================+ + * | FLASH | FLASH(only after Stand-by or Power-on) | + * | SYSTEM | SYSTEM(only after sensor arm-booting) | + * | PARAMETER | PARAMETER | + * | MONITOR | MONITOR(preview), Auto Focus, Face Detection | + * | CAPTURE | Single CAPTURE, Preview(recording) | + * +============================================================+ + * + * The available executing order between each modes are as follows: + * PARAMETER <---> MONITOR <---> CAPTURE + */ +int m5mols_mode(struct m5mols_info *info, u32 mode); + +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u32 reg); +int m5mols_sync_controls(struct m5mols_info *info); +int m5mols_start_capture(struct m5mols_info *info); +int m5mols_do_scenemode(struct m5mols_info *info, u32 mode); +int m5mols_lock_3a(struct m5mols_info *info, bool lock); +int m5mols_set_ctrl(struct v4l2_ctrl *ctrl); + +/* The firmware function */ +int m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)); + +#endif /* M5MOLS_H */ diff --git a/drivers/media/video/m5mols/m5mols_capture.c b/drivers/media/video/m5mols/m5mols_capture.c new file mode 100644 index 00000000000..d71a3903b60 --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_capture.c @@ -0,0 +1,191 @@ +/* + * The Capture code for Fujitsu M-5MOLS ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@samsung.com + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@samsung.com + * + * 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. + */ + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/version.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <linux/version.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/m5mols.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +static int m5mols_capture_error_handler(struct m5mols_info *info, + int timeout) +{ + int ret; + + /* Disable all interrupts and clear relevant interrupt staus bits */ + ret = m5mols_write(&info->sd, SYSTEM_INT_ENABLE, + info->interrupt & ~(REG_INT_CAPTURE)); + if (ret) + return ret; + + if (timeout == 0) + return -ETIMEDOUT; + + return 0; +} +/** + * m5mols_read_rational - I2C read of a rational number + * + * Read numerator and denominator from registers @addr_num and @addr_den + * respectively and return the division result in @val. + */ +static int m5mols_read_rational(struct v4l2_subdev *sd, u32 addr_num, + u32 addr_den, u32 *val) +{ + u32 num, den; + + int ret = m5mols_read(sd, addr_num, &num); + if (!ret) + ret = m5mols_read(sd, addr_den, &den); + if (ret) + return ret; + *val = den == 0 ? 0 : num / den; + return ret; +} + +/** + * m5mols_capture_info - Gather captured image information + * + * For now it gathers only EXIF information and file size. + */ +static int m5mols_capture_info(struct m5mols_info *info) +{ + struct m5mols_exif *exif = &info->cap.exif; + struct v4l2_subdev *sd = &info->sd; + int ret; + + ret = m5mols_read_rational(sd, EXIF_INFO_EXPTIME_NU, + EXIF_INFO_EXPTIME_DE, &exif->exposure_time); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_TV_NU, EXIF_INFO_TV_DE, + &exif->shutter_speed); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_AV_NU, EXIF_INFO_AV_DE, + &exif->aperture); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_BV_NU, EXIF_INFO_BV_DE, + &exif->brightness); + if (ret) + return ret; + ret = m5mols_read_rational(sd, EXIF_INFO_EBV_NU, EXIF_INFO_EBV_DE, + &exif->exposure_bias); + if (ret) + return ret; + + ret = m5mols_read(sd, EXIF_INFO_ISO, (u32 *)&exif->iso_speed); + if (!ret) + ret = m5mols_read(sd, EXIF_INFO_FLASH, (u32 *)&exif->flash); + if (!ret) + ret = m5mols_read(sd, EXIF_INFO_SDR, (u32 *)&exif->sdr); + if (!ret) + ret = m5mols_read(sd, EXIF_INFO_QVAL, (u32 *)&exif->qval); + if (ret) + return ret; + + if (!ret) + ret = m5mols_read(sd, CAPC_IMAGE_SIZE, &info->cap.main); + if (!ret) + ret = m5mols_read(sd, CAPC_THUMB_SIZE, &info->cap.thumb); + if (!ret) + info->cap.total = info->cap.main + info->cap.thumb; + + return ret; +} + +int m5mols_start_capture(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + u32 resolution = info->resolution; + int timeout; + int ret; + + /* + * Preparing capture. Setting control & interrupt before entering + * capture mode + * + * 1) change to MONITOR mode for operating control & interrupt + * 2) set controls (considering v4l2_control value & lock 3A) + * 3) set interrupt + * 4) change to CAPTURE mode + */ + ret = m5mols_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_sync_controls(info); + if (!ret) + ret = m5mols_lock_3a(info, true); + if (!ret) + ret = m5mols_enable_interrupt(sd, REG_INT_CAPTURE); + if (!ret) + ret = m5mols_mode(info, REG_CAPTURE); + if (!ret) { + /* Wait for capture interrupt, after changing capture mode */ + timeout = wait_event_interruptible_timeout(info->irq_waitq, + test_bit(ST_CAPT_IRQ, &info->flags), + msecs_to_jiffies(2000)); + if (test_and_clear_bit(ST_CAPT_IRQ, &info->flags)) + ret = m5mols_capture_error_handler(info, timeout); + } + if (!ret) + ret = m5mols_lock_3a(info, false); + if (ret) + return ret; + /* + * Starting capture. Setting capture frame count and resolution and + * the format(available format: JPEG, Bayer RAW, YUV). + * + * 1) select single or multi(enable to 25), format, size + * 2) set interrupt + * 3) start capture(for main image, now) + * 4) get information + * 5) notify file size to v4l2 device(e.g, to s5p-fimc v4l2 device) + */ + ret = m5mols_write(sd, CAPC_SEL_FRAME, 1); + if (!ret) + ret = m5mols_write(sd, CAPP_YUVOUT_MAIN, REG_JPEG); + if (!ret) + ret = m5mols_write(sd, CAPP_MAIN_IMAGE_SIZE, resolution); + if (!ret) + ret = m5mols_enable_interrupt(sd, REG_INT_CAPTURE); + if (!ret) + ret = m5mols_write(sd, CAPC_START, REG_CAP_START_MAIN); + if (!ret) { + /* Wait for the capture completion interrupt */ + timeout = wait_event_interruptible_timeout(info->irq_waitq, + test_bit(ST_CAPT_IRQ, &info->flags), + msecs_to_jiffies(2000)); + if (test_and_clear_bit(ST_CAPT_IRQ, &info->flags)) { + ret = m5mols_capture_info(info); + if (!ret) + v4l2_subdev_notify(sd, 0, &info->cap.total); + } + } + + return m5mols_capture_error_handler(info, timeout); +} diff --git a/drivers/media/video/m5mols/m5mols_controls.c b/drivers/media/video/m5mols/m5mols_controls.c new file mode 100644 index 00000000000..817c16fec36 --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_controls.c @@ -0,0 +1,299 @@ +/* + * Controls for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@samsung.com + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@samsung.com + * + * 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. + */ + +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +static struct m5mols_scenemode m5mols_default_scenemode[] = { + [REG_SCENE_NORMAL] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_NORMAL, REG_LIGHT_OFF, REG_FLASH_OFF, + 5, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PORTRAIT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 4, + REG_AF_NORMAL, BIT_FD_EN | BIT_FD_DRAW_FACE_FRAME, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_LANDSCAPE] = { + REG_AE_ALL, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 6, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SPORTS] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_PARTY_INDOOR] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_200, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_BEACH_SNOW] = { + REG_AE_CENTER, REG_AE_INDEX_10_POS, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 4, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_SUNSET] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_PRESET, + REG_AWB_DAYLIGHT, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_DAWN_DUSK] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_PRESET, + REG_AWB_FLUORESCENT_1, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FALL] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 5, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_NIGHT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_AGAINST_LIGHT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_FIRE] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_50, REG_CAP_NONE, REG_WDR_OFF, + }, + [REG_SCENE_TEXT] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 7, + REG_AF_MACRO, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_ANTI_SHAKE, REG_WDR_ON, + }, + [REG_SCENE_CANDLE] = { + REG_AE_CENTER, REG_AE_INDEX_00, REG_AWB_AUTO, 0, + REG_CHROMA_ON, 3, REG_EDGE_ON, 5, + REG_AF_NORMAL, REG_FD_OFF, + REG_MCC_OFF, REG_LIGHT_OFF, REG_FLASH_OFF, + 6, REG_ISO_AUTO, REG_CAP_NONE, REG_WDR_OFF, + }, +}; + +/** + * m5mols_do_scenemode() - Change current scenemode + * @mode: Desired mode of the scenemode + * + * WARNING: The execution order is important. Do not change the order. + */ +int m5mols_do_scenemode(struct m5mols_info *info, u32 mode) +{ + struct v4l2_subdev *sd = &info->sd; + struct m5mols_scenemode scenemode = m5mols_default_scenemode[mode]; + int ret; + + if (mode > REG_SCENE_CANDLE) + return -EINVAL; + + ret = m5mols_lock_3a(info, false); + if (!ret) + ret = m5mols_write(sd, AE_EV_PRESET_MONITOR, mode); + if (!ret) + ret = m5mols_write(sd, AE_EV_PRESET_CAPTURE, mode); + if (!ret) + ret = m5mols_write(sd, AE_MODE, scenemode.metering); + if (!ret) + ret = m5mols_write(sd, AE_INDEX, scenemode.ev_bias); + if (!ret) + ret = m5mols_write(sd, AWB_MODE, scenemode.wb_mode); + if (!ret) + ret = m5mols_write(sd, AWB_MANUAL, scenemode.wb_preset); + if (!ret) + ret = m5mols_write(sd, MON_CHROMA_EN, scenemode.chroma_en); + if (!ret) + ret = m5mols_write(sd, MON_CHROMA_LVL, scenemode.chroma_lvl); + if (!ret) + ret = m5mols_write(sd, MON_EDGE_EN, scenemode.edge_en); + if (!ret) + ret = m5mols_write(sd, MON_EDGE_LVL, scenemode.edge_lvl); + if (!ret && is_available_af(info)) + ret = m5mols_write(sd, AF_MODE, scenemode.af_range); + if (!ret && is_available_af(info)) + ret = m5mols_write(sd, FD_CTL, scenemode.fd_mode); + if (!ret) + ret = m5mols_write(sd, MON_TONE_CTL, scenemode.tone); + if (!ret) + ret = m5mols_write(sd, AE_ISO, scenemode.iso); + if (!ret) + ret = m5mols_mode(info, REG_CAPTURE); + if (!ret) + ret = m5mols_write(sd, CAPP_WDR_EN, scenemode.wdr); + if (!ret) + ret = m5mols_write(sd, CAPP_MCC_MODE, scenemode.mcc); + if (!ret) + ret = m5mols_write(sd, CAPP_LIGHT_CTRL, scenemode.light); + if (!ret) + ret = m5mols_write(sd, CAPP_FLASH_CTRL, scenemode.flash); + if (!ret) + ret = m5mols_write(sd, CAPC_MODE, scenemode.capt_mode); + if (!ret) + ret = m5mols_mode(info, REG_MONITOR); + + return ret; +} + +static int m5mols_lock_ae(struct m5mols_info *info, bool lock) +{ + int ret = 0; + + if (info->lock_ae != lock) + ret = m5mols_write(&info->sd, AE_LOCK, + lock ? REG_AE_LOCK : REG_AE_UNLOCK); + if (!ret) + info->lock_ae = lock; + + return ret; +} + +static int m5mols_lock_awb(struct m5mols_info *info, bool lock) +{ + int ret = 0; + + if (info->lock_awb != lock) + ret = m5mols_write(&info->sd, AWB_LOCK, + lock ? REG_AWB_LOCK : REG_AWB_UNLOCK); + if (!ret) + info->lock_awb = lock; + + return ret; +} + +/* m5mols_lock_3a() - Lock 3A(Auto Exposure, Auto Whitebalance, Auto Focus) */ +int m5mols_lock_3a(struct m5mols_info *info, bool lock) +{ + int ret; + + ret = m5mols_lock_ae(info, lock); + if (!ret) + ret = m5mols_lock_awb(info, lock); + /* Don't need to handle unlocking AF */ + if (!ret && is_available_af(info) && lock) + ret = m5mols_write(&info->sd, AF_EXECUTE, REG_AF_STOP); + + return ret; +} + +/* m5mols_set_ctrl() - The main s_ctrl function called by m5mols_set_ctrl() */ +int m5mols_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int ret; + + switch (ctrl->id) { + case V4L2_CID_ZOOM_ABSOLUTE: + return m5mols_write(sd, MON_ZOOM, ctrl->val); + + case V4L2_CID_EXPOSURE_AUTO: + ret = m5mols_lock_ae(info, + ctrl->val == V4L2_EXPOSURE_AUTO ? false : true); + if (!ret && ctrl->val == V4L2_EXPOSURE_AUTO) + ret = m5mols_write(sd, AE_MODE, REG_AE_ALL); + if (!ret && ctrl->val == V4L2_EXPOSURE_MANUAL) { + int val = info->exposure->val; + ret = m5mols_write(sd, AE_MODE, REG_AE_OFF); + if (!ret) + ret = m5mols_write(sd, AE_MAN_GAIN_MON, val); + if (!ret) + ret = m5mols_write(sd, AE_MAN_GAIN_CAP, val); + } + return ret; + + case V4L2_CID_AUTO_WHITE_BALANCE: + ret = m5mols_lock_awb(info, ctrl->val ? false : true); + if (!ret) + ret = m5mols_write(sd, AWB_MODE, ctrl->val ? + REG_AWB_AUTO : REG_AWB_PRESET); + return ret; + + case V4L2_CID_SATURATION: + ret = m5mols_write(sd, MON_CHROMA_LVL, ctrl->val); + if (!ret) + ret = m5mols_write(sd, MON_CHROMA_EN, REG_CHROMA_ON); + return ret; + + case V4L2_CID_COLORFX: + /* + * This control uses two kinds of registers: normal & color. + * The normal effect belongs to category 1, while the color + * one belongs to category 2. + * + * The normal effect uses one register: CAT1_EFFECT. + * The color effect uses three registers: + * CAT2_COLOR_EFFECT, CAT2_CFIXR, CAT2_CFIXB. + */ + ret = m5mols_write(sd, PARM_EFFECT, + ctrl->val == V4L2_COLORFX_NEGATIVE ? REG_EFFECT_NEGA : + ctrl->val == V4L2_COLORFX_EMBOSS ? REG_EFFECT_EMBOSS : + REG_EFFECT_OFF); + if (!ret) + ret = m5mols_write(sd, MON_EFFECT, + ctrl->val == V4L2_COLORFX_SEPIA ? + REG_COLOR_EFFECT_ON : REG_COLOR_EFFECT_OFF); + if (!ret) + ret = m5mols_write(sd, MON_CFIXR, + ctrl->val == V4L2_COLORFX_SEPIA ? + REG_CFIXR_SEPIA : 0); + if (!ret) + ret = m5mols_write(sd, MON_CFIXB, + ctrl->val == V4L2_COLORFX_SEPIA ? + REG_CFIXB_SEPIA : 0); + return ret; + } + + return -EINVAL; +} diff --git a/drivers/media/video/m5mols/m5mols_core.c b/drivers/media/video/m5mols/m5mols_core.c new file mode 100644 index 00000000000..76eac26e84a --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_core.c @@ -0,0 +1,1004 @@ +/* + * Driver for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@samsung.com + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@samsung.com + * + * 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. + */ + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/version.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> +#include <media/m5mols.h> + +#include "m5mols.h" +#include "m5mols_reg.h" + +int m5mols_debug; +module_param(m5mols_debug, int, 0644); + +#define MODULE_NAME "M5MOLS" +#define M5MOLS_I2C_CHECK_RETRY 500 + +/* The regulator consumer names for external voltage regulators */ +static struct regulator_bulk_data supplies[] = { + { + .supply = "core", /* ARM core power, 1.2V */ + }, { + .supply = "dig_18", /* digital power 1, 1.8V */ + }, { + .supply = "d_sensor", /* sensor power 1, 1.8V */ + }, { + .supply = "dig_28", /* digital power 2, 2.8V */ + }, { + .supply = "a_sensor", /* analog power */ + }, { + .supply = "dig_12", /* digital power 3, 1.2V */ + }, +}; + +static struct v4l2_mbus_framefmt m5mols_default_ffmt[M5MOLS_RESTYPE_MAX] = { + [M5MOLS_RESTYPE_MONITOR] = { + .width = 1920, + .height = 1080, + .code = V4L2_MBUS_FMT_VYUY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + [M5MOLS_RESTYPE_CAPTURE] = { + .width = 1920, + .height = 1080, + .code = V4L2_MBUS_FMT_JPEG_1X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, +}; +#define SIZE_DEFAULT_FFMT ARRAY_SIZE(m5mols_default_ffmt) + +static const struct m5mols_resolution m5mols_reg_res[] = { + { 0x01, M5MOLS_RESTYPE_MONITOR, 128, 96 }, /* SUB-QCIF */ + { 0x03, M5MOLS_RESTYPE_MONITOR, 160, 120 }, /* QQVGA */ + { 0x05, M5MOLS_RESTYPE_MONITOR, 176, 144 }, /* QCIF */ + { 0x06, M5MOLS_RESTYPE_MONITOR, 176, 176 }, + { 0x08, M5MOLS_RESTYPE_MONITOR, 240, 320 }, /* QVGA */ + { 0x09, M5MOLS_RESTYPE_MONITOR, 320, 240 }, /* QVGA */ + { 0x0c, M5MOLS_RESTYPE_MONITOR, 240, 400 }, /* WQVGA */ + { 0x0d, M5MOLS_RESTYPE_MONITOR, 400, 240 }, /* WQVGA */ + { 0x0e, M5MOLS_RESTYPE_MONITOR, 352, 288 }, /* CIF */ + { 0x13, M5MOLS_RESTYPE_MONITOR, 480, 360 }, + { 0x15, M5MOLS_RESTYPE_MONITOR, 640, 360 }, /* qHD */ + { 0x17, M5MOLS_RESTYPE_MONITOR, 640, 480 }, /* VGA */ + { 0x18, M5MOLS_RESTYPE_MONITOR, 720, 480 }, + { 0x1a, M5MOLS_RESTYPE_MONITOR, 800, 480 }, /* WVGA */ + { 0x1f, M5MOLS_RESTYPE_MONITOR, 800, 600 }, /* SVGA */ + { 0x21, M5MOLS_RESTYPE_MONITOR, 1280, 720 }, /* HD */ + { 0x25, M5MOLS_RESTYPE_MONITOR, 1920, 1080 }, /* 1080p */ + { 0x29, M5MOLS_RESTYPE_MONITOR, 3264, 2448 }, /* 2.63fps 8M */ + { 0x39, M5MOLS_RESTYPE_MONITOR, 800, 602 }, /* AHS_MON debug */ + + { 0x02, M5MOLS_RESTYPE_CAPTURE, 320, 240 }, /* QVGA */ + { 0x04, M5MOLS_RESTYPE_CAPTURE, 400, 240 }, /* WQVGA */ + { 0x07, M5MOLS_RESTYPE_CAPTURE, 480, 360 }, + { 0x08, M5MOLS_RESTYPE_CAPTURE, 640, 360 }, /* qHD */ + { 0x09, M5MOLS_RESTYPE_CAPTURE, 640, 480 }, /* VGA */ + { 0x0a, M5MOLS_RESTYPE_CAPTURE, 800, 480 }, /* WVGA */ + { 0x10, M5MOLS_RESTYPE_CAPTURE, 1280, 720 }, /* HD */ + { 0x14, M5MOLS_RESTYPE_CAPTURE, 1280, 960 }, /* 1M */ + { 0x17, M5MOLS_RESTYPE_CAPTURE, 1600, 1200 }, /* 2M */ + { 0x19, M5MOLS_RESTYPE_CAPTURE, 1920, 1080 }, /* Full-HD */ + { 0x1a, M5MOLS_RESTYPE_CAPTURE, 2048, 1152 }, /* 3Mega */ + { 0x1b, M5MOLS_RESTYPE_CAPTURE, 2048, 1536 }, + { 0x1c, M5MOLS_RESTYPE_CAPTURE, 2560, 1440 }, /* 4Mega */ + { 0x1d, M5MOLS_RESTYPE_CAPTURE, 2560, 1536 }, + { 0x1f, M5MOLS_RESTYPE_CAPTURE, 2560, 1920 }, /* 5Mega */ + { 0x21, M5MOLS_RESTYPE_CAPTURE, 3264, 1836 }, /* 6Mega */ + { 0x22, M5MOLS_RESTYPE_CAPTURE, 3264, 1960 }, + { 0x25, M5MOLS_RESTYPE_CAPTURE, 3264, 2448 }, /* 8Mega */ +}; + +/** + * m5mols_swap_byte - an byte array to integer conversion function + * @size: size in bytes of I2C packet defined in the M-5MOLS datasheet + * + * Convert I2C data byte array with performing any required byte + * reordering to assure proper values for each data type, regardless + * of the architecture endianness. + */ +static u32 m5mols_swap_byte(u8 *data, u8 length) +{ + if (length == 1) + return *data; + else if (length == 2) + return be16_to_cpu(*((u16 *)data)); + else + return be32_to_cpu(*((u32 *)data)); +} + +/** + * m5mols_read - I2C read function + * @reg: combination of size, category and command for the I2C packet + * @val: read value + */ +int m5mols_read(struct v4l2_subdev *sd, u32 reg, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 rbuf[M5MOLS_I2C_MAX_SIZE + 1]; + u8 size = I2C_SIZE(reg); + u8 category = I2C_CATEGORY(reg); + u8 cmd = I2C_COMMAND(reg); + struct i2c_msg msg[2]; + u8 wbuf[5]; + int ret; + + if (!client->adapter) + return -ENODEV; + + if (size != 1 && size != 2 && size != 4) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 5; + msg[0].buf = wbuf; + wbuf[0] = 5; + wbuf[1] = M5MOLS_BYTE_READ; + wbuf[2] = category; + wbuf[3] = cmd; + wbuf[4] = size; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = size + 1; + msg[1].buf = rbuf; + + /* minimum stabilization time */ + usleep_range(200, 200); + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + v4l2_err(sd, "read failed: size:%d cat:%02x cmd:%02x. %d\n", + size, category, cmd, ret); + return ret; + } + + *val = m5mols_swap_byte(&rbuf[1], size); + + return 0; +} + +/** + * m5mols_write - I2C command write function + * @reg: combination of size, category and command for the I2C packet + * @val: value to write + */ +int m5mols_write(struct v4l2_subdev *sd, u32 reg, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 wbuf[M5MOLS_I2C_MAX_SIZE + 4]; + u8 category = I2C_CATEGORY(reg); + u8 cmd = I2C_COMMAND(reg); + u8 size = I2C_SIZE(reg); + u32 *buf = (u32 *)&wbuf[4]; + struct i2c_msg msg[1]; + int ret; + + if (!client->adapter) + return -ENODEV; + + if (size != 1 && size != 2 && size != 4) { + v4l2_err(sd, "Wrong data size\n"); + return -EINVAL; + } + + msg->addr = client->addr; + msg->flags = 0; + msg->len = (u16)size + 4; + msg->buf = wbuf; + wbuf[0] = size + 4; + wbuf[1] = M5MOLS_BYTE_WRITE; + wbuf[2] = category; + wbuf[3] = cmd; + + *buf = m5mols_swap_byte((u8 *)&val, size); + + usleep_range(200, 200); + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) { + v4l2_err(sd, "write failed: size:%d cat:%02x cmd:%02x. %d\n", + size, category, cmd, ret); + return ret; + } + + return 0; +} + +int m5mols_busy(struct v4l2_subdev *sd, u8 category, u8 cmd, u32 mask) +{ + u32 busy, i; + int ret; + + for (i = 0; i < M5MOLS_I2C_CHECK_RETRY; i++) { + ret = m5mols_read(sd, I2C_REG(category, cmd, 1), &busy); + if (ret < 0) + return ret; + if ((busy & mask) == mask) + return 0; + } + return -EBUSY; +} + +/** + * m5mols_enable_interrupt - Clear interrupt pending bits and unmask interrupts + * + * Before writing desired interrupt value the INT_FACTOR register should + * be read to clear pending interrupts. + */ +int m5mols_enable_interrupt(struct v4l2_subdev *sd, u32 reg) +{ + struct m5mols_info *info = to_m5mols(sd); + u32 mask = is_available_af(info) ? REG_INT_AF : 0; + u32 dummy; + int ret; + + ret = m5mols_read(sd, SYSTEM_INT_FACTOR, &dummy); + if (!ret) + ret = m5mols_write(sd, SYSTEM_INT_ENABLE, reg & ~mask); + return ret; +} + +/** + * m5mols_reg_mode - Write the mode and check busy status + * + * It always accompanies a little delay changing the M-5MOLS mode, so it is + * needed checking current busy status to guarantee right mode. + */ +static int m5mols_reg_mode(struct v4l2_subdev *sd, u32 mode) +{ + int ret = m5mols_write(sd, SYSTEM_SYSMODE, mode); + + return ret ? ret : m5mols_busy(sd, CAT_SYSTEM, CAT0_SYSMODE, mode); +} + +/** + * m5mols_mode - manage the M-5MOLS's mode + * @mode: the required operation mode + * + * The commands of M-5MOLS are grouped into specific modes. Each functionality + * can be guaranteed only when the sensor is operating in mode which which + * a command belongs to. + */ +int m5mols_mode(struct m5mols_info *info, u32 mode) +{ + struct v4l2_subdev *sd = &info->sd; + int ret = -EINVAL; + u32 reg; + + if (mode < REG_PARAMETER && mode > REG_CAPTURE) + return ret; + + ret = m5mols_read(sd, SYSTEM_SYSMODE, ®); + if ((!ret && reg == mode) || ret) + return ret; + + switch (reg) { + case REG_PARAMETER: + ret = m5mols_reg_mode(sd, REG_MONITOR); + if (!ret && mode == REG_MONITOR) + break; + if (!ret) + ret = m5mols_reg_mode(sd, REG_CAPTURE); + break; + + case REG_MONITOR: + if (mode == REG_PARAMETER) { + ret = m5mols_reg_mode(sd, REG_PARAMETER); + break; + } + + ret = m5mols_reg_mode(sd, REG_CAPTURE); + break; + + case REG_CAPTURE: + ret = m5mols_reg_mode(sd, REG_MONITOR); + if (!ret && mode == REG_MONITOR) + break; + if (!ret) + ret = m5mols_reg_mode(sd, REG_PARAMETER); + break; + + default: + v4l2_warn(sd, "Wrong mode: %d\n", mode); + } + + if (!ret) + info->mode = mode; + + return ret; +} + +/** + * m5mols_get_version - retrieve full revisions information of M-5MOLS + * + * The version information includes revisions of hardware and firmware, + * AutoFocus alghorithm version and the version string. + */ +static int m5mols_get_version(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + union { + struct m5mols_version ver; + u8 bytes[VERSION_SIZE]; + } version; + u32 *value; + u8 cmd = CAT0_VER_CUSTOMER; + int ret; + + do { + value = (u32 *)&version.bytes[cmd]; + ret = m5mols_read(sd, SYSTEM_CMD(cmd), value); + if (ret) + return ret; + } while (cmd++ != CAT0_VER_AWB); + + do { + value = (u32 *)&version.bytes[cmd]; + ret = m5mols_read(sd, SYSTEM_VER_STRING, value); + if (ret) + return ret; + if (cmd >= VERSION_SIZE - 1) + return -EINVAL; + } while (version.bytes[cmd++]); + + value = (u32 *)&version.bytes[cmd]; + ret = m5mols_read(sd, AF_VERSION, value); + if (ret) + return ret; + + /* store version information swapped for being readable */ + info->ver = version.ver; + info->ver.fw = be16_to_cpu(info->ver.fw); + info->ver.hw = be16_to_cpu(info->ver.hw); + info->ver.param = be16_to_cpu(info->ver.param); + info->ver.awb = be16_to_cpu(info->ver.awb); + + v4l2_info(sd, "Manufacturer\t[%s]\n", + is_manufacturer(info, REG_SAMSUNG_ELECTRO) ? + "Samsung Electro-Machanics" : + is_manufacturer(info, REG_SAMSUNG_OPTICS) ? + "Samsung Fiber-Optics" : + is_manufacturer(info, REG_SAMSUNG_TECHWIN) ? + "Samsung Techwin" : "None"); + v4l2_info(sd, "Customer/Project\t[0x%02x/0x%02x]\n", + info->ver.customer, info->ver.project); + + if (!is_available_af(info)) + v4l2_info(sd, "No support Auto Focus on this firmware\n"); + + return ret; +} + +/** + * __find_restype - Lookup M-5MOLS resolution type according to pixel code + * @code: pixel code + */ +static enum m5mols_restype __find_restype(enum v4l2_mbus_pixelcode code) +{ + enum m5mols_restype type = M5MOLS_RESTYPE_MONITOR; + + do { + if (code == m5mols_default_ffmt[type].code) + return type; + } while (type++ != SIZE_DEFAULT_FFMT); + + return 0; +} + +/** + * __find_resolution - Lookup preset and type of M-5MOLS's resolution + * @mf: pixel format to find/negotiate the resolution preset for + * @type: M-5MOLS resolution type + * @resolution: M-5MOLS resolution preset register value + * + * Find nearest resolution matching resolution preset and adjust mf + * to supported values. + */ +static int __find_resolution(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf, + enum m5mols_restype *type, + u32 *resolution) +{ + const struct m5mols_resolution *fsize = &m5mols_reg_res[0]; + const struct m5mols_resolution *match = NULL; + enum m5mols_restype stype = __find_restype(mf->code); + int i = ARRAY_SIZE(m5mols_reg_res); + unsigned int min_err = ~0; + + while (i--) { + int err; + if (stype == fsize->type) { + err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + + if (err < min_err) { + min_err = err; + match = fsize; + } + } + fsize++; + } + if (match) { + mf->width = match->width; + mf->height = match->height; + *resolution = match->reg; + *type = stype; + return 0; + } + + return -EINVAL; +} + +static struct v4l2_mbus_framefmt *__find_format(struct m5mols_info *info, + struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which, + enum m5mols_restype type) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return fh ? v4l2_subdev_get_try_format(fh, 0) : NULL; + + return &info->ffmt[type]; +} + +static int m5mols_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format; + + if (fmt->pad != 0) + return -EINVAL; + + format = __find_format(info, fh, fmt->which, info->res_type); + if (!format) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +static int m5mols_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct m5mols_info *info = to_m5mols(sd); + struct v4l2_mbus_framefmt *format = &fmt->format; + struct v4l2_mbus_framefmt *sfmt; + enum m5mols_restype type; + u32 resolution = 0; + int ret; + + if (fmt->pad != 0) + return -EINVAL; + + ret = __find_resolution(sd, format, &type, &resolution); + if (ret < 0) + return ret; + + sfmt = __find_format(info, fh, fmt->which, type); + if (!sfmt) + return 0; + + *sfmt = m5mols_default_ffmt[type]; + sfmt->width = format->width; + sfmt->height = format->height; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + info->resolution = resolution; + info->code = format->code; + info->res_type = type; + } + + return 0; +} + +static int m5mols_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (!code || code->index >= SIZE_DEFAULT_FFMT) + return -EINVAL; + + code->code = m5mols_default_ffmt[code->index].code; + + return 0; +} + +static struct v4l2_subdev_pad_ops m5mols_pad_ops = { + .enum_mbus_code = m5mols_enum_mbus_code, + .get_fmt = m5mols_get_fmt, + .set_fmt = m5mols_set_fmt, +}; + +/** + * m5mols_sync_controls - Apply default scene mode and the current controls + * + * This is used only streaming for syncing between v4l2_ctrl framework and + * m5mols's controls. First, do the scenemode to the sensor, then call + * v4l2_ctrl_handler_setup. It can be same between some commands and + * the scenemode's in the default v4l2_ctrls. But, such commands of control + * should be prior to the scenemode's one. + */ +int m5mols_sync_controls(struct m5mols_info *info) +{ + int ret = -EINVAL; + + if (!is_ctrl_synced(info)) { + ret = m5mols_do_scenemode(info, REG_SCENE_NORMAL); + if (ret) + return ret; + + v4l2_ctrl_handler_setup(&info->handle); + info->ctrl_sync = true; + } + + return ret; +} + +/** + * m5mols_start_monitor - Start the monitor mode + * + * Before applying the controls setup the resolution and frame rate + * in PARAMETER mode, and then switch over to MONITOR mode. + */ +static int m5mols_start_monitor(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + int ret; + + ret = m5mols_mode(info, REG_PARAMETER); + if (!ret) + ret = m5mols_write(sd, PARM_MON_SIZE, info->resolution); + if (!ret) + ret = m5mols_write(sd, PARM_MON_FPS, REG_FPS_30); + if (!ret) + ret = m5mols_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_sync_controls(info); + + return ret; +} + +static int m5mols_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct m5mols_info *info = to_m5mols(sd); + + if (enable) { + int ret = -EINVAL; + + if (is_code(info->code, M5MOLS_RESTYPE_MONITOR)) + ret = m5mols_start_monitor(info); + if (is_code(info->code, M5MOLS_RESTYPE_CAPTURE)) + ret = m5mols_start_capture(info); + + return ret; + } + + return m5mols_mode(info, REG_PARAMETER); +} + +static const struct v4l2_subdev_video_ops m5mols_video_ops = { + .s_stream = m5mols_s_stream, +}; + +static int m5mols_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct m5mols_info *info = to_m5mols(sd); + int ret; + + info->mode_save = info->mode; + + ret = m5mols_mode(info, REG_PARAMETER); + if (!ret) + ret = m5mols_set_ctrl(ctrl); + if (!ret) + ret = m5mols_mode(info, info->mode_save); + + return ret; +} + +static const struct v4l2_ctrl_ops m5mols_ctrl_ops = { + .s_ctrl = m5mols_s_ctrl, +}; + +static int m5mols_sensor_power(struct m5mols_info *info, bool enable) +{ + struct v4l2_subdev *sd = &info->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct m5mols_platform_data *pdata = info->pdata; + int ret; + + if (enable) { + if (is_powered(info)) + return 0; + + if (info->set_power) { + ret = info->set_power(&client->dev, 1); + if (ret) + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + if (ret) { + info->set_power(&client->dev, 0); + return ret; + } + + gpio_set_value(pdata->gpio_reset, !pdata->reset_polarity); + usleep_range(1000, 1000); + info->power = true; + + return ret; + } + + if (!is_powered(info)) + return 0; + + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + if (ret) + return ret; + + if (info->set_power) + info->set_power(&client->dev, 0); + + gpio_set_value(pdata->gpio_reset, pdata->reset_polarity); + usleep_range(1000, 1000); + info->power = false; + + return ret; +} + +/* m5mols_update_fw - optional firmware update routine */ +int __attribute__ ((weak)) m5mols_update_fw(struct v4l2_subdev *sd, + int (*set_power)(struct m5mols_info *, bool)) +{ + return 0; +} + +/** + * m5mols_sensor_armboot - Booting M-5MOLS internal ARM core. + * + * Booting internal ARM core makes the M-5MOLS is ready for getting commands + * with I2C. It's the first thing to be done after it powered up. It must wait + * at least 520ms recommended by M-5MOLS datasheet, after executing arm booting. + */ +static int m5mols_sensor_armboot(struct v4l2_subdev *sd) +{ + int ret; + + ret = m5mols_write(sd, FLASH_CAM_START, REG_START_ARM_BOOT); + if (ret < 0) + return ret; + + msleep(520); + + ret = m5mols_get_version(sd); + if (!ret) + ret = m5mols_update_fw(sd, m5mols_sensor_power); + if (ret) + return ret; + + v4l2_dbg(1, m5mols_debug, sd, "Success ARM Booting\n"); + + ret = m5mols_write(sd, PARM_INTERFACE, REG_INTERFACE_MIPI); + if (!ret) + ret = m5mols_enable_interrupt(sd, REG_INT_AF); + + return ret; +} + +static int m5mols_init_controls(struct m5mols_info *info) +{ + struct v4l2_subdev *sd = &info->sd; + u16 max_exposure; + u16 step_zoom; + int ret; + + /* Determine value's range & step of controls for various FW version */ + ret = m5mols_read(sd, AE_MAX_GAIN_MON, (u32 *)&max_exposure); + if (!ret) + step_zoom = is_manufacturer(info, REG_SAMSUNG_OPTICS) ? 31 : 1; + if (ret) + return ret; + + v4l2_ctrl_handler_init(&info->handle, 6); + info->autowb = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 0); + info->saturation = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_SATURATION, + 1, 5, 1, 3); + info->zoom = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_ZOOM_ABSOLUTE, + 1, 70, step_zoom, 1); + info->exposure = v4l2_ctrl_new_std(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE, + 0, max_exposure, 1, (int)max_exposure/2); + info->colorfx = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_COLORFX, + 4, (1 << V4L2_COLORFX_BW), V4L2_COLORFX_NONE); + info->autoexposure = v4l2_ctrl_new_std_menu(&info->handle, + &m5mols_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, + 1, 0, V4L2_EXPOSURE_MANUAL); + + sd->ctrl_handler = &info->handle; + if (info->handle.error) { + v4l2_err(sd, "Failed to initialize controls: %d\n", ret); + v4l2_ctrl_handler_free(&info->handle); + return info->handle.error; + } + + v4l2_ctrl_cluster(2, &info->autoexposure); + + return 0; +} + +/** + * m5mols_s_power - Main sensor power control function + * + * To prevent breaking the lens when the sensor is powered off the Soft-Landing + * algorithm is called where available. The Soft-Landing algorithm availability + * dependends on the firmware provider. + */ +static int m5mols_s_power(struct v4l2_subdev *sd, int on) +{ + struct m5mols_info *info = to_m5mols(sd); + int ret; + + if (on) { + ret = m5mols_sensor_power(info, true); + if (!ret) + ret = m5mols_sensor_armboot(sd); + if (!ret) + ret = m5mols_init_controls(info); + if (ret) + return ret; + + info->ffmt[M5MOLS_RESTYPE_MONITOR] = + m5mols_default_ffmt[M5MOLS_RESTYPE_MONITOR]; + info->ffmt[M5MOLS_RESTYPE_CAPTURE] = + m5mols_default_ffmt[M5MOLS_RESTYPE_CAPTURE]; + return ret; + } + + if (is_manufacturer(info, REG_SAMSUNG_TECHWIN)) { + ret = m5mols_mode(info, REG_MONITOR); + if (!ret) + ret = m5mols_write(sd, AF_EXECUTE, REG_AF_STOP); + if (!ret) + ret = m5mols_write(sd, AF_MODE, REG_AF_POWEROFF); + if (!ret) + ret = m5mols_busy(sd, CAT_SYSTEM, CAT0_STATUS, + REG_AF_IDLE); + if (!ret) + v4l2_info(sd, "Success soft-landing lens\n"); + } + + ret = m5mols_sensor_power(info, false); + if (!ret) { + v4l2_ctrl_handler_free(&info->handle); + info->ctrl_sync = false; + } + + return ret; +} + +static int m5mols_log_status(struct v4l2_subdev *sd) +{ + struct m5mols_info *info = to_m5mols(sd); + + v4l2_ctrl_handler_log_status(&info->handle, sd->name); + + return 0; +} + +static const struct v4l2_subdev_core_ops m5mols_core_ops = { + .s_power = m5mols_s_power, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .log_status = m5mols_log_status, +}; + +static const struct v4l2_subdev_ops m5mols_ops = { + .core = &m5mols_core_ops, + .pad = &m5mols_pad_ops, + .video = &m5mols_video_ops, +}; + +static void m5mols_irq_work(struct work_struct *work) +{ + struct m5mols_info *info = + container_of(work, struct m5mols_info, work_irq); + struct v4l2_subdev *sd = &info->sd; + u32 reg; + int ret; + + if (!is_powered(info) || + m5mols_read(sd, SYSTEM_INT_FACTOR, &info->interrupt)) + return; + + switch (info->interrupt & REG_INT_MASK) { + case REG_INT_AF: + if (!is_available_af(info)) + break; + ret = m5mols_read(sd, AF_STATUS, ®); + v4l2_dbg(2, m5mols_debug, sd, "AF %s\n", + reg == REG_AF_FAIL ? "Failed" : + reg == REG_AF_SUCCESS ? "Success" : + reg == REG_AF_IDLE ? "Idle" : "Busy"); + break; + case REG_INT_CAPTURE: + if (!test_and_set_bit(ST_CAPT_IRQ, &info->flags)) + wake_up_interruptible(&info->irq_waitq); + + v4l2_dbg(2, m5mols_debug, sd, "CAPTURE\n"); + break; + default: + v4l2_dbg(2, m5mols_debug, sd, "Undefined: %02x\n", reg); + break; + }; +} + +static irqreturn_t m5mols_irq_handler(int irq, void *data) +{ + struct v4l2_subdev *sd = data; + struct m5mols_info *info = to_m5mols(sd); + + schedule_work(&info->work_irq); + + return IRQ_HANDLED; +} + +static int __devinit m5mols_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct m5mols_platform_data *pdata = client->dev.platform_data; + struct m5mols_info *info; + struct v4l2_subdev *sd; + int ret; + + if (pdata == NULL) { + dev_err(&client->dev, "No platform data\n"); + return -EINVAL; + } + + if (!gpio_is_valid(pdata->gpio_reset)) { + dev_err(&client->dev, "No valid RESET GPIO specified\n"); + return -EINVAL; + } + + if (!pdata->irq) { + dev_err(&client->dev, "Interrupt not assigned\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct m5mols_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdata = pdata; + info->set_power = pdata->set_power; + + ret = gpio_request(pdata->gpio_reset, "M5MOLS_NRST"); + if (ret) { + dev_err(&client->dev, "Failed to request gpio: %d\n", ret); + goto out_free; + } + gpio_direction_output(pdata->gpio_reset, pdata->reset_polarity); + + ret = regulator_bulk_get(&client->dev, ARRAY_SIZE(supplies), supplies); + if (ret) { + dev_err(&client->dev, "Failed to get regulators: %d\n", ret); + goto out_gpio; + } + + sd = &info->sd; + strlcpy(sd->name, MODULE_NAME, sizeof(sd->name)); + v4l2_i2c_subdev_init(sd, client, &m5mols_ops); + + info->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&sd->entity, 1, &info->pad, 0); + if (ret < 0) + goto out_reg; + sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + + init_waitqueue_head(&info->irq_waitq); + INIT_WORK(&info->work_irq, m5mols_irq_work); + ret = request_irq(pdata->irq, m5mols_irq_handler, + IRQF_TRIGGER_RISING, MODULE_NAME, sd); + if (ret) { + dev_err(&client->dev, "Interrupt request failed: %d\n", ret); + goto out_me; + } + info->res_type = M5MOLS_RESTYPE_MONITOR; + return 0; +out_me: + media_entity_cleanup(&sd->entity); +out_reg: + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); +out_gpio: + gpio_free(pdata->gpio_reset); +out_free: + kfree(info); + return ret; +} + +static int __devexit m5mols_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct m5mols_info *info = to_m5mols(sd); + + v4l2_device_unregister_subdev(sd); + free_irq(info->pdata->irq, sd); + + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); + gpio_free(info->pdata->gpio_reset); + media_entity_cleanup(&sd->entity); + kfree(info); + return 0; +} + +static const struct i2c_device_id m5mols_id[] = { + { MODULE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, m5mols_id); + +static struct i2c_driver m5mols_i2c_driver = { + .driver = { + .name = MODULE_NAME, + }, + .probe = m5mols_probe, + .remove = __devexit_p(m5mols_remove), + .id_table = m5mols_id, +}; + +static int __init m5mols_mod_init(void) +{ + return i2c_add_driver(&m5mols_i2c_driver); +} + +static void __exit m5mols_mod_exit(void) +{ + i2c_del_driver(&m5mols_i2c_driver); +} + +module_init(m5mols_mod_init); +module_exit(m5mols_mod_exit); + +MODULE_AUTHOR("HeungJun Kim <riverful.kim@samsung.com>"); +MODULE_AUTHOR("Dongsoo Kim <dongsoo45.kim@samsung.com>"); +MODULE_DESCRIPTION("Fujitsu M-5MOLS 8M Pixel camera driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/m5mols/m5mols_reg.h b/drivers/media/video/m5mols/m5mols_reg.h new file mode 100644 index 00000000000..b83e36fc6ac --- /dev/null +++ b/drivers/media/video/m5mols/m5mols_reg.h @@ -0,0 +1,399 @@ +/* + * Register map for M-5MOLS 8M Pixel camera sensor with ISP + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Author: HeungJun Kim, riverful.kim@samsung.com + * + * Copyright (C) 2009 Samsung Electronics Co., Ltd. + * Author: Dongsoo Nathaniel Kim, dongsoo45.kim@samsung.com + * + * 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. + */ + +#ifndef M5MOLS_REG_H +#define M5MOLS_REG_H + +#define M5MOLS_I2C_MAX_SIZE 4 +#define M5MOLS_BYTE_READ 0x01 +#define M5MOLS_BYTE_WRITE 0x02 + +#define I2C_CATEGORY(__cat) ((__cat >> 16) & 0xff) +#define I2C_COMMAND(__comm) ((__comm >> 8) & 0xff) +#define I2C_SIZE(__reg_s) ((__reg_s) & 0xff) +#define I2C_REG(__cat, __cmd, __reg_s) ((__cat << 16) | (__cmd << 8) | __reg_s) + +/* + * Category section register + * + * The category means set including relevant command of M-5MOLS. + */ +#define CAT_SYSTEM 0x00 +#define CAT_PARAM 0x01 +#define CAT_MONITOR 0x02 +#define CAT_AE 0x03 +#define CAT_WB 0x06 +#define CAT_EXIF 0x07 +#define CAT_FD 0x09 +#define CAT_LENS 0x0a +#define CAT_CAPT_PARM 0x0b +#define CAT_CAPT_CTRL 0x0c +#define CAT_FLASH 0x0f /* related to FW, revisions, booting */ + +/* + * Category 0 - SYSTEM mode + * + * The SYSTEM mode in the M-5MOLS means area available to handle with the whole + * & all-round system of sensor. It deals with version/interrupt/setting mode & + * even sensor's status. Especially, the M-5MOLS sensor with ISP varies by + * packaging & manufacturer, even the customer and project code. And the + * function details may vary among them. The version information helps to + * determine what methods shall be used in the driver. + * + * There is many registers between customer version address and awb one. For + * more specific contents, see definition if file m5mols.h. + */ +#define CAT0_VER_CUSTOMER 0x00 /* customer version */ +#define CAT0_VER_AWB 0x09 /* Auto WB version */ +#define CAT0_VER_STRING 0x0a /* string including M-5MOLS */ +#define CAT0_SYSMODE 0x0b /* SYSTEM mode register */ +#define CAT0_STATUS 0x0c /* SYSTEM mode status register */ +#define CAT0_INT_FACTOR 0x10 /* interrupt pending register */ +#define CAT0_INT_ENABLE 0x11 /* interrupt enable register */ + +#define SYSTEM_SYSMODE I2C_REG(CAT_SYSTEM, CAT0_SYSMODE, 1) +#define REG_SYSINIT 0x00 /* SYSTEM mode */ +#define REG_PARAMETER 0x01 /* PARAMETER mode */ +#define REG_MONITOR 0x02 /* MONITOR mode */ +#define REG_CAPTURE 0x03 /* CAPTURE mode */ + +#define SYSTEM_CMD(__cmd) I2C_REG(CAT_SYSTEM, cmd, 1) +#define SYSTEM_VER_STRING I2C_REG(CAT_SYSTEM, CAT0_VER_STRING, 1) +#define REG_SAMSUNG_ELECTRO "SE" /* Samsung Electro-Mechanics */ +#define REG_SAMSUNG_OPTICS "OP" /* Samsung Fiber-Optics */ +#define REG_SAMSUNG_TECHWIN "TB" /* Samsung Techwin */ + +#define SYSTEM_INT_FACTOR I2C_REG(CAT_SYSTEM, CAT0_INT_FACTOR, 1) +#define SYSTEM_INT_ENABLE I2C_REG(CAT_SYSTEM, CAT0_INT_ENABLE, 1) +#define REG_INT_MODE (1 << 0) +#define REG_INT_AF (1 << 1) +#define REG_INT_ZOOM (1 << 2) +#define REG_INT_CAPTURE (1 << 3) +#define REG_INT_FRAMESYNC (1 << 4) +#define REG_INT_FD (1 << 5) +#define REG_INT_LENS_INIT (1 << 6) +#define REG_INT_SOUND (1 << 7) +#define REG_INT_MASK 0x0f + +/* + * category 1 - PARAMETER mode + * + * This category supports function of camera features of M-5MOLS. It means we + * can handle with preview(MONITOR) resolution size/frame per second/interface + * between the sensor and the Application Processor/even the image effect. + */ +#define CAT1_DATA_INTERFACE 0x00 /* interface between sensor and AP */ +#define CAT1_MONITOR_SIZE 0x01 /* resolution at the MONITOR mode */ +#define CAT1_MONITOR_FPS 0x02 /* frame per second at this mode */ +#define CAT1_EFFECT 0x0b /* image effects */ + +#define PARM_MON_SIZE I2C_REG(CAT_PARAM, CAT1_MONITOR_SIZE, 1) + +#define PARM_MON_FPS I2C_REG(CAT_PARAM, CAT1_MONITOR_FPS, 1) +#define REG_FPS_30 0x02 + +#define PARM_INTERFACE I2C_REG(CAT_PARAM, CAT1_DATA_INTERFACE, 1) +#define REG_INTERFACE_MIPI 0x02 + +#define PARM_EFFECT I2C_REG(CAT_PARAM, CAT1_EFFECT, 1) +#define REG_EFFECT_OFF 0x00 +#define REG_EFFECT_NEGA 0x01 +#define REG_EFFECT_EMBOSS 0x06 +#define REG_EFFECT_OUTLINE 0x07 +#define REG_EFFECT_WATERCOLOR 0x08 + +/* + * Category 2 - MONITOR mode + * + * The MONITOR mode is same as preview mode as we said. The M-5MOLS has another + * mode named "Preview", but this preview mode is used at the case specific + * vider-recording mode. This mmode supports only YUYV format. On the other + * hand, the JPEG & RAW formats is supports by CAPTURE mode. And, there are + * another options like zoom/color effect(different with effect in PARAMETER + * mode)/anti hand shaking algorithm. + */ +#define CAT2_ZOOM 0x01 /* set the zoom position & execute */ +#define CAT2_ZOOM_STEP 0x03 /* set the zoom step */ +#define CAT2_CFIXB 0x09 /* CB value for color effect */ +#define CAT2_CFIXR 0x0a /* CR value for color effect */ +#define CAT2_COLOR_EFFECT 0x0b /* set on/off of color effect */ +#define CAT2_CHROMA_LVL 0x0f /* set chroma level */ +#define CAT2_CHROMA_EN 0x10 /* set on/off of choroma */ +#define CAT2_EDGE_LVL 0x11 /* set sharpness level */ +#define CAT2_EDGE_EN 0x12 /* set on/off sharpness */ +#define CAT2_TONE_CTL 0x25 /* set tone color(contrast) */ + +#define MON_ZOOM I2C_REG(CAT_MONITOR, CAT2_ZOOM, 1) + +#define MON_CFIXR I2C_REG(CAT_MONITOR, CAT2_CFIXR, 1) +#define MON_CFIXB I2C_REG(CAT_MONITOR, CAT2_CFIXB, 1) +#define REG_CFIXB_SEPIA 0xd8 +#define REG_CFIXR_SEPIA 0x18 + +#define MON_EFFECT I2C_REG(CAT_MONITOR, CAT2_COLOR_EFFECT, 1) +#define REG_COLOR_EFFECT_OFF 0x00 +#define REG_COLOR_EFFECT_ON 0x01 + +#define MON_CHROMA_EN I2C_REG(CAT_MONITOR, CAT2_CHROMA_EN, 1) +#define MON_CHROMA_LVL I2C_REG(CAT_MONITOR, CAT2_CHROMA_LVL, 1) +#define REG_CHROMA_OFF 0x00 +#define REG_CHROMA_ON 0x01 + +#define MON_EDGE_EN I2C_REG(CAT_MONITOR, CAT2_EDGE_EN, 1) +#define MON_EDGE_LVL I2C_REG(CAT_MONITOR, CAT2_EDGE_LVL, 1) +#define REG_EDGE_OFF 0x00 +#define REG_EDGE_ON 0x01 + +#define MON_TONE_CTL I2C_REG(CAT_MONITOR, CAT2_TONE_CTL, 1) + +/* + * Category 3 - Auto Exposure + * + * The M-5MOLS exposure capbility is detailed as which is similar to digital + * camera. This category supports AE locking/various AE mode(range of exposure) + * /ISO/flickering/EV bias/shutter/meteoring, and anything else. And the + * maximum/minimum exposure gain value depending on M-5MOLS firmware, may be + * different. So, this category also provide getting the max/min values. And, + * each MONITOR and CAPTURE mode has each gain/shutter/max exposure values. + */ +#define CAT3_AE_LOCK 0x00 /* locking Auto exposure */ +#define CAT3_AE_MODE 0x01 /* set AE mode, mode means range */ +#define CAT3_ISO 0x05 /* set ISO */ +#define CAT3_EV_PRESET_MONITOR 0x0a /* EV(scenemode) preset for MONITOR */ +#define CAT3_EV_PRESET_CAPTURE 0x0b /* EV(scenemode) preset for CAPTURE */ +#define CAT3_MANUAL_GAIN_MON 0x12 /* meteoring value for the MONITOR */ +#define CAT3_MAX_GAIN_MON 0x1a /* max gain value for the MONITOR */ +#define CAT3_MANUAL_GAIN_CAP 0x26 /* meteoring value for the CAPTURE */ +#define CAT3_AE_INDEX 0x38 /* AE index */ + +#define AE_LOCK I2C_REG(CAT_AE, CAT3_AE_LOCK, 1) +#define REG_AE_UNLOCK 0x00 +#define REG_AE_LOCK 0x01 + +#define AE_MODE I2C_REG(CAT_AE, CAT3_AE_MODE, 1) +#define REG_AE_OFF 0x00 /* AE off */ +#define REG_AE_ALL 0x01 /* calc AE in all block integral */ +#define REG_AE_CENTER 0x03 /* calc AE in center weighted */ +#define REG_AE_SPOT 0x06 /* calc AE in specific spot */ + +#define AE_ISO I2C_REG(CAT_AE, CAT3_ISO, 1) +#define REG_ISO_AUTO 0x00 +#define REG_ISO_50 0x01 +#define REG_ISO_100 0x02 +#define REG_ISO_200 0x03 +#define REG_ISO_400 0x04 +#define REG_ISO_800 0x05 + +#define AE_EV_PRESET_MONITOR I2C_REG(CAT_AE, CAT3_EV_PRESET_MONITOR, 1) +#define AE_EV_PRESET_CAPTURE I2C_REG(CAT_AE, CAT3_EV_PRESET_CAPTURE, 1) +#define REG_SCENE_NORMAL 0x00 +#define REG_SCENE_PORTRAIT 0x01 +#define REG_SCENE_LANDSCAPE 0x02 +#define REG_SCENE_SPORTS 0x03 +#define REG_SCENE_PARTY_INDOOR 0x04 +#define REG_SCENE_BEACH_SNOW 0x05 +#define REG_SCENE_SUNSET 0x06 +#define REG_SCENE_DAWN_DUSK 0x07 +#define REG_SCENE_FALL 0x08 +#define REG_SCENE_NIGHT 0x09 +#define REG_SCENE_AGAINST_LIGHT 0x0a +#define REG_SCENE_FIRE 0x0b +#define REG_SCENE_TEXT 0x0c +#define REG_SCENE_CANDLE 0x0d + +#define AE_MAN_GAIN_MON I2C_REG(CAT_AE, CAT3_MANUAL_GAIN_MON, 2) +#define AE_MAX_GAIN_MON I2C_REG(CAT_AE, CAT3_MAX_GAIN_MON, 2) +#define AE_MAN_GAIN_CAP I2C_REG(CAT_AE, CAT3_MANUAL_GAIN_CAP, 2) + +#define AE_INDEX I2C_REG(CAT_AE, CAT3_AE_INDEX, 1) +#define REG_AE_INDEX_20_NEG 0x00 +#define REG_AE_INDEX_15_NEG 0x01 +#define REG_AE_INDEX_10_NEG 0x02 +#define REG_AE_INDEX_05_NEG 0x03 +#define REG_AE_INDEX_00 0x04 +#define REG_AE_INDEX_05_POS 0x05 +#define REG_AE_INDEX_10_POS 0x06 +#define REG_AE_INDEX_15_POS 0x07 +#define REG_AE_INDEX_20_POS 0x08 + +/* + * Category 6 - White Balance + * + * This category provide AWB locking/mode/preset/speed/gain bias, etc. + */ +#define CAT6_AWB_LOCK 0x00 /* locking Auto Whitebalance */ +#define CAT6_AWB_MODE 0x02 /* set Auto or Manual */ +#define CAT6_AWB_MANUAL 0x03 /* set Manual(preset) value */ + +#define AWB_LOCK I2C_REG(CAT_WB, CAT6_AWB_LOCK, 1) +#define REG_AWB_UNLOCK 0x00 +#define REG_AWB_LOCK 0x01 + +#define AWB_MODE I2C_REG(CAT_WB, CAT6_AWB_MODE, 1) +#define REG_AWB_AUTO 0x01 /* AWB off */ +#define REG_AWB_PRESET 0x02 /* AWB preset */ + +#define AWB_MANUAL I2C_REG(CAT_WB, CAT6_AWB_MANUAL, 1) +#define REG_AWB_INCANDESCENT 0x01 +#define REG_AWB_FLUORESCENT_1 0x02 +#define REG_AWB_FLUORESCENT_2 0x03 +#define REG_AWB_DAYLIGHT 0x04 +#define REG_AWB_CLOUDY 0x05 +#define REG_AWB_SHADE 0x06 +#define REG_AWB_HORIZON 0x07 +#define REG_AWB_LEDLIGHT 0x09 + +/* + * Category 7 - EXIF information + */ +#define CAT7_INFO_EXPTIME_NU 0x00 +#define CAT7_INFO_EXPTIME_DE 0x04 +#define CAT7_INFO_TV_NU 0x08 +#define CAT7_INFO_TV_DE 0x0c +#define CAT7_INFO_AV_NU 0x10 +#define CAT7_INFO_AV_DE 0x14 +#define CAT7_INFO_BV_NU 0x18 +#define CAT7_INFO_BV_DE 0x1c +#define CAT7_INFO_EBV_NU 0x20 +#define CAT7_INFO_EBV_DE 0x24 +#define CAT7_INFO_ISO 0x28 +#define CAT7_INFO_FLASH 0x2a +#define CAT7_INFO_SDR 0x2c +#define CAT7_INFO_QVAL 0x2e + +#define EXIF_INFO_EXPTIME_NU I2C_REG(CAT_EXIF, CAT7_INFO_EXPTIME_NU, 4) +#define EXIF_INFO_EXPTIME_DE I2C_REG(CAT_EXIF, CAT7_INFO_EXPTIME_DE, 4) +#define EXIF_INFO_TV_NU I2C_REG(CAT_EXIF, CAT7_INFO_TV_NU, 4) +#define EXIF_INFO_TV_DE I2C_REG(CAT_EXIF, CAT7_INFO_TV_DE, 4) +#define EXIF_INFO_AV_NU I2C_REG(CAT_EXIF, CAT7_INFO_AV_NU, 4) +#define EXIF_INFO_AV_DE I2C_REG(CAT_EXIF, CAT7_INFO_AV_DE, 4) +#define EXIF_INFO_BV_NU I2C_REG(CAT_EXIF, CAT7_INFO_BV_NU, 4) +#define EXIF_INFO_BV_DE I2C_REG(CAT_EXIF, CAT7_INFO_BV_DE, 4) +#define EXIF_INFO_EBV_NU I2C_REG(CAT_EXIF, CAT7_INFO_EBV_NU, 4) +#define EXIF_INFO_EBV_DE I2C_REG(CAT_EXIF, CAT7_INFO_EBV_DE, 4) +#define EXIF_INFO_ISO I2C_REG(CAT_EXIF, CAT7_INFO_ISO, 2) +#define EXIF_INFO_FLASH I2C_REG(CAT_EXIF, CAT7_INFO_FLASH, 2) +#define EXIF_INFO_SDR I2C_REG(CAT_EXIF, CAT7_INFO_SDR, 2) +#define EXIF_INFO_QVAL I2C_REG(CAT_EXIF, CAT7_INFO_QVAL, 2) + +/* + * Category 9 - Face Detection + */ +#define CAT9_FD_CTL 0x00 + +#define FD_CTL I2C_REG(CAT_FD, CAT9_FD_CTL, 1) +#define BIT_FD_EN 0 +#define BIT_FD_DRAW_FACE_FRAME 4 +#define BIT_FD_DRAW_SMILE_LVL 6 +#define REG_FD(shift) (1 << shift) +#define REG_FD_OFF 0x0 + +/* + * Category A - Lens Parameter + */ +#define CATA_AF_MODE 0x01 +#define CATA_AF_EXECUTE 0x02 +#define CATA_AF_STATUS 0x03 +#define CATA_AF_VERSION 0x0a + +#define AF_MODE I2C_REG(CAT_LENS, CATA_AF_MODE, 1) +#define REG_AF_NORMAL 0x00 /* Normal AF, one time */ +#define REG_AF_MACRO 0x01 /* Macro AF, one time */ +#define REG_AF_POWEROFF 0x07 + +#define AF_EXECUTE I2C_REG(CAT_LENS, CATA_AF_EXECUTE, 1) +#define REG_AF_STOP 0x00 +#define REG_AF_EXE_AUTO 0x01 +#define REG_AF_EXE_CAF 0x02 + +#define AF_STATUS I2C_REG(CAT_LENS, CATA_AF_STATUS, 1) +#define REG_AF_FAIL 0x00 +#define REG_AF_SUCCESS 0x02 +#define REG_AF_IDLE 0x04 +#define REG_AF_BUSY 0x05 + +#define AF_VERSION I2C_REG(CAT_LENS, CATA_AF_VERSION, 1) + +/* + * Category B - CAPTURE Parameter + */ +#define CATB_YUVOUT_MAIN 0x00 +#define CATB_MAIN_IMAGE_SIZE 0x01 +#define CATB_MCC_MODE 0x1d +#define CATB_WDR_EN 0x2c +#define CATB_LIGHT_CTRL 0x40 +#define CATB_FLASH_CTRL 0x41 + +#define CAPP_YUVOUT_MAIN I2C_REG(CAT_CAPT_PARM, CATB_YUVOUT_MAIN, 1) +#define REG_YUV422 0x00 +#define REG_BAYER10 0x05 +#define REG_BAYER8 0x06 +#define REG_JPEG 0x10 + +#define CAPP_MAIN_IMAGE_SIZE I2C_REG(CAT_CAPT_PARM, CATB_MAIN_IMAGE_SIZE, 1) + +#define CAPP_MCC_MODE I2C_REG(CAT_CAPT_PARM, CATB_MCC_MODE, 1) +#define REG_MCC_OFF 0x00 +#define REG_MCC_NORMAL 0x01 + +#define CAPP_WDR_EN I2C_REG(CAT_CAPT_PARM, CATB_WDR_EN, 1) +#define REG_WDR_OFF 0x00 +#define REG_WDR_ON 0x01 +#define REG_WDR_AUTO 0x02 + +#define CAPP_LIGHT_CTRL I2C_REG(CAT_CAPT_PARM, CATB_LIGHT_CTRL, 1) +#define REG_LIGHT_OFF 0x00 +#define REG_LIGHT_ON 0x01 +#define REG_LIGHT_AUTO 0x02 + +#define CAPP_FLASH_CTRL I2C_REG(CAT_CAPT_PARM, CATB_FLASH_CTRL, 1) +#define REG_FLASH_OFF 0x00 +#define REG_FLASH_ON 0x01 +#define REG_FLASH_AUTO 0x02 + +/* + * Category C - CAPTURE Control + */ +#define CATC_CAP_MODE 0x00 +#define CATC_CAP_SEL_FRAME 0x06 /* It determines Single or Multi */ +#define CATC_CAP_START 0x09 +#define CATC_CAP_IMAGE_SIZE 0x0d +#define CATC_CAP_THUMB_SIZE 0x11 + +#define CAPC_MODE I2C_REG(CAT_CAPT_CTRL, CATC_CAP_MODE, 1) +#define REG_CAP_NONE 0x00 +#define REG_CAP_ANTI_SHAKE 0x02 + +#define CAPC_SEL_FRAME I2C_REG(CAT_CAPT_CTRL, CATC_CAP_SEL_FRAME, 1) + +#define CAPC_START I2C_REG(CAT_CAPT_CTRL, CATC_CAP_START, 1) +#define REG_CAP_START_MAIN 0x01 +#define REG_CAP_START_THUMB 0x03 + +#define CAPC_IMAGE_SIZE I2C_REG(CAT_CAPT_CTRL, CATC_CAP_IMAGE_SIZE, 1) +#define CAPC_THUMB_SIZE I2C_REG(CAT_CAPT_CTRL, CATC_CAP_THUMB_SIZE, 1) + +/* + * Category F - Flash + * + * This mode provides functions about internal flash stuff and system startup. + */ +#define CATF_CAM_START 0x12 /* It starts internal ARM core booting + * after power-up */ + +#define FLASH_CAM_START I2C_REG(CAT_FLASH, CATF_CAM_START, 1) +#define REG_START_ARM_BOOT 0x01 + +#endif /* M5MOLS_REG_H */ diff --git a/drivers/media/video/timblogiw.c b/drivers/media/video/timblogiw.c index 84d4c7c8343..fc611ebeb82 100644 --- a/drivers/media/video/timblogiw.c +++ b/drivers/media/video/timblogiw.c @@ -24,7 +24,6 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/dmaengine.h> -#include <linux/mfd/core.h> #include <linux/scatterlist.h> #include <linux/interrupt.h> #include <linux/list.h> @@ -791,7 +790,7 @@ static int __devinit timblogiw_probe(struct platform_device *pdev) { int err; struct timblogiw *lw = NULL; - struct timb_video_platform_data *pdata = mfd_get_data(pdev); + struct timb_video_platform_data *pdata = pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "No platform data\n"); diff --git a/drivers/media/video/uvc/Makefile b/drivers/media/video/uvc/Makefile index 968c1994eda..2071ca8a2f0 100644 --- a/drivers/media/video/uvc/Makefile +++ b/drivers/media/video/uvc/Makefile @@ -1,3 +1,6 @@ uvcvideo-objs := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \ uvc_status.o uvc_isight.o +ifeq ($(CONFIG_MEDIA_CONTROLLER),y) +uvcvideo-objs += uvc_entity.o +endif obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o diff --git a/drivers/media/video/uvc/uvc_driver.c b/drivers/media/video/uvc/uvc_driver.c index 823f4b38974..b6eae48d7fb 100644 --- a/drivers/media/video/uvc/uvc_driver.c +++ b/drivers/media/video/uvc/uvc_driver.c @@ -248,7 +248,7 @@ uint32_t uvc_fraction_to_interval(uint32_t numerator, uint32_t denominator) * Terminal and unit management */ -static struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id) +struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id) { struct uvc_entity *entity; @@ -795,9 +795,12 @@ static struct uvc_entity *uvc_alloc_entity(u16 type, u8 id, struct uvc_entity *entity; unsigned int num_inputs; unsigned int size; + unsigned int i; + extra_size = ALIGN(extra_size, sizeof(*entity->pads)); num_inputs = (type & UVC_TERM_OUTPUT) ? num_pads : num_pads - 1; - size = sizeof(*entity) + extra_size + num_inputs; + size = sizeof(*entity) + extra_size + sizeof(*entity->pads) * num_pads + + num_inputs; entity = kzalloc(size, GFP_KERNEL); if (entity == NULL) return NULL; @@ -805,8 +808,17 @@ static struct uvc_entity *uvc_alloc_entity(u16 type, u8 id, entity->id = id; entity->type = type; + entity->num_links = 0; + entity->num_pads = num_pads; + entity->pads = ((void *)(entity + 1)) + extra_size; + + for (i = 0; i < num_inputs; ++i) + entity->pads[i].flags = MEDIA_PAD_FL_SINK; + if (!UVC_ENTITY_IS_OTERM(entity)) + entity->pads[num_pads-1].flags = MEDIA_PAD_FL_SOURCE; + entity->bNrInPins = num_inputs; - entity->baSourceID = ((__u8 *)entity) + sizeof(*entity) + extra_size; + entity->baSourceID = (__u8 *)(&entity->pads[num_pads]); return entity; } @@ -1585,6 +1597,13 @@ static void uvc_delete(struct uvc_device *dev) uvc_status_cleanup(dev); uvc_ctrl_cleanup_device(dev); + if (dev->vdev.dev) + v4l2_device_unregister(&dev->vdev); +#ifdef CONFIG_MEDIA_CONTROLLER + if (media_devnode_is_registered(&dev->mdev.devnode)) + media_device_unregister(&dev->mdev); +#endif + list_for_each_safe(p, n, &dev->chains) { struct uvc_video_chain *chain; chain = list_entry(p, struct uvc_video_chain, list); @@ -1594,6 +1613,13 @@ static void uvc_delete(struct uvc_device *dev) list_for_each_safe(p, n, &dev->entities) { struct uvc_entity *entity; entity = list_entry(p, struct uvc_entity, list); +#ifdef CONFIG_MEDIA_CONTROLLER + uvc_mc_cleanup_entity(entity); +#endif + if (entity->vdev) { + video_device_release(entity->vdev); + entity->vdev = NULL; + } kfree(entity); } @@ -1616,8 +1642,6 @@ static void uvc_release(struct video_device *vdev) struct uvc_streaming *stream = video_get_drvdata(vdev); struct uvc_device *dev = stream->dev; - video_device_release(vdev); - /* Decrement the registered streams count and delete the device when it * reaches zero. */ @@ -1682,7 +1706,7 @@ static int uvc_register_video(struct uvc_device *dev, * unregistered before the reference is released, so we don't need to * get another one. */ - vdev->parent = &dev->intf->dev; + vdev->v4l2_dev = &dev->vdev; vdev->fops = &uvc_fops; vdev->release = uvc_release; strlcpy(vdev->name, dev->name, sizeof vdev->name); @@ -1731,6 +1755,8 @@ static int uvc_register_terms(struct uvc_device *dev, ret = uvc_register_video(dev, stream); if (ret < 0) return ret; + + term->vdev = stream->vdev; } return 0; @@ -1745,6 +1771,14 @@ static int uvc_register_chains(struct uvc_device *dev) ret = uvc_register_terms(dev, chain); if (ret < 0) return ret; + +#ifdef CONFIG_MEDIA_CONTROLLER + ret = uvc_mc_register_entities(chain); + if (ret < 0) { + uvc_printk(KERN_INFO, "Failed to register entites " + "(%d).\n", ret); + } +#endif } return 0; @@ -1814,6 +1848,24 @@ static int uvc_probe(struct usb_interface *intf, "linux-uvc-devel mailing list.\n"); } + /* Register the media and V4L2 devices. */ +#ifdef CONFIG_MEDIA_CONTROLLER + dev->mdev.dev = &intf->dev; + strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model)); + if (udev->serial) + strlcpy(dev->mdev.serial, udev->serial, + sizeof(dev->mdev.serial)); + strcpy(dev->mdev.bus_info, udev->devpath); + dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice); + dev->mdev.driver_version = DRIVER_VERSION_NUMBER; + if (media_device_register(&dev->mdev) < 0) + goto error; + + dev->vdev.mdev = &dev->mdev; +#endif + if (v4l2_device_register(&intf->dev, &dev->vdev) < 0) + goto error; + /* Initialize controls. */ if (uvc_ctrl_init_device(dev) < 0) goto error; @@ -1822,7 +1874,7 @@ static int uvc_probe(struct usb_interface *intf, if (uvc_scan_device(dev) < 0) goto error; - /* Register video devices. */ + /* Register video device nodes. */ if (uvc_register_chains(dev) < 0) goto error; diff --git a/drivers/media/video/uvc/uvc_entity.c b/drivers/media/video/uvc/uvc_entity.c new file mode 100644 index 00000000000..ede7852bb1d --- /dev/null +++ b/drivers/media/video/uvc/uvc_entity.c @@ -0,0 +1,118 @@ +/* + * uvc_entity.c -- USB Video Class driver + * + * Copyright (C) 2005-2011 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/videodev2.h> + +#include <media/v4l2-common.h> + +#include "uvcvideo.h" + +/* ------------------------------------------------------------------------ + * Video subdevices registration and unregistration + */ + +static int uvc_mc_register_entity(struct uvc_video_chain *chain, + struct uvc_entity *entity) +{ + const u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE; + struct uvc_entity *remote; + unsigned int i; + u8 remote_pad; + int ret; + + for (i = 0; i < entity->num_pads; ++i) { + struct media_entity *source; + struct media_entity *sink; + + if (!(entity->pads[i].flags & MEDIA_PAD_FL_SINK)) + continue; + + remote = uvc_entity_by_id(chain->dev, entity->baSourceID[i]); + if (remote == NULL) + return -EINVAL; + + source = (UVC_ENTITY_TYPE(remote) == UVC_TT_STREAMING) + ? &remote->vdev->entity : &remote->subdev.entity; + sink = (UVC_ENTITY_TYPE(entity) == UVC_TT_STREAMING) + ? &entity->vdev->entity : &entity->subdev.entity; + + remote_pad = remote->num_pads - 1; + ret = media_entity_create_link(source, remote_pad, + sink, i, flags); + if (ret < 0) + return ret; + } + + if (UVC_ENTITY_TYPE(entity) != UVC_TT_STREAMING) + ret = v4l2_device_register_subdev(&chain->dev->vdev, + &entity->subdev); + + return ret; +} + +static struct v4l2_subdev_ops uvc_subdev_ops = { +}; + +void uvc_mc_cleanup_entity(struct uvc_entity *entity) +{ + if (UVC_ENTITY_TYPE(entity) != UVC_TT_STREAMING) + media_entity_cleanup(&entity->subdev.entity); + else if (entity->vdev != NULL) + media_entity_cleanup(&entity->vdev->entity); +} + +static int uvc_mc_init_entity(struct uvc_entity *entity) +{ + int ret; + + if (UVC_ENTITY_TYPE(entity) != UVC_TT_STREAMING) { + v4l2_subdev_init(&entity->subdev, &uvc_subdev_ops); + strlcpy(entity->subdev.name, entity->name, + sizeof(entity->subdev.name)); + + ret = media_entity_init(&entity->subdev.entity, + entity->num_pads, entity->pads, 0); + } else + ret = media_entity_init(&entity->vdev->entity, + entity->num_pads, entity->pads, 0); + + return ret; +} + +int uvc_mc_register_entities(struct uvc_video_chain *chain) +{ + struct uvc_entity *entity; + int ret; + + list_for_each_entry(entity, &chain->entities, chain) { + ret = uvc_mc_init_entity(entity); + if (ret < 0) { + uvc_printk(KERN_INFO, "Failed to initialize entity for " + "entity %u\n", entity->id); + return ret; + } + } + + list_for_each_entry(entity, &chain->entities, chain) { + ret = uvc_mc_register_entity(chain, entity); + if (ret < 0) { + uvc_printk(KERN_INFO, "Failed to register entity for " + "entity %u\n", entity->id); + return ret; + } + } + + return 0; +} diff --git a/drivers/media/video/uvc/uvcvideo.h b/drivers/media/video/uvc/uvcvideo.h index 7cf224bae2e..20107fd3574 100644 --- a/drivers/media/video/uvc/uvcvideo.h +++ b/drivers/media/video/uvc/uvcvideo.h @@ -98,8 +98,11 @@ struct uvc_xu_control { #ifdef __KERNEL__ #include <linux/poll.h> +#include <linux/usb.h> #include <linux/usb/video.h> #include <linux/uvcvideo.h> +#include <media/media-device.h> +#include <media/v4l2-device.h> /* -------------------------------------------------------------------------- * UVC constants @@ -301,6 +304,13 @@ struct uvc_entity { __u16 type; char name[64]; + /* Media controller-related fields. */ + struct video_device *vdev; + struct v4l2_subdev subdev; + unsigned int num_pads; + unsigned int num_links; + struct media_pad *pads; + union { struct { __u16 wObjectiveFocalLengthMin; @@ -504,6 +514,10 @@ struct uvc_device { atomic_t nmappings; /* Video control interface */ +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_device mdev; +#endif + struct v4l2_device vdev; __u16 uvc_version; __u32 clock_frequency; @@ -583,6 +597,8 @@ extern unsigned int uvc_timeout_param; /* Core driver */ extern struct uvc_driver uvc_driver; +extern struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id); + /* Video buffers queue management. */ extern void uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type, int drop_corrupted); @@ -616,6 +632,10 @@ static inline int uvc_queue_streaming(struct uvc_video_queue *queue) /* V4L2 interface */ extern const struct v4l2_file_operations uvc_fops; +/* Media controller */ +extern int uvc_mc_register_entities(struct uvc_video_chain *chain); +extern void uvc_mc_cleanup_entity(struct uvc_entity *entity); + /* Video */ extern int uvc_video_init(struct uvc_streaming *stream); extern int uvc_video_suspend(struct uvc_streaming *stream); diff --git a/drivers/mfd/88pm860x-core.c b/drivers/mfd/88pm860x-core.c index 011cb6ce861..17dfe9bb6d2 100644 --- a/drivers/mfd/88pm860x-core.c +++ b/drivers/mfd/88pm860x-core.c @@ -21,13 +21,13 @@ #define INT_STATUS_NUM 3 -static struct resource bk_resources[] __initdata = { +static struct resource bk_resources[] __devinitdata = { {PM8606_BACKLIGHT1, PM8606_BACKLIGHT1, "backlight-0", IORESOURCE_IO,}, {PM8606_BACKLIGHT2, PM8606_BACKLIGHT2, "backlight-1", IORESOURCE_IO,}, {PM8606_BACKLIGHT3, PM8606_BACKLIGHT3, "backlight-2", IORESOURCE_IO,}, }; -static struct resource led_resources[] __initdata = { +static struct resource led_resources[] __devinitdata = { {PM8606_LED1_RED, PM8606_LED1_RED, "led0-red", IORESOURCE_IO,}, {PM8606_LED1_GREEN, PM8606_LED1_GREEN, "led0-green", IORESOURCE_IO,}, {PM8606_LED1_BLUE, PM8606_LED1_BLUE, "led0-blue", IORESOURCE_IO,}, @@ -36,7 +36,7 @@ static struct resource led_resources[] __initdata = { {PM8606_LED2_BLUE, PM8606_LED2_BLUE, "led1-blue", IORESOURCE_IO,}, }; -static struct resource regulator_resources[] __initdata = { +static struct resource regulator_resources[] __devinitdata = { {PM8607_ID_BUCK1, PM8607_ID_BUCK1, "buck-1", IORESOURCE_IO,}, {PM8607_ID_BUCK2, PM8607_ID_BUCK2, "buck-2", IORESOURCE_IO,}, {PM8607_ID_BUCK3, PM8607_ID_BUCK3, "buck-3", IORESOURCE_IO,}, @@ -57,15 +57,15 @@ static struct resource regulator_resources[] __initdata = { {PM8607_ID_LDO15, PM8607_ID_LDO15, "ldo-15", IORESOURCE_IO,}, }; -static struct resource touch_resources[] __initdata = { +static struct resource touch_resources[] __devinitdata = { {PM8607_IRQ_PEN, PM8607_IRQ_PEN, "touch", IORESOURCE_IRQ,}, }; -static struct resource onkey_resources[] __initdata = { +static struct resource onkey_resources[] __devinitdata = { {PM8607_IRQ_ONKEY, PM8607_IRQ_ONKEY, "onkey", IORESOURCE_IRQ,}, }; -static struct resource codec_resources[] __initdata = { +static struct resource codec_resources[] __devinitdata = { /* Headset microphone insertion or removal */ {PM8607_IRQ_MICIN, PM8607_IRQ_MICIN, "micin", IORESOURCE_IRQ,}, /* Hook-switch press or release */ @@ -76,12 +76,12 @@ static struct resource codec_resources[] __initdata = { {PM8607_IRQ_AUDIO_SHORT, PM8607_IRQ_AUDIO_SHORT, "audio-short", IORESOURCE_IRQ,}, }; -static struct resource battery_resources[] __initdata = { +static struct resource battery_resources[] __devinitdata = { {PM8607_IRQ_CC, PM8607_IRQ_CC, "columb counter", IORESOURCE_IRQ,}, {PM8607_IRQ_BAT, PM8607_IRQ_BAT, "battery", IORESOURCE_IRQ,}, }; -static struct resource charger_resources[] __initdata = { +static struct resource charger_resources[] __devinitdata = { {PM8607_IRQ_CHG, PM8607_IRQ_CHG, "charger detect", IORESOURCE_IRQ,}, {PM8607_IRQ_CHG_DONE, PM8607_IRQ_CHG_DONE, "charging done", IORESOURCE_IRQ,}, {PM8607_IRQ_CHG_FAULT, PM8607_IRQ_CHG_FAULT, "charging timeout", IORESOURCE_IRQ,}, @@ -90,13 +90,17 @@ static struct resource charger_resources[] __initdata = { {PM8607_IRQ_VCHG, PM8607_IRQ_VCHG, "vchg voltage", IORESOURCE_IRQ,}, }; -static struct mfd_cell bk_devs[] __initdata = { +static struct resource rtc_resources[] __devinitdata = { + {PM8607_IRQ_RTC, PM8607_IRQ_RTC, "rtc", IORESOURCE_IRQ,}, +}; + +static struct mfd_cell bk_devs[] = { {"88pm860x-backlight", 0,}, {"88pm860x-backlight", 1,}, {"88pm860x-backlight", 2,}, }; -static struct mfd_cell led_devs[] __initdata = { +static struct mfd_cell led_devs[] = { {"88pm860x-led", 0,}, {"88pm860x-led", 1,}, {"88pm860x-led", 2,}, @@ -105,7 +109,7 @@ static struct mfd_cell led_devs[] __initdata = { {"88pm860x-led", 5,}, }; -static struct mfd_cell regulator_devs[] __initdata = { +static struct mfd_cell regulator_devs[] = { {"88pm860x-regulator", 0,}, {"88pm860x-regulator", 1,}, {"88pm860x-regulator", 2,}, @@ -126,15 +130,15 @@ static struct mfd_cell regulator_devs[] __initdata = { {"88pm860x-regulator", 17,}, }; -static struct mfd_cell touch_devs[] __initdata = { +static struct mfd_cell touch_devs[] = { {"88pm860x-touch", -1,}, }; -static struct mfd_cell onkey_devs[] __initdata = { +static struct mfd_cell onkey_devs[] = { {"88pm860x-onkey", -1,}, }; -static struct mfd_cell codec_devs[] __initdata = { +static struct mfd_cell codec_devs[] = { {"88pm860x-codec", -1,}, }; @@ -143,11 +147,10 @@ static struct mfd_cell power_devs[] = { {"88pm860x-charger", -1,}, }; -static struct pm860x_backlight_pdata bk_pdata[ARRAY_SIZE(bk_devs)]; -static struct pm860x_led_pdata led_pdata[ARRAY_SIZE(led_devs)]; -static struct regulator_init_data regulator_pdata[ARRAY_SIZE(regulator_devs)]; -static struct pm860x_touch_pdata touch_pdata; -static struct pm860x_power_pdata power_pdata; +static struct mfd_cell rtc_devs[] = { + {"88pm860x-rtc", -1,}, +}; + struct pm860x_irq_data { int reg; @@ -501,7 +504,6 @@ static void device_irq_exit(struct pm860x_chip *chip) } static void __devinit device_bk_init(struct pm860x_chip *chip, - struct i2c_client *i2c, struct pm860x_platform_data *pdata) { int ret; @@ -514,13 +516,12 @@ static void __devinit device_bk_init(struct pm860x_chip *chip, pdata->num_backlights = ARRAY_SIZE(bk_devs); for (i = 0; i < pdata->num_backlights; i++) { - memcpy(&bk_pdata[i], &pdata->backlight[i], - sizeof(struct pm860x_backlight_pdata)); - bk_devs[i].mfd_data = &bk_pdata[i]; + bk_devs[i].platform_data = &pdata->backlight[i]; + bk_devs[i].pdata_size = sizeof(struct pm860x_backlight_pdata); for (j = 0; j < ARRAY_SIZE(bk_devs); j++) { id = bk_resources[j].start; - if (bk_pdata[i].flags != id) + if (pdata->backlight[i].flags != id) continue; bk_devs[i].num_resources = 1; @@ -538,7 +539,6 @@ static void __devinit device_bk_init(struct pm860x_chip *chip, } static void __devinit device_led_init(struct pm860x_chip *chip, - struct i2c_client *i2c, struct pm860x_platform_data *pdata) { int ret; @@ -551,13 +551,12 @@ static void __devinit device_led_init(struct pm860x_chip *chip, pdata->num_leds = ARRAY_SIZE(led_devs); for (i = 0; i < pdata->num_leds; i++) { - memcpy(&led_pdata[i], &pdata->led[i], - sizeof(struct pm860x_led_pdata)); - led_devs[i].mfd_data = &led_pdata[i]; + led_devs[i].platform_data = &pdata->led[i]; + led_devs[i].pdata_size = sizeof(struct pm860x_led_pdata); for (j = 0; j < ARRAY_SIZE(led_devs); j++) { id = led_resources[j].start; - if (led_pdata[i].flags != id) + if (pdata->led[i].flags != id) continue; led_devs[i].num_resources = 1; @@ -575,12 +574,11 @@ static void __devinit device_led_init(struct pm860x_chip *chip, } static void __devinit device_regulator_init(struct pm860x_chip *chip, - struct i2c_client *i2c, struct pm860x_platform_data *pdata) { struct regulator_init_data *initdata; int ret; - int i, j; + int i, seq; if ((pdata == NULL) || (pdata->regulator == NULL)) return; @@ -588,41 +586,21 @@ static void __devinit device_regulator_init(struct pm860x_chip *chip, if (pdata->num_regulators > ARRAY_SIZE(regulator_devs)) pdata->num_regulators = ARRAY_SIZE(regulator_devs); - for (i = 0, j = -1; i < pdata->num_regulators; i++) { + for (i = 0, seq = -1; i < pdata->num_regulators; i++) { initdata = &pdata->regulator[i]; - if (strstr(initdata->constraints.name, "BUCK")) { - sscanf(initdata->constraints.name, "BUCK%d", &j); - /* BUCK1 ~ BUCK3 */ - if ((j < 1) || (j > 3)) { - dev_err(chip->dev, "Failed to add constraint " - "(%s)\n", initdata->constraints.name); - goto out; - } - j = (j - 1) + PM8607_ID_BUCK1; - } - if (strstr(initdata->constraints.name, "LDO")) { - sscanf(initdata->constraints.name, "LDO%d", &j); - /* LDO1 ~ LDO15 */ - if ((j < 1) || (j > 15)) { - dev_err(chip->dev, "Failed to add constraint " - "(%s)\n", initdata->constraints.name); - goto out; - } - j = (j - 1) + PM8607_ID_LDO1; - } - if (j == -1) { - dev_err(chip->dev, "Failed to add constraint (%s)\n", - initdata->constraints.name); + seq = *(unsigned int *)initdata->driver_data; + if ((seq < 0) || (seq > PM8607_ID_RG_MAX)) { + dev_err(chip->dev, "Wrong ID(%d) on regulator(%s)\n", + seq, initdata->constraints.name); goto out; } - memcpy(®ulator_pdata[i], &pdata->regulator[i], - sizeof(struct regulator_init_data)); - regulator_devs[i].mfd_data = ®ulator_pdata[i]; + regulator_devs[i].platform_data = &pdata->regulator[i]; + regulator_devs[i].pdata_size = sizeof(struct regulator_init_data); regulator_devs[i].num_resources = 1; - regulator_devs[i].resources = ®ulator_resources[j]; + regulator_devs[i].resources = ®ulator_resources[seq]; ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[i], 1, - ®ulator_resources[j], 0); + ®ulator_resources[seq], 0); if (ret < 0) { dev_err(chip->dev, "Failed to add regulator subdev\n"); goto out; @@ -632,17 +610,35 @@ out: return; } +static void __devinit device_rtc_init(struct pm860x_chip *chip, + struct pm860x_platform_data *pdata) +{ + int ret; + + if ((pdata == NULL)) + return; + + rtc_devs[0].platform_data = pdata->rtc; + rtc_devs[0].pdata_size = sizeof(struct pm860x_rtc_pdata); + rtc_devs[0].num_resources = ARRAY_SIZE(rtc_resources); + rtc_devs[0].resources = &rtc_resources[0]; + ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0], + ARRAY_SIZE(rtc_devs), &rtc_resources[0], + chip->irq_base); + if (ret < 0) + dev_err(chip->dev, "Failed to add rtc subdev\n"); +} + static void __devinit device_touch_init(struct pm860x_chip *chip, - struct i2c_client *i2c, struct pm860x_platform_data *pdata) { int ret; - if ((pdata == NULL) || (pdata->touch == NULL)) + if (pdata == NULL) return; - memcpy(&touch_pdata, pdata->touch, sizeof(struct pm860x_touch_pdata)); - touch_devs[0].mfd_data = &touch_pdata; + touch_devs[0].platform_data = pdata->touch; + touch_devs[0].pdata_size = sizeof(struct pm860x_touch_pdata); touch_devs[0].num_resources = ARRAY_SIZE(touch_resources); touch_devs[0].resources = &touch_resources[0]; ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], @@ -653,16 +649,15 @@ static void __devinit device_touch_init(struct pm860x_chip *chip, } static void __devinit device_power_init(struct pm860x_chip *chip, - struct i2c_client *i2c, struct pm860x_platform_data *pdata) { int ret; - if ((pdata == NULL) || (pdata->power == NULL)) + if (pdata == NULL) return; - memcpy(&power_pdata, pdata->power, sizeof(struct pm860x_power_pdata)); - power_devs[0].mfd_data = &power_pdata; + power_devs[0].platform_data = pdata->power; + power_devs[0].pdata_size = sizeof(struct pm860x_power_pdata); power_devs[0].num_resources = ARRAY_SIZE(battery_resources); power_devs[0].resources = &battery_resources[0], ret = mfd_add_devices(chip->dev, 0, &power_devs[0], 1, @@ -670,7 +665,8 @@ static void __devinit device_power_init(struct pm860x_chip *chip, if (ret < 0) dev_err(chip->dev, "Failed to add battery subdev\n"); - power_devs[1].mfd_data = &power_pdata; + power_devs[1].platform_data = pdata->power; + power_devs[1].pdata_size = sizeof(struct pm860x_power_pdata); power_devs[1].num_resources = ARRAY_SIZE(charger_resources); power_devs[1].resources = &charger_resources[0], ret = mfd_add_devices(chip->dev, 0, &power_devs[1], 1, @@ -680,7 +676,6 @@ static void __devinit device_power_init(struct pm860x_chip *chip, } static void __devinit device_onkey_init(struct pm860x_chip *chip, - struct i2c_client *i2c, struct pm860x_platform_data *pdata) { int ret; @@ -695,7 +690,6 @@ static void __devinit device_onkey_init(struct pm860x_chip *chip, } static void __devinit device_codec_init(struct pm860x_chip *chip, - struct i2c_client *i2c, struct pm860x_platform_data *pdata) { int ret; @@ -763,11 +757,12 @@ static void __devinit device_8607_init(struct pm860x_chip *chip, if (ret < 0) goto out; - device_regulator_init(chip, i2c, pdata); - device_onkey_init(chip, i2c, pdata); - device_touch_init(chip, i2c, pdata); - device_power_init(chip, i2c, pdata); - device_codec_init(chip, i2c, pdata); + device_regulator_init(chip, pdata); + device_rtc_init(chip, pdata); + device_onkey_init(chip, pdata); + device_touch_init(chip, pdata); + device_power_init(chip, pdata); + device_codec_init(chip, pdata); out: return; } @@ -779,8 +774,8 @@ int __devinit pm860x_device_init(struct pm860x_chip *chip, switch (chip->id) { case CHIP_PM8606: - device_bk_init(chip, chip->client, pdata); - device_led_init(chip, chip->client, pdata); + device_bk_init(chip, pdata); + device_led_init(chip, pdata); break; case CHIP_PM8607: device_8607_init(chip, chip->client, pdata); @@ -790,8 +785,8 @@ int __devinit pm860x_device_init(struct pm860x_chip *chip, if (chip->companion) { switch (chip->id) { case CHIP_PM8607: - device_bk_init(chip, chip->companion, pdata); - device_led_init(chip, chip->companion, pdata); + device_bk_init(chip, pdata); + device_led_init(chip, pdata); break; case CHIP_PM8606: device_8607_init(chip, chip->companion, pdata); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 481770ab271..b6c267724e1 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -157,6 +157,20 @@ config TPS6507X This driver can also be built as a module. If so, the module will be called tps6507x. +config MFD_TPS6586X + bool "TPS6586x Power Management chips" + depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS + select MFD_CORE + help + If you say yes here you get support for the TPS6586X series of + Power Management chips. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + + This driver can also be built as a module. If so, the module + will be called tps6586x. + config MENELAUS bool "Texas Instruments TWL92330/Menelaus PM chip" depends on I2C=y && ARCH_OMAP2 @@ -455,6 +469,20 @@ config MFD_PCF50633 facilities, and registers devices for the various functions so that function-specific drivers can bind to them. +config PCF50633_ADC + tristate "Support for NXP PCF50633 ADC" + depends on MFD_PCF50633 + help + Say yes here if you want to include support for ADC in the + NXP PCF50633 chip. + +config PCF50633_GPIO + tristate "Support for NXP PCF50633 GPIO" + depends on MFD_PCF50633 + help + Say yes here if you want to include support GPIO for pins on + the PCF50633 chip. + config MFD_MC13783 tristate @@ -470,20 +498,6 @@ config MFD_MC13XXX additional drivers must be enabled in order to use the functionality of the device. -config PCF50633_ADC - tristate "Support for NXP PCF50633 ADC" - depends on MFD_PCF50633 - help - Say yes here if you want to include support for ADC in the - NXP PCF50633 chip. - -config PCF50633_GPIO - tristate "Support for NXP PCF50633 GPIO" - depends on MFD_PCF50633 - help - Say yes here if you want to include support GPIO for pins on - the PCF50633 chip. - config ABX500_CORE bool "ST-Ericsson ABX500 Mixed Signal Circuit register functions" default y if ARCH_U300 || ARCH_U8500 @@ -649,20 +663,6 @@ config MFD_JZ4740_ADC Say yes here if you want support for the ADC unit in the JZ4740 SoC. This driver is necessary for jz4740-battery and jz4740-hwmon driver. -config MFD_TPS6586X - bool "TPS6586x Power Management chips" - depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS - select MFD_CORE - help - If you say yes here you get support for the TPS6586X series of - Power Management chips. - This driver provides common support for accessing the device, - additional drivers must be enabled in order to use the - functionality of the device. - - This driver can also be built as a module. If so, the module - will be called tps6586x. - config MFD_VX855 tristate "Support for VIA VX855/VX875 integrated south bridge" depends on PCI @@ -691,6 +691,43 @@ config MFD_OMAP_USB_HOST This MFD driver does the required setup functionalities for OMAP USB Host drivers. +config MFD_PM8XXX + tristate + +config MFD_PM8921_CORE + tristate "Qualcomm PM8921 PMIC chip" + depends on MSM_SSBI + select MFD_CORE + select MFD_PM8XXX + help + If you say yes to this option, support will be included for the + built-in PM8921 PMIC chip. + + This is required if your board has a PM8921 and uses its features, + such as: MPPs, GPIOs, regulators, interrupts, and PWM. + + Say M here if you want to include support for PM8921 chip as a module. + This will build a module called "pm8921-core". + +config MFD_PM8XXX_IRQ + bool "Support for Qualcomm PM8xxx IRQ features" + depends on MFD_PM8XXX + default y if MFD_PM8XXX + help + This is the IRQ driver for Qualcomm PM 8xxx PMIC chips. + + This is required to use certain other PM 8xxx features, such as GPIO + and MPP. + +config MFD_TPS65910 + bool "TPS65910 Power Management chip" + depends on I2C=y + select MFD_CORE + select GPIO_TPS65910 + help + if you say yes here you get support for the TPS65910 series of + Power Management chips. + endif # MFD_SUPPORT menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 24aa44448da..efe3cc33ed9 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -91,3 +91,6 @@ obj-$(CONFIG_MFD_VX855) += vx855.o obj-$(CONFIG_MFD_WL1273_CORE) += wl1273-core.o obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o +obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o +obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o +obj-$(CONFIG_MFD_TPS65910) += tps65910.o tps65910-irq.o diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c index a751927047a..a20e1c41bed 100644 --- a/drivers/mfd/ab3100-core.c +++ b/drivers/mfd/ab3100-core.c @@ -949,8 +949,10 @@ static int __devinit ab3100_probe(struct i2c_client *client, goto exit_no_ops; /* Set up and register the platform devices. */ - for (i = 0; i < ARRAY_SIZE(ab3100_devs); i++) - ab3100_devs[i].mfd_data = ab3100_plf_data; + for (i = 0; i < ARRAY_SIZE(ab3100_devs); i++) { + ab3100_devs[i].platform_data = ab3100_plf_data; + ab3100_devs[i].pdata_size = sizeof(struct ab3100_platform_data); + } err = mfd_add_devices(&client->dev, 0, ab3100_devs, ARRAY_SIZE(ab3100_devs), NULL, 0); diff --git a/drivers/mfd/ab3550-core.c b/drivers/mfd/ab3550-core.c index ff86acf3e6b..3d7dce671b9 100644 --- a/drivers/mfd/ab3550-core.c +++ b/drivers/mfd/ab3550-core.c @@ -1320,8 +1320,10 @@ static int __init ab3550_probe(struct i2c_client *client, goto exit_no_ops; /* Set up and register the platform devices. */ - for (i = 0; i < AB3550_NUM_DEVICES; i++) - ab3550_devs[i].mfd_data = ab3550_plf_data->dev_data[i]; + for (i = 0; i < AB3550_NUM_DEVICES; i++) { + ab3550_devs[i].platform_data = ab3550_plf_data->dev_data[i]; + ab3550_devs[i].pdata_size = ab3550_plf_data->dev_data_sz[i]; + } err = mfd_add_devices(&client->dev, 0, ab3550_devs, ARRAY_SIZE(ab3550_devs), NULL, diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 67d01c93828..fc0c1af1566 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -254,8 +254,9 @@ static void ab8500_irq_sync_unlock(struct irq_data *data) if (new == old) continue; - /* Interrupt register 12 does'nt exist prior to version 0x20 */ - if (ab8500_irq_regoffset[i] == 11 && ab8500->chip_id < 0x20) + /* Interrupt register 12 doesn't exist prior to version 2.0 */ + if (ab8500_irq_regoffset[i] == 11 && + ab8500->chip_id < AB8500_CUT2P0) continue; ab8500->oldmask[i] = new; @@ -307,8 +308,8 @@ static irqreturn_t ab8500_irq(int irq, void *dev) int status; u8 value; - /* Interrupt register 12 does'nt exist prior to version 0x20 */ - if (regoffset == 11 && ab8500->chip_id < 0x20) + /* Interrupt register 12 doesn't exist prior to version 2.0 */ + if (regoffset == 11 && ab8500->chip_id < AB8500_CUT2P0) continue; status = get_register_interruptible(ab8500, AB8500_INTERRUPT, @@ -724,17 +725,15 @@ int __devinit ab8500_init(struct ab8500 *ab8500) if (ret < 0) return ret; - /* - * 0x0 - Early Drop - * 0x10 - Cut 1.0 - * 0x11 - Cut 1.1 - * 0x20 - Cut 2.0 - * 0x30 - Cut 3.0 - */ - if (value == 0x0 || value == 0x10 || value == 0x11 || value == 0x20 || - value == 0x30) { + switch (value) { + case AB8500_CUTEARLY: + case AB8500_CUT1P0: + case AB8500_CUT1P1: + case AB8500_CUT2P0: + case AB8500_CUT3P0: dev_info(ab8500->dev, "detected chip, revision: %#x\n", value); - } else { + break; + default: dev_err(ab8500->dev, "unknown chip, revision: %#x\n", value); return -EINVAL; } @@ -763,8 +762,9 @@ int __devinit ab8500_init(struct ab8500 *ab8500) /* Clear and mask all interrupts */ for (i = 0; i < AB8500_NUM_IRQ_REGS; i++) { - /* Interrupt register 12 does'nt exist prior to version 0x20 */ - if (ab8500_irq_regoffset[i] == 11 && ab8500->chip_id < 0x20) + /* Interrupt register 12 doesn't exist prior to version 2.0 */ + if (ab8500_irq_regoffset[i] == 11 && + ab8500->chip_id < AB8500_CUT2P0) continue; get_register_interruptible(ab8500, AB8500_INTERRUPT, diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 6421ad1160d..f16afb234ff 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -57,6 +57,7 @@ #define SW_AVG_16 0x60 #define ADC_SW_CONV 0x04 #define EN_ICHAR 0x80 +#define BTEMP_PULL_UP 0x08 #define EN_BUF 0x40 #define DIS_ZERO 0x00 #define GPADC_BUSY 0x01 @@ -101,6 +102,7 @@ struct adc_cal_data { /** * struct ab8500_gpadc - AB8500 GPADC device information + * @chip_id ABB chip id * @dev: pointer to the struct device * @node: a list of AB8500 GPADCs, hence prepared for reentrance @@ -112,6 +114,7 @@ struct adc_cal_data { * @cal_data array of ADC calibration data structs */ struct ab8500_gpadc { + u8 chip_id; struct device *dev; struct list_head node; struct completion ab8500_gpadc_complete; @@ -274,6 +277,7 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) dev_err(gpadc->dev, "gpadc_conversion: enable gpadc failed\n"); goto out; } + /* Select the input source and set average samples to 16 */ ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL2_REG, (input | SW_AVG_16)); @@ -282,9 +286,11 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) "gpadc_conversion: set avg samples failed\n"); goto out; } + /* * Enable ADC, buffering, select rising edge and enable ADC path - * charging current sense if it needed + * charging current sense if it needed, ABB 3.0 needs some special + * treatment too. */ switch (input) { case MAIN_CHARGER_C: @@ -294,6 +300,23 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) EN_BUF | EN_ICHAR, EN_BUF | EN_ICHAR); break; + case BTEMP_BALL: + if (gpadc->chip_id >= AB8500_CUT3P0) { + /* Turn on btemp pull-up on ABB 3.0 */ + ret = abx500_mask_and_set_register_interruptible( + gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, + EN_BUF | BTEMP_PULL_UP, + EN_BUF | BTEMP_PULL_UP); + + /* + * Delay might be needed for ABB8500 cut 3.0, if not, remove + * when hardware will be availible + */ + msleep(1); + break; + } + /* Intentional fallthrough */ default: ret = abx500_mask_and_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF); @@ -304,6 +327,7 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) "gpadc_conversion: select falling edge failed\n"); goto out; } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV); if (ret < 0) { @@ -552,6 +576,14 @@ static int __devinit ab8500_gpadc_probe(struct platform_device *pdev) goto fail; } + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(gpadc->dev); + if (ret < 0) { + dev_err(gpadc->dev, "failed to get chip ID\n"); + goto fail_irq; + } + gpadc->chip_id = (u8) ret; + /* VTVout LDO used to power up ab8500-GPADC */ gpadc->regu = regulator_get(&pdev->dev, "vddadc"); if (IS_ERR(gpadc->regu)) { diff --git a/drivers/mfd/asic3.c b/drivers/mfd/asic3.c index 0b4d5b23bec..c27fd1fc3b8 100644 --- a/drivers/mfd/asic3.c +++ b/drivers/mfd/asic3.c @@ -88,19 +88,19 @@ struct asic3 { static int asic3_gpio_get(struct gpio_chip *chip, unsigned offset); -static inline void asic3_write_register(struct asic3 *asic, - unsigned int reg, u32 value) +void asic3_write_register(struct asic3 *asic, unsigned int reg, u32 value) { iowrite16(value, asic->mapping + (reg >> asic->bus_shift)); } +EXPORT_SYMBOL_GPL(asic3_write_register); -static inline u32 asic3_read_register(struct asic3 *asic, - unsigned int reg) +u32 asic3_read_register(struct asic3 *asic, unsigned int reg) { return ioread16(asic->mapping + (reg >> asic->bus_shift)); } +EXPORT_SYMBOL_GPL(asic3_read_register); static void asic3_set_register(struct asic3 *asic, u32 reg, u32 bits, bool set) { @@ -676,7 +676,8 @@ static struct mfd_cell asic3_cell_ds1wm = { .name = "ds1wm", .enable = ds1wm_enable, .disable = ds1wm_disable, - .mfd_data = &ds1wm_pdata, + .platform_data = &ds1wm_pdata, + .pdata_size = sizeof(ds1wm_pdata), .num_resources = ARRAY_SIZE(ds1wm_resources), .resources = ds1wm_resources, }; @@ -777,12 +778,61 @@ static struct mfd_cell asic3_cell_mmc = { .name = "tmio-mmc", .enable = asic3_mmc_enable, .disable = asic3_mmc_disable, - .mfd_data = &asic3_mmc_data, + .platform_data = &asic3_mmc_data, + .pdata_size = sizeof(asic3_mmc_data), .num_resources = ARRAY_SIZE(asic3_mmc_resources), .resources = asic3_mmc_resources, }; +static const int clock_ledn[ASIC3_NUM_LEDS] = { + [0] = ASIC3_CLOCK_LED0, + [1] = ASIC3_CLOCK_LED1, + [2] = ASIC3_CLOCK_LED2, +}; + +static int asic3_leds_enable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + asic3_clk_enable(asic, &asic->clocks[clock_ledn[cell->id]]); + + return 0; +} + +static int asic3_leds_disable(struct platform_device *pdev) +{ + const struct mfd_cell *cell = mfd_get_cell(pdev); + struct asic3 *asic = dev_get_drvdata(pdev->dev.parent); + + asic3_clk_disable(asic, &asic->clocks[clock_ledn[cell->id]]); + + return 0; +} + +static struct mfd_cell asic3_cell_leds[ASIC3_NUM_LEDS] = { + [0] = { + .name = "leds-asic3", + .id = 0, + .enable = asic3_leds_enable, + .disable = asic3_leds_disable, + }, + [1] = { + .name = "leds-asic3", + .id = 1, + .enable = asic3_leds_enable, + .disable = asic3_leds_disable, + }, + [2] = { + .name = "leds-asic3", + .id = 2, + .enable = asic3_leds_enable, + .disable = asic3_leds_disable, + }, +}; + static int __init asic3_mfd_probe(struct platform_device *pdev, + struct asic3_platform_data *pdata, struct resource *mem) { struct asic3 *asic = platform_get_drvdata(pdev); @@ -806,7 +856,8 @@ static int __init asic3_mfd_probe(struct platform_device *pdev, /* MMC */ asic->tmio_cnf = ioremap((ASIC3_SD_CONFIG_BASE >> asic->bus_shift) + - mem_sdio->start, 0x400 >> asic->bus_shift); + mem_sdio->start, + ASIC3_SD_CONFIG_SIZE >> asic->bus_shift); if (!asic->tmio_cnf) { ret = -ENOMEM; dev_dbg(asic->dev, "Couldn't ioremap SD_CONFIG\n"); @@ -820,9 +871,23 @@ static int __init asic3_mfd_probe(struct platform_device *pdev, if (ret < 0) goto out; - if (mem_sdio && (irq >= 0)) + if (mem_sdio && (irq >= 0)) { ret = mfd_add_devices(&pdev->dev, pdev->id, &asic3_cell_mmc, 1, mem_sdio, irq); + if (ret < 0) + goto out; + } + + if (pdata->leds) { + int i; + + for (i = 0; i < ASIC3_NUM_LEDS; ++i) { + asic3_cell_leds[i].platform_data = &pdata->leds[i]; + asic3_cell_leds[i].pdata_size = sizeof(pdata->leds[i]); + } + ret = mfd_add_devices(&pdev->dev, 0, + asic3_cell_leds, ASIC3_NUM_LEDS, NULL, 0); + } out: return ret; @@ -903,7 +968,7 @@ static int __init asic3_probe(struct platform_device *pdev) */ memcpy(asic->clocks, asic3_clk_init, sizeof(asic3_clk_init)); - asic3_mfd_probe(pdev, mem); + asic3_mfd_probe(pdev, pdata, mem); dev_info(asic->dev, "ASIC3 Core driver\n"); diff --git a/drivers/mfd/davinci_voicecodec.c b/drivers/mfd/davinci_voicecodec.c index 414783b0484..4e2af2cb2d2 100644 --- a/drivers/mfd/davinci_voicecodec.c +++ b/drivers/mfd/davinci_voicecodec.c @@ -119,12 +119,14 @@ static int __init davinci_vc_probe(struct platform_device *pdev) /* Voice codec interface client */ cell = &davinci_vc->cells[DAVINCI_VC_VCIF_CELL]; cell->name = "davinci-vcif"; - cell->mfd_data = davinci_vc; + cell->platform_data = davinci_vc; + cell->pdata_size = sizeof(*davinci_vc); /* Voice codec CQ93VC client */ cell = &davinci_vc->cells[DAVINCI_VC_CQ93VC_CELL]; cell->name = "cq93vc-codec"; - cell->mfd_data = davinci_vc; + cell->platform_data = davinci_vc; + cell->pdata_size = sizeof(*davinci_vc); ret = mfd_add_devices(&pdev->dev, pdev->id, davinci_vc->cells, DAVINCI_VC_CELLS, NULL, 0); diff --git a/drivers/mfd/htc-pasic3.c b/drivers/mfd/htc-pasic3.c index fb9770b39a3..2808bd125d1 100644 --- a/drivers/mfd/htc-pasic3.c +++ b/drivers/mfd/htc-pasic3.c @@ -117,7 +117,8 @@ static struct mfd_cell ds1wm_cell __initdata = { .name = "ds1wm", .enable = ds1wm_enable, .disable = ds1wm_disable, - .mfd_data = &ds1wm_pdata, + .platform_data = &ds1wm_pdata, + .pdata_size = sizeof(ds1wm_pdata), .num_resources = 2, .resources = ds1wm_resources, }; @@ -172,6 +173,8 @@ static int __init pasic3_probe(struct platform_device *pdev) } if (pdata && pdata->led_pdata) { + led_cell.platform_data = pdata->led_pdata; + led_cell.pdata_size = sizeof(struct pasic3_leds_machinfo); ret = mfd_add_devices(&pdev->dev, pdev->id, &led_cell, 1, r, 0); if (ret < 0) dev_warn(dev, "failed to register LED device\n"); diff --git a/drivers/mfd/janz-cmodio.c b/drivers/mfd/janz-cmodio.c index fc4191137e9..5c2a06acb77 100644 --- a/drivers/mfd/janz-cmodio.c +++ b/drivers/mfd/janz-cmodio.c @@ -86,7 +86,8 @@ static int __devinit cmodio_setup_subdevice(struct cmodio_device *priv, /* Add platform data */ pdata->modno = modno; - cell->mfd_data = pdata; + cell->platform_data = pdata; + cell->pdata_size = sizeof(*pdata); /* MODULbus registers -- PCI BAR3 is big-endian MODULbus access */ res->flags = IORESOURCE_MEM; diff --git a/drivers/mfd/max8925-core.c b/drivers/mfd/max8925-core.c index 58cc5fdde01..e1e59c92f75 100644 --- a/drivers/mfd/max8925-core.c +++ b/drivers/mfd/max8925-core.c @@ -627,7 +627,7 @@ int __devinit max8925_device_init(struct max8925_chip *chip, goto out_dev; } - if (pdata && pdata->regulator[0]) { + if (pdata) { ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], ARRAY_SIZE(regulator_devs), ®ulator_resources[0], 0); diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 668634e89e8..7e4d44bf92a 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -683,13 +683,14 @@ out: EXPORT_SYMBOL_GPL(mc13783_adc_do_conversion); static int mc13xxx_add_subdevice_pdata(struct mc13xxx *mc13xxx, - const char *format, void *pdata) + const char *format, void *pdata, size_t pdata_size) { char buf[30]; const char *name = mc13xxx_get_chipname(mc13xxx); struct mfd_cell cell = { - .mfd_data = pdata, + .platform_data = pdata, + .pdata_size = pdata_size, }; /* there is no asnprintf in the kernel :-( */ @@ -705,7 +706,7 @@ static int mc13xxx_add_subdevice_pdata(struct mc13xxx *mc13xxx, static int mc13xxx_add_subdevice(struct mc13xxx *mc13xxx, const char *format) { - return mc13xxx_add_subdevice_pdata(mc13xxx, format, NULL); + return mc13xxx_add_subdevice_pdata(mc13xxx, format, NULL, 0); } static int mc13xxx_probe(struct spi_device *spi) @@ -764,7 +765,7 @@ err_revision: if (pdata->flags & MC13XXX_USE_REGULATOR) { mc13xxx_add_subdevice_pdata(mc13xxx, "%s-regulator", - &pdata->regulators); + &pdata->regulators, sizeof(pdata->regulators)); } if (pdata->flags & MC13XXX_USE_RTC) @@ -774,7 +775,8 @@ err_revision: mc13xxx_add_subdevice(mc13xxx, "%s-ts"); if (pdata->flags & MC13XXX_USE_LED) - mc13xxx_add_subdevice_pdata(mc13xxx, "%s-led", pdata->leds); + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-led", + pdata->leds, sizeof(*pdata->leds)); return 0; } diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index f4c8c844b91..0902523af62 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -88,6 +88,13 @@ static int mfd_add_device(struct device *parent, int id, pdev->dev.parent = parent; + if (cell->pdata_size) { + ret = platform_device_add_data(pdev, + cell->platform_data, cell->pdata_size); + if (ret) + goto fail_res; + } + ret = mfd_platform_add_cell(pdev, cell); if (ret) goto fail_res; diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c index 3ab9ffa00aa..855219526cc 100644 --- a/drivers/mfd/omap-usb-host.c +++ b/drivers/mfd/omap-usb-host.c @@ -26,6 +26,7 @@ #include <linux/spinlock.h> #include <linux/gpio.h> #include <plat/usb.h> +#include <linux/pm_runtime.h> #define USBHS_DRIVER_NAME "usbhs-omap" #define OMAP_EHCI_DEVICE "ehci-omap" @@ -146,9 +147,6 @@ struct usbhs_hcd_omap { - struct clk *usbhost_ick; - struct clk *usbhost_hs_fck; - struct clk *usbhost_fs_fck; struct clk *xclk60mhsp1_ck; struct clk *xclk60mhsp2_ck; struct clk *utmi_p1_fck; @@ -158,8 +156,6 @@ struct usbhs_hcd_omap { struct clk *usbhost_p2_fck; struct clk *usbtll_p2_fck; struct clk *init_60m_fclk; - struct clk *usbtll_fck; - struct clk *usbtll_ick; void __iomem *uhh_base; void __iomem *tll_base; @@ -281,6 +277,7 @@ static int omap_usbhs_alloc_children(struct platform_device *pdev) if (!ehci) { dev_err(dev, "omap_usbhs_alloc_child failed\n"); + ret = -ENOMEM; goto err_end; } @@ -304,13 +301,14 @@ static int omap_usbhs_alloc_children(struct platform_device *pdev) sizeof(*ohci_data), dev); if (!ohci) { dev_err(dev, "omap_usbhs_alloc_child failed\n"); + ret = -ENOMEM; goto err_ehci; } return 0; err_ehci: - platform_device_put(ehci); + platform_device_unregister(ehci); err_end: return ret; @@ -351,46 +349,13 @@ static int __devinit usbhs_omap_probe(struct platform_device *pdev) omap->platdata.ehci_data = pdata->ehci_data; omap->platdata.ohci_data = pdata->ohci_data; - omap->usbhost_ick = clk_get(dev, "usbhost_ick"); - if (IS_ERR(omap->usbhost_ick)) { - ret = PTR_ERR(omap->usbhost_ick); - dev_err(dev, "usbhost_ick failed error:%d\n", ret); - goto err_end; - } - - omap->usbhost_hs_fck = clk_get(dev, "hs_fck"); - if (IS_ERR(omap->usbhost_hs_fck)) { - ret = PTR_ERR(omap->usbhost_hs_fck); - dev_err(dev, "usbhost_hs_fck failed error:%d\n", ret); - goto err_usbhost_ick; - } - - omap->usbhost_fs_fck = clk_get(dev, "fs_fck"); - if (IS_ERR(omap->usbhost_fs_fck)) { - ret = PTR_ERR(omap->usbhost_fs_fck); - dev_err(dev, "usbhost_fs_fck failed error:%d\n", ret); - goto err_usbhost_hs_fck; - } - - omap->usbtll_fck = clk_get(dev, "usbtll_fck"); - if (IS_ERR(omap->usbtll_fck)) { - ret = PTR_ERR(omap->usbtll_fck); - dev_err(dev, "usbtll_fck failed error:%d\n", ret); - goto err_usbhost_fs_fck; - } - - omap->usbtll_ick = clk_get(dev, "usbtll_ick"); - if (IS_ERR(omap->usbtll_ick)) { - ret = PTR_ERR(omap->usbtll_ick); - dev_err(dev, "usbtll_ick failed error:%d\n", ret); - goto err_usbtll_fck; - } + pm_runtime_enable(&pdev->dev); omap->utmi_p1_fck = clk_get(dev, "utmi_p1_gfclk"); if (IS_ERR(omap->utmi_p1_fck)) { ret = PTR_ERR(omap->utmi_p1_fck); dev_err(dev, "utmi_p1_gfclk failed error:%d\n", ret); - goto err_usbtll_ick; + goto err_end; } omap->xclk60mhsp1_ck = clk_get(dev, "xclk60mhsp1_ck"); @@ -520,22 +485,8 @@ err_xclk60mhsp1_ck: err_utmi_p1_fck: clk_put(omap->utmi_p1_fck); -err_usbtll_ick: - clk_put(omap->usbtll_ick); - -err_usbtll_fck: - clk_put(omap->usbtll_fck); - -err_usbhost_fs_fck: - clk_put(omap->usbhost_fs_fck); - -err_usbhost_hs_fck: - clk_put(omap->usbhost_hs_fck); - -err_usbhost_ick: - clk_put(omap->usbhost_ick); - err_end: + pm_runtime_disable(&pdev->dev); kfree(omap); end_probe: @@ -569,11 +520,7 @@ static int __devexit usbhs_omap_remove(struct platform_device *pdev) clk_put(omap->utmi_p2_fck); clk_put(omap->xclk60mhsp1_ck); clk_put(omap->utmi_p1_fck); - clk_put(omap->usbtll_ick); - clk_put(omap->usbtll_fck); - clk_put(omap->usbhost_fs_fck); - clk_put(omap->usbhost_hs_fck); - clk_put(omap->usbhost_ick); + pm_runtime_disable(&pdev->dev); kfree(omap); return 0; @@ -693,7 +640,6 @@ static int usbhs_enable(struct device *dev) struct usbhs_omap_platform_data *pdata = &omap->platdata; unsigned long flags = 0; int ret = 0; - unsigned long timeout; unsigned reg; dev_dbg(dev, "starting TI HSUSB Controller\n"); @@ -706,11 +652,7 @@ static int usbhs_enable(struct device *dev) if (omap->count > 0) goto end_count; - clk_enable(omap->usbhost_ick); - clk_enable(omap->usbhost_hs_fck); - clk_enable(omap->usbhost_fs_fck); - clk_enable(omap->usbtll_fck); - clk_enable(omap->usbtll_ick); + pm_runtime_get_sync(dev); if (pdata->ehci_data->phy_reset) { if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0])) { @@ -734,50 +676,6 @@ static int usbhs_enable(struct device *dev) omap->usbhs_rev = usbhs_read(omap->uhh_base, OMAP_UHH_REVISION); dev_dbg(dev, "OMAP UHH_REVISION 0x%x\n", omap->usbhs_rev); - /* perform TLL soft reset, and wait until reset is complete */ - usbhs_write(omap->tll_base, OMAP_USBTLL_SYSCONFIG, - OMAP_USBTLL_SYSCONFIG_SOFTRESET); - - /* Wait for TLL reset to complete */ - timeout = jiffies + msecs_to_jiffies(1000); - while (!(usbhs_read(omap->tll_base, OMAP_USBTLL_SYSSTATUS) - & OMAP_USBTLL_SYSSTATUS_RESETDONE)) { - cpu_relax(); - - if (time_after(jiffies, timeout)) { - dev_dbg(dev, "operation timed out\n"); - ret = -EINVAL; - goto err_tll; - } - } - - dev_dbg(dev, "TLL RESET DONE\n"); - - /* (1<<3) = no idle mode only for initial debugging */ - usbhs_write(omap->tll_base, OMAP_USBTLL_SYSCONFIG, - OMAP_USBTLL_SYSCONFIG_ENAWAKEUP | - OMAP_USBTLL_SYSCONFIG_SIDLEMODE | - OMAP_USBTLL_SYSCONFIG_AUTOIDLE); - - /* Put UHH in NoIdle/NoStandby mode */ - reg = usbhs_read(omap->uhh_base, OMAP_UHH_SYSCONFIG); - if (is_omap_usbhs_rev1(omap)) { - reg |= (OMAP_UHH_SYSCONFIG_ENAWAKEUP - | OMAP_UHH_SYSCONFIG_SIDLEMODE - | OMAP_UHH_SYSCONFIG_CACTIVITY - | OMAP_UHH_SYSCONFIG_MIDLEMODE); - reg &= ~OMAP_UHH_SYSCONFIG_AUTOIDLE; - - - } else if (is_omap_usbhs_rev2(omap)) { - reg &= ~OMAP4_UHH_SYSCONFIG_IDLEMODE_CLEAR; - reg |= OMAP4_UHH_SYSCONFIG_NOIDLE; - reg &= ~OMAP4_UHH_SYSCONFIG_STDBYMODE_CLEAR; - reg |= OMAP4_UHH_SYSCONFIG_NOSTDBY; - } - - usbhs_write(omap->uhh_base, OMAP_UHH_SYSCONFIG, reg); - reg = usbhs_read(omap->uhh_base, OMAP_UHH_HOSTCONFIG); /* setup ULPI bypass and burst configurations */ reg |= (OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN @@ -917,6 +815,8 @@ end_count: return 0; err_tll: + pm_runtime_put_sync(dev); + spin_unlock_irqrestore(&omap->lock, flags); if (pdata->ehci_data->phy_reset) { if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0])) gpio_free(pdata->ehci_data->reset_gpio_port[0]); @@ -924,13 +824,6 @@ err_tll: if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[1])) gpio_free(pdata->ehci_data->reset_gpio_port[1]); } - - clk_disable(omap->usbtll_ick); - clk_disable(omap->usbtll_fck); - clk_disable(omap->usbhost_fs_fck); - clk_disable(omap->usbhost_hs_fck); - clk_disable(omap->usbhost_ick); - spin_unlock_irqrestore(&omap->lock, flags); return ret; } @@ -994,6 +887,20 @@ static void usbhs_disable(struct device *dev) dev_dbg(dev, "operation timed out\n"); } + if (is_omap_usbhs_rev2(omap)) { + if (is_ehci_tll_mode(pdata->port_mode[0])) + clk_enable(omap->usbtll_p1_fck); + if (is_ehci_tll_mode(pdata->port_mode[1])) + clk_enable(omap->usbtll_p2_fck); + clk_disable(omap->utmi_p2_fck); + clk_disable(omap->utmi_p1_fck); + } + + pm_runtime_put_sync(dev); + + /* The gpio_free migh sleep; so unlock the spinlock */ + spin_unlock_irqrestore(&omap->lock, flags); + if (pdata->ehci_data->phy_reset) { if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[0])) gpio_free(pdata->ehci_data->reset_gpio_port[0]); @@ -1001,14 +908,7 @@ static void usbhs_disable(struct device *dev) if (gpio_is_valid(pdata->ehci_data->reset_gpio_port[1])) gpio_free(pdata->ehci_data->reset_gpio_port[1]); } - - clk_disable(omap->utmi_p2_fck); - clk_disable(omap->utmi_p1_fck); - clk_disable(omap->usbtll_ick); - clk_disable(omap->usbtll_fck); - clk_disable(omap->usbhost_fs_fck); - clk_disable(omap->usbhost_hs_fck); - clk_disable(omap->usbhost_ick); + return; end_disble: spin_unlock_irqrestore(&omap->lock, flags); diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c new file mode 100644 index 00000000000..e873b15753d --- /dev/null +++ b/drivers/mfd/pm8921-core.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/msm_ssbi.h> +#include <linux/mfd/core.h> +#include <linux/mfd/pm8xxx/pm8921.h> +#include <linux/mfd/pm8xxx/core.h> + +#define REG_HWREV 0x002 /* PMIC4 revision */ +#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ + +struct pm8921 { + struct device *dev; + struct pm_irq_chip *irq_chip; +}; + +static int pm8921_readb(const struct device *dev, u16 addr, u8 *val) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, val, 1); +} + +static int pm8921_writeb(const struct device *dev, u16 addr, u8 val) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, &val, 1); +} + +static int pm8921_read_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + + return msm_ssbi_read(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8921_write_buf(const struct device *dev, u16 addr, u8 *buf, + int cnt) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + + return msm_ssbi_write(pmic->dev->parent, addr, buf, cnt); +} + +static int pm8921_read_irq_stat(const struct device *dev, int irq) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + + return pm8xxx_get_irq_stat(pmic->irq_chip, irq); +} + +static struct pm8xxx_drvdata pm8921_drvdata = { + .pmic_readb = pm8921_readb, + .pmic_writeb = pm8921_writeb, + .pmic_read_buf = pm8921_read_buf, + .pmic_write_buf = pm8921_write_buf, + .pmic_read_irq_stat = pm8921_read_irq_stat, +}; + +static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data + *pdata, + struct pm8921 *pmic, + u32 rev) +{ + int ret = 0, irq_base = 0; + struct pm_irq_chip *irq_chip; + + if (pdata->irq_pdata) { + pdata->irq_pdata->irq_cdata.nirqs = PM8921_NR_IRQS; + pdata->irq_pdata->irq_cdata.rev = rev; + irq_base = pdata->irq_pdata->irq_base; + irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); + + if (IS_ERR(irq_chip)) { + pr_err("Failed to init interrupts ret=%ld\n", + PTR_ERR(irq_chip)); + return PTR_ERR(irq_chip); + } + pmic->irq_chip = irq_chip; + } + return ret; +} + +static int __devinit pm8921_probe(struct platform_device *pdev) +{ + const struct pm8921_platform_data *pdata = pdev->dev.platform_data; + struct pm8921 *pmic; + int rc; + u8 val; + u32 rev; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pmic = kzalloc(sizeof(struct pm8921), GFP_KERNEL); + if (!pmic) { + pr_err("Cannot alloc pm8921 struct\n"); + return -ENOMEM; + } + + /* Read PMIC chip revision */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); + goto err_read_rev; + } + pr_info("PMIC revision 1: %02X\n", val); + rev = val; + + /* Read PMIC chip revision 2 */ + rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); + if (rc) { + pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", + REG_HWREV_2, rc); + goto err_read_rev; + } + pr_info("PMIC revision 2: %02X\n", val); + rev |= val << BITS_PER_BYTE; + + pmic->dev = &pdev->dev; + pm8921_drvdata.pm_chip_data = pmic; + platform_set_drvdata(pdev, &pm8921_drvdata); + + rc = pm8921_add_subdevices(pdata, pmic, rev); + if (rc) { + pr_err("Cannot add subdevices rc=%d\n", rc); + goto err; + } + + /* gpio might not work if no irq device is found */ + WARN_ON(pmic->irq_chip == NULL); + + return 0; + +err: + mfd_remove_devices(pmic->dev); + platform_set_drvdata(pdev, NULL); +err_read_rev: + kfree(pmic); + return rc; +} + +static int __devexit pm8921_remove(struct platform_device *pdev) +{ + struct pm8xxx_drvdata *drvdata; + struct pm8921 *pmic = NULL; + + drvdata = platform_get_drvdata(pdev); + if (drvdata) + pmic = drvdata->pm_chip_data; + if (pmic) + mfd_remove_devices(pmic->dev); + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } + platform_set_drvdata(pdev, NULL); + kfree(pmic); + + return 0; +} + +static struct platform_driver pm8921_driver = { + .probe = pm8921_probe, + .remove = __devexit_p(pm8921_remove), + .driver = { + .name = "pm8921-core", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8921_init(void) +{ + return platform_driver_register(&pm8921_driver); +} +subsys_initcall(pm8921_init); + +static void __exit pm8921_exit(void) +{ + platform_driver_unregister(&pm8921_driver); +} +module_exit(pm8921_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC 8921 core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8921-core"); diff --git a/drivers/mfd/pm8xxx-irq.c b/drivers/mfd/pm8xxx-irq.c new file mode 100644 index 00000000000..d452dd01308 --- /dev/null +++ b/drivers/mfd/pm8xxx-irq.c @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/mfd/pm8xxx/core.h> +#include <linux/mfd/pm8xxx/irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* PMIC8xxx IRQ */ + +#define SSBI_REG_ADDR_IRQ_BASE 0x1BB + +#define SSBI_REG_ADDR_IRQ_ROOT (SSBI_REG_ADDR_IRQ_BASE + 0) +#define SSBI_REG_ADDR_IRQ_M_STATUS1 (SSBI_REG_ADDR_IRQ_BASE + 1) +#define SSBI_REG_ADDR_IRQ_M_STATUS2 (SSBI_REG_ADDR_IRQ_BASE + 2) +#define SSBI_REG_ADDR_IRQ_M_STATUS3 (SSBI_REG_ADDR_IRQ_BASE + 3) +#define SSBI_REG_ADDR_IRQ_M_STATUS4 (SSBI_REG_ADDR_IRQ_BASE + 4) +#define SSBI_REG_ADDR_IRQ_BLK_SEL (SSBI_REG_ADDR_IRQ_BASE + 5) +#define SSBI_REG_ADDR_IRQ_IT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 6) +#define SSBI_REG_ADDR_IRQ_CONFIG (SSBI_REG_ADDR_IRQ_BASE + 7) +#define SSBI_REG_ADDR_IRQ_RT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 8) + +#define PM_IRQF_LVL_SEL 0x01 /* level select */ +#define PM_IRQF_MASK_FE 0x02 /* mask falling edge */ +#define PM_IRQF_MASK_RE 0x04 /* mask rising edge */ +#define PM_IRQF_CLR 0x08 /* clear interrupt */ +#define PM_IRQF_BITS_MASK 0x70 +#define PM_IRQF_BITS_SHIFT 4 +#define PM_IRQF_WRITE 0x80 + +#define PM_IRQF_MASK_ALL (PM_IRQF_MASK_FE | \ + PM_IRQF_MASK_RE) + +struct pm_irq_chip { + struct device *dev; + spinlock_t pm_irq_lock; + unsigned int devirq; + unsigned int irq_base; + unsigned int num_irqs; + unsigned int num_blocks; + unsigned int num_masters; + u8 config[0]; +}; + +static int pm8xxx_read_root_irq(const struct pm_irq_chip *chip, u8 *rp) +{ + return pm8xxx_readb(chip->dev, SSBI_REG_ADDR_IRQ_ROOT, rp); +} + +static int pm8xxx_read_master_irq(const struct pm_irq_chip *chip, u8 m, u8 *bp) +{ + return pm8xxx_readb(chip->dev, + SSBI_REG_ADDR_IRQ_M_STATUS1 + m, bp); +} + +static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, u8 bp, u8 *ip) +{ + int rc; + + spin_lock(&chip->pm_irq_lock); + rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); + goto bail; + } + + rc = pm8xxx_readb(chip->dev, SSBI_REG_ADDR_IRQ_IT_STATUS, ip); + if (rc) + pr_err("Failed Reading Status rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + return rc; +} + +static int pm8xxx_config_irq(struct pm_irq_chip *chip, u8 bp, u8 cp) +{ + int rc; + + spin_lock(&chip->pm_irq_lock); + rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); + goto bail; + } + + cp |= PM_IRQF_WRITE; + rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_CONFIG, cp); + if (rc) + pr_err("Failed Configuring IRQ rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + return rc; +} + +static int pm8xxx_irq_block_handler(struct pm_irq_chip *chip, int block) +{ + int pmirq, irq, i, ret = 0; + u8 bits; + + ret = pm8xxx_read_block_irq(chip, block, &bits); + if (ret) { + pr_err("Failed reading %d block ret=%d", block, ret); + return ret; + } + if (!bits) { + pr_err("block bit set in master but no irqs: %d", block); + return 0; + } + + /* Check IRQ bits */ + for (i = 0; i < 8; i++) { + if (bits & (1 << i)) { + pmirq = block * 8 + i; + irq = pmirq + chip->irq_base; + generic_handle_irq(irq); + } + } + return 0; +} + +static int pm8xxx_irq_master_handler(struct pm_irq_chip *chip, int master) +{ + u8 blockbits; + int block_number, i, ret = 0; + + ret = pm8xxx_read_master_irq(chip, master, &blockbits); + if (ret) { + pr_err("Failed to read master %d ret=%d\n", master, ret); + return ret; + } + if (!blockbits) { + pr_err("master bit set in root but no blocks: %d", master); + return 0; + } + + for (i = 0; i < 8; i++) + if (blockbits & (1 << i)) { + block_number = master * 8 + i; /* block # */ + ret |= pm8xxx_irq_block_handler(chip, block_number); + } + return ret; +} + +static void pm8xxx_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct pm_irq_chip *chip = irq_desc_get_handler_data(desc); + struct irq_chip *irq_chip = irq_desc_get_chip(desc); + u8 root; + int i, ret, masters = 0; + + ret = pm8xxx_read_root_irq(chip, &root); + if (ret) { + pr_err("Can't read root status ret=%d\n", ret); + return; + } + + /* on pm8xxx series masters start from bit 1 of the root */ + masters = root >> 1; + + /* Read allowed masters for blocks. */ + for (i = 0; i < chip->num_masters; i++) + if (masters & (1 << i)) + pm8xxx_irq_master_handler(chip, i); + + irq_chip->irq_ack(&desc->irq_data); +} + +static void pm8xxx_irq_mask_ack(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = d->irq - chip->irq_base; + int master, irq_bit; + u8 block, config; + + block = pmirq / 8; + master = block / 8; + irq_bit = pmirq % 8; + + config = chip->config[pmirq] | PM_IRQF_MASK_ALL | PM_IRQF_CLR; + pm8xxx_config_irq(chip, block, config); +} + +static void pm8xxx_irq_unmask(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = d->irq - chip->irq_base; + int master, irq_bit; + u8 block, config; + + block = pmirq / 8; + master = block / 8; + irq_bit = pmirq % 8; + + config = chip->config[pmirq]; + pm8xxx_config_irq(chip, block, config); +} + +static int pm8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = d->irq - chip->irq_base; + int master, irq_bit; + u8 block, config; + + block = pmirq / 8; + master = block / 8; + irq_bit = pmirq % 8; + + chip->config[pmirq] = (irq_bit << PM_IRQF_BITS_SHIFT) + | PM_IRQF_MASK_ALL; + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + if (flow_type & IRQF_TRIGGER_RISING) + chip->config[pmirq] &= ~PM_IRQF_MASK_RE; + if (flow_type & IRQF_TRIGGER_FALLING) + chip->config[pmirq] &= ~PM_IRQF_MASK_FE; + } else { + chip->config[pmirq] |= PM_IRQF_LVL_SEL; + + if (flow_type & IRQF_TRIGGER_HIGH) + chip->config[pmirq] &= ~PM_IRQF_MASK_RE; + else + chip->config[pmirq] &= ~PM_IRQF_MASK_FE; + } + + config = chip->config[pmirq] | PM_IRQF_CLR; + return pm8xxx_config_irq(chip, block, config); +} + +static int pm8xxx_irq_set_wake(struct irq_data *d, unsigned int on) +{ + return 0; +} + +static struct irq_chip pm8xxx_irq_chip = { + .name = "pm8xxx", + .irq_mask_ack = pm8xxx_irq_mask_ack, + .irq_unmask = pm8xxx_irq_unmask, + .irq_set_type = pm8xxx_irq_set_type, + .irq_set_wake = pm8xxx_irq_set_wake, + .flags = IRQCHIP_MASK_ON_SUSPEND, +}; + +/** + * pm8xxx_get_irq_stat - get the status of the irq line + * @chip: pointer to identify a pmic irq controller + * @irq: the irq number + * + * The pm8xxx gpio and mpp rely on the interrupt block to read + * the values on their pins. This function is to facilitate reading + * the status of a gpio or an mpp line. The caller has to convert the + * gpio number to irq number. + * + * RETURNS: + * an int indicating the value read on that line + */ +int pm8xxx_get_irq_stat(struct pm_irq_chip *chip, int irq) +{ + int pmirq, rc; + u8 block, bits, bit; + unsigned long flags; + + if (chip == NULL || irq < chip->irq_base || + irq >= chip->irq_base + chip->num_irqs) + return -EINVAL; + + pmirq = irq - chip->irq_base; + + block = pmirq / 8; + bit = pmirq % 8; + + spin_lock_irqsave(&chip->pm_irq_lock, flags); + + rc = pm8xxx_writeb(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, block); + if (rc) { + pr_err("Failed Selecting block irq=%d pmirq=%d blk=%d rc=%d\n", + irq, pmirq, block, rc); + goto bail_out; + } + + rc = pm8xxx_readb(chip->dev, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits); + if (rc) { + pr_err("Failed Configuring irq=%d pmirq=%d blk=%d rc=%d\n", + irq, pmirq, block, rc); + goto bail_out; + } + + rc = (bits & (1 << bit)) ? 1 : 0; + +bail_out: + spin_unlock_irqrestore(&chip->pm_irq_lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_get_irq_stat); + +struct pm_irq_chip * __devinit pm8xxx_irq_init(struct device *dev, + const struct pm8xxx_irq_platform_data *pdata) +{ + struct pm_irq_chip *chip; + int devirq, rc; + unsigned int pmirq; + + if (!pdata) { + pr_err("No platform data\n"); + return ERR_PTR(-EINVAL); + } + + devirq = pdata->devirq; + if (devirq < 0) { + pr_err("missing devirq\n"); + rc = devirq; + return ERR_PTR(-EINVAL); + } + + chip = kzalloc(sizeof(struct pm_irq_chip) + + sizeof(u8) * pdata->irq_cdata.nirqs, GFP_KERNEL); + if (!chip) { + pr_err("Cannot alloc pm_irq_chip struct\n"); + return ERR_PTR(-EINVAL); + } + + chip->dev = dev; + chip->devirq = devirq; + chip->irq_base = pdata->irq_base; + chip->num_irqs = pdata->irq_cdata.nirqs; + chip->num_blocks = DIV_ROUND_UP(chip->num_irqs, 8); + chip->num_masters = DIV_ROUND_UP(chip->num_blocks, 8); + spin_lock_init(&chip->pm_irq_lock); + + for (pmirq = 0; pmirq < chip->num_irqs; pmirq++) { + irq_set_chip_and_handler(chip->irq_base + pmirq, + &pm8xxx_irq_chip, + handle_level_irq); + irq_set_chip_data(chip->irq_base + pmirq, chip); +#ifdef CONFIG_ARM + set_irq_flags(chip->irq_base + pmirq, IRQF_VALID); +#else + irq_set_noprobe(chip->irq_base + pmirq); +#endif + } + + irq_set_irq_type(devirq, pdata->irq_trigger_flag); + irq_set_handler_data(devirq, chip); + irq_set_chained_handler(devirq, pm8xxx_irq_handler); + set_irq_wake(devirq, 1); + + return chip; +} + +int __devexit pm8xxx_irq_exit(struct pm_irq_chip *chip) +{ + irq_set_chained_handler(chip->devirq, NULL); + kfree(chip); + return 0; +} diff --git a/drivers/mfd/rdc321x-southbridge.c b/drivers/mfd/rdc321x-southbridge.c index 10dbe6374a8..809bd4a6108 100644 --- a/drivers/mfd/rdc321x-southbridge.c +++ b/drivers/mfd/rdc321x-southbridge.c @@ -61,12 +61,14 @@ static struct mfd_cell rdc321x_sb_cells[] = { .name = "rdc321x-wdt", .resources = rdc321x_wdt_resource, .num_resources = ARRAY_SIZE(rdc321x_wdt_resource), - .mfd_data = &rdc321x_wdt_pdata, + .platform_data = &rdc321x_wdt_pdata, + .pdata_size = sizeof(rdc321x_wdt_pdata), }, { .name = "rdc321x-gpio", .resources = rdc321x_gpio_resources, .num_resources = ARRAY_SIZE(rdc321x_gpio_resources), - .mfd_data = &rdc321x_gpio_pdata, + .platform_data = &rdc321x_gpio_pdata, + .pdata_size = sizeof(rdc321x_gpio_pdata), }, }; diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index 42830e69296..91ad21ef772 100644 --- a/drivers/mfd/t7l66xb.c +++ b/drivers/mfd/t7l66xb.c @@ -170,7 +170,8 @@ static struct mfd_cell t7l66xb_cells[] = { .name = "tmio-mmc", .enable = t7l66xb_mmc_enable, .disable = t7l66xb_mmc_disable, - .mfd_data = &t7166xb_mmc_data, + .platform_data = &t7166xb_mmc_data, + .pdata_size = sizeof(t7166xb_mmc_data), .num_resources = ARRAY_SIZE(t7l66xb_mmc_resources), .resources = t7l66xb_mmc_resources, }, @@ -382,7 +383,8 @@ static int t7l66xb_probe(struct platform_device *dev) t7l66xb_attach_irq(dev); - t7l66xb_cells[T7L66XB_CELL_NAND].mfd_data = pdata->nand_data; + t7l66xb_cells[T7L66XB_CELL_NAND].platform_data = pdata->nand_data; + t7l66xb_cells[T7L66XB_CELL_NAND].pdata_size = sizeof(*pdata->nand_data); ret = mfd_add_devices(&dev->dev, dev->id, t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells), diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c index b006f7cee95..ad715bf49ca 100644 --- a/drivers/mfd/tc6387xb.c +++ b/drivers/mfd/tc6387xb.c @@ -131,7 +131,8 @@ static struct mfd_cell tc6387xb_cells[] = { .name = "tmio-mmc", .enable = tc6387xb_mmc_enable, .disable = tc6387xb_mmc_disable, - .mfd_data = &tc6387xb_mmc_data, + .platform_data = &tc6387xb_mmc_data, + .pdata_size = sizeof(tc6387xb_mmc_data), .num_resources = ARRAY_SIZE(tc6387xb_mmc_resources), .resources = tc6387xb_mmc_resources, }, diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c index fc53ce28760..9612264f0e6 100644 --- a/drivers/mfd/tc6393xb.c +++ b/drivers/mfd/tc6393xb.c @@ -393,7 +393,8 @@ static struct mfd_cell __devinitdata tc6393xb_cells[] = { .name = "tmio-mmc", .enable = tc6393xb_mmc_enable, .resume = tc6393xb_mmc_resume, - .mfd_data = &tc6393xb_mmc_data, + .platform_data = &tc6393xb_mmc_data, + .pdata_size = sizeof(tc6393xb_mmc_data), .num_resources = ARRAY_SIZE(tc6393xb_mmc_resources), .resources = tc6393xb_mmc_resources, }, @@ -692,8 +693,11 @@ static int __devinit tc6393xb_probe(struct platform_device *dev) goto err_setup; } - tc6393xb_cells[TC6393XB_CELL_NAND].mfd_data = tcpd->nand_data; - tc6393xb_cells[TC6393XB_CELL_FB].mfd_data = tcpd->fb_data; + tc6393xb_cells[TC6393XB_CELL_NAND].platform_data = tcpd->nand_data; + tc6393xb_cells[TC6393XB_CELL_NAND].pdata_size = + sizeof(*tcpd->nand_data); + tc6393xb_cells[TC6393XB_CELL_FB].platform_data = tcpd->fb_data; + tc6393xb_cells[TC6393XB_CELL_FB].pdata_size = sizeof(*tcpd->fb_data); ret = mfd_add_devices(&dev->dev, dev->id, tc6393xb_cells, ARRAY_SIZE(tc6393xb_cells), diff --git a/drivers/mfd/timberdale.c b/drivers/mfd/timberdale.c index 94c6c8afad1..69272e4e345 100644 --- a/drivers/mfd/timberdale.c +++ b/drivers/mfd/timberdale.c @@ -384,7 +384,8 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .mfd_data = &timb_dma_platform_data, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), }, { .name = "timb-uart", @@ -395,37 +396,43 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = { .name = "xiic-i2c", .num_resources = ARRAY_SIZE(timberdale_xiic_resources), .resources = timberdale_xiic_resources, - .mfd_data = &timberdale_xiic_platform_data, + .platform_data = &timberdale_xiic_platform_data, + .pdata_size = sizeof(timberdale_xiic_platform_data), }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .mfd_data = &timberdale_gpio_platform_data, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), }, { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .mfd_data = &timberdale_video_platform_data, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .mfd_data = &timberdale_radio_platform_data, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .mfd_data = &timberdale_xspi_platform_data, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), }, { .name = "ks8842", .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, - .mfd_data = &timberdale_ks8842_platform_data, + .platform_data = &timberdale_ks8842_platform_data, + .pdata_size = sizeof(timberdale_ks8842_platform_data), }, }; @@ -434,7 +441,8 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .mfd_data = &timb_dma_platform_data, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), }, { .name = "timb-uart", @@ -450,13 +458,15 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .name = "xiic-i2c", .num_resources = ARRAY_SIZE(timberdale_xiic_resources), .resources = timberdale_xiic_resources, - .mfd_data = &timberdale_xiic_platform_data, + .platform_data = &timberdale_xiic_platform_data, + .pdata_size = sizeof(timberdale_xiic_platform_data), }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .mfd_data = &timberdale_gpio_platform_data, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), }, { .name = "timb-mlogicore", @@ -467,25 +477,29 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .mfd_data = &timberdale_video_platform_data, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .mfd_data = &timberdale_radio_platform_data, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .mfd_data = &timberdale_xspi_platform_data, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), }, { .name = "ks8842", .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, - .mfd_data = &timberdale_ks8842_platform_data, + .platform_data = &timberdale_ks8842_platform_data, + .pdata_size = sizeof(timberdale_ks8842_platform_data), }, }; @@ -494,7 +508,8 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .mfd_data = &timb_dma_platform_data, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), }, { .name = "timb-uart", @@ -505,31 +520,36 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = { .name = "xiic-i2c", .num_resources = ARRAY_SIZE(timberdale_xiic_resources), .resources = timberdale_xiic_resources, - .mfd_data = &timberdale_xiic_platform_data, + .platform_data = &timberdale_xiic_platform_data, + .pdata_size = sizeof(timberdale_xiic_platform_data), }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .mfd_data = &timberdale_gpio_platform_data, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), }, { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .mfd_data = &timberdale_video_platform_data, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .mfd_data = &timberdale_radio_platform_data, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .mfd_data = &timberdale_xspi_platform_data, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), }, }; @@ -538,7 +558,8 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = { .name = "timb-dma", .num_resources = ARRAY_SIZE(timberdale_dma_resources), .resources = timberdale_dma_resources, - .mfd_data = &timb_dma_platform_data, + .platform_data = &timb_dma_platform_data, + .pdata_size = sizeof(timb_dma_platform_data), }, { .name = "timb-uart", @@ -549,37 +570,43 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = { .name = "ocores-i2c", .num_resources = ARRAY_SIZE(timberdale_ocores_resources), .resources = timberdale_ocores_resources, - .mfd_data = &timberdale_ocores_platform_data, + .platform_data = &timberdale_ocores_platform_data, + .pdata_size = sizeof(timberdale_ocores_platform_data), }, { .name = "timb-gpio", .num_resources = ARRAY_SIZE(timberdale_gpio_resources), .resources = timberdale_gpio_resources, - .mfd_data = &timberdale_gpio_platform_data, + .platform_data = &timberdale_gpio_platform_data, + .pdata_size = sizeof(timberdale_gpio_platform_data), }, { .name = "timb-video", .num_resources = ARRAY_SIZE(timberdale_video_resources), .resources = timberdale_video_resources, - .mfd_data = &timberdale_video_platform_data, + .platform_data = &timberdale_video_platform_data, + .pdata_size = sizeof(timberdale_video_platform_data), }, { .name = "timb-radio", .num_resources = ARRAY_SIZE(timberdale_radio_resources), .resources = timberdale_radio_resources, - .mfd_data = &timberdale_radio_platform_data, + .platform_data = &timberdale_radio_platform_data, + .pdata_size = sizeof(timberdale_radio_platform_data), }, { .name = "xilinx_spi", .num_resources = ARRAY_SIZE(timberdale_spi_resources), .resources = timberdale_spi_resources, - .mfd_data = &timberdale_xspi_platform_data, + .platform_data = &timberdale_xspi_platform_data, + .pdata_size = sizeof(timberdale_xspi_platform_data), }, { .name = "ks8842", .num_resources = ARRAY_SIZE(timberdale_eth_resources), .resources = timberdale_eth_resources, - .mfd_data = &timberdale_ks8842_platform_data, + .platform_data = &timberdale_ks8842_platform_data, + .pdata_size = sizeof(timberdale_ks8842_platform_data), }, }; diff --git a/drivers/mfd/tps6105x.c b/drivers/mfd/tps6105x.c index 46d8205646b..a293b978e27 100644 --- a/drivers/mfd/tps6105x.c +++ b/drivers/mfd/tps6105x.c @@ -183,7 +183,8 @@ static int __devinit tps6105x_probe(struct i2c_client *client, /* Set up and register the platform devices. */ for (i = 0; i < ARRAY_SIZE(tps6105x_cells); i++) { /* One state holder for all drivers, this is simple */ - tps6105x_cells[i].mfd_data = tps6105x; + tps6105x_cells[i].platform_data = tps6105x; + tps6105x_cells[i].pdata_size = sizeof(*tps6105x); } ret = mfd_add_devices(&client->dev, 0, tps6105x_cells, diff --git a/drivers/mfd/tps6586x.c b/drivers/mfd/tps6586x.c index b600808690c..bba26d96c24 100644 --- a/drivers/mfd/tps6586x.c +++ b/drivers/mfd/tps6586x.c @@ -270,8 +270,8 @@ static void tps6586x_gpio_set(struct gpio_chip *chip, unsigned offset, { struct tps6586x *tps6586x = container_of(chip, struct tps6586x, gpio); - __tps6586x_write(tps6586x->client, TPS6586X_GPIOSET2, - value << offset); + tps6586x_update(tps6586x->dev, TPS6586X_GPIOSET2, + value << offset, 1 << offset); } static int tps6586x_gpio_output(struct gpio_chip *gc, unsigned offset, diff --git a/drivers/mfd/tps65910-irq.c b/drivers/mfd/tps65910-irq.c new file mode 100644 index 00000000000..2bfad5c86cc --- /dev/null +++ b/drivers/mfd/tps65910-irq.c @@ -0,0 +1,218 @@ +/* + * tps65910-irq.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65910.h> + +static inline int irq_to_tps65910_irq(struct tps65910 *tps65910, + int irq) +{ + return (irq - tps65910->irq_base); +} + +/* + * This is a threaded IRQ handler so can access I2C/SPI. Since all + * interrupts are clear on read the IRQ line will be reasserted and + * the physical IRQ will be handled again if another interrupt is + * asserted while we run - in the normal course of events this is a + * rare occurrence so we save I2C/SPI reads. We're also assuming that + * it's rare to get lots of interrupts firing simultaneously so try to + * minimise I/O. + */ +static irqreturn_t tps65910_irq(int irq, void *irq_data) +{ + struct tps65910 *tps65910 = irq_data; + u32 irq_sts; + u32 irq_mask; + u8 reg; + int i; + + tps65910->read(tps65910, TPS65910_INT_STS, 1, ®); + irq_sts = reg; + tps65910->read(tps65910, TPS65910_INT_STS2, 1, ®); + irq_sts |= reg << 8; + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + tps65910->read(tps65910, TPS65910_INT_STS3, 1, ®); + irq_sts |= reg << 16; + } + + tps65910->read(tps65910, TPS65910_INT_MSK, 1, ®); + irq_mask = reg; + tps65910->read(tps65910, TPS65910_INT_MSK2, 1, ®); + irq_mask |= reg << 8; + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + tps65910->read(tps65910, TPS65910_INT_MSK3, 1, ®); + irq_mask |= reg << 16; + } + + irq_sts &= ~irq_mask; + + if (!irq_sts) + return IRQ_NONE; + + for (i = 0; i < tps65910->irq_num; i++) { + + if (!(irq_sts & (1 << i))) + continue; + + handle_nested_irq(tps65910->irq_base + i); + } + + /* Write the STS register back to clear IRQs we handled */ + reg = irq_sts & 0xFF; + irq_sts >>= 8; + tps65910->write(tps65910, TPS65910_INT_STS, 1, ®); + reg = irq_sts & 0xFF; + tps65910->write(tps65910, TPS65910_INT_STS2, 1, ®); + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + reg = irq_sts >> 8; + tps65910->write(tps65910, TPS65910_INT_STS3, 1, ®); + } + + return IRQ_HANDLED; +} + +static void tps65910_irq_lock(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + + mutex_lock(&tps65910->irq_lock); +} + +static void tps65910_irq_sync_unlock(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + u32 reg_mask; + u8 reg; + + tps65910->read(tps65910, TPS65910_INT_MSK, 1, ®); + reg_mask = reg; + tps65910->read(tps65910, TPS65910_INT_MSK2, 1, ®); + reg_mask |= reg << 8; + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + tps65910->read(tps65910, TPS65910_INT_MSK3, 1, ®); + reg_mask |= reg << 16; + } + + if (tps65910->irq_mask != reg_mask) { + reg = tps65910->irq_mask & 0xFF; + tps65910->write(tps65910, TPS65910_INT_MSK, 1, ®); + reg = tps65910->irq_mask >> 8 & 0xFF; + tps65910->write(tps65910, TPS65910_INT_MSK2, 1, ®); + switch (tps65910_chip_id(tps65910)) { + case TPS65911: + reg = tps65910->irq_mask >> 16; + tps65910->write(tps65910, TPS65910_INT_MSK3, 1, ®); + } + } + mutex_unlock(&tps65910->irq_lock); +} + +static void tps65910_irq_enable(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + + tps65910->irq_mask &= ~( 1 << irq_to_tps65910_irq(tps65910, data->irq)); +} + +static void tps65910_irq_disable(struct irq_data *data) +{ + struct tps65910 *tps65910 = irq_data_get_irq_chip_data(data); + + tps65910->irq_mask |= ( 1 << irq_to_tps65910_irq(tps65910, data->irq)); +} + +static struct irq_chip tps65910_irq_chip = { + .name = "tps65910", + .irq_bus_lock = tps65910_irq_lock, + .irq_bus_sync_unlock = tps65910_irq_sync_unlock, + .irq_disable = tps65910_irq_disable, + .irq_enable = tps65910_irq_enable, +}; + +int tps65910_irq_init(struct tps65910 *tps65910, int irq, + struct tps65910_platform_data *pdata) +{ + int ret, cur_irq; + int flags = IRQF_ONESHOT; + + if (!irq) { + dev_warn(tps65910->dev, "No interrupt support, no core IRQ\n"); + return -EINVAL; + } + + if (!pdata || !pdata->irq_base) { + dev_warn(tps65910->dev, "No interrupt support, no IRQ base\n"); + return -EINVAL; + } + + tps65910->irq_mask = 0xFFFFFF; + + mutex_init(&tps65910->irq_lock); + tps65910->chip_irq = irq; + tps65910->irq_base = pdata->irq_base; + + switch (tps65910_chip_id(tps65910)) { + case TPS65910: + tps65910->irq_num = TPS65910_NUM_IRQ; + case TPS65911: + tps65910->irq_num = TPS65911_NUM_IRQ; + } + + /* Register with genirq */ + for (cur_irq = tps65910->irq_base; + cur_irq < tps65910->irq_num + tps65910->irq_base; + cur_irq++) { + irq_set_chip_data(cur_irq, tps65910); + irq_set_chip_and_handler(cur_irq, &tps65910_irq_chip, + handle_edge_irq); + irq_set_nested_thread(cur_irq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(irq, NULL, tps65910_irq, flags, + "tps65910", tps65910); + + irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW); + + if (ret != 0) + dev_err(tps65910->dev, "Failed to request IRQ: %d\n", ret); + + return ret; +} + +int tps65910_irq_exit(struct tps65910 *tps65910) +{ + free_irq(tps65910->chip_irq, tps65910); + return 0; +} diff --git a/drivers/mfd/tps65910.c b/drivers/mfd/tps65910.c new file mode 100644 index 00000000000..2229e66d80d --- /dev/null +++ b/drivers/mfd/tps65910.c @@ -0,0 +1,229 @@ +/* + * tps65910.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tps65910.h> + +static struct mfd_cell tps65910s[] = { + { + .name = "tps65910-pmic", + }, + { + .name = "tps65910-rtc", + }, + { + .name = "tps65910-power", + }, +}; + + +static int tps65910_i2c_read(struct tps65910 *tps65910, u8 reg, + int bytes, void *dest) +{ + struct i2c_client *i2c = tps65910->i2c_client; + struct i2c_msg xfer[2]; + int ret; + + /* Write register */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = bytes; + xfer[1].buf = dest; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + return ret; +} + +static int tps65910_i2c_write(struct tps65910 *tps65910, u8 reg, + int bytes, void *src) +{ + struct i2c_client *i2c = tps65910->i2c_client; + /* we add 1 byte for device register */ + u8 msg[TPS65910_MAX_REGISTER + 1]; + int ret; + + if (bytes > TPS65910_MAX_REGISTER) + return -EINVAL; + + msg[0] = reg; + memcpy(&msg[1], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 1); + if (ret < 0) + return ret; + if (ret != bytes + 1) + return -EIO; + return 0; +} + +int tps65910_set_bits(struct tps65910 *tps65910, u8 reg, u8 mask) +{ + u8 data; + int err; + + mutex_lock(&tps65910->io_mutex); + err = tps65910_i2c_read(tps65910, reg, 1, &data); + if (err) { + dev_err(tps65910->dev, "read from reg %x failed\n", reg); + goto out; + } + + data |= mask; + err = tps65910_i2c_write(tps65910, reg, 1, &data); + if (err) + dev_err(tps65910->dev, "write to reg %x failed\n", reg); + +out: + mutex_unlock(&tps65910->io_mutex); + return err; +} +EXPORT_SYMBOL_GPL(tps65910_set_bits); + +int tps65910_clear_bits(struct tps65910 *tps65910, u8 reg, u8 mask) +{ + u8 data; + int err; + + mutex_lock(&tps65910->io_mutex); + err = tps65910_i2c_read(tps65910, reg, 1, &data); + if (err) { + dev_err(tps65910->dev, "read from reg %x failed\n", reg); + goto out; + } + + data &= mask; + err = tps65910_i2c_write(tps65910, reg, 1, &data); + if (err) + dev_err(tps65910->dev, "write to reg %x failed\n", reg); + +out: + mutex_unlock(&tps65910->io_mutex); + return err; +} +EXPORT_SYMBOL_GPL(tps65910_clear_bits); + +static int tps65910_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tps65910 *tps65910; + struct tps65910_board *pmic_plat_data; + struct tps65910_platform_data *init_data; + int ret = 0; + + pmic_plat_data = dev_get_platdata(&i2c->dev); + if (!pmic_plat_data) + return -EINVAL; + + init_data = kzalloc(sizeof(struct tps65910_platform_data), GFP_KERNEL); + if (init_data == NULL) + return -ENOMEM; + + init_data->irq = pmic_plat_data->irq; + init_data->irq_base = pmic_plat_data->irq; + + tps65910 = kzalloc(sizeof(struct tps65910), GFP_KERNEL); + if (tps65910 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, tps65910); + tps65910->dev = &i2c->dev; + tps65910->i2c_client = i2c; + tps65910->id = id->driver_data; + tps65910->read = tps65910_i2c_read; + tps65910->write = tps65910_i2c_write; + mutex_init(&tps65910->io_mutex); + + ret = mfd_add_devices(tps65910->dev, -1, + tps65910s, ARRAY_SIZE(tps65910s), + NULL, 0); + if (ret < 0) + goto err; + + tps65910_gpio_init(tps65910, pmic_plat_data->gpio_base); + + ret = tps65910_irq_init(tps65910, init_data->irq, init_data); + if (ret < 0) + goto err; + + return ret; + +err: + mfd_remove_devices(tps65910->dev); + kfree(tps65910); + return ret; +} + +static int tps65910_i2c_remove(struct i2c_client *i2c) +{ + struct tps65910 *tps65910 = i2c_get_clientdata(i2c); + + mfd_remove_devices(tps65910->dev); + kfree(tps65910); + + return 0; +} + +static const struct i2c_device_id tps65910_i2c_id[] = { + { "tps65910", TPS65910 }, + { "tps65911", TPS65911 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps65910_i2c_id); + + +static struct i2c_driver tps65910_i2c_driver = { + .driver = { + .name = "tps65910", + .owner = THIS_MODULE, + }, + .probe = tps65910_i2c_probe, + .remove = tps65910_i2c_remove, + .id_table = tps65910_i2c_id, +}; + +static int __init tps65910_i2c_init(void) +{ + return i2c_add_driver(&tps65910_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(tps65910_i2c_init); + +static void __exit tps65910_i2c_exit(void) +{ + i2c_del_driver(&tps65910_i2c_driver); +} +module_exit(tps65910_i2c_exit); + +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_AUTHOR("Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS6591x chip family multi-function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/tps65911-comparator.c b/drivers/mfd/tps65911-comparator.c new file mode 100644 index 00000000000..3d2dc56a3d4 --- /dev/null +++ b/drivers/mfd/tps65911-comparator.c @@ -0,0 +1,188 @@ +/* + * tps65910.c -- TI TPS6591x + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/debugfs.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65910.h> + +#define COMP 0 +#define COMP1 1 +#define COMP2 2 + +/* Comparator 1 voltage selection table in milivolts */ +static const u16 COMP_VSEL_TABLE[] = { + 0, 2500, 2500, 2500, 2500, 2550, 2600, 2650, + 2700, 2750, 2800, 2850, 2900, 2950, 3000, 3050, + 3100, 3150, 3200, 3250, 3300, 3350, 3400, 3450, + 3500, +}; + +struct comparator { + const char *name; + int reg; + int uV_max; + const u16 *vsel_table; +}; + +static struct comparator tps_comparators[] = { + { + .name = "COMP1", + .reg = TPS65911_VMBCH, + .uV_max = 3500, + .vsel_table = COMP_VSEL_TABLE, + }, + { + .name = "COMP2", + .reg = TPS65911_VMBCH2, + .uV_max = 3500, + .vsel_table = COMP_VSEL_TABLE, + }, +}; + +static int comp_threshold_set(struct tps65910 *tps65910, int id, int voltage) +{ + struct comparator tps_comp = tps_comparators[id]; + int curr_voltage = 0; + int ret; + u8 index = 0, val; + + if (id == COMP) + return 0; + + while (curr_voltage < tps_comp.uV_max) { + curr_voltage = tps_comp.vsel_table[index]; + if (curr_voltage >= voltage) + break; + else if (curr_voltage < voltage) + index ++; + } + + if (curr_voltage > tps_comp.uV_max) + return -EINVAL; + + val = index << 1; + ret = tps65910->write(tps65910, tps_comp.reg, 1, &val); + + return ret; +} + +static int comp_threshold_get(struct tps65910 *tps65910, int id) +{ + struct comparator tps_comp = tps_comparators[id]; + int ret; + u8 val; + + if (id == COMP) + return 0; + + ret = tps65910->read(tps65910, tps_comp.reg, 1, &val); + if (ret < 0) + return ret; + + val >>= 1; + return tps_comp.vsel_table[val]; +} + +static ssize_t comp_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tps65910 *tps65910 = dev_get_drvdata(dev->parent); + struct attribute comp_attr = attr->attr; + int id, uVolt; + + if (!strcmp(comp_attr.name, "comp1_threshold")) + id = COMP1; + else if (!strcmp(comp_attr.name, "comp2_threshold")) + id = COMP2; + else + return -EINVAL; + + uVolt = comp_threshold_get(tps65910, id); + + return sprintf(buf, "%d\n", uVolt); +} + +static DEVICE_ATTR(comp1_threshold, S_IRUGO, comp_threshold_show, NULL); +static DEVICE_ATTR(comp2_threshold, S_IRUGO, comp_threshold_show, NULL); + +static __devinit int tps65911_comparator_probe(struct platform_device *pdev) +{ + struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent); + struct tps65910_platform_data *pdata = dev_get_platdata(tps65910->dev); + int ret; + + ret = comp_threshold_set(tps65910, COMP1, pdata->vmbch_threshold); + if (ret < 0) { + dev_err(&pdev->dev, "cannot set COMP1 threshold\n"); + return ret; + } + + ret = comp_threshold_set(tps65910, COMP2, pdata->vmbch2_threshold); + if (ret < 0) { + dev_err(&pdev->dev, "cannot set COMP2 theshold\n"); + return ret; + } + + /* Create sysfs entry */ + ret = device_create_file(&pdev->dev, &dev_attr_comp1_threshold); + if (ret < 0) + dev_err(&pdev->dev, "failed to add COMP1 sysfs file\n"); + + ret = device_create_file(&pdev->dev, &dev_attr_comp2_threshold); + if (ret < 0) + dev_err(&pdev->dev, "failed to add COMP2 sysfs file\n"); + + return ret; +} + +static __devexit int tps65911_comparator_remove(struct platform_device *pdev) +{ + struct tps65910 *tps65910; + + tps65910 = dev_get_drvdata(pdev->dev.parent); + + return 0; +} + +static struct platform_driver tps65911_comparator_driver = { + .driver = { + .name = "tps65911-comparator", + .owner = THIS_MODULE, + }, + .probe = tps65911_comparator_probe, + .remove = __devexit_p(tps65911_comparator_remove), +}; + +static int __init tps65911_comparator_init(void) +{ + return platform_driver_register(&tps65911_comparator_driver); +} +subsys_initcall(tps65911_comparator_init); + +static void __exit tps65911_comparator_exit(void) +{ + platform_driver_unregister(&tps65911_comparator_driver); +} +module_exit(tps65911_comparator_exit); + +MODULE_AUTHOR("Jorge Eduardo Candelaria <jedu@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS65911 comparator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps65911-comparator"); diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 960b5bed7f5..b8f2a4e7f6e 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -198,6 +198,7 @@ #define TWL6030_BASEADD_GASGAUGE 0x00C0 #define TWL6030_BASEADD_PIH 0x00D0 #define TWL6030_BASEADD_CHARGER 0x00E0 +#define TWL6025_BASEADD_CHARGER 0x00DA /* subchip/slave 2 0x4A - DFT */ #define TWL6030_BASEADD_DIEID 0x00C0 @@ -229,6 +230,9 @@ /* is driver active, bound to a chip? */ static bool inuse; +/* TWL IDCODE Register value */ +static u32 twl_idcode; + static unsigned int twl_id; unsigned int twl_rev(void) { @@ -328,6 +332,7 @@ static struct twl_mapping twl6030_map[] = { { SUB_CHIP_ID0, TWL6030_BASEADD_RTC }, { SUB_CHIP_ID0, TWL6030_BASEADD_MEM }, + { SUB_CHIP_ID1, TWL6025_BASEADD_CHARGER }, }; /*----------------------------------------------------------------------*/ @@ -487,6 +492,58 @@ EXPORT_SYMBOL(twl_i2c_read_u8); /*----------------------------------------------------------------------*/ +/** + * twl_read_idcode_register - API to read the IDCODE register. + * + * Unlocks the IDCODE register and read the 32 bit value. + */ +static int twl_read_idcode_register(void) +{ + int err; + + err = twl_i2c_write_u8(TWL4030_MODULE_INTBR, TWL_EEPROM_R_UNLOCK, + REG_UNLOCK_TEST_REG); + if (err) { + pr_err("TWL4030 Unable to unlock IDCODE registers -%d\n", err); + goto fail; + } + + err = twl_i2c_read(TWL4030_MODULE_INTBR, (u8 *)(&twl_idcode), + REG_IDCODE_7_0, 4); + if (err) { + pr_err("TWL4030: unable to read IDCODE -%d\n", err); + goto fail; + } + + err = twl_i2c_write_u8(TWL4030_MODULE_INTBR, 0x0, REG_UNLOCK_TEST_REG); + if (err) + pr_err("TWL4030 Unable to relock IDCODE registers -%d\n", err); +fail: + return err; +} + +/** + * twl_get_type - API to get TWL Si type. + * + * Api to get the TWL Si type from IDCODE value. + */ +int twl_get_type(void) +{ + return TWL_SIL_TYPE(twl_idcode); +} +EXPORT_SYMBOL_GPL(twl_get_type); + +/** + * twl_get_version - API to get TWL Si version. + * + * Api to get the TWL Si version from IDCODE value. + */ +int twl_get_version(void) +{ + return TWL_SIL_REV(twl_idcode); +} +EXPORT_SYMBOL_GPL(twl_get_version); + static struct device * add_numbered_child(unsigned chip, const char *name, int num, void *pdata, unsigned pdata_len, @@ -549,7 +606,7 @@ static inline struct device *add_child(unsigned chip, const char *name, static struct device * add_regulator_linked(int num, struct regulator_init_data *pdata, struct regulator_consumer_supply *consumers, - unsigned num_consumers) + unsigned num_consumers, unsigned long features) { unsigned sub_chip_id; /* regulator framework demands init_data ... */ @@ -561,6 +618,8 @@ add_regulator_linked(int num, struct regulator_init_data *pdata, pdata->num_consumer_supplies = num_consumers; } + pdata->driver_data = (void *)features; + /* NOTE: we currently ignore regulator IRQs, e.g. for short circuits */ sub_chip_id = twl_map[TWL_MODULE_PM_MASTER].sid; return add_numbered_child(sub_chip_id, "twl_reg", num, @@ -568,9 +627,10 @@ add_regulator_linked(int num, struct regulator_init_data *pdata, } static struct device * -add_regulator(int num, struct regulator_init_data *pdata) +add_regulator(int num, struct regulator_init_data *pdata, + unsigned long features) { - return add_regulator_linked(num, pdata, NULL, 0); + return add_regulator_linked(num, pdata, NULL, 0, features); } /* @@ -650,17 +710,20 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) }; child = add_regulator_linked(TWL4030_REG_VUSB1V5, - &usb_fixed, &usb1v5, 1); + &usb_fixed, &usb1v5, 1, + features); if (IS_ERR(child)) return PTR_ERR(child); child = add_regulator_linked(TWL4030_REG_VUSB1V8, - &usb_fixed, &usb1v8, 1); + &usb_fixed, &usb1v8, 1, + features); if (IS_ERR(child)) return PTR_ERR(child); child = add_regulator_linked(TWL4030_REG_VUSB3V1, - &usb_fixed, &usb3v1, 1); + &usb_fixed, &usb3v1, 1, + features); if (IS_ERR(child)) return PTR_ERR(child); @@ -685,9 +748,8 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) } if (twl_has_usb() && pdata->usb && twl_class_is_6030()) { - static struct regulator_consumer_supply usb3v3 = { - .supply = "vusb", - }; + static struct regulator_consumer_supply usb3v3; + int regulator; if (twl_has_regulator()) { /* this is a template that gets copied */ @@ -700,12 +762,22 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) | REGULATOR_CHANGE_STATUS, }; - child = add_regulator_linked(TWL6030_REG_VUSB, - &usb_fixed, &usb3v3, 1); + if (features & TWL6025_SUBCLASS) { + usb3v3.supply = "ldousb"; + regulator = TWL6025_REG_LDOUSB; + } else { + usb3v3.supply = "vusb"; + regulator = TWL6030_REG_VUSB; + } + child = add_regulator_linked(regulator, &usb_fixed, + &usb3v3, 1, + features); if (IS_ERR(child)) return PTR_ERR(child); } + pdata->usb->features = features; + child = add_child(0, "twl6030_usb", pdata->usb, sizeof(*pdata->usb), true, @@ -718,7 +790,16 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) /* we need to connect regulators to this transceiver */ if (twl_has_regulator() && child) usb3v3.dev = child; + } else if (twl_has_regulator() && twl_class_is_6030()) { + if (features & TWL6025_SUBCLASS) + child = add_regulator(TWL6025_REG_LDOUSB, + pdata->ldousb, features); + else + child = add_regulator(TWL6030_REG_VUSB, + pdata->vusb, features); + if (IS_ERR(child)) + return PTR_ERR(child); } if (twl_has_watchdog() && twl_class_is_4030()) { @@ -755,46 +836,55 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) /* twl4030 regulators */ if (twl_has_regulator() && twl_class_is_4030()) { - child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1); + child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VIO, pdata->vio); + child = add_regulator(TWL4030_REG_VIO, pdata->vio, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VDD1, pdata->vdd1); + child = add_regulator(TWL4030_REG_VDD1, pdata->vdd1, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VDD2, pdata->vdd2); + child = add_regulator(TWL4030_REG_VDD2, pdata->vdd2, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VMMC1, pdata->vmmc1); + child = add_regulator(TWL4030_REG_VMMC1, pdata->vmmc1, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VDAC, pdata->vdac); + child = add_regulator(TWL4030_REG_VDAC, pdata->vdac, + features); if (IS_ERR(child)) return PTR_ERR(child); child = add_regulator((features & TWL4030_VAUX2) ? TWL4030_REG_VAUX2_4030 : TWL4030_REG_VAUX2, - pdata->vaux2); + pdata->vaux2, features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VINTANA1, pdata->vintana1); + child = add_regulator(TWL4030_REG_VINTANA1, pdata->vintana1, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VINTANA2, pdata->vintana2); + child = add_regulator(TWL4030_REG_VINTANA2, pdata->vintana2, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VINTDIG, pdata->vintdig); + child = add_regulator(TWL4030_REG_VINTDIG, pdata->vintdig, + features); if (IS_ERR(child)) return PTR_ERR(child); } @@ -802,72 +892,152 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) /* maybe add LDOs that are omitted on cost-reduced parts */ if (twl_has_regulator() && !(features & TPS_SUBSET) && twl_class_is_4030()) { - child = add_regulator(TWL4030_REG_VPLL2, pdata->vpll2); + child = add_regulator(TWL4030_REG_VPLL2, pdata->vpll2, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VMMC2, pdata->vmmc2); + child = add_regulator(TWL4030_REG_VMMC2, pdata->vmmc2, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VSIM, pdata->vsim); + child = add_regulator(TWL4030_REG_VSIM, pdata->vsim, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VAUX1, pdata->vaux1); + child = add_regulator(TWL4030_REG_VAUX1, pdata->vaux1, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VAUX3, pdata->vaux3); + child = add_regulator(TWL4030_REG_VAUX3, pdata->vaux3, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL4030_REG_VAUX4, pdata->vaux4); + child = add_regulator(TWL4030_REG_VAUX4, pdata->vaux4, + features); if (IS_ERR(child)) return PTR_ERR(child); } /* twl6030 regulators */ + if (twl_has_regulator() && twl_class_is_6030() && + !(features & TWL6025_SUBCLASS)) { + child = add_regulator(TWL6030_REG_VMMC, pdata->vmmc, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VPP, pdata->vpp, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VUSIM, pdata->vusim, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VCXIO, pdata->vcxio, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VDAC, pdata->vdac, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VAUX1_6030, pdata->vaux1, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VAUX2_6030, pdata->vaux2, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_VAUX3_6030, pdata->vaux3, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6030_REG_CLK32KG, pdata->clk32kg, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* 6030 and 6025 share this regulator */ if (twl_has_regulator() && twl_class_is_6030()) { - child = add_regulator(TWL6030_REG_VMMC, pdata->vmmc); + child = add_regulator(TWL6030_REG_VANA, pdata->vana, + features); if (IS_ERR(child)) return PTR_ERR(child); + } - child = add_regulator(TWL6030_REG_VPP, pdata->vpp); + /* twl6025 regulators */ + if (twl_has_regulator() && twl_class_is_6030() && + (features & TWL6025_SUBCLASS)) { + child = add_regulator(TWL6025_REG_LDO5, pdata->ldo5, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_VUSIM, pdata->vusim); + child = add_regulator(TWL6025_REG_LDO1, pdata->ldo1, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_VANA, pdata->vana); + child = add_regulator(TWL6025_REG_LDO7, pdata->ldo7, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_VCXIO, pdata->vcxio); + child = add_regulator(TWL6025_REG_LDO6, pdata->ldo6, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_VDAC, pdata->vdac); + child = add_regulator(TWL6025_REG_LDOLN, pdata->ldoln, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_VAUX1_6030, pdata->vaux1); + child = add_regulator(TWL6025_REG_LDO2, pdata->ldo2, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_VAUX2_6030, pdata->vaux2); + child = add_regulator(TWL6025_REG_LDO4, pdata->ldo4, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_VAUX3_6030, pdata->vaux3); + child = add_regulator(TWL6025_REG_LDO3, pdata->ldo3, + features); if (IS_ERR(child)) return PTR_ERR(child); - child = add_regulator(TWL6030_REG_CLK32KG, pdata->clk32kg); + child = add_regulator(TWL6025_REG_SMPS3, pdata->smps3, + features); if (IS_ERR(child)) return PTR_ERR(child); + + child = add_regulator(TWL6025_REG_SMPS4, pdata->smps4, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + + child = add_regulator(TWL6025_REG_VIO, pdata->vio6025, + features); + if (IS_ERR(child)) + return PTR_ERR(child); + } if (twl_has_bci() && pdata->bci && @@ -1014,6 +1184,7 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id) unsigned i; struct twl4030_platform_data *pdata = client->dev.platform_data; u8 temp; + int ret = 0; if (!pdata) { dev_dbg(&client->dev, "no platform data?\n"); @@ -1060,6 +1231,12 @@ twl_probe(struct i2c_client *client, const struct i2c_device_id *id) /* setup clock framework */ clocks_init(&client->dev, pdata->clock); + /* read TWL IDCODE Register */ + if (twl_id == TWL4030_CLASS_ID) { + ret = twl_read_idcode_register(); + WARN(ret < 0, "Error: reading twl_idcode register value\n"); + } + /* load power event scripts */ if (twl_has_power() && pdata->power) twl4030_power_init(pdata->power); @@ -1108,6 +1285,7 @@ static const struct i2c_device_id twl_ids[] = { { "tps65930", TPS_SUBSET }, /* fewer LDOs and DACs; no charger */ { "tps65920", TPS_SUBSET }, /* fewer LDOs; no codec or charger */ { "twl6030", TWL6030_CLASS }, /* "Phoenix power chip" */ + { "twl6025", TWL6030_CLASS | TWL6025_SUBCLASS }, /* "Phoenix lite" */ { /* end of list */ }, }; MODULE_DEVICE_TABLE(i2c, twl_ids); diff --git a/drivers/mfd/twl4030-codec.c b/drivers/mfd/twl4030-codec.c index c02fded316c..2bf4136464c 100644 --- a/drivers/mfd/twl4030-codec.c +++ b/drivers/mfd/twl4030-codec.c @@ -1,7 +1,7 @@ /* * MFD driver for twl4030 codec submodule * - * Author: Peter Ujfalusi <peter.ujfalusi@nokia.com> + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> * * Copyright: (C) 2009 Nokia Corporation * @@ -208,13 +208,15 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev) if (pdata->audio) { cell = &codec->cells[childs]; cell->name = "twl4030-codec"; - cell->mfd_data = pdata->audio; + cell->platform_data = pdata->audio; + cell->pdata_size = sizeof(*pdata->audio); childs++; } if (pdata->vibra) { cell = &codec->cells[childs]; cell->name = "twl4030-vibra"; - cell->mfd_data = pdata->vibra; + cell->platform_data = pdata->vibra; + cell->pdata_size = sizeof(*pdata->vibra); childs++; } @@ -270,6 +272,6 @@ static void __devexit twl4030_codec_exit(void) } module_exit(twl4030_codec_exit); -MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>"); +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index 2c0d4d16491..a764676f092 100644 --- a/drivers/mfd/twl4030-power.c +++ b/drivers/mfd/twl4030-power.c @@ -120,7 +120,7 @@ static u8 res_config_addrs[] = { [RES_HFCLKOUT] = 0x8b, [RES_32KCLKOUT] = 0x8e, [RES_RESET] = 0x91, - [RES_Main_Ref] = 0x94, + [RES_MAIN_REF] = 0x94, }; static int __init twl4030_write_script_byte(u8 address, u8 byte) @@ -448,7 +448,7 @@ static int __init load_twl4030_script(struct twl4030_script *tscript, goto out; } if (tscript->flags & TWL4030_SLEEP_SCRIPT) { - if (order) + if (!order) pr_warning("TWL4030: Bad order of scripts (sleep "\ "script before wakeup) Leads to boot"\ "failure on some boards\n"); @@ -485,9 +485,9 @@ int twl4030_remove_script(u8 flags) return err; } if (flags & TWL4030_WAKEUP12_SCRIPT) { - if (err) err = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, END_OF_SCRIPT, R_SEQ_ADD_S2A12); + if (err) return err; } if (flags & TWL4030_WAKEUP3_SCRIPT) { diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c index dfbae34e180..eb3b5f88e56 100644 --- a/drivers/mfd/twl6030-irq.c +++ b/drivers/mfd/twl6030-irq.c @@ -76,8 +76,8 @@ static int twl6030_interrupt_mapping[24] = { USBOTG_INTR_OFFSET, /* Bit 18 ID */ USB_PRES_INTR_OFFSET, /* Bit 19 VBUS */ CHARGER_INTR_OFFSET, /* Bit 20 CHRG_CTRL */ - CHARGER_INTR_OFFSET, /* Bit 21 EXT_CHRG */ - CHARGER_INTR_OFFSET, /* Bit 22 INT_CHRG */ + CHARGERFAULT_INTR_OFFSET, /* Bit 21 EXT_CHRG */ + CHARGERFAULT_INTR_OFFSET, /* Bit 22 INT_CHRG */ RSV_INTR_OFFSET, /* Bit 23 Reserved */ }; /*----------------------------------------------------------------------*/ diff --git a/drivers/mfd/wl1273-core.c b/drivers/mfd/wl1273-core.c index 04914f2836c..d97a8694517 100644 --- a/drivers/mfd/wl1273-core.c +++ b/drivers/mfd/wl1273-core.c @@ -153,7 +153,6 @@ out: */ static int wl1273_fm_set_volume(struct wl1273_core *core, unsigned int volume) { - u16 val; int r; if (volume > WL1273_MAX_VOLUME) @@ -217,7 +216,8 @@ static int __devinit wl1273_core_probe(struct i2c_client *client, cell = &core->cells[children]; cell->name = "wl1273_fm_radio"; - cell->mfd_data = &core; + cell->platform_data = &core; + cell->pdata_size = sizeof(core); children++; core->read = wl1273_fm_read_reg; @@ -231,7 +231,8 @@ static int __devinit wl1273_core_probe(struct i2c_client *client, dev_dbg(&client->dev, "%s: Have codec.\n", __func__); cell->name = "wl1273-codec"; - cell->mfd_data = &core; + cell->platform_data = &core; + cell->pdata_size = sizeof(core); children++; } diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c index 3fe9a58fe6c..265f75fc6a2 100644 --- a/drivers/mfd/wm831x-core.c +++ b/drivers/mfd/wm831x-core.c @@ -1442,7 +1442,7 @@ int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) struct wm831x_pdata *pdata = wm831x->dev->platform_data; int rev; enum wm831x_parent parent; - int ret; + int ret, i; mutex_init(&wm831x->io_lock); mutex_init(&wm831x->key_lock); @@ -1581,6 +1581,17 @@ int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq) } } + if (pdata) { + for (i = 0; i < ARRAY_SIZE(pdata->gpio_defaults); i++) { + if (!pdata->gpio_defaults[i]) + continue; + + wm831x_reg_write(wm831x, + WM831X_GPIO1_CONTROL + i, + pdata->gpio_defaults[i] & 0xffff); + } + } + ret = wm831x_irq_init(wm831x, irq); if (ret != 0) goto err; diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index 23e66af89de..42b928ec891 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -515,12 +515,6 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq) 0xffff); } - if (!irq) { - dev_warn(wm831x->dev, - "No interrupt specified - functionality limited\n"); - return 0; - } - if (!pdata || !pdata->irq_base) { dev_err(wm831x->dev, "No interrupt base specified, no interrupts\n"); @@ -567,15 +561,22 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq) #endif } - ret = request_threaded_irq(irq, NULL, wm831x_irq_thread, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "wm831x", wm831x); - if (ret != 0) { - dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", - irq, ret); - return ret; + if (irq) { + ret = request_threaded_irq(irq, NULL, wm831x_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "wm831x", wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + return ret; + } + } else { + dev_warn(wm831x->dev, + "No interrupt specified - functionality limited\n"); } + + /* Enable top level interrupts, we mask at secondary level */ wm831x_reg_write(wm831x, WM831X_SYSTEM_INTERRUPTS_MASK, 0); diff --git a/drivers/mfd/wm8400-core.c b/drivers/mfd/wm8400-core.c index 3a6e78cb038..597f82edaca 100644 --- a/drivers/mfd/wm8400-core.c +++ b/drivers/mfd/wm8400-core.c @@ -245,7 +245,8 @@ static int wm8400_register_codec(struct wm8400 *wm8400) { struct mfd_cell cell = { .name = "wm8400-codec", - .mfd_data = wm8400, + .platform_data = wm8400, + .pdata_size = sizeof(*wm8400), }; return mfd_add_devices(wm8400->dev, -1, &cell, 1, NULL, 0); diff --git a/drivers/misc/kgdbts.c b/drivers/misc/kgdbts.c index 74f16f167b8..b0c56313dbb 100644 --- a/drivers/misc/kgdbts.c +++ b/drivers/misc/kgdbts.c @@ -285,33 +285,26 @@ static void hw_break_val_write(void) static int check_and_rewind_pc(char *put_str, char *arg) { unsigned long addr = lookup_addr(arg); + unsigned long ip; int offset = 0; kgdb_hex2mem(&put_str[1], (char *)kgdbts_gdb_regs, NUMREGBYTES); gdb_regs_to_pt_regs(kgdbts_gdb_regs, &kgdbts_regs); - v2printk("Stopped at IP: %lx\n", instruction_pointer(&kgdbts_regs)); -#ifdef CONFIG_X86 - /* On x86 a breakpoint stop requires it to be decremented */ - if (addr + 1 == kgdbts_regs.ip) - offset = -1; -#elif defined(CONFIG_SUPERH) - /* On SUPERH a breakpoint stop requires it to be decremented */ - if (addr + 2 == kgdbts_regs.pc) - offset = -2; + ip = instruction_pointer(&kgdbts_regs); + v2printk("Stopped at IP: %lx\n", ip); +#ifdef GDB_ADJUSTS_BREAK_OFFSET + /* On some arches, a breakpoint stop requires it to be decremented */ + if (addr + BREAK_INSTR_SIZE == ip) + offset = -BREAK_INSTR_SIZE; #endif - if (strcmp(arg, "silent") && - instruction_pointer(&kgdbts_regs) + offset != addr) { + if (strcmp(arg, "silent") && ip + offset != addr) { eprintk("kgdbts: BP mismatch %lx expected %lx\n", - instruction_pointer(&kgdbts_regs) + offset, addr); + ip + offset, addr); return 1; } -#ifdef CONFIG_X86 - /* On x86 adjust the instruction pointer if needed */ - kgdbts_regs.ip += offset; -#elif defined(CONFIG_SUPERH) - kgdbts_regs.pc += offset; -#endif + /* Readjust the instruction pointer if needed */ + instruction_pointer_set(&kgdbts_regs, ip + offset); return 0; } diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c index 14479f9ef53..8d185de90d2 100644 --- a/drivers/mmc/host/tmio_mmc.c +++ b/drivers/mmc/host/tmio_mmc.c @@ -69,7 +69,7 @@ static int __devinit tmio_mmc_probe(struct platform_device *pdev) if (pdev->num_resources != 2) goto out; - pdata = mfd_get_data(pdev); + pdata = pdev->dev.platform_data; if (!pdata || !pdata->hclk) goto out; diff --git a/drivers/mtd/nand/tmio_nand.c b/drivers/mtd/nand/tmio_nand.c index 14c57870782..c004e474631 100644 --- a/drivers/mtd/nand/tmio_nand.c +++ b/drivers/mtd/nand/tmio_nand.c @@ -372,7 +372,7 @@ static void tmio_hw_stop(struct platform_device *dev, struct tmio_nand *tmio) static int tmio_probe(struct platform_device *dev) { - struct tmio_nand_data *data = mfd_get_data(dev); + struct tmio_nand_data *data = dev->dev.platform_data; struct resource *fcr = platform_get_resource(dev, IORESOURCE_MEM, 0); struct resource *ccr = platform_get_resource(dev, diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 6141667c5fb..17b4dd94da9 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -113,9 +113,11 @@ MODULE_PARM_DESC(max_bonds, "Max number of bonded devices"); module_param(tx_queues, int, 0); MODULE_PARM_DESC(tx_queues, "Max number of transmit queues (default = 16)"); module_param_named(num_grat_arp, num_peer_notif, int, 0644); -MODULE_PARM_DESC(num_grat_arp, "Number of peer notifications to send on failover event (alias of num_unsol_na)"); +MODULE_PARM_DESC(num_grat_arp, "Number of peer notifications to send on " + "failover event (alias of num_unsol_na)"); module_param_named(num_unsol_na, num_peer_notif, int, 0644); -MODULE_PARM_DESC(num_unsol_na, "Number of peer notifications to send on failover event (alias of num_grat_arp)"); +MODULE_PARM_DESC(num_unsol_na, "Number of peer notifications to send on " + "failover event (alias of num_grat_arp)"); module_param(miimon, int, 0); MODULE_PARM_DESC(miimon, "Link check interval in milliseconds"); module_param(updelay, int, 0); @@ -127,7 +129,7 @@ module_param(use_carrier, int, 0); MODULE_PARM_DESC(use_carrier, "Use netif_carrier_ok (vs MII ioctls) in miimon; " "0 for off, 1 for on (default)"); module_param(mode, charp, 0); -MODULE_PARM_DESC(mode, "Mode of operation : 0 for balance-rr, " +MODULE_PARM_DESC(mode, "Mode of operation; 0 for balance-rr, " "1 for active-backup, 2 for balance-xor, " "3 for broadcast, 4 for 802.3ad, 5 for balance-tlb, " "6 for balance-alb"); @@ -142,27 +144,35 @@ MODULE_PARM_DESC(primary_reselect, "Reselect primary slave " "2 for only on active slave " "failure"); module_param(lacp_rate, charp, 0); -MODULE_PARM_DESC(lacp_rate, "LACPDU tx rate to request from 802.3ad partner " - "(slow/fast)"); +MODULE_PARM_DESC(lacp_rate, "LACPDU tx rate to request from 802.3ad partner; " + "0 for slow, 1 for fast"); module_param(ad_select, charp, 0); -MODULE_PARM_DESC(ad_select, "803.ad aggregation selection logic: stable (0, default), bandwidth (1), count (2)"); +MODULE_PARM_DESC(ad_select, "803.ad aggregation selection logic; " + "0 for stable (default), 1 for bandwidth, " + "2 for count"); module_param(xmit_hash_policy, charp, 0); -MODULE_PARM_DESC(xmit_hash_policy, "XOR hashing method: 0 for layer 2 (default)" - ", 1 for layer 3+4"); +MODULE_PARM_DESC(xmit_hash_policy, "balance-xor and 802.3ad hashing method; " + "0 for layer 2 (default), 1 for layer 3+4, " + "2 for layer 2+3"); module_param(arp_interval, int, 0); MODULE_PARM_DESC(arp_interval, "arp interval in milliseconds"); module_param_array(arp_ip_target, charp, NULL, 0); MODULE_PARM_DESC(arp_ip_target, "arp targets in n.n.n.n form"); module_param(arp_validate, charp, 0); -MODULE_PARM_DESC(arp_validate, "validate src/dst of ARP probes: none (default), active, backup or all"); +MODULE_PARM_DESC(arp_validate, "validate src/dst of ARP probes; " + "0 for none (default), 1 for active, " + "2 for backup, 3 for all"); module_param(fail_over_mac, charp, 0); -MODULE_PARM_DESC(fail_over_mac, "For active-backup, do not set all slaves to the same MAC. none (default), active or follow"); +MODULE_PARM_DESC(fail_over_mac, "For active-backup, do not set all slaves to " + "the same MAC; 0 for none (default), " + "1 for active, 2 for follow"); module_param(all_slaves_active, int, 0); MODULE_PARM_DESC(all_slaves_active, "Keep all frames received on an interface" - "by setting active flag for all slaves. " + "by setting active flag for all slaves; " "0 for never (default), 1 for always."); module_param(resend_igmp, int, 0); -MODULE_PARM_DESC(resend_igmp, "Number of IGMP membership reports to send on link failure"); +MODULE_PARM_DESC(resend_igmp, "Number of IGMP membership reports to send on " + "link failure"); /*----------------------------- Global variables ----------------------------*/ diff --git a/drivers/net/can/janz-ican3.c b/drivers/net/can/janz-ican3.c index 587fba48cdd..f1942cab35f 100644 --- a/drivers/net/can/janz-ican3.c +++ b/drivers/net/can/janz-ican3.c @@ -15,7 +15,6 @@ #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/netdevice.h> #include <linux/can.h> @@ -1644,7 +1643,7 @@ static int __devinit ican3_probe(struct platform_device *pdev) struct device *dev; int ret; - pdata = mfd_get_data(pdev); + pdata = pdev->dev.platform_data; if (!pdata) return -ENXIO; diff --git a/drivers/net/davinci_emac.c b/drivers/net/davinci_emac.c index 807b6bb200e..29a4f06fbfc 100644 --- a/drivers/net/davinci_emac.c +++ b/drivers/net/davinci_emac.c @@ -1772,7 +1772,7 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) /* obtain emac clock from kernel */ emac_clk = clk_get(&pdev->dev, NULL); if (IS_ERR(emac_clk)) { - printk(KERN_ERR "DaVinci EMAC: Failed to get EMAC clock\n"); + dev_err(&pdev->dev, "failed to get EMAC clock\n"); return -EBUSY; } emac_bus_frequency = clk_get_rate(emac_clk); @@ -1780,7 +1780,7 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) ndev = alloc_etherdev(sizeof(struct emac_priv)); if (!ndev) { - printk(KERN_ERR "DaVinci EMAC: Error allocating net_device\n"); + dev_err(&pdev->dev, "error allocating net_device\n"); clk_put(emac_clk); return -ENOMEM; } @@ -1795,7 +1795,7 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) pdata = pdev->dev.platform_data; if (!pdata) { - printk(KERN_ERR "DaVinci EMAC: No platform data\n"); + dev_err(&pdev->dev, "no platform data\n"); return -ENODEV; } @@ -1814,7 +1814,7 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) /* Get EMAC platform data */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { - dev_err(emac_dev, "DaVinci EMAC: Error getting res\n"); + dev_err(&pdev->dev,"error getting res\n"); rc = -ENOENT; goto probe_quit; } @@ -1822,14 +1822,14 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) priv->emac_base_phys = res->start + pdata->ctrl_reg_offset; size = res->end - res->start + 1; if (!request_mem_region(res->start, size, ndev->name)) { - dev_err(emac_dev, "DaVinci EMAC: failed request_mem_region() for regs\n"); + dev_err(&pdev->dev, "failed request_mem_region() for regs\n"); rc = -ENXIO; goto probe_quit; } priv->remap_addr = ioremap(res->start, size); if (!priv->remap_addr) { - dev_err(emac_dev, "Unable to map IO\n"); + dev_err(&pdev->dev, "unable to map IO\n"); rc = -ENOMEM; release_mem_region(res->start, size); goto probe_quit; @@ -1863,7 +1863,7 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) priv->dma = cpdma_ctlr_create(&dma_params); if (!priv->dma) { - dev_err(emac_dev, "DaVinci EMAC: Error initializing DMA\n"); + dev_err(&pdev->dev, "error initializing DMA\n"); rc = -ENOMEM; goto no_dma; } @@ -1879,7 +1879,7 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { - dev_err(emac_dev, "DaVinci EMAC: Error getting irq res\n"); + dev_err(&pdev->dev, "error getting irq res\n"); rc = -ENOENT; goto no_irq_res; } @@ -1888,8 +1888,8 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) if (!is_valid_ether_addr(priv->mac_addr)) { /* Use random MAC if none passed */ random_ether_addr(priv->mac_addr); - printk(KERN_WARNING "%s: using random MAC addr: %pM\n", - __func__, priv->mac_addr); + dev_warn(&pdev->dev, "using random MAC addr: %pM\n", + priv->mac_addr); } ndev->netdev_ops = &emac_netdev_ops; @@ -1902,7 +1902,7 @@ static int __devinit davinci_emac_probe(struct platform_device *pdev) SET_NETDEV_DEV(ndev, &pdev->dev); rc = register_netdev(ndev); if (rc) { - dev_err(emac_dev, "DaVinci EMAC: Error in register_netdev\n"); + dev_err(&pdev->dev, "error in register_netdev\n"); rc = -ENODEV; goto netdev_reg_err; } diff --git a/drivers/net/hamradio/baycom_epp.c b/drivers/net/hamradio/baycom_epp.c index a3c0dc9d8b9..9537aaa50c2 100644 --- a/drivers/net/hamradio/baycom_epp.c +++ b/drivers/net/hamradio/baycom_epp.c @@ -69,7 +69,7 @@ static const char paranoia_str[] = KERN_ERR static const char bc_drvname[] = "baycom_epp"; static const char bc_drvinfo[] = KERN_INFO "baycom_epp: (C) 1998-2000 Thomas Sailer, HB9JNX/AE4WA\n" -"baycom_epp: version 0.7 compiled " __TIME__ " " __DATE__ "\n"; +"baycom_epp: version 0.7\n"; /* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/baycom_par.c b/drivers/net/hamradio/baycom_par.c index 5f5af9a606f..279d2296290 100644 --- a/drivers/net/hamradio/baycom_par.c +++ b/drivers/net/hamradio/baycom_par.c @@ -102,7 +102,7 @@ static const char bc_drvname[] = "baycom_par"; static const char bc_drvinfo[] = KERN_INFO "baycom_par: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n" -"baycom_par: version 0.9 compiled " __TIME__ " " __DATE__ "\n"; +"baycom_par: version 0.9\n"; /* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/baycom_ser_fdx.c b/drivers/net/hamradio/baycom_ser_fdx.c index 3e25f10cabd..99cdce33df8 100644 --- a/drivers/net/hamradio/baycom_ser_fdx.c +++ b/drivers/net/hamradio/baycom_ser_fdx.c @@ -92,7 +92,7 @@ static const char bc_drvname[] = "baycom_ser_fdx"; static const char bc_drvinfo[] = KERN_INFO "baycom_ser_fdx: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n" -"baycom_ser_fdx: version 0.10 compiled " __TIME__ " " __DATE__ "\n"; +"baycom_ser_fdx: version 0.10\n"; /* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/baycom_ser_hdx.c b/drivers/net/hamradio/baycom_ser_hdx.c index 1686f6dcbbc..d92fe6ca788 100644 --- a/drivers/net/hamradio/baycom_ser_hdx.c +++ b/drivers/net/hamradio/baycom_ser_hdx.c @@ -80,7 +80,7 @@ static const char bc_drvname[] = "baycom_ser_hdx"; static const char bc_drvinfo[] = KERN_INFO "baycom_ser_hdx: (C) 1996-2000 Thomas Sailer, HB9JNX/AE4WA\n" -"baycom_ser_hdx: version 0.10 compiled " __TIME__ " " __DATE__ "\n"; +"baycom_ser_hdx: version 0.10\n"; /* --------------------------------------------------------------------- */ diff --git a/drivers/net/hamradio/hdlcdrv.c b/drivers/net/hamradio/hdlcdrv.c index 5b37579e84b..a4a3516b6bb 100644 --- a/drivers/net/hamradio/hdlcdrv.c +++ b/drivers/net/hamradio/hdlcdrv.c @@ -749,7 +749,7 @@ EXPORT_SYMBOL(hdlcdrv_unregister); static int __init hdlcdrv_init_driver(void) { printk(KERN_INFO "hdlcdrv: (C) 1996-2000 Thomas Sailer HB9JNX/AE4WA\n"); - printk(KERN_INFO "hdlcdrv: version 0.8 compiled " __TIME__ " " __DATE__ "\n"); + printk(KERN_INFO "hdlcdrv: version 0.8\n"); return 0; } diff --git a/drivers/net/ks8842.c b/drivers/net/ks8842.c index f0d8346d0fa..4d40626b3bf 100644 --- a/drivers/net/ks8842.c +++ b/drivers/net/ks8842.c @@ -26,7 +26,6 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/ethtool.h> @@ -1146,7 +1145,7 @@ static int __devinit ks8842_probe(struct platform_device *pdev) struct resource *iomem; struct net_device *netdev; struct ks8842_adapter *adapter; - struct ks8842_platform_data *pdata = mfd_get_data(pdev); + struct ks8842_platform_data *pdata = pdev->dev.platform_data; u16 id; unsigned i; diff --git a/drivers/net/wan/pc300_drv.c b/drivers/net/wan/pc300_drv.c index 737b59f1a8d..9617d3d0ee3 100644 --- a/drivers/net/wan/pc300_drv.c +++ b/drivers/net/wan/pc300_drv.c @@ -3242,8 +3242,7 @@ static inline void show_version(void) rcsdate++; tmp = strrchr(rcsdate, ' '); *tmp = '\0'; - printk(KERN_INFO "Cyclades-PC300 driver %s %s (built %s %s)\n", - rcsvers, rcsdate, __DATE__, __TIME__); + printk(KERN_INFO "Cyclades-PC300 driver %s %s\n", rcsvers, rcsdate); } /* show_version */ static const struct net_device_ops cpc_netdev_ops = { diff --git a/drivers/parport/parport_ip32.c b/drivers/parport/parport_ip32.c index d3d7809af8b..0dc34f12f92 100644 --- a/drivers/parport/parport_ip32.c +++ b/drivers/parport/parport_ip32.c @@ -2203,7 +2203,6 @@ static __exit void parport_ip32_unregister_port(struct parport *p) static int __init parport_ip32_init(void) { pr_info(PPIP32 "SGI IP32 built-in parallel port driver v0.6\n"); - pr_debug1(PPIP32 "Compiled on %s, %s\n", __DATE__, __TIME__); this_port = parport_ip32_probe_port(); return IS_ERR(this_port) ? PTR_ERR(this_port) : 0; } diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 52a462fc6b8..e57b50b3856 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -68,6 +68,13 @@ config BATTERY_DS2760 help Say Y here to enable support for batteries with ds2760 chip. +config BATTERY_DS2780 + tristate "DS2780 battery driver" + select W1 + select W1_SLAVE_DS2780 + help + Say Y here to enable support for batteries with ds2780 chip. + config BATTERY_DS2782 tristate "DS2782/DS2786 standalone gas-gauge" depends on I2C @@ -203,6 +210,15 @@ config CHARGER_ISP1704 Say Y to enable support for USB Charger Detection with ISP1707/ISP1704 USB transceivers. +config CHARGER_MAX8903 + tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power" + depends on GENERIC_HARDIRQS + help + Say Y to enable support for the MAX8903 DC-DC charger and sysfs. + The driver supports controlling charger-enable and current-limit + pins based on the status of charger connections with interrupt + handlers. + config CHARGER_TWL4030 tristate "OMAP TWL4030 BCI charger driver" depends on TWL4030_CORE diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 8385bfae872..009a90fa8ac 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_WM8350_POWER) += wm8350_power.o obj-$(CONFIG_TEST_POWER) += test_power.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o +obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o @@ -32,5 +33,6 @@ obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o +obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c index 59e68dbd028..bb16f5b7e16 100644 --- a/drivers/power/bq27x00_battery.c +++ b/drivers/power/bq27x00_battery.c @@ -4,6 +4,7 @@ * Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it> * Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it> * Copyright (C) 2010-2011 Lars-Peter Clausen <lars@metafoo.de> + * Copyright (C) 2011 Pali Rohár <pali.rohar@gmail.com> * * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. * @@ -76,7 +77,7 @@ struct bq27x00_reg_cache { int time_to_empty_avg; int time_to_full; int charge_full; - int charge_counter; + int cycle_count; int capacity; int flags; @@ -115,7 +116,7 @@ static enum power_supply_property bq27x00_battery_props[] = { POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_ENERGY_NOW, }; @@ -267,7 +268,7 @@ static void bq27x00_update(struct bq27x00_device_info *di) cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP); cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF); cache.charge_full = bq27x00_battery_read_lmd(di); - cache.charge_counter = bq27x00_battery_read_cyct(di); + cache.cycle_count = bq27x00_battery_read_cyct(di); if (!is_bq27500) cache.current_now = bq27x00_read(di, BQ27x00_REG_AI, false); @@ -496,8 +497,8 @@ static int bq27x00_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ret = bq27x00_simple_value(di->charge_design_full, val); break; - case POWER_SUPPLY_PROP_CHARGE_COUNTER: - ret = bq27x00_simple_value(di->cache.charge_counter, val); + case POWER_SUPPLY_PROP_CYCLE_COUNT: + ret = bq27x00_simple_value(di->cache.cycle_count, val); break; case POWER_SUPPLY_PROP_ENERGY_NOW: ret = bq27x00_battery_energy(di, val); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c index e534290f325..f2c9cc33c0f 100644 --- a/drivers/power/ds2760_battery.c +++ b/drivers/power/ds2760_battery.c @@ -86,7 +86,11 @@ static int rated_capacities[] = { 920, /* NEC */ 1440, /* Samsung */ 1440, /* BYD */ +#ifdef CONFIG_MACH_H4700 + 1800, /* HP iPAQ hx4700 3.7V 1800mAh (359113-001) */ +#else 1440, /* Lishen */ +#endif 1440, /* NEC */ 2880, /* Samsung */ 2880, /* BYD */ @@ -186,7 +190,7 @@ static int ds2760_battery_read_status(struct ds2760_device_info *di) scale[0] = di->full_active_uAh; for (i = 1; i < 5; i++) - scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 2 + i]; + scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 1 + i]; di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10); di->full_active_uAh *= 1000; /* convert to µAh */ diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c new file mode 100644 index 00000000000..1fefe82e12e --- /dev/null +++ b/drivers/power/ds2780_battery.c @@ -0,0 +1,853 @@ +/* + * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * Based on ds2760_battery and ds2782_battery drivers + * + * 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 <linux/module.h> +#include <linux/slab.h> +#include <linux/param.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> + +#include "../w1/w1.h" +#include "../w1/slaves/w1_ds2780.h" + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2780_CURRENT_UNITS 1563 +/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ +#define DS2780_CHARGE_UNITS 6250 +/* Number of bytes in user EEPROM space */ +#define DS2780_USER_EEPROM_SIZE (DS2780_EEPROM_BLOCK0_END - \ + DS2780_EEPROM_BLOCK0_START + 1) +/* Number of bytes in parameter EEPROM space */ +#define DS2780_PARAM_EEPROM_SIZE (DS2780_EEPROM_BLOCK1_END - \ + DS2780_EEPROM_BLOCK1_START + 1) + +struct ds2780_device_info { + struct device *dev; + struct power_supply bat; + struct device *w1_dev; +}; + +enum current_types { + CURRENT_NOW, + CURRENT_AVG, +}; + +static const char model[] = "DS2780"; +static const char manufacturer[] = "Maxim/Dallas"; + +static inline struct ds2780_device_info *to_ds2780_device_info( + struct power_supply *psy) +{ + return container_of(psy, struct ds2780_device_info, bat); +} + +static inline struct power_supply *to_power_supply(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static inline int ds2780_read8(struct device *dev, u8 *val, int addr) +{ + return w1_ds2780_io(dev, val, addr, sizeof(u8), 0); +} + +static int ds2780_read16(struct device *dev, s16 *val, int addr) +{ + int ret; + u8 raw[2]; + + ret = w1_ds2780_io(dev, raw, addr, sizeof(u8) * 2, 0); + if (ret < 0) + return ret; + + *val = (raw[0] << 8) | raw[1]; + + return 0; +} + +static inline int ds2780_read_block(struct device *dev, u8 *val, int addr, + size_t count) +{ + return w1_ds2780_io(dev, val, addr, count, 0); +} + +static inline int ds2780_write(struct device *dev, u8 *val, int addr, + size_t count) +{ + return w1_ds2780_io(dev, val, addr, count, 1); +} + +static inline int ds2780_store_eeprom(struct device *dev, int addr) +{ + return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_COPY_DATA); +} + +static inline int ds2780_recall_eeprom(struct device *dev, int addr) +{ + return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_RECALL_DATA); +} + +static int ds2780_save_eeprom(struct ds2780_device_info *dev_info, int reg) +{ + int ret; + + ret = ds2780_store_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + ret = ds2780_recall_eeprom(dev_info->w1_dev, reg); + if (ret < 0) + return ret; + + return 0; +} + +/* Set sense resistor value in mhos */ +static int ds2780_set_sense_register(struct ds2780_device_info *dev_info, + u8 conductance) +{ + int ret; + + ret = ds2780_write(dev_info->w1_dev, &conductance, + DS2780_RSNSP_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_RSNSP_REG); +} + +/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2780_get_rsgain_register(struct ds2780_device_info *dev_info, + u16 *rsgain) +{ + return ds2780_read16(dev_info->w1_dev, rsgain, DS2780_RSGAIN_MSB_REG); +} + +/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */ +static int ds2780_set_rsgain_register(struct ds2780_device_info *dev_info, + u16 rsgain) +{ + int ret; + u8 raw[] = {rsgain >> 8, rsgain & 0xFF}; + + ret = ds2780_write(dev_info->w1_dev, raw, + DS2780_RSGAIN_MSB_REG, sizeof(u8) * 2); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_RSGAIN_MSB_REG); +} + +static int ds2780_get_voltage(struct ds2780_device_info *dev_info, + int *voltage_uV) +{ + int ret; + s16 voltage_raw; + + /* + * The voltage value is located in 10 bits across the voltage MSB + * and LSB registers in two's compliment form + * Sign bit of the voltage value is in bit 7 of the voltage MSB register + * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the + * voltage MSB register + * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the + * voltage LSB register + */ + ret = ds2780_read16(dev_info->w1_dev, &voltage_raw, + DS2780_VOLT_MSB_REG); + if (ret < 0) + return ret; + + /* + * DS2780 reports voltage in units of 4.88mV, but the battery class + * reports in units of uV, so convert by multiplying by 4880. + */ + *voltage_uV = (voltage_raw / 32) * 4880; + return 0; +} + +static int ds2780_get_temperature(struct ds2780_device_info *dev_info, + int *temperature) +{ + int ret; + s16 temperature_raw; + + /* + * The temperature value is located in 10 bits across the temperature + * MSB and LSB registers in two's compliment form + * Sign bit of the temperature value is in bit 7 of the temperature + * MSB register + * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the + * temperature MSB register + * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the + * temperature LSB register + */ + ret = ds2780_read16(dev_info->w1_dev, &temperature_raw, + DS2780_TEMP_MSB_REG); + if (ret < 0) + return ret; + + /* + * Temperature is measured in units of 0.125 degrees celcius, the + * power_supply class measures temperature in tenths of degrees + * celsius. The temperature value is stored as a 10 bit number, plus + * sign in the upper bits of a 16 bit register. + */ + *temperature = ((temperature_raw / 32) * 125) / 100; + return 0; +} + +static int ds2780_get_current(struct ds2780_device_info *dev_info, + enum current_types type, int *current_uA) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw, reg_msb; + + /* + * The units of measurement for current are dependent on the value of + * the sense resistor. + */ + ret = ds2780_read8(dev_info->w1_dev, &sense_res_raw, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -ENXIO; + } + sense_res = 1000 / sense_res_raw; + + if (type == CURRENT_NOW) + reg_msb = DS2780_CURRENT_MSB_REG; + else if (type == CURRENT_AVG) + reg_msb = DS2780_IAVG_MSB_REG; + else + return -EINVAL; + + /* + * The current value is located in 16 bits across the current MSB + * and LSB registers in two's compliment form + * Sign bit of the current value is in bit 7 of the current MSB register + * Bits 14 - 8 of the current value are in bits 6 - 0 of the current + * MSB register + * Bits 7 - 0 of the current value are in bits 7 - 0 of the current + * LSB register + */ + ret = ds2780_read16(dev_info->w1_dev, ¤t_raw, reg_msb); + if (ret < 0) + return ret; + + *current_uA = current_raw * (DS2780_CURRENT_UNITS / sense_res); + return 0; +} + +static int ds2780_get_accumulated_current(struct ds2780_device_info *dev_info, + int *accumulated_current) +{ + int ret, sense_res; + s16 current_raw; + u8 sense_res_raw; + + /* + * The units of measurement for accumulated current are dependent on + * the value of the sense resistor. + */ + ret = ds2780_read8(dev_info->w1_dev, &sense_res_raw, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + if (sense_res_raw == 0) { + dev_err(dev_info->dev, "sense resistor value is 0\n"); + return -ENXIO; + } + sense_res = 1000 / sense_res_raw; + + /* + * The ACR value is located in 16 bits across the ACR MSB and + * LSB registers + * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR + * MSB register + * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR + * LSB register + */ + ret = ds2780_read16(dev_info->w1_dev, ¤t_raw, DS2780_ACR_MSB_REG); + if (ret < 0) + return ret; + + *accumulated_current = current_raw * (DS2780_CHARGE_UNITS / sense_res); + return 0; +} + +static int ds2780_get_capacity(struct ds2780_device_info *dev_info, + int *capacity) +{ + int ret; + u8 raw; + + ret = ds2780_read8(dev_info->w1_dev, &raw, DS2780_RARC_REG); + if (ret < 0) + return ret; + + *capacity = raw; + return raw; +} + +static int ds2780_get_status(struct ds2780_device_info *dev_info, int *status) +{ + int ret, current_uA, capacity; + + ret = ds2780_get_current(dev_info, CURRENT_NOW, ¤t_uA); + if (ret < 0) + return ret; + + ret = ds2780_get_capacity(dev_info, &capacity); + if (ret < 0) + return ret; + + if (capacity == 100) + *status = POWER_SUPPLY_STATUS_FULL; + else if (current_uA == 0) + *status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (current_uA < 0) + *status = POWER_SUPPLY_STATUS_DISCHARGING; + else + *status = POWER_SUPPLY_STATUS_CHARGING; + + return 0; +} + +static int ds2780_get_charge_now(struct ds2780_device_info *dev_info, + int *charge_now) +{ + int ret; + u16 charge_raw; + + /* + * The RAAC value is located in 16 bits across the RAAC MSB and + * LSB registers + * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC + * MSB register + * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC + * LSB register + */ + ret = ds2780_read16(dev_info->w1_dev, &charge_raw, DS2780_RAAC_MSB_REG); + if (ret < 0) + return ret; + + *charge_now = charge_raw * 1600; + return 0; +} + +static int ds2780_get_control_register(struct ds2780_device_info *dev_info, + u8 *control_reg) +{ + return ds2780_read8(dev_info->w1_dev, control_reg, DS2780_CONTROL_REG); +} + +static int ds2780_set_control_register(struct ds2780_device_info *dev_info, + u8 control_reg) +{ + int ret; + + ret = ds2780_write(dev_info->w1_dev, &control_reg, + DS2780_CONTROL_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return ds2780_save_eeprom(dev_info, DS2780_CONTROL_REG); +} + +static int ds2780_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ds2780_get_voltage(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_TEMP: + ret = ds2780_get_temperature(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ds2780_get_current(dev_info, CURRENT_NOW, &val->intval); + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = ds2780_get_current(dev_info, CURRENT_AVG, &val->intval); + break; + + case POWER_SUPPLY_PROP_STATUS: + ret = ds2780_get_status(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = ds2780_get_capacity(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = ds2780_get_accumulated_current(dev_info, &val->intval); + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = ds2780_get_charge_now(dev_info, &val->intval); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static enum power_supply_property ds2780_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static ssize_t ds2780_get_pmod_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 control_reg; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + /* Get power mode */ + ret = ds2780_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", + !!(control_reg & DS2780_CONTROL_REG_PMOD)); +} + +static ssize_t ds2780_set_pmod_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 control_reg, new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + /* Set power mode */ + ret = ds2780_get_control_register(dev_info, &control_reg); + if (ret < 0) + return ret; + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n"); + return -EINVAL; + } + + if (new_setting) + control_reg |= DS2780_CONTROL_REG_PMOD; + else + control_reg &= ~DS2780_CONTROL_REG_PMOD; + + ret = ds2780_set_control_register(dev_info, control_reg); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sense_resistor; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_read8(dev_info->w1_dev, &sense_resistor, DS2780_RSNSP_REG); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sense_resistor); + return ret; +} + +static ssize_t ds2780_set_sense_resistor_value(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + ret = ds2780_set_sense_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_rsgain_setting(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 rsgain; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_get_rsgain_register(dev_info, &rsgain); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", rsgain); +} + +static ssize_t ds2780_set_rsgain_setting(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u16 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou16(buf, 0, &new_setting); + if (ret < 0) + return ret; + + /* Gain can only be from 0 to 1.999 in steps of .001 */ + if (new_setting > 1999) { + dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n"); + return -EINVAL; + } + + ret = ds2780_set_rsgain_register(dev_info, new_setting); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_get_pio_pin(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 sfr; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = ds2780_read8(dev_info->w1_dev, &sfr, DS2780_SFR_REG); + if (ret < 0) + return ret; + + ret = sprintf(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC); + return ret; +} + +static ssize_t ds2780_set_pio_pin(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u8 new_setting; + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + ret = kstrtou8(buf, 0, &new_setting); + if (ret < 0) + return ret; + + if ((new_setting != 0) && (new_setting != 1)) { + dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n"); + return -EINVAL; + } + + ret = ds2780_write(dev_info->w1_dev, &new_setting, + DS2780_SFR_REG, sizeof(u8)); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t ds2780_read_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK1_END - + DS2780_EEPROM_BLOCK1_START + 1 - off); + + return ds2780_read_block(dev_info->w1_dev, buf, + DS2780_EEPROM_BLOCK1_START + off, count); +} + +static ssize_t ds2780_write_param_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + int ret; + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK1_END - + DS2780_EEPROM_BLOCK1_START + 1 - off); + + ret = ds2780_write(dev_info->w1_dev, buf, + DS2780_EEPROM_BLOCK1_START + off, count); + if (ret < 0) + return ret; + + ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK1_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2780_param_eeprom_bin_attr = { + .attr = { + .name = "param_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2780_EEPROM_BLOCK1_END - DS2780_EEPROM_BLOCK1_START + 1, + .read = ds2780_read_param_eeprom_bin, + .write = ds2780_write_param_eeprom_bin, +}; + +static ssize_t ds2780_read_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK0_END - + DS2780_EEPROM_BLOCK0_START + 1 - off); + + return ds2780_read_block(dev_info->w1_dev, buf, + DS2780_EEPROM_BLOCK0_START + off, count); + +} + +static ssize_t ds2780_write_user_eeprom_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct power_supply *psy = to_power_supply(dev); + struct ds2780_device_info *dev_info = to_ds2780_device_info(psy); + int ret; + + count = min_t(loff_t, count, + DS2780_EEPROM_BLOCK0_END - + DS2780_EEPROM_BLOCK0_START + 1 - off); + + ret = ds2780_write(dev_info->w1_dev, buf, + DS2780_EEPROM_BLOCK0_START + off, count); + if (ret < 0) + return ret; + + ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK0_START); + if (ret < 0) + return ret; + + return count; +} + +static struct bin_attribute ds2780_user_eeprom_bin_attr = { + .attr = { + .name = "user_eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = DS2780_EEPROM_BLOCK0_END - DS2780_EEPROM_BLOCK0_START + 1, + .read = ds2780_read_user_eeprom_bin, + .write = ds2780_write_user_eeprom_bin, +}; + +static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled, + ds2780_set_pmod_enabled); +static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR, + ds2780_get_sense_resistor_value, ds2780_set_sense_resistor_value); +static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2780_get_rsgain_setting, + ds2780_set_rsgain_setting); +static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2780_get_pio_pin, + ds2780_set_pio_pin); + + +static struct attribute *ds2780_attributes[] = { + &dev_attr_pmod_enabled.attr, + &dev_attr_sense_resistor_value.attr, + &dev_attr_rsgain_setting.attr, + &dev_attr_pio_pin.attr, + NULL +}; + +static const struct attribute_group ds2780_attr_group = { + .attrs = ds2780_attributes, +}; + +static int __devinit ds2780_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ds2780_device_info *dev_info; + + dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL); + if (!dev_info) { + ret = -ENOMEM; + goto fail; + } + + platform_set_drvdata(pdev, dev_info); + + dev_info->dev = &pdev->dev; + dev_info->w1_dev = pdev->dev.parent; + dev_info->bat.name = dev_name(&pdev->dev); + dev_info->bat.type = POWER_SUPPLY_TYPE_BATTERY; + dev_info->bat.properties = ds2780_battery_props; + dev_info->bat.num_properties = ARRAY_SIZE(ds2780_battery_props); + dev_info->bat.get_property = ds2780_battery_get_property; + + ret = power_supply_register(&pdev->dev, &dev_info->bat); + if (ret) { + dev_err(dev_info->dev, "failed to register battery\n"); + goto fail_free_info; + } + + ret = sysfs_create_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); + if (ret) { + dev_err(dev_info->dev, "failed to create sysfs group\n"); + goto fail_unregister; + } + + ret = sysfs_create_bin_file(&dev_info->bat.dev->kobj, + &ds2780_param_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create param eeprom bin file"); + goto fail_remove_group; + } + + ret = sysfs_create_bin_file(&dev_info->bat.dev->kobj, + &ds2780_user_eeprom_bin_attr); + if (ret) { + dev_err(dev_info->dev, + "failed to create user eeprom bin file"); + goto fail_remove_bin_file; + } + + return 0; + +fail_remove_bin_file: + sysfs_remove_bin_file(&dev_info->bat.dev->kobj, + &ds2780_param_eeprom_bin_attr); +fail_remove_group: + sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); +fail_unregister: + power_supply_unregister(&dev_info->bat); +fail_free_info: + kfree(dev_info); +fail: + return ret; +} + +static int __devexit ds2780_battery_remove(struct platform_device *pdev) +{ + struct ds2780_device_info *dev_info = platform_get_drvdata(pdev); + + /* remove attributes */ + sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); + + power_supply_unregister(&dev_info->bat); + + kfree(dev_info); + return 0; +} + +MODULE_ALIAS("platform:ds2780-battery"); + +static struct platform_driver ds2780_battery_driver = { + .driver = { + .name = "ds2780-battery", + }, + .probe = ds2780_battery_probe, + .remove = ds2780_battery_remove, +}; + +static int __init ds2780_battery_init(void) +{ + return platform_driver_register(&ds2780_battery_driver); +} + +static void __exit ds2780_battery_exit(void) +{ + platform_driver_unregister(&ds2780_battery_driver); +} + +module_init(ds2780_battery_init); +module_exit(ds2780_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>"); +MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver"); diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c index 25b88ac1d44..718f2c53782 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -161,12 +161,27 @@ static int __devexit gpio_charger_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int gpio_charger_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); + + power_supply_changed(&gpio_charger->charger); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, NULL, gpio_charger_resume); + static struct platform_driver gpio_charger_driver = { .probe = gpio_charger_probe, .remove = __devexit_p(gpio_charger_remove), .driver = { .name = "gpio-charger", .owner = THIS_MODULE, + .pm = &gpio_charger_pm_ops, }, }; diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c index 2ad9b14a5ce..f6d72b402a8 100644 --- a/drivers/power/isp1704_charger.c +++ b/drivers/power/isp1704_charger.c @@ -33,6 +33,7 @@ #include <linux/usb/ulpi.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> +#include <linux/power/isp1704_charger.h> /* Vendor specific Power Control register */ #define ISP1704_PWR_CTRL 0x3d @@ -71,6 +72,18 @@ struct isp1704_charger { }; /* + * Disable/enable the power from the isp1704 if a function for it + * has been provided with platform data. + */ +static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) +{ + struct isp1704_charger_data *board = isp->dev->platform_data; + + if (board->set_power) + board->set_power(on); +} + +/* * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB * chargers). * @@ -222,6 +235,9 @@ static void isp1704_charger_work(struct work_struct *data) mutex_lock(&lock); + if (event != USB_EVENT_NONE) + isp1704_charger_set_power(isp, 1); + switch (event) { case USB_EVENT_VBUS: isp->online = true; @@ -269,6 +285,8 @@ static void isp1704_charger_work(struct work_struct *data) */ if (isp->otg->gadget) usb_gadget_disconnect(isp->otg->gadget); + + isp1704_charger_set_power(isp, 0); break; case USB_EVENT_ENUMERATED: if (isp->present) @@ -394,6 +412,8 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev) isp->dev = &pdev->dev; platform_set_drvdata(pdev, isp); + isp1704_charger_set_power(isp, 1); + ret = isp1704_test_ulpi(isp); if (ret < 0) goto fail1; @@ -434,6 +454,7 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev) /* Detect charger if VBUS is valid (the cable was already plugged). */ ret = otg_io_read(isp->otg, ULPI_USB_INT_STS); + isp1704_charger_set_power(isp, 0); if ((ret & ULPI_INT_VBUS_VALID) && !isp->otg->default_a) { isp->event = USB_EVENT_VBUS; schedule_work(&isp->work); @@ -459,6 +480,7 @@ static int __devexit isp1704_charger_remove(struct platform_device *pdev) otg_unregister_notifier(isp->otg, &isp->nb); power_supply_unregister(&isp->psy); otg_put_transceiver(isp->otg); + isp1704_charger_set_power(isp, 0); kfree(isp); return 0; diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c new file mode 100644 index 00000000000..33ff0e37809 --- /dev/null +++ b/drivers/power/max8903_charger.c @@ -0,0 +1,391 @@ +/* + * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham <myungjoo.ham@samsung.com> + * + * 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 <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/power/max8903_charger.h> + +struct max8903_data { + struct max8903_pdata *pdata; + struct device *dev; + struct power_supply psy; + bool fault; + bool usb_in; + bool ta_in; +}; + +static enum power_supply_property max8903_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, /* Charger status output */ + POWER_SUPPLY_PROP_ONLINE, /* External power source */ + POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ +}; + +static int max8903_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max8903_data *data = container_of(psy, + struct max8903_data, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + if (data->pdata->chg) { + if (gpio_get_value(data->pdata->chg) == 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (data->usb_in || data->ta_in) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = 0; + if (data->usb_in || data->ta_in) + val->intval = 1; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + if (data->fault) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + default: + return -EINVAL; + } + return 0; +} + +static irqreturn_t max8903_dcin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool ta_in; + enum power_supply_type old_type; + + ta_in = gpio_get_value(pdata->dok) ? false : true; + + if (ta_in == data->ta_in) + return IRQ_HANDLED; + + data->ta_in = ta_in; + + /* Set Current-Limit-Mode 1:DC 0:USB */ + if (pdata->dcm) + gpio_set_value(pdata->dcm, ta_in ? 1 : 0); + + /* Charger Enable / Disable (cen is negated) */ + if (pdata->cen) + gpio_set_value(pdata->cen, ta_in ? 0 : + (data->usb_in ? 0 : 1)); + + dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); + + old_type = data->psy.type; + + if (data->ta_in) + data->psy.type = POWER_SUPPLY_TYPE_MAINS; + else if (data->usb_in) + data->psy.type = POWER_SUPPLY_TYPE_USB; + else + data->psy.type = POWER_SUPPLY_TYPE_BATTERY; + + if (old_type != data->psy.type) + power_supply_changed(&data->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t max8903_usbin(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool usb_in; + enum power_supply_type old_type; + + usb_in = gpio_get_value(pdata->uok) ? false : true; + + if (usb_in == data->usb_in) + return IRQ_HANDLED; + + data->usb_in = usb_in; + + /* Do not touch Current-Limit-Mode */ + + /* Charger Enable / Disable (cen is negated) */ + if (pdata->cen) + gpio_set_value(pdata->cen, usb_in ? 0 : + (data->ta_in ? 0 : 1)); + + dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); + + old_type = data->psy.type; + + if (data->ta_in) + data->psy.type = POWER_SUPPLY_TYPE_MAINS; + else if (data->usb_in) + data->psy.type = POWER_SUPPLY_TYPE_USB; + else + data->psy.type = POWER_SUPPLY_TYPE_BATTERY; + + if (old_type != data->psy.type) + power_supply_changed(&data->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t max8903_fault(int irq, void *_data) +{ + struct max8903_data *data = _data; + struct max8903_pdata *pdata = data->pdata; + bool fault; + + fault = gpio_get_value(pdata->flt) ? false : true; + + if (fault == data->fault) + return IRQ_HANDLED; + + data->fault = fault; + + if (fault) + dev_err(data->dev, "Charger suffers a fault and stops.\n"); + else + dev_err(data->dev, "Charger recovered from a fault.\n"); + + return IRQ_HANDLED; +} + +static __devinit int max8903_probe(struct platform_device *pdev) +{ + struct max8903_data *data; + struct device *dev = &pdev->dev; + struct max8903_pdata *pdata = pdev->dev.platform_data; + int ret = 0; + int gpio; + int ta_in = 0; + int usb_in = 0; + + data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Cannot allocate memory.\n"); + return -ENOMEM; + } + data->pdata = pdata; + data->dev = dev; + platform_set_drvdata(pdev, data); + + if (pdata->dc_valid == false && pdata->usb_valid == false) { + dev_err(dev, "No valid power sources.\n"); + ret = -EINVAL; + goto err; + } + + if (pdata->dc_valid) { + if (pdata->dok && gpio_is_valid(pdata->dok) && + pdata->dcm && gpio_is_valid(pdata->dcm)) { + gpio = pdata->dok; /* PULL_UPed Interrupt */ + ta_in = gpio_get_value(gpio) ? 0 : 1; + + gpio = pdata->dcm; /* Output */ + gpio_set_value(gpio, ta_in); + } else { + dev_err(dev, "When DC is wired, DOK and DCM should" + " be wired as well.\n"); + ret = -EINVAL; + goto err; + } + } else { + if (pdata->dcm) { + if (gpio_is_valid(pdata->dcm)) + gpio_set_value(pdata->dcm, 0); + else { + dev_err(dev, "Invalid pin: dcm.\n"); + ret = -EINVAL; + goto err; + } + } + } + + if (pdata->usb_valid) { + if (pdata->uok && gpio_is_valid(pdata->uok)) { + gpio = pdata->uok; + usb_in = gpio_get_value(gpio) ? 0 : 1; + } else { + dev_err(dev, "When USB is wired, UOK should be wired." + "as well.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->cen) { + if (gpio_is_valid(pdata->cen)) { + gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1); + } else { + dev_err(dev, "Invalid pin: cen.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->chg) { + if (!gpio_is_valid(pdata->chg)) { + dev_err(dev, "Invalid pin: chg.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->flt) { + if (!gpio_is_valid(pdata->flt)) { + dev_err(dev, "Invalid pin: flt.\n"); + ret = -EINVAL; + goto err; + } + } + + if (pdata->usus) { + if (!gpio_is_valid(pdata->usus)) { + dev_err(dev, "Invalid pin: usus.\n"); + ret = -EINVAL; + goto err; + } + } + + data->fault = false; + data->ta_in = ta_in; + data->usb_in = usb_in; + + data->psy.name = "max8903_charger"; + data->psy.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS : + ((usb_in) ? POWER_SUPPLY_TYPE_USB : + POWER_SUPPLY_TYPE_BATTERY); + data->psy.get_property = max8903_get_property; + data->psy.properties = max8903_charger_props; + data->psy.num_properties = ARRAY_SIZE(max8903_charger_props); + + ret = power_supply_register(dev, &data->psy); + if (ret) { + dev_err(dev, "failed: power supply register.\n"); + goto err; + } + + if (pdata->dc_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->dok), + NULL, max8903_dcin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 DC IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for DC (%d)\n", + gpio_to_irq(pdata->dok), ret); + goto err_psy; + } + } + + if (pdata->usb_valid) { + ret = request_threaded_irq(gpio_to_irq(pdata->uok), + NULL, max8903_usbin, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 USB IN", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for USB (%d)\n", + gpio_to_irq(pdata->uok), ret); + goto err_dc_irq; + } + } + + if (pdata->flt) { + ret = request_threaded_irq(gpio_to_irq(pdata->flt), + NULL, max8903_fault, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "MAX8903 Fault", data); + if (ret) { + dev_err(dev, "Cannot request irq %d for Fault (%d)\n", + gpio_to_irq(pdata->flt), ret); + goto err_usb_irq; + } + } + + return 0; + +err_usb_irq: + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); +err_dc_irq: + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); +err_psy: + power_supply_unregister(&data->psy); +err: + kfree(data); + return ret; +} + +static __devexit int max8903_remove(struct platform_device *pdev) +{ + struct max8903_data *data = platform_get_drvdata(pdev); + + if (data) { + struct max8903_pdata *pdata = data->pdata; + + if (pdata->flt) + free_irq(gpio_to_irq(pdata->flt), data); + if (pdata->usb_valid) + free_irq(gpio_to_irq(pdata->uok), data); + if (pdata->dc_valid) + free_irq(gpio_to_irq(pdata->dok), data); + power_supply_unregister(&data->psy); + kfree(data); + } + + return 0; +} + +static struct platform_driver max8903_driver = { + .probe = max8903_probe, + .remove = __devexit_p(max8903_remove), + .driver = { + .name = "max8903-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init max8903_init(void) +{ + return platform_driver_register(&max8903_driver); +} +module_init(max8903_init); + +static void __exit max8903_exit(void) +{ + platform_driver_unregister(&max8903_driver); +} +module_exit(max8903_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MAX8903 Charger Driver"); +MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_ALIAS("max8903-charger"); diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index 8e5aec26086..a70e16d3a3d 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -425,16 +425,11 @@ static __devexit int max8925_deinit_charger(struct max8925_power_info *info) static __devinit int max8925_power_probe(struct platform_device *pdev) { struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct max8925_platform_data *max8925_pdata; struct max8925_power_pdata *pdata = NULL; struct max8925_power_info *info; int ret; - if (pdev->dev.parent->platform_data) { - max8925_pdata = pdev->dev.parent->platform_data; - pdata = max8925_pdata->power; - } - + pdata = pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "platform data isn't assigned to " "power supply\n"); @@ -447,6 +442,7 @@ static __devinit int max8925_power_probe(struct platform_device *pdev) info->chip = chip; info->gpm = chip->i2c; info->adc = chip->adc; + platform_set_drvdata(pdev, info); info->ac.name = "max8925-ac"; info->ac.type = POWER_SUPPLY_TYPE_MAINS; @@ -482,8 +478,6 @@ static __devinit int max8925_power_probe(struct platform_device *pdev) info->topoff_threshold = pdata->topoff_threshold; info->fast_charge = pdata->fast_charge; info->set_charger = pdata->set_charger; - dev_set_drvdata(&pdev->dev, info); - platform_set_drvdata(pdev, info); max8925_init_charger(chip, info); return 0; diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c index 0cd9f67d33e..b527c93bf2f 100644 --- a/drivers/power/test_power.c +++ b/drivers/power/test_power.c @@ -3,6 +3,12 @@ * * Copyright 2010 Anton Vorontsov <cbouatmailru@gmail.com> * + * Dynamic module parameter code from the Virtual Battery Driver + * Copyright (C) 2008 Pylone, Inc. + * By: Masashi YOKOTA <yokota@pylone.jp> + * Originally found here: + * http://downloads.pylone.jp/src/virtual_battery/virtual_battery-0.0.1.tar.bz2 + * * 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. @@ -15,8 +21,12 @@ #include <linux/delay.h> #include <linux/vermagic.h> -static int test_power_ac_online = 1; -static int test_power_battery_status = POWER_SUPPLY_STATUS_CHARGING; +static int ac_online = 1; +static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING; +static int battery_health = POWER_SUPPLY_HEALTH_GOOD; +static int battery_present = 1; /* true */ +static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION; +static int battery_capacity = 50; static int test_power_get_ac_property(struct power_supply *psy, enum power_supply_property psp, @@ -24,7 +34,7 @@ static int test_power_get_ac_property(struct power_supply *psy, { switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - val->intval = test_power_ac_online; + val->intval = ac_online; break; default: return -EINVAL; @@ -47,22 +57,30 @@ static int test_power_get_battery_property(struct power_supply *psy, val->strval = UTS_RELEASE; break; case POWER_SUPPLY_PROP_STATUS: - val->intval = test_power_battery_status; + val->intval = battery_status; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; break; case POWER_SUPPLY_PROP_HEALTH: - val->intval = POWER_SUPPLY_HEALTH_GOOD; + val->intval = battery_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery_present; break; case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + val->intval = battery_technology; break; case POWER_SUPPLY_PROP_CAPACITY_LEVEL: val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; break; case POWER_SUPPLY_PROP_CAPACITY: - val->intval = 50; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = battery_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = 100; break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: @@ -84,9 +102,11 @@ static enum power_supply_property test_power_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, @@ -118,6 +138,7 @@ static struct power_supply test_power_supplies[] = { }, }; + static int __init test_power_init(void) { int i; @@ -145,8 +166,8 @@ static void __exit test_power_exit(void) int i; /* Let's see how we handle changes... */ - test_power_ac_online = 0; - test_power_battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + ac_online = 0; + battery_status = POWER_SUPPLY_STATUS_DISCHARGING; for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) power_supply_changed(&test_power_supplies[i]); pr_info("%s: 'changed' event sent, sleeping for 10 seconds...\n", @@ -158,6 +179,241 @@ static void __exit test_power_exit(void) } module_exit(test_power_exit); + + +#define MAX_KEYLENGTH 256 +struct battery_property_map { + int value; + char const *key; +}; + +static struct battery_property_map map_ac_online[] = { + { 0, "on" }, + { 1, "off" }, + { -1, NULL }, +}; + +static struct battery_property_map map_status[] = { + { POWER_SUPPLY_STATUS_CHARGING, "charging" }, + { POWER_SUPPLY_STATUS_DISCHARGING, "discharging" }, + { POWER_SUPPLY_STATUS_NOT_CHARGING, "not-charging" }, + { POWER_SUPPLY_STATUS_FULL, "full" }, + { -1, NULL }, +}; + +static struct battery_property_map map_health[] = { + { POWER_SUPPLY_HEALTH_GOOD, "good" }, + { POWER_SUPPLY_HEALTH_OVERHEAT, "overheat" }, + { POWER_SUPPLY_HEALTH_DEAD, "dead" }, + { POWER_SUPPLY_HEALTH_OVERVOLTAGE, "overvoltage" }, + { POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, "failure" }, + { -1, NULL }, +}; + +static struct battery_property_map map_present[] = { + { 0, "false" }, + { 1, "true" }, + { -1, NULL }, +}; + +static struct battery_property_map map_technology[] = { + { POWER_SUPPLY_TECHNOLOGY_NiMH, "NiMH" }, + { POWER_SUPPLY_TECHNOLOGY_LION, "LION" }, + { POWER_SUPPLY_TECHNOLOGY_LIPO, "LIPO" }, + { POWER_SUPPLY_TECHNOLOGY_LiFe, "LiFe" }, + { POWER_SUPPLY_TECHNOLOGY_NiCd, "NiCd" }, + { POWER_SUPPLY_TECHNOLOGY_LiMn, "LiMn" }, + { -1, NULL }, +}; + + +static int map_get_value(struct battery_property_map *map, const char *key, + int def_val) +{ + char buf[MAX_KEYLENGTH]; + int cr; + + strncpy(buf, key, MAX_KEYLENGTH); + buf[MAX_KEYLENGTH-1] = '\0'; + + cr = strnlen(buf, MAX_KEYLENGTH) - 1; + if (buf[cr] == '\n') + buf[cr] = '\0'; + + while (map->key) { + if (strncasecmp(map->key, buf, MAX_KEYLENGTH) == 0) + return map->value; + map++; + } + + return def_val; +} + + +static const char *map_get_key(struct battery_property_map *map, int value, + const char *def_key) +{ + while (map->key) { + if (map->value == value) + return map->key; + map++; + } + + return def_key; +} + +static int param_set_ac_online(const char *key, const struct kernel_param *kp) +{ + ac_online = map_get_value(map_ac_online, key, ac_online); + power_supply_changed(&test_power_supplies[0]); + return 0; +} + +static int param_get_ac_online(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_status(const char *key, + const struct kernel_param *kp) +{ + battery_status = map_get_value(map_status, key, battery_status); + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +static int param_get_battery_status(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_status, battery_status, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_health(const char *key, + const struct kernel_param *kp) +{ + battery_health = map_get_value(map_health, key, battery_health); + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +static int param_get_battery_health(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_health, battery_health, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_present(const char *key, + const struct kernel_param *kp) +{ + battery_present = map_get_value(map_present, key, battery_present); + power_supply_changed(&test_power_supplies[0]); + return 0; +} + +static int param_get_battery_present(char *buffer, + const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_present, battery_present, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_technology(const char *key, + const struct kernel_param *kp) +{ + battery_technology = map_get_value(map_technology, key, + battery_technology); + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +static int param_get_battery_technology(char *buffer, + const struct kernel_param *kp) +{ + strcpy(buffer, + map_get_key(map_technology, battery_technology, "unknown")); + return strlen(buffer); +} + +static int param_set_battery_capacity(const char *key, + const struct kernel_param *kp) +{ + int tmp; + + if (1 != sscanf(key, "%d", &tmp)) + return -EINVAL; + + battery_capacity = tmp; + power_supply_changed(&test_power_supplies[1]); + return 0; +} + +#define param_get_battery_capacity param_get_int + + + +static struct kernel_param_ops param_ops_ac_online = { + .set = param_set_ac_online, + .get = param_get_ac_online, +}; + +static struct kernel_param_ops param_ops_battery_status = { + .set = param_set_battery_status, + .get = param_get_battery_status, +}; + +static struct kernel_param_ops param_ops_battery_present = { + .set = param_set_battery_present, + .get = param_get_battery_present, +}; + +static struct kernel_param_ops param_ops_battery_technology = { + .set = param_set_battery_technology, + .get = param_get_battery_technology, +}; + +static struct kernel_param_ops param_ops_battery_health = { + .set = param_set_battery_health, + .get = param_get_battery_health, +}; + +static struct kernel_param_ops param_ops_battery_capacity = { + .set = param_set_battery_capacity, + .get = param_get_battery_capacity, +}; + + +#define param_check_ac_online(name, p) __param_check(name, p, void); +#define param_check_battery_status(name, p) __param_check(name, p, void); +#define param_check_battery_present(name, p) __param_check(name, p, void); +#define param_check_battery_technology(name, p) __param_check(name, p, void); +#define param_check_battery_health(name, p) __param_check(name, p, void); +#define param_check_battery_capacity(name, p) __param_check(name, p, void); + + +module_param(ac_online, ac_online, 0644); +MODULE_PARM_DESC(ac_online, "AC charging state <on|off>"); + +module_param(battery_status, battery_status, 0644); +MODULE_PARM_DESC(battery_status, + "battery status <charging|discharging|not-charging|full>"); + +module_param(battery_present, battery_present, 0644); +MODULE_PARM_DESC(battery_present, + "battery presence state <good|overheat|dead|overvoltage|failure>"); + +module_param(battery_technology, battery_technology, 0644); +MODULE_PARM_DESC(battery_technology, + "battery technology <NiMH|LION|LIPO|LiFe|NiCd|LiMn>"); + +module_param(battery_health, battery_health, 0644); +MODULE_PARM_DESC(battery_health, + "battery health state <good|overheat|dead|overvoltage|failure>"); + +module_param(battery_capacity, battery_capacity, 0644); +MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)"); + + MODULE_DESCRIPTION("Power supply driver for testing"); MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c index e5ced3a4c1e..d119c38b3ff 100644 --- a/drivers/power/z2_battery.c +++ b/drivers/power/z2_battery.c @@ -271,24 +271,33 @@ static int __devexit z2_batt_remove(struct i2c_client *client) } #ifdef CONFIG_PM -static int z2_batt_suspend(struct i2c_client *client, pm_message_t state) +static int z2_batt_suspend(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct z2_charger *charger = i2c_get_clientdata(client); flush_work_sync(&charger->bat_work); return 0; } -static int z2_batt_resume(struct i2c_client *client) +static int z2_batt_resume(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct z2_charger *charger = i2c_get_clientdata(client); schedule_work(&charger->bat_work); return 0; } + +static const struct dev_pm_ops z2_battery_pm_ops = { + .suspend = z2_batt_suspend, + .resume = z2_batt_resume, +}; + +#define Z2_BATTERY_PM_OPS (&z2_battery_pm_ops) + #else -#define z2_batt_suspend NULL -#define z2_batt_resume NULL +#define Z2_BATTERY_PM_OPS (NULL) #endif static const struct i2c_device_id z2_batt_id[] = { @@ -301,11 +310,10 @@ static struct i2c_driver z2_batt_driver = { .driver = { .name = "z2-battery", .owner = THIS_MODULE, + .pm = Z2_BATTERY_PM_OPS }, .probe = z2_batt_probe, .remove = z2_batt_remove, - .suspend = z2_batt_suspend, - .resume = z2_batt_resume, .id_table = z2_batt_id, }; diff --git a/drivers/regulator/88pm8607.c b/drivers/regulator/88pm8607.c index 859251250b5..d63fddb0fbb 100644 --- a/drivers/regulator/88pm8607.c +++ b/drivers/regulator/88pm8607.c @@ -15,7 +15,6 @@ #include <linux/platform_device.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> -#include <linux/mfd/core.h> #include <linux/mfd/88pm860x.h> struct pm8607_regulator_info { @@ -399,36 +398,33 @@ static int __devinit pm8607_regulator_probe(struct platform_device *pdev) { struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); struct pm8607_regulator_info *info = NULL; - struct regulator_init_data *pdata; - struct mfd_cell *cell; + struct regulator_init_data *pdata = pdev->dev.platform_data; + struct resource *res; int i; - cell = pdev->dev.platform_data; - if (cell == NULL) - return -ENODEV; - pdata = cell->mfd_data; - if (pdata == NULL) + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource!\n"); return -EINVAL; - + } for (i = 0; i < ARRAY_SIZE(pm8607_regulator_info); i++) { info = &pm8607_regulator_info[i]; - if (!strcmp(info->desc.name, pdata->constraints.name)) + if (info->desc.id == res->start) break; } - if (i > ARRAY_SIZE(pm8607_regulator_info)) { - dev_err(&pdev->dev, "Failed to find regulator %s\n", - pdata->constraints.name); + if ((i < 0) || (i > PM8607_ID_RG_MAX)) { + dev_err(&pdev->dev, "Failed to find regulator %llu\n", + (unsigned long long)res->start); return -EINVAL; } - info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; info->chip = chip; /* check DVC ramp slope double */ - if (!strcmp(info->desc.name, "BUCK3")) - if (info->chip->buck3_double) - info->slope_double = 1; + if ((i == PM8607_ID_BUCK3) && info->chip->buck3_double) + info->slope_double = 1; + /* replace driver_data with info */ info->regulator = regulator_register(&info->desc, &pdev->dev, pdata, info); if (IS_ERR(info->regulator)) { diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f0b13a0d185..d7ed20f293d 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -297,5 +297,11 @@ config REGULATOR_TPS6524X serial interface currently supported on the sequencer serial port controller. +config REGULATOR_TPS65910 + tristate "TI TPS65910 Power Regulator" + depends on MFD_TPS65910 + help + This driver supports TPS65910 voltage regulator chips. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 165ff5371e9..3932d2ec38f 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -42,5 +42,6 @@ obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o +obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c index b1d77946e9c..585e4946fe0 100644 --- a/drivers/regulator/ab3100.c +++ b/drivers/regulator/ab3100.c @@ -17,7 +17,6 @@ #include <linux/platform_device.h> #include <linux/regulator/driver.h> #include <linux/mfd/abx500.h> -#include <linux/mfd/core.h> /* LDO registers and some handy masking definitions for AB3100 */ #define AB3100_LDO_A 0x40 @@ -582,7 +581,7 @@ ab3100_regulator_desc[AB3100_NUM_REGULATORS] = { static int __devinit ab3100_regulators_probe(struct platform_device *pdev) { - struct ab3100_platform_data *plfdata = mfd_get_data(pdev); + struct ab3100_platform_data *plfdata = pdev->dev.platform_data; int err = 0; u8 data; int i; diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 0fae51c4845..d3e38790906 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -158,6 +158,13 @@ static int regulator_check_consumers(struct regulator_dev *rdev, struct regulator *regulator; list_for_each_entry(regulator, &rdev->consumer_list, list) { + /* + * Assume consumers that didn't say anything are OK + * with anything in the constraint range. + */ + if (!regulator->min_uV && !regulator->max_uV) + continue; + if (*max_uV > regulator->max_uV) *max_uV = regulator->max_uV; if (*min_uV < regulator->min_uV) @@ -197,9 +204,9 @@ static int regulator_check_current_limit(struct regulator_dev *rdev, } /* operating mode constraint check */ -static int regulator_check_mode(struct regulator_dev *rdev, int mode) +static int regulator_mode_constrain(struct regulator_dev *rdev, int *mode) { - switch (mode) { + switch (*mode) { case REGULATOR_MODE_FAST: case REGULATOR_MODE_NORMAL: case REGULATOR_MODE_IDLE: @@ -217,11 +224,17 @@ static int regulator_check_mode(struct regulator_dev *rdev, int mode) rdev_err(rdev, "operation not allowed\n"); return -EPERM; } - if (!(rdev->constraints->valid_modes_mask & mode)) { - rdev_err(rdev, "invalid mode %x\n", mode); - return -EINVAL; + + /* The modes are bitmasks, the most power hungry modes having + * the lowest values. If the requested mode isn't supported + * try higher modes. */ + while (*mode) { + if (rdev->constraints->valid_modes_mask & *mode) + return 0; + *mode /= 2; } - return 0; + + return -EINVAL; } /* dynamic regulator mode switching constraint check */ @@ -612,7 +625,7 @@ static void drms_uA_update(struct regulator_dev *rdev) output_uV, current_uA); /* check the new mode is allowed */ - err = regulator_check_mode(rdev, mode); + err = regulator_mode_constrain(rdev, &mode); if (err == 0) rdev->desc->ops->set_mode(rdev, mode); } @@ -718,6 +731,10 @@ static void print_constraints(struct regulator_dev *rdev) count += sprintf(buf + count, "at %d mV ", ret / 1000); } + if (constraints->uV_offset) + count += sprintf(buf, "%dmV offset ", + constraints->uV_offset / 1000); + if (constraints->min_uA && constraints->max_uA) { if (constraints->min_uA == constraints->max_uA) count += sprintf(buf + count, "%d mA ", @@ -1498,13 +1515,14 @@ static int _regulator_force_disable(struct regulator_dev *rdev, */ int regulator_force_disable(struct regulator *regulator) { + struct regulator_dev *rdev = regulator->rdev; struct regulator_dev *supply_rdev = NULL; int ret; - mutex_lock(®ulator->rdev->mutex); + mutex_lock(&rdev->mutex); regulator->uA_load = 0; - ret = _regulator_force_disable(regulator->rdev, &supply_rdev); - mutex_unlock(®ulator->rdev->mutex); + ret = _regulator_force_disable(rdev, &supply_rdev); + mutex_unlock(&rdev->mutex); if (supply_rdev) regulator_disable(get_device_regulator(rdev_get_dev(supply_rdev))); @@ -1634,6 +1652,9 @@ static int _regulator_do_set_voltage(struct regulator_dev *rdev, trace_regulator_set_voltage(rdev_get_name(rdev), min_uV, max_uV); + min_uV += rdev->constraints->uV_offset; + max_uV += rdev->constraints->uV_offset; + if (rdev->desc->ops->set_voltage) { ret = rdev->desc->ops->set_voltage(rdev, min_uV, max_uV, &selector); @@ -1858,18 +1879,22 @@ EXPORT_SYMBOL_GPL(regulator_sync_voltage); static int _regulator_get_voltage(struct regulator_dev *rdev) { - int sel; + int sel, ret; if (rdev->desc->ops->get_voltage_sel) { sel = rdev->desc->ops->get_voltage_sel(rdev); if (sel < 0) return sel; - return rdev->desc->ops->list_voltage(rdev, sel); - } - if (rdev->desc->ops->get_voltage) - return rdev->desc->ops->get_voltage(rdev); - else + ret = rdev->desc->ops->list_voltage(rdev, sel); + } else if (rdev->desc->ops->get_voltage) { + ret = rdev->desc->ops->get_voltage(rdev); + } else { return -EINVAL; + } + + if (ret < 0) + return ret; + return ret - rdev->constraints->uV_offset; } /** @@ -2005,7 +2030,7 @@ int regulator_set_mode(struct regulator *regulator, unsigned int mode) } /* constraints check */ - ret = regulator_check_mode(rdev, mode); + ret = regulator_mode_constrain(rdev, &mode); if (ret < 0) goto out; @@ -2081,16 +2106,26 @@ int regulator_set_optimum_mode(struct regulator *regulator, int uA_load) mutex_lock(&rdev->mutex); + /* + * first check to see if we can set modes at all, otherwise just + * tell the consumer everything is OK. + */ regulator->uA_load = uA_load; ret = regulator_check_drms(rdev); - if (ret < 0) + if (ret < 0) { + ret = 0; goto out; - ret = -EINVAL; + } - /* sanity check */ if (!rdev->desc->ops->get_optimum_mode) goto out; + /* + * we can actually do this so any errors are indicators of + * potential real failure. + */ + ret = -EINVAL; + /* get output voltage */ output_uV = _regulator_get_voltage(rdev); if (output_uV <= 0) { @@ -2116,7 +2151,7 @@ int regulator_set_optimum_mode(struct regulator *regulator, int uA_load) mode = rdev->desc->ops->get_optimum_mode(rdev, input_uV, output_uV, total_uA_load); - ret = regulator_check_mode(rdev, mode); + ret = regulator_mode_constrain(rdev, &mode); if (ret < 0) { rdev_err(rdev, "failed to get optimum mode @ %d uA %d -> %d uV\n", total_uA_load, input_uV, output_uV); @@ -2589,14 +2624,6 @@ struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc, if (ret < 0) goto scrub; - /* set supply regulator if it exists */ - if (init_data->supply_regulator && init_data->supply_regulator_dev) { - dev_err(dev, - "Supply regulator specified by both name and dev\n"); - ret = -EINVAL; - goto scrub; - } - if (init_data->supply_regulator) { struct regulator_dev *r; int found = 0; @@ -2621,14 +2648,6 @@ struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc, goto scrub; } - if (init_data->supply_regulator_dev) { - dev_warn(dev, "Uses supply_regulator_dev instead of regulator_supply\n"); - ret = set_supply(rdev, - dev_get_drvdata(init_data->supply_regulator_dev)); - if (ret < 0) - goto scrub; - } - /* add consumers devices */ for (i = 0; i < init_data->num_consumer_supplies; i++) { ret = set_consumer_device_supply(rdev, diff --git a/drivers/regulator/db8500-prcmu.c b/drivers/regulator/db8500-prcmu.c index 1089a961616..e5f7b8fe51f 100644 --- a/drivers/regulator/db8500-prcmu.c +++ b/drivers/regulator/db8500-prcmu.c @@ -13,7 +13,6 @@ #include <linux/err.h> #include <linux/spinlock.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/mfd/db8500-prcmu.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> @@ -471,7 +470,8 @@ static struct db8500_regulator_info static int __devinit db8500_regulator_probe(struct platform_device *pdev) { - struct regulator_init_data *db8500_init_data = mfd_get_data(pdev); + struct regulator_init_data *db8500_init_data = + dev_get_platdata(&pdev->dev); int i, err; /* register all regulators */ diff --git a/drivers/regulator/max8925-regulator.c b/drivers/regulator/max8925-regulator.c index 8ae147549c6..e4dbd667c04 100644 --- a/drivers/regulator/max8925-regulator.c +++ b/drivers/regulator/max8925-regulator.c @@ -23,6 +23,10 @@ #define SD1_DVM_SHIFT 5 /* SDCTL1 bit5 */ #define SD1_DVM_EN 6 /* SDV1 bit 6 */ +/* bit definitions in SD & LDO control registers */ +#define OUT_ENABLE 0x1f /* Power U/D sequence as I2C */ +#define OUT_DISABLE 0x1e /* Power U/D sequence as I2C */ + struct max8925_regulator_info { struct regulator_desc desc; struct regulator_dev *regulator; @@ -93,8 +97,8 @@ static int max8925_enable(struct regulator_dev *rdev) struct max8925_regulator_info *info = rdev_get_drvdata(rdev); return max8925_set_bits(info->i2c, info->enable_reg, - 1 << info->enable_bit, - 1 << info->enable_bit); + OUT_ENABLE << info->enable_bit, + OUT_ENABLE << info->enable_bit); } static int max8925_disable(struct regulator_dev *rdev) @@ -102,7 +106,8 @@ static int max8925_disable(struct regulator_dev *rdev) struct max8925_regulator_info *info = rdev_get_drvdata(rdev); return max8925_set_bits(info->i2c, info->enable_reg, - 1 << info->enable_bit, 0); + OUT_ENABLE << info->enable_bit, + OUT_DISABLE << info->enable_bit); } static int max8925_is_enabled(struct regulator_dev *rdev) diff --git a/drivers/regulator/max8997.c b/drivers/regulator/max8997.c index 77e0cfb30b2..10d5a1d9768 100644 --- a/drivers/regulator/max8997.c +++ b/drivers/regulator/max8997.c @@ -267,7 +267,6 @@ static int max8997_get_enable_register(struct regulator_dev *rdev, default: /* Not controllable or not exists */ return -EINVAL; - break; } return 0; @@ -1033,11 +1032,11 @@ static __devinit int max8997_pmic_probe(struct platform_device *pdev) /* For the safety, set max voltage before setting up */ for (i = 0; i < 8; i++) { - max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS(i + 1), + max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS1 + i, max_buck1, 0x3f); - max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS(i + 1), + max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS1 + i, max_buck2, 0x3f); - max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS(i + 1), + max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS1 + i, max_buck5, 0x3f); } @@ -1114,13 +1113,13 @@ static __devinit int max8997_pmic_probe(struct platform_device *pdev) /* Initialize all the DVS related BUCK registers */ for (i = 0; i < 8; i++) { - max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS(i + 1), + max8997_update_reg(i2c, MAX8997_REG_BUCK1DVS1 + i, max8997->buck1_vol[i], 0x3f); - max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS(i + 1), + max8997_update_reg(i2c, MAX8997_REG_BUCK2DVS1 + i, max8997->buck2_vol[i], 0x3f); - max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS(i + 1), + max8997_update_reg(i2c, MAX8997_REG_BUCK5DVS1 + i, max8997->buck5_vol[i], 0x3f); } diff --git a/drivers/regulator/max8998.c b/drivers/regulator/max8998.c index f57e9c42fdb..41a1495eec2 100644 --- a/drivers/regulator/max8998.c +++ b/drivers/regulator/max8998.c @@ -732,13 +732,15 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) if (!pdata->buck1_set1) { printk(KERN_ERR "MAX8998 SET1 GPIO defined as 0 !\n"); WARN_ON(!pdata->buck1_set1); - return -EIO; + ret = -EIO; + goto err_free_mem; } /* Check if SET2 is not equal to 0 */ if (!pdata->buck1_set2) { printk(KERN_ERR "MAX8998 SET2 GPIO defined as 0 !\n"); WARN_ON(!pdata->buck1_set2); - return -EIO; + ret = -EIO; + goto err_free_mem; } gpio_request(pdata->buck1_set1, "MAX8998 BUCK1_SET1"); @@ -758,7 +760,7 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) max8998->buck1_vol[0] = i; ret = max8998_write_reg(i2c, MAX8998_REG_BUCK1_VOLTAGE1, i); if (ret) - return ret; + goto err_free_mem; /* Set predefined value for BUCK1 register 2 */ i = 0; @@ -770,7 +772,7 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) max8998->buck1_vol[1] = i; ret = max8998_write_reg(i2c, MAX8998_REG_BUCK1_VOLTAGE2, i); if (ret) - return ret; + goto err_free_mem; /* Set predefined value for BUCK1 register 3 */ i = 0; @@ -782,7 +784,7 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) max8998->buck1_vol[2] = i; ret = max8998_write_reg(i2c, MAX8998_REG_BUCK1_VOLTAGE3, i); if (ret) - return ret; + goto err_free_mem; /* Set predefined value for BUCK1 register 4 */ i = 0; @@ -794,7 +796,7 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) max8998->buck1_vol[3] = i; ret = max8998_write_reg(i2c, MAX8998_REG_BUCK1_VOLTAGE4, i); if (ret) - return ret; + goto err_free_mem; } @@ -803,7 +805,8 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) if (!pdata->buck2_set3) { printk(KERN_ERR "MAX8998 SET3 GPIO defined as 0 !\n"); WARN_ON(!pdata->buck2_set3); - return -EIO; + ret = -EIO; + goto err_free_mem; } gpio_request(pdata->buck2_set3, "MAX8998 BUCK2_SET3"); gpio_direction_output(pdata->buck2_set3, @@ -818,7 +821,7 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) max8998->buck2_vol[0] = i; ret = max8998_write_reg(i2c, MAX8998_REG_BUCK2_VOLTAGE1, i); if (ret) - return ret; + goto err_free_mem; /* BUCK2 register 2 */ i = 0; @@ -830,7 +833,7 @@ static __devinit int max8998_pmic_probe(struct platform_device *pdev) max8998->buck2_vol[1] = i; ret = max8998_write_reg(i2c, MAX8998_REG_BUCK2_VOLTAGE2, i); if (ret) - return ret; + goto err_free_mem; } for (i = 0; i < pdata->num_regulators; i++) { @@ -860,6 +863,7 @@ err: if (rdev[i]) regulator_unregister(rdev[i]); +err_free_mem: kfree(max8998->rdev); kfree(max8998); diff --git a/drivers/regulator/mc13783-regulator.c b/drivers/regulator/mc13783-regulator.c index b8a00c7fa44..730f43ad415 100644 --- a/drivers/regulator/mc13783-regulator.c +++ b/drivers/regulator/mc13783-regulator.c @@ -15,7 +15,6 @@ #include <linux/regulator/driver.h> #include <linux/platform_device.h> #include <linux/kernel.h> -#include <linux/mfd/core.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/err.h> @@ -337,7 +336,8 @@ static int __devinit mc13783_regulator_probe(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv; struct mc13xxx *mc13783 = dev_get_drvdata(pdev->dev.parent); - struct mc13783_regulator_platform_data *pdata = mfd_get_data(pdev); + struct mc13783_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); struct mc13783_regulator_init_data *init_data; int i, ret; @@ -381,7 +381,8 @@ err: static int __devexit mc13783_regulator_remove(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv = platform_get_drvdata(pdev); - struct mc13783_regulator_platform_data *pdata = mfd_get_data(pdev); + struct mc13783_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); int i; platform_set_drvdata(pdev, NULL); diff --git a/drivers/regulator/mc13892-regulator.c b/drivers/regulator/mc13892-regulator.c index 6f15168e5ed..3285d41842f 100644 --- a/drivers/regulator/mc13892-regulator.c +++ b/drivers/regulator/mc13892-regulator.c @@ -15,7 +15,6 @@ #include <linux/regulator/driver.h> #include <linux/platform_device.h> #include <linux/kernel.h> -#include <linux/mfd/core.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/err.h> @@ -432,7 +431,8 @@ static int mc13892_sw_regulator_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, unsigned *selector) { struct mc13xxx_regulator_priv *priv = rdev_get_drvdata(rdev); - int hi, value, val, mask, id = rdev_get_id(rdev); + int hi, value, mask, id = rdev_get_id(rdev); + u32 valread; int ret; dev_dbg(rdev_get_dev(rdev), "%s id: %d min_uV: %d max_uV: %d\n", @@ -448,15 +448,16 @@ static int mc13892_sw_regulator_set_voltage(struct regulator_dev *rdev, mc13xxx_lock(priv->mc13xxx); ret = mc13xxx_reg_read(priv->mc13xxx, - mc13892_regulators[id].vsel_reg, &val); + mc13892_regulators[id].vsel_reg, &valread); if (ret) goto err; - hi = val & MC13892_SWITCHERS0_SWxHI; - if (value > 1375) + if (value > 1375000) hi = 1; - if (value < 1100) + else if (value < 1100000) hi = 0; + else + hi = valread & MC13892_SWITCHERS0_SWxHI; if (hi) { value = (value - 1100000) / 25000; @@ -465,8 +466,10 @@ static int mc13892_sw_regulator_set_voltage(struct regulator_dev *rdev, value = (value - 600000) / 25000; mask = mc13892_regulators[id].vsel_mask | MC13892_SWITCHERS0_SWxHI; - ret = mc13xxx_reg_rmw(priv->mc13xxx, mc13892_regulators[id].vsel_reg, - mask, value << mc13892_regulators[id].vsel_shift); + valread = (valread & ~mask) | + (value << mc13892_regulators[id].vsel_shift); + ret = mc13xxx_reg_write(priv->mc13xxx, mc13892_regulators[id].vsel_reg, + valread); err: mc13xxx_unlock(priv->mc13xxx); @@ -521,7 +524,8 @@ static int __devinit mc13892_regulator_probe(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv; struct mc13xxx *mc13892 = dev_get_drvdata(pdev->dev.parent); - struct mc13xxx_regulator_platform_data *pdata = mfd_get_data(pdev); + struct mc13xxx_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); struct mc13xxx_regulator_init_data *init_data; int i, ret; u32 val; @@ -595,7 +599,8 @@ err_free: static int __devexit mc13892_regulator_remove(struct platform_device *pdev) { struct mc13xxx_regulator_priv *priv = platform_get_drvdata(pdev); - struct mc13xxx_regulator_platform_data *pdata = mfd_get_data(pdev); + struct mc13xxx_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); int i; platform_set_drvdata(pdev, NULL); diff --git a/drivers/regulator/mc13xxx-regulator-core.c b/drivers/regulator/mc13xxx-regulator-core.c index 2bb5de1f242..bc27ab13637 100644 --- a/drivers/regulator/mc13xxx-regulator-core.c +++ b/drivers/regulator/mc13xxx-regulator-core.c @@ -174,7 +174,7 @@ static int mc13xxx_regulator_get_voltage(struct regulator_dev *rdev) dev_dbg(rdev_get_dev(rdev), "%s id: %d val: %d\n", __func__, id, val); - BUG_ON(val > mc13xxx_regulators[id].desc.n_voltages); + BUG_ON(val >= mc13xxx_regulators[id].desc.n_voltages); return mc13xxx_regulators[id].voltages[val]; } diff --git a/drivers/regulator/tps6105x-regulator.c b/drivers/regulator/tps6105x-regulator.c index 1661499feda..1011873896d 100644 --- a/drivers/regulator/tps6105x-regulator.c +++ b/drivers/regulator/tps6105x-regulator.c @@ -137,7 +137,7 @@ static struct regulator_desc tps6105x_regulator_desc = { */ static int __devinit tps6105x_regulator_probe(struct platform_device *pdev) { - struct tps6105x *tps6105x = mfd_get_data(pdev); + struct tps6105x *tps6105x = dev_get_platdata(&pdev->dev); struct tps6105x_platform_data *pdata = tps6105x->pdata; int ret; @@ -158,13 +158,14 @@ static int __devinit tps6105x_regulator_probe(struct platform_device *pdev) "failed to register regulator\n"); return ret; } + platform_set_drvdata(pdev, tps6105x); return 0; } static int __devexit tps6105x_regulator_remove(struct platform_device *pdev) { - struct tps6105x *tps6105x = platform_get_drvdata(pdev); + struct tps6105x *tps6105x = dev_get_platdata(&pdev->dev); regulator_unregister(tps6105x->regulator); return 0; } diff --git a/drivers/regulator/tps65023-regulator.c b/drivers/regulator/tps65023-regulator.c index 60a7ca5409e..fbddc15e181 100644 --- a/drivers/regulator/tps65023-regulator.c +++ b/drivers/regulator/tps65023-regulator.c @@ -466,7 +466,6 @@ static struct regulator_ops tps65023_ldo_ops = { static int __devinit tps_65023_probe(struct i2c_client *client, const struct i2c_device_id *id) { - static int desc_id; const struct tps_info *info = (void *)id->driver_data; struct regulator_init_data *init_data; struct regulator_dev *rdev; @@ -499,7 +498,7 @@ static int __devinit tps_65023_probe(struct i2c_client *client, tps->info[i] = info; tps->desc[i].name = info->name; - tps->desc[i].id = desc_id++; + tps->desc[i].id = i; tps->desc[i].n_voltages = num_voltages[i]; tps->desc[i].ops = (i > TPS65023_DCDC_3 ? &tps65023_ldo_ops : &tps65023_dcdc_ops); diff --git a/drivers/regulator/tps6507x-regulator.c b/drivers/regulator/tps6507x-regulator.c index 06475529059..bfffabc21ed 100644 --- a/drivers/regulator/tps6507x-regulator.c +++ b/drivers/regulator/tps6507x-regulator.c @@ -553,7 +553,6 @@ static __devinit int tps6507x_pmic_probe(struct platform_device *pdev) { struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent); - static int desc_id; struct tps_info *info = &tps6507x_pmic_regs[0]; struct regulator_init_data *init_data; struct regulator_dev *rdev; @@ -598,7 +597,7 @@ int tps6507x_pmic_probe(struct platform_device *pdev) } tps->desc[i].name = info->name; - tps->desc[i].id = desc_id++; + tps->desc[i].id = i; tps->desc[i].n_voltages = num_voltages[i]; tps->desc[i].ops = (i > TPS6507X_DCDC_3 ? &tps6507x_pmic_ldo_ops : &tps6507x_pmic_dcdc_ops); diff --git a/drivers/regulator/tps65910-regulator.c b/drivers/regulator/tps65910-regulator.c new file mode 100644 index 00000000000..55dd4e6650d --- /dev/null +++ b/drivers/regulator/tps65910-regulator.c @@ -0,0 +1,993 @@ +/* + * tps65910.c -- TI tps65910 + * + * Copyright 2010 Texas Instruments Inc. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Jorge Eduardo Candelaria <jedu@slimlogic.co.uk> + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/mfd/tps65910.h> + +#define TPS65910_REG_VRTC 0 +#define TPS65910_REG_VIO 1 +#define TPS65910_REG_VDD1 2 +#define TPS65910_REG_VDD2 3 +#define TPS65910_REG_VDD3 4 +#define TPS65910_REG_VDIG1 5 +#define TPS65910_REG_VDIG2 6 +#define TPS65910_REG_VPLL 7 +#define TPS65910_REG_VDAC 8 +#define TPS65910_REG_VAUX1 9 +#define TPS65910_REG_VAUX2 10 +#define TPS65910_REG_VAUX33 11 +#define TPS65910_REG_VMMC 12 + +#define TPS65911_REG_VDDCTRL 4 +#define TPS65911_REG_LDO1 5 +#define TPS65911_REG_LDO2 6 +#define TPS65911_REG_LDO3 7 +#define TPS65911_REG_LDO4 8 +#define TPS65911_REG_LDO5 9 +#define TPS65911_REG_LDO6 10 +#define TPS65911_REG_LDO7 11 +#define TPS65911_REG_LDO8 12 + +#define TPS65910_NUM_REGULATOR 13 +#define TPS65910_SUPPLY_STATE_ENABLED 0x1 + +/* supported VIO voltages in milivolts */ +static const u16 VIO_VSEL_table[] = { + 1500, 1800, 2500, 3300, +}; + +/* VSEL tables for TPS65910 specific LDOs and dcdc's */ + +/* supported VDD3 voltages in milivolts */ +static const u16 VDD3_VSEL_table[] = { + 5000, +}; + +/* supported VDIG1 voltages in milivolts */ +static const u16 VDIG1_VSEL_table[] = { + 1200, 1500, 1800, 2700, +}; + +/* supported VDIG2 voltages in milivolts */ +static const u16 VDIG2_VSEL_table[] = { + 1000, 1100, 1200, 1800, +}; + +/* supported VPLL voltages in milivolts */ +static const u16 VPLL_VSEL_table[] = { + 1000, 1100, 1800, 2500, +}; + +/* supported VDAC voltages in milivolts */ +static const u16 VDAC_VSEL_table[] = { + 1800, 2600, 2800, 2850, +}; + +/* supported VAUX1 voltages in milivolts */ +static const u16 VAUX1_VSEL_table[] = { + 1800, 2500, 2800, 2850, +}; + +/* supported VAUX2 voltages in milivolts */ +static const u16 VAUX2_VSEL_table[] = { + 1800, 2800, 2900, 3300, +}; + +/* supported VAUX33 voltages in milivolts */ +static const u16 VAUX33_VSEL_table[] = { + 1800, 2000, 2800, 3300, +}; + +/* supported VMMC voltages in milivolts */ +static const u16 VMMC_VSEL_table[] = { + 1800, 2800, 3000, 3300, +}; + +struct tps_info { + const char *name; + unsigned min_uV; + unsigned max_uV; + u8 table_len; + const u16 *table; +}; + +static struct tps_info tps65910_regs[] = { + { + .name = "VRTC", + }, + { + .name = "VIO", + .min_uV = 1500000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VIO_VSEL_table), + .table = VIO_VSEL_table, + }, + { + .name = "VDD1", + .min_uV = 600000, + .max_uV = 4500000, + }, + { + .name = "VDD2", + .min_uV = 600000, + .max_uV = 4500000, + }, + { + .name = "VDD3", + .min_uV = 5000000, + .max_uV = 5000000, + .table_len = ARRAY_SIZE(VDD3_VSEL_table), + .table = VDD3_VSEL_table, + }, + { + .name = "VDIG1", + .min_uV = 1200000, + .max_uV = 2700000, + .table_len = ARRAY_SIZE(VDIG1_VSEL_table), + .table = VDIG1_VSEL_table, + }, + { + .name = "VDIG2", + .min_uV = 1000000, + .max_uV = 1800000, + .table_len = ARRAY_SIZE(VDIG2_VSEL_table), + .table = VDIG2_VSEL_table, + }, + { + .name = "VPLL", + .min_uV = 1000000, + .max_uV = 2500000, + .table_len = ARRAY_SIZE(VPLL_VSEL_table), + .table = VPLL_VSEL_table, + }, + { + .name = "VDAC", + .min_uV = 1800000, + .max_uV = 2850000, + .table_len = ARRAY_SIZE(VDAC_VSEL_table), + .table = VDAC_VSEL_table, + }, + { + .name = "VAUX1", + .min_uV = 1800000, + .max_uV = 2850000, + .table_len = ARRAY_SIZE(VAUX1_VSEL_table), + .table = VAUX1_VSEL_table, + }, + { + .name = "VAUX2", + .min_uV = 1800000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VAUX2_VSEL_table), + .table = VAUX2_VSEL_table, + }, + { + .name = "VAUX33", + .min_uV = 1800000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VAUX33_VSEL_table), + .table = VAUX33_VSEL_table, + }, + { + .name = "VMMC", + .min_uV = 1800000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VMMC_VSEL_table), + .table = VMMC_VSEL_table, + }, +}; + +static struct tps_info tps65911_regs[] = { + { + .name = "VIO", + .min_uV = 1500000, + .max_uV = 3300000, + .table_len = ARRAY_SIZE(VIO_VSEL_table), + .table = VIO_VSEL_table, + }, + { + .name = "VDD1", + .min_uV = 600000, + .max_uV = 4500000, + }, + { + .name = "VDD2", + .min_uV = 600000, + .max_uV = 4500000, + }, + { + .name = "VDDCTRL", + .min_uV = 600000, + .max_uV = 1400000, + }, + { + .name = "LDO1", + .min_uV = 1000000, + .max_uV = 3300000, + }, + { + .name = "LDO2", + .min_uV = 1000000, + .max_uV = 3300000, + }, + { + .name = "LDO3", + .min_uV = 1000000, + .max_uV = 3300000, + }, + { + .name = "LDO4", + .min_uV = 1000000, + .max_uV = 3300000, + }, + { + .name = "LDO5", + .min_uV = 1000000, + .max_uV = 3300000, + }, + { + .name = "LDO6", + .min_uV = 1000000, + .max_uV = 3300000, + }, + { + .name = "LDO7", + .min_uV = 1000000, + .max_uV = 3300000, + }, + { + .name = "LDO8", + .min_uV = 1000000, + .max_uV = 3300000, + }, +}; + +struct tps65910_reg { + struct regulator_desc desc[TPS65910_NUM_REGULATOR]; + struct tps65910 *mfd; + struct regulator_dev *rdev[TPS65910_NUM_REGULATOR]; + struct tps_info *info[TPS65910_NUM_REGULATOR]; + struct mutex mutex; + int mode; + int (*get_ctrl_reg)(int); +}; + +static inline int tps65910_read(struct tps65910_reg *pmic, u8 reg) +{ + u8 val; + int err; + + err = pmic->mfd->read(pmic->mfd, reg, 1, &val); + if (err) + return err; + + return val; +} + +static inline int tps65910_write(struct tps65910_reg *pmic, u8 reg, u8 val) +{ + return pmic->mfd->write(pmic->mfd, reg, 1, &val); +} + +static int tps65910_modify_bits(struct tps65910_reg *pmic, u8 reg, + u8 set_mask, u8 clear_mask) +{ + int err, data; + + mutex_lock(&pmic->mutex); + + data = tps65910_read(pmic, reg); + if (data < 0) { + dev_err(pmic->mfd->dev, "Read from reg 0x%x failed\n", reg); + err = data; + goto out; + } + + data &= ~clear_mask; + data |= set_mask; + err = tps65910_write(pmic, reg, data); + if (err) + dev_err(pmic->mfd->dev, "Write for reg 0x%x failed\n", reg); + +out: + mutex_unlock(&pmic->mutex); + return err; +} + +static int tps65910_reg_read(struct tps65910_reg *pmic, u8 reg) +{ + int data; + + mutex_lock(&pmic->mutex); + + data = tps65910_read(pmic, reg); + if (data < 0) + dev_err(pmic->mfd->dev, "Read from reg 0x%x failed\n", reg); + + mutex_unlock(&pmic->mutex); + return data; +} + +static int tps65910_reg_write(struct tps65910_reg *pmic, u8 reg, u8 val) +{ + int err; + + mutex_lock(&pmic->mutex); + + err = tps65910_write(pmic, reg, val); + if (err < 0) + dev_err(pmic->mfd->dev, "Write for reg 0x%x failed\n", reg); + + mutex_unlock(&pmic->mutex); + return err; +} + +static int tps65910_get_ctrl_register(int id) +{ + switch (id) { + case TPS65910_REG_VRTC: + return TPS65910_VRTC; + case TPS65910_REG_VIO: + return TPS65910_VIO; + case TPS65910_REG_VDD1: + return TPS65910_VDD1; + case TPS65910_REG_VDD2: + return TPS65910_VDD2; + case TPS65910_REG_VDD3: + return TPS65910_VDD3; + case TPS65910_REG_VDIG1: + return TPS65910_VDIG1; + case TPS65910_REG_VDIG2: + return TPS65910_VDIG2; + case TPS65910_REG_VPLL: + return TPS65910_VPLL; + case TPS65910_REG_VDAC: + return TPS65910_VDAC; + case TPS65910_REG_VAUX1: + return TPS65910_VAUX1; + case TPS65910_REG_VAUX2: + return TPS65910_VAUX2; + case TPS65910_REG_VAUX33: + return TPS65910_VAUX33; + case TPS65910_REG_VMMC: + return TPS65910_VMMC; + default: + return -EINVAL; + } +} + +static int tps65911_get_ctrl_register(int id) +{ + switch (id) { + case TPS65910_REG_VRTC: + return TPS65910_VRTC; + case TPS65910_REG_VIO: + return TPS65910_VIO; + case TPS65910_REG_VDD1: + return TPS65910_VDD1; + case TPS65910_REG_VDD2: + return TPS65910_VDD2; + case TPS65911_REG_VDDCTRL: + return TPS65911_VDDCTRL; + case TPS65911_REG_LDO1: + return TPS65911_LDO1; + case TPS65911_REG_LDO2: + return TPS65911_LDO2; + case TPS65911_REG_LDO3: + return TPS65911_LDO3; + case TPS65911_REG_LDO4: + return TPS65911_LDO4; + case TPS65911_REG_LDO5: + return TPS65911_LDO5; + case TPS65911_REG_LDO6: + return TPS65911_LDO6; + case TPS65911_REG_LDO7: + return TPS65911_LDO7; + case TPS65911_REG_LDO8: + return TPS65911_LDO8; + default: + return -EINVAL; + } +} + +static int tps65910_is_enabled(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int reg, value, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + value = tps65910_reg_read(pmic, reg); + if (value < 0) + return value; + + return value & TPS65910_SUPPLY_STATE_ENABLED; +} + +static int tps65910_enable(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + struct tps65910 *mfd = pmic->mfd; + int reg, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + return tps65910_set_bits(mfd, reg, TPS65910_SUPPLY_STATE_ENABLED); +} + +static int tps65910_disable(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + struct tps65910 *mfd = pmic->mfd; + int reg, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + return tps65910_clear_bits(mfd, reg, TPS65910_SUPPLY_STATE_ENABLED); +} + + +static int tps65910_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + struct tps65910 *mfd = pmic->mfd; + int reg, value, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + return tps65910_modify_bits(pmic, reg, LDO_ST_ON_BIT, + LDO_ST_MODE_BIT); + case REGULATOR_MODE_IDLE: + value = LDO_ST_ON_BIT | LDO_ST_MODE_BIT; + return tps65910_set_bits(mfd, reg, value); + case REGULATOR_MODE_STANDBY: + return tps65910_clear_bits(mfd, reg, LDO_ST_ON_BIT); + } + + return -EINVAL; +} + +static unsigned int tps65910_get_mode(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int reg, value, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + value = tps65910_reg_read(pmic, reg); + if (value < 0) + return value; + + if (value & LDO_ST_ON_BIT) + return REGULATOR_MODE_STANDBY; + else if (value & LDO_ST_MODE_BIT) + return REGULATOR_MODE_IDLE; + else + return REGULATOR_MODE_NORMAL; +} + +static int tps65910_get_voltage_dcdc(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int id = rdev_get_id(dev), voltage = 0; + int opvsel = 0, srvsel = 0, vselmax = 0, mult = 0, sr = 0; + + switch (id) { + case TPS65910_REG_VDD1: + opvsel = tps65910_reg_read(pmic, TPS65910_VDD1_OP); + mult = tps65910_reg_read(pmic, TPS65910_VDD1); + mult = (mult & VDD1_VGAIN_SEL_MASK) >> VDD1_VGAIN_SEL_SHIFT; + srvsel = tps65910_reg_read(pmic, TPS65910_VDD1_SR); + sr = opvsel & VDD1_OP_CMD_MASK; + opvsel &= VDD1_OP_SEL_MASK; + srvsel &= VDD1_SR_SEL_MASK; + vselmax = 75; + break; + case TPS65910_REG_VDD2: + opvsel = tps65910_reg_read(pmic, TPS65910_VDD2_OP); + mult = tps65910_reg_read(pmic, TPS65910_VDD2); + mult = (mult & VDD2_VGAIN_SEL_MASK) >> VDD2_VGAIN_SEL_SHIFT; + srvsel = tps65910_reg_read(pmic, TPS65910_VDD2_SR); + sr = opvsel & VDD2_OP_CMD_MASK; + opvsel &= VDD2_OP_SEL_MASK; + srvsel &= VDD2_SR_SEL_MASK; + vselmax = 75; + break; + case TPS65911_REG_VDDCTRL: + opvsel = tps65910_reg_read(pmic, TPS65911_VDDCTRL_OP); + srvsel = tps65910_reg_read(pmic, TPS65911_VDDCTRL_SR); + sr = opvsel & VDDCTRL_OP_CMD_MASK; + opvsel &= VDDCTRL_OP_SEL_MASK; + srvsel &= VDDCTRL_SR_SEL_MASK; + vselmax = 64; + break; + } + + /* multiplier 0 == 1 but 2,3 normal */ + if (!mult) + mult=1; + + if (sr) { + /* normalise to valid range */ + if (srvsel < 3) + srvsel = 3; + if (srvsel > vselmax) + srvsel = vselmax; + srvsel -= 3; + + voltage = (srvsel * VDD1_2_OFFSET + VDD1_2_MIN_VOLT) * 100; + } else { + + /* normalise to valid range*/ + if (opvsel < 3) + opvsel = 3; + if (opvsel > vselmax) + opvsel = vselmax; + opvsel -= 3; + + voltage = (opvsel * VDD1_2_OFFSET + VDD1_2_MIN_VOLT) * 100; + } + + voltage *= mult; + + return voltage; +} + +static int tps65910_get_voltage(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int reg, value, id = rdev_get_id(dev), voltage = 0; + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + value = tps65910_reg_read(pmic, reg); + if (value < 0) + return value; + + switch (id) { + case TPS65910_REG_VIO: + case TPS65910_REG_VDIG1: + case TPS65910_REG_VDIG2: + case TPS65910_REG_VPLL: + case TPS65910_REG_VDAC: + case TPS65910_REG_VAUX1: + case TPS65910_REG_VAUX2: + case TPS65910_REG_VAUX33: + case TPS65910_REG_VMMC: + value &= LDO_SEL_MASK; + value >>= LDO_SEL_SHIFT; + break; + default: + return -EINVAL; + } + + voltage = pmic->info[id]->table[value] * 1000; + + return voltage; +} + +static int tps65910_get_voltage_vdd3(struct regulator_dev *dev) +{ + return 5 * 1000 * 1000; +} + +static int tps65911_get_voltage(struct regulator_dev *dev) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int step_mv, id = rdev_get_id(dev); + u8 value, reg; + + reg = pmic->get_ctrl_reg(id); + + value = tps65910_reg_read(pmic, reg); + + switch (id) { + case TPS65911_REG_LDO1: + case TPS65911_REG_LDO2: + case TPS65911_REG_LDO4: + value &= LDO1_SEL_MASK; + value >>= LDO_SEL_SHIFT; + /* The first 5 values of the selector correspond to 1V */ + if (value < 5) + value = 0; + else + value -= 4; + + step_mv = 50; + break; + case TPS65911_REG_LDO3: + case TPS65911_REG_LDO5: + case TPS65911_REG_LDO6: + case TPS65911_REG_LDO7: + case TPS65911_REG_LDO8: + value &= LDO3_SEL_MASK; + value >>= LDO_SEL_SHIFT; + /* The first 3 values of the selector correspond to 1V */ + if (value < 3) + value = 0; + else + value -= 2; + + step_mv = 100; + break; + case TPS65910_REG_VIO: + return pmic->info[id]->table[value] * 1000; + break; + default: + return -EINVAL; + } + + return (LDO_MIN_VOLT + value * step_mv) * 1000; +} + +static int tps65910_set_voltage_dcdc(struct regulator_dev *dev, + unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int id = rdev_get_id(dev), vsel; + int dcdc_mult = 0; + + switch (id) { + case TPS65910_REG_VDD1: + dcdc_mult = (selector / VDD1_2_NUM_VOLTS) + 1; + if (dcdc_mult == 1) + dcdc_mult--; + vsel = (selector % VDD1_2_NUM_VOLTS) + 3; + + tps65910_modify_bits(pmic, TPS65910_VDD1, + (dcdc_mult << VDD1_VGAIN_SEL_SHIFT), + VDD1_VGAIN_SEL_MASK); + tps65910_reg_write(pmic, TPS65910_VDD1_OP, vsel); + break; + case TPS65910_REG_VDD2: + dcdc_mult = (selector / VDD1_2_NUM_VOLTS) + 1; + if (dcdc_mult == 1) + dcdc_mult--; + vsel = (selector % VDD1_2_NUM_VOLTS) + 3; + + tps65910_modify_bits(pmic, TPS65910_VDD2, + (dcdc_mult << VDD2_VGAIN_SEL_SHIFT), + VDD1_VGAIN_SEL_MASK); + tps65910_reg_write(pmic, TPS65910_VDD2_OP, vsel); + break; + case TPS65911_REG_VDDCTRL: + vsel = selector; + tps65910_reg_write(pmic, TPS65911_VDDCTRL_OP, vsel); + } + + return 0; +} + +static int tps65910_set_voltage(struct regulator_dev *dev, unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int reg, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + switch (id) { + case TPS65910_REG_VIO: + case TPS65910_REG_VDIG1: + case TPS65910_REG_VDIG2: + case TPS65910_REG_VPLL: + case TPS65910_REG_VDAC: + case TPS65910_REG_VAUX1: + case TPS65910_REG_VAUX2: + case TPS65910_REG_VAUX33: + case TPS65910_REG_VMMC: + return tps65910_modify_bits(pmic, reg, + (selector << LDO_SEL_SHIFT), LDO_SEL_MASK); + } + + return -EINVAL; +} + +static int tps65911_set_voltage(struct regulator_dev *dev, unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int reg, id = rdev_get_id(dev); + + reg = pmic->get_ctrl_reg(id); + if (reg < 0) + return reg; + + switch (id) { + case TPS65911_REG_LDO1: + case TPS65911_REG_LDO2: + case TPS65911_REG_LDO4: + return tps65910_modify_bits(pmic, reg, + (selector << LDO_SEL_SHIFT), LDO1_SEL_MASK); + case TPS65911_REG_LDO3: + case TPS65911_REG_LDO5: + case TPS65911_REG_LDO6: + case TPS65911_REG_LDO7: + case TPS65911_REG_LDO8: + case TPS65910_REG_VIO: + return tps65910_modify_bits(pmic, reg, + (selector << LDO_SEL_SHIFT), LDO3_SEL_MASK); + } + + return -EINVAL; +} + + +static int tps65910_list_voltage_dcdc(struct regulator_dev *dev, + unsigned selector) +{ + int volt, mult = 1, id = rdev_get_id(dev); + + switch (id) { + case TPS65910_REG_VDD1: + case TPS65910_REG_VDD2: + mult = (selector / VDD1_2_NUM_VOLTS) + 1; + volt = VDD1_2_MIN_VOLT + + (selector % VDD1_2_NUM_VOLTS) * VDD1_2_OFFSET; + case TPS65911_REG_VDDCTRL: + volt = VDDCTRL_MIN_VOLT + (selector * VDDCTRL_OFFSET); + } + + return volt * 100 * mult; +} + +static int tps65910_list_voltage(struct regulator_dev *dev, + unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int id = rdev_get_id(dev), voltage; + + if (id < TPS65910_REG_VIO || id > TPS65910_REG_VMMC) + return -EINVAL; + + if (selector >= pmic->info[id]->table_len) + return -EINVAL; + else + voltage = pmic->info[id]->table[selector] * 1000; + + return voltage; +} + +static int tps65911_list_voltage(struct regulator_dev *dev, unsigned selector) +{ + struct tps65910_reg *pmic = rdev_get_drvdata(dev); + int step_mv = 0, id = rdev_get_id(dev); + + switch(id) { + case TPS65911_REG_LDO1: + case TPS65911_REG_LDO2: + case TPS65911_REG_LDO4: + /* The first 5 values of the selector correspond to 1V */ + if (selector < 5) + selector = 0; + else + selector -= 4; + + step_mv = 50; + break; + case TPS65911_REG_LDO3: + case TPS65911_REG_LDO5: + case TPS65911_REG_LDO6: + case TPS65911_REG_LDO7: + case TPS65911_REG_LDO8: + /* The first 3 values of the selector correspond to 1V */ + if (selector < 3) + selector = 0; + else + selector -= 2; + + step_mv = 100; + break; + case TPS65910_REG_VIO: + return pmic->info[id]->table[selector] * 1000; + default: + return -EINVAL; + } + + return (LDO_MIN_VOLT + selector * step_mv) * 1000; +} + +/* Regulator ops (except VRTC) */ +static struct regulator_ops tps65910_ops_dcdc = { + .is_enabled = tps65910_is_enabled, + .enable = tps65910_enable, + .disable = tps65910_disable, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage = tps65910_get_voltage_dcdc, + .set_voltage_sel = tps65910_set_voltage_dcdc, + .list_voltage = tps65910_list_voltage_dcdc, +}; + +static struct regulator_ops tps65910_ops_vdd3 = { + .is_enabled = tps65910_is_enabled, + .enable = tps65910_enable, + .disable = tps65910_disable, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage = tps65910_get_voltage_vdd3, + .list_voltage = tps65910_list_voltage, +}; + +static struct regulator_ops tps65910_ops = { + .is_enabled = tps65910_is_enabled, + .enable = tps65910_enable, + .disable = tps65910_disable, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage = tps65910_get_voltage, + .set_voltage_sel = tps65910_set_voltage, + .list_voltage = tps65910_list_voltage, +}; + +static struct regulator_ops tps65911_ops = { + .is_enabled = tps65910_is_enabled, + .enable = tps65910_enable, + .disable = tps65910_disable, + .set_mode = tps65910_set_mode, + .get_mode = tps65910_get_mode, + .get_voltage = tps65911_get_voltage, + .set_voltage_sel = tps65911_set_voltage, + .list_voltage = tps65911_list_voltage, +}; + +static __devinit int tps65910_probe(struct platform_device *pdev) +{ + struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent); + struct tps_info *info; + struct regulator_init_data *reg_data; + struct regulator_dev *rdev; + struct tps65910_reg *pmic; + struct tps65910_board *pmic_plat_data; + int i, err; + + pmic_plat_data = dev_get_platdata(tps65910->dev); + if (!pmic_plat_data) + return -EINVAL; + + reg_data = pmic_plat_data->tps65910_pmic_init_data; + + pmic = kzalloc(sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + mutex_init(&pmic->mutex); + pmic->mfd = tps65910; + platform_set_drvdata(pdev, pmic); + + /* Give control of all register to control port */ + tps65910_set_bits(pmic->mfd, TPS65910_DEVCTRL, + DEVCTRL_SR_CTL_I2C_SEL_MASK); + + switch(tps65910_chip_id(tps65910)) { + case TPS65910: + pmic->get_ctrl_reg = &tps65910_get_ctrl_register; + info = tps65910_regs; + case TPS65911: + pmic->get_ctrl_reg = &tps65911_get_ctrl_register; + info = tps65911_regs; + default: + pr_err("Invalid tps chip version\n"); + return -ENODEV; + } + + for (i = 0; i < TPS65910_NUM_REGULATOR; i++, info++, reg_data++) { + /* Register the regulators */ + pmic->info[i] = info; + + pmic->desc[i].name = info->name; + pmic->desc[i].id = i; + pmic->desc[i].n_voltages = info->table_len; + + if (i == TPS65910_REG_VDD1 || i == TPS65910_REG_VDD2) { + pmic->desc[i].ops = &tps65910_ops_dcdc; + } else if (i == TPS65910_REG_VDD3) { + if (tps65910_chip_id(tps65910) == TPS65910) + pmic->desc[i].ops = &tps65910_ops_vdd3; + else + pmic->desc[i].ops = &tps65910_ops_dcdc; + } else { + if (tps65910_chip_id(tps65910) == TPS65910) + pmic->desc[i].ops = &tps65910_ops; + else + pmic->desc[i].ops = &tps65911_ops; + } + + pmic->desc[i].type = REGULATOR_VOLTAGE; + pmic->desc[i].owner = THIS_MODULE; + + rdev = regulator_register(&pmic->desc[i], + tps65910->dev, reg_data, pmic); + if (IS_ERR(rdev)) { + dev_err(tps65910->dev, + "failed to register %s regulator\n", + pdev->name); + err = PTR_ERR(rdev); + goto err; + } + + /* Save regulator for cleanup */ + pmic->rdev[i] = rdev; + } + return 0; + +err: + while (--i >= 0) + regulator_unregister(pmic->rdev[i]); + + kfree(pmic); + return err; +} + +static int __devexit tps65910_remove(struct platform_device *pdev) +{ + struct tps65910_reg *tps65910_reg = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < TPS65910_NUM_REGULATOR; i++) + regulator_unregister(tps65910_reg->rdev[i]); + + kfree(tps65910_reg); + return 0; +} + +static struct platform_driver tps65910_driver = { + .driver = { + .name = "tps65910-pmic", + .owner = THIS_MODULE, + }, + .probe = tps65910_probe, + .remove = __devexit_p(tps65910_remove), +}; + +static int __init tps65910_init(void) +{ + return platform_driver_register(&tps65910_driver); +} +subsys_initcall(tps65910_init); + +static void __exit tps65910_cleanup(void) +{ + platform_driver_unregister(&tps65910_driver); +} +module_exit(tps65910_cleanup); + +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("TPS6507x voltage regulator driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tps65910-pmic"); diff --git a/drivers/regulator/twl-regulator.c b/drivers/regulator/twl-regulator.c index 6a292852a35..87fe0f75a56 100644 --- a/drivers/regulator/twl-regulator.c +++ b/drivers/regulator/twl-regulator.c @@ -51,8 +51,13 @@ struct twlreg_info { u16 min_mV; u16 max_mV; + u8 flags; + /* used by regulator core */ struct regulator_desc desc; + + /* chip specific features */ + unsigned long features; }; @@ -70,12 +75,35 @@ struct twlreg_info { #define VREG_TRANS 1 #define VREG_STATE 2 #define VREG_VOLTAGE 3 +#define VREG_VOLTAGE_SMPS 4 /* TWL6030 Misc register offsets */ #define VREG_BC_ALL 1 #define VREG_BC_REF 2 #define VREG_BC_PROC 3 #define VREG_BC_CLK_RST 4 +/* TWL6030 LDO register values for CFG_STATE */ +#define TWL6030_CFG_STATE_OFF 0x00 +#define TWL6030_CFG_STATE_ON 0x01 +#define TWL6030_CFG_STATE_OFF2 0x02 +#define TWL6030_CFG_STATE_SLEEP 0x03 +#define TWL6030_CFG_STATE_GRP_SHIFT 5 +#define TWL6030_CFG_STATE_APP_SHIFT 2 +#define TWL6030_CFG_STATE_APP_MASK (0x03 << TWL6030_CFG_STATE_APP_SHIFT) +#define TWL6030_CFG_STATE_APP(v) (((v) & TWL6030_CFG_STATE_APP_MASK) >>\ + TWL6030_CFG_STATE_APP_SHIFT) + +/* Flags for SMPS Voltage reading */ +#define SMPS_OFFSET_EN BIT(0) +#define SMPS_EXTENDED_EN BIT(1) + +/* twl6025 SMPS EPROM values */ +#define TWL6030_SMPS_OFFSET 0xB0 +#define TWL6030_SMPS_MULT 0xB3 +#define SMPS_MULTOFFSET_SMPS4 BIT(0) +#define SMPS_MULTOFFSET_VIO BIT(1) +#define SMPS_MULTOFFSET_SMPS3 BIT(6) + static inline int twlreg_read(struct twlreg_info *info, unsigned slave_subgp, unsigned offset) { @@ -118,21 +146,38 @@ static int twlreg_grp(struct regulator_dev *rdev) #define P2_GRP_6030 BIT(1) /* "peripherals" */ #define P1_GRP_6030 BIT(0) /* CPU/Linux */ -static int twlreg_is_enabled(struct regulator_dev *rdev) +static int twl4030reg_is_enabled(struct regulator_dev *rdev) { int state = twlreg_grp(rdev); if (state < 0) return state; - if (twl_class_is_4030()) - state &= P1_GRP_4030; + return state & P1_GRP_4030; +} + +static int twl6030reg_is_enabled(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0, val; + + if (!(twl_class_is_6030() && (info->features & TWL6025_SUBCLASS))) + grp = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_GRP); + if (grp < 0) + return grp; + + if (!(twl_class_is_6030() && (info->features & TWL6025_SUBCLASS))) + grp &= P1_GRP_6030; else - state &= P1_GRP_6030; - return state; + grp = 1; + + val = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_STATE); + val = TWL6030_CFG_STATE_APP(val); + + return grp && (val == TWL6030_CFG_STATE_ON); } -static int twlreg_enable(struct regulator_dev *rdev) +static int twl4030reg_enable(struct regulator_dev *rdev) { struct twlreg_info *info = rdev_get_drvdata(rdev); int grp; @@ -142,10 +187,7 @@ static int twlreg_enable(struct regulator_dev *rdev) if (grp < 0) return grp; - if (twl_class_is_4030()) - grp |= P1_GRP_4030; - else - grp |= P1_GRP_6030; + grp |= P1_GRP_4030; ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_GRP, grp); @@ -154,29 +196,63 @@ static int twlreg_enable(struct regulator_dev *rdev) return ret; } -static int twlreg_disable(struct regulator_dev *rdev) +static int twl6030reg_enable(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0; + int ret; + + if (!(twl_class_is_6030() && (info->features & TWL6025_SUBCLASS))) + grp = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_GRP); + if (grp < 0) + return grp; + + ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_STATE, + grp << TWL6030_CFG_STATE_GRP_SHIFT | + TWL6030_CFG_STATE_ON); + + udelay(info->delay); + + return ret; +} + +static int twl4030reg_disable(struct regulator_dev *rdev) { struct twlreg_info *info = rdev_get_drvdata(rdev); int grp; + int ret; grp = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_GRP); if (grp < 0) return grp; - if (twl_class_is_4030()) - grp &= ~(P1_GRP_4030 | P2_GRP_4030 | P3_GRP_4030); - else - grp &= ~(P1_GRP_6030 | P2_GRP_6030 | P3_GRP_6030); + grp &= ~(P1_GRP_4030 | P2_GRP_4030 | P3_GRP_4030); - return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_GRP, grp); + ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_GRP, grp); + + return ret; } -static int twlreg_get_status(struct regulator_dev *rdev) +static int twl6030reg_disable(struct regulator_dev *rdev) { - int state = twlreg_grp(rdev); + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0; + int ret; + + if (!(twl_class_is_6030() && (info->features & TWL6025_SUBCLASS))) + grp = P1_GRP_6030 | P2_GRP_6030 | P3_GRP_6030; + + /* For 6030, set the off state for all grps enabled */ + ret = twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_STATE, + (grp) << TWL6030_CFG_STATE_GRP_SHIFT | + TWL6030_CFG_STATE_OFF); + + return ret; +} - if (twl_class_is_6030()) - return 0; /* FIXME return for 6030 regulator */ +static int twl4030reg_get_status(struct regulator_dev *rdev) +{ + int state = twlreg_grp(rdev); if (state < 0) return state; @@ -190,15 +266,39 @@ static int twlreg_get_status(struct regulator_dev *rdev) : REGULATOR_STATUS_STANDBY; } -static int twlreg_set_mode(struct regulator_dev *rdev, unsigned mode) +static int twl6030reg_get_status(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int val; + + val = twlreg_grp(rdev); + if (val < 0) + return val; + + val = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_STATE); + + switch (TWL6030_CFG_STATE_APP(val)) { + case TWL6030_CFG_STATE_ON: + return REGULATOR_STATUS_NORMAL; + + case TWL6030_CFG_STATE_SLEEP: + return REGULATOR_STATUS_STANDBY; + + case TWL6030_CFG_STATE_OFF: + case TWL6030_CFG_STATE_OFF2: + default: + break; + } + + return REGULATOR_STATUS_OFF; +} + +static int twl4030reg_set_mode(struct regulator_dev *rdev, unsigned mode) { struct twlreg_info *info = rdev_get_drvdata(rdev); unsigned message; int status; - if (twl_class_is_6030()) - return 0; /* FIXME return for 6030 regulator */ - /* We can only set the mode through state machine commands... */ switch (mode) { case REGULATOR_MODE_NORMAL: @@ -227,6 +327,36 @@ static int twlreg_set_mode(struct regulator_dev *rdev, unsigned mode) message & 0xff, TWL4030_PM_MASTER_PB_WORD_LSB); } +static int twl6030reg_set_mode(struct regulator_dev *rdev, unsigned mode) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int grp = 0; + int val; + + if (!(twl_class_is_6030() && (info->features & TWL6025_SUBCLASS))) + grp = twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_GRP); + + if (grp < 0) + return grp; + + /* Compose the state register settings */ + val = grp << TWL6030_CFG_STATE_GRP_SHIFT; + /* We can only set the mode through state machine commands... */ + switch (mode) { + case REGULATOR_MODE_NORMAL: + val |= TWL6030_CFG_STATE_ON; + break; + case REGULATOR_MODE_STANDBY: + val |= TWL6030_CFG_STATE_SLEEP; + break; + + default: + return -EINVAL; + } + + return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_STATE, val); +} + /*----------------------------------------------------------------------*/ /* @@ -375,13 +505,13 @@ static struct regulator_ops twl4030ldo_ops = { .set_voltage = twl4030ldo_set_voltage, .get_voltage = twl4030ldo_get_voltage, - .enable = twlreg_enable, - .disable = twlreg_disable, - .is_enabled = twlreg_is_enabled, + .enable = twl4030reg_enable, + .disable = twl4030reg_disable, + .is_enabled = twl4030reg_is_enabled, - .set_mode = twlreg_set_mode, + .set_mode = twl4030reg_set_mode, - .get_status = twlreg_get_status, + .get_status = twl4030reg_get_status, }; static int twl6030ldo_list_voltage(struct regulator_dev *rdev, unsigned index) @@ -433,13 +563,13 @@ static struct regulator_ops twl6030ldo_ops = { .set_voltage = twl6030ldo_set_voltage, .get_voltage = twl6030ldo_get_voltage, - .enable = twlreg_enable, - .disable = twlreg_disable, - .is_enabled = twlreg_is_enabled, + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, - .set_mode = twlreg_set_mode, + .set_mode = twl6030reg_set_mode, - .get_status = twlreg_get_status, + .get_status = twl6030reg_get_status, }; /*----------------------------------------------------------------------*/ @@ -461,25 +591,242 @@ static int twlfixed_get_voltage(struct regulator_dev *rdev) return info->min_mV * 1000; } -static struct regulator_ops twlfixed_ops = { +static struct regulator_ops twl4030fixed_ops = { + .list_voltage = twlfixed_list_voltage, + + .get_voltage = twlfixed_get_voltage, + + .enable = twl4030reg_enable, + .disable = twl4030reg_disable, + .is_enabled = twl4030reg_is_enabled, + + .set_mode = twl4030reg_set_mode, + + .get_status = twl4030reg_get_status, +}; + +static struct regulator_ops twl6030fixed_ops = { .list_voltage = twlfixed_list_voltage, .get_voltage = twlfixed_get_voltage, - .enable = twlreg_enable, - .disable = twlreg_disable, - .is_enabled = twlreg_is_enabled, + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, - .set_mode = twlreg_set_mode, + .set_mode = twl6030reg_set_mode, - .get_status = twlreg_get_status, + .get_status = twl6030reg_get_status, }; static struct regulator_ops twl6030_fixed_resource = { - .enable = twlreg_enable, - .disable = twlreg_disable, - .is_enabled = twlreg_is_enabled, - .get_status = twlreg_get_status, + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, + .get_status = twl6030reg_get_status, +}; + +/* + * SMPS status and control + */ + +static int twl6030smps_list_voltage(struct regulator_dev *rdev, unsigned index) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + int voltage = 0; + + switch (info->flags) { + case SMPS_OFFSET_EN: + voltage = 100000; + /* fall through */ + case 0: + switch (index) { + case 0: + voltage = 0; + break; + case 58: + voltage = 1350 * 1000; + break; + case 59: + voltage = 1500 * 1000; + break; + case 60: + voltage = 1800 * 1000; + break; + case 61: + voltage = 1900 * 1000; + break; + case 62: + voltage = 2100 * 1000; + break; + default: + voltage += (600000 + (12500 * (index - 1))); + } + break; + case SMPS_EXTENDED_EN: + switch (index) { + case 0: + voltage = 0; + break; + case 58: + voltage = 2084 * 1000; + break; + case 59: + voltage = 2315 * 1000; + break; + case 60: + voltage = 2778 * 1000; + break; + case 61: + voltage = 2932 * 1000; + break; + case 62: + voltage = 3241 * 1000; + break; + default: + voltage = (1852000 + (38600 * (index - 1))); + } + break; + case SMPS_OFFSET_EN | SMPS_EXTENDED_EN: + switch (index) { + case 0: + voltage = 0; + break; + case 58: + voltage = 4167 * 1000; + break; + case 59: + voltage = 2315 * 1000; + break; + case 60: + voltage = 2778 * 1000; + break; + case 61: + voltage = 2932 * 1000; + break; + case 62: + voltage = 3241 * 1000; + break; + default: + voltage = (2161000 + (38600 * (index - 1))); + } + break; + } + + return voltage; +} + +static int +twl6030smps_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned int *selector) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + int vsel = 0; + + switch (info->flags) { + case 0: + if (min_uV == 0) + vsel = 0; + else if ((min_uV >= 600000) && (max_uV <= 1300000)) { + vsel = (min_uV - 600000) / 125; + if (vsel % 100) + vsel += 100; + vsel /= 100; + vsel++; + } + /* Values 1..57 for vsel are linear and can be calculated + * values 58..62 are non linear. + */ + else if ((min_uV > 1900000) && (max_uV >= 2100000)) + vsel = 62; + else if ((min_uV > 1800000) && (max_uV >= 1900000)) + vsel = 61; + else if ((min_uV > 1500000) && (max_uV >= 1800000)) + vsel = 60; + else if ((min_uV > 1350000) && (max_uV >= 1500000)) + vsel = 59; + else if ((min_uV > 1300000) && (max_uV >= 1350000)) + vsel = 58; + else + return -EINVAL; + break; + case SMPS_OFFSET_EN: + if (min_uV == 0) + vsel = 0; + else if ((min_uV >= 700000) && (max_uV <= 1420000)) { + vsel = (min_uV - 700000) / 125; + if (vsel % 100) + vsel += 100; + vsel /= 100; + vsel++; + } + /* Values 1..57 for vsel are linear and can be calculated + * values 58..62 are non linear. + */ + else if ((min_uV > 1900000) && (max_uV >= 2100000)) + vsel = 62; + else if ((min_uV > 1800000) && (max_uV >= 1900000)) + vsel = 61; + else if ((min_uV > 1350000) && (max_uV >= 1800000)) + vsel = 60; + else if ((min_uV > 1350000) && (max_uV >= 1500000)) + vsel = 59; + else if ((min_uV > 1300000) && (max_uV >= 1350000)) + vsel = 58; + else + return -EINVAL; + break; + case SMPS_EXTENDED_EN: + if (min_uV == 0) + vsel = 0; + else if ((min_uV >= 1852000) && (max_uV <= 4013600)) { + vsel = (min_uV - 1852000) / 386; + if (vsel % 100) + vsel += 100; + vsel /= 100; + vsel++; + } + break; + case SMPS_OFFSET_EN|SMPS_EXTENDED_EN: + if (min_uV == 0) + vsel = 0; + else if ((min_uV >= 2161000) && (max_uV <= 4321000)) { + vsel = (min_uV - 1852000) / 386; + if (vsel % 100) + vsel += 100; + vsel /= 100; + vsel++; + } + break; + } + + *selector = vsel; + + return twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE_SMPS, + vsel); +} + +static int twl6030smps_get_voltage_sel(struct regulator_dev *rdev) +{ + struct twlreg_info *info = rdev_get_drvdata(rdev); + + return twlreg_read(info, TWL_MODULE_PM_RECEIVER, VREG_VOLTAGE_SMPS); +} + +static struct regulator_ops twlsmps_ops = { + .list_voltage = twl6030smps_list_voltage, + + .set_voltage = twl6030smps_set_voltage, + .get_voltage_sel = twl6030smps_get_voltage_sel, + + .enable = twl6030reg_enable, + .disable = twl6030reg_disable, + .is_enabled = twl6030reg_is_enabled, + + .set_mode = twl6030reg_set_mode, + + .get_status = twl6030reg_get_status, }; /*----------------------------------------------------------------------*/ @@ -487,11 +834,10 @@ static struct regulator_ops twl6030_fixed_resource = { #define TWL4030_FIXED_LDO(label, offset, mVolts, num, turnon_delay, \ remap_conf) \ TWL_FIXED_LDO(label, offset, mVolts, num, turnon_delay, \ - remap_conf, TWL4030) -#define TWL6030_FIXED_LDO(label, offset, mVolts, num, turnon_delay, \ - remap_conf) \ + remap_conf, TWL4030, twl4030fixed_ops) +#define TWL6030_FIXED_LDO(label, offset, mVolts, num, turnon_delay) \ TWL_FIXED_LDO(label, offset, mVolts, num, turnon_delay, \ - remap_conf, TWL6030) + 0x0, TWL6030, twl6030fixed_ops) #define TWL4030_ADJUSTABLE_LDO(label, offset, num, turnon_delay, remap_conf) { \ .base = offset, \ @@ -510,13 +856,11 @@ static struct regulator_ops twl6030_fixed_resource = { }, \ } -#define TWL6030_ADJUSTABLE_LDO(label, offset, min_mVolts, max_mVolts, num, \ - remap_conf) { \ +#define TWL6030_ADJUSTABLE_LDO(label, offset, min_mVolts, max_mVolts, num) { \ .base = offset, \ .id = num, \ .min_mV = min_mVolts, \ .max_mV = max_mVolts, \ - .remap = remap_conf, \ .desc = { \ .name = #label, \ .id = TWL6030_REG_##label, \ @@ -527,9 +871,23 @@ static struct regulator_ops twl6030_fixed_resource = { }, \ } +#define TWL6025_ADJUSTABLE_LDO(label, offset, min_mVolts, max_mVolts, num) { \ + .base = offset, \ + .id = num, \ + .min_mV = min_mVolts, \ + .max_mV = max_mVolts, \ + .desc = { \ + .name = #label, \ + .id = TWL6025_REG_##label, \ + .n_voltages = ((max_mVolts - min_mVolts)/100) + 1, \ + .ops = &twl6030ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + } #define TWL_FIXED_LDO(label, offset, mVolts, num, turnon_delay, remap_conf, \ - family) { \ + family, operations) { \ .base = offset, \ .id = num, \ .min_mV = mVolts, \ @@ -539,17 +897,16 @@ static struct regulator_ops twl6030_fixed_resource = { .name = #label, \ .id = family##_REG_##label, \ .n_voltages = 1, \ - .ops = &twlfixed_ops, \ + .ops = &operations, \ .type = REGULATOR_VOLTAGE, \ .owner = THIS_MODULE, \ }, \ } -#define TWL6030_FIXED_RESOURCE(label, offset, num, turnon_delay, remap_conf) { \ +#define TWL6030_FIXED_RESOURCE(label, offset, num, turnon_delay) { \ .base = offset, \ .id = num, \ .delay = turnon_delay, \ - .remap = remap_conf, \ .desc = { \ .name = #label, \ .id = TWL6030_REG_##label, \ @@ -559,6 +916,21 @@ static struct regulator_ops twl6030_fixed_resource = { }, \ } +#define TWL6025_ADJUSTABLE_SMPS(label, offset, num) { \ + .base = offset, \ + .id = num, \ + .min_mV = 600, \ + .max_mV = 2100, \ + .desc = { \ + .name = #label, \ + .id = TWL6025_REG_##label, \ + .n_voltages = 63, \ + .ops = &twlsmps_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + } + /* * We list regulators here if systems need some level of * software control over them after boot. @@ -589,19 +961,52 @@ static struct twlreg_info twl_regs[] = { /* 6030 REG with base as PMC Slave Misc : 0x0030 */ /* Turnon-delay and remap configuration values for 6030 are not verified since the specification is not public */ - TWL6030_ADJUSTABLE_LDO(VAUX1_6030, 0x54, 1000, 3300, 1, 0x21), - TWL6030_ADJUSTABLE_LDO(VAUX2_6030, 0x58, 1000, 3300, 2, 0x21), - TWL6030_ADJUSTABLE_LDO(VAUX3_6030, 0x5c, 1000, 3300, 3, 0x21), - TWL6030_ADJUSTABLE_LDO(VMMC, 0x68, 1000, 3300, 4, 0x21), - TWL6030_ADJUSTABLE_LDO(VPP, 0x6c, 1000, 3300, 5, 0x21), - TWL6030_ADJUSTABLE_LDO(VUSIM, 0x74, 1000, 3300, 7, 0x21), - TWL6030_FIXED_LDO(VANA, 0x50, 2100, 15, 0, 0x21), - TWL6030_FIXED_LDO(VCXIO, 0x60, 1800, 16, 0, 0x21), - TWL6030_FIXED_LDO(VDAC, 0x64, 1800, 17, 0, 0x21), - TWL6030_FIXED_LDO(VUSB, 0x70, 3300, 18, 0, 0x21), - TWL6030_FIXED_RESOURCE(CLK32KG, 0x8C, 48, 0, 0x21), + TWL6030_ADJUSTABLE_LDO(VAUX1_6030, 0x54, 1000, 3300, 1), + TWL6030_ADJUSTABLE_LDO(VAUX2_6030, 0x58, 1000, 3300, 2), + TWL6030_ADJUSTABLE_LDO(VAUX3_6030, 0x5c, 1000, 3300, 3), + TWL6030_ADJUSTABLE_LDO(VMMC, 0x68, 1000, 3300, 4), + TWL6030_ADJUSTABLE_LDO(VPP, 0x6c, 1000, 3300, 5), + TWL6030_ADJUSTABLE_LDO(VUSIM, 0x74, 1000, 3300, 7), + TWL6030_FIXED_LDO(VANA, 0x50, 2100, 15, 0), + TWL6030_FIXED_LDO(VCXIO, 0x60, 1800, 16, 0), + TWL6030_FIXED_LDO(VDAC, 0x64, 1800, 17, 0), + TWL6030_FIXED_LDO(VUSB, 0x70, 3300, 18, 0), + TWL6030_FIXED_RESOURCE(CLK32KG, 0x8C, 48, 0), + + /* 6025 are renamed compared to 6030 versions */ + TWL6025_ADJUSTABLE_LDO(LDO2, 0x54, 1000, 3300, 1), + TWL6025_ADJUSTABLE_LDO(LDO4, 0x58, 1000, 3300, 2), + TWL6025_ADJUSTABLE_LDO(LDO3, 0x5c, 1000, 3300, 3), + TWL6025_ADJUSTABLE_LDO(LDO5, 0x68, 1000, 3300, 4), + TWL6025_ADJUSTABLE_LDO(LDO1, 0x6c, 1000, 3300, 5), + TWL6025_ADJUSTABLE_LDO(LDO7, 0x74, 1000, 3300, 7), + TWL6025_ADJUSTABLE_LDO(LDO6, 0x60, 1000, 3300, 16), + TWL6025_ADJUSTABLE_LDO(LDOLN, 0x64, 1000, 3300, 17), + TWL6025_ADJUSTABLE_LDO(LDOUSB, 0x70, 1000, 3300, 18), + + TWL6025_ADJUSTABLE_SMPS(SMPS3, 0x34, 1), + TWL6025_ADJUSTABLE_SMPS(SMPS4, 0x10, 2), + TWL6025_ADJUSTABLE_SMPS(VIO, 0x16, 3), }; +static u8 twl_get_smps_offset(void) +{ + u8 value; + + twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &value, + TWL6030_SMPS_OFFSET); + return value; +} + +static u8 twl_get_smps_mult(void) +{ + u8 value; + + twl_i2c_read_u8(TWL_MODULE_PM_RECEIVER, &value, + TWL6030_SMPS_MULT); + return value; +} + static int __devinit twlreg_probe(struct platform_device *pdev) { int i; @@ -623,6 +1028,9 @@ static int __devinit twlreg_probe(struct platform_device *pdev) if (!initdata) return -EINVAL; + /* copy the features into regulator data */ + info->features = (unsigned long)initdata->driver_data; + /* Constrain board-specific capabilities according to what * this driver and the chip itself can actually do. */ @@ -645,6 +1053,27 @@ static int __devinit twlreg_probe(struct platform_device *pdev) break; } + switch (pdev->id) { + case TWL6025_REG_SMPS3: + if (twl_get_smps_mult() & SMPS_MULTOFFSET_SMPS3) + info->flags |= SMPS_EXTENDED_EN; + if (twl_get_smps_offset() & SMPS_MULTOFFSET_SMPS3) + info->flags |= SMPS_OFFSET_EN; + break; + case TWL6025_REG_SMPS4: + if (twl_get_smps_mult() & SMPS_MULTOFFSET_SMPS4) + info->flags |= SMPS_EXTENDED_EN; + if (twl_get_smps_offset() & SMPS_MULTOFFSET_SMPS4) + info->flags |= SMPS_OFFSET_EN; + break; + case TWL6025_REG_VIO: + if (twl_get_smps_mult() & SMPS_MULTOFFSET_VIO) + info->flags |= SMPS_EXTENDED_EN; + if (twl_get_smps_offset() & SMPS_MULTOFFSET_VIO) + info->flags |= SMPS_OFFSET_EN; + break; + } + rdev = regulator_register(&info->desc, &pdev->dev, initdata, info); if (IS_ERR(rdev)) { dev_err(&pdev->dev, "can't register %s, %ld\n", @@ -653,7 +1082,8 @@ static int __devinit twlreg_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, rdev); - twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_REMAP, + if (twl_class_is_4030()) + twlreg_write(info, TWL_MODULE_PM_RECEIVER, VREG_REMAP, info->remap); /* NOTE: many regulators support short-circuit IRQs (presentable diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c index e93453b1b97..a0982e80985 100644 --- a/drivers/regulator/wm831x-dcdc.c +++ b/drivers/regulator/wm831x-dcdc.c @@ -600,7 +600,6 @@ err: static __devexit int wm831x_buckv_remove(struct platform_device *pdev) { struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); - struct wm831x *wm831x = dcdc->wm831x; platform_set_drvdata(pdev, NULL); @@ -776,7 +775,6 @@ err: static __devexit int wm831x_buckp_remove(struct platform_device *pdev) { struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev); - struct wm831x *wm831x = dcdc->wm831x; platform_set_drvdata(pdev, NULL); diff --git a/drivers/regulator/wm8400-regulator.c b/drivers/regulator/wm8400-regulator.c index b42d01cef35..0f12c70bebc 100644 --- a/drivers/regulator/wm8400-regulator.c +++ b/drivers/regulator/wm8400-regulator.c @@ -55,7 +55,7 @@ static int wm8400_ldo_list_voltage(struct regulator_dev *dev, return 1600000 + ((selector - 14) * 100000); } -static int wm8400_ldo_get_voltage(struct regulator_dev *dev) +static int wm8400_ldo_get_voltage_sel(struct regulator_dev *dev) { struct wm8400 *wm8400 = rdev_get_drvdata(dev); u16 val; @@ -63,7 +63,7 @@ static int wm8400_ldo_get_voltage(struct regulator_dev *dev) val = wm8400_reg_read(wm8400, WM8400_LDO1_CONTROL + rdev_get_id(dev)); val &= WM8400_LDO1_VSEL_MASK; - return wm8400_ldo_list_voltage(dev, val); + return val; } static int wm8400_ldo_set_voltage(struct regulator_dev *dev, @@ -104,7 +104,7 @@ static struct regulator_ops wm8400_ldo_ops = { .enable = wm8400_ldo_enable, .disable = wm8400_ldo_disable, .list_voltage = wm8400_ldo_list_voltage, - .get_voltage = wm8400_ldo_get_voltage, + .get_voltage_sel = wm8400_ldo_get_voltage_sel, .set_voltage = wm8400_ldo_set_voltage, }; @@ -145,7 +145,7 @@ static int wm8400_dcdc_list_voltage(struct regulator_dev *dev, return 850000 + (selector * 25000); } -static int wm8400_dcdc_get_voltage(struct regulator_dev *dev) +static int wm8400_dcdc_get_voltage_sel(struct regulator_dev *dev) { struct wm8400 *wm8400 = rdev_get_drvdata(dev); u16 val; @@ -154,7 +154,7 @@ static int wm8400_dcdc_get_voltage(struct regulator_dev *dev) val = wm8400_reg_read(wm8400, WM8400_DCDC1_CONTROL_1 + offset); val &= WM8400_DC1_VSEL_MASK; - return 850000 + (25000 * val); + return val; } static int wm8400_dcdc_set_voltage(struct regulator_dev *dev, @@ -261,7 +261,7 @@ static struct regulator_ops wm8400_dcdc_ops = { .enable = wm8400_dcdc_enable, .disable = wm8400_dcdc_disable, .list_voltage = wm8400_dcdc_list_voltage, - .get_voltage = wm8400_dcdc_get_voltage, + .get_voltage_sel = wm8400_dcdc_get_voltage_sel, .set_voltage = wm8400_dcdc_set_voltage, .get_mode = wm8400_dcdc_get_mode, .set_mode = wm8400_dcdc_set_mode, diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index b8f4e9e66cd..f822e13dc04 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -125,6 +125,16 @@ comment "I2C RTC drivers" if I2C +config RTC_DRV_88PM860X + tristate "Marvell 88PM860x" + depends on RTC_CLASS && I2C && MFD_88PM860X + help + If you say yes here you get support for RTC function in Marvell + 88PM860x chips. + + This driver can also be built as a module. If so, the module + will be called rtc-88pm860x. + config RTC_DRV_DS1307 tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025" help @@ -351,12 +361,39 @@ config RTC_DRV_RX8025 This driver can also be built as a module. If so, the module will be called rtc-rx8025. +config RTC_DRV_EM3027 + tristate "EM Microelectronic EM3027" + help + If you say yes here you get support for the EM + Microelectronic EM3027 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-em3027. + +config RTC_DRV_RV3029C2 + tristate "Micro Crystal RTC" + help + If you say yes here you get support for the Micro Crystal + RV3029-C2 RTC chips. + + This driver can also be built as a module. If so, the module + will be called rtc-rv3029c2. + endif # I2C comment "SPI RTC drivers" if SPI_MASTER +config RTC_DRV_M41T93 + tristate "ST M41T93" + help + If you say yes here you will get support for the + ST M41T93 SPI RTC chip. + + This driver can also be built as a module. If so, the module + will be called rtc-m41t93. + config RTC_DRV_M41T94 tristate "ST M41T94" help @@ -645,6 +682,14 @@ config RTC_DRV_WM8350 This driver can also be built as a module. If so, the module will be called "rtc-wm8350". +config RTC_DRV_SPEAR + tristate "SPEAR ST RTC" + depends on PLAT_SPEAR + default y + help + If you say Y here you will get support for the RTC found on + spear + config RTC_DRV_PCF50633 depends on MFD_PCF50633 tristate "NXP PCF50633 RTC" @@ -874,6 +919,13 @@ config RTC_DRV_PXA This RTC driver uses PXA RTC registers available since pxa27x series (RDxR, RYxR) instead of legacy RCNR, RTAR. +config RTC_DRV_VT8500 + tristate "VIA/WonderMedia 85xx SoC RTC" + depends on ARCH_VT8500 + help + If you say Y here you will get access to the real time clock + built into your VIA VT8500 SoC or its relatives. + config RTC_DRV_SUN4V bool "SUN4V Hypervisor RTC" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 9574748d1c7..213d725f16d 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -15,6 +15,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o # Keep the list ordered. +obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-88pm860x.o obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o @@ -43,6 +44,7 @@ obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o obj-$(CONFIG_RTC_DRV_DS3232) += rtc-ds3232.o obj-$(CONFIG_RTC_DRV_DS3234) += rtc-ds3234.o obj-$(CONFIG_RTC_DRV_EFI) += rtc-efi.o +obj-$(CONFIG_RTC_DRV_EM3027) += rtc-em3027.o obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o @@ -52,6 +54,7 @@ obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o obj-$(CONFIG_RTC_DRV_LPC32XX) += rtc-lpc32xx.o obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o +obj-$(CONFIG_RTC_DRV_M41T93) += rtc-m41t93.o obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o @@ -81,12 +84,14 @@ obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o +obj-$(CONFIG_RTC_DRV_RV3029C2) += rtc-rv3029c2.o obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o obj-$(CONFIG_RTC_DRV_SA1100) += rtc-sa1100.o obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o +obj-$(CONFIG_RTC_DRV_SPEAR) += rtc-spear.o obj-$(CONFIG_RTC_DRV_STARFIRE) += rtc-starfire.o obj-$(CONFIG_RTC_DRV_STK17TA8) += rtc-stk17ta8.o obj-$(CONFIG_RTC_DRV_STMP) += rtc-stmp3xxx.o @@ -98,6 +103,7 @@ obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl.o obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o +obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o diff --git a/drivers/rtc/rtc-88pm860x.c b/drivers/rtc/rtc-88pm860x.c new file mode 100644 index 00000000000..64b847b7f97 --- /dev/null +++ b/drivers/rtc/rtc-88pm860x.c @@ -0,0 +1,427 @@ +/* + * Real Time Clock driver for Marvell 88PM860x PMIC + * + * Copyright (c) 2010 Marvell International Ltd. + * Author: Haojian Zhuang <haojian.zhuang@marvell.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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/rtc.h> +#include <linux/delay.h> +#include <linux/mfd/core.h> +#include <linux/mfd/88pm860x.h> + +#define VRTC_CALIBRATION + +struct pm860x_rtc_info { + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct rtc_device *rtc_dev; + struct device *dev; + struct delayed_work calib_work; + + int irq; + int vrtc; + int (*sync)(unsigned int ticks); +}; + +#define REG_VRTC_MEAS1 0x7D + +#define REG0_ADDR 0xB0 +#define REG1_ADDR 0xB2 +#define REG2_ADDR 0xB4 +#define REG3_ADDR 0xB6 + +#define REG0_DATA 0xB1 +#define REG1_DATA 0xB3 +#define REG2_DATA 0xB5 +#define REG3_DATA 0xB7 + +/* bit definitions of Measurement Enable Register 2 (0x51) */ +#define MEAS2_VRTC (1 << 0) + +/* bit definitions of RTC Register 1 (0xA0) */ +#define ALARM_EN (1 << 3) +#define ALARM_WAKEUP (1 << 4) +#define ALARM (1 << 5) +#define RTC1_USE_XO (1 << 7) + +#define VRTC_CALIB_INTERVAL (HZ * 60 * 10) /* 10 minutes */ + +static irqreturn_t rtc_update_handler(int irq, void *data) +{ + struct pm860x_rtc_info *info = (struct pm860x_rtc_info *)data; + int mask; + + mask = ALARM | ALARM_WAKEUP; + pm860x_set_bits(info->i2c, PM8607_RTC1, mask | ALARM_EN, mask); + rtc_update_irq(info->rtc_dev, 1, RTC_AF); + return IRQ_HANDLED; +} + +static int pm860x_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + + if (enabled) + pm860x_set_bits(info->i2c, PM8607_RTC1, ALARM, ALARM); + else + pm860x_set_bits(info->i2c, PM8607_RTC1, ALARM, 0); + return 0; +} + +/* + * Calculate the next alarm time given the requested alarm time mask + * and the current time. + */ +static void rtc_next_alarm_time(struct rtc_time *next, struct rtc_time *now, + struct rtc_time *alrm) +{ + unsigned long next_time; + unsigned long now_time; + + next->tm_year = now->tm_year; + next->tm_mon = now->tm_mon; + next->tm_mday = now->tm_mday; + next->tm_hour = alrm->tm_hour; + next->tm_min = alrm->tm_min; + next->tm_sec = alrm->tm_sec; + + rtc_tm_to_time(now, &now_time); + rtc_tm_to_time(next, &next_time); + + if (next_time < now_time) { + /* Advance one day */ + next_time += 60 * 60 * 24; + rtc_time_to_tm(next_time, next); + } +} + +static int pm860x_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[8]; + unsigned long ticks, base, data; + + pm860x_page_bulk_read(info->i2c, REG0_ADDR, 8, buf); + dev_dbg(info->dev, "%x-%x-%x-%x-%x-%x-%x-%x\n", buf[0], buf[1], + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + base = (buf[1] << 24) | (buf[3] << 16) | (buf[5] << 8) | buf[7]; + + /* load 32-bit read-only counter */ + pm860x_bulk_read(info->i2c, PM8607_RTC_COUNTER1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + ticks = base + data; + dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + rtc_time_to_tm(ticks, tm); + + return 0; +} + +static int pm860x_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[4]; + unsigned long ticks, base, data; + + if ((tm->tm_year < 70) || (tm->tm_year > 138)) { + dev_dbg(info->dev, "Set time %d out of range. " + "Please set time between 1970 to 2038.\n", + 1900 + tm->tm_year); + return -EINVAL; + } + rtc_tm_to_time(tm, &ticks); + + /* load 32-bit read-only counter */ + pm860x_bulk_read(info->i2c, PM8607_RTC_COUNTER1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + base = ticks - data; + dev_dbg(info->dev, "set base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + pm860x_page_reg_write(info->i2c, REG0_DATA, (base >> 24) & 0xFF); + pm860x_page_reg_write(info->i2c, REG1_DATA, (base >> 16) & 0xFF); + pm860x_page_reg_write(info->i2c, REG2_DATA, (base >> 8) & 0xFF); + pm860x_page_reg_write(info->i2c, REG3_DATA, base & 0xFF); + + if (info->sync) + info->sync(ticks); + return 0; +} + +static int pm860x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + unsigned char buf[8]; + unsigned long ticks, base, data; + int ret; + + pm860x_page_bulk_read(info->i2c, REG0_ADDR, 8, buf); + dev_dbg(info->dev, "%x-%x-%x-%x-%x-%x-%x-%x\n", buf[0], buf[1], + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + base = (buf[1] << 24) | (buf[3] << 16) | (buf[5] << 8) | buf[7]; + + pm860x_bulk_read(info->i2c, PM8607_RTC_EXPIRE1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + ticks = base + data; + dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + rtc_time_to_tm(ticks, &alrm->time); + ret = pm860x_reg_read(info->i2c, PM8607_RTC1); + alrm->enabled = (ret & ALARM_EN) ? 1 : 0; + alrm->pending = (ret & (ALARM | ALARM_WAKEUP)) ? 1 : 0; + return 0; +} + +static int pm860x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct pm860x_rtc_info *info = dev_get_drvdata(dev); + struct rtc_time now_tm, alarm_tm; + unsigned long ticks, base, data; + unsigned char buf[8]; + int mask; + + pm860x_set_bits(info->i2c, PM8607_RTC1, ALARM_EN, 0); + + pm860x_page_bulk_read(info->i2c, REG0_ADDR, 8, buf); + dev_dbg(info->dev, "%x-%x-%x-%x-%x-%x-%x-%x\n", buf[0], buf[1], + buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]); + base = (buf[1] << 24) | (buf[3] << 16) | (buf[5] << 8) | buf[7]; + + /* load 32-bit read-only counter */ + pm860x_bulk_read(info->i2c, PM8607_RTC_COUNTER1, 4, buf); + data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + ticks = base + data; + dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n", + base, data, ticks); + + rtc_time_to_tm(ticks, &now_tm); + rtc_next_alarm_time(&alarm_tm, &now_tm, &alrm->time); + /* get new ticks for alarm in 24 hours */ + rtc_tm_to_time(&alarm_tm, &ticks); + data = ticks - base; + + buf[0] = data & 0xff; + buf[1] = (data >> 8) & 0xff; + buf[2] = (data >> 16) & 0xff; + buf[3] = (data >> 24) & 0xff; + pm860x_bulk_write(info->i2c, PM8607_RTC_EXPIRE1, 4, buf); + if (alrm->enabled) { + mask = ALARM | ALARM_WAKEUP | ALARM_EN; + pm860x_set_bits(info->i2c, PM8607_RTC1, mask, mask); + } else { + mask = ALARM | ALARM_WAKEUP | ALARM_EN; + pm860x_set_bits(info->i2c, PM8607_RTC1, mask, + ALARM | ALARM_WAKEUP); + } + return 0; +} + +static const struct rtc_class_ops pm860x_rtc_ops = { + .read_time = pm860x_rtc_read_time, + .set_time = pm860x_rtc_set_time, + .read_alarm = pm860x_rtc_read_alarm, + .set_alarm = pm860x_rtc_set_alarm, + .alarm_irq_enable = pm860x_rtc_alarm_irq_enable, +}; + +#ifdef VRTC_CALIBRATION +static void calibrate_vrtc_work(struct work_struct *work) +{ + struct pm860x_rtc_info *info = container_of(work, + struct pm860x_rtc_info, calib_work.work); + unsigned char buf[2]; + unsigned int sum, data, mean, vrtc_set; + int i; + + for (i = 0, sum = 0; i < 16; i++) { + msleep(100); + pm860x_bulk_read(info->i2c, REG_VRTC_MEAS1, 2, buf); + data = (buf[0] << 4) | buf[1]; + data = (data * 5400) >> 12; /* convert to mv */ + sum += data; + } + mean = sum >> 4; + vrtc_set = 2700 + (info->vrtc & 0x3) * 200; + dev_dbg(info->dev, "mean:%d, vrtc_set:%d\n", mean, vrtc_set); + + sum = pm860x_reg_read(info->i2c, PM8607_RTC_MISC1); + data = sum & 0x3; + if ((mean + 200) < vrtc_set) { + /* try higher voltage */ + if (++data == 4) + goto out; + data = (sum & 0xf8) | (data & 0x3); + pm860x_reg_write(info->i2c, PM8607_RTC_MISC1, data); + } else if ((mean - 200) > vrtc_set) { + /* try lower voltage */ + if (data-- == 0) + goto out; + data = (sum & 0xf8) | (data & 0x3); + pm860x_reg_write(info->i2c, PM8607_RTC_MISC1, data); + } else + goto out; + dev_dbg(info->dev, "set 0x%x to RTC_MISC1\n", data); + /* trigger next calibration since VRTC is updated */ + schedule_delayed_work(&info->calib_work, VRTC_CALIB_INTERVAL); + return; +out: + /* disable measurement */ + pm860x_set_bits(info->i2c, PM8607_MEAS_EN2, MEAS2_VRTC, 0); + dev_dbg(info->dev, "finish VRTC calibration\n"); + return; +} +#endif + +static int __devinit pm860x_rtc_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_rtc_pdata *pdata = NULL; + struct pm860x_rtc_info *info; + struct rtc_time tm; + unsigned long ticks = 0; + int ret; + + pdata = pdev->dev.platform_data; + if (pdata == NULL) + dev_warn(&pdev->dev, "No platform data!\n"); + + info = kzalloc(sizeof(struct pm860x_rtc_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + ret = -EINVAL; + goto out; + } + + info->chip = chip; + info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + info->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + ret = request_threaded_irq(info->irq, NULL, rtc_update_handler, + IRQF_ONESHOT, "rtc", info); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + info->irq, ret); + goto out; + } + + /* set addresses of 32-bit base value for RTC time */ + pm860x_page_reg_write(info->i2c, REG0_ADDR, REG0_DATA); + pm860x_page_reg_write(info->i2c, REG1_ADDR, REG1_DATA); + pm860x_page_reg_write(info->i2c, REG2_ADDR, REG2_DATA); + pm860x_page_reg_write(info->i2c, REG3_ADDR, REG3_DATA); + + ret = pm860x_rtc_read_time(&pdev->dev, &tm); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read initial time.\n"); + goto out_rtc; + } + if ((tm.tm_year < 70) || (tm.tm_year > 138)) { + tm.tm_year = 70; + tm.tm_mon = 0; + tm.tm_mday = 1; + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + ret = pm860x_rtc_set_time(&pdev->dev, &tm); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to set initial time.\n"); + goto out_rtc; + } + } + rtc_tm_to_time(&tm, &ticks); + if (pdata && pdata->sync) { + pdata->sync(ticks); + info->sync = pdata->sync; + } + + info->rtc_dev = rtc_device_register("88pm860x-rtc", &pdev->dev, + &pm860x_rtc_ops, THIS_MODULE); + ret = PTR_ERR(info->rtc_dev); + if (IS_ERR(info->rtc_dev)) { + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret); + goto out_rtc; + } + + /* + * enable internal XO instead of internal 3.25MHz clock since it can + * free running in PMIC power-down state. + */ + pm860x_set_bits(info->i2c, PM8607_RTC1, RTC1_USE_XO, RTC1_USE_XO); + +#ifdef VRTC_CALIBRATION + /* <00> -- 2.7V, <01> -- 2.9V, <10> -- 3.1V, <11> -- 3.3V */ + if (pdata && pdata->vrtc) + info->vrtc = pdata->vrtc & 0x3; + else + info->vrtc = 1; + pm860x_set_bits(info->i2c, PM8607_MEAS_EN2, MEAS2_VRTC, MEAS2_VRTC); + + /* calibrate VRTC */ + INIT_DELAYED_WORK(&info->calib_work, calibrate_vrtc_work); + schedule_delayed_work(&info->calib_work, VRTC_CALIB_INTERVAL); +#endif /* VRTC_CALIBRATION */ + return 0; +out_rtc: + free_irq(info->irq, info); +out: + kfree(info); + return ret; +} + +static int __devexit pm860x_rtc_remove(struct platform_device *pdev) +{ + struct pm860x_rtc_info *info = platform_get_drvdata(pdev); + +#ifdef VRTC_CALIBRATION + flush_scheduled_work(); + /* disable measurement */ + pm860x_set_bits(info->i2c, PM8607_MEAS_EN2, MEAS2_VRTC, 0); +#endif /* VRTC_CALIBRATION */ + + platform_set_drvdata(pdev, NULL); + rtc_device_unregister(info->rtc_dev); + free_irq(info->irq, info); + kfree(info); + return 0; +} + +static struct platform_driver pm860x_rtc_driver = { + .driver = { + .name = "88pm860x-rtc", + .owner = THIS_MODULE, + }, + .probe = pm860x_rtc_probe, + .remove = __devexit_p(pm860x_rtc_remove), +}; + +static int __init pm860x_rtc_init(void) +{ + return platform_driver_register(&pm860x_rtc_driver); +} +module_init(pm860x_rtc_init); + +static void __exit pm860x_rtc_exit(void) +{ + platform_driver_unregister(&pm860x_rtc_driver); +} +module_exit(pm860x_rtc_exit); + +MODULE_DESCRIPTION("Marvell 88PM860x RTC driver"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-em3027.c b/drivers/rtc/rtc-em3027.c new file mode 100644 index 00000000000..d8e1c257855 --- /dev/null +++ b/drivers/rtc/rtc-em3027.c @@ -0,0 +1,161 @@ +/* + * An rtc/i2c driver for the EM Microelectronic EM3027 + * Copyright 2011 CompuLab, Ltd. + * + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Based on rtc-ds1672.c by Alessandro Zummo <a.zummo@towertech.it> + * + * 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 <linux/i2c.h> +#include <linux/rtc.h> +#include <linux/bcd.h> + +/* Registers */ +#define EM3027_REG_ON_OFF_CTRL 0x00 +#define EM3027_REG_IRQ_CTRL 0x01 +#define EM3027_REG_IRQ_FLAGS 0x02 +#define EM3027_REG_STATUS 0x03 +#define EM3027_REG_RST_CTRL 0x04 + +#define EM3027_REG_WATCH_SEC 0x08 +#define EM3027_REG_WATCH_MIN 0x09 +#define EM3027_REG_WATCH_HOUR 0x0a +#define EM3027_REG_WATCH_DATE 0x0b +#define EM3027_REG_WATCH_DAY 0x0c +#define EM3027_REG_WATCH_MON 0x0d +#define EM3027_REG_WATCH_YEAR 0x0e + +#define EM3027_REG_ALARM_SEC 0x10 +#define EM3027_REG_ALARM_MIN 0x11 +#define EM3027_REG_ALARM_HOUR 0x12 +#define EM3027_REG_ALARM_DATE 0x13 +#define EM3027_REG_ALARM_DAY 0x14 +#define EM3027_REG_ALARM_MON 0x15 +#define EM3027_REG_ALARM_YEAR 0x16 + +static struct i2c_driver em3027_driver; + +static int em3027_get_time(struct device *dev, struct rtc_time *tm) +{ + struct i2c_client *client = to_i2c_client(dev); + + unsigned char addr = EM3027_REG_WATCH_SEC; + unsigned char buf[7]; + + struct i2c_msg msgs[] = { + {client->addr, 0, 1, &addr}, /* setup read addr */ + {client->addr, I2C_M_RD, 7, buf}, /* read time/date */ + }; + + /* read time/date registers */ + if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) { + dev_err(&client->dev, "%s: read error\n", __func__); + return -EIO; + } + + tm->tm_sec = bcd2bin(buf[0]); + tm->tm_min = bcd2bin(buf[1]); + tm->tm_hour = bcd2bin(buf[2]); + tm->tm_mday = bcd2bin(buf[3]); + tm->tm_wday = bcd2bin(buf[4]); + tm->tm_mon = bcd2bin(buf[5]); + tm->tm_year = bcd2bin(buf[6]) + 100; + + return 0; +} + +static int em3027_set_time(struct device *dev, struct rtc_time *tm) +{ + struct i2c_client *client = to_i2c_client(dev); + unsigned char buf[8]; + + struct i2c_msg msg = { + client->addr, 0, 8, buf, /* write time/date */ + }; + + buf[0] = EM3027_REG_WATCH_SEC; + buf[1] = bin2bcd(tm->tm_sec); + buf[2] = bin2bcd(tm->tm_min); + buf[3] = bin2bcd(tm->tm_hour); + buf[4] = bin2bcd(tm->tm_mday); + buf[5] = bin2bcd(tm->tm_wday); + buf[6] = bin2bcd(tm->tm_mon); + buf[7] = bin2bcd(tm->tm_year % 100); + + /* write time/date registers */ + if ((i2c_transfer(client->adapter, &msg, 1)) != 1) { + dev_err(&client->dev, "%s: write error\n", __func__); + return -EIO; + } + + return 0; +} + +static const struct rtc_class_ops em3027_rtc_ops = { + .read_time = em3027_get_time, + .set_time = em3027_set_time, +}; + +static int em3027_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct rtc_device *rtc; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + rtc = rtc_device_register(em3027_driver.driver.name, &client->dev, + &em3027_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + i2c_set_clientdata(client, rtc); + + return 0; +} + +static int em3027_remove(struct i2c_client *client) +{ + struct rtc_device *rtc = i2c_get_clientdata(client); + + if (rtc) + rtc_device_unregister(rtc); + + return 0; +} + +static struct i2c_device_id em3027_id[] = { + { "em3027", 0 }, + { } +}; + +static struct i2c_driver em3027_driver = { + .driver = { + .name = "rtc-em3027", + }, + .probe = &em3027_probe, + .remove = &em3027_remove, + .id_table = em3027_id, +}; + +static int __init em3027_init(void) +{ + return i2c_add_driver(&em3027_driver); +} + +static void __exit em3027_exit(void) +{ + i2c_del_driver(&em3027_driver); +} + +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("EM Microelectronic EM3027 RTC driver"); +MODULE_LICENSE("GPL"); + +module_init(em3027_init); +module_exit(em3027_exit); diff --git a/drivers/rtc/rtc-m41t93.c b/drivers/rtc/rtc-m41t93.c new file mode 100644 index 00000000000..1a84b3e227d --- /dev/null +++ b/drivers/rtc/rtc-m41t93.c @@ -0,0 +1,225 @@ +/* + * + * Driver for ST M41T93 SPI RTC + * + * (c) 2010 Nikolaus Voss, Weinmann Medical GmbH + * + * 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 <linux/bcd.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/spi/spi.h> + +#define M41T93_REG_SSEC 0 +#define M41T93_REG_ST_SEC 1 +#define M41T93_REG_MIN 2 +#define M41T93_REG_CENT_HOUR 3 +#define M41T93_REG_WDAY 4 +#define M41T93_REG_DAY 5 +#define M41T93_REG_MON 6 +#define M41T93_REG_YEAR 7 + + +#define M41T93_REG_ALM_HOUR_HT 0xc +#define M41T93_REG_FLAGS 0xf + +#define M41T93_FLAG_ST (1 << 7) +#define M41T93_FLAG_OF (1 << 2) +#define M41T93_FLAG_BL (1 << 4) +#define M41T93_FLAG_HT (1 << 6) + +static inline int m41t93_set_reg(struct spi_device *spi, u8 addr, u8 data) +{ + u8 buf[2]; + + /* MSB must be '1' to write */ + buf[0] = addr | 0x80; + buf[1] = data; + + return spi_write(spi, buf, sizeof(buf)); +} + +static int m41t93_set_time(struct device *dev, struct rtc_time *tm) +{ + struct spi_device *spi = to_spi_device(dev); + u8 buf[9] = {0x80}; /* write cmd + 8 data bytes */ + u8 * const data = &buf[1]; /* ptr to first data byte */ + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "write", tm->tm_sec, tm->tm_min, + tm->tm_hour, tm->tm_mday, + tm->tm_mon, tm->tm_year, tm->tm_wday); + + if (tm->tm_year < 100) { + dev_warn(&spi->dev, "unsupported date (before 2000-01-01).\n"); + return -EINVAL; + } + + data[M41T93_REG_SSEC] = 0; + data[M41T93_REG_ST_SEC] = bin2bcd(tm->tm_sec); + data[M41T93_REG_MIN] = bin2bcd(tm->tm_min); + data[M41T93_REG_CENT_HOUR] = bin2bcd(tm->tm_hour) | + ((tm->tm_year/100-1) << 6); + data[M41T93_REG_DAY] = bin2bcd(tm->tm_mday); + data[M41T93_REG_WDAY] = bin2bcd(tm->tm_wday + 1); + data[M41T93_REG_MON] = bin2bcd(tm->tm_mon + 1); + data[M41T93_REG_YEAR] = bin2bcd(tm->tm_year % 100); + + return spi_write(spi, buf, sizeof(buf)); +} + + +static int m41t93_get_time(struct device *dev, struct rtc_time *tm) +{ + struct spi_device *spi = to_spi_device(dev); + const u8 start_addr = 0; + u8 buf[8]; + int century_after_1900; + int tmp; + int ret = 0; + + /* Check status of clock. Two states must be considered: + 1. halt bit (HT) is set: the clock is running but update of readout + registers has been disabled due to power failure. This is normal + case after poweron. Time is valid after resetting HT bit. + 2. oscillator fail bit (OF) is set. Oscillator has be stopped and + time is invalid: + a) OF can be immeditely reset. + b) OF cannot be immediately reset: oscillator has to be restarted. + */ + tmp = spi_w8r8(spi, M41T93_REG_ALM_HOUR_HT); + if (tmp < 0) + return tmp; + + if (tmp & M41T93_FLAG_HT) { + dev_dbg(&spi->dev, "HT bit is set, reenable clock update.\n"); + m41t93_set_reg(spi, M41T93_REG_ALM_HOUR_HT, + tmp & ~M41T93_FLAG_HT); + } + + tmp = spi_w8r8(spi, M41T93_REG_FLAGS); + if (tmp < 0) + return tmp; + + if (tmp & M41T93_FLAG_OF) { + ret = -EINVAL; + dev_warn(&spi->dev, "OF bit is set, resetting.\n"); + m41t93_set_reg(spi, M41T93_REG_FLAGS, tmp & ~M41T93_FLAG_OF); + + tmp = spi_w8r8(spi, M41T93_REG_FLAGS); + if (tmp < 0) + return tmp; + else if (tmp & M41T93_FLAG_OF) { + u8 reset_osc = buf[M41T93_REG_ST_SEC] | M41T93_FLAG_ST; + + dev_warn(&spi->dev, + "OF bit is still set, kickstarting clock.\n"); + m41t93_set_reg(spi, M41T93_REG_ST_SEC, reset_osc); + reset_osc &= ~M41T93_FLAG_ST; + m41t93_set_reg(spi, M41T93_REG_ST_SEC, reset_osc); + } + } + + if (tmp & M41T93_FLAG_BL) + dev_warn(&spi->dev, "BL bit is set, replace battery.\n"); + + /* read actual time/date */ + tmp = spi_write_then_read(spi, &start_addr, 1, buf, sizeof(buf)); + if (tmp < 0) + return tmp; + + tm->tm_sec = bcd2bin(buf[M41T93_REG_ST_SEC]); + tm->tm_min = bcd2bin(buf[M41T93_REG_MIN]); + tm->tm_hour = bcd2bin(buf[M41T93_REG_CENT_HOUR] & 0x3f); + tm->tm_mday = bcd2bin(buf[M41T93_REG_DAY]); + tm->tm_mon = bcd2bin(buf[M41T93_REG_MON]) - 1; + tm->tm_wday = bcd2bin(buf[M41T93_REG_WDAY] & 0x0f) - 1; + + century_after_1900 = (buf[M41T93_REG_CENT_HOUR] >> 6) + 1; + tm->tm_year = bcd2bin(buf[M41T93_REG_YEAR]) + century_after_1900 * 100; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "read", tm->tm_sec, tm->tm_min, + tm->tm_hour, tm->tm_mday, + tm->tm_mon, tm->tm_year, tm->tm_wday); + + return ret < 0 ? ret : rtc_valid_tm(tm); +} + + +static const struct rtc_class_ops m41t93_rtc_ops = { + .read_time = m41t93_get_time, + .set_time = m41t93_set_time, +}; + +static struct spi_driver m41t93_driver; + +static int __devinit m41t93_probe(struct spi_device *spi) +{ + struct rtc_device *rtc; + int res; + + spi->bits_per_word = 8; + spi_setup(spi); + + res = spi_w8r8(spi, M41T93_REG_WDAY); + if (res < 0 || (res & 0xf8) != 0) { + dev_err(&spi->dev, "not found 0x%x.\n", res); + return -ENODEV; + } + + rtc = rtc_device_register(m41t93_driver.driver.name, + &spi->dev, &m41t93_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + dev_set_drvdata(&spi->dev, rtc); + + return 0; +} + + +static int __devexit m41t93_remove(struct spi_device *spi) +{ + struct rtc_device *rtc = platform_get_drvdata(spi); + + if (rtc) + rtc_device_unregister(rtc); + + return 0; +} + +static struct spi_driver m41t93_driver = { + .driver = { + .name = "rtc-m41t93", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = m41t93_probe, + .remove = __devexit_p(m41t93_remove), +}; + +static __init int m41t93_init(void) +{ + return spi_register_driver(&m41t93_driver); +} +module_init(m41t93_init); + +static __exit void m41t93_exit(void) +{ + spi_unregister_driver(&m41t93_driver); +} +module_exit(m41t93_exit); + +MODULE_AUTHOR("Nikolaus Voss <n.voss@weinmann.de>"); +MODULE_DESCRIPTION("Driver for ST M41T93 SPI RTC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:rtc-m41t93"); diff --git a/drivers/rtc/rtc-mrst.c b/drivers/rtc/rtc-mrst.c index b2f096871a9..0cec5650d56 100644 --- a/drivers/rtc/rtc-mrst.c +++ b/drivers/rtc/rtc-mrst.c @@ -380,7 +380,7 @@ cleanup1: cleanup0: dev_set_drvdata(dev, NULL); mrst_rtc.dev = NULL; - release_region(iomem->start, iomem->end + 1 - iomem->start); + release_mem_region(iomem->start, resource_size(iomem)); dev_err(dev, "rtc-mrst: unable to initialise\n"); return retval; } @@ -406,7 +406,7 @@ static void __devexit rtc_mrst_do_remove(struct device *dev) mrst->rtc = NULL; iomem = mrst->iomem; - release_region(iomem->start, iomem->end + 1 - iomem->start); + release_mem_region(iomem->start, resource_size(iomem)); mrst->iomem = NULL; mrst->dev = NULL; diff --git a/drivers/rtc/rtc-mxc.c b/drivers/rtc/rtc-mxc.c index d814417bee8..39e41fbdf08 100644 --- a/drivers/rtc/rtc-mxc.c +++ b/drivers/rtc/rtc-mxc.c @@ -55,12 +55,6 @@ static const u32 PIE_BIT_DEF[MAX_PIE_NUM][2] = { { MAX_PIE_FREQ, RTC_SAM7_BIT }, }; -/* Those are the bits from a classic RTC we want to mimic */ -#define RTC_IRQF 0x80 /* any of the following 3 is active */ -#define RTC_PF 0x40 /* Periodic interrupt */ -#define RTC_AF 0x20 /* Alarm interrupt */ -#define RTC_UF 0x10 /* Update interrupt for 1Hz RTC */ - #define MXC_RTC_TIME 0 #define MXC_RTC_ALARM 1 diff --git a/drivers/rtc/rtc-pcf50633.c b/drivers/rtc/rtc-pcf50633.c index f90c574f9d0..0c423892923 100644 --- a/drivers/rtc/rtc-pcf50633.c +++ b/drivers/rtc/rtc-pcf50633.c @@ -58,7 +58,6 @@ struct pcf50633_time { struct pcf50633_rtc { int alarm_enabled; - int second_enabled; int alarm_pending; struct pcf50633 *pcf; @@ -143,7 +142,7 @@ static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct pcf50633_rtc *rtc; struct pcf50633_time pcf_tm; - int second_masked, alarm_masked, ret = 0; + int alarm_masked, ret = 0; rtc = dev_get_drvdata(dev); @@ -162,11 +161,8 @@ static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm) pcf_tm.time[PCF50633_TI_SEC]); - second_masked = pcf50633_irq_mask_get(rtc->pcf, PCF50633_IRQ_SECOND); alarm_masked = pcf50633_irq_mask_get(rtc->pcf, PCF50633_IRQ_ALARM); - if (!second_masked) - pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_SECOND); if (!alarm_masked) pcf50633_irq_mask(rtc->pcf, PCF50633_IRQ_ALARM); @@ -175,8 +171,6 @@ static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm) PCF50633_TI_EXTENT, &pcf_tm.time[0]); - if (!second_masked) - pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_SECOND); if (!alarm_masked) pcf50633_irq_unmask(rtc->pcf, PCF50633_IRQ_ALARM); @@ -250,15 +244,8 @@ static void pcf50633_rtc_irq(int irq, void *data) { struct pcf50633_rtc *rtc = data; - switch (irq) { - case PCF50633_IRQ_ALARM: - rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF); - rtc->alarm_pending = 1; - break; - case PCF50633_IRQ_SECOND: - rtc_update_irq(rtc->rtc_dev, 1, RTC_UF | RTC_IRQF); - break; - } + rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF); + rtc->alarm_pending = 1; } static int __devinit pcf50633_rtc_probe(struct platform_device *pdev) @@ -282,9 +269,6 @@ static int __devinit pcf50633_rtc_probe(struct platform_device *pdev) pcf50633_register_irq(rtc->pcf, PCF50633_IRQ_ALARM, pcf50633_rtc_irq, rtc); - pcf50633_register_irq(rtc->pcf, PCF50633_IRQ_SECOND, - pcf50633_rtc_irq, rtc); - return 0; } @@ -295,7 +279,6 @@ static int __devexit pcf50633_rtc_remove(struct platform_device *pdev) rtc = platform_get_drvdata(pdev); pcf50633_free_irq(rtc->pcf, PCF50633_IRQ_ALARM); - pcf50633_free_irq(rtc->pcf, PCF50633_IRQ_SECOND); rtc_device_unregister(rtc->rtc_dev); kfree(rtc); diff --git a/drivers/rtc/rtc-rv3029c2.c b/drivers/rtc/rtc-rv3029c2.c new file mode 100644 index 00000000000..ea09ff211dc --- /dev/null +++ b/drivers/rtc/rtc-rv3029c2.c @@ -0,0 +1,454 @@ +/* + * Micro Crystal RV-3029C2 rtc class driver + * + * Author: Gregory Hermant <gregory.hermant@calao-systems.com> + * + * based on previously existing rtc class drivers + * + * 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. + * + * NOTE: Currently this driver only supports the bare minimum for read + * and write the RTC and alarms. The extra features provided by this chip + * (trickle charger, eeprom, T° compensation) are unavailable. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/rtc.h> + +/* Register map */ +/* control section */ +#define RV3029C2_ONOFF_CTRL 0x00 +#define RV3029C2_IRQ_CTRL 0x01 +#define RV3029C2_IRQ_CTRL_AIE (1 << 0) +#define RV3029C2_IRQ_FLAGS 0x02 +#define RV3029C2_IRQ_FLAGS_AF (1 << 0) +#define RV3029C2_STATUS 0x03 +#define RV3029C2_STATUS_VLOW1 (1 << 2) +#define RV3029C2_STATUS_VLOW2 (1 << 3) +#define RV3029C2_STATUS_SR (1 << 4) +#define RV3029C2_STATUS_PON (1 << 5) +#define RV3029C2_STATUS_EEBUSY (1 << 7) +#define RV3029C2_RST_CTRL 0x04 +#define RV3029C2_CONTROL_SECTION_LEN 0x05 + +/* watch section */ +#define RV3029C2_W_SEC 0x08 +#define RV3029C2_W_MINUTES 0x09 +#define RV3029C2_W_HOURS 0x0A +#define RV3029C2_REG_HR_12_24 (1<<6) /* 24h/12h mode */ +#define RV3029C2_REG_HR_PM (1<<5) /* PM/AM bit in 12h mode */ +#define RV3029C2_W_DATE 0x0B +#define RV3029C2_W_DAYS 0x0C +#define RV3029C2_W_MONTHS 0x0D +#define RV3029C2_W_YEARS 0x0E +#define RV3029C2_WATCH_SECTION_LEN 0x07 + +/* alarm section */ +#define RV3029C2_A_SC 0x10 +#define RV3029C2_A_MN 0x11 +#define RV3029C2_A_HR 0x12 +#define RV3029C2_A_DT 0x13 +#define RV3029C2_A_DW 0x14 +#define RV3029C2_A_MO 0x15 +#define RV3029C2_A_YR 0x16 +#define RV3029C2_ALARM_SECTION_LEN 0x07 + +/* timer section */ +#define RV3029C2_TIMER_LOW 0x18 +#define RV3029C2_TIMER_HIGH 0x19 + +/* temperature section */ +#define RV3029C2_TEMP_PAGE 0x20 + +/* eeprom data section */ +#define RV3029C2_E2P_EEDATA1 0x28 +#define RV3029C2_E2P_EEDATA2 0x29 + +/* eeprom control section */ +#define RV3029C2_CONTROL_E2P_EECTRL 0x30 +#define RV3029C2_TRICKLE_1K (1<<0) /* 1K resistance */ +#define RV3029C2_TRICKLE_5K (1<<1) /* 5K resistance */ +#define RV3029C2_TRICKLE_20K (1<<2) /* 20K resistance */ +#define RV3029C2_TRICKLE_80K (1<<3) /* 80K resistance */ +#define RV3029C2_CONTROL_E2P_XTALOFFSET 0x31 +#define RV3029C2_CONTROL_E2P_QCOEF 0x32 +#define RV3029C2_CONTROL_E2P_TURNOVER 0x33 + +/* user ram section */ +#define RV3029C2_USR1_RAM_PAGE 0x38 +#define RV3029C2_USR1_SECTION_LEN 0x04 +#define RV3029C2_USR2_RAM_PAGE 0x3C +#define RV3029C2_USR2_SECTION_LEN 0x04 + +static int +rv3029c2_i2c_read_regs(struct i2c_client *client, u8 reg, u8 *buf, + unsigned len) +{ + int ret; + + if ((reg > RV3029C2_USR1_RAM_PAGE + 7) || + (reg + len > RV3029C2_USR1_RAM_PAGE + 8)) + return -EINVAL; + + ret = i2c_smbus_read_i2c_block_data(client, reg, len, buf); + if (ret < 0) + return ret; + if (ret < len) + return -EIO; + return 0; +} + +static int +rv3029c2_i2c_write_regs(struct i2c_client *client, u8 reg, u8 const buf[], + unsigned len) +{ + if ((reg > RV3029C2_USR1_RAM_PAGE + 7) || + (reg + len > RV3029C2_USR1_RAM_PAGE + 8)) + return -EINVAL; + + return i2c_smbus_write_i2c_block_data(client, reg, len, buf); +} + +static int +rv3029c2_i2c_get_sr(struct i2c_client *client, u8 *buf) +{ + int ret = rv3029c2_i2c_read_regs(client, RV3029C2_STATUS, buf, 1); + + if (ret < 0) + return -EIO; + dev_dbg(&client->dev, "status = 0x%.2x (%d)\n", buf[0], buf[0]); + return 0; +} + +static int +rv3029c2_i2c_set_sr(struct i2c_client *client, u8 val) +{ + u8 buf[1]; + int sr; + + buf[0] = val; + sr = rv3029c2_i2c_write_regs(client, RV3029C2_STATUS, buf, 1); + dev_dbg(&client->dev, "status = 0x%.2x (%d)\n", buf[0], buf[0]); + if (sr < 0) + return -EIO; + return 0; +} + +static int +rv3029c2_i2c_read_time(struct i2c_client *client, struct rtc_time *tm) +{ + u8 buf[1]; + int ret; + u8 regs[RV3029C2_WATCH_SECTION_LEN] = { 0, }; + + ret = rv3029c2_i2c_get_sr(client, buf); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + + ret = rv3029c2_i2c_read_regs(client, RV3029C2_W_SEC , regs, + RV3029C2_WATCH_SECTION_LEN); + if (ret < 0) { + dev_err(&client->dev, "%s: reading RTC section failed\n", + __func__); + return ret; + } + + tm->tm_sec = bcd2bin(regs[RV3029C2_W_SEC-RV3029C2_W_SEC]); + tm->tm_min = bcd2bin(regs[RV3029C2_W_MINUTES-RV3029C2_W_SEC]); + + /* HR field has a more complex interpretation */ + { + const u8 _hr = regs[RV3029C2_W_HOURS-RV3029C2_W_SEC]; + if (_hr & RV3029C2_REG_HR_12_24) { + /* 12h format */ + tm->tm_hour = bcd2bin(_hr & 0x1f); + if (_hr & RV3029C2_REG_HR_PM) /* PM flag set */ + tm->tm_hour += 12; + } else /* 24h format */ + tm->tm_hour = bcd2bin(_hr & 0x3f); + } + + tm->tm_mday = bcd2bin(regs[RV3029C2_W_DATE-RV3029C2_W_SEC]); + tm->tm_mon = bcd2bin(regs[RV3029C2_W_MONTHS-RV3029C2_W_SEC]) - 1; + tm->tm_year = bcd2bin(regs[RV3029C2_W_YEARS-RV3029C2_W_SEC]) + 100; + tm->tm_wday = bcd2bin(regs[RV3029C2_W_DAYS-RV3029C2_W_SEC]) - 1; + + return 0; +} + +static int rv3029c2_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + return rv3029c2_i2c_read_time(to_i2c_client(dev), tm); +} + +static int +rv3029c2_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm) +{ + struct rtc_time *const tm = &alarm->time; + int ret; + u8 regs[8]; + + ret = rv3029c2_i2c_get_sr(client, regs); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + + ret = rv3029c2_i2c_read_regs(client, RV3029C2_A_SC, regs, + RV3029C2_ALARM_SECTION_LEN); + + if (ret < 0) { + dev_err(&client->dev, "%s: reading alarm section failed\n", + __func__); + return ret; + } + + tm->tm_sec = bcd2bin(regs[RV3029C2_A_SC-RV3029C2_A_SC] & 0x7f); + tm->tm_min = bcd2bin(regs[RV3029C2_A_MN-RV3029C2_A_SC] & 0x7f); + tm->tm_hour = bcd2bin(regs[RV3029C2_A_HR-RV3029C2_A_SC] & 0x3f); + tm->tm_mday = bcd2bin(regs[RV3029C2_A_DT-RV3029C2_A_SC] & 0x3f); + tm->tm_mon = bcd2bin(regs[RV3029C2_A_MO-RV3029C2_A_SC] & 0x1f) - 1; + tm->tm_year = bcd2bin(regs[RV3029C2_A_YR-RV3029C2_A_SC] & 0x7f) + 100; + tm->tm_wday = bcd2bin(regs[RV3029C2_A_DW-RV3029C2_A_SC] & 0x07) - 1; + + return 0; +} + +static int +rv3029c2_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + return rv3029c2_i2c_read_alarm(to_i2c_client(dev), alarm); +} + +static int rv3029c2_rtc_i2c_alarm_set_irq(struct i2c_client *client, + int enable) +{ + int ret; + u8 buf[1]; + + /* enable AIE irq */ + ret = rv3029c2_i2c_read_regs(client, RV3029C2_IRQ_CTRL, buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't read INT reg\n"); + return ret; + } + if (enable) + buf[0] |= RV3029C2_IRQ_CTRL_AIE; + else + buf[0] &= ~RV3029C2_IRQ_CTRL_AIE; + + ret = rv3029c2_i2c_write_regs(client, RV3029C2_IRQ_CTRL, buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't set INT reg\n"); + return ret; + } + + return 0; +} + +static int rv3029c2_rtc_i2c_set_alarm(struct i2c_client *client, + struct rtc_wkalrm *alarm) +{ + struct rtc_time *const tm = &alarm->time; + int ret; + u8 regs[8]; + + /* + * The clock has an 8 bit wide bcd-coded register (they never learn) + * for the year. tm_year is an offset from 1900 and we are interested + * in the 2000-2099 range, so any value less than 100 is invalid. + */ + if (tm->tm_year < 100) + return -EINVAL; + + ret = rv3029c2_i2c_get_sr(client, regs); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return -EIO; + } + regs[RV3029C2_A_SC-RV3029C2_A_SC] = bin2bcd(tm->tm_sec & 0x7f); + regs[RV3029C2_A_MN-RV3029C2_A_SC] = bin2bcd(tm->tm_min & 0x7f); + regs[RV3029C2_A_HR-RV3029C2_A_SC] = bin2bcd(tm->tm_hour & 0x3f); + regs[RV3029C2_A_DT-RV3029C2_A_SC] = bin2bcd(tm->tm_mday & 0x3f); + regs[RV3029C2_A_MO-RV3029C2_A_SC] = bin2bcd((tm->tm_mon & 0x1f) - 1); + regs[RV3029C2_A_DW-RV3029C2_A_SC] = bin2bcd((tm->tm_wday & 7) - 1); + regs[RV3029C2_A_YR-RV3029C2_A_SC] = bin2bcd((tm->tm_year & 0x7f) - 100); + + ret = rv3029c2_i2c_write_regs(client, RV3029C2_A_SC, regs, + RV3029C2_ALARM_SECTION_LEN); + if (ret < 0) + return ret; + + if (alarm->enabled) { + u8 buf[1]; + + /* clear AF flag */ + ret = rv3029c2_i2c_read_regs(client, RV3029C2_IRQ_FLAGS, + buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't read alarm flag\n"); + return ret; + } + buf[0] &= ~RV3029C2_IRQ_FLAGS_AF; + ret = rv3029c2_i2c_write_regs(client, RV3029C2_IRQ_FLAGS, + buf, 1); + if (ret < 0) { + dev_err(&client->dev, "can't set alarm flag\n"); + return ret; + } + /* enable AIE irq */ + ret = rv3029c2_rtc_i2c_alarm_set_irq(client, 1); + if (ret) + return ret; + + dev_dbg(&client->dev, "alarm IRQ armed\n"); + } else { + /* disable AIE irq */ + ret = rv3029c2_rtc_i2c_alarm_set_irq(client, 1); + if (ret) + return ret; + + dev_dbg(&client->dev, "alarm IRQ disabled\n"); + } + + return 0; +} + +static int rv3029c2_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + return rv3029c2_rtc_i2c_set_alarm(to_i2c_client(dev), alarm); +} + +static int +rv3029c2_i2c_set_time(struct i2c_client *client, struct rtc_time const *tm) +{ + u8 regs[8]; + int ret; + + /* + * The clock has an 8 bit wide bcd-coded register (they never learn) + * for the year. tm_year is an offset from 1900 and we are interested + * in the 2000-2099 range, so any value less than 100 is invalid. + */ + if (tm->tm_year < 100) + return -EINVAL; + + regs[RV3029C2_W_SEC-RV3029C2_W_SEC] = bin2bcd(tm->tm_sec); + regs[RV3029C2_W_MINUTES-RV3029C2_W_SEC] = bin2bcd(tm->tm_min); + regs[RV3029C2_W_HOURS-RV3029C2_W_SEC] = bin2bcd(tm->tm_hour); + regs[RV3029C2_W_DATE-RV3029C2_W_SEC] = bin2bcd(tm->tm_mday); + regs[RV3029C2_W_MONTHS-RV3029C2_W_SEC] = bin2bcd(tm->tm_mon+1); + regs[RV3029C2_W_DAYS-RV3029C2_W_SEC] = bin2bcd((tm->tm_wday & 7)+1); + regs[RV3029C2_W_YEARS-RV3029C2_W_SEC] = bin2bcd(tm->tm_year - 100); + + ret = rv3029c2_i2c_write_regs(client, RV3029C2_W_SEC, regs, + RV3029C2_WATCH_SECTION_LEN); + if (ret < 0) + return ret; + + ret = rv3029c2_i2c_get_sr(client, regs); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return ret; + } + /* clear PON bit */ + ret = rv3029c2_i2c_set_sr(client, (regs[0] & ~RV3029C2_STATUS_PON)); + if (ret < 0) { + dev_err(&client->dev, "%s: reading SR failed\n", __func__); + return ret; + } + + return 0; +} + +static int rv3029c2_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + return rv3029c2_i2c_set_time(to_i2c_client(dev), tm); +} + +static const struct rtc_class_ops rv3029c2_rtc_ops = { + .read_time = rv3029c2_rtc_read_time, + .set_time = rv3029c2_rtc_set_time, + .read_alarm = rv3029c2_rtc_read_alarm, + .set_alarm = rv3029c2_rtc_set_alarm, +}; + +static struct i2c_device_id rv3029c2_id[] = { + { "rv3029c2", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rv3029c2_id); + +static int __devinit +rv3029c2_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct rtc_device *rtc; + int rc = 0; + u8 buf[1]; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_EMUL)) + return -ENODEV; + + rtc = rtc_device_register(client->name, + &client->dev, &rv3029c2_rtc_ops, + THIS_MODULE); + + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + i2c_set_clientdata(client, rtc); + + rc = rv3029c2_i2c_get_sr(client, buf); + if (rc < 0) { + dev_err(&client->dev, "reading status failed\n"); + goto exit_unregister; + } + + return 0; + +exit_unregister: + rtc_device_unregister(rtc); + + return rc; +} + +static int __devexit rv3029c2_remove(struct i2c_client *client) +{ + struct rtc_device *rtc = i2c_get_clientdata(client); + + rtc_device_unregister(rtc); + + return 0; +} + +static struct i2c_driver rv3029c2_driver = { + .driver = { + .name = "rtc-rv3029c2", + }, + .probe = rv3029c2_probe, + .remove = __devexit_p(rv3029c2_remove), + .id_table = rv3029c2_id, +}; + +static int __init rv3029c2_init(void) +{ + return i2c_add_driver(&rv3029c2_driver); +} + +static void __exit rv3029c2_exit(void) +{ + i2c_del_driver(&rv3029c2_driver); +} + +module_init(rv3029c2_init); +module_exit(rv3029c2_exit); + +MODULE_AUTHOR("Gregory Hermant <gregory.hermant@calao-systems.com>"); +MODULE_DESCRIPTION("Micro Crystal RV3029C2 RTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-spear.c b/drivers/rtc/rtc-spear.c new file mode 100644 index 00000000000..893bac2bb21 --- /dev/null +++ b/drivers/rtc/rtc-spear.c @@ -0,0 +1,534 @@ +/* + * drivers/rtc/rtc-spear.c + * + * Copyright (C) 2010 ST Microelectronics + * Rajeev Kumar<rajeev-dlh.kumar@st.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/bcd.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +/* RTC registers */ +#define TIME_REG 0x00 +#define DATE_REG 0x04 +#define ALARM_TIME_REG 0x08 +#define ALARM_DATE_REG 0x0C +#define CTRL_REG 0x10 +#define STATUS_REG 0x14 + +/* TIME_REG & ALARM_TIME_REG */ +#define SECONDS_UNITS (0xf<<0) /* seconds units position */ +#define SECONDS_TENS (0x7<<4) /* seconds tens position */ +#define MINUTES_UNITS (0xf<<8) /* minutes units position */ +#define MINUTES_TENS (0x7<<12) /* minutes tens position */ +#define HOURS_UNITS (0xf<<16) /* hours units position */ +#define HOURS_TENS (0x3<<20) /* hours tens position */ + +/* DATE_REG & ALARM_DATE_REG */ +#define DAYS_UNITS (0xf<<0) /* days units position */ +#define DAYS_TENS (0x3<<4) /* days tens position */ +#define MONTHS_UNITS (0xf<<8) /* months units position */ +#define MONTHS_TENS (0x1<<12) /* months tens position */ +#define YEARS_UNITS (0xf<<16) /* years units position */ +#define YEARS_TENS (0xf<<20) /* years tens position */ +#define YEARS_HUNDREDS (0xf<<24) /* years hundereds position */ +#define YEARS_MILLENIUMS (0xf<<28) /* years millenium position */ + +/* MASK SHIFT TIME_REG & ALARM_TIME_REG*/ +#define SECOND_SHIFT 0x00 /* seconds units */ +#define MINUTE_SHIFT 0x08 /* minutes units position */ +#define HOUR_SHIFT 0x10 /* hours units position */ +#define MDAY_SHIFT 0x00 /* Month day shift */ +#define MONTH_SHIFT 0x08 /* Month shift */ +#define YEAR_SHIFT 0x10 /* Year shift */ + +#define SECOND_MASK 0x7F +#define MIN_MASK 0x7F +#define HOUR_MASK 0x3F +#define DAY_MASK 0x3F +#define MONTH_MASK 0x7F +#define YEAR_MASK 0xFFFF + +/* date reg equal to time reg, for debug only */ +#define TIME_BYP (1<<9) +#define INT_ENABLE (1<<31) /* interrupt enable */ + +/* STATUS_REG */ +#define CLK_UNCONNECTED (1<<0) +#define PEND_WR_TIME (1<<2) +#define PEND_WR_DATE (1<<3) +#define LOST_WR_TIME (1<<4) +#define LOST_WR_DATE (1<<5) +#define RTC_INT_MASK (1<<31) +#define STATUS_BUSY (PEND_WR_TIME | PEND_WR_DATE) +#define STATUS_FAIL (LOST_WR_TIME | LOST_WR_DATE) + +struct spear_rtc_config { + struct clk *clk; + spinlock_t lock; + void __iomem *ioaddr; +}; + +static inline void spear_rtc_clear_interrupt(struct spear_rtc_config *config) +{ + unsigned int val; + unsigned long flags; + + spin_lock_irqsave(&config->lock, flags); + val = readl(config->ioaddr + STATUS_REG); + val |= RTC_INT_MASK; + writel(val, config->ioaddr + STATUS_REG); + spin_unlock_irqrestore(&config->lock, flags); +} + +static inline void spear_rtc_enable_interrupt(struct spear_rtc_config *config) +{ + unsigned int val; + + val = readl(config->ioaddr + CTRL_REG); + if (!(val & INT_ENABLE)) { + spear_rtc_clear_interrupt(config); + val |= INT_ENABLE; + writel(val, config->ioaddr + CTRL_REG); + } +} + +static inline void spear_rtc_disable_interrupt(struct spear_rtc_config *config) +{ + unsigned int val; + + val = readl(config->ioaddr + CTRL_REG); + if (val & INT_ENABLE) { + val &= ~INT_ENABLE; + writel(val, config->ioaddr + CTRL_REG); + } +} + +static inline int is_write_complete(struct spear_rtc_config *config) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&config->lock, flags); + if ((readl(config->ioaddr + STATUS_REG)) & STATUS_FAIL) + ret = -EIO; + spin_unlock_irqrestore(&config->lock, flags); + + return ret; +} + +static void rtc_wait_not_busy(struct spear_rtc_config *config) +{ + int status, count = 0; + unsigned long flags; + + /* Assuming BUSY may stay active for 80 msec) */ + for (count = 0; count < 80; count++) { + spin_lock_irqsave(&config->lock, flags); + status = readl(config->ioaddr + STATUS_REG); + spin_unlock_irqrestore(&config->lock, flags); + if ((status & STATUS_BUSY) == 0) + break; + /* check status busy, after each msec */ + msleep(1); + } +} + +static irqreturn_t spear_rtc_irq(int irq, void *dev_id) +{ + struct rtc_device *rtc = (struct rtc_device *)dev_id; + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned long flags, events = 0; + unsigned int irq_data; + + spin_lock_irqsave(&config->lock, flags); + irq_data = readl(config->ioaddr + STATUS_REG); + spin_unlock_irqrestore(&config->lock, flags); + + if ((irq_data & RTC_INT_MASK)) { + spear_rtc_clear_interrupt(config); + events = RTC_IRQF | RTC_AF; + rtc_update_irq(rtc, 1, events); + return IRQ_HANDLED; + } else + return IRQ_NONE; + +} + +static int tm2bcd(struct rtc_time *tm) +{ + if (rtc_valid_tm(tm) != 0) + return -EINVAL; + tm->tm_sec = bin2bcd(tm->tm_sec); + tm->tm_min = bin2bcd(tm->tm_min); + tm->tm_hour = bin2bcd(tm->tm_hour); + tm->tm_mday = bin2bcd(tm->tm_mday); + tm->tm_mon = bin2bcd(tm->tm_mon + 1); + tm->tm_year = bin2bcd(tm->tm_year); + + return 0; +} + +static void bcd2tm(struct rtc_time *tm) +{ + tm->tm_sec = bcd2bin(tm->tm_sec); + tm->tm_min = bcd2bin(tm->tm_min); + tm->tm_hour = bcd2bin(tm->tm_hour); + tm->tm_mday = bcd2bin(tm->tm_mday); + tm->tm_mon = bcd2bin(tm->tm_mon) - 1; + /* epoch == 1900 */ + tm->tm_year = bcd2bin(tm->tm_year); +} + +/* + * spear_rtc_read_time - set the time + * @dev: rtc device in use + * @tm: holds date and time + * + * This function read time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date; + + /* we don't report wday/yday/isdst ... */ + rtc_wait_not_busy(config); + + time = readl(config->ioaddr + TIME_REG); + date = readl(config->ioaddr + DATE_REG); + tm->tm_sec = (time >> SECOND_SHIFT) & SECOND_MASK; + tm->tm_min = (time >> MINUTE_SHIFT) & MIN_MASK; + tm->tm_hour = (time >> HOUR_SHIFT) & HOUR_MASK; + tm->tm_mday = (date >> MDAY_SHIFT) & DAY_MASK; + tm->tm_mon = (date >> MONTH_SHIFT) & MONTH_MASK; + tm->tm_year = (date >> YEAR_SHIFT) & YEAR_MASK; + + bcd2tm(tm); + return 0; +} + +/* + * spear_rtc_set_time - set the time + * @dev: rtc device in use + * @tm: holds date and time + * + * This function set time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date, err = 0; + + if (tm2bcd(tm) < 0) + return -EINVAL; + + rtc_wait_not_busy(config); + time = (tm->tm_sec << SECOND_SHIFT) | (tm->tm_min << MINUTE_SHIFT) | + (tm->tm_hour << HOUR_SHIFT); + date = (tm->tm_mday << MDAY_SHIFT) | (tm->tm_mon << MONTH_SHIFT) | + (tm->tm_year << YEAR_SHIFT); + writel(time, config->ioaddr + TIME_REG); + writel(date, config->ioaddr + DATE_REG); + err = is_write_complete(config); + if (err < 0) + return err; + + return 0; +} + +/* + * spear_rtc_read_alarm - read the alarm time + * @dev: rtc device in use + * @alm: holds alarm date and time + * + * This function read alarm time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date; + + rtc_wait_not_busy(config); + + time = readl(config->ioaddr + ALARM_TIME_REG); + date = readl(config->ioaddr + ALARM_DATE_REG); + alm->time.tm_sec = (time >> SECOND_SHIFT) & SECOND_MASK; + alm->time.tm_min = (time >> MINUTE_SHIFT) & MIN_MASK; + alm->time.tm_hour = (time >> HOUR_SHIFT) & HOUR_MASK; + alm->time.tm_mday = (date >> MDAY_SHIFT) & DAY_MASK; + alm->time.tm_mon = (date >> MONTH_SHIFT) & MONTH_MASK; + alm->time.tm_year = (date >> YEAR_SHIFT) & YEAR_MASK; + + bcd2tm(&alm->time); + alm->enabled = readl(config->ioaddr + CTRL_REG) & INT_ENABLE; + + return 0; +} + +/* + * spear_rtc_set_alarm - set the alarm time + * @dev: rtc device in use + * @alm: holds alarm date and time + * + * This function set alarm time and date. On success it will return 0 + * otherwise -ve error is returned. + */ +static int spear_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + unsigned int time, date, err = 0; + + if (tm2bcd(&alm->time) < 0) + return -EINVAL; + + rtc_wait_not_busy(config); + + time = (alm->time.tm_sec << SECOND_SHIFT) | (alm->time.tm_min << + MINUTE_SHIFT) | (alm->time.tm_hour << HOUR_SHIFT); + date = (alm->time.tm_mday << MDAY_SHIFT) | (alm->time.tm_mon << + MONTH_SHIFT) | (alm->time.tm_year << YEAR_SHIFT); + + writel(time, config->ioaddr + ALARM_TIME_REG); + writel(date, config->ioaddr + ALARM_DATE_REG); + err = is_write_complete(config); + if (err < 0) + return err; + + if (alm->enabled) + spear_rtc_enable_interrupt(config); + else + spear_rtc_disable_interrupt(config); + + return 0; +} +static struct rtc_class_ops spear_rtc_ops = { + .read_time = spear_rtc_read_time, + .set_time = spear_rtc_set_time, + .read_alarm = spear_rtc_read_alarm, + .set_alarm = spear_rtc_set_alarm, +}; + +static int __devinit spear_rtc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct rtc_device *rtc; + struct spear_rtc_config *config; + unsigned int status = 0; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no resource defined\n"); + return -EBUSY; + } + if (!request_mem_region(res->start, resource_size(res), pdev->name)) { + dev_err(&pdev->dev, "rtc region already claimed\n"); + return -EBUSY; + } + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) { + dev_err(&pdev->dev, "out of memory\n"); + status = -ENOMEM; + goto err_release_region; + } + + config->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(config->clk)) { + status = PTR_ERR(config->clk); + goto err_kfree; + } + + status = clk_enable(config->clk); + if (status < 0) + goto err_clk_put; + + config->ioaddr = ioremap(res->start, resource_size(res)); + if (!config->ioaddr) { + dev_err(&pdev->dev, "ioremap fail\n"); + status = -ENOMEM; + goto err_disable_clock; + } + + spin_lock_init(&config->lock); + + rtc = rtc_device_register(pdev->name, &pdev->dev, &spear_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + dev_err(&pdev->dev, "can't register RTC device, err %ld\n", + PTR_ERR(rtc)); + status = PTR_ERR(rtc); + goto err_iounmap; + } + + platform_set_drvdata(pdev, rtc); + dev_set_drvdata(&rtc->dev, config); + + /* alarm irqs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no update irq?\n"); + status = irq; + goto err_clear_platdata; + } + + status = request_irq(irq, spear_rtc_irq, 0, pdev->name, rtc); + if (status) { + dev_err(&pdev->dev, "Alarm interrupt IRQ%d already \ + claimed\n", irq); + goto err_clear_platdata; + } + + if (!device_can_wakeup(&pdev->dev)) + device_init_wakeup(&pdev->dev, 1); + + return 0; + +err_clear_platdata: + platform_set_drvdata(pdev, NULL); + dev_set_drvdata(&rtc->dev, NULL); + rtc_device_unregister(rtc); +err_iounmap: + iounmap(config->ioaddr); +err_disable_clock: + clk_disable(config->clk); +err_clk_put: + clk_put(config->clk); +err_kfree: + kfree(config); +err_release_region: + release_mem_region(res->start, resource_size(res)); + + return status; +} + +static int __devexit spear_rtc_remove(struct platform_device *pdev) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + int irq; + struct resource *res; + + /* leave rtc running, but disable irqs */ + spear_rtc_disable_interrupt(config); + device_init_wakeup(&pdev->dev, 0); + irq = platform_get_irq(pdev, 0); + if (irq) + free_irq(irq, pdev); + clk_disable(config->clk); + clk_put(config->clk); + iounmap(config->ioaddr); + kfree(config); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + platform_set_drvdata(pdev, NULL); + dev_set_drvdata(&rtc->dev, NULL); + rtc_device_unregister(rtc); + + return 0; +} + +#ifdef CONFIG_PM + +static int spear_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + int irq; + + irq = platform_get_irq(pdev, 0); + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(irq); + else { + spear_rtc_disable_interrupt(config); + clk_disable(config->clk); + } + + return 0; +} + +static int spear_rtc_resume(struct platform_device *pdev) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + int irq; + + irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(irq); + else { + clk_enable(config->clk); + spear_rtc_enable_interrupt(config); + } + + return 0; +} + +#else +#define spear_rtc_suspend NULL +#define spear_rtc_resume NULL +#endif + +static void spear_rtc_shutdown(struct platform_device *pdev) +{ + struct rtc_device *rtc = platform_get_drvdata(pdev); + struct spear_rtc_config *config = dev_get_drvdata(&rtc->dev); + + spear_rtc_disable_interrupt(config); + clk_disable(config->clk); +} + +static struct platform_driver spear_rtc_driver = { + .probe = spear_rtc_probe, + .remove = __devexit_p(spear_rtc_remove), + .suspend = spear_rtc_suspend, + .resume = spear_rtc_resume, + .shutdown = spear_rtc_shutdown, + .driver = { + .name = "rtc-spear", + }, +}; + +static int __init rtc_init(void) +{ + return platform_driver_register(&spear_rtc_driver); +} +module_init(rtc_init); + +static void __exit rtc_exit(void) +{ + platform_driver_unregister(&spear_rtc_driver); +} +module_exit(rtc_exit); + +MODULE_ALIAS("platform:rtc-spear"); +MODULE_AUTHOR("Rajeev Kumar <rajeev-dlh.kumar@st.com>"); +MODULE_DESCRIPTION("ST SPEAr Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-vt8500.c b/drivers/rtc/rtc-vt8500.c new file mode 100644 index 00000000000..b8bc862903a --- /dev/null +++ b/drivers/rtc/rtc-vt8500.c @@ -0,0 +1,366 @@ +/* + * drivers/rtc/rtc-vt8500.c + * + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * Based on rtc-pxa.c + * + * 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 <linux/module.h> +#include <linux/rtc.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/bcd.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* + * Register definitions + */ +#define VT8500_RTC_TS 0x00 /* Time set */ +#define VT8500_RTC_DS 0x04 /* Date set */ +#define VT8500_RTC_AS 0x08 /* Alarm set */ +#define VT8500_RTC_CR 0x0c /* Control */ +#define VT8500_RTC_TR 0x10 /* Time read */ +#define VT8500_RTC_DR 0x14 /* Date read */ +#define VT8500_RTC_WS 0x18 /* Write status */ +#define VT8500_RTC_CL 0x20 /* Calibration */ +#define VT8500_RTC_IS 0x24 /* Interrupt status */ +#define VT8500_RTC_ST 0x28 /* Status */ + +#define INVALID_TIME_BIT (1 << 31) + +#define DATE_CENTURY_S 19 +#define DATE_YEAR_S 11 +#define DATE_YEAR_MASK (0xff << DATE_YEAR_S) +#define DATE_MONTH_S 6 +#define DATE_MONTH_MASK (0x1f << DATE_MONTH_S) +#define DATE_DAY_MASK 0x3f + +#define TIME_DOW_S 20 +#define TIME_DOW_MASK (0x07 << TIME_DOW_S) +#define TIME_HOUR_S 14 +#define TIME_HOUR_MASK (0x3f << TIME_HOUR_S) +#define TIME_MIN_S 7 +#define TIME_MIN_MASK (0x7f << TIME_MIN_S) +#define TIME_SEC_MASK 0x7f + +#define ALARM_DAY_S 20 +#define ALARM_DAY_MASK (0x3f << ALARM_DAY_S) + +#define ALARM_DAY_BIT (1 << 29) +#define ALARM_HOUR_BIT (1 << 28) +#define ALARM_MIN_BIT (1 << 27) +#define ALARM_SEC_BIT (1 << 26) + +#define ALARM_ENABLE_MASK (ALARM_DAY_BIT \ + | ALARM_HOUR_BIT \ + | ALARM_MIN_BIT \ + | ALARM_SEC_BIT) + +#define VT8500_RTC_CR_ENABLE (1 << 0) /* Enable RTC */ +#define VT8500_RTC_CR_24H (1 << 1) /* 24h time format */ +#define VT8500_RTC_CR_SM_ENABLE (1 << 2) /* Enable periodic irqs */ +#define VT8500_RTC_CR_SM_SEC (1 << 3) /* 0: 1Hz/60, 1: 1Hz */ +#define VT8500_RTC_CR_CALIB (1 << 4) /* Enable calibration */ + +struct vt8500_rtc { + void __iomem *regbase; + struct resource *res; + int irq_alarm; + int irq_hz; + struct rtc_device *rtc; + spinlock_t lock; /* Protects this structure */ +}; + +static irqreturn_t vt8500_rtc_irq(int irq, void *dev_id) +{ + struct vt8500_rtc *vt8500_rtc = dev_id; + u32 isr; + unsigned long events = 0; + + spin_lock(&vt8500_rtc->lock); + + /* clear interrupt sources */ + isr = readl(vt8500_rtc->regbase + VT8500_RTC_IS); + writel(isr, vt8500_rtc->regbase + VT8500_RTC_IS); + + spin_unlock(&vt8500_rtc->lock); + + if (isr & 1) + events |= RTC_AF | RTC_IRQF; + + /* Only second/minute interrupts are supported */ + if (isr & 2) + events |= RTC_UF | RTC_IRQF; + + rtc_update_irq(vt8500_rtc->rtc, 1, events); + + return IRQ_HANDLED; +} + +static int vt8500_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + u32 date, time; + + date = readl(vt8500_rtc->regbase + VT8500_RTC_DR); + time = readl(vt8500_rtc->regbase + VT8500_RTC_TR); + + tm->tm_sec = bcd2bin(time & TIME_SEC_MASK); + tm->tm_min = bcd2bin((time & TIME_MIN_MASK) >> TIME_MIN_S); + tm->tm_hour = bcd2bin((time & TIME_HOUR_MASK) >> TIME_HOUR_S); + tm->tm_mday = bcd2bin(date & DATE_DAY_MASK); + tm->tm_mon = bcd2bin((date & DATE_MONTH_MASK) >> DATE_MONTH_S); + tm->tm_year = bcd2bin((date & DATE_YEAR_MASK) >> DATE_YEAR_S) + + ((date >> DATE_CENTURY_S) & 1 ? 200 : 100); + tm->tm_wday = (time & TIME_DOW_MASK) >> TIME_DOW_S; + + return 0; +} + +static int vt8500_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + + if (tm->tm_year < 100) { + dev_warn(dev, "Only years 2000-2199 are supported by the " + "hardware!\n"); + return -EINVAL; + } + + writel((bin2bcd(tm->tm_year - 100) << DATE_YEAR_S) + | (bin2bcd(tm->tm_mon) << DATE_MONTH_S) + | (bin2bcd(tm->tm_mday)), + vt8500_rtc->regbase + VT8500_RTC_DS); + writel((bin2bcd(tm->tm_wday) << TIME_DOW_S) + | (bin2bcd(tm->tm_hour) << TIME_HOUR_S) + | (bin2bcd(tm->tm_min) << TIME_MIN_S) + | (bin2bcd(tm->tm_sec)), + vt8500_rtc->regbase + VT8500_RTC_TS); + + return 0; +} + +static int vt8500_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + u32 isr, alarm; + + alarm = readl(vt8500_rtc->regbase + VT8500_RTC_AS); + isr = readl(vt8500_rtc->regbase + VT8500_RTC_IS); + + alrm->time.tm_mday = bcd2bin((alarm & ALARM_DAY_MASK) >> ALARM_DAY_S); + alrm->time.tm_hour = bcd2bin((alarm & TIME_HOUR_MASK) >> TIME_HOUR_S); + alrm->time.tm_min = bcd2bin((alarm & TIME_MIN_MASK) >> TIME_MIN_S); + alrm->time.tm_sec = bcd2bin((alarm & TIME_SEC_MASK)); + + alrm->enabled = (alarm & ALARM_ENABLE_MASK) ? 1 : 0; + + alrm->pending = (isr & 1) ? 1 : 0; + return rtc_valid_tm(&alrm->time); +} + +static int vt8500_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + + writel((alrm->enabled ? ALARM_ENABLE_MASK : 0) + | (bin2bcd(alrm->time.tm_mday) << ALARM_DAY_S) + | (bin2bcd(alrm->time.tm_hour) << TIME_HOUR_S) + | (bin2bcd(alrm->time.tm_min) << TIME_MIN_S) + | (bin2bcd(alrm->time.tm_sec)), + vt8500_rtc->regbase + VT8500_RTC_AS); + + return 0; +} + +static int vt8500_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + unsigned long tmp = readl(vt8500_rtc->regbase + VT8500_RTC_AS); + + if (enabled) + tmp |= ALARM_ENABLE_MASK; + else + tmp &= ~ALARM_ENABLE_MASK; + + writel(tmp, vt8500_rtc->regbase + VT8500_RTC_AS); + return 0; +} + +static int vt8500_update_irq_enable(struct device *dev, unsigned int enabled) +{ + struct vt8500_rtc *vt8500_rtc = dev_get_drvdata(dev); + unsigned long tmp = readl(vt8500_rtc->regbase + VT8500_RTC_CR); + + if (enabled) + tmp |= VT8500_RTC_CR_SM_SEC | VT8500_RTC_CR_SM_ENABLE; + else + tmp &= ~VT8500_RTC_CR_SM_ENABLE; + + writel(tmp, vt8500_rtc->regbase + VT8500_RTC_CR); + return 0; +} + +static const struct rtc_class_ops vt8500_rtc_ops = { + .read_time = vt8500_rtc_read_time, + .set_time = vt8500_rtc_set_time, + .read_alarm = vt8500_rtc_read_alarm, + .set_alarm = vt8500_rtc_set_alarm, + .alarm_irq_enable = vt8500_alarm_irq_enable, + .update_irq_enable = vt8500_update_irq_enable, +}; + +static int __devinit vt8500_rtc_probe(struct platform_device *pdev) +{ + struct vt8500_rtc *vt8500_rtc; + int ret; + + vt8500_rtc = kzalloc(sizeof(struct vt8500_rtc), GFP_KERNEL); + if (!vt8500_rtc) + return -ENOMEM; + + spin_lock_init(&vt8500_rtc->lock); + platform_set_drvdata(pdev, vt8500_rtc); + + vt8500_rtc->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!vt8500_rtc->res) { + dev_err(&pdev->dev, "No I/O memory resource defined\n"); + ret = -ENXIO; + goto err_free; + } + + vt8500_rtc->irq_alarm = platform_get_irq(pdev, 0); + if (vt8500_rtc->irq_alarm < 0) { + dev_err(&pdev->dev, "No alarm IRQ resource defined\n"); + ret = -ENXIO; + goto err_free; + } + + vt8500_rtc->irq_hz = platform_get_irq(pdev, 1); + if (vt8500_rtc->irq_hz < 0) { + dev_err(&pdev->dev, "No 1Hz IRQ resource defined\n"); + ret = -ENXIO; + goto err_free; + } + + vt8500_rtc->res = request_mem_region(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res), + "vt8500-rtc"); + if (vt8500_rtc->res == NULL) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + ret = -EBUSY; + goto err_free; + } + + vt8500_rtc->regbase = ioremap(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res)); + if (!vt8500_rtc->regbase) { + dev_err(&pdev->dev, "Unable to map RTC I/O memory\n"); + ret = -EBUSY; + goto err_release; + } + + /* Enable the second/minute interrupt generation and enable RTC */ + writel(VT8500_RTC_CR_ENABLE | VT8500_RTC_CR_24H + | VT8500_RTC_CR_SM_ENABLE | VT8500_RTC_CR_SM_SEC, + vt8500_rtc->regbase + VT8500_RTC_CR); + + vt8500_rtc->rtc = rtc_device_register("vt8500-rtc", &pdev->dev, + &vt8500_rtc_ops, THIS_MODULE); + if (IS_ERR(vt8500_rtc->rtc)) { + ret = PTR_ERR(vt8500_rtc->rtc); + dev_err(&pdev->dev, + "Failed to register RTC device -> %d\n", ret); + goto err_unmap; + } + + ret = request_irq(vt8500_rtc->irq_hz, vt8500_rtc_irq, 0, + "rtc 1Hz", vt8500_rtc); + if (ret < 0) { + dev_err(&pdev->dev, "can't get irq %i, err %d\n", + vt8500_rtc->irq_hz, ret); + goto err_unreg; + } + + ret = request_irq(vt8500_rtc->irq_alarm, vt8500_rtc_irq, 0, + "rtc alarm", vt8500_rtc); + if (ret < 0) { + dev_err(&pdev->dev, "can't get irq %i, err %d\n", + vt8500_rtc->irq_alarm, ret); + goto err_free_hz; + } + + return 0; + +err_free_hz: + free_irq(vt8500_rtc->irq_hz, vt8500_rtc); +err_unreg: + rtc_device_unregister(vt8500_rtc->rtc); +err_unmap: + iounmap(vt8500_rtc->regbase); +err_release: + release_mem_region(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res)); +err_free: + kfree(vt8500_rtc); + return ret; +} + +static int __devexit vt8500_rtc_remove(struct platform_device *pdev) +{ + struct vt8500_rtc *vt8500_rtc = platform_get_drvdata(pdev); + + free_irq(vt8500_rtc->irq_alarm, vt8500_rtc); + free_irq(vt8500_rtc->irq_hz, vt8500_rtc); + + rtc_device_unregister(vt8500_rtc->rtc); + + /* Disable alarm matching */ + writel(0, vt8500_rtc->regbase + VT8500_RTC_IS); + iounmap(vt8500_rtc->regbase); + release_mem_region(vt8500_rtc->res->start, + resource_size(vt8500_rtc->res)); + + kfree(vt8500_rtc); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver vt8500_rtc_driver = { + .probe = vt8500_rtc_probe, + .remove = __devexit_p(vt8500_rtc_remove), + .driver = { + .name = "vt8500-rtc", + .owner = THIS_MODULE, + }, +}; + +static int __init vt8500_rtc_init(void) +{ + return platform_driver_register(&vt8500_rtc_driver); +} +module_init(vt8500_rtc_init); + +static void __exit vt8500_rtc_exit(void) +{ + platform_driver_unregister(&vt8500_rtc_driver); +} +module_exit(vt8500_rtc_exit); + +MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>"); +MODULE_DESCRIPTION("VIA VT8500 SoC Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:vt8500-rtc"); diff --git a/drivers/s390/block/dasd_diag.c b/drivers/s390/block/dasd_diag.c index 85dddb1e412..46784b83c5c 100644 --- a/drivers/s390/block/dasd_diag.c +++ b/drivers/s390/block/dasd_diag.c @@ -24,7 +24,7 @@ #include <asm/debug.h> #include <asm/ebcdic.h> #include <asm/io.h> -#include <asm/s390_ext.h> +#include <asm/irq.h> #include <asm/vtoc.h> #include <asm/diag.h> @@ -642,7 +642,7 @@ dasd_diag_init(void) } ASCEBC(dasd_diag_discipline.ebcname, 4); - ctl_set_bit(0, 9); + service_subclass_irq_register(); register_external_interrupt(0x2603, dasd_ext_handler); dasd_diag_discipline_pointer = &dasd_diag_discipline; return 0; @@ -652,7 +652,7 @@ static void __exit dasd_diag_cleanup(void) { unregister_external_interrupt(0x2603, dasd_ext_handler); - ctl_clear_bit(0, 9); + service_subclass_irq_unregister(); dasd_diag_discipline_pointer = NULL; } diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c index b76c61f8248..eaa7e78186f 100644 --- a/drivers/s390/char/sclp.c +++ b/drivers/s390/char/sclp.c @@ -19,7 +19,6 @@ #include <linux/suspend.h> #include <linux/completion.h> #include <linux/platform_device.h> -#include <asm/s390_ext.h> #include <asm/types.h> #include <asm/irq.h> @@ -885,12 +884,12 @@ sclp_check_interface(void) spin_unlock_irqrestore(&sclp_lock, flags); /* Enable service-signal interruption - needs to happen * with IRQs enabled. */ - ctl_set_bit(0, 9); + service_subclass_irq_register(); /* Wait for signal from interrupt or timeout */ sclp_sync_wait(); /* Disable service-signal interruption - needs to happen * with IRQs enabled. */ - ctl_clear_bit(0,9); + service_subclass_irq_unregister(); spin_lock_irqsave(&sclp_lock, flags); del_timer(&sclp_request_timer); if (sclp_init_req.status == SCLP_REQ_DONE && @@ -1070,7 +1069,7 @@ sclp_init(void) spin_unlock_irqrestore(&sclp_lock, flags); /* Enable service-signal external interruption - needs to happen with * IRQs enabled. */ - ctl_set_bit(0, 9); + service_subclass_irq_register(); sclp_init_mask(1); return 0; diff --git a/drivers/s390/kvm/kvm_virtio.c b/drivers/s390/kvm/kvm_virtio.c index 607998f0b7d..aec60d55b10 100644 --- a/drivers/s390/kvm/kvm_virtio.c +++ b/drivers/s390/kvm/kvm_virtio.c @@ -25,7 +25,6 @@ #include <asm/kvm_para.h> #include <asm/kvm_virtio.h> #include <asm/setup.h> -#include <asm/s390_ext.h> #include <asm/irq.h> #define VIRTIO_SUBCODE_64 0x0D00 @@ -441,7 +440,7 @@ static int __init kvm_devices_init(void) INIT_WORK(&hotplug_work, hotplug_devices); - ctl_set_bit(0, 9); + service_subclass_irq_register(); register_external_interrupt(0x2603, kvm_extint_handler); scan_devices(); diff --git a/drivers/scsi/aacraid/linit.c b/drivers/scsi/aacraid/linit.c index 4ff26521d75..3382475dc22 100644 --- a/drivers/scsi/aacraid/linit.c +++ b/drivers/scsi/aacraid/linit.c @@ -59,7 +59,6 @@ #ifndef AAC_DRIVER_BRANCH #define AAC_DRIVER_BRANCH "" #endif -#define AAC_DRIVER_BUILD_DATE __DATE__ " " __TIME__ #define AAC_DRIVERNAME "aacraid" #ifdef AAC_DRIVER_BUILD @@ -67,7 +66,7 @@ #define str(x) _str(x) #define AAC_DRIVER_FULL_VERSION AAC_DRIVER_VERSION "[" str(AAC_DRIVER_BUILD) "]" AAC_DRIVER_BRANCH #else -#define AAC_DRIVER_FULL_VERSION AAC_DRIVER_VERSION AAC_DRIVER_BRANCH " " AAC_DRIVER_BUILD_DATE +#define AAC_DRIVER_FULL_VERSION AAC_DRIVER_VERSION AAC_DRIVER_BRANCH #endif MODULE_AUTHOR("Red Hat Inc and Adaptec"); diff --git a/drivers/scsi/in2000.c b/drivers/scsi/in2000.c index 92109b12639..112f1bec775 100644 --- a/drivers/scsi/in2000.c +++ b/drivers/scsi/in2000.c @@ -2227,7 +2227,7 @@ static int in2000_proc_info(struct Scsi_Host *instance, char *buf, char **start, bp = buf; *bp = '\0'; if (hd->proc & PR_VERSION) { - sprintf(tbuf, "\nVersion %s - %s. Compiled %s %s", IN2000_VERSION, IN2000_DATE, __DATE__, __TIME__); + sprintf(tbuf, "\nVersion %s - %s.", IN2000_VERSION, IN2000_DATE); strcat(bp, tbuf); } if (hd->proc & PR_INFO) { diff --git a/drivers/scsi/pmcraid.c b/drivers/scsi/pmcraid.c index 7f636b11828..fca6a895307 100644 --- a/drivers/scsi/pmcraid.c +++ b/drivers/scsi/pmcraid.c @@ -4252,8 +4252,8 @@ static ssize_t pmcraid_show_drv_version( char *buf ) { - return snprintf(buf, PAGE_SIZE, "version: %s, build date: %s\n", - PMCRAID_DRIVER_VERSION, PMCRAID_DRIVER_DATE); + return snprintf(buf, PAGE_SIZE, "version: %s\n", + PMCRAID_DRIVER_VERSION); } static struct device_attribute pmcraid_driver_version_attr = { @@ -6096,9 +6096,8 @@ static int __init pmcraid_init(void) dev_t dev; int error; - pmcraid_info("%s Device Driver version: %s %s\n", - PMCRAID_DRIVER_NAME, - PMCRAID_DRIVER_VERSION, PMCRAID_DRIVER_DATE); + pmcraid_info("%s Device Driver version: %s\n", + PMCRAID_DRIVER_NAME, PMCRAID_DRIVER_VERSION); error = alloc_chrdev_region(&dev, 0, PMCRAID_MAX_ADAPTERS, diff --git a/drivers/scsi/pmcraid.h b/drivers/scsi/pmcraid.h index 34e4c915002..f920baf3ff2 100644 --- a/drivers/scsi/pmcraid.h +++ b/drivers/scsi/pmcraid.h @@ -43,7 +43,6 @@ #define PMCRAID_DRIVER_NAME "PMC MaxRAID" #define PMCRAID_DEVFILE "pmcsas" #define PMCRAID_DRIVER_VERSION "1.0.3" -#define PMCRAID_DRIVER_DATE __DATE__ #define PMCRAID_FW_VERSION_1 0x002 diff --git a/drivers/scsi/wd33c93.c b/drivers/scsi/wd33c93.c index 97ae716134d..c0ee4ea28a1 100644 --- a/drivers/scsi/wd33c93.c +++ b/drivers/scsi/wd33c93.c @@ -2051,8 +2051,7 @@ wd33c93_init(struct Scsi_Host *instance, const wd33c93_regs regs, for (i = 0; i < MAX_SETUP_ARGS; i++) printk("%s,", setup_args[i]); printk("\n"); - printk(" Version %s - %s, Compiled %s at %s\n", - WD33C93_VERSION, WD33C93_DATE, __DATE__, __TIME__); + printk(" Version %s - %s\n", WD33C93_VERSION, WD33C93_DATE); } int @@ -2132,8 +2131,8 @@ wd33c93_proc_info(struct Scsi_Host *instance, char *buf, char **start, off_t off bp = buf; *bp = '\0'; if (hd->proc & PR_VERSION) { - sprintf(tbuf, "\nVersion %s - %s. Compiled %s %s", - WD33C93_VERSION, WD33C93_DATE, __DATE__, __TIME__); + sprintf(tbuf, "\nVersion %s - %s.", + WD33C93_VERSION, WD33C93_DATE); strcat(bp, tbuf); } if (hd->proc & PR_INFO) { diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index fc14b8dea0d..fbd96b29530 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -271,8 +271,8 @@ config SPI_ORION This enables using the SPI master controller on the Orion chips. config SPI_PL022 - tristate "ARM AMBA PL022 SSP controller (EXPERIMENTAL)" - depends on ARM_AMBA && EXPERIMENTAL + tristate "ARM AMBA PL022 SSP controller" + depends on ARM_AMBA default y if MACH_U300 default y if ARCH_REALVIEW default y if INTEGRATOR_IMPD1 diff --git a/drivers/spi/amba-pl022.c b/drivers/spi/amba-pl022.c index 08de58e7f59..6a9e58dd36c 100644 --- a/drivers/spi/amba-pl022.c +++ b/drivers/spi/amba-pl022.c @@ -24,11 +24,6 @@ * GNU General Public License for more details. */ -/* - * TODO: - * - add timeout on polled transfers - */ - #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> @@ -287,6 +282,8 @@ #define CLEAR_ALL_INTERRUPTS 0x3 +#define SPI_POLLING_TIMEOUT 1000 + /* * The type of reading going on on this chip @@ -1063,7 +1060,7 @@ static int __init pl022_dma_probe(struct pl022 *pl022) pl022->master_info->dma_filter, pl022->master_info->dma_rx_param); if (!pl022->dma_rx_channel) { - dev_err(&pl022->adev->dev, "no RX DMA channel!\n"); + dev_dbg(&pl022->adev->dev, "no RX DMA channel!\n"); goto err_no_rxchan; } @@ -1071,13 +1068,13 @@ static int __init pl022_dma_probe(struct pl022 *pl022) pl022->master_info->dma_filter, pl022->master_info->dma_tx_param); if (!pl022->dma_tx_channel) { - dev_err(&pl022->adev->dev, "no TX DMA channel!\n"); + dev_dbg(&pl022->adev->dev, "no TX DMA channel!\n"); goto err_no_txchan; } pl022->dummypage = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!pl022->dummypage) { - dev_err(&pl022->adev->dev, "no DMA dummypage!\n"); + dev_dbg(&pl022->adev->dev, "no DMA dummypage!\n"); goto err_no_dummypage; } @@ -1093,6 +1090,8 @@ err_no_txchan: dma_release_channel(pl022->dma_rx_channel); pl022->dma_rx_channel = NULL; err_no_rxchan: + dev_err(&pl022->adev->dev, + "Failed to work in dma mode, work without dma!\n"); return -ENODEV; } @@ -1378,6 +1377,7 @@ static void do_polling_transfer(struct pl022 *pl022) struct spi_transfer *transfer = NULL; struct spi_transfer *previous = NULL; struct chip_data *chip; + unsigned long time, timeout; chip = pl022->cur_chip; message = pl022->cur_msg; @@ -1415,9 +1415,19 @@ static void do_polling_transfer(struct pl022 *pl022) SSP_CR1(pl022->virtbase)); dev_dbg(&pl022->adev->dev, "polling transfer ongoing ...\n"); - /* FIXME: insert a timeout so we don't hang here indefinitely */ - while (pl022->tx < pl022->tx_end || pl022->rx < pl022->rx_end) + + timeout = jiffies + msecs_to_jiffies(SPI_POLLING_TIMEOUT); + while (pl022->tx < pl022->tx_end || pl022->rx < pl022->rx_end) { + time = jiffies; readwriter(pl022); + if (time_after(time, timeout)) { + dev_warn(&pl022->adev->dev, + "%s: timeout!\n", __func__); + message->state = STATE_ERROR; + goto out; + } + cpu_relax(); + } /* Update total byte transferred */ message->actual_length += pl022->cur_transfer->len; @@ -1426,7 +1436,7 @@ static void do_polling_transfer(struct pl022 *pl022) /* Move to next transfer */ message->state = next_transfer(pl022); } - +out: /* Handle end of message */ if (message->state == STATE_DONE) message->status = 0; @@ -2107,7 +2117,7 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) if (platform_info->enable_dma) { status = pl022_dma_probe(pl022); if (status != 0) - goto err_no_dma; + platform_info->enable_dma = 0; } /* Initialize and start queue */ @@ -2143,7 +2153,6 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) err_init_queue: destroy_queue(pl022); pl022_dma_remove(pl022); - err_no_dma: free_irq(adev->irq[0], pl022); err_no_irq: clk_put(pl022->clk); diff --git a/drivers/spi/dw_spi.c b/drivers/spi/dw_spi.c index 871e337c917..919fa9d9e16 100644 --- a/drivers/spi/dw_spi.c +++ b/drivers/spi/dw_spi.c @@ -58,8 +58,6 @@ struct chip_data { u8 bits_per_word; u16 clk_div; /* baud rate divider */ u32 speed_hz; /* baud rate */ - int (*write)(struct dw_spi *dws); - int (*read)(struct dw_spi *dws); void (*cs_control)(u32 command); }; @@ -162,107 +160,70 @@ static inline void mrst_spi_debugfs_remove(struct dw_spi *dws) } #endif /* CONFIG_DEBUG_FS */ -static void wait_till_not_busy(struct dw_spi *dws) +/* Return the max entries we can fill into tx fifo */ +static inline u32 tx_max(struct dw_spi *dws) { - unsigned long end = jiffies + 1 + usecs_to_jiffies(5000); + u32 tx_left, tx_room, rxtx_gap; - while (time_before(jiffies, end)) { - if (!(dw_readw(dws, sr) & SR_BUSY)) - return; - cpu_relax(); - } - dev_err(&dws->master->dev, - "DW SPI: Status keeps busy for 5000us after a read/write!\n"); -} - -static void flush(struct dw_spi *dws) -{ - while (dw_readw(dws, sr) & SR_RF_NOT_EMPT) { - dw_readw(dws, dr); - cpu_relax(); - } - - wait_till_not_busy(dws); -} - -static int null_writer(struct dw_spi *dws) -{ - u8 n_bytes = dws->n_bytes; + tx_left = (dws->tx_end - dws->tx) / dws->n_bytes; + tx_room = dws->fifo_len - dw_readw(dws, txflr); - if (!(dw_readw(dws, sr) & SR_TF_NOT_FULL) - || (dws->tx == dws->tx_end)) - return 0; - dw_writew(dws, dr, 0); - dws->tx += n_bytes; + /* + * Another concern is about the tx/rx mismatch, we + * though to use (dws->fifo_len - rxflr - txflr) as + * one maximum value for tx, but it doesn't cover the + * data which is out of tx/rx fifo and inside the + * shift registers. So a control from sw point of + * view is taken. + */ + rxtx_gap = ((dws->rx_end - dws->rx) - (dws->tx_end - dws->tx)) + / dws->n_bytes; - wait_till_not_busy(dws); - return 1; + return min3(tx_left, tx_room, (u32) (dws->fifo_len - rxtx_gap)); } -static int null_reader(struct dw_spi *dws) +/* Return the max entries we should read out of rx fifo */ +static inline u32 rx_max(struct dw_spi *dws) { - u8 n_bytes = dws->n_bytes; + u32 rx_left = (dws->rx_end - dws->rx) / dws->n_bytes; - while ((dw_readw(dws, sr) & SR_RF_NOT_EMPT) - && (dws->rx < dws->rx_end)) { - dw_readw(dws, dr); - dws->rx += n_bytes; - } - wait_till_not_busy(dws); - return dws->rx == dws->rx_end; + return min(rx_left, (u32)dw_readw(dws, rxflr)); } -static int u8_writer(struct dw_spi *dws) +static void dw_writer(struct dw_spi *dws) { - if (!(dw_readw(dws, sr) & SR_TF_NOT_FULL) - || (dws->tx == dws->tx_end)) - return 0; + u32 max = tx_max(dws); + u16 txw = 0; - dw_writew(dws, dr, *(u8 *)(dws->tx)); - ++dws->tx; - - wait_till_not_busy(dws); - return 1; -} - -static int u8_reader(struct dw_spi *dws) -{ - while ((dw_readw(dws, sr) & SR_RF_NOT_EMPT) - && (dws->rx < dws->rx_end)) { - *(u8 *)(dws->rx) = dw_readw(dws, dr); - ++dws->rx; + while (max--) { + /* Set the tx word if the transfer's original "tx" is not null */ + if (dws->tx_end - dws->len) { + if (dws->n_bytes == 1) + txw = *(u8 *)(dws->tx); + else + txw = *(u16 *)(dws->tx); + } + dw_writew(dws, dr, txw); + dws->tx += dws->n_bytes; } - - wait_till_not_busy(dws); - return dws->rx == dws->rx_end; } -static int u16_writer(struct dw_spi *dws) +static void dw_reader(struct dw_spi *dws) { - if (!(dw_readw(dws, sr) & SR_TF_NOT_FULL) - || (dws->tx == dws->tx_end)) - return 0; + u32 max = rx_max(dws); + u16 rxw; - dw_writew(dws, dr, *(u16 *)(dws->tx)); - dws->tx += 2; - - wait_till_not_busy(dws); - return 1; -} - -static int u16_reader(struct dw_spi *dws) -{ - u16 temp; - - while ((dw_readw(dws, sr) & SR_RF_NOT_EMPT) - && (dws->rx < dws->rx_end)) { - temp = dw_readw(dws, dr); - *(u16 *)(dws->rx) = temp; - dws->rx += 2; + while (max--) { + rxw = dw_readw(dws, dr); + /* Care rx only if the transfer's original "rx" is not null */ + if (dws->rx_end - dws->len) { + if (dws->n_bytes == 1) + *(u8 *)(dws->rx) = rxw; + else + *(u16 *)(dws->rx) = rxw; + } + dws->rx += dws->n_bytes; } - - wait_till_not_busy(dws); - return dws->rx == dws->rx_end; } static void *next_transfer(struct dw_spi *dws) @@ -334,8 +295,7 @@ static void giveback(struct dw_spi *dws) static void int_error_stop(struct dw_spi *dws, const char *msg) { - /* Stop and reset hw */ - flush(dws); + /* Stop the hw */ spi_enable_chip(dws, 0); dev_err(&dws->master->dev, "%s\n", msg); @@ -362,35 +322,28 @@ EXPORT_SYMBOL_GPL(dw_spi_xfer_done); static irqreturn_t interrupt_transfer(struct dw_spi *dws) { - u16 irq_status, irq_mask = 0x3f; - u32 int_level = dws->fifo_len / 2; - u32 left; + u16 irq_status = dw_readw(dws, isr); - irq_status = dw_readw(dws, isr) & irq_mask; /* Error handling */ if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) { dw_readw(dws, txoicr); dw_readw(dws, rxoicr); dw_readw(dws, rxuicr); - int_error_stop(dws, "interrupt_transfer: fifo overrun"); + int_error_stop(dws, "interrupt_transfer: fifo overrun/underrun"); return IRQ_HANDLED; } + dw_reader(dws); + if (dws->rx_end == dws->rx) { + spi_mask_intr(dws, SPI_INT_TXEI); + dw_spi_xfer_done(dws); + return IRQ_HANDLED; + } if (irq_status & SPI_INT_TXEI) { spi_mask_intr(dws, SPI_INT_TXEI); - - left = (dws->tx_end - dws->tx) / dws->n_bytes; - left = (left > int_level) ? int_level : left; - - while (left--) - dws->write(dws); - dws->read(dws); - - /* Re-enable the IRQ if there is still data left to tx */ - if (dws->tx_end > dws->tx) - spi_umask_intr(dws, SPI_INT_TXEI); - else - dw_spi_xfer_done(dws); + dw_writer(dws); + /* Enable TX irq always, it will be disabled when RX finished */ + spi_umask_intr(dws, SPI_INT_TXEI); } return IRQ_HANDLED; @@ -399,15 +352,13 @@ static irqreturn_t interrupt_transfer(struct dw_spi *dws) static irqreturn_t dw_spi_irq(int irq, void *dev_id) { struct dw_spi *dws = dev_id; - u16 irq_status, irq_mask = 0x3f; + u16 irq_status = dw_readw(dws, isr) & 0x3f; - irq_status = dw_readw(dws, isr) & irq_mask; if (!irq_status) return IRQ_NONE; if (!dws->cur_msg) { spi_mask_intr(dws, SPI_INT_TXEI); - /* Never fail */ return IRQ_HANDLED; } @@ -417,13 +368,11 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) /* Must be called inside pump_transfers() */ static void poll_transfer(struct dw_spi *dws) { - while (dws->write(dws)) - dws->read(dws); - /* - * There is a possibility that the last word of a transaction - * will be lost if data is not ready. Re-read to solve this issue. - */ - dws->read(dws); + do { + dw_writer(dws); + dw_reader(dws); + cpu_relax(); + } while (dws->rx_end > dws->rx); dw_spi_xfer_done(dws); } @@ -483,8 +432,6 @@ static void pump_transfers(unsigned long data) dws->tx_end = dws->tx + transfer->len; dws->rx = transfer->rx_buf; dws->rx_end = dws->rx + transfer->len; - dws->write = dws->tx ? chip->write : null_writer; - dws->read = dws->rx ? chip->read : null_reader; dws->cs_change = transfer->cs_change; dws->len = dws->cur_transfer->len; if (chip != dws->prev_chip) @@ -518,20 +465,8 @@ static void pump_transfers(unsigned long data) switch (bits) { case 8: - dws->n_bytes = 1; - dws->dma_width = 1; - dws->read = (dws->read != null_reader) ? - u8_reader : null_reader; - dws->write = (dws->write != null_writer) ? - u8_writer : null_writer; - break; case 16: - dws->n_bytes = 2; - dws->dma_width = 2; - dws->read = (dws->read != null_reader) ? - u16_reader : null_reader; - dws->write = (dws->write != null_writer) ? - u16_writer : null_writer; + dws->n_bytes = dws->dma_width = bits >> 3; break; default: printk(KERN_ERR "MRST SPI0: unsupported bits:" @@ -575,7 +510,7 @@ static void pump_transfers(unsigned long data) txint_level = dws->fifo_len / 2; txint_level = (templen > txint_level) ? txint_level : templen; - imask |= SPI_INT_TXEI; + imask |= SPI_INT_TXEI | SPI_INT_TXOI | SPI_INT_RXUI | SPI_INT_RXOI; dws->transfer_handler = interrupt_transfer; } @@ -733,13 +668,9 @@ static int dw_spi_setup(struct spi_device *spi) if (spi->bits_per_word <= 8) { chip->n_bytes = 1; chip->dma_width = 1; - chip->read = u8_reader; - chip->write = u8_writer; } else if (spi->bits_per_word <= 16) { chip->n_bytes = 2; chip->dma_width = 2; - chip->read = u16_reader; - chip->write = u16_writer; } else { /* Never take >16b case for MRST SPIC */ dev_err(&spi->dev, "invalid wordsize\n"); @@ -851,7 +782,6 @@ static void spi_hw_init(struct dw_spi *dws) spi_enable_chip(dws, 0); spi_mask_intr(dws, 0xff); spi_enable_chip(dws, 1); - flush(dws); /* * Try to detect the FIFO depth if not set by interface driver, diff --git a/drivers/spi/dw_spi.h b/drivers/spi/dw_spi.h index b23e452adaf..7a5e78d2a5c 100644 --- a/drivers/spi/dw_spi.h +++ b/drivers/spi/dw_spi.h @@ -137,8 +137,6 @@ struct dw_spi { u8 max_bits_per_word; /* maxim is 16b */ u32 dma_width; int cs_change; - int (*write)(struct dw_spi *dws); - int (*read)(struct dw_spi *dws); irqreturn_t (*transfer_handler)(struct dw_spi *dws); void (*cs_control)(u32 command); diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 82b9a428c32..2e13a14bba3 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1047,8 +1047,8 @@ static u8 *buf; * spi_{async,sync}() calls with dma-safe buffers. */ int spi_write_then_read(struct spi_device *spi, - const u8 *txbuf, unsigned n_tx, - u8 *rxbuf, unsigned n_rx) + const void *txbuf, unsigned n_tx, + void *rxbuf, unsigned n_rx) { static DEFINE_MUTEX(lock); diff --git a/drivers/spi/spi_nuc900.c b/drivers/spi/spi_nuc900.c index d5be18b3078..3cd15f690f1 100644 --- a/drivers/spi/spi_nuc900.c +++ b/drivers/spi/spi_nuc900.c @@ -463,7 +463,7 @@ static int __devexit nuc900_spi_remove(struct platform_device *dev) platform_set_drvdata(dev, NULL); - spi_unregister_master(hw->master); + spi_bitbang_stop(&hw->bitbang); clk_disable(hw->clk); clk_put(hw->clk); diff --git a/drivers/spi/spi_s3c24xx.c b/drivers/spi/spi_s3c24xx.c index 151a95e4065..1a5fcabfd56 100644 --- a/drivers/spi/spi_s3c24xx.c +++ b/drivers/spi/spi_s3c24xx.c @@ -668,7 +668,7 @@ static int __exit s3c24xx_spi_remove(struct platform_device *dev) platform_set_drvdata(dev, NULL); - spi_unregister_master(hw->master); + spi_bitbang_stop(&hw->bitbang); clk_disable(hw->clk); clk_put(hw->clk); diff --git a/drivers/spi/spi_sh.c b/drivers/spi/spi_sh.c index 869a07d375d..9eedd71ad89 100644 --- a/drivers/spi/spi_sh.c +++ b/drivers/spi/spi_sh.c @@ -427,10 +427,10 @@ static int __devexit spi_sh_remove(struct platform_device *pdev) { struct spi_sh_data *ss = dev_get_drvdata(&pdev->dev); + spi_unregister_master(ss->master); destroy_workqueue(ss->workqueue); free_irq(ss->irq, ss); iounmap(ss->addr); - spi_master_put(ss->master); return 0; } diff --git a/drivers/spi/spi_tegra.c b/drivers/spi/spi_tegra.c index 891e5909038..6c3aa6ecaad 100644 --- a/drivers/spi/spi_tegra.c +++ b/drivers/spi/spi_tegra.c @@ -578,6 +578,7 @@ static int __devexit spi_tegra_remove(struct platform_device *pdev) master = dev_get_drvdata(&pdev->dev); tspi = spi_master_get_devdata(master); + spi_unregister_master(master); tegra_dma_free_channel(tspi->rx_dma); dma_free_coherent(&pdev->dev, sizeof(u32) * BB_LEN, @@ -586,7 +587,6 @@ static int __devexit spi_tegra_remove(struct platform_device *pdev) clk_put(tspi->clk); iounmap(tspi->base); - spi_master_put(master); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); release_mem_region(r->start, (r->end - r->start) + 1); diff --git a/drivers/spi/xilinx_spi.c b/drivers/spi/xilinx_spi.c index c69c6f2c2c5..4d2c75df886 100644 --- a/drivers/spi/xilinx_spi.c +++ b/drivers/spi/xilinx_spi.c @@ -18,7 +18,6 @@ #include <linux/interrupt.h> #include <linux/of.h> #include <linux/platform_device.h> -#include <linux/mfd/core.h> #include <linux/spi/spi.h> #include <linux/spi/spi_bitbang.h> #include <linux/spi/xilinx_spi.h> @@ -471,7 +470,7 @@ static int __devinit xilinx_spi_probe(struct platform_device *dev) struct spi_master *master; u8 i; - pdata = mfd_get_data(dev); + pdata = dev->dev.platform_data; if (pdata) { num_cs = pdata->num_chipselect; little_endian = pdata->little_endian; diff --git a/drivers/staging/generic_serial/rio/rioinit.c b/drivers/staging/generic_serial/rio/rioinit.c index 24a282bb89d..fb62b383f1d 100644 --- a/drivers/staging/generic_serial/rio/rioinit.c +++ b/drivers/staging/generic_serial/rio/rioinit.c @@ -381,7 +381,7 @@ struct rioVersion *RIOVersid(void) { strlcpy(stVersion.version, "RIO driver for linux V1.0", sizeof(stVersion.version)); - strlcpy(stVersion.buildDate, __DATE__, + strlcpy(stVersion.buildDate, "Aug 15 2010", sizeof(stVersion.buildDate)); return &stVersion; diff --git a/drivers/tty/cyclades.c b/drivers/tty/cyclades.c index bfa05e80182..c0e8f2eeb88 100644 --- a/drivers/tty/cyclades.c +++ b/drivers/tty/cyclades.c @@ -4096,8 +4096,7 @@ static int __init cy_init(void) if (!cy_serial_driver) goto err; - printk(KERN_INFO "Cyclades driver " CY_VERSION " (built %s %s)\n", - __DATE__, __TIME__); + printk(KERN_INFO "Cyclades driver " CY_VERSION "\n"); /* Initialize the tty_driver structure */ diff --git a/drivers/tty/nozomi.c b/drivers/tty/nozomi.c index b1aecc7bb32..fd347ff34d0 100644 --- a/drivers/tty/nozomi.c +++ b/drivers/tty/nozomi.c @@ -61,8 +61,7 @@ #include <linux/delay.h> -#define VERSION_STRING DRIVER_DESC " 2.1d (build date: " \ - __DATE__ " " __TIME__ ")" +#define VERSION_STRING DRIVER_DESC " 2.1d" /* Macros definitions */ diff --git a/drivers/tty/serial/m32r_sio.c b/drivers/tty/serial/m32r_sio.c index bea5c215460..84db7321cce 100644 --- a/drivers/tty/serial/m32r_sio.c +++ b/drivers/tty/serial/m32r_sio.c @@ -907,9 +907,10 @@ static int m32r_sio_request_port(struct uart_port *port) return ret; } -static void m32r_sio_config_port(struct uart_port *port, int flags) +static void m32r_sio_config_port(struct uart_port *port, int unused) { struct uart_sio_port *up = (struct uart_sio_port *)port; + unsigned long flags; spin_lock_irqsave(&up->port.lock, flags); diff --git a/drivers/usb/otg/twl6030-usb.c b/drivers/usb/otg/twl6030-usb.c index 3f2e07011a4..cfb5aa72b19 100644 --- a/drivers/usb/otg/twl6030-usb.c +++ b/drivers/usb/otg/twl6030-usb.c @@ -100,6 +100,7 @@ struct twl6030_usb { u8 linkstat; u8 asleep; bool irq_enabled; + unsigned long features; }; #define xceiv_to_twl(x) container_of((x), struct twl6030_usb, otg) @@ -204,6 +205,12 @@ static int twl6030_start_srp(struct otg_transceiver *x) static int twl6030_usb_ldo_init(struct twl6030_usb *twl) { + char *regulator_name; + + if (twl->features & TWL6025_SUBCLASS) + regulator_name = "ldousb"; + else + regulator_name = "vusb"; /* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */ twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG); @@ -214,7 +221,7 @@ static int twl6030_usb_ldo_init(struct twl6030_usb *twl) /* Program MISC2 register and set bit VUSB_IN_VBAT */ twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2); - twl->usb3v3 = regulator_get(twl->dev, "vusb"); + twl->usb3v3 = regulator_get(twl->dev, regulator_name); if (IS_ERR(twl->usb3v3)) return -ENODEV; @@ -409,6 +416,7 @@ static int __devinit twl6030_usb_probe(struct platform_device *pdev) twl->dev = &pdev->dev; twl->irq1 = platform_get_irq(pdev, 0); twl->irq2 = platform_get_irq(pdev, 1); + twl->features = pdata->features; twl->otg.dev = twl->dev; twl->otg.label = "twl6030"; twl->otg.set_host = twl6030_set_host; diff --git a/drivers/video/backlight/88pm860x_bl.c b/drivers/video/backlight/88pm860x_bl.c index c8b520e9a11..c04b94da81f 100644 --- a/drivers/video/backlight/88pm860x_bl.c +++ b/drivers/video/backlight/88pm860x_bl.c @@ -16,7 +16,6 @@ #include <linux/fb.h> #include <linux/i2c.h> #include <linux/backlight.h> -#include <linux/mfd/core.h> #include <linux/mfd/88pm860x.h> #define MAX_BRIGHTNESS (0xFF) @@ -168,7 +167,6 @@ static int pm860x_backlight_probe(struct platform_device *pdev) struct pm860x_backlight_pdata *pdata = NULL; struct pm860x_backlight_data *data; struct backlight_device *bl; - struct mfd_cell *cell; struct resource *res; struct backlight_properties props; unsigned char value; @@ -181,10 +179,7 @@ static int pm860x_backlight_probe(struct platform_device *pdev) return -EINVAL; } - cell = pdev->dev.platform_data; - if (cell == NULL) - return -ENODEV; - pdata = cell->mfd_data; + pdata = pdev->dev.platform_data; if (pdata == NULL) { dev_err(&pdev->dev, "platform data isn't assigned to " "backlight\n"); diff --git a/drivers/video/mb862xx/mb862xxfbdrv.c b/drivers/video/mb862xx/mb862xxfbdrv.c index ea39336addf..f70bd63b018 100644 --- a/drivers/video/mb862xx/mb862xxfbdrv.c +++ b/drivers/video/mb862xx/mb862xxfbdrv.c @@ -16,6 +16,7 @@ #include <linux/fb.h> #include <linux/delay.h> +#include <linux/uaccess.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/pci.h> diff --git a/drivers/video/omap/Makefile b/drivers/video/omap/Makefile index 49226a1b909..25db55696e1 100644 --- a/drivers/video/omap/Makefile +++ b/drivers/video/omap/Makefile @@ -30,7 +30,6 @@ objs-y$(CONFIG_MACH_OMAP_APOLLON) += lcd_apollon.o objs-y$(CONFIG_MACH_OMAP_2430SDP) += lcd_2430sdp.o objs-y$(CONFIG_MACH_OMAP_3430SDP) += lcd_2430sdp.o objs-y$(CONFIG_MACH_OMAP_LDP) += lcd_ldp.o -objs-y$(CONFIG_MACH_OMAP2EVM) += lcd_omap2evm.o objs-y$(CONFIG_MACH_OMAP3EVM) += lcd_omap3evm.o objs-y$(CONFIG_MACH_OMAP3_BEAGLE) += lcd_omap3beagle.o objs-y$(CONFIG_FB_OMAP_LCD_MIPID) += lcd_mipid.o diff --git a/drivers/video/omap/lcd_omap2evm.c b/drivers/video/omap/lcd_omap2evm.c deleted file mode 100644 index 7e7a65c0845..00000000000 --- a/drivers/video/omap/lcd_omap2evm.c +++ /dev/null @@ -1,192 +0,0 @@ -/* - * LCD panel support for the MISTRAL OMAP2EVM board - * - * Author: Arun C <arunedarath@mistralsolutions.com> - * - * Derived from drivers/video/omap/lcd_omap3evm.c - * Derived from drivers/video/omap/lcd-apollon.c - * - * 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 <linux/module.h> -#include <linux/platform_device.h> -#include <linux/gpio.h> -#include <linux/i2c/twl.h> - -#include <plat/mux.h> -#include <asm/mach-types.h> - -#include "omapfb.h" - -#define LCD_PANEL_ENABLE_GPIO 154 -#define LCD_PANEL_LR 128 -#define LCD_PANEL_UD 129 -#define LCD_PANEL_INI 152 -#define LCD_PANEL_QVGA 148 -#define LCD_PANEL_RESB 153 - -#define TWL_LED_LEDEN 0x00 -#define TWL_PWMA_PWMAON 0x00 -#define TWL_PWMA_PWMAOFF 0x01 - -static unsigned int bklight_level; - -static int omap2evm_panel_init(struct lcd_panel *panel, - struct omapfb_device *fbdev) -{ - gpio_request(LCD_PANEL_ENABLE_GPIO, "LCD enable"); - gpio_request(LCD_PANEL_LR, "LCD lr"); - gpio_request(LCD_PANEL_UD, "LCD ud"); - gpio_request(LCD_PANEL_INI, "LCD ini"); - gpio_request(LCD_PANEL_QVGA, "LCD qvga"); - gpio_request(LCD_PANEL_RESB, "LCD resb"); - - gpio_direction_output(LCD_PANEL_ENABLE_GPIO, 1); - gpio_direction_output(LCD_PANEL_RESB, 1); - gpio_direction_output(LCD_PANEL_INI, 1); - gpio_direction_output(LCD_PANEL_QVGA, 0); - gpio_direction_output(LCD_PANEL_LR, 1); - gpio_direction_output(LCD_PANEL_UD, 1); - - twl_i2c_write_u8(TWL4030_MODULE_LED, 0x11, TWL_LED_LEDEN); - twl_i2c_write_u8(TWL4030_MODULE_PWMA, 0x01, TWL_PWMA_PWMAON); - twl_i2c_write_u8(TWL4030_MODULE_PWMA, 0x02, TWL_PWMA_PWMAOFF); - bklight_level = 100; - - return 0; -} - -static void omap2evm_panel_cleanup(struct lcd_panel *panel) -{ - gpio_free(LCD_PANEL_RESB); - gpio_free(LCD_PANEL_QVGA); - gpio_free(LCD_PANEL_INI); - gpio_free(LCD_PANEL_UD); - gpio_free(LCD_PANEL_LR); - gpio_free(LCD_PANEL_ENABLE_GPIO); -} - -static int omap2evm_panel_enable(struct lcd_panel *panel) -{ - gpio_set_value(LCD_PANEL_ENABLE_GPIO, 0); - return 0; -} - -static void omap2evm_panel_disable(struct lcd_panel *panel) -{ - gpio_set_value(LCD_PANEL_ENABLE_GPIO, 1); -} - -static unsigned long omap2evm_panel_get_caps(struct lcd_panel *panel) -{ - return 0; -} - -static int omap2evm_bklight_setlevel(struct lcd_panel *panel, - unsigned int level) -{ - u8 c; - if ((level >= 0) && (level <= 100)) { - c = (125 * (100 - level)) / 100 + 2; - twl_i2c_write_u8(TWL4030_MODULE_PWMA, c, TWL_PWMA_PWMAOFF); - bklight_level = level; - } - return 0; -} - -static unsigned int omap2evm_bklight_getlevel(struct lcd_panel *panel) -{ - return bklight_level; -} - -static unsigned int omap2evm_bklight_getmaxlevel(struct lcd_panel *panel) -{ - return 100; -} - -struct lcd_panel omap2evm_panel = { - .name = "omap2evm", - .config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC | - OMAP_LCDC_INV_HSYNC, - - .bpp = 16, - .data_lines = 18, - .x_res = 480, - .y_res = 640, - .hsw = 3, - .hfp = 0, - .hbp = 28, - .vsw = 2, - .vfp = 1, - .vbp = 0, - - .pixel_clock = 20000, - - .init = omap2evm_panel_init, - .cleanup = omap2evm_panel_cleanup, - .enable = omap2evm_panel_enable, - .disable = omap2evm_panel_disable, - .get_caps = omap2evm_panel_get_caps, - .set_bklight_level = omap2evm_bklight_setlevel, - .get_bklight_level = omap2evm_bklight_getlevel, - .get_bklight_max = omap2evm_bklight_getmaxlevel, -}; - -static int omap2evm_panel_probe(struct platform_device *pdev) -{ - omapfb_register_panel(&omap2evm_panel); - return 0; -} - -static int omap2evm_panel_remove(struct platform_device *pdev) -{ - return 0; -} - -static int omap2evm_panel_suspend(struct platform_device *pdev, - pm_message_t mesg) -{ - return 0; -} - -static int omap2evm_panel_resume(struct platform_device *pdev) -{ - return 0; -} - -struct platform_driver omap2evm_panel_driver = { - .probe = omap2evm_panel_probe, - .remove = omap2evm_panel_remove, - .suspend = omap2evm_panel_suspend, - .resume = omap2evm_panel_resume, - .driver = { - .name = "omap2evm_lcd", - .owner = THIS_MODULE, - }, -}; - -static int __init omap2evm_panel_drv_init(void) -{ - return platform_driver_register(&omap2evm_panel_driver); -} - -static void __exit omap2evm_panel_drv_exit(void) -{ - platform_driver_unregister(&omap2evm_panel_driver); -} - -module_init(omap2evm_panel_drv_init); -module_exit(omap2evm_panel_drv_exit); diff --git a/drivers/video/tmiofb.c b/drivers/video/tmiofb.c index 0c341d73960..cd1c4dcef8f 100644 --- a/drivers/video/tmiofb.c +++ b/drivers/video/tmiofb.c @@ -250,7 +250,7 @@ static irqreturn_t tmiofb_irq(int irq, void *__info) */ static int tmiofb_hw_stop(struct platform_device *dev) { - struct tmio_fb_data *data = mfd_get_data(dev); + struct tmio_fb_data *data = dev->dev.platform_data; struct fb_info *info = platform_get_drvdata(dev); struct tmiofb_par *par = info->par; @@ -311,7 +311,7 @@ static int tmiofb_hw_init(struct platform_device *dev) */ static void tmiofb_hw_mode(struct platform_device *dev) { - struct tmio_fb_data *data = mfd_get_data(dev); + struct tmio_fb_data *data = dev->dev.platform_data; struct fb_info *info = platform_get_drvdata(dev); struct fb_videomode *mode = info->mode; struct tmiofb_par *par = info->par; @@ -557,8 +557,7 @@ static int tmiofb_ioctl(struct fb_info *fbi, static struct fb_videomode * tmiofb_find_mode(struct fb_info *info, struct fb_var_screeninfo *var) { - struct tmio_fb_data *data = - mfd_get_data(to_platform_device(info->device)); + struct tmio_fb_data *data = info->device->platform_data; struct fb_videomode *best = NULL; int i; @@ -578,8 +577,7 @@ static int tmiofb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct fb_videomode *mode; - struct tmio_fb_data *data = - mfd_get_data(to_platform_device(info->device)); + struct tmio_fb_data *data = info->device->platform_data; mode = tmiofb_find_mode(info, var); if (!mode || var->bits_per_pixel > 16) @@ -680,7 +678,7 @@ static struct fb_ops tmiofb_ops = { static int __devinit tmiofb_probe(struct platform_device *dev) { const struct mfd_cell *cell = mfd_get_cell(dev); - struct tmio_fb_data *data = mfd_get_data(dev); + struct tmio_fb_data *data = dev->dev.platform_data; struct resource *ccr = platform_get_resource(dev, IORESOURCE_MEM, 1); struct resource *lcr = platform_get_resource(dev, IORESOURCE_MEM, 0); struct resource *vram = platform_get_resource(dev, IORESOURCE_MEM, 2); diff --git a/drivers/video/via/via-gpio.c b/drivers/video/via/via-gpio.c index c2a0a1cfd3b..ab5341814c7 100644 --- a/drivers/video/via/via-gpio.c +++ b/drivers/video/via/via-gpio.c @@ -145,7 +145,7 @@ static int via_gpio_get(struct gpio_chip *chip, unsigned int nr) } -static struct viafb_gpio_cfg gpio_config = { +static struct viafb_gpio_cfg viafb_gpio_config = { .gpio_chip = { .label = "VIAFB onboard GPIO", .owner = THIS_MODULE, @@ -183,8 +183,8 @@ static int viafb_gpio_resume(void *private) { int i; - for (i = 0; i < gpio_config.gpio_chip.ngpio; i += 2) - viafb_gpio_enable(gpio_config.active_gpios[i]); + for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) + viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); return 0; } @@ -201,9 +201,9 @@ int viafb_gpio_lookup(const char *name) { int i; - for (i = 0; i < gpio_config.gpio_chip.ngpio; i++) - if (!strcmp(name, gpio_config.active_gpios[i]->vg_name)) - return gpio_config.gpio_chip.base + i; + for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i++) + if (!strcmp(name, viafb_gpio_config.active_gpios[i]->vg_name)) + return viafb_gpio_config.gpio_chip.base + i; return -1; } EXPORT_SYMBOL_GPL(viafb_gpio_lookup); @@ -229,14 +229,15 @@ static __devinit int viafb_gpio_probe(struct platform_device *platdev) for (gpio = viafb_all_gpios; gpio < viafb_all_gpios + VIAFB_NUM_GPIOS; gpio++) if (gpio->vg_port_index == port_cfg[i].ioport_index) { - gpio_config.active_gpios[ngpio] = gpio; - gpio_config.gpio_names[ngpio] = gpio->vg_name; + viafb_gpio_config.active_gpios[ngpio] = gpio; + viafb_gpio_config.gpio_names[ngpio] = + gpio->vg_name; ngpio++; } } - gpio_config.gpio_chip.ngpio = ngpio; - gpio_config.gpio_chip.names = gpio_config.gpio_names; - gpio_config.vdev = vdev; + viafb_gpio_config.gpio_chip.ngpio = ngpio; + viafb_gpio_config.gpio_chip.names = viafb_gpio_config.gpio_names; + viafb_gpio_config.vdev = vdev; if (ngpio == 0) { printk(KERN_INFO "viafb: no GPIOs configured\n"); return 0; @@ -245,18 +246,18 @@ static __devinit int viafb_gpio_probe(struct platform_device *platdev) * Enable the ports. They come in pairs, with a single * enable bit for both. */ - spin_lock_irqsave(&gpio_config.vdev->reg_lock, flags); + spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); for (i = 0; i < ngpio; i += 2) - viafb_gpio_enable(gpio_config.active_gpios[i]); - spin_unlock_irqrestore(&gpio_config.vdev->reg_lock, flags); + viafb_gpio_enable(viafb_gpio_config.active_gpios[i]); + spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); /* * Get registered. */ - gpio_config.gpio_chip.base = -1; /* Dynamic */ - ret = gpiochip_add(&gpio_config.gpio_chip); + viafb_gpio_config.gpio_chip.base = -1; /* Dynamic */ + ret = gpiochip_add(&viafb_gpio_config.gpio_chip); if (ret) { printk(KERN_ERR "viafb: failed to add gpios (%d)\n", ret); - gpio_config.gpio_chip.ngpio = 0; + viafb_gpio_config.gpio_chip.ngpio = 0; } #ifdef CONFIG_PM viafb_pm_register(&viafb_gpio_pm_hooks); @@ -277,8 +278,8 @@ static int viafb_gpio_remove(struct platform_device *platdev) /* * Get unregistered. */ - if (gpio_config.gpio_chip.ngpio > 0) { - ret = gpiochip_remove(&gpio_config.gpio_chip); + if (viafb_gpio_config.gpio_chip.ngpio > 0) { + ret = gpiochip_remove(&viafb_gpio_config.gpio_chip); if (ret) { /* Somebody still using it? */ printk(KERN_ERR "Viafb: GPIO remove failed\n"); return ret; @@ -287,11 +288,11 @@ static int viafb_gpio_remove(struct platform_device *platdev) /* * Disable the ports. */ - spin_lock_irqsave(&gpio_config.vdev->reg_lock, flags); - for (i = 0; i < gpio_config.gpio_chip.ngpio; i += 2) - viafb_gpio_disable(gpio_config.active_gpios[i]); - gpio_config.gpio_chip.ngpio = 0; - spin_unlock_irqrestore(&gpio_config.vdev->reg_lock, flags); + spin_lock_irqsave(&viafb_gpio_config.vdev->reg_lock, flags); + for (i = 0; i < viafb_gpio_config.gpio_chip.ngpio; i += 2) + viafb_gpio_disable(viafb_gpio_config.active_gpios[i]); + viafb_gpio_config.gpio_chip.ngpio = 0; + spin_unlock_irqrestore(&viafb_gpio_config.vdev->reg_lock, flags); return ret; } diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig index 7c608c5ccf8..00d615d7aa2 100644 --- a/drivers/w1/masters/Kconfig +++ b/drivers/w1/masters/Kconfig @@ -42,7 +42,7 @@ config W1_MASTER_MXC config W1_MASTER_DS1WM tristate "Maxim DS1WM 1-wire busmaster" - depends on W1 && ARM && HAVE_CLK + depends on W1 help Say Y here to enable the DS1WM 1-wire driver, such as that in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like diff --git a/drivers/w1/masters/ds1wm.c b/drivers/w1/masters/ds1wm.c index 2f4fa02744a..ad57593d224 100644 --- a/drivers/w1/masters/ds1wm.c +++ b/drivers/w1/masters/ds1wm.c @@ -33,6 +33,7 @@ #define DS1WM_INT 0x02 /* R/W interrupt status */ #define DS1WM_INT_EN 0x03 /* R/W interrupt enable */ #define DS1WM_CLKDIV 0x04 /* R/W 5 bits of divisor and pre-scale */ +#define DS1WM_CNTRL 0x05 /* R/W master control register (not used yet) */ #define DS1WM_CMD_1W_RESET (1 << 0) /* force reset on 1-wire bus */ #define DS1WM_CMD_SRA (1 << 1) /* enable Search ROM accelerator mode */ @@ -56,6 +57,7 @@ #define DS1WM_INTEN_ERSRF (1 << 5) /* enable rx shift register full int */ #define DS1WM_INTEN_DQO (1 << 6) /* enable direct bus driving ops */ +#define DS1WM_INTEN_NOT_IAS (~DS1WM_INTEN_IAS) /* all but INTR active state */ #define DS1WM_TIMEOUT (HZ * 5) @@ -63,41 +65,50 @@ static struct { unsigned long freq; unsigned long divisor; } freq[] = { - { 4000000, 0x8 }, - { 5000000, 0x2 }, - { 6000000, 0x5 }, - { 7000000, 0x3 }, - { 8000000, 0xc }, - { 10000000, 0x6 }, - { 12000000, 0x9 }, - { 14000000, 0x7 }, - { 16000000, 0x10 }, - { 20000000, 0xa }, - { 24000000, 0xd }, - { 28000000, 0xb }, - { 32000000, 0x14 }, - { 40000000, 0xe }, - { 48000000, 0x11 }, - { 56000000, 0xf }, - { 64000000, 0x18 }, - { 80000000, 0x12 }, - { 96000000, 0x15 }, - { 112000000, 0x13 }, - { 128000000, 0x1c }, + { 1000000, 0x80 }, + { 2000000, 0x84 }, + { 3000000, 0x81 }, + { 4000000, 0x88 }, + { 5000000, 0x82 }, + { 6000000, 0x85 }, + { 7000000, 0x83 }, + { 8000000, 0x8c }, + { 10000000, 0x86 }, + { 12000000, 0x89 }, + { 14000000, 0x87 }, + { 16000000, 0x90 }, + { 20000000, 0x8a }, + { 24000000, 0x8d }, + { 28000000, 0x8b }, + { 32000000, 0x94 }, + { 40000000, 0x8e }, + { 48000000, 0x91 }, + { 56000000, 0x8f }, + { 64000000, 0x98 }, + { 80000000, 0x92 }, + { 96000000, 0x95 }, + { 112000000, 0x93 }, + { 128000000, 0x9c }, +/* you can continue this table, consult the OPERATION - CLOCK DIVISOR + section of the ds1wm spec sheet. */ }; struct ds1wm_data { - void __iomem *map; - int bus_shift; /* # of shifts to calc register offsets */ + void __iomem *map; + int bus_shift; /* # of shifts to calc register offsets */ struct platform_device *pdev; - const struct mfd_cell *cell; - int irq; - int active_high; - int slave_present; - void *reset_complete; - void *read_complete; - void *write_complete; - u8 read_byte; /* last byte received */ + const struct mfd_cell *cell; + int irq; + int slave_present; + void *reset_complete; + void *read_complete; + void *write_complete; + int read_error; + /* last byte received */ + u8 read_byte; + /* byte to write that makes all intr disabled, */ + /* considering active_state (IAS) (optimization) */ + u8 int_en_reg_none; }; static inline void ds1wm_write_register(struct ds1wm_data *ds1wm_data, u32 reg, @@ -115,23 +126,39 @@ static inline u8 ds1wm_read_register(struct ds1wm_data *ds1wm_data, u32 reg) static irqreturn_t ds1wm_isr(int isr, void *data) { struct ds1wm_data *ds1wm_data = data; - u8 intr = ds1wm_read_register(ds1wm_data, DS1WM_INT); + u8 intr; + u8 inten = ds1wm_read_register(ds1wm_data, DS1WM_INT_EN); + /* if no bits are set in int enable register (except the IAS) + than go no further, reading the regs below has side effects */ + if (!(inten & DS1WM_INTEN_NOT_IAS)) + return IRQ_NONE; - ds1wm_data->slave_present = (intr & DS1WM_INT_PDR) ? 0 : 1; + ds1wm_write_register(ds1wm_data, + DS1WM_INT_EN, ds1wm_data->int_en_reg_none); - if ((intr & DS1WM_INT_PD) && ds1wm_data->reset_complete) - complete(ds1wm_data->reset_complete); + /* this read action clears the INTR and certain flags in ds1wm */ + intr = ds1wm_read_register(ds1wm_data, DS1WM_INT); - if ((intr & DS1WM_INT_TSRE) && ds1wm_data->write_complete) - complete(ds1wm_data->write_complete); + ds1wm_data->slave_present = (intr & DS1WM_INT_PDR) ? 0 : 1; + if ((intr & DS1WM_INT_TSRE) && ds1wm_data->write_complete) { + inten &= ~DS1WM_INTEN_ETMT; + complete(ds1wm_data->write_complete); + } if (intr & DS1WM_INT_RBF) { + /* this read clears the RBF flag */ ds1wm_data->read_byte = ds1wm_read_register(ds1wm_data, - DS1WM_DATA); + DS1WM_DATA); + inten &= ~DS1WM_INTEN_ERBF; if (ds1wm_data->read_complete) complete(ds1wm_data->read_complete); } + if ((intr & DS1WM_INT_PD) && ds1wm_data->reset_complete) { + inten &= ~DS1WM_INTEN_EPD; + complete(ds1wm_data->reset_complete); + } + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, inten); return IRQ_HANDLED; } @@ -142,33 +169,19 @@ static int ds1wm_reset(struct ds1wm_data *ds1wm_data) ds1wm_data->reset_complete = &reset_done; + /* enable Presence detect only */ ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, DS1WM_INTEN_EPD | - (ds1wm_data->active_high ? DS1WM_INTEN_IAS : 0)); + ds1wm_data->int_en_reg_none); ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_1W_RESET); timeleft = wait_for_completion_timeout(&reset_done, DS1WM_TIMEOUT); ds1wm_data->reset_complete = NULL; if (!timeleft) { - dev_err(&ds1wm_data->pdev->dev, "reset failed\n"); + dev_err(&ds1wm_data->pdev->dev, "reset failed, timed out\n"); return 1; } - /* Wait for the end of the reset. According to the specs, the time - * from when the interrupt is asserted to the end of the reset is: - * tRSTH - tPDH - tPDL - tPDI - * 625 us - 60 us - 240 us - 100 ns = 324.9 us - * - * We'll wait a bit longer just to be sure. - * Was udelay(500), but if it is going to busywait the cpu that long, - * might as well come back later. - */ - msleep(1); - - ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, - DS1WM_INTEN_ERBF | DS1WM_INTEN_ETMT | DS1WM_INTEN_EPD | - (ds1wm_data->active_high ? DS1WM_INTEN_IAS : 0)); - if (!ds1wm_data->slave_present) { dev_dbg(&ds1wm_data->pdev->dev, "reset: no devices found\n"); return 1; @@ -179,26 +192,47 @@ static int ds1wm_reset(struct ds1wm_data *ds1wm_data) static int ds1wm_write(struct ds1wm_data *ds1wm_data, u8 data) { + unsigned long timeleft; DECLARE_COMPLETION_ONSTACK(write_done); ds1wm_data->write_complete = &write_done; + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, + ds1wm_data->int_en_reg_none | DS1WM_INTEN_ETMT); + ds1wm_write_register(ds1wm_data, DS1WM_DATA, data); - wait_for_completion_timeout(&write_done, DS1WM_TIMEOUT); + timeleft = wait_for_completion_timeout(&write_done, DS1WM_TIMEOUT); + ds1wm_data->write_complete = NULL; + if (!timeleft) { + dev_err(&ds1wm_data->pdev->dev, "write failed, timed out\n"); + return -ETIMEDOUT; + } return 0; } -static int ds1wm_read(struct ds1wm_data *ds1wm_data, unsigned char write_data) +static u8 ds1wm_read(struct ds1wm_data *ds1wm_data, unsigned char write_data) { + unsigned long timeleft; + u8 intEnable = DS1WM_INTEN_ERBF | ds1wm_data->int_en_reg_none; DECLARE_COMPLETION_ONSTACK(read_done); + + ds1wm_read_register(ds1wm_data, DS1WM_DATA); + ds1wm_data->read_complete = &read_done; + ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, intEnable); - ds1wm_write(ds1wm_data, write_data); - wait_for_completion_timeout(&read_done, DS1WM_TIMEOUT); - ds1wm_data->read_complete = NULL; + ds1wm_write_register(ds1wm_data, DS1WM_DATA, write_data); + timeleft = wait_for_completion_timeout(&read_done, DS1WM_TIMEOUT); + ds1wm_data->read_complete = NULL; + if (!timeleft) { + dev_err(&ds1wm_data->pdev->dev, "read failed, timed out\n"); + ds1wm_data->read_error = -ETIMEDOUT; + return 0xFF; + } + ds1wm_data->read_error = 0; return ds1wm_data->read_byte; } @@ -206,8 +240,8 @@ static int ds1wm_find_divisor(int gclk) { int i; - for (i = 0; i < ARRAY_SIZE(freq); i++) - if (gclk <= freq[i].freq) + for (i = ARRAY_SIZE(freq)-1; i >= 0; --i) + if (gclk >= freq[i].freq) return freq[i].divisor; return 0; @@ -216,12 +250,14 @@ static int ds1wm_find_divisor(int gclk) static void ds1wm_up(struct ds1wm_data *ds1wm_data) { int divisor; - struct ds1wm_driver_data *plat = mfd_get_data(ds1wm_data->pdev); + struct ds1wm_driver_data *plat = ds1wm_data->pdev->dev.platform_data; if (ds1wm_data->cell->enable) ds1wm_data->cell->enable(ds1wm_data->pdev); divisor = ds1wm_find_divisor(plat->clock_rate); + dev_dbg(&ds1wm_data->pdev->dev, + "found divisor 0x%x for clock %d\n", divisor, plat->clock_rate); if (divisor == 0) { dev_err(&ds1wm_data->pdev->dev, "no suitable divisor for %dHz clock\n", @@ -242,7 +278,7 @@ static void ds1wm_down(struct ds1wm_data *ds1wm_data) /* Disable interrupts. */ ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, - ds1wm_data->active_high ? DS1WM_INTEN_IAS : 0); + ds1wm_data->int_en_reg_none); if (ds1wm_data->cell->disable) ds1wm_data->cell->disable(ds1wm_data->pdev); @@ -279,41 +315,121 @@ static void ds1wm_search(void *data, struct w1_master *master_dev, { struct ds1wm_data *ds1wm_data = data; int i; - unsigned long long rom_id; - - /* XXX We need to iterate for multiple devices per the DS1WM docs. - * See http://www.maxim-ic.com/appnotes.cfm/appnote_number/120. */ - if (ds1wm_reset(ds1wm_data)) - return; - - ds1wm_write(ds1wm_data, search_type); - ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_SRA); - - for (rom_id = 0, i = 0; i < 16; i++) { - - unsigned char resp, r, d; - - resp = ds1wm_read(ds1wm_data, 0x00); - - r = ((resp & 0x02) >> 1) | - ((resp & 0x08) >> 2) | - ((resp & 0x20) >> 3) | - ((resp & 0x80) >> 4); - - d = ((resp & 0x01) >> 0) | - ((resp & 0x04) >> 1) | - ((resp & 0x10) >> 2) | - ((resp & 0x40) >> 3); - - rom_id |= (unsigned long long) r << (i * 4); - - } - dev_dbg(&ds1wm_data->pdev->dev, "found 0x%08llX\n", rom_id); - - ds1wm_write_register(ds1wm_data, DS1WM_CMD, ~DS1WM_CMD_SRA); - ds1wm_reset(ds1wm_data); - - slave_found(master_dev, rom_id); + int ms_discrep_bit = -1; + u64 r = 0; /* holds the progress of the search */ + u64 r_prime, d; + unsigned slaves_found = 0; + unsigned int pass = 0; + + dev_dbg(&ds1wm_data->pdev->dev, "search begin\n"); + while (true) { + ++pass; + if (pass > 100) { + dev_dbg(&ds1wm_data->pdev->dev, + "too many attempts (100), search aborted\n"); + return; + } + + if (ds1wm_reset(ds1wm_data)) { + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d reset error (or no slaves)\n", pass); + break; + } + + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d r : %0#18llx writing SEARCH_ROM\n", pass, r); + ds1wm_write(ds1wm_data, search_type); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d entering ASM\n", pass); + ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_SRA); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d begining nibble loop\n", pass); + + r_prime = 0; + d = 0; + /* we work one nibble at a time */ + /* each nibble is interleaved to form a byte */ + for (i = 0; i < 16; i++) { + + unsigned char resp, _r, _r_prime, _d; + + _r = (r >> (4*i)) & 0xf; + _r = ((_r & 0x1) << 1) | + ((_r & 0x2) << 2) | + ((_r & 0x4) << 3) | + ((_r & 0x8) << 4); + + /* writes _r, then reads back: */ + resp = ds1wm_read(ds1wm_data, _r); + + if (ds1wm_data->read_error) { + dev_err(&ds1wm_data->pdev->dev, + "pass: %d nibble: %d read error\n", pass, i); + break; + } + + _r_prime = ((resp & 0x02) >> 1) | + ((resp & 0x08) >> 2) | + ((resp & 0x20) >> 3) | + ((resp & 0x80) >> 4); + + _d = ((resp & 0x01) >> 0) | + ((resp & 0x04) >> 1) | + ((resp & 0x10) >> 2) | + ((resp & 0x40) >> 3); + + r_prime |= (unsigned long long) _r_prime << (i * 4); + d |= (unsigned long long) _d << (i * 4); + + } + if (ds1wm_data->read_error) { + dev_err(&ds1wm_data->pdev->dev, + "pass: %d read error, retrying\n", pass); + break; + } + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d r\': %0#18llx d:%0#18llx\n", + pass, r_prime, d); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d nibble loop complete, exiting ASM\n", pass); + ds1wm_write_register(ds1wm_data, DS1WM_CMD, ~DS1WM_CMD_SRA); + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d resetting bus\n", pass); + ds1wm_reset(ds1wm_data); + if ((r_prime & ((u64)1 << 63)) && (d & ((u64)1 << 63))) { + dev_err(&ds1wm_data->pdev->dev, + "pass: %d bus error, retrying\n", pass); + continue; /* start over */ + } + + + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d found %0#18llx\n", pass, r_prime); + slave_found(master_dev, r_prime); + ++slaves_found; + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d complete, preparing next pass\n", pass); + + /* any discrepency found which we already choose the + '1' branch is now is now irrelevant we reveal the + next branch with this: */ + d &= ~r; + /* find last bit set, i.e. the most signif. bit set */ + ms_discrep_bit = fls64(d) - 1; + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d new d:%0#18llx MS discrep bit:%d\n", + pass, d, ms_discrep_bit); + + /* prev_ms_discrep_bit = ms_discrep_bit; + prepare for next ROM search: */ + if (ms_discrep_bit == -1) + break; + + r = (r & ~(~0ull << (ms_discrep_bit))) | 1 << ms_discrep_bit; + } /* end while true */ + dev_dbg(&ds1wm_data->pdev->dev, + "pass: %d total: %d search done ms d bit pos: %d\n", pass, + slaves_found, ms_discrep_bit); } /* --------------------------------------------------------------------- */ @@ -351,13 +467,21 @@ static int ds1wm_probe(struct platform_device *pdev) ret = -ENOMEM; goto err0; } - plat = mfd_get_data(pdev); /* calculate bus shift from mem resource */ ds1wm_data->bus_shift = resource_size(res) >> 3; ds1wm_data->pdev = pdev; ds1wm_data->cell = mfd_get_cell(pdev); + if (!ds1wm_data->cell) { + ret = -ENODEV; + goto err1; + } + plat = pdev->dev.platform_data; + if (!plat) { + ret = -ENODEV; + goto err1; + } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { @@ -365,15 +489,15 @@ static int ds1wm_probe(struct platform_device *pdev) goto err1; } ds1wm_data->irq = res->start; - ds1wm_data->active_high = plat->active_high; + ds1wm_data->int_en_reg_none = (plat->active_high ? DS1WM_INTEN_IAS : 0); if (res->flags & IORESOURCE_IRQ_HIGHEDGE) irq_set_irq_type(ds1wm_data->irq, IRQ_TYPE_EDGE_RISING); if (res->flags & IORESOURCE_IRQ_LOWEDGE) irq_set_irq_type(ds1wm_data->irq, IRQ_TYPE_EDGE_FALLING); - ret = request_irq(ds1wm_data->irq, ds1wm_isr, IRQF_DISABLED, - "ds1wm", ds1wm_data); + ret = request_irq(ds1wm_data->irq, ds1wm_isr, + IRQF_DISABLED | IRQF_SHARED, "ds1wm", ds1wm_data); if (ret) goto err1; @@ -460,5 +584,6 @@ module_exit(ds1wm_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, " - "Matt Reimer <mreimer@vpop.net>"); + "Matt Reimer <mreimer@vpop.net>," + "Jean-Francois Dagenais <dagenaisj@sonatest.com>"); MODULE_DESCRIPTION("DS1WM w1 busmaster driver"); diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index f0c909625bd..d0cb01b4201 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -16,6 +16,13 @@ config W1_SLAVE_SMEM Say Y here if you want to connect 1-wire simple 64bit memory rom(ds2401/ds2411/ds1990*) to your wire. +config W1_SLAVE_DS2408 + tristate "8-Channel Addressable Switch (IO Expander) 0x29 family support (DS2408)" + help + Say Y here if you want to use a 1-wire + + DS2408 8-Channel Addressable Switch device support + config W1_SLAVE_DS2423 tristate "Counter 1-wire device (DS2423)" select CRC16 @@ -61,6 +68,19 @@ config W1_SLAVE_DS2760 If you are unsure, say N. +config W1_SLAVE_DS2780 + tristate "Dallas 2780 battery monitor chip" + depends on W1 + help + If you enable this you will have the DS2780 battery monitor + chip support. + + The battery monitor chip is used in many batteries/devices + as the one who is responsible for charging/discharging/monitoring + Li+ batteries. + + If you are unsure, say N. + config W1_SLAVE_BQ27000 tristate "BQ27000 slave support" depends on W1 diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index 3c76350a24f..1f31e9fb0b2 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -4,8 +4,10 @@ obj-$(CONFIG_W1_SLAVE_THERM) += w1_therm.o obj-$(CONFIG_W1_SLAVE_SMEM) += w1_smem.o +obj-$(CONFIG_W1_SLAVE_DS2408) += w1_ds2408.o obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o +obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o obj-$(CONFIG_W1_SLAVE_BQ27000) += w1_bq27000.o diff --git a/drivers/w1/slaves/w1_ds2408.c b/drivers/w1/slaves/w1_ds2408.c new file mode 100644 index 00000000000..c37781899d9 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2408.c @@ -0,0 +1,402 @@ +/* + * w1_ds2408.c - w1 family 29 (DS2408) driver + * + * Copyright (c) 2010 Jean-Francois Dagenais <dagenaisj@sonatest.com> + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jean-Francois Dagenais <dagenaisj@sonatest.com>"); +MODULE_DESCRIPTION("w1 family 29 driver for DS2408 8 Pin IO"); + + +#define W1_F29_RETRIES 3 + +#define W1_F29_REG_LOGIG_STATE 0x88 /* R */ +#define W1_F29_REG_OUTPUT_LATCH_STATE 0x89 /* R */ +#define W1_F29_REG_ACTIVITY_LATCH_STATE 0x8A /* R */ +#define W1_F29_REG_COND_SEARCH_SELECT_MASK 0x8B /* RW */ +#define W1_F29_REG_COND_SEARCH_POL_SELECT 0x8C /* RW */ +#define W1_F29_REG_CONTROL_AND_STATUS 0x8D /* RW */ + +#define W1_F29_FUNC_READ_PIO_REGS 0xF0 +#define W1_F29_FUNC_CHANN_ACCESS_READ 0xF5 +#define W1_F29_FUNC_CHANN_ACCESS_WRITE 0x5A +/* also used to write the control/status reg (0x8D): */ +#define W1_F29_FUNC_WRITE_COND_SEARCH_REG 0xCC +#define W1_F29_FUNC_RESET_ACTIVITY_LATCHES 0xC3 + +#define W1_F29_SUCCESS_CONFIRM_BYTE 0xAA + +static int _read_reg(struct w1_slave *sl, u8 address, unsigned char* buf) +{ + u8 wrbuf[3]; + dev_dbg(&sl->dev, + "Reading with slave: %p, reg addr: %0#4x, buff addr: %p", + sl, (unsigned int)address, buf); + + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + dev_dbg(&sl->dev, "mutex locked"); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->mutex); + return -EIO; + } + + wrbuf[0] = W1_F29_FUNC_READ_PIO_REGS; + wrbuf[1] = address; + wrbuf[2] = 0; + w1_write_block(sl->master, wrbuf, 3); + *buf = w1_read_8(sl->master); + + mutex_unlock(&sl->master->mutex); + dev_dbg(&sl->dev, "mutex unlocked"); + return 1; +} + +static ssize_t w1_f29_read_state( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), W1_F29_REG_LOGIG_STATE, buf); +} + +static ssize_t w1_f29_read_output( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_OUTPUT_LATCH_STATE, buf); +} + +static ssize_t w1_f29_read_activity( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_ACTIVITY_LATCH_STATE, buf); +} + +static ssize_t w1_f29_read_cond_search_mask( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_COND_SEARCH_SELECT_MASK, buf); +} + +static ssize_t w1_f29_read_cond_search_polarity( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_COND_SEARCH_POL_SELECT, buf); +} + +static ssize_t w1_f29_read_status_control( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_CONTROL_AND_STATUS, buf); +} + + + + +static ssize_t w1_f29_write_output( + 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 w1_buf[3]; + u8 readBack; + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + dev_dbg(&sl->dev, "locking mutex for write_output"); + mutex_lock(&sl->master->mutex); + dev_dbg(&sl->dev, "mutex locked"); + + if (w1_reset_select_slave(sl)) + goto error; + + while (retries--) { + w1_buf[0] = W1_F29_FUNC_CHANN_ACCESS_WRITE; + w1_buf[1] = *buf; + w1_buf[2] = ~(*buf); + w1_write_block(sl->master, w1_buf, 3); + + readBack = w1_read_8(sl->master); + /* here the master could read another byte which + would be the PIO reg (the actual pin logic state) + since in this driver we don't know which pins are + in and outs, there's no value to read the state and + compare. with (*buf) so end this command abruptly: */ + if (w1_reset_resume_command(sl->master)) + goto error; + + if (readBack != 0xAA) { + /* try again, the slave is ready for a command */ + continue; + } + + /* go read back the output latches */ + /* (the direct effect of the write above) */ + w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS; + w1_buf[1] = W1_F29_REG_OUTPUT_LATCH_STATE; + w1_buf[2] = 0; + w1_write_block(sl->master, w1_buf, 3); + /* read the result of the READ_PIO_REGS command */ + if (w1_read_8(sl->master) == *buf) { + /* success! */ + mutex_unlock(&sl->master->mutex); + dev_dbg(&sl->dev, + "mutex unlocked, retries:%d", retries); + return 1; + } + } +error: + mutex_unlock(&sl->master->mutex); + dev_dbg(&sl->dev, "mutex unlocked in error, retries:%d", retries); + + return -EIO; +} + + +/** + * Writing to the activity file resets the activity latches. + */ +static ssize_t w1_f29_write_activity( + 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); + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->mutex); + + if (w1_reset_select_slave(sl)) + goto error; + + while (retries--) { + w1_write_8(sl->master, W1_F29_FUNC_RESET_ACTIVITY_LATCHES); + if (w1_read_8(sl->master) == W1_F29_SUCCESS_CONFIRM_BYTE) { + mutex_unlock(&sl->master->mutex); + return 1; + } + if (w1_reset_resume_command(sl->master)) + goto error; + } + +error: + mutex_unlock(&sl->master->mutex); + return -EIO; +} + +static ssize_t w1_f29_write_status_control( + 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 w1_buf[4]; + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->mutex); + + if (w1_reset_select_slave(sl)) + goto error; + + while (retries--) { + w1_buf[0] = W1_F29_FUNC_WRITE_COND_SEARCH_REG; + w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS; + w1_buf[2] = 0; + w1_buf[3] = *buf; + + w1_write_block(sl->master, w1_buf, 4); + if (w1_reset_resume_command(sl->master)) + goto error; + + w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS; + w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS; + w1_buf[2] = 0; + + w1_write_block(sl->master, w1_buf, 3); + if (w1_read_8(sl->master) == *buf) { + /* success! */ + mutex_unlock(&sl->master->mutex); + return 1; + } + } +error: + mutex_unlock(&sl->master->mutex); + + return -EIO; +} + + + +#define NB_SYSFS_BIN_FILES 6 +static struct bin_attribute w1_f29_sysfs_bin_files[NB_SYSFS_BIN_FILES] = { + { + .attr = { + .name = "state", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_state, + }, + { + .attr = { + .name = "output", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = 1, + .read = w1_f29_read_output, + .write = w1_f29_write_output, + }, + { + .attr = { + .name = "activity", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_activity, + .write = w1_f29_write_activity, + }, + { + .attr = { + .name = "cond_search_mask", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_cond_search_mask, + .write = 0, + }, + { + .attr = { + .name = "cond_search_polarity", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f29_read_cond_search_polarity, + .write = 0, + }, + { + .attr = { + .name = "status_control", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = 1, + .read = w1_f29_read_status_control, + .write = w1_f29_write_status_control, + } +}; + +static int w1_f29_add_slave(struct w1_slave *sl) +{ + int err = 0; + int i; + + for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) + err = sysfs_create_bin_file( + &sl->dev.kobj, + &(w1_f29_sysfs_bin_files[i])); + if (err) + while (--i >= 0) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f29_sysfs_bin_files[i])); + return err; +} + +static void w1_f29_remove_slave(struct w1_slave *sl) +{ + int i; + for (i = NB_SYSFS_BIN_FILES; i <= 0; --i) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f29_sysfs_bin_files[i])); +} + +static struct w1_family_ops w1_f29_fops = { + .add_slave = w1_f29_add_slave, + .remove_slave = w1_f29_remove_slave, +}; + +static struct w1_family w1_family_29 = { + .fid = W1_FAMILY_DS2408, + .fops = &w1_f29_fops, +}; + +static int __init w1_f29_init(void) +{ + return w1_register_family(&w1_family_29); +} + +static void __exit w1_f29_exit(void) +{ + w1_unregister_family(&w1_family_29); +} + +module_init(w1_f29_init); +module_exit(w1_f29_exit); diff --git a/drivers/w1/slaves/w1_ds2780.c b/drivers/w1/slaves/w1_ds2780.c new file mode 100644 index 00000000000..274c8f38303 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.c @@ -0,0 +1,217 @@ +/* + * 1-Wire implementation for the ds2780 chip + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * Based on w1-ds2760 driver + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/idr.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" +#include "w1_ds2780.h" + +int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, + int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return -ENODEV; + + mutex_lock(&sl->master->mutex); + + if (addr > DS2780_DATA_SIZE || addr < 0) { + count = 0; + goto out; + } + count = min_t(int, count, DS2780_DATA_SIZE - addr); + + if (w1_reset_select_slave(sl) == 0) { + if (io) { + w1_write_8(sl->master, W1_DS2780_WRITE_DATA); + w1_write_8(sl->master, addr); + w1_write_block(sl->master, buf, count); + /* XXX w1_write_block returns void, not n_written */ + } else { + w1_write_8(sl->master, W1_DS2780_READ_DATA); + w1_write_8(sl->master, addr); + count = w1_read_block(sl->master, buf, count); + } + } + +out: + mutex_unlock(&sl->master->mutex); + + return count; +} +EXPORT_SYMBOL(w1_ds2780_io); + +int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->mutex); + return 0; +} +EXPORT_SYMBOL(w1_ds2780_eeprom_cmd); + +static ssize_t w1_ds2780_read_bin(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + return w1_ds2780_io(dev, buf, off, count, 0); +} + +static struct bin_attribute w1_ds2780_bin_attr = { + .attr = { + .name = "w1_slave", + .mode = S_IRUGO, + }, + .size = DS2780_DATA_SIZE, + .read = w1_ds2780_read_bin, +}; + +static DEFINE_IDR(bat_idr); +static DEFINE_MUTEX(bat_idr_lock); + +static int new_bat_id(void) +{ + int ret; + + while (1) { + int id; + + ret = idr_pre_get(&bat_idr, GFP_KERNEL); + if (ret == 0) + return -ENOMEM; + + mutex_lock(&bat_idr_lock); + ret = idr_get_new(&bat_idr, NULL, &id); + mutex_unlock(&bat_idr_lock); + + if (ret == 0) { + ret = id & MAX_ID_MASK; + break; + } else if (ret == -EAGAIN) { + continue; + } else { + break; + } + } + + return ret; +} + +static void release_bat_id(int id) +{ + mutex_lock(&bat_idr_lock); + idr_remove(&bat_idr, id); + mutex_unlock(&bat_idr_lock); +} + +static int w1_ds2780_add_slave(struct w1_slave *sl) +{ + int ret; + int id; + struct platform_device *pdev; + + id = new_bat_id(); + if (id < 0) { + ret = id; + goto noid; + } + + pdev = platform_device_alloc("ds2780-battery", id); + if (!pdev) { + ret = -ENOMEM; + goto pdev_alloc_failed; + } + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + ret = sysfs_create_bin_file(&sl->dev.kobj, &w1_ds2780_bin_attr); + if (ret) + goto bin_attr_failed; + + dev_set_drvdata(&sl->dev, pdev); + + return 0; + +bin_attr_failed: +pdev_add_failed: + platform_device_unregister(pdev); +pdev_alloc_failed: + release_bat_id(id); +noid: + return ret; +} + +static void w1_ds2780_remove_slave(struct w1_slave *sl) +{ + struct platform_device *pdev = dev_get_drvdata(&sl->dev); + int id = pdev->id; + + platform_device_unregister(pdev); + release_bat_id(id); + sysfs_remove_bin_file(&sl->dev.kobj, &w1_ds2780_bin_attr); +} + +static struct w1_family_ops w1_ds2780_fops = { + .add_slave = w1_ds2780_add_slave, + .remove_slave = w1_ds2780_remove_slave, +}; + +static struct w1_family w1_ds2780_family = { + .fid = W1_FAMILY_DS2780, + .fops = &w1_ds2780_fops, +}; + +static int __init w1_ds2780_init(void) +{ + idr_init(&bat_idr); + return w1_register_family(&w1_ds2780_family); +} + +static void __exit w1_ds2780_exit(void) +{ + w1_unregister_family(&w1_ds2780_family); + idr_destroy(&bat_idr); +} + +module_init(w1_ds2780_init); +module_exit(w1_ds2780_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>"); +MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC"); diff --git a/drivers/w1/slaves/w1_ds2780.h b/drivers/w1/slaves/w1_ds2780.h new file mode 100644 index 00000000000..a1fba79eb1b --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.h @@ -0,0 +1,129 @@ +/* + * 1-Wire implementation for the ds2780 chip + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * Based on w1-ds2760 driver + * + * 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 _W1_DS2780_H +#define _W1_DS2780_H + +/* Function commands */ +#define W1_DS2780_READ_DATA 0x69 +#define W1_DS2780_WRITE_DATA 0x6C +#define W1_DS2780_COPY_DATA 0x48 +#define W1_DS2780_RECALL_DATA 0xB8 +#define W1_DS2780_LOCK 0x6A + +/* Register map */ +/* Register 0x00 Reserved */ +#define DS2780_STATUS_REG 0x01 +#define DS2780_RAAC_MSB_REG 0x02 +#define DS2780_RAAC_LSB_REG 0x03 +#define DS2780_RSAC_MSB_REG 0x04 +#define DS2780_RSAC_LSB_REG 0x05 +#define DS2780_RARC_REG 0x06 +#define DS2780_RSRC_REG 0x07 +#define DS2780_IAVG_MSB_REG 0x08 +#define DS2780_IAVG_LSB_REG 0x09 +#define DS2780_TEMP_MSB_REG 0x0A +#define DS2780_TEMP_LSB_REG 0x0B +#define DS2780_VOLT_MSB_REG 0x0C +#define DS2780_VOLT_LSB_REG 0x0D +#define DS2780_CURRENT_MSB_REG 0x0E +#define DS2780_CURRENT_LSB_REG 0x0F +#define DS2780_ACR_MSB_REG 0x10 +#define DS2780_ACR_LSB_REG 0x11 +#define DS2780_ACRL_MSB_REG 0x12 +#define DS2780_ACRL_LSB_REG 0x13 +#define DS2780_AS_REG 0x14 +#define DS2780_SFR_REG 0x15 +#define DS2780_FULL_MSB_REG 0x16 +#define DS2780_FULL_LSB_REG 0x17 +#define DS2780_AE_MSB_REG 0x18 +#define DS2780_AE_LSB_REG 0x19 +#define DS2780_SE_MSB_REG 0x1A +#define DS2780_SE_LSB_REG 0x1B +/* Register 0x1C - 0x1E Reserved */ +#define DS2780_EEPROM_REG 0x1F +#define DS2780_EEPROM_BLOCK0_START 0x20 +/* Register 0x20 - 0x2F User EEPROM */ +#define DS2780_EEPROM_BLOCK0_END 0x2F +/* Register 0x30 - 0x5F Reserved */ +#define DS2780_EEPROM_BLOCK1_START 0x60 +#define DS2780_CONTROL_REG 0x60 +#define DS2780_AB_REG 0x61 +#define DS2780_AC_MSB_REG 0x62 +#define DS2780_AC_LSB_REG 0x63 +#define DS2780_VCHG_REG 0x64 +#define DS2780_IMIN_REG 0x65 +#define DS2780_VAE_REG 0x66 +#define DS2780_IAE_REG 0x67 +#define DS2780_AE_40_REG 0x68 +#define DS2780_RSNSP_REG 0x69 +#define DS2780_FULL_40_MSB_REG 0x6A +#define DS2780_FULL_40_LSB_REG 0x6B +#define DS2780_FULL_3040_SLOPE_REG 0x6C +#define DS2780_FULL_2030_SLOPE_REG 0x6D +#define DS2780_FULL_1020_SLOPE_REG 0x6E +#define DS2780_FULL_0010_SLOPE_REG 0x6F +#define DS2780_AE_3040_SLOPE_REG 0x70 +#define DS2780_AE_2030_SLOPE_REG 0x71 +#define DS2780_AE_1020_SLOPE_REG 0x72 +#define DS2780_AE_0010_SLOPE_REG 0x73 +#define DS2780_SE_3040_SLOPE_REG 0x74 +#define DS2780_SE_2030_SLOPE_REG 0x75 +#define DS2780_SE_1020_SLOPE_REG 0x76 +#define DS2780_SE_0010_SLOPE_REG 0x77 +#define DS2780_RSGAIN_MSB_REG 0x78 +#define DS2780_RSGAIN_LSB_REG 0x79 +#define DS2780_RSTC_REG 0x7A +#define DS2780_FRSGAIN_MSB_REG 0x7B +#define DS2780_FRSGAIN_LSB_REG 0x7C +#define DS2780_EEPROM_BLOCK1_END 0x7C +/* Register 0x7D - 0xFF Reserved */ + +/* Number of valid register addresses */ +#define DS2780_DATA_SIZE 0x80 + +/* Status register bits */ +#define DS2780_STATUS_REG_CHGTF (1 << 7) +#define DS2780_STATUS_REG_AEF (1 << 6) +#define DS2780_STATUS_REG_SEF (1 << 5) +#define DS2780_STATUS_REG_LEARNF (1 << 4) +/* Bit 3 Reserved */ +#define DS2780_STATUS_REG_UVF (1 << 2) +#define DS2780_STATUS_REG_PORF (1 << 1) +/* Bit 0 Reserved */ + +/* Control register bits */ +/* Bit 7 Reserved */ +#define DS2780_CONTROL_REG_UVEN (1 << 6) +#define DS2780_CONTROL_REG_PMOD (1 << 5) +#define DS2780_CONTROL_REG_RNAOP (1 << 4) +/* Bit 0 - 3 Reserved */ + +/* Special feature register bits */ +/* Bit 1 - 7 Reserved */ +#define DS2780_SFR_REG_PIOSC (1 << 0) + +/* EEPROM register bits */ +#define DS2780_EEPROM_REG_EEC (1 << 7) +#define DS2780_EEPROM_REG_LOCK (1 << 6) +/* Bit 2 - 6 Reserved */ +#define DS2780_EEPROM_REG_BL1 (1 << 1) +#define DS2780_EEPROM_REG_BL0 (1 << 0) + +extern int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, + int io); +extern int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd); + +#endif /* !_W1_DS2780_H */ diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index b7b5014ff71..10606c82275 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -827,7 +827,7 @@ void w1_reconnect_slaves(struct w1_family *f, int attach) mutex_unlock(&w1_mlock); } -static void w1_slave_found(struct w1_master *dev, u64 rn) +void w1_slave_found(struct w1_master *dev, u64 rn) { struct w1_slave *sl; struct w1_reg_num *tmp; @@ -933,14 +933,15 @@ void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb } } -void w1_search_process(struct w1_master *dev, u8 search_type) +void w1_search_process_cb(struct w1_master *dev, u8 search_type, + w1_slave_found_callback cb) { struct w1_slave *sl, *sln; list_for_each_entry(sl, &dev->slist, w1_slave_entry) clear_bit(W1_SLAVE_ACTIVE, (long *)&sl->flags); - w1_search_devices(dev, search_type, w1_slave_found); + w1_search_devices(dev, search_type, cb); list_for_each_entry_safe(sl, sln, &dev->slist, w1_slave_entry) { if (!test_bit(W1_SLAVE_ACTIVE, (unsigned long *)&sl->flags) && !--sl->ttl) @@ -953,6 +954,11 @@ void w1_search_process(struct w1_master *dev, u8 search_type) dev->search_count--; } +static void w1_search_process(struct w1_master *dev, u8 search_type) +{ + w1_search_process_cb(dev, search_type, w1_slave_found); +} + int w1_process(void *data) { struct w1_master *dev = (struct w1_master *) data; diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index d8a9709f344..1ce23fc6186 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -55,6 +55,7 @@ struct w1_reg_num #define W1_READ_ROM 0x33 #define W1_READ_PSUPPLY 0xB4 #define W1_MATCH_ROM 0x55 +#define W1_RESUME_CMD 0xA5 #define W1_SLAVE_ACTIVE 0 @@ -193,7 +194,9 @@ void w1_destroy_master_attributes(struct w1_master *master); void w1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb); void w1_search_devices(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb); struct w1_slave *w1_search_slave(struct w1_reg_num *id); -void w1_search_process(struct w1_master *dev, u8 search_type); +void w1_slave_found(struct w1_master *dev, u64 rn); +void w1_search_process_cb(struct w1_master *dev, u8 search_type, + w1_slave_found_callback cb); struct w1_master *w1_search_master_id(u32 id); /* Disconnect and reconnect devices in the given family. Used for finding @@ -213,6 +216,7 @@ void w1_write_block(struct w1_master *, const u8 *, int); void w1_touch_block(struct w1_master *, u8 *, int); u8 w1_read_block(struct w1_master *, u8 *, int); int w1_reset_select_slave(struct w1_slave *sl); +int w1_reset_resume_command(struct w1_master *); void w1_next_pullup(struct w1_master *, int); static inline struct w1_slave* dev_to_w1_slave(struct device *dev) diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index f3b636d7caf..97479ae70b9 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -34,8 +34,10 @@ #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 #define W1_THERM_DS18B20 0x28 +#define W1_FAMILY_DS2408 0x29 #define W1_EEPROM_DS2431 0x2D #define W1_FAMILY_DS2760 0x30 +#define W1_FAMILY_DS2780 0x32 #define MAXNAMELEN 32 diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c index 3ebe9726a9e..8e8b64cfafb 100644 --- a/drivers/w1/w1_io.c +++ b/drivers/w1/w1_io.c @@ -390,6 +390,32 @@ int w1_reset_select_slave(struct w1_slave *sl) EXPORT_SYMBOL_GPL(w1_reset_select_slave); /** + * When the workflow with a slave amongst many requires several + * successive commands a reset between each, this function is similar + * to doing a reset then a match ROM for the last matched ROM. The + * advantage being that the matched ROM step is skipped in favor of the + * resume command. The slave must support the command of course. + * + * If the bus has only one slave, traditionnaly the match ROM is skipped + * and a "SKIP ROM" is done for efficiency. On multi-slave busses, this + * doesn't work of course, but the resume command is the next best thing. + * + * The w1 master lock must be held. + * + * @param dev the master device + */ +int w1_reset_resume_command(struct w1_master *dev) +{ + if (w1_reset_bus(dev)) + return -1; + + /* This will make only the last matched slave perform a skip ROM. */ + w1_write_8(dev, W1_RESUME_CMD); + return 0; +} +EXPORT_SYMBOL_GPL(w1_reset_resume_command); + +/** * Put out a strong pull-up of the specified duration after the next write * operation. Not all hardware supports strong pullups. Hardware that * doesn't support strong pullups will sleep for the given time after the diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c index 7e667bc77ef..55aabd927c6 100644 --- a/drivers/w1/w1_netlink.c +++ b/drivers/w1/w1_netlink.c @@ -55,6 +55,9 @@ static void w1_send_slave(struct w1_master *dev, u64 rn) struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1); int avail; + /* update kernel slave list */ + w1_slave_found(dev, rn); + avail = dev->priv_size - cmd->len; if (avail > 8) { @@ -85,7 +88,7 @@ static int w1_process_search_command(struct w1_master *dev, struct cn_msg *msg, dev->priv = msg; dev->priv_size = avail; - w1_search_devices(dev, search_type, w1_send_slave); + w1_search_process_cb(dev, search_type, w1_send_slave); msg->ack = 0; cn_netlink_send(msg, 0, GFP_KERNEL); diff --git a/drivers/watchdog/rdc321x_wdt.c b/drivers/watchdog/rdc321x_wdt.c index d8e725082fd..428f8a1583e 100644 --- a/drivers/watchdog/rdc321x_wdt.c +++ b/drivers/watchdog/rdc321x_wdt.c @@ -37,7 +37,6 @@ #include <linux/io.h> #include <linux/uaccess.h> #include <linux/mfd/rdc321x.h> -#include <linux/mfd/core.h> #define RDC_WDT_MASK 0x80000000 /* Mask */ #define RDC_WDT_EN 0x00800000 /* Enable bit */ @@ -232,7 +231,7 @@ static int __devinit rdc321x_wdt_probe(struct platform_device *pdev) struct resource *r; struct rdc321x_wdt_pdata *pdata; - pdata = mfd_get_data(pdev); + pdata = pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "no platform data supplied\n"); return -ENODEV; diff --git a/drivers/xen/Makefile b/drivers/xen/Makefile index 4781f806701..bbc18258ecc 100644 --- a/drivers/xen/Makefile +++ b/drivers/xen/Makefile @@ -1,5 +1,6 @@ obj-y += grant-table.o features.o events.o manage.o balloon.o obj-y += xenbus/ +obj-y += tmem.o nostackp := $(call cc-option, -fno-stack-protector) CFLAGS_features.o := $(nostackp) diff --git a/drivers/xen/tmem.c b/drivers/xen/tmem.c new file mode 100644 index 00000000000..816a44959ef --- /dev/null +++ b/drivers/xen/tmem.c @@ -0,0 +1,264 @@ +/* + * Xen implementation for transcendent memory (tmem) + * + * Copyright (C) 2009-2010 Oracle Corp. All rights reserved. + * Author: Dan Magenheimer + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/pagemap.h> +#include <linux/cleancache.h> + +#include <xen/xen.h> +#include <xen/interface/xen.h> +#include <asm/xen/hypercall.h> +#include <asm/xen/page.h> +#include <asm/xen/hypervisor.h> + +#define TMEM_CONTROL 0 +#define TMEM_NEW_POOL 1 +#define TMEM_DESTROY_POOL 2 +#define TMEM_NEW_PAGE 3 +#define TMEM_PUT_PAGE 4 +#define TMEM_GET_PAGE 5 +#define TMEM_FLUSH_PAGE 6 +#define TMEM_FLUSH_OBJECT 7 +#define TMEM_READ 8 +#define TMEM_WRITE 9 +#define TMEM_XCHG 10 + +/* Bits for HYPERVISOR_tmem_op(TMEM_NEW_POOL) */ +#define TMEM_POOL_PERSIST 1 +#define TMEM_POOL_SHARED 2 +#define TMEM_POOL_PAGESIZE_SHIFT 4 +#define TMEM_VERSION_SHIFT 24 + + +struct tmem_pool_uuid { + u64 uuid_lo; + u64 uuid_hi; +}; + +struct tmem_oid { + u64 oid[3]; +}; + +#define TMEM_POOL_PRIVATE_UUID { 0, 0 } + +/* flags for tmem_ops.new_pool */ +#define TMEM_POOL_PERSIST 1 +#define TMEM_POOL_SHARED 2 + +/* xen tmem foundation ops/hypercalls */ + +static inline int xen_tmem_op(u32 tmem_cmd, u32 tmem_pool, struct tmem_oid oid, + u32 index, unsigned long gmfn, u32 tmem_offset, u32 pfn_offset, u32 len) +{ + struct tmem_op op; + int rc = 0; + + op.cmd = tmem_cmd; + op.pool_id = tmem_pool; + op.u.gen.oid[0] = oid.oid[0]; + op.u.gen.oid[1] = oid.oid[1]; + op.u.gen.oid[2] = oid.oid[2]; + op.u.gen.index = index; + op.u.gen.tmem_offset = tmem_offset; + op.u.gen.pfn_offset = pfn_offset; + op.u.gen.len = len; + set_xen_guest_handle(op.u.gen.gmfn, (void *)gmfn); + rc = HYPERVISOR_tmem_op(&op); + return rc; +} + +static int xen_tmem_new_pool(struct tmem_pool_uuid uuid, + u32 flags, unsigned long pagesize) +{ + struct tmem_op op; + int rc = 0, pageshift; + + for (pageshift = 0; pagesize != 1; pageshift++) + pagesize >>= 1; + flags |= (pageshift - 12) << TMEM_POOL_PAGESIZE_SHIFT; + flags |= TMEM_SPEC_VERSION << TMEM_VERSION_SHIFT; + op.cmd = TMEM_NEW_POOL; + op.u.new.uuid[0] = uuid.uuid_lo; + op.u.new.uuid[1] = uuid.uuid_hi; + op.u.new.flags = flags; + rc = HYPERVISOR_tmem_op(&op); + return rc; +} + +/* xen generic tmem ops */ + +static int xen_tmem_put_page(u32 pool_id, struct tmem_oid oid, + u32 index, unsigned long pfn) +{ + unsigned long gmfn = xen_pv_domain() ? pfn_to_mfn(pfn) : pfn; + + return xen_tmem_op(TMEM_PUT_PAGE, pool_id, oid, index, + gmfn, 0, 0, 0); +} + +static int xen_tmem_get_page(u32 pool_id, struct tmem_oid oid, + u32 index, unsigned long pfn) +{ + unsigned long gmfn = xen_pv_domain() ? pfn_to_mfn(pfn) : pfn; + + return xen_tmem_op(TMEM_GET_PAGE, pool_id, oid, index, + gmfn, 0, 0, 0); +} + +static int xen_tmem_flush_page(u32 pool_id, struct tmem_oid oid, u32 index) +{ + return xen_tmem_op(TMEM_FLUSH_PAGE, pool_id, oid, index, + 0, 0, 0, 0); +} + +static int xen_tmem_flush_object(u32 pool_id, struct tmem_oid oid) +{ + return xen_tmem_op(TMEM_FLUSH_OBJECT, pool_id, oid, 0, 0, 0, 0, 0); +} + +static int xen_tmem_destroy_pool(u32 pool_id) +{ + struct tmem_oid oid = { { 0 } }; + + return xen_tmem_op(TMEM_DESTROY_POOL, pool_id, oid, 0, 0, 0, 0, 0); +} + +int tmem_enabled; + +static int __init enable_tmem(char *s) +{ + tmem_enabled = 1; + return 1; +} + +__setup("tmem", enable_tmem); + +/* cleancache ops */ + +static void tmem_cleancache_put_page(int pool, struct cleancache_filekey key, + pgoff_t index, struct page *page) +{ + u32 ind = (u32) index; + struct tmem_oid oid = *(struct tmem_oid *)&key; + unsigned long pfn = page_to_pfn(page); + + if (pool < 0) + return; + if (ind != index) + return; + mb(); /* ensure page is quiescent; tmem may address it with an alias */ + (void)xen_tmem_put_page((u32)pool, oid, ind, pfn); +} + +static int tmem_cleancache_get_page(int pool, struct cleancache_filekey key, + pgoff_t index, struct page *page) +{ + u32 ind = (u32) index; + struct tmem_oid oid = *(struct tmem_oid *)&key; + unsigned long pfn = page_to_pfn(page); + int ret; + + /* translate return values to linux semantics */ + if (pool < 0) + return -1; + if (ind != index) + return -1; + ret = xen_tmem_get_page((u32)pool, oid, ind, pfn); + if (ret == 1) + return 0; + else + return -1; +} + +static void tmem_cleancache_flush_page(int pool, struct cleancache_filekey key, + pgoff_t index) +{ + u32 ind = (u32) index; + struct tmem_oid oid = *(struct tmem_oid *)&key; + + if (pool < 0) + return; + if (ind != index) + return; + (void)xen_tmem_flush_page((u32)pool, oid, ind); +} + +static void tmem_cleancache_flush_inode(int pool, struct cleancache_filekey key) +{ + struct tmem_oid oid = *(struct tmem_oid *)&key; + + if (pool < 0) + return; + (void)xen_tmem_flush_object((u32)pool, oid); +} + +static void tmem_cleancache_flush_fs(int pool) +{ + if (pool < 0) + return; + (void)xen_tmem_destroy_pool((u32)pool); +} + +static int tmem_cleancache_init_fs(size_t pagesize) +{ + struct tmem_pool_uuid uuid_private = TMEM_POOL_PRIVATE_UUID; + + return xen_tmem_new_pool(uuid_private, 0, pagesize); +} + +static int tmem_cleancache_init_shared_fs(char *uuid, size_t pagesize) +{ + struct tmem_pool_uuid shared_uuid; + + shared_uuid.uuid_lo = *(u64 *)uuid; + shared_uuid.uuid_hi = *(u64 *)(&uuid[8]); + return xen_tmem_new_pool(shared_uuid, TMEM_POOL_SHARED, pagesize); +} + +static int use_cleancache = 1; + +static int __init no_cleancache(char *s) +{ + use_cleancache = 0; + return 1; +} + +__setup("nocleancache", no_cleancache); + +static struct cleancache_ops tmem_cleancache_ops = { + .put_page = tmem_cleancache_put_page, + .get_page = tmem_cleancache_get_page, + .flush_page = tmem_cleancache_flush_page, + .flush_inode = tmem_cleancache_flush_inode, + .flush_fs = tmem_cleancache_flush_fs, + .init_shared_fs = tmem_cleancache_init_shared_fs, + .init_fs = tmem_cleancache_init_fs +}; + +static int __init xen_tmem_init(void) +{ + struct cleancache_ops old_ops; + + if (!xen_domain()) + return 0; +#ifdef CONFIG_CLEANCACHE + BUG_ON(sizeof(struct cleancache_filekey) != sizeof(struct tmem_oid)); + if (tmem_enabled && use_cleancache) { + char *s = ""; + old_ops = cleancache_register_ops(&tmem_cleancache_ops); + if (old_ops.init_fs != NULL) + s = " (WARNING: cleancache_ops overridden)"; + printk(KERN_INFO "cleancache enabled, RAM provided by " + "Xen Transcendent Memory%s\n", s); + } +#endif + return 0; +} + +module_init(xen_tmem_init) |