diff options
Diffstat (limited to 'drivers')
691 files changed, 37439 insertions, 13913 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index decf8e420856..6f0459cb745b 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -130,6 +130,10 @@ source "drivers/clocksource/Kconfig" source "drivers/iommu/Kconfig" +source "drivers/remoteproc/Kconfig" + +source "drivers/rpmsg/Kconfig" + source "drivers/virt/Kconfig" source "drivers/devfreq/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 932e8bf20356..262b19d6b627 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -125,6 +125,8 @@ obj-y += clk/ obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ obj-$(CONFIG_NFC) += nfc/ obj-$(CONFIG_IOMMU_SUPPORT) += iommu/ +obj-$(CONFIG_REMOTEPROC) += remoteproc/ +obj-$(CONFIG_RPMSG) += rpmsg/ # Virtualization drivers obj-$(CONFIG_VIRT_DRIVERS) += virt/ diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c index 54eaf96ab217..01c2cf4efcdd 100644 --- a/drivers/amba/bus.c +++ b/drivers/amba/bus.c @@ -497,37 +497,22 @@ static void amba_device_release(struct device *dev) } /** - * amba_device_register - register an AMBA device - * @dev: AMBA device to register - * @parent: parent memory resource + * amba_device_add - add a previously allocated AMBA device structure + * @dev: AMBA device allocated by amba_device_alloc + * @parent: resource parent for this devices resources * - * Setup the AMBA device, reading the cell ID if present. - * Claim the resource, and register the AMBA device with - * the Linux device manager. + * Claim the resource, and read the device cell ID if not already + * initialized. Register the AMBA device with the Linux device + * manager. */ -int amba_device_register(struct amba_device *dev, struct resource *parent) +int amba_device_add(struct amba_device *dev, struct resource *parent) { u32 size; void __iomem *tmp; int i, ret; - device_initialize(&dev->dev); - - /* - * Copy from device_add - */ - if (dev->dev.init_name) { - dev_set_name(&dev->dev, "%s", dev->dev.init_name); - dev->dev.init_name = NULL; - } - - dev->dev.release = amba_device_release; - dev->dev.bus = &amba_bustype; - dev->dev.dma_mask = &dev->dma_mask; - dev->res.name = dev_name(&dev->dev); - - if (!dev->dev.coherent_dma_mask && dev->dma_mask) - dev_warn(&dev->dev, "coherent dma mask is unset\n"); + WARN_ON(dev->irq[0] == (unsigned int)-1); + WARN_ON(dev->irq[1] == (unsigned int)-1); ret = request_resource(parent, &dev->res); if (ret) @@ -582,9 +567,9 @@ int amba_device_register(struct amba_device *dev, struct resource *parent) if (ret) goto err_release; - if (dev->irq[0] != NO_IRQ) + if (dev->irq[0] && dev->irq[0] != NO_IRQ) ret = device_create_file(&dev->dev, &dev_attr_irq0); - if (ret == 0 && dev->irq[1] != NO_IRQ) + if (ret == 0 && dev->irq[1] && dev->irq[1] != NO_IRQ) ret = device_create_file(&dev->dev, &dev_attr_irq1); if (ret == 0) return ret; @@ -596,6 +581,74 @@ int amba_device_register(struct amba_device *dev, struct resource *parent) err_out: return ret; } +EXPORT_SYMBOL_GPL(amba_device_add); + +static void amba_device_initialize(struct amba_device *dev, const char *name) +{ + device_initialize(&dev->dev); + if (name) + dev_set_name(&dev->dev, "%s", name); + dev->dev.release = amba_device_release; + dev->dev.bus = &amba_bustype; + dev->dev.dma_mask = &dev->dma_mask; + dev->res.name = dev_name(&dev->dev); +} + +/** + * amba_device_alloc - allocate an AMBA device + * @name: sysfs name of the AMBA device + * @base: base of AMBA device + * @size: size of AMBA device + * + * Allocate and initialize an AMBA device structure. Returns %NULL + * on failure. + */ +struct amba_device *amba_device_alloc(const char *name, resource_size_t base, + size_t size) +{ + struct amba_device *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev) { + amba_device_initialize(dev, name); + dev->res.start = base; + dev->res.end = base + size - 1; + dev->res.flags = IORESOURCE_MEM; + } + + return dev; +} +EXPORT_SYMBOL_GPL(amba_device_alloc); + +/** + * amba_device_register - register an AMBA device + * @dev: AMBA device to register + * @parent: parent memory resource + * + * Setup the AMBA device, reading the cell ID if present. + * Claim the resource, and register the AMBA device with + * the Linux device manager. + */ +int amba_device_register(struct amba_device *dev, struct resource *parent) +{ + amba_device_initialize(dev, dev->dev.init_name); + dev->dev.init_name = NULL; + + if (!dev->dev.coherent_dma_mask && dev->dma_mask) + dev_warn(&dev->dev, "coherent dma mask is unset\n"); + + return amba_device_add(dev, parent); +} + +/** + * amba_device_put - put an AMBA device + * @dev: AMBA device to put + */ +void amba_device_put(struct amba_device *dev) +{ + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(amba_device_put); /** * amba_device_unregister - unregister an AMBA device diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index 428e55e012dc..869d7ff2227f 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -8,6 +8,7 @@ #include <linux/init.h> #include <linux/kernel.h> +#include <linux/device.h> #include <linux/io.h> #include <linux/pm.h> #include <linux/pm_clock.h> diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index 4af7c1cbf909..a14085cc613f 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -8,6 +8,7 @@ #include <linux/init.h> #include <linux/kernel.h> +#include <linux/device.h> #include <linux/export.h> #include <linux/slab.h> #include <linux/pm_clock.h> diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 95706fa24c73..ac993eafec82 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -17,6 +17,7 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/cpufreq.h> +#include <linux/device.h> #include <linux/list.h> #include <linux/rculist.h> #include <linux/rcupdate.h> diff --git a/drivers/base/regmap/regcache-lzo.c b/drivers/base/regmap/regcache-lzo.c index 8d0061569326..483b06d4a380 100644 --- a/drivers/base/regmap/regcache-lzo.c +++ b/drivers/base/regmap/regcache-lzo.c @@ -11,6 +11,7 @@ */ #include <linux/slab.h> +#include <linux/device.h> #include <linux/lzo.h> #include "internal.h" @@ -341,7 +342,7 @@ static int regcache_lzo_sync(struct regmap *map, unsigned int min, lzo_blocks = map->cache; i = min; - for_each_set_bit_cont(i, lzo_blocks[0]->sync_bmp, + for_each_set_bit_from(i, lzo_blocks[0]->sync_bmp, lzo_blocks[0]->sync_bmp_nbits) { if (i > max) continue; diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 8d51916a283d..5157fa04c2f0 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -11,6 +11,7 @@ */ #include <linux/slab.h> +#include <linux/device.h> #include <linux/debugfs.h> #include <linux/rbtree.h> #include <linux/seq_file.h> diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 938cb1d2ea26..87f54dbf601b 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -12,6 +12,7 @@ #include <linux/slab.h> #include <linux/export.h> +#include <linux/device.h> #include <trace/events/regmap.h> #include <linux/bsearch.h> #include <linux/sort.h> diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 372f81a21201..58517a5dac13 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -14,6 +14,7 @@ #include <linux/mutex.h> #include <linux/debugfs.h> #include <linux/uaccess.h> +#include <linux/device.h> #include "internal.h" diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 428836fc5835..1befaa7a31cb 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -11,6 +11,7 @@ */ #include <linux/export.h> +#include <linux/device.h> #include <linux/regmap.h> #include <linux/irq.h> #include <linux/interrupt.h> diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c index e09f9cebbb20..abfaacaaf346 100644 --- a/drivers/block/drbd/drbd_nl.c +++ b/drivers/block/drbd/drbd_nl.c @@ -179,7 +179,7 @@ int drbd_khelper(struct drbd_conf *mdev, char *cmd) dev_info(DEV, "helper command: %s %s %s\n", usermode_helper, cmd, mb); drbd_bcast_ev_helper(mdev, cmd); - ret = call_usermodehelper(usermode_helper, argv, envp, 1); + ret = call_usermodehelper(usermode_helper, argv, envp, UMH_WAIT_PROC); if (ret) dev_warn(DEV, "helper command: %s %s %s exit code %u (0x%x)\n", usermode_helper, cmd, mb, diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index a6278e7e61a0..013c7a549fb6 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -41,19 +41,35 @@ #include "rbd_types.h" -#define DRV_NAME "rbd" -#define DRV_NAME_LONG "rbd (rados block device)" +/* + * The basic unit of block I/O is a sector. It is interpreted in a + * number of contexts in Linux (blk, bio, genhd), but the default is + * universally 512 bytes. These symbols are just slightly more + * meaningful than the bare numbers they represent. + */ +#define SECTOR_SHIFT 9 +#define SECTOR_SIZE (1ULL << SECTOR_SHIFT) + +#define RBD_DRV_NAME "rbd" +#define RBD_DRV_NAME_LONG "rbd (rados block device)" #define RBD_MINORS_PER_MAJOR 256 /* max minors per blkdev */ -#define RBD_MAX_MD_NAME_LEN (96 + sizeof(RBD_SUFFIX)) +#define RBD_MAX_MD_NAME_LEN (RBD_MAX_OBJ_NAME_LEN + sizeof(RBD_SUFFIX)) #define RBD_MAX_POOL_NAME_LEN 64 #define RBD_MAX_SNAP_NAME_LEN 32 #define RBD_MAX_OPT_LEN 1024 #define RBD_SNAP_HEAD_NAME "-" +/* + * An RBD device name will be "rbd#", where the "rbd" comes from + * RBD_DRV_NAME above, and # is a unique integer identifier. + * MAX_INT_FORMAT_WIDTH is used in ensuring DEV_NAME_LEN is big + * enough to hold all possible device names. + */ #define DEV_NAME_LEN 32 +#define MAX_INT_FORMAT_WIDTH ((5 * sizeof (int)) / 2 + 1) #define RBD_NOTIFY_TIMEOUT_DEFAULT 10 @@ -66,7 +82,6 @@ struct rbd_image_header { __u8 obj_order; __u8 crypt_type; __u8 comp_type; - struct rw_semaphore snap_rwsem; struct ceph_snap_context *snapc; size_t snap_names_len; u64 snap_seq; @@ -83,7 +98,7 @@ struct rbd_options { }; /* - * an instance of the client. multiple devices may share a client. + * an instance of the client. multiple devices may share an rbd client. */ struct rbd_client { struct ceph_client *client; @@ -92,20 +107,9 @@ struct rbd_client { struct list_head node; }; -struct rbd_req_coll; - /* - * a single io request + * a request completion status */ -struct rbd_request { - struct request *rq; /* blk layer request */ - struct bio *bio; /* cloned bio */ - struct page **pages; /* list of used pages */ - u64 len; - int coll_index; - struct rbd_req_coll *coll; -}; - struct rbd_req_status { int done; int rc; @@ -122,6 +126,18 @@ struct rbd_req_coll { struct rbd_req_status status[0]; }; +/* + * a single io request + */ +struct rbd_request { + struct request *rq; /* blk layer request */ + struct bio *bio; /* cloned bio */ + struct page **pages; /* list of used pages */ + u64 len; + int coll_index; + struct rbd_req_coll *coll; +}; + struct rbd_snap { struct device dev; const char *name; @@ -140,7 +156,6 @@ struct rbd_device { struct gendisk *disk; /* blkdev's gendisk and rq */ struct request_queue *q; - struct ceph_client *client; struct rbd_client *rbd_client; char name[DEV_NAME_LEN]; /* blkdev name, e.g. rbd3 */ @@ -157,6 +172,8 @@ struct rbd_device { struct ceph_osd_event *watch_event; struct ceph_osd_request *watch_request; + /* protects updating the header */ + struct rw_semaphore header_rwsem; char snap_name[RBD_MAX_SNAP_NAME_LEN]; u32 cur_snap; /* index+1 of current snapshot within snap context 0 - for the head */ @@ -171,15 +188,13 @@ struct rbd_device { struct device dev; }; -static struct bus_type rbd_bus_type = { - .name = "rbd", -}; - -static spinlock_t node_lock; /* protects client get/put */ - static DEFINE_MUTEX(ctl_mutex); /* Serialize open/close/setup/teardown */ + static LIST_HEAD(rbd_dev_list); /* devices */ -static LIST_HEAD(rbd_client_list); /* clients */ +static DEFINE_SPINLOCK(rbd_dev_list_lock); + +static LIST_HEAD(rbd_client_list); /* clients */ +static DEFINE_SPINLOCK(rbd_client_list_lock); static int __rbd_init_snaps_header(struct rbd_device *rbd_dev); static void rbd_dev_release(struct device *dev); @@ -190,12 +205,32 @@ static ssize_t rbd_snap_add(struct device *dev, static void __rbd_remove_snap_dev(struct rbd_device *rbd_dev, struct rbd_snap *snap); +static ssize_t rbd_add(struct bus_type *bus, const char *buf, + size_t count); +static ssize_t rbd_remove(struct bus_type *bus, const char *buf, + size_t count); -static struct rbd_device *dev_to_rbd(struct device *dev) +static struct bus_attribute rbd_bus_attrs[] = { + __ATTR(add, S_IWUSR, NULL, rbd_add), + __ATTR(remove, S_IWUSR, NULL, rbd_remove), + __ATTR_NULL +}; + +static struct bus_type rbd_bus_type = { + .name = "rbd", + .bus_attrs = rbd_bus_attrs, +}; + +static void rbd_root_dev_release(struct device *dev) { - return container_of(dev, struct rbd_device, dev); } +static struct device rbd_root_dev = { + .init_name = "rbd", + .release = rbd_root_dev_release, +}; + + static struct device *rbd_get_dev(struct rbd_device *rbd_dev) { return get_device(&rbd_dev->dev); @@ -210,8 +245,7 @@ static int __rbd_update_snaps(struct rbd_device *rbd_dev); static int rbd_open(struct block_device *bdev, fmode_t mode) { - struct gendisk *disk = bdev->bd_disk; - struct rbd_device *rbd_dev = disk->private_data; + struct rbd_device *rbd_dev = bdev->bd_disk->private_data; rbd_get_dev(rbd_dev); @@ -256,9 +290,11 @@ static struct rbd_client *rbd_client_create(struct ceph_options *opt, kref_init(&rbdc->kref); INIT_LIST_HEAD(&rbdc->node); + mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); + rbdc->client = ceph_create_client(opt, rbdc, 0, 0); if (IS_ERR(rbdc->client)) - goto out_rbdc; + goto out_mutex; opt = NULL; /* Now rbdc->client is responsible for opt */ ret = ceph_open_session(rbdc->client); @@ -267,16 +303,19 @@ static struct rbd_client *rbd_client_create(struct ceph_options *opt, rbdc->rbd_opts = rbd_opts; - spin_lock(&node_lock); + spin_lock(&rbd_client_list_lock); list_add_tail(&rbdc->node, &rbd_client_list); - spin_unlock(&node_lock); + spin_unlock(&rbd_client_list_lock); + + mutex_unlock(&ctl_mutex); dout("rbd_client_create created %p\n", rbdc); return rbdc; out_err: ceph_destroy_client(rbdc->client); -out_rbdc: +out_mutex: + mutex_unlock(&ctl_mutex); kfree(rbdc); out_opt: if (opt) @@ -324,7 +363,7 @@ static int parse_rbd_opts_token(char *c, void *private) substring_t argstr[MAX_OPT_ARGS]; int token, intval, ret; - token = match_token((char *)c, rbdopt_tokens, argstr); + token = match_token(c, rbdopt_tokens, argstr); if (token < 0) return -EINVAL; @@ -357,58 +396,54 @@ static int parse_rbd_opts_token(char *c, void *private) * Get a ceph client with specific addr and configuration, if one does * not exist create it. */ -static int rbd_get_client(struct rbd_device *rbd_dev, const char *mon_addr, - char *options) +static struct rbd_client *rbd_get_client(const char *mon_addr, + size_t mon_addr_len, + char *options) { struct rbd_client *rbdc; struct ceph_options *opt; - int ret; struct rbd_options *rbd_opts; rbd_opts = kzalloc(sizeof(*rbd_opts), GFP_KERNEL); if (!rbd_opts) - return -ENOMEM; + return ERR_PTR(-ENOMEM); rbd_opts->notify_timeout = RBD_NOTIFY_TIMEOUT_DEFAULT; - ret = ceph_parse_options(&opt, options, mon_addr, - mon_addr + strlen(mon_addr), parse_rbd_opts_token, rbd_opts); - if (ret < 0) - goto done_err; + opt = ceph_parse_options(options, mon_addr, + mon_addr + mon_addr_len, + parse_rbd_opts_token, rbd_opts); + if (IS_ERR(opt)) { + kfree(rbd_opts); + return ERR_CAST(opt); + } - spin_lock(&node_lock); + spin_lock(&rbd_client_list_lock); rbdc = __rbd_client_find(opt); if (rbdc) { + /* using an existing client */ + kref_get(&rbdc->kref); + spin_unlock(&rbd_client_list_lock); + ceph_destroy_options(opt); kfree(rbd_opts); - /* using an existing client */ - kref_get(&rbdc->kref); - rbd_dev->rbd_client = rbdc; - rbd_dev->client = rbdc->client; - spin_unlock(&node_lock); - return 0; + return rbdc; } - spin_unlock(&node_lock); + spin_unlock(&rbd_client_list_lock); rbdc = rbd_client_create(opt, rbd_opts); - if (IS_ERR(rbdc)) { - ret = PTR_ERR(rbdc); - goto done_err; - } - rbd_dev->rbd_client = rbdc; - rbd_dev->client = rbdc->client; - return 0; -done_err: - kfree(rbd_opts); - return ret; + if (IS_ERR(rbdc)) + kfree(rbd_opts); + + return rbdc; } /* * Destroy ceph client * - * Caller must hold node_lock. + * Caller must hold rbd_client_list_lock. */ static void rbd_client_release(struct kref *kref) { @@ -428,11 +463,10 @@ static void rbd_client_release(struct kref *kref) */ static void rbd_put_client(struct rbd_device *rbd_dev) { - spin_lock(&node_lock); + spin_lock(&rbd_client_list_lock); kref_put(&rbd_dev->rbd_client->kref, rbd_client_release); - spin_unlock(&node_lock); + spin_unlock(&rbd_client_list_lock); rbd_dev->rbd_client = NULL; - rbd_dev->client = NULL; } /* @@ -457,21 +491,19 @@ static int rbd_header_from_disk(struct rbd_image_header *header, gfp_t gfp_flags) { int i; - u32 snap_count = le32_to_cpu(ondisk->snap_count); - int ret = -ENOMEM; + u32 snap_count; - if (memcmp(ondisk, RBD_HEADER_TEXT, sizeof(RBD_HEADER_TEXT))) { + if (memcmp(ondisk, RBD_HEADER_TEXT, sizeof(RBD_HEADER_TEXT))) return -ENXIO; - } - init_rwsem(&header->snap_rwsem); - header->snap_names_len = le64_to_cpu(ondisk->snap_names_len); + snap_count = le32_to_cpu(ondisk->snap_count); header->snapc = kmalloc(sizeof(struct ceph_snap_context) + - snap_count * - sizeof(struct rbd_image_snap_ondisk), + snap_count * sizeof (*ondisk), gfp_flags); if (!header->snapc) return -ENOMEM; + + header->snap_names_len = le64_to_cpu(ondisk->snap_names_len); if (snap_count) { header->snap_names = kmalloc(header->snap_names_len, GFP_KERNEL); @@ -498,8 +530,7 @@ static int rbd_header_from_disk(struct rbd_image_header *header, header->snapc->num_snaps = snap_count; header->total_snaps = snap_count; - if (snap_count && - allocated_snaps == snap_count) { + if (snap_count && allocated_snaps == snap_count) { for (i = 0; i < snap_count; i++) { header->snapc->snaps[i] = le64_to_cpu(ondisk->snaps[i].id); @@ -518,7 +549,7 @@ err_names: kfree(header->snap_names); err_snapc: kfree(header->snapc); - return ret; + return -ENOMEM; } static int snap_index(struct rbd_image_header *header, int snap_num) @@ -542,35 +573,34 @@ static int snap_by_name(struct rbd_image_header *header, const char *snap_name, int i; char *p = header->snap_names; - for (i = 0; i < header->total_snaps; i++, p += strlen(p) + 1) { - if (strcmp(snap_name, p) == 0) - break; - } - if (i == header->total_snaps) - return -ENOENT; - if (seq) - *seq = header->snapc->snaps[i]; + for (i = 0; i < header->total_snaps; i++) { + if (!strcmp(snap_name, p)) { - if (size) - *size = header->snap_sizes[i]; + /* Found it. Pass back its id and/or size */ - return i; + if (seq) + *seq = header->snapc->snaps[i]; + if (size) + *size = header->snap_sizes[i]; + return i; + } + p += strlen(p) + 1; /* Skip ahead to the next name */ + } + return -ENOENT; } -static int rbd_header_set_snap(struct rbd_device *dev, - const char *snap_name, - u64 *size) +static int rbd_header_set_snap(struct rbd_device *dev, u64 *size) { struct rbd_image_header *header = &dev->header; struct ceph_snap_context *snapc = header->snapc; int ret = -ENOENT; - down_write(&header->snap_rwsem); + BUILD_BUG_ON(sizeof (dev->snap_name) < sizeof (RBD_SNAP_HEAD_NAME)); - if (!snap_name || - !*snap_name || - strcmp(snap_name, "-") == 0 || - strcmp(snap_name, RBD_SNAP_HEAD_NAME) == 0) { + down_write(&dev->header_rwsem); + + if (!memcmp(dev->snap_name, RBD_SNAP_HEAD_NAME, + sizeof (RBD_SNAP_HEAD_NAME))) { if (header->total_snaps) snapc->seq = header->snap_seq; else @@ -580,7 +610,7 @@ static int rbd_header_set_snap(struct rbd_device *dev, if (size) *size = header->image_size; } else { - ret = snap_by_name(header, snap_name, &snapc->seq, size); + ret = snap_by_name(header, dev->snap_name, &snapc->seq, size); if (ret < 0) goto done; @@ -590,7 +620,7 @@ static int rbd_header_set_snap(struct rbd_device *dev, ret = 0; done: - up_write(&header->snap_rwsem); + up_write(&dev->header_rwsem); return ret; } @@ -717,7 +747,7 @@ static struct bio *bio_chain_clone(struct bio **old, struct bio **next, /* split the bio. We'll release it either in the next call, or it will have to be released outside */ - bp = bio_split(old_chain, (len - total) / 512ULL); + bp = bio_split(old_chain, (len - total) / SECTOR_SIZE); if (!bp) goto err_out; @@ -857,7 +887,7 @@ static int rbd_do_request(struct request *rq, struct timespec mtime = CURRENT_TIME; struct rbd_request *req_data; struct ceph_osd_request_head *reqhead; - struct rbd_image_header *header = &dev->header; + struct ceph_osd_client *osdc; req_data = kzalloc(sizeof(*req_data), GFP_NOIO); if (!req_data) { @@ -874,15 +904,13 @@ static int rbd_do_request(struct request *rq, dout("rbd_do_request obj=%s ofs=%lld len=%lld\n", obj, len, ofs); - down_read(&header->snap_rwsem); + down_read(&dev->header_rwsem); - req = ceph_osdc_alloc_request(&dev->client->osdc, flags, - snapc, - ops, - false, - GFP_NOIO, pages, bio); + osdc = &dev->rbd_client->client->osdc; + req = ceph_osdc_alloc_request(osdc, flags, snapc, ops, + false, GFP_NOIO, pages, bio); if (!req) { - up_read(&header->snap_rwsem); + up_read(&dev->header_rwsem); ret = -ENOMEM; goto done_pages; } @@ -909,27 +937,27 @@ static int rbd_do_request(struct request *rq, layout->fl_object_size = cpu_to_le32(1 << RBD_MAX_OBJ_ORDER); layout->fl_pg_preferred = cpu_to_le32(-1); layout->fl_pg_pool = cpu_to_le32(dev->poolid); - ceph_calc_raw_layout(&dev->client->osdc, layout, snapid, - ofs, &len, &bno, req, ops); + ceph_calc_raw_layout(osdc, layout, snapid, ofs, &len, &bno, + req, ops); ceph_osdc_build_request(req, ofs, &len, ops, snapc, &mtime, req->r_oid, req->r_oid_len); - up_read(&header->snap_rwsem); + up_read(&dev->header_rwsem); if (linger_req) { - ceph_osdc_set_request_linger(&dev->client->osdc, req); + ceph_osdc_set_request_linger(osdc, req); *linger_req = req; } - ret = ceph_osdc_start_request(&dev->client->osdc, req, false); + ret = ceph_osdc_start_request(osdc, req, false); if (ret < 0) goto done_err; if (!rbd_cb) { - ret = ceph_osdc_wait_request(&dev->client->osdc, req); + ret = ceph_osdc_wait_request(osdc, req); if (ver) *ver = le64_to_cpu(req->r_reassert_version.version); dout("reassert_ver=%lld\n", @@ -1213,8 +1241,8 @@ static void rbd_watch_cb(u64 ver, u64 notify_id, u8 opcode, void *data) rc = __rbd_update_snaps(dev); mutex_unlock(&ctl_mutex); if (rc) - pr_warning(DRV_NAME "%d got notification but failed to update" - " snaps: %d\n", dev->major, rc); + pr_warning(RBD_DRV_NAME "%d got notification but failed to " + " update snaps: %d\n", dev->major, rc); rbd_req_sync_notify_ack(dev, ver, notify_id, dev->obj_md_name); } @@ -1227,7 +1255,7 @@ static int rbd_req_sync_watch(struct rbd_device *dev, u64 ver) { struct ceph_osd_req_op *ops; - struct ceph_osd_client *osdc = &dev->client->osdc; + struct ceph_osd_client *osdc = &dev->rbd_client->client->osdc; int ret = rbd_create_rw_ops(&ops, 1, CEPH_OSD_OP_WATCH, 0); if (ret < 0) @@ -1314,7 +1342,7 @@ static int rbd_req_sync_notify(struct rbd_device *dev, const char *obj) { struct ceph_osd_req_op *ops; - struct ceph_osd_client *osdc = &dev->client->osdc; + struct ceph_osd_client *osdc = &dev->rbd_client->client->osdc; struct ceph_osd_event *event; struct rbd_notify_info info; int payload_len = sizeof(u32) + sizeof(u32); @@ -1421,9 +1449,7 @@ static void rbd_rq_fn(struct request_queue *q) struct request *rq; struct bio_pair *bp = NULL; - rq = blk_fetch_request(q); - - while (1) { + while ((rq = blk_fetch_request(q))) { struct bio *bio; struct bio *rq_bio, *next_bio = NULL; bool do_write; @@ -1441,32 +1467,32 @@ static void rbd_rq_fn(struct request_queue *q) /* filter out block requests we don't understand */ if ((rq->cmd_type != REQ_TYPE_FS)) { __blk_end_request_all(rq, 0); - goto next; + continue; } /* deduce our operation (read, write) */ do_write = (rq_data_dir(rq) == WRITE); size = blk_rq_bytes(rq); - ofs = blk_rq_pos(rq) * 512ULL; + ofs = blk_rq_pos(rq) * SECTOR_SIZE; rq_bio = rq->bio; if (do_write && rbd_dev->read_only) { __blk_end_request_all(rq, -EROFS); - goto next; + continue; } spin_unlock_irq(q->queue_lock); dout("%s 0x%x bytes at 0x%llx\n", do_write ? "write" : "read", - size, blk_rq_pos(rq) * 512ULL); + size, blk_rq_pos(rq) * SECTOR_SIZE); num_segs = rbd_get_num_segments(&rbd_dev->header, ofs, size); coll = rbd_alloc_coll(num_segs); if (!coll) { spin_lock_irq(q->queue_lock); __blk_end_request_all(rq, -ENOMEM); - goto next; + continue; } do { @@ -1512,8 +1538,6 @@ next_seg: if (bp) bio_pair_release(bp); spin_lock_irq(q->queue_lock); -next: - rq = blk_fetch_request(q); } } @@ -1526,13 +1550,17 @@ static int rbd_merge_bvec(struct request_queue *q, struct bvec_merge_data *bmd, struct bio_vec *bvec) { struct rbd_device *rbd_dev = q->queuedata; - unsigned int chunk_sectors = 1 << (rbd_dev->header.obj_order - 9); - sector_t sector = bmd->bi_sector + get_start_sect(bmd->bi_bdev); - unsigned int bio_sectors = bmd->bi_size >> 9; + unsigned int chunk_sectors; + sector_t sector; + unsigned int bio_sectors; int max; + chunk_sectors = 1 << (rbd_dev->header.obj_order - SECTOR_SHIFT); + sector = bmd->bi_sector + get_start_sect(bmd->bi_bdev); + bio_sectors = bmd->bi_size >> SECTOR_SHIFT; + max = (chunk_sectors - ((sector & (chunk_sectors - 1)) - + bio_sectors)) << 9; + + bio_sectors)) << SECTOR_SHIFT; if (max < 0) max = 0; /* bio_add cannot handle a negative return */ if (max <= bvec->bv_len && bio_sectors == 0) @@ -1565,15 +1593,16 @@ static int rbd_read_header(struct rbd_device *rbd_dev, ssize_t rc; struct rbd_image_header_ondisk *dh; int snap_count = 0; - u64 snap_names_len = 0; u64 ver; + size_t len; + /* + * First reads the fixed-size header to determine the number + * of snapshots, then re-reads it, along with all snapshot + * records as well as their stored names. + */ + len = sizeof (*dh); while (1) { - int len = sizeof(*dh) + - snap_count * sizeof(struct rbd_image_snap_ondisk) + - snap_names_len; - - rc = -ENOMEM; dh = kmalloc(len, GFP_KERNEL); if (!dh) return -ENOMEM; @@ -1588,21 +1617,22 @@ static int rbd_read_header(struct rbd_device *rbd_dev, rc = rbd_header_from_disk(header, dh, snap_count, GFP_KERNEL); if (rc < 0) { - if (rc == -ENXIO) { + if (rc == -ENXIO) pr_warning("unrecognized header format" " for image %s", rbd_dev->obj); - } goto out_dh; } - if (snap_count != header->total_snaps) { - snap_count = header->total_snaps; - snap_names_len = header->snap_names_len; - rbd_header_free(header); - kfree(dh); - continue; - } - break; + if (snap_count == header->total_snaps) + break; + + snap_count = header->total_snaps; + len = sizeof (*dh) + + snap_count * sizeof(struct rbd_image_snap_ondisk) + + header->snap_names_len; + + rbd_header_free(header); + kfree(dh); } header->obj_version = ver; @@ -1623,13 +1653,14 @@ static int rbd_header_add_snap(struct rbd_device *dev, int ret; void *data, *p, *e; u64 ver; + struct ceph_mon_client *monc; /* we should create a snapshot only if we're pointing at the head */ if (dev->cur_snap) return -EINVAL; - ret = ceph_monc_create_snapid(&dev->client->monc, dev->poolid, - &new_snapid); + monc = &dev->rbd_client->client->monc; + ret = ceph_monc_create_snapid(monc, dev->poolid, &new_snapid); dout("created snapid=%lld\n", new_snapid); if (ret < 0) return ret; @@ -1684,9 +1715,9 @@ static int __rbd_update_snaps(struct rbd_device *rbd_dev) return ret; /* resized? */ - set_capacity(rbd_dev->disk, h.image_size / 512ULL); + set_capacity(rbd_dev->disk, h.image_size / SECTOR_SIZE); - down_write(&rbd_dev->header.snap_rwsem); + down_write(&rbd_dev->header_rwsem); snap_seq = rbd_dev->header.snapc->seq; if (rbd_dev->header.total_snaps && @@ -1711,7 +1742,7 @@ static int __rbd_update_snaps(struct rbd_device *rbd_dev) ret = __rbd_init_snaps_header(rbd_dev); - up_write(&rbd_dev->header.snap_rwsem); + up_write(&rbd_dev->header_rwsem); return ret; } @@ -1721,6 +1752,7 @@ static int rbd_init_disk(struct rbd_device *rbd_dev) struct gendisk *disk; struct request_queue *q; int rc; + u64 segment_size; u64 total_size = 0; /* contact OSD, request size info about the object being mapped */ @@ -1733,7 +1765,7 @@ static int rbd_init_disk(struct rbd_device *rbd_dev) if (rc) return rc; - rc = rbd_header_set_snap(rbd_dev, rbd_dev->snap_name, &total_size); + rc = rbd_header_set_snap(rbd_dev, &total_size); if (rc) return rc; @@ -1743,7 +1775,7 @@ static int rbd_init_disk(struct rbd_device *rbd_dev) if (!disk) goto out; - snprintf(disk->disk_name, sizeof(disk->disk_name), DRV_NAME "%d", + snprintf(disk->disk_name, sizeof(disk->disk_name), RBD_DRV_NAME "%d", rbd_dev->id); disk->major = rbd_dev->major; disk->first_minor = 0; @@ -1756,11 +1788,15 @@ static int rbd_init_disk(struct rbd_device *rbd_dev) if (!q) goto out_disk; + /* We use the default size, but let's be explicit about it. */ + blk_queue_physical_block_size(q, SECTOR_SIZE); + /* set io sizes to object size */ - blk_queue_max_hw_sectors(q, rbd_obj_bytes(&rbd_dev->header) / 512ULL); - blk_queue_max_segment_size(q, rbd_obj_bytes(&rbd_dev->header)); - blk_queue_io_min(q, rbd_obj_bytes(&rbd_dev->header)); - blk_queue_io_opt(q, rbd_obj_bytes(&rbd_dev->header)); + segment_size = rbd_obj_bytes(&rbd_dev->header); + blk_queue_max_hw_sectors(q, segment_size / SECTOR_SIZE); + blk_queue_max_segment_size(q, segment_size); + blk_queue_io_min(q, segment_size); + blk_queue_io_opt(q, segment_size); blk_queue_merge_bvec(q, rbd_merge_bvec); disk->queue = q; @@ -1771,7 +1807,7 @@ static int rbd_init_disk(struct rbd_device *rbd_dev) rbd_dev->q = q; /* finally, announce the disk to the world */ - set_capacity(disk, total_size / 512ULL); + set_capacity(disk, total_size / SECTOR_SIZE); add_disk(disk); pr_info("%s: added with size 0x%llx\n", @@ -1788,10 +1824,15 @@ out: sysfs */ +static struct rbd_device *dev_to_rbd_dev(struct device *dev) +{ + return container_of(dev, struct rbd_device, dev); +} + static ssize_t rbd_size_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); return sprintf(buf, "%llu\n", (unsigned long long)rbd_dev->header.image_size); } @@ -1799,7 +1840,7 @@ static ssize_t rbd_size_show(struct device *dev, static ssize_t rbd_major_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); return sprintf(buf, "%d\n", rbd_dev->major); } @@ -1807,15 +1848,16 @@ static ssize_t rbd_major_show(struct device *dev, static ssize_t rbd_client_id_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); - return sprintf(buf, "client%lld\n", ceph_client_id(rbd_dev->client)); + return sprintf(buf, "client%lld\n", + ceph_client_id(rbd_dev->rbd_client->client)); } static ssize_t rbd_pool_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); return sprintf(buf, "%s\n", rbd_dev->pool_name); } @@ -1823,7 +1865,7 @@ static ssize_t rbd_pool_show(struct device *dev, static ssize_t rbd_name_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); return sprintf(buf, "%s\n", rbd_dev->obj); } @@ -1832,7 +1874,7 @@ static ssize_t rbd_snap_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); return sprintf(buf, "%s\n", rbd_dev->snap_name); } @@ -1842,7 +1884,7 @@ static ssize_t rbd_image_refresh(struct device *dev, const char *buf, size_t size) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); int rc; int ret = size; @@ -1907,7 +1949,7 @@ static ssize_t rbd_snap_size_show(struct device *dev, { struct rbd_snap *snap = container_of(dev, struct rbd_snap, dev); - return sprintf(buf, "%lld\n", (long long)snap->size); + return sprintf(buf, "%zd\n", snap->size); } static ssize_t rbd_snap_id_show(struct device *dev, @@ -1916,7 +1958,7 @@ static ssize_t rbd_snap_id_show(struct device *dev, { struct rbd_snap *snap = container_of(dev, struct rbd_snap, dev); - return sprintf(buf, "%lld\n", (long long)snap->id); + return sprintf(buf, "%llu\n", (unsigned long long) snap->id); } static DEVICE_ATTR(snap_size, S_IRUGO, rbd_snap_size_show, NULL); @@ -2088,19 +2130,9 @@ static int __rbd_init_snaps_header(struct rbd_device *rbd_dev) return 0; } - -static void rbd_root_dev_release(struct device *dev) -{ -} - -static struct device rbd_root_dev = { - .init_name = "rbd", - .release = rbd_root_dev_release, -}; - static int rbd_bus_add_dev(struct rbd_device *rbd_dev) { - int ret = -ENOMEM; + int ret; struct device *dev; struct rbd_snap *snap; @@ -2114,7 +2146,7 @@ static int rbd_bus_add_dev(struct rbd_device *rbd_dev) dev_set_name(dev, "%d", rbd_dev->id); ret = device_register(dev); if (ret < 0) - goto done_free; + goto out; list_for_each_entry(snap, &rbd_dev->snaps, node) { ret = rbd_register_snap_dev(rbd_dev, snap, @@ -2122,10 +2154,7 @@ static int rbd_bus_add_dev(struct rbd_device *rbd_dev) if (ret < 0) break; } - - mutex_unlock(&ctl_mutex); - return 0; -done_free: +out: mutex_unlock(&ctl_mutex); return ret; } @@ -2154,104 +2183,250 @@ static int rbd_init_watch_dev(struct rbd_device *rbd_dev) return ret; } +static atomic64_t rbd_id_max = ATOMIC64_INIT(0); + +/* + * Get a unique rbd identifier for the given new rbd_dev, and add + * the rbd_dev to the global list. The minimum rbd id is 1. + */ +static void rbd_id_get(struct rbd_device *rbd_dev) +{ + rbd_dev->id = atomic64_inc_return(&rbd_id_max); + + spin_lock(&rbd_dev_list_lock); + list_add_tail(&rbd_dev->node, &rbd_dev_list); + spin_unlock(&rbd_dev_list_lock); +} + +/* + * Remove an rbd_dev from the global list, and record that its + * identifier is no longer in use. + */ +static void rbd_id_put(struct rbd_device *rbd_dev) +{ + struct list_head *tmp; + int rbd_id = rbd_dev->id; + int max_id; + + BUG_ON(rbd_id < 1); + + spin_lock(&rbd_dev_list_lock); + list_del_init(&rbd_dev->node); + + /* + * If the id being "put" is not the current maximum, there + * is nothing special we need to do. + */ + if (rbd_id != atomic64_read(&rbd_id_max)) { + spin_unlock(&rbd_dev_list_lock); + return; + } + + /* + * We need to update the current maximum id. Search the + * list to find out what it is. We're more likely to find + * the maximum at the end, so search the list backward. + */ + max_id = 0; + list_for_each_prev(tmp, &rbd_dev_list) { + struct rbd_device *rbd_dev; + + rbd_dev = list_entry(tmp, struct rbd_device, node); + if (rbd_id > max_id) + max_id = rbd_id; + } + spin_unlock(&rbd_dev_list_lock); + + /* + * The max id could have been updated by rbd_id_get(), in + * which case it now accurately reflects the new maximum. + * Be careful not to overwrite the maximum value in that + * case. + */ + atomic64_cmpxchg(&rbd_id_max, rbd_id, max_id); +} + +/* + * Skips over white space at *buf, and updates *buf to point to the + * first found non-space character (if any). Returns the length of + * the token (string of non-white space characters) found. Note + * that *buf must be terminated with '\0'. + */ +static inline size_t next_token(const char **buf) +{ + /* + * These are the characters that produce nonzero for + * isspace() in the "C" and "POSIX" locales. + */ + const char *spaces = " \f\n\r\t\v"; + + *buf += strspn(*buf, spaces); /* Find start of token */ + + return strcspn(*buf, spaces); /* Return token length */ +} + +/* + * Finds the next token in *buf, and if the provided token buffer is + * big enough, copies the found token into it. The result, if + * copied, is guaranteed to be terminated with '\0'. Note that *buf + * must be terminated with '\0' on entry. + * + * Returns the length of the token found (not including the '\0'). + * Return value will be 0 if no token is found, and it will be >= + * token_size if the token would not fit. + * + * The *buf pointer will be updated to point beyond the end of the + * found token. Note that this occurs even if the token buffer is + * too small to hold it. + */ +static inline size_t copy_token(const char **buf, + char *token, + size_t token_size) +{ + size_t len; + + len = next_token(buf); + if (len < token_size) { + memcpy(token, *buf, len); + *(token + len) = '\0'; + } + *buf += len; + + return len; +} + +/* + * This fills in the pool_name, obj, obj_len, snap_name, obj_len, + * rbd_dev, rbd_md_name, and name fields of the given rbd_dev, based + * on the list of monitor addresses and other options provided via + * /sys/bus/rbd/add. + */ +static int rbd_add_parse_args(struct rbd_device *rbd_dev, + const char *buf, + const char **mon_addrs, + size_t *mon_addrs_size, + char *options, + size_t options_size) +{ + size_t len; + + /* The first four tokens are required */ + + len = next_token(&buf); + if (!len) + return -EINVAL; + *mon_addrs_size = len + 1; + *mon_addrs = buf; + + buf += len; + + len = copy_token(&buf, options, options_size); + if (!len || len >= options_size) + return -EINVAL; + + len = copy_token(&buf, rbd_dev->pool_name, sizeof (rbd_dev->pool_name)); + if (!len || len >= sizeof (rbd_dev->pool_name)) + return -EINVAL; + + len = copy_token(&buf, rbd_dev->obj, sizeof (rbd_dev->obj)); + if (!len || len >= sizeof (rbd_dev->obj)) + return -EINVAL; + + /* We have the object length in hand, save it. */ + + rbd_dev->obj_len = len; + + BUILD_BUG_ON(RBD_MAX_MD_NAME_LEN + < RBD_MAX_OBJ_NAME_LEN + sizeof (RBD_SUFFIX)); + sprintf(rbd_dev->obj_md_name, "%s%s", rbd_dev->obj, RBD_SUFFIX); + + /* + * The snapshot name is optional, but it's an error if it's + * too long. If no snapshot is supplied, fill in the default. + */ + len = copy_token(&buf, rbd_dev->snap_name, sizeof (rbd_dev->snap_name)); + if (!len) + memcpy(rbd_dev->snap_name, RBD_SNAP_HEAD_NAME, + sizeof (RBD_SNAP_HEAD_NAME)); + else if (len >= sizeof (rbd_dev->snap_name)) + return -EINVAL; + + return 0; +} + static ssize_t rbd_add(struct bus_type *bus, const char *buf, size_t count) { - struct ceph_osd_client *osdc; struct rbd_device *rbd_dev; - ssize_t rc = -ENOMEM; - int irc, new_id = 0; - struct list_head *tmp; - char *mon_dev_name; - char *options; + const char *mon_addrs = NULL; + size_t mon_addrs_size = 0; + char *options = NULL; + struct ceph_osd_client *osdc; + int rc = -ENOMEM; if (!try_module_get(THIS_MODULE)) return -ENODEV; - mon_dev_name = kmalloc(RBD_MAX_OPT_LEN, GFP_KERNEL); - if (!mon_dev_name) - goto err_out_mod; - - options = kmalloc(RBD_MAX_OPT_LEN, GFP_KERNEL); - if (!options) - goto err_mon_dev; - - /* new rbd_device object */ rbd_dev = kzalloc(sizeof(*rbd_dev), GFP_KERNEL); if (!rbd_dev) - goto err_out_opt; + goto err_nomem; + options = kmalloc(count, GFP_KERNEL); + if (!options) + goto err_nomem; /* static rbd_device initialization */ spin_lock_init(&rbd_dev->lock); INIT_LIST_HEAD(&rbd_dev->node); INIT_LIST_HEAD(&rbd_dev->snaps); + init_rwsem(&rbd_dev->header_rwsem); - init_rwsem(&rbd_dev->header.snap_rwsem); + init_rwsem(&rbd_dev->header_rwsem); /* generate unique id: find highest unique id, add one */ - mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); - - list_for_each(tmp, &rbd_dev_list) { - struct rbd_device *rbd_dev; + rbd_id_get(rbd_dev); - rbd_dev = list_entry(tmp, struct rbd_device, node); - if (rbd_dev->id >= new_id) - new_id = rbd_dev->id + 1; - } - - rbd_dev->id = new_id; - - /* add to global list */ - list_add_tail(&rbd_dev->node, &rbd_dev_list); + /* Fill in the device name, now that we have its id. */ + BUILD_BUG_ON(DEV_NAME_LEN + < sizeof (RBD_DRV_NAME) + MAX_INT_FORMAT_WIDTH); + sprintf(rbd_dev->name, "%s%d", RBD_DRV_NAME, rbd_dev->id); /* parse add command */ - if (sscanf(buf, "%" __stringify(RBD_MAX_OPT_LEN) "s " - "%" __stringify(RBD_MAX_OPT_LEN) "s " - "%" __stringify(RBD_MAX_POOL_NAME_LEN) "s " - "%" __stringify(RBD_MAX_OBJ_NAME_LEN) "s" - "%" __stringify(RBD_MAX_SNAP_NAME_LEN) "s", - mon_dev_name, options, rbd_dev->pool_name, - rbd_dev->obj, rbd_dev->snap_name) < 4) { - rc = -EINVAL; - goto err_out_slot; - } - - if (rbd_dev->snap_name[0] == 0) - rbd_dev->snap_name[0] = '-'; - - rbd_dev->obj_len = strlen(rbd_dev->obj); - snprintf(rbd_dev->obj_md_name, sizeof(rbd_dev->obj_md_name), "%s%s", - rbd_dev->obj, RBD_SUFFIX); - - /* initialize rest of new object */ - snprintf(rbd_dev->name, DEV_NAME_LEN, DRV_NAME "%d", rbd_dev->id); - rc = rbd_get_client(rbd_dev, mon_dev_name, options); - if (rc < 0) - goto err_out_slot; + rc = rbd_add_parse_args(rbd_dev, buf, &mon_addrs, &mon_addrs_size, + options, count); + if (rc) + goto err_put_id; - mutex_unlock(&ctl_mutex); + rbd_dev->rbd_client = rbd_get_client(mon_addrs, mon_addrs_size - 1, + options); + if (IS_ERR(rbd_dev->rbd_client)) { + rc = PTR_ERR(rbd_dev->rbd_client); + goto err_put_id; + } /* pick the pool */ - osdc = &rbd_dev->client->osdc; + osdc = &rbd_dev->rbd_client->client->osdc; rc = ceph_pg_poolid_by_name(osdc->osdmap, rbd_dev->pool_name); if (rc < 0) goto err_out_client; rbd_dev->poolid = rc; /* register our block device */ - irc = register_blkdev(0, rbd_dev->name); - if (irc < 0) { - rc = irc; + rc = register_blkdev(0, rbd_dev->name); + if (rc < 0) goto err_out_client; - } - rbd_dev->major = irc; + rbd_dev->major = rc; rc = rbd_bus_add_dev(rbd_dev); if (rc) goto err_out_blkdev; - /* set up and announce blkdev mapping */ + /* + * At this point cleanup in the event of an error is the job + * of the sysfs code (initiated by rbd_bus_del_dev()). + * + * Set up and announce blkdev mapping. + */ rc = rbd_init_disk(rbd_dev); if (rc) goto err_out_bus; @@ -2263,35 +2438,26 @@ static ssize_t rbd_add(struct bus_type *bus, return count; err_out_bus: - mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); - list_del_init(&rbd_dev->node); - mutex_unlock(&ctl_mutex); - /* this will also clean up rest of rbd_dev stuff */ rbd_bus_del_dev(rbd_dev); kfree(options); - kfree(mon_dev_name); return rc; err_out_blkdev: unregister_blkdev(rbd_dev->major, rbd_dev->name); err_out_client: rbd_put_client(rbd_dev); - mutex_lock_nested(&ctl_mutex, SINGLE_DEPTH_NESTING); -err_out_slot: - list_del_init(&rbd_dev->node); - mutex_unlock(&ctl_mutex); - - kfree(rbd_dev); -err_out_opt: +err_put_id: + rbd_id_put(rbd_dev); +err_nomem: kfree(options); -err_mon_dev: - kfree(mon_dev_name); -err_out_mod: + kfree(rbd_dev); + dout("Error adding device %s\n", buf); module_put(THIS_MODULE); - return rc; + + return (ssize_t) rc; } static struct rbd_device *__rbd_get_dev(unsigned long id) @@ -2299,22 +2465,28 @@ static struct rbd_device *__rbd_get_dev(unsigned long id) struct list_head *tmp; struct rbd_device *rbd_dev; + spin_lock(&rbd_dev_list_lock); list_for_each(tmp, &rbd_dev_list) { rbd_dev = list_entry(tmp, struct rbd_device, node); - if (rbd_dev->id == id) + if (rbd_dev->id == id) { + spin_unlock(&rbd_dev_list_lock); return rbd_dev; + } } + spin_unlock(&rbd_dev_list_lock); return NULL; } static void rbd_dev_release(struct device *dev) { - struct rbd_device *rbd_dev = - container_of(dev, struct rbd_device, dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); - if (rbd_dev->watch_request) - ceph_osdc_unregister_linger_request(&rbd_dev->client->osdc, + if (rbd_dev->watch_request) { + struct ceph_client *client = rbd_dev->rbd_client->client; + + ceph_osdc_unregister_linger_request(&client->osdc, rbd_dev->watch_request); + } if (rbd_dev->watch_event) rbd_req_sync_unwatch(rbd_dev, rbd_dev->obj_md_name); @@ -2323,6 +2495,9 @@ static void rbd_dev_release(struct device *dev) /* clean up and free blkdev */ rbd_free_disk(rbd_dev); unregister_blkdev(rbd_dev->major, rbd_dev->name); + + /* done with the id, and with the rbd_dev */ + rbd_id_put(rbd_dev); kfree(rbd_dev); /* release module ref */ @@ -2355,8 +2530,6 @@ static ssize_t rbd_remove(struct bus_type *bus, goto done; } - list_del_init(&rbd_dev->node); - __rbd_remove_all_snaps(rbd_dev); rbd_bus_del_dev(rbd_dev); @@ -2370,7 +2543,7 @@ static ssize_t rbd_snap_add(struct device *dev, const char *buf, size_t count) { - struct rbd_device *rbd_dev = dev_to_rbd(dev); + struct rbd_device *rbd_dev = dev_to_rbd_dev(dev); int ret; char *name = kmalloc(count + 1, GFP_KERNEL); if (!name) @@ -2406,12 +2579,6 @@ err_unlock: return ret; } -static struct bus_attribute rbd_bus_attrs[] = { - __ATTR(add, S_IWUSR, NULL, rbd_add), - __ATTR(remove, S_IWUSR, NULL, rbd_remove), - __ATTR_NULL -}; - /* * create control files in sysfs * /sys/bus/rbd/... @@ -2420,21 +2587,21 @@ static int rbd_sysfs_init(void) { int ret; - rbd_bus_type.bus_attrs = rbd_bus_attrs; - - ret = bus_register(&rbd_bus_type); - if (ret < 0) + ret = device_register(&rbd_root_dev); + if (ret < 0) return ret; - ret = device_register(&rbd_root_dev); + ret = bus_register(&rbd_bus_type); + if (ret < 0) + device_unregister(&rbd_root_dev); return ret; } static void rbd_sysfs_cleanup(void) { - device_unregister(&rbd_root_dev); bus_unregister(&rbd_bus_type); + device_unregister(&rbd_root_dev); } int __init rbd_init(void) @@ -2444,8 +2611,7 @@ int __init rbd_init(void) rc = rbd_sysfs_init(); if (rc) return rc; - spin_lock_init(&node_lock); - pr_info("loaded " DRV_NAME_LONG "\n"); + pr_info("loaded " RBD_DRV_NAME_LONG "\n"); return 0; } diff --git a/drivers/block/rbd_types.h b/drivers/block/rbd_types.h index fc6c678aa2cb..950708688f17 100644 --- a/drivers/block/rbd_types.h +++ b/drivers/block/rbd_types.h @@ -41,10 +41,6 @@ #define RBD_HEADER_SIGNATURE "RBD" #define RBD_HEADER_VERSION "001.005" -struct rbd_info { - __le64 max_id; -} __attribute__ ((packed)); - struct rbd_image_snap_ondisk { __le64 id; __le64 image_size; diff --git a/drivers/block/xen-blkfront.c b/drivers/block/xen-blkfront.c index 2f22874c0a37..d5e1ab956740 100644 --- a/drivers/block/xen-blkfront.c +++ b/drivers/block/xen-blkfront.c @@ -1475,6 +1475,9 @@ static int __init xlblk_init(void) if (!xen_domain()) return -ENODEV; + if (!xen_platform_pci_unplug) + return -ENODEV; + if (register_blkdev(XENVBD_MAJOR, DEV_NAME)) { printk(KERN_WARNING "xen_blk: can't get major %d with name %s\n", XENVBD_MAJOR, DEV_NAME); diff --git a/drivers/char/hw_random/nomadik-rng.c b/drivers/char/hw_random/nomadik-rng.c index 3d3c1e6703b4..96de0249e595 100644 --- a/drivers/char/hw_random/nomadik-rng.c +++ b/drivers/char/hw_random/nomadik-rng.c @@ -107,17 +107,6 @@ static struct amba_driver nmk_rng_driver = { .id_table = nmk_rng_ids, }; -static int __init nmk_rng_init(void) -{ - return amba_driver_register(&nmk_rng_driver); -} - -static void __devexit nmk_rng_exit(void) -{ - amba_driver_unregister(&nmk_rng_driver); -} - -module_init(nmk_rng_init); -module_exit(nmk_rng_exit); +module_amba_driver(nmk_rng_driver); MODULE_LICENSE("GPL"); diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c index b757fac3cd1f..a07a5caa599c 100644 --- a/drivers/char/hw_random/omap-rng.c +++ b/drivers/char/hw_random/omap-rng.c @@ -26,6 +26,8 @@ #include <asm/io.h> +#include <plat/cpu.h> + #define RNG_OUT_REG 0x00 /* Output register */ #define RNG_STAT_REG 0x04 /* Status register [0] = STAT_BUSY */ diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 9b3cd08cd0ed..165e1febae53 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -8,3 +8,40 @@ config HAVE_CLK_PREPARE config HAVE_MACH_CLKDEV bool + +config COMMON_CLK + bool + select HAVE_CLK_PREPARE + ---help--- + The common clock framework is a single definition of struct + clk, useful across many platforms, as well as an + implementation of the clock API in include/linux/clk.h. + Architectures utilizing the common struct clk should select + this option. + +menu "Common Clock Framework" + depends on COMMON_CLK + +config COMMON_CLK_DISABLE_UNUSED + bool "Disabled unused clocks at boot" + depends on COMMON_CLK + ---help--- + Traverses the entire clock tree and disables any clocks that are + enabled in hardware but have not been enabled by any device drivers. + This saves power and keeps the software model of the clock in line + with reality. + + If in doubt, say "N". + +config COMMON_CLK_DEBUG + bool "DebugFS representation of clock tree" + depends on COMMON_CLK + select DEBUG_FS + ---help--- + Creates a directory hierchy in debugfs for visualizing the clk + tree structure. Each directory contains read-only members + that export information specific to that clk node: clk_rate, + clk_flags, clk_prepare_count, clk_enable_count & + clk_notifier_count. + +endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 07613fa172c9..1f736bc11c4b 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -1,2 +1,4 @@ obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o +obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed-rate.o clk-gate.o \ + clk-mux.o clk-divider.o diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c new file mode 100644 index 000000000000..d5ac6a75ea57 --- /dev/null +++ b/drivers/clk/clk-divider.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> + * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org> + * + * 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. + * + * Adjustable divider clock implementation + */ + +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/string.h> + +/* + * DOC: basic adjustable divider clock that cannot gate + * + * Traits of this clock: + * prepare - clk_prepare only ensures that parents are prepared + * enable - clk_enable only ensures that parents are enabled + * rate - rate is adjustable. clk->rate = parent->rate / divisor + * parent - fixed parent. No clk_set_parent support + */ + +#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw) + +#define div_mask(d) ((1 << (d->width)) - 1) + +static unsigned long clk_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + unsigned int div; + + div = readl(divider->reg) >> divider->shift; + div &= div_mask(divider); + + if (!(divider->flags & CLK_DIVIDER_ONE_BASED)) + div++; + + return parent_rate / div; +} +EXPORT_SYMBOL_GPL(clk_divider_recalc_rate); + +/* + * The reverse of DIV_ROUND_UP: The maximum number which + * divided by m is r + */ +#define MULT_ROUND_UP(r, m) ((r) * (m) + (m) - 1) + +static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + int i, bestdiv = 0; + unsigned long parent_rate, best = 0, now, maxdiv; + + if (!rate) + rate = 1; + + maxdiv = (1 << divider->width); + + if (divider->flags & CLK_DIVIDER_ONE_BASED) + maxdiv--; + + if (!best_parent_rate) { + parent_rate = __clk_get_rate(__clk_get_parent(hw->clk)); + bestdiv = DIV_ROUND_UP(parent_rate, rate); + bestdiv = bestdiv == 0 ? 1 : bestdiv; + bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv; + return bestdiv; + } + + /* + * The maximum divider we can use without overflowing + * unsigned long in rate * i below + */ + maxdiv = min(ULONG_MAX / rate, maxdiv); + + for (i = 1; i <= maxdiv; i++) { + parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), + MULT_ROUND_UP(rate, i)); + now = parent_rate / i; + if (now <= rate && now > best) { + bestdiv = i; + best = now; + *best_parent_rate = parent_rate; + } + } + + if (!bestdiv) { + bestdiv = (1 << divider->width); + if (divider->flags & CLK_DIVIDER_ONE_BASED) + bestdiv--; + *best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), 1); + } + + return bestdiv; +} + +static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + int div; + div = clk_divider_bestdiv(hw, rate, prate); + + if (prate) + return *prate / div; + else { + unsigned long r; + r = __clk_get_rate(__clk_get_parent(hw->clk)); + return r / div; + } +} +EXPORT_SYMBOL_GPL(clk_divider_round_rate); + +static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + unsigned int div; + unsigned long flags = 0; + u32 val; + + div = __clk_get_rate(__clk_get_parent(hw->clk)) / rate; + + if (!(divider->flags & CLK_DIVIDER_ONE_BASED)) + div--; + + if (div > div_mask(divider)) + div = div_mask(divider); + + if (divider->lock) + spin_lock_irqsave(divider->lock, flags); + + val = readl(divider->reg); + val &= ~(div_mask(divider) << divider->shift); + val |= div << divider->shift; + writel(val, divider->reg); + + if (divider->lock) + spin_unlock_irqrestore(divider->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(clk_divider_set_rate); + +struct clk_ops clk_divider_ops = { + .recalc_rate = clk_divider_recalc_rate, + .round_rate = clk_divider_round_rate, + .set_rate = clk_divider_set_rate, +}; +EXPORT_SYMBOL_GPL(clk_divider_ops); + +struct clk *clk_register_divider(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + void __iomem *reg, u8 shift, u8 width, + u8 clk_divider_flags, spinlock_t *lock) +{ + struct clk_divider *div; + struct clk *clk; + + div = kzalloc(sizeof(struct clk_divider), GFP_KERNEL); + + if (!div) { + pr_err("%s: could not allocate divider clk\n", __func__); + return NULL; + } + + /* struct clk_divider assignments */ + div->reg = reg; + div->shift = shift; + div->width = width; + div->flags = clk_divider_flags; + div->lock = lock; + + if (parent_name) { + div->parent[0] = kstrdup(parent_name, GFP_KERNEL); + if (!div->parent[0]) + goto out; + } + + clk = clk_register(dev, name, + &clk_divider_ops, &div->hw, + div->parent, + (parent_name ? 1 : 0), + flags); + if (clk) + return clk; + +out: + kfree(div->parent[0]); + kfree(div); + + return NULL; +} diff --git a/drivers/clk/clk-fixed-rate.c b/drivers/clk/clk-fixed-rate.c new file mode 100644 index 000000000000..90c79fb5d1bd --- /dev/null +++ b/drivers/clk/clk-fixed-rate.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010-2011 Canonical Ltd <jeremy.kerr@canonical.com> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org> + * + * 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. + * + * Fixed rate clock implementation + */ + +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> + +/* + * DOC: basic fixed-rate clock that cannot gate + * + * Traits of this clock: + * prepare - clk_(un)prepare only ensures parents are prepared + * enable - clk_enable only ensures parents are enabled + * rate - rate is always a fixed value. No clk_set_rate support + * parent - fixed parent. No clk_set_parent support + */ + +#define to_clk_fixed_rate(_hw) container_of(_hw, struct clk_fixed_rate, hw) + +static unsigned long clk_fixed_rate_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return to_clk_fixed_rate(hw)->fixed_rate; +} +EXPORT_SYMBOL_GPL(clk_fixed_rate_recalc_rate); + +struct clk_ops clk_fixed_rate_ops = { + .recalc_rate = clk_fixed_rate_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_fixed_rate_ops); + +struct clk *clk_register_fixed_rate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + unsigned long fixed_rate) +{ + struct clk_fixed_rate *fixed; + char **parent_names = NULL; + u8 len; + + fixed = kzalloc(sizeof(struct clk_fixed_rate), GFP_KERNEL); + + if (!fixed) { + pr_err("%s: could not allocate fixed clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + /* struct clk_fixed_rate assignments */ + fixed->fixed_rate = fixed_rate; + + if (parent_name) { + parent_names = kmalloc(sizeof(char *), GFP_KERNEL); + + if (! parent_names) + goto out; + + len = sizeof(char) * strlen(parent_name); + + parent_names[0] = kmalloc(len, GFP_KERNEL); + + if (!parent_names[0]) + goto out; + + strncpy(parent_names[0], parent_name, len); + } + +out: + return clk_register(dev, name, + &clk_fixed_rate_ops, &fixed->hw, + parent_names, + (parent_name ? 1 : 0), + flags); +} diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c new file mode 100644 index 000000000000..b5902e2ef2fd --- /dev/null +++ b/drivers/clk/clk-gate.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2010-2011 Canonical Ltd <jeremy.kerr@canonical.com> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org> + * + * 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. + * + * Gated clock implementation + */ + +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/string.h> + +/** + * DOC: basic gatable clock which can gate and ungate it's ouput + * + * Traits of this clock: + * prepare - clk_(un)prepare only ensures parent is (un)prepared + * enable - clk_enable and clk_disable are functional & control gating + * rate - inherits rate from parent. No clk_set_rate support + * parent - fixed parent. No clk_set_parent support + */ + +#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw) + +static void clk_gate_set_bit(struct clk_gate *gate) +{ + u32 reg; + unsigned long flags = 0; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + + reg = readl(gate->reg); + reg |= BIT(gate->bit_idx); + writel(reg, gate->reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); +} + +static void clk_gate_clear_bit(struct clk_gate *gate) +{ + u32 reg; + unsigned long flags = 0; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + + reg = readl(gate->reg); + reg &= ~BIT(gate->bit_idx); + writel(reg, gate->reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); +} + +static int clk_gate_enable(struct clk_hw *hw) +{ + struct clk_gate *gate = to_clk_gate(hw); + + if (gate->flags & CLK_GATE_SET_TO_DISABLE) + clk_gate_clear_bit(gate); + else + clk_gate_set_bit(gate); + + return 0; +} +EXPORT_SYMBOL_GPL(clk_gate_enable); + +static void clk_gate_disable(struct clk_hw *hw) +{ + struct clk_gate *gate = to_clk_gate(hw); + + if (gate->flags & CLK_GATE_SET_TO_DISABLE) + clk_gate_set_bit(gate); + else + clk_gate_clear_bit(gate); +} +EXPORT_SYMBOL_GPL(clk_gate_disable); + +static int clk_gate_is_enabled(struct clk_hw *hw) +{ + u32 reg; + struct clk_gate *gate = to_clk_gate(hw); + + reg = readl(gate->reg); + + /* if a set bit disables this clk, flip it before masking */ + if (gate->flags & CLK_GATE_SET_TO_DISABLE) + reg ^= BIT(gate->bit_idx); + + reg &= BIT(gate->bit_idx); + + return reg ? 1 : 0; +} +EXPORT_SYMBOL_GPL(clk_gate_is_enabled); + +struct clk_ops clk_gate_ops = { + .enable = clk_gate_enable, + .disable = clk_gate_disable, + .is_enabled = clk_gate_is_enabled, +}; +EXPORT_SYMBOL_GPL(clk_gate_ops); + +struct clk *clk_register_gate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock) +{ + struct clk_gate *gate; + struct clk *clk; + + gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL); + + if (!gate) { + pr_err("%s: could not allocate gated clk\n", __func__); + return NULL; + } + + /* struct clk_gate assignments */ + gate->reg = reg; + gate->bit_idx = bit_idx; + gate->flags = clk_gate_flags; + gate->lock = lock; + + if (parent_name) { + gate->parent[0] = kstrdup(parent_name, GFP_KERNEL); + if (!gate->parent[0]) + goto out; + } + + clk = clk_register(dev, name, + &clk_gate_ops, &gate->hw, + gate->parent, + (parent_name ? 1 : 0), + flags); + if (clk) + return clk; +out: + kfree(gate->parent[0]); + kfree(gate); + + return NULL; +} diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c new file mode 100644 index 000000000000..c71ad1f41a97 --- /dev/null +++ b/drivers/clk/clk-mux.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> + * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org> + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org> + * + * 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. + * + * Simple multiplexer clock implementation + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> + +/* + * DOC: basic adjustable multiplexer clock that cannot gate + * + * Traits of this clock: + * prepare - clk_prepare only ensures that parents are prepared + * enable - clk_enable only ensures that parents are enabled + * rate - rate is only affected by parent switching. No clk_set_rate support + * parent - parent is adjustable through clk_set_parent + */ + +#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) + +static u8 clk_mux_get_parent(struct clk_hw *hw) +{ + struct clk_mux *mux = to_clk_mux(hw); + u32 val; + + /* + * FIXME need a mux-specific flag to determine if val is bitwise or numeric + * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 + * to 0x7 (index starts at one) + * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so + * val = 0x4 really means "bit 2, index starts at bit 0" + */ + val = readl(mux->reg) >> mux->shift; + val &= (1 << mux->width) - 1; + + if (val && (mux->flags & CLK_MUX_INDEX_BIT)) + val = ffs(val) - 1; + + if (val && (mux->flags & CLK_MUX_INDEX_ONE)) + val--; + + if (val >= __clk_get_num_parents(hw->clk)) + return -EINVAL; + + return val; +} +EXPORT_SYMBOL_GPL(clk_mux_get_parent); + +static int clk_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_mux *mux = to_clk_mux(hw); + u32 val; + unsigned long flags = 0; + + if (mux->flags & CLK_MUX_INDEX_BIT) + index = (1 << ffs(index)); + + if (mux->flags & CLK_MUX_INDEX_ONE) + index++; + + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + + val = readl(mux->reg); + val &= ~(((1 << mux->width) - 1) << mux->shift); + val |= index << mux->shift; + writel(val, mux->reg); + + if (mux->lock) + spin_unlock_irqrestore(mux->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(clk_mux_set_parent); + +struct clk_ops clk_mux_ops = { + .get_parent = clk_mux_get_parent, + .set_parent = clk_mux_set_parent, +}; +EXPORT_SYMBOL_GPL(clk_mux_ops); + +struct clk *clk_register_mux(struct device *dev, const char *name, + char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u8 width, + u8 clk_mux_flags, spinlock_t *lock) +{ + struct clk_mux *mux; + + mux = kmalloc(sizeof(struct clk_mux), GFP_KERNEL); + + if (!mux) { + pr_err("%s: could not allocate mux clk\n", __func__); + return ERR_PTR(-ENOMEM); + } + + /* struct clk_mux assignments */ + mux->reg = reg; + mux->shift = shift; + mux->width = width; + mux->flags = clk_mux_flags; + mux->lock = lock; + + return clk_register(dev, name, &clk_mux_ops, &mux->hw, + parent_names, num_parents, flags); +} diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c new file mode 100644 index 000000000000..9cf6f59e3e19 --- /dev/null +++ b/drivers/clk/clk.c @@ -0,0 +1,1461 @@ +/* + * Copyright (C) 2010-2011 Canonical Ltd <jeremy.kerr@canonical.com> + * Copyright (C) 2011-2012 Linaro Ltd <mturquette@linaro.org> + * + * 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. + * + * Standard functionality for the common clock API. See Documentation/clk.txt + */ + +#include <linux/clk-private.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <linux/list.h> +#include <linux/slab.h> + +static DEFINE_SPINLOCK(enable_lock); +static DEFINE_MUTEX(prepare_lock); + +static HLIST_HEAD(clk_root_list); +static HLIST_HEAD(clk_orphan_list); +static LIST_HEAD(clk_notifier_list); + +/*** debugfs support ***/ + +#ifdef CONFIG_COMMON_CLK_DEBUG +#include <linux/debugfs.h> + +static struct dentry *rootdir; +static struct dentry *orphandir; +static int inited = 0; + +/* caller must hold prepare_lock */ +static int clk_debug_create_one(struct clk *clk, struct dentry *pdentry) +{ + struct dentry *d; + int ret = -ENOMEM; + + if (!clk || !pdentry) { + ret = -EINVAL; + goto out; + } + + d = debugfs_create_dir(clk->name, pdentry); + if (!d) + goto out; + + clk->dentry = d; + + d = debugfs_create_u32("clk_rate", S_IRUGO, clk->dentry, + (u32 *)&clk->rate); + if (!d) + goto err_out; + + d = debugfs_create_x32("clk_flags", S_IRUGO, clk->dentry, + (u32 *)&clk->flags); + if (!d) + goto err_out; + + d = debugfs_create_u32("clk_prepare_count", S_IRUGO, clk->dentry, + (u32 *)&clk->prepare_count); + if (!d) + goto err_out; + + d = debugfs_create_u32("clk_enable_count", S_IRUGO, clk->dentry, + (u32 *)&clk->enable_count); + if (!d) + goto err_out; + + d = debugfs_create_u32("clk_notifier_count", S_IRUGO, clk->dentry, + (u32 *)&clk->notifier_count); + if (!d) + goto err_out; + + ret = 0; + goto out; + +err_out: + debugfs_remove(clk->dentry); +out: + return ret; +} + +/* caller must hold prepare_lock */ +static int clk_debug_create_subtree(struct clk *clk, struct dentry *pdentry) +{ + struct clk *child; + struct hlist_node *tmp; + int ret = -EINVAL;; + + if (!clk || !pdentry) + goto out; + + ret = clk_debug_create_one(clk, pdentry); + + if (ret) + goto out; + + hlist_for_each_entry(child, tmp, &clk->children, child_node) + clk_debug_create_subtree(child, clk->dentry); + + ret = 0; +out: + return ret; +} + +/** + * clk_debug_register - add a clk node to the debugfs clk tree + * @clk: the clk being added to the debugfs clk tree + * + * Dynamically adds a clk to the debugfs clk tree if debugfs has been + * initialized. Otherwise it bails out early since the debugfs clk tree + * will be created lazily by clk_debug_init as part of a late_initcall. + * + * Caller must hold prepare_lock. Only clk_init calls this function (so + * far) so this is taken care. + */ +static int clk_debug_register(struct clk *clk) +{ + struct clk *parent; + struct dentry *pdentry; + int ret = 0; + + if (!inited) + goto out; + + parent = clk->parent; + + /* + * Check to see if a clk is a root clk. Also check that it is + * safe to add this clk to debugfs + */ + if (!parent) + if (clk->flags & CLK_IS_ROOT) + pdentry = rootdir; + else + pdentry = orphandir; + else + if (parent->dentry) + pdentry = parent->dentry; + else + goto out; + + ret = clk_debug_create_subtree(clk, pdentry); + +out: + return ret; +} + +/** + * clk_debug_init - lazily create the debugfs clk tree visualization + * + * clks are often initialized very early during boot before memory can + * be dynamically allocated and well before debugfs is setup. + * clk_debug_init walks the clk tree hierarchy while holding + * prepare_lock and creates the topology as part of a late_initcall, + * thus insuring that clks initialized very early will still be + * represented in the debugfs clk tree. This function should only be + * called once at boot-time, and all other clks added dynamically will + * be done so with clk_debug_register. + */ +static int __init clk_debug_init(void) +{ + struct clk *clk; + struct hlist_node *tmp; + + rootdir = debugfs_create_dir("clk", NULL); + + if (!rootdir) + return -ENOMEM; + + orphandir = debugfs_create_dir("orphans", rootdir); + + if (!orphandir) + return -ENOMEM; + + mutex_lock(&prepare_lock); + + hlist_for_each_entry(clk, tmp, &clk_root_list, child_node) + clk_debug_create_subtree(clk, rootdir); + + hlist_for_each_entry(clk, tmp, &clk_orphan_list, child_node) + clk_debug_create_subtree(clk, orphandir); + + inited = 1; + + mutex_unlock(&prepare_lock); + + return 0; +} +late_initcall(clk_debug_init); +#else +static inline int clk_debug_register(struct clk *clk) { return 0; } +#endif /* CONFIG_COMMON_CLK_DEBUG */ + +#ifdef CONFIG_COMMON_CLK_DISABLE_UNUSED +/* caller must hold prepare_lock */ +static void clk_disable_unused_subtree(struct clk *clk) +{ + struct clk *child; + struct hlist_node *tmp; + unsigned long flags; + + if (!clk) + goto out; + + hlist_for_each_entry(child, tmp, &clk->children, child_node) + clk_disable_unused_subtree(child); + + spin_lock_irqsave(&enable_lock, flags); + + if (clk->enable_count) + goto unlock_out; + + if (clk->flags & CLK_IGNORE_UNUSED) + goto unlock_out; + + if (__clk_is_enabled(clk) && clk->ops->disable) + clk->ops->disable(clk->hw); + +unlock_out: + spin_unlock_irqrestore(&enable_lock, flags); + +out: + return; +} + +static int clk_disable_unused(void) +{ + struct clk *clk; + struct hlist_node *tmp; + + mutex_lock(&prepare_lock); + + hlist_for_each_entry(clk, tmp, &clk_root_list, child_node) + clk_disable_unused_subtree(clk); + + hlist_for_each_entry(clk, tmp, &clk_orphan_list, child_node) + clk_disable_unused_subtree(clk); + + mutex_unlock(&prepare_lock); + + return 0; +} +late_initcall(clk_disable_unused); +#else +static inline int clk_disable_unused(struct clk *clk) { return 0; } +#endif /* CONFIG_COMMON_CLK_DISABLE_UNUSED */ + +/*** helper functions ***/ + +inline const char *__clk_get_name(struct clk *clk) +{ + return !clk ? NULL : clk->name; +} + +inline struct clk_hw *__clk_get_hw(struct clk *clk) +{ + return !clk ? NULL : clk->hw; +} + +inline u8 __clk_get_num_parents(struct clk *clk) +{ + return !clk ? -EINVAL : clk->num_parents; +} + +inline struct clk *__clk_get_parent(struct clk *clk) +{ + return !clk ? NULL : clk->parent; +} + +inline int __clk_get_enable_count(struct clk *clk) +{ + return !clk ? -EINVAL : clk->enable_count; +} + +inline int __clk_get_prepare_count(struct clk *clk) +{ + return !clk ? -EINVAL : clk->prepare_count; +} + +unsigned long __clk_get_rate(struct clk *clk) +{ + unsigned long ret; + + if (!clk) { + ret = -EINVAL; + goto out; + } + + ret = clk->rate; + + if (clk->flags & CLK_IS_ROOT) + goto out; + + if (!clk->parent) + ret = -ENODEV; + +out: + return ret; +} + +inline unsigned long __clk_get_flags(struct clk *clk) +{ + return !clk ? -EINVAL : clk->flags; +} + +int __clk_is_enabled(struct clk *clk) +{ + int ret; + + if (!clk) + return -EINVAL; + + /* + * .is_enabled is only mandatory for clocks that gate + * fall back to software usage counter if .is_enabled is missing + */ + if (!clk->ops->is_enabled) { + ret = clk->enable_count ? 1 : 0; + goto out; + } + + ret = clk->ops->is_enabled(clk->hw); +out: + return ret; +} + +static struct clk *__clk_lookup_subtree(const char *name, struct clk *clk) +{ + struct clk *child; + struct clk *ret; + struct hlist_node *tmp; + + if (!strcmp(clk->name, name)) + return clk; + + hlist_for_each_entry(child, tmp, &clk->children, child_node) { + ret = __clk_lookup_subtree(name, child); + if (ret) + return ret; + } + + return NULL; +} + +struct clk *__clk_lookup(const char *name) +{ + struct clk *root_clk; + struct clk *ret; + struct hlist_node *tmp; + + if (!name) + return NULL; + + /* search the 'proper' clk tree first */ + hlist_for_each_entry(root_clk, tmp, &clk_root_list, child_node) { + ret = __clk_lookup_subtree(name, root_clk); + if (ret) + return ret; + } + + /* if not found, then search the orphan tree */ + hlist_for_each_entry(root_clk, tmp, &clk_orphan_list, child_node) { + ret = __clk_lookup_subtree(name, root_clk); + if (ret) + return ret; + } + + return NULL; +} + +/*** clk api ***/ + +void __clk_unprepare(struct clk *clk) +{ + if (!clk) + return; + + if (WARN_ON(clk->prepare_count == 0)) + return; + + if (--clk->prepare_count > 0) + return; + + WARN_ON(clk->enable_count > 0); + + if (clk->ops->unprepare) + clk->ops->unprepare(clk->hw); + + __clk_unprepare(clk->parent); +} + +/** + * clk_unprepare - undo preparation of a clock source + * @clk: the clk being unprepare + * + * clk_unprepare may sleep, which differentiates it from clk_disable. In a + * simple case, clk_unprepare can be used instead of clk_disable to gate a clk + * if the operation may sleep. One example is a clk which is accessed over + * I2c. In the complex case a clk gate operation may require a fast and a slow + * part. It is this reason that clk_unprepare and clk_disable are not mutually + * exclusive. In fact clk_disable must be called before clk_unprepare. + */ +void clk_unprepare(struct clk *clk) +{ + mutex_lock(&prepare_lock); + __clk_unprepare(clk); + mutex_unlock(&prepare_lock); +} +EXPORT_SYMBOL_GPL(clk_unprepare); + +int __clk_prepare(struct clk *clk) +{ + int ret = 0; + + if (!clk) + return 0; + + if (clk->prepare_count == 0) { + ret = __clk_prepare(clk->parent); + if (ret) + return ret; + + if (clk->ops->prepare) { + ret = clk->ops->prepare(clk->hw); + if (ret) { + __clk_unprepare(clk->parent); + return ret; + } + } + } + + clk->prepare_count++; + + return 0; +} + +/** + * clk_prepare - prepare a clock source + * @clk: the clk being prepared + * + * clk_prepare may sleep, which differentiates it from clk_enable. In a simple + * case, clk_prepare can be used instead of clk_enable to ungate a clk if the + * operation may sleep. One example is a clk which is accessed over I2c. In + * the complex case a clk ungate operation may require a fast and a slow part. + * It is this reason that clk_prepare and clk_enable are not mutually + * exclusive. In fact clk_prepare must be called before clk_enable. + * Returns 0 on success, -EERROR otherwise. + */ +int clk_prepare(struct clk *clk) +{ + int ret; + + mutex_lock(&prepare_lock); + ret = __clk_prepare(clk); + mutex_unlock(&prepare_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_prepare); + +static void __clk_disable(struct clk *clk) +{ + if (!clk) + return; + + if (WARN_ON(clk->enable_count == 0)) + return; + + if (--clk->enable_count > 0) + return; + + if (clk->ops->disable) + clk->ops->disable(clk->hw); + + __clk_disable(clk->parent); +} + +/** + * clk_disable - gate a clock + * @clk: the clk being gated + * + * clk_disable must not sleep, which differentiates it from clk_unprepare. In + * a simple case, clk_disable can be used instead of clk_unprepare to gate a + * clk if the operation is fast and will never sleep. One example is a + * SoC-internal clk which is controlled via simple register writes. In the + * complex case a clk gate operation may require a fast and a slow part. It is + * this reason that clk_unprepare and clk_disable are not mutually exclusive. + * In fact clk_disable must be called before clk_unprepare. + */ +void clk_disable(struct clk *clk) +{ + unsigned long flags; + + spin_lock_irqsave(&enable_lock, flags); + __clk_disable(clk); + spin_unlock_irqrestore(&enable_lock, flags); +} +EXPORT_SYMBOL_GPL(clk_disable); + +static int __clk_enable(struct clk *clk) +{ + int ret = 0; + + if (!clk) + return 0; + + if (WARN_ON(clk->prepare_count == 0)) + return -ESHUTDOWN; + + if (clk->enable_count == 0) { + ret = __clk_enable(clk->parent); + + if (ret) + return ret; + + if (clk->ops->enable) { + ret = clk->ops->enable(clk->hw); + if (ret) { + __clk_disable(clk->parent); + return ret; + } + } + } + + clk->enable_count++; + return 0; +} + +/** + * clk_enable - ungate a clock + * @clk: the clk being ungated + * + * clk_enable must not sleep, which differentiates it from clk_prepare. In a + * simple case, clk_enable can be used instead of clk_prepare to ungate a clk + * if the operation will never sleep. One example is a SoC-internal clk which + * is controlled via simple register writes. In the complex case a clk ungate + * operation may require a fast and a slow part. It is this reason that + * clk_enable and clk_prepare are not mutually exclusive. In fact clk_prepare + * must be called before clk_enable. Returns 0 on success, -EERROR + * otherwise. + */ +int clk_enable(struct clk *clk) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&enable_lock, flags); + ret = __clk_enable(clk); + spin_unlock_irqrestore(&enable_lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_enable); + +/** + * clk_get_rate - return the rate of clk + * @clk: the clk whose rate is being returned + * + * Simply returns the cached rate of the clk. Does not query the hardware. If + * clk is NULL then returns -EINVAL. + */ +unsigned long clk_get_rate(struct clk *clk) +{ + unsigned long rate; + + mutex_lock(&prepare_lock); + rate = __clk_get_rate(clk); + mutex_unlock(&prepare_lock); + + return rate; +} +EXPORT_SYMBOL_GPL(clk_get_rate); + +/** + * __clk_round_rate - round the given rate for a clk + * @clk: round the rate of this clock + * + * Caller must hold prepare_lock. Useful for clk_ops such as .set_rate + */ +unsigned long __clk_round_rate(struct clk *clk, unsigned long rate) +{ + unsigned long unused; + + if (!clk) + return -EINVAL; + + if (!clk->ops->round_rate) + return clk->rate; + + if (clk->flags & CLK_SET_RATE_PARENT) + return clk->ops->round_rate(clk->hw, rate, &unused); + else + return clk->ops->round_rate(clk->hw, rate, NULL); +} + +/** + * clk_round_rate - round the given rate for a clk + * @clk: the clk for which we are rounding a rate + * @rate: the rate which is to be rounded + * + * Takes in a rate as input and rounds it to a rate that the clk can actually + * use which is then returned. If clk doesn't support round_rate operation + * then the parent rate is returned. + */ +long clk_round_rate(struct clk *clk, unsigned long rate) +{ + unsigned long ret; + + mutex_lock(&prepare_lock); + ret = __clk_round_rate(clk, rate); + mutex_unlock(&prepare_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_round_rate); + +/** + * __clk_notify - call clk notifier chain + * @clk: struct clk * that is changing rate + * @msg: clk notifier type (see include/linux/clk.h) + * @old_rate: old clk rate + * @new_rate: new clk rate + * + * Triggers a notifier call chain on the clk rate-change notification + * for 'clk'. Passes a pointer to the struct clk and the previous + * and current rates to the notifier callback. Intended to be called by + * internal clock code only. Returns NOTIFY_DONE from the last driver + * called if all went well, or NOTIFY_STOP or NOTIFY_BAD immediately if + * a driver returns that. + */ +static int __clk_notify(struct clk *clk, unsigned long msg, + unsigned long old_rate, unsigned long new_rate) +{ + struct clk_notifier *cn; + struct clk_notifier_data cnd; + int ret = NOTIFY_DONE; + + cnd.clk = clk; + cnd.old_rate = old_rate; + cnd.new_rate = new_rate; + + list_for_each_entry(cn, &clk_notifier_list, node) { + if (cn->clk == clk) { + ret = srcu_notifier_call_chain(&cn->notifier_head, msg, + &cnd); + break; + } + } + + return ret; +} + +/** + * __clk_recalc_rates + * @clk: first clk in the subtree + * @msg: notification type (see include/linux/clk.h) + * + * Walks the subtree of clks starting with clk and recalculates rates as it + * goes. Note that if a clk does not implement the .recalc_rate callback then + * it is assumed that the clock will take on the rate of it's parent. + * + * clk_recalc_rates also propagates the POST_RATE_CHANGE notification, + * if necessary. + * + * Caller must hold prepare_lock. + */ +static void __clk_recalc_rates(struct clk *clk, unsigned long msg) +{ + unsigned long old_rate; + unsigned long parent_rate = 0; + struct hlist_node *tmp; + struct clk *child; + + old_rate = clk->rate; + + if (clk->parent) + parent_rate = clk->parent->rate; + + if (clk->ops->recalc_rate) + clk->rate = clk->ops->recalc_rate(clk->hw, parent_rate); + else + clk->rate = parent_rate; + + /* + * ignore NOTIFY_STOP and NOTIFY_BAD return values for POST_RATE_CHANGE + * & ABORT_RATE_CHANGE notifiers + */ + if (clk->notifier_count && msg) + __clk_notify(clk, msg, old_rate, clk->rate); + + hlist_for_each_entry(child, tmp, &clk->children, child_node) + __clk_recalc_rates(child, msg); +} + +/** + * __clk_speculate_rates + * @clk: first clk in the subtree + * @parent_rate: the "future" rate of clk's parent + * + * Walks the subtree of clks starting with clk, speculating rates as it + * goes and firing off PRE_RATE_CHANGE notifications as necessary. + * + * Unlike clk_recalc_rates, clk_speculate_rates exists only for sending + * pre-rate change notifications and returns early if no clks in the + * subtree have subscribed to the notifications. Note that if a clk does not + * implement the .recalc_rate callback then it is assumed that the clock will + * take on the rate of it's parent. + * + * Caller must hold prepare_lock. + */ +static int __clk_speculate_rates(struct clk *clk, unsigned long parent_rate) +{ + struct hlist_node *tmp; + struct clk *child; + unsigned long new_rate; + int ret = NOTIFY_DONE; + + if (clk->ops->recalc_rate) + new_rate = clk->ops->recalc_rate(clk->hw, parent_rate); + else + new_rate = parent_rate; + + /* abort the rate change if a driver returns NOTIFY_BAD */ + if (clk->notifier_count) + ret = __clk_notify(clk, PRE_RATE_CHANGE, clk->rate, new_rate); + + if (ret == NOTIFY_BAD) + goto out; + + hlist_for_each_entry(child, tmp, &clk->children, child_node) { + ret = __clk_speculate_rates(child, new_rate); + if (ret == NOTIFY_BAD) + break; + } + +out: + return ret; +} + +static void clk_calc_subtree(struct clk *clk, unsigned long new_rate) +{ + struct clk *child; + struct hlist_node *tmp; + + clk->new_rate = new_rate; + + hlist_for_each_entry(child, tmp, &clk->children, child_node) { + if (child->ops->recalc_rate) + child->new_rate = child->ops->recalc_rate(child->hw, new_rate); + else + child->new_rate = new_rate; + clk_calc_subtree(child, child->new_rate); + } +} + +/* + * calculate the new rates returning the topmost clock that has to be + * changed. + */ +static struct clk *clk_calc_new_rates(struct clk *clk, unsigned long rate) +{ + struct clk *top = clk; + unsigned long best_parent_rate = clk->parent->rate; + unsigned long new_rate; + + if (!clk->ops->round_rate && !(clk->flags & CLK_SET_RATE_PARENT)) { + clk->new_rate = clk->rate; + return NULL; + } + + if (!clk->ops->round_rate && (clk->flags & CLK_SET_RATE_PARENT)) { + top = clk_calc_new_rates(clk->parent, rate); + new_rate = clk->new_rate = clk->parent->new_rate; + + goto out; + } + + if (clk->flags & CLK_SET_RATE_PARENT) + new_rate = clk->ops->round_rate(clk->hw, rate, &best_parent_rate); + else + new_rate = clk->ops->round_rate(clk->hw, rate, NULL); + + if (best_parent_rate != clk->parent->rate) { + top = clk_calc_new_rates(clk->parent, best_parent_rate); + + goto out; + } + +out: + clk_calc_subtree(clk, new_rate); + + return top; +} + +/* + * Notify about rate changes in a subtree. Always walk down the whole tree + * so that in case of an error we can walk down the whole tree again and + * abort the change. + */ +static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long event) +{ + struct hlist_node *tmp; + struct clk *child, *fail_clk = NULL; + int ret = NOTIFY_DONE; + + if (clk->rate == clk->new_rate) + return 0; + + if (clk->notifier_count) { + ret = __clk_notify(clk, event, clk->rate, clk->new_rate); + if (ret == NOTIFY_BAD) + fail_clk = clk; + } + + hlist_for_each_entry(child, tmp, &clk->children, child_node) { + clk = clk_propagate_rate_change(child, event); + if (clk) + fail_clk = clk; + } + + return fail_clk; +} + +/* + * walk down a subtree and set the new rates notifying the rate + * change on the way + */ +static void clk_change_rate(struct clk *clk) +{ + struct clk *child; + unsigned long old_rate; + struct hlist_node *tmp; + + old_rate = clk->rate; + + if (clk->ops->set_rate) + clk->ops->set_rate(clk->hw, clk->new_rate); + + if (clk->ops->recalc_rate) + clk->rate = clk->ops->recalc_rate(clk->hw, + clk->parent->rate); + else + clk->rate = clk->parent->rate; + + if (clk->notifier_count && old_rate != clk->rate) + __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate); + + hlist_for_each_entry(child, tmp, &clk->children, child_node) + clk_change_rate(child); +} + +/** + * clk_set_rate - specify a new rate for clk + * @clk: the clk whose rate is being changed + * @rate: the new rate for clk + * + * In the simplest case clk_set_rate will only change the rate of clk. + * + * If clk has the CLK_SET_RATE_GATE flag set and it is enabled this call + * will fail; only when the clk is disabled will it be able to change + * its rate. + * + * Setting the CLK_SET_RATE_PARENT flag allows clk_set_rate to + * recursively propagate up to clk's parent; whether or not this happens + * depends on the outcome of clk's .round_rate implementation. If + * *parent_rate is 0 after calling .round_rate then upstream parent + * propagation is ignored. If *parent_rate comes back with a new rate + * for clk's parent then we propagate up to clk's parent and set it's + * rate. Upward propagation will continue until either a clk does not + * support the CLK_SET_RATE_PARENT flag or .round_rate stops requesting + * changes to clk's parent_rate. If there is a failure during upstream + * propagation then clk_set_rate will unwind and restore each clk's rate + * that had been successfully changed. Afterwards a rate change abort + * notification will be propagated downstream, starting from the clk + * that failed. + * + * At the end of all of the rate setting, clk_set_rate internally calls + * __clk_recalc_rates and propagates the rate changes downstream, + * starting from the highest clk whose rate was changed. This has the + * added benefit of propagating post-rate change notifiers. + * + * Note that while post-rate change and rate change abort notifications + * are guaranteed to be sent to a clk only once per call to + * clk_set_rate, pre-change notifications will be sent for every clk + * whose rate is changed. Stacking pre-change notifications is noisy + * for the drivers subscribed to them, but this allows drivers to react + * to intermediate clk rate changes up until the point where the final + * rate is achieved at the end of upstream propagation. + * + * Returns 0 on success, -EERROR otherwise. + */ +int clk_set_rate(struct clk *clk, unsigned long rate) +{ + struct clk *top, *fail_clk; + int ret = 0; + + /* prevent racing with updates to the clock topology */ + mutex_lock(&prepare_lock); + + /* bail early if nothing to do */ + if (rate == clk->rate) + goto out; + + /* calculate new rates and get the topmost changed clock */ + top = clk_calc_new_rates(clk, rate); + if (!top) { + ret = -EINVAL; + goto out; + } + + /* notify that we are about to change rates */ + fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE); + if (fail_clk) { + pr_warn("%s: failed to set %s rate\n", __func__, + fail_clk->name); + clk_propagate_rate_change(top, ABORT_RATE_CHANGE); + ret = -EBUSY; + goto out; + } + + /* change the rates */ + clk_change_rate(top); + + mutex_unlock(&prepare_lock); + + return 0; +out: + mutex_unlock(&prepare_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_set_rate); + +/** + * clk_get_parent - return the parent of a clk + * @clk: the clk whose parent gets returned + * + * Simply returns clk->parent. Returns NULL if clk is NULL. + */ +struct clk *clk_get_parent(struct clk *clk) +{ + struct clk *parent; + + mutex_lock(&prepare_lock); + parent = __clk_get_parent(clk); + mutex_unlock(&prepare_lock); + + return parent; +} +EXPORT_SYMBOL_GPL(clk_get_parent); + +/* + * .get_parent is mandatory for clocks with multiple possible parents. It is + * optional for single-parent clocks. Always call .get_parent if it is + * available and WARN if it is missing for multi-parent clocks. + * + * For single-parent clocks without .get_parent, first check to see if the + * .parents array exists, and if so use it to avoid an expensive tree + * traversal. If .parents does not exist then walk the tree with __clk_lookup. + */ +static struct clk *__clk_init_parent(struct clk *clk) +{ + struct clk *ret = NULL; + u8 index; + + /* handle the trivial cases */ + + if (!clk->num_parents) + goto out; + + if (clk->num_parents == 1) { + if (IS_ERR_OR_NULL(clk->parent)) + ret = clk->parent = __clk_lookup(clk->parent_names[0]); + ret = clk->parent; + goto out; + } + + if (!clk->ops->get_parent) { + WARN(!clk->ops->get_parent, + "%s: multi-parent clocks must implement .get_parent\n", + __func__); + goto out; + }; + + /* + * Do our best to cache parent clocks in clk->parents. This prevents + * unnecessary and expensive calls to __clk_lookup. We don't set + * clk->parent here; that is done by the calling function + */ + + index = clk->ops->get_parent(clk->hw); + + if (!clk->parents) + clk->parents = + kmalloc((sizeof(struct clk*) * clk->num_parents), + GFP_KERNEL); + + if (!clk->parents) + ret = __clk_lookup(clk->parent_names[index]); + else if (!clk->parents[index]) + ret = clk->parents[index] = + __clk_lookup(clk->parent_names[index]); + else + ret = clk->parents[index]; + +out: + return ret; +} + +void __clk_reparent(struct clk *clk, struct clk *new_parent) +{ +#ifdef CONFIG_COMMON_CLK_DEBUG + struct dentry *d; + struct dentry *new_parent_d; +#endif + + if (!clk || !new_parent) + return; + + hlist_del(&clk->child_node); + + if (new_parent) + hlist_add_head(&clk->child_node, &new_parent->children); + else + hlist_add_head(&clk->child_node, &clk_orphan_list); + +#ifdef CONFIG_COMMON_CLK_DEBUG + if (!inited) + goto out; + + if (new_parent) + new_parent_d = new_parent->dentry; + else + new_parent_d = orphandir; + + d = debugfs_rename(clk->dentry->d_parent, clk->dentry, + new_parent_d, clk->name); + if (d) + clk->dentry = d; + else + pr_debug("%s: failed to rename debugfs entry for %s\n", + __func__, clk->name); +out: +#endif + + clk->parent = new_parent; + + __clk_recalc_rates(clk, POST_RATE_CHANGE); +} + +static int __clk_set_parent(struct clk *clk, struct clk *parent) +{ + struct clk *old_parent; + unsigned long flags; + int ret = -EINVAL; + u8 i; + + old_parent = clk->parent; + + /* find index of new parent clock using cached parent ptrs */ + for (i = 0; i < clk->num_parents; i++) + if (clk->parents[i] == parent) + break; + + /* + * find index of new parent clock using string name comparison + * also try to cache the parent to avoid future calls to __clk_lookup + */ + if (i == clk->num_parents) + for (i = 0; i < clk->num_parents; i++) + if (!strcmp(clk->parent_names[i], parent->name)) { + clk->parents[i] = __clk_lookup(parent->name); + break; + } + + if (i == clk->num_parents) { + pr_debug("%s: clock %s is not a possible parent of clock %s\n", + __func__, parent->name, clk->name); + goto out; + } + + /* migrate prepare and enable */ + if (clk->prepare_count) + __clk_prepare(parent); + + /* FIXME replace with clk_is_enabled(clk) someday */ + spin_lock_irqsave(&enable_lock, flags); + if (clk->enable_count) + __clk_enable(parent); + spin_unlock_irqrestore(&enable_lock, flags); + + /* change clock input source */ + ret = clk->ops->set_parent(clk->hw, i); + + /* clean up old prepare and enable */ + spin_lock_irqsave(&enable_lock, flags); + if (clk->enable_count) + __clk_disable(old_parent); + spin_unlock_irqrestore(&enable_lock, flags); + + if (clk->prepare_count) + __clk_unprepare(old_parent); + +out: + return ret; +} + +/** + * clk_set_parent - switch the parent of a mux clk + * @clk: the mux clk whose input we are switching + * @parent: the new input to clk + * + * Re-parent clk to use parent as it's new input source. If clk has the + * CLK_SET_PARENT_GATE flag set then clk must be gated for this + * operation to succeed. After successfully changing clk's parent + * clk_set_parent will update the clk topology, sysfs topology and + * propagate rate recalculation via __clk_recalc_rates. Returns 0 on + * success, -EERROR otherwise. + */ +int clk_set_parent(struct clk *clk, struct clk *parent) +{ + int ret = 0; + + if (!clk || !clk->ops) + return -EINVAL; + + if (!clk->ops->set_parent) + return -ENOSYS; + + /* prevent racing with updates to the clock topology */ + mutex_lock(&prepare_lock); + + if (clk->parent == parent) + goto out; + + /* propagate PRE_RATE_CHANGE notifications */ + if (clk->notifier_count) + ret = __clk_speculate_rates(clk, parent->rate); + + /* abort if a driver objects */ + if (ret == NOTIFY_STOP) + goto out; + + /* only re-parent if the clock is not in use */ + if ((clk->flags & CLK_SET_PARENT_GATE) && clk->prepare_count) + ret = -EBUSY; + else + ret = __clk_set_parent(clk, parent); + + /* propagate ABORT_RATE_CHANGE if .set_parent failed */ + if (ret) { + __clk_recalc_rates(clk, ABORT_RATE_CHANGE); + goto out; + } + + /* propagate rate recalculation downstream */ + __clk_reparent(clk, parent); + +out: + mutex_unlock(&prepare_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_set_parent); + +/** + * __clk_init - initialize the data structures in a struct clk + * @dev: device initializing this clk, placeholder for now + * @clk: clk being initialized + * + * Initializes the lists in struct clk, queries the hardware for the + * parent and rate and sets them both. + * + * Any struct clk passed into __clk_init must have the following members + * populated: + * .name + * .ops + * .hw + * .parent_names + * .num_parents + * .flags + * + * Essentially, everything that would normally be passed into clk_register is + * assumed to be initialized already in __clk_init. The other members may be + * populated, but are optional. + * + * __clk_init is only exposed via clk-private.h and is intended for use with + * very large numbers of clocks that need to be statically initialized. It is + * a layering violation to include clk-private.h from any code which implements + * a clock's .ops; as such any statically initialized clock data MUST be in a + * separate C file from the logic that implements it's operations. + */ +void __clk_init(struct device *dev, struct clk *clk) +{ + int i; + struct clk *orphan; + struct hlist_node *tmp, *tmp2; + + if (!clk) + return; + + mutex_lock(&prepare_lock); + + /* check to see if a clock with this name is already registered */ + if (__clk_lookup(clk->name)) + goto out; + + /* throw a WARN if any entries in parent_names are NULL */ + for (i = 0; i < clk->num_parents; i++) + WARN(!clk->parent_names[i], + "%s: invalid NULL in %s's .parent_names\n", + __func__, clk->name); + + /* + * Allocate an array of struct clk *'s to avoid unnecessary string + * look-ups of clk's possible parents. This can fail for clocks passed + * in to clk_init during early boot; thus any access to clk->parents[] + * must always check for a NULL pointer and try to populate it if + * necessary. + * + * If clk->parents is not NULL we skip this entire block. This allows + * for clock drivers to statically initialize clk->parents. + */ + if (clk->num_parents && !clk->parents) { + clk->parents = kmalloc((sizeof(struct clk*) * clk->num_parents), + GFP_KERNEL); + /* + * __clk_lookup returns NULL for parents that have not been + * clk_init'd; thus any access to clk->parents[] must check + * for a NULL pointer. We can always perform lazy lookups for + * missing parents later on. + */ + if (clk->parents) + for (i = 0; i < clk->num_parents; i++) + clk->parents[i] = + __clk_lookup(clk->parent_names[i]); + } + + clk->parent = __clk_init_parent(clk); + + /* + * Populate clk->parent if parent has already been __clk_init'd. If + * parent has not yet been __clk_init'd then place clk in the orphan + * list. If clk has set the CLK_IS_ROOT flag then place it in the root + * clk list. + * + * Every time a new clk is clk_init'd then we walk the list of orphan + * clocks and re-parent any that are children of the clock currently + * being clk_init'd. + */ + if (clk->parent) + hlist_add_head(&clk->child_node, + &clk->parent->children); + else if (clk->flags & CLK_IS_ROOT) + hlist_add_head(&clk->child_node, &clk_root_list); + else + hlist_add_head(&clk->child_node, &clk_orphan_list); + + /* + * Set clk's rate. The preferred method is to use .recalc_rate. For + * simple clocks and lazy developers the default fallback is to use the + * parent's rate. If a clock doesn't have a parent (or is orphaned) + * then rate is set to zero. + */ + if (clk->ops->recalc_rate) + clk->rate = clk->ops->recalc_rate(clk->hw, + __clk_get_rate(clk->parent)); + else if (clk->parent) + clk->rate = clk->parent->rate; + else + clk->rate = 0; + + /* + * walk the list of orphan clocks and reparent any that are children of + * this clock + */ + hlist_for_each_entry_safe(orphan, tmp, tmp2, &clk_orphan_list, child_node) + for (i = 0; i < orphan->num_parents; i++) + if (!strcmp(clk->name, orphan->parent_names[i])) { + __clk_reparent(orphan, clk); + break; + } + + /* + * optional platform-specific magic + * + * The .init callback is not used by any of the basic clock types, but + * exists for weird hardware that must perform initialization magic. + * Please consider other ways of solving initialization problems before + * using this callback, as it's use is discouraged. + */ + if (clk->ops->init) + clk->ops->init(clk->hw); + + clk_debug_register(clk); + +out: + mutex_unlock(&prepare_lock); + + return; +} + +/** + * clk_register - allocate a new clock, register it and return an opaque cookie + * @dev: device that is registering this clock + * @name: clock name + * @ops: operations this clock supports + * @hw: link to hardware-specific clock data + * @parent_names: array of string names for all possible parents + * @num_parents: number of possible parents + * @flags: framework-level hints and quirks + * + * clk_register is the primary interface for populating the clock tree with new + * clock nodes. It returns a pointer to the newly allocated struct clk which + * cannot be dereferenced by driver code but may be used in conjuction with the + * rest of the clock API. + */ +struct clk *clk_register(struct device *dev, const char *name, + const struct clk_ops *ops, struct clk_hw *hw, + char **parent_names, u8 num_parents, unsigned long flags) +{ + struct clk *clk; + + clk = kzalloc(sizeof(*clk), GFP_KERNEL); + if (!clk) + return NULL; + + clk->name = name; + clk->ops = ops; + clk->hw = hw; + clk->flags = flags; + clk->parent_names = parent_names; + clk->num_parents = num_parents; + hw->clk = clk; + + __clk_init(dev, clk); + + return clk; +} +EXPORT_SYMBOL_GPL(clk_register); + +/*** clk rate change notifiers ***/ + +/** + * clk_notifier_register - add a clk rate change notifier + * @clk: struct clk * to watch + * @nb: struct notifier_block * with callback info + * + * Request notification when clk's rate changes. This uses an SRCU + * notifier because we want it to block and notifier unregistrations are + * uncommon. The callbacks associated with the notifier must not + * re-enter into the clk framework by calling any top-level clk APIs; + * this will cause a nested prepare_lock mutex. + * + * Pre-change notifier callbacks will be passed the current, pre-change + * rate of the clk via struct clk_notifier_data.old_rate. The new, + * post-change rate of the clk is passed via struct + * clk_notifier_data.new_rate. + * + * Post-change notifiers will pass the now-current, post-change rate of + * the clk in both struct clk_notifier_data.old_rate and struct + * clk_notifier_data.new_rate. + * + * Abort-change notifiers are effectively the opposite of pre-change + * notifiers: the original pre-change clk rate is passed in via struct + * clk_notifier_data.new_rate and the failed post-change rate is passed + * in via struct clk_notifier_data.old_rate. + * + * clk_notifier_register() must be called from non-atomic context. + * Returns -EINVAL if called with null arguments, -ENOMEM upon + * allocation failure; otherwise, passes along the return value of + * srcu_notifier_chain_register(). + */ +int clk_notifier_register(struct clk *clk, struct notifier_block *nb) +{ + struct clk_notifier *cn; + int ret = -ENOMEM; + + if (!clk || !nb) + return -EINVAL; + + mutex_lock(&prepare_lock); + + /* search the list of notifiers for this clk */ + list_for_each_entry(cn, &clk_notifier_list, node) + if (cn->clk == clk) + break; + + /* if clk wasn't in the notifier list, allocate new clk_notifier */ + if (cn->clk != clk) { + cn = kzalloc(sizeof(struct clk_notifier), GFP_KERNEL); + if (!cn) + goto out; + + cn->clk = clk; + srcu_init_notifier_head(&cn->notifier_head); + + list_add(&cn->node, &clk_notifier_list); + } + + ret = srcu_notifier_chain_register(&cn->notifier_head, nb); + + clk->notifier_count++; + +out: + mutex_unlock(&prepare_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_notifier_register); + +/** + * clk_notifier_unregister - remove a clk rate change notifier + * @clk: struct clk * + * @nb: struct notifier_block * with callback info + * + * Request no further notification for changes to 'clk' and frees memory + * allocated in clk_notifier_register. + * + * Returns -EINVAL if called with null arguments; otherwise, passes + * along the return value of srcu_notifier_chain_unregister(). + */ +int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb) +{ + struct clk_notifier *cn = NULL; + int ret = -EINVAL; + + if (!clk || !nb) + return -EINVAL; + + mutex_lock(&prepare_lock); + + list_for_each_entry(cn, &clk_notifier_list, node) + if (cn->clk == clk) + break; + + if (cn->clk == clk) { + ret = srcu_notifier_chain_unregister(&cn->notifier_head, nb); + + clk->notifier_count--; + + /* XXX the notifier code should handle this better */ + if (!cn->notifier_head.head) { + srcu_cleanup_notifier_head(&cn->notifier_head); + kfree(cn); + } + + } else { + ret = -ENOENT; + } + + mutex_unlock(&prepare_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_notifier_unregister); diff --git a/drivers/clocksource/tcb_clksrc.c b/drivers/clocksource/tcb_clksrc.c index 55d0f95f82f9..32cb929b8eb6 100644 --- a/drivers/clocksource/tcb_clksrc.c +++ b/drivers/clocksource/tcb_clksrc.c @@ -19,6 +19,8 @@ * - Two channels combine to create a free-running 32 bit counter * with a base rate of 5+ MHz, packaged as a clocksource (with * resolution better than 200 nsec). + * - Some chips support 32 bit counter. A single channel is used for + * this 32 bit free-running counter. the second channel is not used. * * - The third channel may be used to provide a 16-bit clockevent * source, used in either periodic or oneshot mode. This runs @@ -54,6 +56,11 @@ static cycle_t tc_get_cycles(struct clocksource *cs) return (upper << 16) | lower; } +static cycle_t tc_get_cycles32(struct clocksource *cs) +{ + return __raw_readl(tcaddr + ATMEL_TC_REG(0, CV)); +} + static struct clocksource clksrc = { .name = "tcb_clksrc", .rating = 200, @@ -209,6 +216,48 @@ static void __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) #endif +static void __init tcb_setup_dual_chan(struct atmel_tc *tc, int mck_divisor_idx) +{ + /* channel 0: waveform mode, input mclk/8, clock TIOA0 on overflow */ + __raw_writel(mck_divisor_idx /* likely divide-by-8 */ + | ATMEL_TC_WAVE + | ATMEL_TC_WAVESEL_UP /* free-run */ + | ATMEL_TC_ACPA_SET /* TIOA0 rises at 0 */ + | ATMEL_TC_ACPC_CLEAR, /* (duty cycle 50%) */ + tcaddr + ATMEL_TC_REG(0, CMR)); + __raw_writel(0x0000, tcaddr + ATMEL_TC_REG(0, RA)); + __raw_writel(0x8000, tcaddr + ATMEL_TC_REG(0, RC)); + __raw_writel(0xff, tcaddr + ATMEL_TC_REG(0, IDR)); /* no irqs */ + __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(0, CCR)); + + /* channel 1: waveform mode, input TIOA0 */ + __raw_writel(ATMEL_TC_XC1 /* input: TIOA0 */ + | ATMEL_TC_WAVE + | ATMEL_TC_WAVESEL_UP, /* free-run */ + tcaddr + ATMEL_TC_REG(1, CMR)); + __raw_writel(0xff, tcaddr + ATMEL_TC_REG(1, IDR)); /* no irqs */ + __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(1, CCR)); + + /* chain channel 0 to channel 1*/ + __raw_writel(ATMEL_TC_TC1XC1S_TIOA0, tcaddr + ATMEL_TC_BMR); + /* then reset all the timers */ + __raw_writel(ATMEL_TC_SYNC, tcaddr + ATMEL_TC_BCR); +} + +static void __init tcb_setup_single_chan(struct atmel_tc *tc, int mck_divisor_idx) +{ + /* channel 0: waveform mode, input mclk/8 */ + __raw_writel(mck_divisor_idx /* likely divide-by-8 */ + | ATMEL_TC_WAVE + | ATMEL_TC_WAVESEL_UP, /* free-run */ + tcaddr + ATMEL_TC_REG(0, CMR)); + __raw_writel(0xff, tcaddr + ATMEL_TC_REG(0, IDR)); /* no irqs */ + __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(0, CCR)); + + /* then reset all the timers */ + __raw_writel(ATMEL_TC_SYNC, tcaddr + ATMEL_TC_BCR); +} + static int __init tcb_clksrc_init(void) { static char bootinfo[] __initdata @@ -260,34 +309,19 @@ static int __init tcb_clksrc_init(void) divided_rate / 1000000, ((divided_rate + 500000) % 1000000) / 1000); - /* tclib will give us three clocks no matter what the - * underlying platform supports. - */ - clk_enable(tc->clk[1]); - - /* channel 0: waveform mode, input mclk/8, clock TIOA0 on overflow */ - __raw_writel(best_divisor_idx /* likely divide-by-8 */ - | ATMEL_TC_WAVE - | ATMEL_TC_WAVESEL_UP /* free-run */ - | ATMEL_TC_ACPA_SET /* TIOA0 rises at 0 */ - | ATMEL_TC_ACPC_CLEAR, /* (duty cycle 50%) */ - tcaddr + ATMEL_TC_REG(0, CMR)); - __raw_writel(0x0000, tcaddr + ATMEL_TC_REG(0, RA)); - __raw_writel(0x8000, tcaddr + ATMEL_TC_REG(0, RC)); - __raw_writel(0xff, tcaddr + ATMEL_TC_REG(0, IDR)); /* no irqs */ - __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(0, CCR)); - - /* channel 1: waveform mode, input TIOA0 */ - __raw_writel(ATMEL_TC_XC1 /* input: TIOA0 */ - | ATMEL_TC_WAVE - | ATMEL_TC_WAVESEL_UP, /* free-run */ - tcaddr + ATMEL_TC_REG(1, CMR)); - __raw_writel(0xff, tcaddr + ATMEL_TC_REG(1, IDR)); /* no irqs */ - __raw_writel(ATMEL_TC_CLKEN, tcaddr + ATMEL_TC_REG(1, CCR)); - - /* chain channel 0 to channel 1, then reset all the timers */ - __raw_writel(ATMEL_TC_TC1XC1S_TIOA0, tcaddr + ATMEL_TC_BMR); - __raw_writel(ATMEL_TC_SYNC, tcaddr + ATMEL_TC_BCR); + if (tc->tcb_config && tc->tcb_config->counter_width == 32) { + /* use apropriate function to read 32 bit counter */ + clksrc.read = tc_get_cycles32; + /* setup ony channel 0 */ + tcb_setup_single_chan(tc, best_divisor_idx); + } else { + /* tclib will give us three clocks no matter what the + * underlying platform supports. + */ + clk_enable(tc->clk[1]); + /* setup both channel 0 & 1 */ + tcb_setup_dual_chan(tc, best_divisor_idx); + } /* and away we go! */ clocksource_register_hz(&clksrc, divided_rate); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index e0664fed018a..32d790dd8180 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -2,6 +2,33 @@ # ARM CPU Frequency scaling drivers # +config ARM_OMAP2PLUS_CPUFREQ + bool "TI OMAP2+" + default ARCH_OMAP2PLUS + select CPU_FREQ_TABLE + +config ARM_S3C2416_CPUFREQ + bool "S3C2416 CPU Frequency scaling support" + depends on CPU_S3C2416 + help + This adds the CPUFreq driver for the Samsung S3C2416 and + S3C2450 SoC. The S3C2416 supports changing the rate of the + armdiv clock source and also entering a so called dynamic + voltage scaling mode in which it is possible to reduce the + core voltage of the cpu. + + If in doubt, say N. + +config ARM_S3C2416_CPUFREQ_VCORESCALE + bool "Allow voltage scaling for S3C2416 arm core (EXPERIMENTAL)" + depends on ARM_S3C2416_CPUFREQ && REGULATOR && EXPERIMENTAL + help + Enable CPU voltage scaling when entering the dvs mode. + It uses information gathered through existing hardware and + tests but not documented in any datasheet. + + If in doubt, say N. + config ARM_S3C64XX_CPUFREQ bool "Samsung S3C64XX" depends on CPU_S3C6410 @@ -25,6 +52,8 @@ config ARM_EXYNOS_CPUFREQ bool "SAMSUNG EXYNOS SoCs" depends on ARCH_EXYNOS select ARM_EXYNOS4210_CPUFREQ if CPU_EXYNOS4210 + select ARM_EXYNOS4X12_CPUFREQ if (SOC_EXYNOS4212 || SOC_EXYNOS4412) + select ARM_EXYNOS5250_CPUFREQ if SOC_EXYNOS5250 default y help This adds the CPUFreq driver common part for Samsung @@ -34,6 +63,19 @@ config ARM_EXYNOS_CPUFREQ config ARM_EXYNOS4210_CPUFREQ bool "Samsung EXYNOS4210" + depends on ARCH_EXYNOS help This adds the CPUFreq driver for Samsung EXYNOS4210 SoC (S5PV310 or S5PC210). + +config ARM_EXYNOS4X12_CPUFREQ + bool "Samsung EXYNOS4X12" + help + This adds the CPUFreq driver for Samsung EXYNOS4X12 + SoC (EXYNOS4212 or EXYNOS4412). + +config ARM_EXYNOS5250_CPUFREQ + bool "Samsung EXYNOS5250" + help + This adds the CPUFreq driver for Samsung EXYNOS5250 + SoC. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index ac000fa76bbb..9531fc2eda22 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -40,11 +40,14 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o ################################################################################## # ARM SoC drivers obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o +obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o -obj-$(CONFIG_ARCH_OMAP2PLUS) += omap-cpufreq.o +obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o +obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o +obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o ################################################################################## # PowerPC platform drivers diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 622013fb7890..7f2f149ae40f 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -126,6 +126,15 @@ static int __init init_cpufreq_transition_notifier_list(void) } pure_initcall(init_cpufreq_transition_notifier_list); +static int off __read_mostly; +int cpufreq_disabled(void) +{ + return off; +} +void disable_cpufreq(void) +{ + off = 1; +} static LIST_HEAD(cpufreq_governor_list); static DEFINE_MUTEX(cpufreq_governor_mutex); @@ -1441,6 +1450,9 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy, { int retval = -EINVAL; + if (cpufreq_disabled()) + return -ENODEV; + pr_debug("target for CPU %u: %u kHz, relation %u\n", policy->cpu, target_freq, relation); if (cpu_online(policy->cpu) && cpufreq_driver->target) @@ -1549,6 +1561,9 @@ int cpufreq_register_governor(struct cpufreq_governor *governor) if (!governor) return -EINVAL; + if (cpufreq_disabled()) + return -ENODEV; + mutex_lock(&cpufreq_governor_mutex); err = -EBUSY; @@ -1572,6 +1587,9 @@ void cpufreq_unregister_governor(struct cpufreq_governor *governor) if (!governor) return; + if (cpufreq_disabled()) + return; + #ifdef CONFIG_HOTPLUG_CPU for_each_present_cpu(cpu) { if (cpu_online(cpu)) @@ -1814,6 +1832,9 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data) unsigned long flags; int ret; + if (cpufreq_disabled()) + return -ENODEV; + if (!driver_data || !driver_data->verify || !driver_data->init || ((!driver_data->setpolicy) && (!driver_data->target))) return -EINVAL; @@ -1901,6 +1922,9 @@ static int __init cpufreq_core_init(void) { int cpu; + if (cpufreq_disabled()) + return -ENODEV; + for_each_possible_cpu(cpu) { per_cpu(cpufreq_policy_cpu, cpu) = -1; init_rwsem(&per_cpu(cpu_policy_rwsem, cpu)); diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c index c3e0652520a1..836e9b062e5e 100644 --- a/drivers/cpufreq/cpufreq_ondemand.c +++ b/drivers/cpufreq/cpufreq_ondemand.c @@ -257,6 +257,62 @@ show_one(sampling_down_factor, sampling_down_factor); show_one(ignore_nice_load, ignore_nice); show_one(powersave_bias, powersave_bias); +/** + * update_sampling_rate - update sampling rate effective immediately if needed. + * @new_rate: new sampling rate + * + * If new rate is smaller than the old, simply updaing + * dbs_tuners_int.sampling_rate might not be appropriate. For example, + * if the original sampling_rate was 1 second and the requested new sampling + * rate is 10 ms because the user needs immediate reaction from ondemand + * governor, but not sure if higher frequency will be required or not, + * then, the governor may change the sampling rate too late; up to 1 second + * later. Thus, if we are reducing the sampling rate, we need to make the + * new value effective immediately. + */ +static void update_sampling_rate(unsigned int new_rate) +{ + int cpu; + + dbs_tuners_ins.sampling_rate = new_rate + = max(new_rate, min_sampling_rate); + + for_each_online_cpu(cpu) { + struct cpufreq_policy *policy; + struct cpu_dbs_info_s *dbs_info; + unsigned long next_sampling, appointed_at; + + policy = cpufreq_cpu_get(cpu); + if (!policy) + continue; + dbs_info = &per_cpu(od_cpu_dbs_info, policy->cpu); + cpufreq_cpu_put(policy); + + mutex_lock(&dbs_info->timer_mutex); + + if (!delayed_work_pending(&dbs_info->work)) { + mutex_unlock(&dbs_info->timer_mutex); + continue; + } + + next_sampling = jiffies + usecs_to_jiffies(new_rate); + appointed_at = dbs_info->work.timer.expires; + + + if (time_before(next_sampling, appointed_at)) { + + mutex_unlock(&dbs_info->timer_mutex); + cancel_delayed_work_sync(&dbs_info->work); + mutex_lock(&dbs_info->timer_mutex); + + schedule_delayed_work_on(dbs_info->cpu, &dbs_info->work, + usecs_to_jiffies(new_rate)); + + } + mutex_unlock(&dbs_info->timer_mutex); + } +} + static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b, const char *buf, size_t count) { @@ -265,7 +321,7 @@ static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b, ret = sscanf(buf, "%u", &input); if (ret != 1) return -EINVAL; - dbs_tuners_ins.sampling_rate = max(input, min_sampling_rate); + update_sampling_rate(input); return count; } diff --git a/drivers/cpufreq/exynos-cpufreq.c b/drivers/cpufreq/exynos-cpufreq.c index 5467879ea07d..b243a7ee01f6 100644 --- a/drivers/cpufreq/exynos-cpufreq.c +++ b/drivers/cpufreq/exynos-cpufreq.c @@ -210,6 +210,8 @@ static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy) cpufreq_frequency_table_get_attr(exynos_info->freq_table, policy->cpu); + locking_frequency = exynos_getspeed(0); + /* set the transition latency value */ policy->cpuinfo.transition_latency = 100000; @@ -252,6 +254,10 @@ static int __init exynos_cpufreq_init(void) if (soc_is_exynos4210()) ret = exynos4210_cpufreq_init(exynos_info); + else if (soc_is_exynos4212() || soc_is_exynos4412()) + ret = exynos4x12_cpufreq_init(exynos_info); + else if (soc_is_exynos5250()) + ret = exynos5250_cpufreq_init(exynos_info); else pr_err("%s: CPU type not found\n", __func__); diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index 065da5b702f1..fb148fa27678 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -121,25 +121,25 @@ static void exynos4210_set_clkdiv(unsigned int div_index) tmp = exynos4210_clkdiv_table[div_index].clkdiv; - __raw_writel(tmp, S5P_CLKDIV_CPU); + __raw_writel(tmp, EXYNOS4_CLKDIV_CPU); do { - tmp = __raw_readl(S5P_CLKDIV_STATCPU); + tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU); } while (tmp & 0x1111111); /* Change Divider - CPU1 */ - tmp = __raw_readl(S5P_CLKDIV_CPU1); + tmp = __raw_readl(EXYNOS4_CLKDIV_CPU1); tmp &= ~((0x7 << 4) | 0x7); tmp |= ((clkdiv_cpu1[div_index][0] << 4) | (clkdiv_cpu1[div_index][1] << 0)); - __raw_writel(tmp, S5P_CLKDIV_CPU1); + __raw_writel(tmp, EXYNOS4_CLKDIV_CPU1); do { - tmp = __raw_readl(S5P_CLKDIV_STATCPU1); + tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU1); } while (tmp & 0x11); } @@ -151,32 +151,32 @@ static void exynos4210_set_apll(unsigned int index) clk_set_parent(moutcore, mout_mpll); do { - tmp = (__raw_readl(S5P_CLKMUX_STATCPU) - >> S5P_CLKSRC_CPU_MUXCORE_SHIFT); + tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU) + >> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT); tmp &= 0x7; } while (tmp != 0x2); /* 2. Set APLL Lock time */ - __raw_writel(S5P_APLL_LOCKTIME, S5P_APLL_LOCK); + __raw_writel(EXYNOS4_APLL_LOCKTIME, EXYNOS4_APLL_LOCK); /* 3. Change PLL PMS values */ - tmp = __raw_readl(S5P_APLL_CON0); + tmp = __raw_readl(EXYNOS4_APLL_CON0); tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0)); tmp |= exynos4210_apll_pms_table[index]; - __raw_writel(tmp, S5P_APLL_CON0); + __raw_writel(tmp, EXYNOS4_APLL_CON0); /* 4. wait_lock_time */ do { - tmp = __raw_readl(S5P_APLL_CON0); - } while (!(tmp & (0x1 << S5P_APLLCON0_LOCKED_SHIFT))); + tmp = __raw_readl(EXYNOS4_APLL_CON0); + } while (!(tmp & (0x1 << EXYNOS4_APLLCON0_LOCKED_SHIFT))); /* 5. MUX_CORE_SEL = APLL */ clk_set_parent(moutcore, mout_apll); do { - tmp = __raw_readl(S5P_CLKMUX_STATCPU); - tmp &= S5P_CLKMUX_STATCPU_MUXCORE_MASK; - } while (tmp != (0x1 << S5P_CLKSRC_CPU_MUXCORE_SHIFT)); + tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU); + tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK; + } while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT)); } bool exynos4210_pms_change(unsigned int old_index, unsigned int new_index) @@ -198,10 +198,10 @@ static void exynos4210_set_frequency(unsigned int old_index, exynos4210_set_clkdiv(new_index); /* 2. Change just s value in apll m,p,s value */ - tmp = __raw_readl(S5P_APLL_CON0); + tmp = __raw_readl(EXYNOS4_APLL_CON0); tmp &= ~(0x7 << 0); tmp |= (exynos4210_apll_pms_table[new_index] & 0x7); - __raw_writel(tmp, S5P_APLL_CON0); + __raw_writel(tmp, EXYNOS4_APLL_CON0); } else { /* Clock Configuration Procedure */ /* 1. Change the system clock divider values */ @@ -212,10 +212,10 @@ static void exynos4210_set_frequency(unsigned int old_index, } else if (old_index < new_index) { if (!exynos4210_pms_change(old_index, new_index)) { /* 1. Change just s value in apll m,p,s value */ - tmp = __raw_readl(S5P_APLL_CON0); + tmp = __raw_readl(EXYNOS4_APLL_CON0); tmp &= ~(0x7 << 0); tmp |= (exynos4210_apll_pms_table[new_index] & 0x7); - __raw_writel(tmp, S5P_APLL_CON0); + __raw_writel(tmp, EXYNOS4_APLL_CON0); /* 2. Change the system clock divider values */ exynos4210_set_clkdiv(new_index); @@ -253,24 +253,24 @@ int exynos4210_cpufreq_init(struct exynos_dvfs_info *info) if (IS_ERR(mout_apll)) goto err_mout_apll; - tmp = __raw_readl(S5P_CLKDIV_CPU); + tmp = __raw_readl(EXYNOS4_CLKDIV_CPU); for (i = L0; i < CPUFREQ_LEVEL_END; i++) { - tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK | - S5P_CLKDIV_CPU0_COREM0_MASK | - S5P_CLKDIV_CPU0_COREM1_MASK | - S5P_CLKDIV_CPU0_PERIPH_MASK | - S5P_CLKDIV_CPU0_ATB_MASK | - S5P_CLKDIV_CPU0_PCLKDBG_MASK | - S5P_CLKDIV_CPU0_APLL_MASK); - - tmp |= ((clkdiv_cpu0[i][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) | - (clkdiv_cpu0[i][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) | - (clkdiv_cpu0[i][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) | - (clkdiv_cpu0[i][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) | - (clkdiv_cpu0[i][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) | - (clkdiv_cpu0[i][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) | - (clkdiv_cpu0[i][6] << S5P_CLKDIV_CPU0_APLL_SHIFT)); + tmp &= ~(EXYNOS4_CLKDIV_CPU0_CORE_MASK | + EXYNOS4_CLKDIV_CPU0_COREM0_MASK | + EXYNOS4_CLKDIV_CPU0_COREM1_MASK | + EXYNOS4_CLKDIV_CPU0_PERIPH_MASK | + EXYNOS4_CLKDIV_CPU0_ATB_MASK | + EXYNOS4_CLKDIV_CPU0_PCLKDBG_MASK | + EXYNOS4_CLKDIV_CPU0_APLL_MASK); + + tmp |= ((clkdiv_cpu0[i][0] << EXYNOS4_CLKDIV_CPU0_CORE_SHIFT) | + (clkdiv_cpu0[i][1] << EXYNOS4_CLKDIV_CPU0_COREM0_SHIFT) | + (clkdiv_cpu0[i][2] << EXYNOS4_CLKDIV_CPU0_COREM1_SHIFT) | + (clkdiv_cpu0[i][3] << EXYNOS4_CLKDIV_CPU0_PERIPH_SHIFT) | + (clkdiv_cpu0[i][4] << EXYNOS4_CLKDIV_CPU0_ATB_SHIFT) | + (clkdiv_cpu0[i][5] << EXYNOS4_CLKDIV_CPU0_PCLKDBG_SHIFT) | + (clkdiv_cpu0[i][6] << EXYNOS4_CLKDIV_CPU0_APLL_SHIFT)); exynos4210_clkdiv_table[i].clkdiv = tmp; } diff --git a/drivers/cpufreq/exynos4x12-cpufreq.c b/drivers/cpufreq/exynos4x12-cpufreq.c new file mode 100644 index 000000000000..8c5a7afa5b0b --- /dev/null +++ b/drivers/cpufreq/exynos4x12-cpufreq.c @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2010-2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS4X12 - CPU frequency scaling support + * + * 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/kernel.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/cpufreq.h> + +#include <mach/regs-clock.h> +#include <mach/cpufreq.h> + +#define CPUFREQ_LEVEL_END (L13 + 1) + +static int max_support_idx; +static int min_support_idx = (CPUFREQ_LEVEL_END - 1); + +static struct clk *cpu_clk; +static struct clk *moutcore; +static struct clk *mout_mpll; +static struct clk *mout_apll; + +struct cpufreq_clkdiv { + unsigned int index; + unsigned int clkdiv; + unsigned int clkdiv1; +}; + +static unsigned int exynos4x12_volt_table[CPUFREQ_LEVEL_END]; + +static struct cpufreq_frequency_table exynos4x12_freq_table[] = { + {L0, 1500 * 1000}, + {L1, 1400 * 1000}, + {L2, 1300 * 1000}, + {L3, 1200 * 1000}, + {L4, 1100 * 1000}, + {L5, 1000 * 1000}, + {L6, 900 * 1000}, + {L7, 800 * 1000}, + {L8, 700 * 1000}, + {L9, 600 * 1000}, + {L10, 500 * 1000}, + {L11, 400 * 1000}, + {L12, 300 * 1000}, + {L13, 200 * 1000}, + {0, CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_clkdiv exynos4x12_clkdiv_table[CPUFREQ_LEVEL_END]; + +static unsigned int clkdiv_cpu0_4212[CPUFREQ_LEVEL_END][8] = { + /* + * Clock divider value for following + * { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH, + * DIVATB, DIVPCLK_DBG, DIVAPLL, DIVCORE2 } + */ + /* ARM L0: 1500 MHz */ + { 0, 3, 7, 0, 6, 1, 2, 0 }, + + /* ARM L1: 1400 MHz */ + { 0, 3, 7, 0, 6, 1, 2, 0 }, + + /* ARM L2: 1300 MHz */ + { 0, 3, 7, 0, 5, 1, 2, 0 }, + + /* ARM L3: 1200 MHz */ + { 0, 3, 7, 0, 5, 1, 2, 0 }, + + /* ARM L4: 1100 MHz */ + { 0, 3, 6, 0, 4, 1, 2, 0 }, + + /* ARM L5: 1000 MHz */ + { 0, 2, 5, 0, 4, 1, 1, 0 }, + + /* ARM L6: 900 MHz */ + { 0, 2, 5, 0, 3, 1, 1, 0 }, + + /* ARM L7: 800 MHz */ + { 0, 2, 5, 0, 3, 1, 1, 0 }, + + /* ARM L8: 700 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L9: 600 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L10: 500 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L11: 400 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L12: 300 MHz */ + { 0, 2, 4, 0, 2, 1, 1, 0 }, + + /* ARM L13: 200 MHz */ + { 0, 1, 3, 0, 1, 1, 1, 0 }, +}; + +static unsigned int clkdiv_cpu0_4412[CPUFREQ_LEVEL_END][8] = { + /* + * Clock divider value for following + * { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH, + * DIVATB, DIVPCLK_DBG, DIVAPLL, DIVCORE2 } + */ + /* ARM L0: 1500 MHz */ + { 0, 3, 7, 0, 6, 1, 2, 0 }, + + /* ARM L1: 1400 MHz */ + { 0, 3, 7, 0, 6, 1, 2, 0 }, + + /* ARM L2: 1300 MHz */ + { 0, 3, 7, 0, 5, 1, 2, 0 }, + + /* ARM L3: 1200 MHz */ + { 0, 3, 7, 0, 5, 1, 2, 0 }, + + /* ARM L4: 1100 MHz */ + { 0, 3, 6, 0, 4, 1, 2, 0 }, + + /* ARM L5: 1000 MHz */ + { 0, 2, 5, 0, 4, 1, 1, 0 }, + + /* ARM L6: 900 MHz */ + { 0, 2, 5, 0, 3, 1, 1, 0 }, + + /* ARM L7: 800 MHz */ + { 0, 2, 5, 0, 3, 1, 1, 0 }, + + /* ARM L8: 700 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L9: 600 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L10: 500 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L11: 400 MHz */ + { 0, 2, 4, 0, 3, 1, 1, 0 }, + + /* ARM L12: 300 MHz */ + { 0, 2, 4, 0, 2, 1, 1, 0 }, + + /* ARM L13: 200 MHz */ + { 0, 1, 3, 0, 1, 1, 1, 0 }, +}; + +static unsigned int clkdiv_cpu1_4212[CPUFREQ_LEVEL_END][2] = { + /* Clock divider value for following + * { DIVCOPY, DIVHPM } + */ + /* ARM L0: 1500 MHz */ + { 6, 0 }, + + /* ARM L1: 1400 MHz */ + { 6, 0 }, + + /* ARM L2: 1300 MHz */ + { 5, 0 }, + + /* ARM L3: 1200 MHz */ + { 5, 0 }, + + /* ARM L4: 1100 MHz */ + { 4, 0 }, + + /* ARM L5: 1000 MHz */ + { 4, 0 }, + + /* ARM L6: 900 MHz */ + { 3, 0 }, + + /* ARM L7: 800 MHz */ + { 3, 0 }, + + /* ARM L8: 700 MHz */ + { 3, 0 }, + + /* ARM L9: 600 MHz */ + { 3, 0 }, + + /* ARM L10: 500 MHz */ + { 3, 0 }, + + /* ARM L11: 400 MHz */ + { 3, 0 }, + + /* ARM L12: 300 MHz */ + { 3, 0 }, + + /* ARM L13: 200 MHz */ + { 3, 0 }, +}; + +static unsigned int clkdiv_cpu1_4412[CPUFREQ_LEVEL_END][3] = { + /* Clock divider value for following + * { DIVCOPY, DIVHPM, DIVCORES } + */ + /* ARM L0: 1500 MHz */ + { 6, 0, 7 }, + + /* ARM L1: 1400 MHz */ + { 6, 0, 6 }, + + /* ARM L2: 1300 MHz */ + { 5, 0, 6 }, + + /* ARM L3: 1200 MHz */ + { 5, 0, 5 }, + + /* ARM L4: 1100 MHz */ + { 4, 0, 5 }, + + /* ARM L5: 1000 MHz */ + { 4, 0, 4 }, + + /* ARM L6: 900 MHz */ + { 3, 0, 4 }, + + /* ARM L7: 800 MHz */ + { 3, 0, 3 }, + + /* ARM L8: 700 MHz */ + { 3, 0, 3 }, + + /* ARM L9: 600 MHz */ + { 3, 0, 2 }, + + /* ARM L10: 500 MHz */ + { 3, 0, 2 }, + + /* ARM L11: 400 MHz */ + { 3, 0, 1 }, + + /* ARM L12: 300 MHz */ + { 3, 0, 1 }, + + /* ARM L13: 200 MHz */ + { 3, 0, 0 }, +}; + +static unsigned int exynos4x12_apll_pms_table[CPUFREQ_LEVEL_END] = { + /* APLL FOUT L0: 1500 MHz */ + ((250 << 16) | (4 << 8) | (0x0)), + + /* APLL FOUT L1: 1400 MHz */ + ((175 << 16) | (3 << 8) | (0x0)), + + /* APLL FOUT L2: 1300 MHz */ + ((325 << 16) | (6 << 8) | (0x0)), + + /* APLL FOUT L3: 1200 MHz */ + ((200 << 16) | (4 << 8) | (0x0)), + + /* APLL FOUT L4: 1100 MHz */ + ((275 << 16) | (6 << 8) | (0x0)), + + /* APLL FOUT L5: 1000 MHz */ + ((125 << 16) | (3 << 8) | (0x0)), + + /* APLL FOUT L6: 900 MHz */ + ((150 << 16) | (4 << 8) | (0x0)), + + /* APLL FOUT L7: 800 MHz */ + ((100 << 16) | (3 << 8) | (0x0)), + + /* APLL FOUT L8: 700 MHz */ + ((175 << 16) | (3 << 8) | (0x1)), + + /* APLL FOUT L9: 600 MHz */ + ((200 << 16) | (4 << 8) | (0x1)), + + /* APLL FOUT L10: 500 MHz */ + ((125 << 16) | (3 << 8) | (0x1)), + + /* APLL FOUT L11 400 MHz */ + ((100 << 16) | (3 << 8) | (0x1)), + + /* APLL FOUT L12: 300 MHz */ + ((200 << 16) | (4 << 8) | (0x2)), + + /* APLL FOUT L13: 200 MHz */ + ((100 << 16) | (3 << 8) | (0x2)), +}; + +static const unsigned int asv_voltage_4x12[CPUFREQ_LEVEL_END] = { + 1350000, 1287500, 1250000, 1187500, 1137500, 1087500, 1037500, + 1000000, 987500, 975000, 950000, 925000, 900000, 900000 +}; + +static void exynos4x12_set_clkdiv(unsigned int div_index) +{ + unsigned int tmp; + unsigned int stat_cpu1; + + /* Change Divider - CPU0 */ + + tmp = exynos4x12_clkdiv_table[div_index].clkdiv; + + __raw_writel(tmp, EXYNOS4_CLKDIV_CPU); + + while (__raw_readl(EXYNOS4_CLKDIV_STATCPU) & 0x11111111) + cpu_relax(); + + /* Change Divider - CPU1 */ + tmp = exynos4x12_clkdiv_table[div_index].clkdiv1; + + __raw_writel(tmp, EXYNOS4_CLKDIV_CPU1); + if (soc_is_exynos4212()) + stat_cpu1 = 0x11; + else + stat_cpu1 = 0x111; + + while (__raw_readl(EXYNOS4_CLKDIV_STATCPU1) & stat_cpu1) + cpu_relax(); +} + +static void exynos4x12_set_apll(unsigned int index) +{ + unsigned int tmp, pdiv; + + /* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */ + clk_set_parent(moutcore, mout_mpll); + + do { + cpu_relax(); + tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU) + >> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT); + tmp &= 0x7; + } while (tmp != 0x2); + + /* 2. Set APLL Lock time */ + pdiv = ((exynos4x12_apll_pms_table[index] >> 8) & 0x3f); + + __raw_writel((pdiv * 250), EXYNOS4_APLL_LOCK); + + /* 3. Change PLL PMS values */ + tmp = __raw_readl(EXYNOS4_APLL_CON0); + tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0)); + tmp |= exynos4x12_apll_pms_table[index]; + __raw_writel(tmp, EXYNOS4_APLL_CON0); + + /* 4. wait_lock_time */ + do { + cpu_relax(); + tmp = __raw_readl(EXYNOS4_APLL_CON0); + } while (!(tmp & (0x1 << EXYNOS4_APLLCON0_LOCKED_SHIFT))); + + /* 5. MUX_CORE_SEL = APLL */ + clk_set_parent(moutcore, mout_apll); + + do { + cpu_relax(); + tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU); + tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK; + } while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT)); +} + +bool exynos4x12_pms_change(unsigned int old_index, unsigned int new_index) +{ + unsigned int old_pm = exynos4x12_apll_pms_table[old_index] >> 8; + unsigned int new_pm = exynos4x12_apll_pms_table[new_index] >> 8; + + return (old_pm == new_pm) ? 0 : 1; +} + +static void exynos4x12_set_frequency(unsigned int old_index, + unsigned int new_index) +{ + unsigned int tmp; + + if (old_index > new_index) { + if (!exynos4x12_pms_change(old_index, new_index)) { + /* 1. Change the system clock divider values */ + exynos4x12_set_clkdiv(new_index); + /* 2. Change just s value in apll m,p,s value */ + tmp = __raw_readl(EXYNOS4_APLL_CON0); + tmp &= ~(0x7 << 0); + tmp |= (exynos4x12_apll_pms_table[new_index] & 0x7); + __raw_writel(tmp, EXYNOS4_APLL_CON0); + + } else { + /* Clock Configuration Procedure */ + /* 1. Change the system clock divider values */ + exynos4x12_set_clkdiv(new_index); + /* 2. Change the apll m,p,s value */ + exynos4x12_set_apll(new_index); + } + } else if (old_index < new_index) { + if (!exynos4x12_pms_change(old_index, new_index)) { + /* 1. Change just s value in apll m,p,s value */ + tmp = __raw_readl(EXYNOS4_APLL_CON0); + tmp &= ~(0x7 << 0); + tmp |= (exynos4x12_apll_pms_table[new_index] & 0x7); + __raw_writel(tmp, EXYNOS4_APLL_CON0); + /* 2. Change the system clock divider values */ + exynos4x12_set_clkdiv(new_index); + } else { + /* Clock Configuration Procedure */ + /* 1. Change the apll m,p,s value */ + exynos4x12_set_apll(new_index); + /* 2. Change the system clock divider values */ + exynos4x12_set_clkdiv(new_index); + } + } +} + +static void __init set_volt_table(void) +{ + unsigned int i; + + max_support_idx = L1; + + /* Not supported */ + exynos4x12_freq_table[L0].frequency = CPUFREQ_ENTRY_INVALID; + + for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++) + exynos4x12_volt_table[i] = asv_voltage_4x12[i]; +} + +int exynos4x12_cpufreq_init(struct exynos_dvfs_info *info) +{ + int i; + unsigned int tmp; + unsigned long rate; + + set_volt_table(); + + cpu_clk = clk_get(NULL, "armclk"); + if (IS_ERR(cpu_clk)) + return PTR_ERR(cpu_clk); + + moutcore = clk_get(NULL, "moutcore"); + if (IS_ERR(moutcore)) + goto err_moutcore; + + mout_mpll = clk_get(NULL, "mout_mpll"); + if (IS_ERR(mout_mpll)) + goto err_mout_mpll; + + rate = clk_get_rate(mout_mpll) / 1000; + + mout_apll = clk_get(NULL, "mout_apll"); + if (IS_ERR(mout_apll)) + goto err_mout_apll; + + for (i = L0; i < CPUFREQ_LEVEL_END; i++) { + + exynos4x12_clkdiv_table[i].index = i; + + tmp = __raw_readl(EXYNOS4_CLKDIV_CPU); + + tmp &= ~(EXYNOS4_CLKDIV_CPU0_CORE_MASK | + EXYNOS4_CLKDIV_CPU0_COREM0_MASK | + EXYNOS4_CLKDIV_CPU0_COREM1_MASK | + EXYNOS4_CLKDIV_CPU0_PERIPH_MASK | + EXYNOS4_CLKDIV_CPU0_ATB_MASK | + EXYNOS4_CLKDIV_CPU0_PCLKDBG_MASK | + EXYNOS4_CLKDIV_CPU0_APLL_MASK); + + if (soc_is_exynos4212()) { + tmp |= ((clkdiv_cpu0_4212[i][0] << EXYNOS4_CLKDIV_CPU0_CORE_SHIFT) | + (clkdiv_cpu0_4212[i][1] << EXYNOS4_CLKDIV_CPU0_COREM0_SHIFT) | + (clkdiv_cpu0_4212[i][2] << EXYNOS4_CLKDIV_CPU0_COREM1_SHIFT) | + (clkdiv_cpu0_4212[i][3] << EXYNOS4_CLKDIV_CPU0_PERIPH_SHIFT) | + (clkdiv_cpu0_4212[i][4] << EXYNOS4_CLKDIV_CPU0_ATB_SHIFT) | + (clkdiv_cpu0_4212[i][5] << EXYNOS4_CLKDIV_CPU0_PCLKDBG_SHIFT) | + (clkdiv_cpu0_4212[i][6] << EXYNOS4_CLKDIV_CPU0_APLL_SHIFT)); + } else { + tmp &= ~EXYNOS4_CLKDIV_CPU0_CORE2_MASK; + + tmp |= ((clkdiv_cpu0_4412[i][0] << EXYNOS4_CLKDIV_CPU0_CORE_SHIFT) | + (clkdiv_cpu0_4412[i][1] << EXYNOS4_CLKDIV_CPU0_COREM0_SHIFT) | + (clkdiv_cpu0_4412[i][2] << EXYNOS4_CLKDIV_CPU0_COREM1_SHIFT) | + (clkdiv_cpu0_4412[i][3] << EXYNOS4_CLKDIV_CPU0_PERIPH_SHIFT) | + (clkdiv_cpu0_4412[i][4] << EXYNOS4_CLKDIV_CPU0_ATB_SHIFT) | + (clkdiv_cpu0_4412[i][5] << EXYNOS4_CLKDIV_CPU0_PCLKDBG_SHIFT) | + (clkdiv_cpu0_4412[i][6] << EXYNOS4_CLKDIV_CPU0_APLL_SHIFT) | + (clkdiv_cpu0_4412[i][7] << EXYNOS4_CLKDIV_CPU0_CORE2_SHIFT)); + } + + exynos4x12_clkdiv_table[i].clkdiv = tmp; + + tmp = __raw_readl(EXYNOS4_CLKDIV_CPU1); + + if (soc_is_exynos4212()) { + tmp &= ~(EXYNOS4_CLKDIV_CPU1_COPY_MASK | + EXYNOS4_CLKDIV_CPU1_HPM_MASK); + tmp |= ((clkdiv_cpu1_4212[i][0] << EXYNOS4_CLKDIV_CPU1_COPY_SHIFT) | + (clkdiv_cpu1_4212[i][1] << EXYNOS4_CLKDIV_CPU1_HPM_SHIFT)); + } else { + tmp &= ~(EXYNOS4_CLKDIV_CPU1_COPY_MASK | + EXYNOS4_CLKDIV_CPU1_HPM_MASK | + EXYNOS4_CLKDIV_CPU1_CORES_MASK); + tmp |= ((clkdiv_cpu1_4412[i][0] << EXYNOS4_CLKDIV_CPU1_COPY_SHIFT) | + (clkdiv_cpu1_4412[i][1] << EXYNOS4_CLKDIV_CPU1_HPM_SHIFT) | + (clkdiv_cpu1_4412[i][2] << EXYNOS4_CLKDIV_CPU1_CORES_SHIFT)); + } + exynos4x12_clkdiv_table[i].clkdiv1 = tmp; + } + + info->mpll_freq_khz = rate; + info->pm_lock_idx = L5; + info->pll_safe_idx = L7; + info->max_support_idx = max_support_idx; + info->min_support_idx = min_support_idx; + info->cpu_clk = cpu_clk; + info->volt_table = exynos4x12_volt_table; + info->freq_table = exynos4x12_freq_table; + info->set_freq = exynos4x12_set_frequency; + info->need_apll_change = exynos4x12_pms_change; + + return 0; + +err_mout_apll: + clk_put(mout_mpll); +err_mout_mpll: + clk_put(moutcore); +err_moutcore: + clk_put(cpu_clk); + + pr_debug("%s: failed initialization\n", __func__); + return -EINVAL; +} +EXPORT_SYMBOL(exynos4x12_cpufreq_init); diff --git a/drivers/cpufreq/exynos5250-cpufreq.c b/drivers/cpufreq/exynos5250-cpufreq.c new file mode 100644 index 000000000000..a88331644ebf --- /dev/null +++ b/drivers/cpufreq/exynos5250-cpufreq.c @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2010-20122Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS5250 - CPU frequency scaling support + * + * 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/kernel.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/cpufreq.h> + +#include <mach/map.h> +#include <mach/regs-clock.h> +#include <mach/cpufreq.h> + +#define CPUFREQ_LEVEL_END (L15 + 1) + +static int max_support_idx; +static int min_support_idx = (CPUFREQ_LEVEL_END - 1); +static struct clk *cpu_clk; +static struct clk *moutcore; +static struct clk *mout_mpll; +static struct clk *mout_apll; + +struct cpufreq_clkdiv { + unsigned int index; + unsigned int clkdiv; + unsigned int clkdiv1; +}; + +static unsigned int exynos5250_volt_table[CPUFREQ_LEVEL_END]; + +static struct cpufreq_frequency_table exynos5250_freq_table[] = { + {L0, 1700 * 1000}, + {L1, 1600 * 1000}, + {L2, 1500 * 1000}, + {L3, 1400 * 1000}, + {L4, 1300 * 1000}, + {L5, 1200 * 1000}, + {L6, 1100 * 1000}, + {L7, 1000 * 1000}, + {L8, 900 * 1000}, + {L9, 800 * 1000}, + {L10, 700 * 1000}, + {L11, 600 * 1000}, + {L12, 500 * 1000}, + {L13, 400 * 1000}, + {L14, 300 * 1000}, + {L15, 200 * 1000}, + {0, CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_clkdiv exynos5250_clkdiv_table[CPUFREQ_LEVEL_END]; + +static unsigned int clkdiv_cpu0_5250[CPUFREQ_LEVEL_END][8] = { + /* + * Clock divider value for following + * { ARM, CPUD, ACP, PERIPH, ATB, PCLK_DBG, APLL, ARM2 } + */ + { 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1700 MHz - N/A */ + { 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1600 MHz - N/A */ + { 0, 3, 7, 7, 5, 1, 3, 0 }, /* 1500 MHz - N/A */ + { 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1400 MHz */ + { 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1300 MHz */ + { 0, 3, 7, 7, 5, 1, 3, 0 }, /* 1200 MHz */ + { 0, 2, 7, 7, 5, 1, 2, 0 }, /* 1100 MHz */ + { 0, 2, 7, 7, 4, 1, 2, 0 }, /* 1000 MHz */ + { 0, 2, 7, 7, 4, 1, 2, 0 }, /* 900 MHz */ + { 0, 2, 7, 7, 3, 1, 1, 0 }, /* 800 MHz */ + { 0, 1, 7, 7, 3, 1, 1, 0 }, /* 700 MHz */ + { 0, 1, 7, 7, 2, 1, 1, 0 }, /* 600 MHz */ + { 0, 1, 7, 7, 2, 1, 1, 0 }, /* 500 MHz */ + { 0, 1, 7, 7, 1, 1, 1, 0 }, /* 400 MHz */ + { 0, 1, 7, 7, 1, 1, 1, 0 }, /* 300 MHz */ + { 0, 1, 7, 7, 1, 1, 1, 0 }, /* 200 MHz */ +}; + +static unsigned int clkdiv_cpu1_5250[CPUFREQ_LEVEL_END][2] = { + /* Clock divider value for following + * { COPY, HPM } + */ + { 0, 2 }, /* 1700 MHz - N/A */ + { 0, 2 }, /* 1600 MHz - N/A */ + { 0, 2 }, /* 1500 MHz - N/A */ + { 0, 2 }, /* 1400 MHz */ + { 0, 2 }, /* 1300 MHz */ + { 0, 2 }, /* 1200 MHz */ + { 0, 2 }, /* 1100 MHz */ + { 0, 2 }, /* 1000 MHz */ + { 0, 2 }, /* 900 MHz */ + { 0, 2 }, /* 800 MHz */ + { 0, 2 }, /* 700 MHz */ + { 0, 2 }, /* 600 MHz */ + { 0, 2 }, /* 500 MHz */ + { 0, 2 }, /* 400 MHz */ + { 0, 2 }, /* 300 MHz */ + { 0, 2 }, /* 200 MHz */ +}; + +static unsigned int exynos5_apll_pms_table[CPUFREQ_LEVEL_END] = { + (0), /* 1700 MHz - N/A */ + (0), /* 1600 MHz - N/A */ + (0), /* 1500 MHz - N/A */ + (0), /* 1400 MHz */ + ((325 << 16) | (6 << 8) | 0), /* 1300 MHz */ + ((200 << 16) | (4 << 8) | 0), /* 1200 MHz */ + ((275 << 16) | (6 << 8) | 0), /* 1100 MHz */ + ((125 << 16) | (3 << 8) | 0), /* 1000 MHz */ + ((150 << 16) | (4 << 8) | 0), /* 900 MHz */ + ((100 << 16) | (3 << 8) | 0), /* 800 MHz */ + ((175 << 16) | (3 << 8) | 1), /* 700 MHz */ + ((200 << 16) | (4 << 8) | 1), /* 600 MHz */ + ((125 << 16) | (3 << 8) | 1), /* 500 MHz */ + ((100 << 16) | (3 << 8) | 1), /* 400 MHz */ + ((200 << 16) | (4 << 8) | 2), /* 300 MHz */ + ((100 << 16) | (3 << 8) | 2), /* 200 MHz */ +}; + +/* ASV group voltage table */ +static const unsigned int asv_voltage_5250[CPUFREQ_LEVEL_END] = { + 0, 0, 0, 0, 0, 0, 0, /* 1700 MHz ~ 1100 MHz Not supported */ + 1175000, 1125000, 1075000, 1050000, 1000000, + 950000, 925000, 925000, 900000 +}; + +static void set_clkdiv(unsigned int div_index) +{ + unsigned int tmp; + + /* Change Divider - CPU0 */ + + tmp = exynos5250_clkdiv_table[div_index].clkdiv; + + __raw_writel(tmp, EXYNOS5_CLKDIV_CPU0); + + while (__raw_readl(EXYNOS5_CLKDIV_STATCPU0) & 0x11111111) + cpu_relax(); + + /* Change Divider - CPU1 */ + tmp = exynos5250_clkdiv_table[div_index].clkdiv1; + + __raw_writel(tmp, EXYNOS5_CLKDIV_CPU1); + + while (__raw_readl(EXYNOS5_CLKDIV_STATCPU1) & 0x11) + cpu_relax(); +} + +static void set_apll(unsigned int new_index, + unsigned int old_index) +{ + unsigned int tmp, pdiv; + + /* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */ + clk_set_parent(moutcore, mout_mpll); + + do { + cpu_relax(); + tmp = (__raw_readl(EXYNOS5_CLKMUX_STATCPU) >> 16); + tmp &= 0x7; + } while (tmp != 0x2); + + /* 2. Set APLL Lock time */ + pdiv = ((exynos5_apll_pms_table[new_index] >> 8) & 0x3f); + + __raw_writel((pdiv * 250), EXYNOS5_APLL_LOCK); + + /* 3. Change PLL PMS values */ + tmp = __raw_readl(EXYNOS5_APLL_CON0); + tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0)); + tmp |= exynos5_apll_pms_table[new_index]; + __raw_writel(tmp, EXYNOS5_APLL_CON0); + + /* 4. wait_lock_time */ + do { + cpu_relax(); + tmp = __raw_readl(EXYNOS5_APLL_CON0); + } while (!(tmp & (0x1 << 29))); + + /* 5. MUX_CORE_SEL = APLL */ + clk_set_parent(moutcore, mout_apll); + + do { + cpu_relax(); + tmp = __raw_readl(EXYNOS5_CLKMUX_STATCPU); + tmp &= (0x7 << 16); + } while (tmp != (0x1 << 16)); + +} + +bool exynos5250_pms_change(unsigned int old_index, unsigned int new_index) +{ + unsigned int old_pm = (exynos5_apll_pms_table[old_index] >> 8); + unsigned int new_pm = (exynos5_apll_pms_table[new_index] >> 8); + + return (old_pm == new_pm) ? 0 : 1; +} + +static void exynos5250_set_frequency(unsigned int old_index, + unsigned int new_index) +{ + unsigned int tmp; + + if (old_index > new_index) { + if (!exynos5250_pms_change(old_index, new_index)) { + /* 1. Change the system clock divider values */ + set_clkdiv(new_index); + /* 2. Change just s value in apll m,p,s value */ + tmp = __raw_readl(EXYNOS5_APLL_CON0); + tmp &= ~(0x7 << 0); + tmp |= (exynos5_apll_pms_table[new_index] & 0x7); + __raw_writel(tmp, EXYNOS5_APLL_CON0); + + } else { + /* Clock Configuration Procedure */ + /* 1. Change the system clock divider values */ + set_clkdiv(new_index); + /* 2. Change the apll m,p,s value */ + set_apll(new_index, old_index); + } + } else if (old_index < new_index) { + if (!exynos5250_pms_change(old_index, new_index)) { + /* 1. Change just s value in apll m,p,s value */ + tmp = __raw_readl(EXYNOS5_APLL_CON0); + tmp &= ~(0x7 << 0); + tmp |= (exynos5_apll_pms_table[new_index] & 0x7); + __raw_writel(tmp, EXYNOS5_APLL_CON0); + /* 2. Change the system clock divider values */ + set_clkdiv(new_index); + } else { + /* Clock Configuration Procedure */ + /* 1. Change the apll m,p,s value */ + set_apll(new_index, old_index); + /* 2. Change the system clock divider values */ + set_clkdiv(new_index); + } + } +} + +static void __init set_volt_table(void) +{ + unsigned int i; + + exynos5250_freq_table[L0].frequency = CPUFREQ_ENTRY_INVALID; + exynos5250_freq_table[L1].frequency = CPUFREQ_ENTRY_INVALID; + exynos5250_freq_table[L2].frequency = CPUFREQ_ENTRY_INVALID; + exynos5250_freq_table[L3].frequency = CPUFREQ_ENTRY_INVALID; + exynos5250_freq_table[L4].frequency = CPUFREQ_ENTRY_INVALID; + exynos5250_freq_table[L5].frequency = CPUFREQ_ENTRY_INVALID; + exynos5250_freq_table[L6].frequency = CPUFREQ_ENTRY_INVALID; + + max_support_idx = L7; + + for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++) + exynos5250_volt_table[i] = asv_voltage_5250[i]; +} + +int exynos5250_cpufreq_init(struct exynos_dvfs_info *info) +{ + int i; + unsigned int tmp; + unsigned long rate; + + set_volt_table(); + + cpu_clk = clk_get(NULL, "armclk"); + if (IS_ERR(cpu_clk)) + return PTR_ERR(cpu_clk); + + moutcore = clk_get(NULL, "mout_cpu"); + if (IS_ERR(moutcore)) + goto err_moutcore; + + mout_mpll = clk_get(NULL, "mout_mpll"); + if (IS_ERR(mout_mpll)) + goto err_mout_mpll; + + rate = clk_get_rate(mout_mpll) / 1000; + + mout_apll = clk_get(NULL, "mout_apll"); + if (IS_ERR(mout_apll)) + goto err_mout_apll; + + for (i = L0; i < CPUFREQ_LEVEL_END; i++) { + + exynos5250_clkdiv_table[i].index = i; + + tmp = __raw_readl(EXYNOS5_CLKDIV_CPU0); + + tmp &= ~((0x7 << 0) | (0x7 << 4) | (0x7 << 8) | + (0x7 << 12) | (0x7 << 16) | (0x7 << 20) | + (0x7 << 24) | (0x7 << 28)); + + tmp |= ((clkdiv_cpu0_5250[i][0] << 0) | + (clkdiv_cpu0_5250[i][1] << 4) | + (clkdiv_cpu0_5250[i][2] << 8) | + (clkdiv_cpu0_5250[i][3] << 12) | + (clkdiv_cpu0_5250[i][4] << 16) | + (clkdiv_cpu0_5250[i][5] << 20) | + (clkdiv_cpu0_5250[i][6] << 24) | + (clkdiv_cpu0_5250[i][7] << 28)); + + exynos5250_clkdiv_table[i].clkdiv = tmp; + + tmp = __raw_readl(EXYNOS5_CLKDIV_CPU1); + + tmp &= ~((0x7 << 0) | (0x7 << 4)); + + tmp |= ((clkdiv_cpu1_5250[i][0] << 0) | + (clkdiv_cpu1_5250[i][1] << 4)); + + exynos5250_clkdiv_table[i].clkdiv1 = tmp; + } + + info->mpll_freq_khz = rate; + /* 1000Mhz */ + info->pm_lock_idx = L7; + /* 800Mhz */ + info->pll_safe_idx = L9; + info->max_support_idx = max_support_idx; + info->min_support_idx = min_support_idx; + info->cpu_clk = cpu_clk; + info->volt_table = exynos5250_volt_table; + info->freq_table = exynos5250_freq_table; + info->set_freq = exynos5250_set_frequency; + info->need_apll_change = exynos5250_pms_change; + + return 0; + +err_mout_apll: + clk_put(mout_mpll); +err_mout_mpll: + clk_put(moutcore); +err_moutcore: + clk_put(cpu_clk); + + pr_err("%s: failed initialization\n", __func__); + return -EINVAL; +} +EXPORT_SYMBOL(exynos5250_cpufreq_init); diff --git a/drivers/cpufreq/omap-cpufreq.c b/drivers/cpufreq/omap-cpufreq.c index 5d04c57aae30..67bbb06d0460 100644 --- a/drivers/cpufreq/omap-cpufreq.c +++ b/drivers/cpufreq/omap-cpufreq.c @@ -25,6 +25,7 @@ #include <linux/opp.h> #include <linux/cpu.h> #include <linux/module.h> +#include <linux/regulator/consumer.h> #include <asm/system.h> #include <asm/smp_plat.h> @@ -37,6 +38,9 @@ #include <mach/hardware.h> +/* OPP tolerance in percentage */ +#define OPP_TOLERANCE 4 + #ifdef CONFIG_SMP struct lpj_info { unsigned long ref; @@ -52,6 +56,7 @@ static atomic_t freq_table_users = ATOMIC_INIT(0); static struct clk *mpu_clk; static char *mpu_clk_name; static struct device *mpu_dev; +static struct regulator *mpu_reg; static int omap_verify_speed(struct cpufreq_policy *policy) { @@ -76,8 +81,10 @@ static int omap_target(struct cpufreq_policy *policy, unsigned int relation) { unsigned int i; - int ret = 0; + int r, ret = 0; struct cpufreq_freqs freqs; + struct opp *opp; + unsigned long freq, volt = 0, volt_old = 0, tol = 0; if (!freq_table) { dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__, @@ -111,13 +118,50 @@ static int omap_target(struct cpufreq_policy *policy, cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); } -#ifdef CONFIG_CPU_FREQ_DEBUG - pr_info("cpufreq-omap: transition: %u --> %u\n", freqs.old, freqs.new); -#endif + freq = freqs.new * 1000; + + if (mpu_reg) { + opp = opp_find_freq_ceil(mpu_dev, &freq); + if (IS_ERR(opp)) { + dev_err(mpu_dev, "%s: unable to find MPU OPP for %d\n", + __func__, freqs.new); + return -EINVAL; + } + volt = opp_get_voltage(opp); + tol = volt * OPP_TOLERANCE / 100; + volt_old = regulator_get_voltage(mpu_reg); + } + + dev_dbg(mpu_dev, "cpufreq-omap: %u MHz, %ld mV --> %u MHz, %ld mV\n", + freqs.old / 1000, volt_old ? volt_old / 1000 : -1, + freqs.new / 1000, volt ? volt / 1000 : -1); + + /* scaling up? scale voltage before frequency */ + if (mpu_reg && (freqs.new > freqs.old)) { + r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); + if (r < 0) { + dev_warn(mpu_dev, "%s: unable to scale voltage up.\n", + __func__); + freqs.new = freqs.old; + goto done; + } + } ret = clk_set_rate(mpu_clk, freqs.new * 1000); - freqs.new = omap_getspeed(policy->cpu); + /* scaling down? scale voltage after frequency */ + if (mpu_reg && (freqs.new < freqs.old)) { + r = regulator_set_voltage(mpu_reg, volt - tol, volt + tol); + if (r < 0) { + dev_warn(mpu_dev, "%s: unable to scale voltage down.\n", + __func__); + ret = clk_set_rate(mpu_clk, freqs.old * 1000); + freqs.new = freqs.old; + goto done; + } + } + + freqs.new = omap_getspeed(policy->cpu); #ifdef CONFIG_SMP /* * Note that loops_per_jiffy is not updated on SMP systems in @@ -144,6 +188,7 @@ static int omap_target(struct cpufreq_policy *policy, freqs.new); #endif +done: /* notifiers */ for_each_cpu(i, policy->cpus) { freqs.cpu = i; @@ -260,6 +305,23 @@ static int __init omap_cpufreq_init(void) return -EINVAL; } + mpu_reg = regulator_get(mpu_dev, "vcc"); + if (IS_ERR(mpu_reg)) { + pr_warning("%s: unable to get MPU regulator\n", __func__); + mpu_reg = NULL; + } else { + /* + * Ensure physical regulator is present. + * (e.g. could be dummy regulator.) + */ + if (regulator_get_voltage(mpu_reg) < 0) { + pr_warn("%s: physical regulator not present for MPU\n", + __func__); + regulator_put(mpu_reg); + mpu_reg = NULL; + } + } + return cpufreq_register_driver(&omap_driver); } diff --git a/drivers/cpufreq/s3c2416-cpufreq.c b/drivers/cpufreq/s3c2416-cpufreq.c new file mode 100644 index 000000000000..50d2f15a3c8a --- /dev/null +++ b/drivers/cpufreq/s3c2416-cpufreq.c @@ -0,0 +1,542 @@ +/* + * S3C2416/2450 CPUfreq Support + * + * Copyright 2011 Heiko Stuebner <heiko@sntech.de> + * + * based on s3c64xx_cpufreq.c + * + * Copyright 2009 Wolfson Microelectronics plc + * + * 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/types.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/regulator/consumer.h> +#include <linux/reboot.h> +#include <linux/module.h> + +static DEFINE_MUTEX(cpufreq_lock); + +struct s3c2416_data { + struct clk *armdiv; + struct clk *armclk; + struct clk *hclk; + + unsigned long regulator_latency; +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE + struct regulator *vddarm; +#endif + + struct cpufreq_frequency_table *freq_table; + + bool is_dvs; + bool disable_dvs; +}; + +static struct s3c2416_data s3c2416_cpufreq; + +struct s3c2416_dvfs { + unsigned int vddarm_min; + unsigned int vddarm_max; +}; + +/* pseudo-frequency for dvs mode */ +#define FREQ_DVS 132333 + +/* frequency to sleep and reboot in + * it's essential to leave dvs, as some boards do not reconfigure the + * regulator on reboot + */ +#define FREQ_SLEEP 133333 + +/* Sources for the ARMCLK */ +#define SOURCE_HCLK 0 +#define SOURCE_ARMDIV 1 + +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE +/* S3C2416 only supports changing the voltage in the dvs-mode. + * Voltages down to 1.0V seem to work, so we take what the regulator + * can get us. + */ +static struct s3c2416_dvfs s3c2416_dvfs_table[] = { + [SOURCE_HCLK] = { 950000, 1250000 }, + [SOURCE_ARMDIV] = { 1250000, 1350000 }, +}; +#endif + +static struct cpufreq_frequency_table s3c2416_freq_table[] = { + { SOURCE_HCLK, FREQ_DVS }, + { SOURCE_ARMDIV, 133333 }, + { SOURCE_ARMDIV, 266666 }, + { SOURCE_ARMDIV, 400000 }, + { 0, CPUFREQ_TABLE_END }, +}; + +static struct cpufreq_frequency_table s3c2450_freq_table[] = { + { SOURCE_HCLK, FREQ_DVS }, + { SOURCE_ARMDIV, 133500 }, + { SOURCE_ARMDIV, 267000 }, + { SOURCE_ARMDIV, 534000 }, + { 0, CPUFREQ_TABLE_END }, +}; + +static int s3c2416_cpufreq_verify_speed(struct cpufreq_policy *policy) +{ + struct s3c2416_data *s3c_freq = &s3c2416_cpufreq; + + if (policy->cpu != 0) + return -EINVAL; + + return cpufreq_frequency_table_verify(policy, s3c_freq->freq_table); +} + +static unsigned int s3c2416_cpufreq_get_speed(unsigned int cpu) +{ + struct s3c2416_data *s3c_freq = &s3c2416_cpufreq; + + if (cpu != 0) + return 0; + + /* return our pseudo-frequency when in dvs mode */ + if (s3c_freq->is_dvs) + return FREQ_DVS; + + return clk_get_rate(s3c_freq->armclk) / 1000; +} + +static int s3c2416_cpufreq_set_armdiv(struct s3c2416_data *s3c_freq, + unsigned int freq) +{ + int ret; + + if (clk_get_rate(s3c_freq->armdiv) / 1000 != freq) { + ret = clk_set_rate(s3c_freq->armdiv, freq * 1000); + if (ret < 0) { + pr_err("cpufreq: Failed to set armdiv rate %dkHz: %d\n", + freq, ret); + return ret; + } + } + + return 0; +} + +static int s3c2416_cpufreq_enter_dvs(struct s3c2416_data *s3c_freq, int idx) +{ +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE + struct s3c2416_dvfs *dvfs; +#endif + int ret; + + if (s3c_freq->is_dvs) { + pr_debug("cpufreq: already in dvs mode, nothing to do\n"); + return 0; + } + + pr_debug("cpufreq: switching armclk to hclk (%lukHz)\n", + clk_get_rate(s3c_freq->hclk) / 1000); + ret = clk_set_parent(s3c_freq->armclk, s3c_freq->hclk); + if (ret < 0) { + pr_err("cpufreq: Failed to switch armclk to hclk: %d\n", ret); + return ret; + } + +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE + /* changing the core voltage is only allowed when in dvs mode */ + if (s3c_freq->vddarm) { + dvfs = &s3c2416_dvfs_table[idx]; + + pr_debug("cpufreq: setting regultor to %d-%d\n", + dvfs->vddarm_min, dvfs->vddarm_max); + ret = regulator_set_voltage(s3c_freq->vddarm, + dvfs->vddarm_min, + dvfs->vddarm_max); + + /* when lowering the voltage failed, there is nothing to do */ + if (ret != 0) + pr_err("cpufreq: Failed to set VDDARM: %d\n", ret); + } +#endif + + s3c_freq->is_dvs = 1; + + return 0; +} + +static int s3c2416_cpufreq_leave_dvs(struct s3c2416_data *s3c_freq, int idx) +{ +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE + struct s3c2416_dvfs *dvfs; +#endif + int ret; + + if (!s3c_freq->is_dvs) { + pr_debug("cpufreq: not in dvs mode, so can't leave\n"); + return 0; + } + +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE + if (s3c_freq->vddarm) { + dvfs = &s3c2416_dvfs_table[idx]; + + pr_debug("cpufreq: setting regultor to %d-%d\n", + dvfs->vddarm_min, dvfs->vddarm_max); + ret = regulator_set_voltage(s3c_freq->vddarm, + dvfs->vddarm_min, + dvfs->vddarm_max); + if (ret != 0) { + pr_err("cpufreq: Failed to set VDDARM: %d\n", ret); + return ret; + } + } +#endif + + /* force armdiv to hclk frequency for transition from dvs*/ + if (clk_get_rate(s3c_freq->armdiv) > clk_get_rate(s3c_freq->hclk)) { + pr_debug("cpufreq: force armdiv to hclk frequency (%lukHz)\n", + clk_get_rate(s3c_freq->hclk) / 1000); + ret = s3c2416_cpufreq_set_armdiv(s3c_freq, + clk_get_rate(s3c_freq->hclk) / 1000); + if (ret < 0) { + pr_err("cpufreq: Failed to to set the armdiv to %lukHz: %d\n", + clk_get_rate(s3c_freq->hclk) / 1000, ret); + return ret; + } + } + + pr_debug("cpufreq: switching armclk parent to armdiv (%lukHz)\n", + clk_get_rate(s3c_freq->armdiv) / 1000); + + ret = clk_set_parent(s3c_freq->armclk, s3c_freq->armdiv); + if (ret < 0) { + pr_err("cpufreq: Failed to switch armclk clock parent to armdiv: %d\n", + ret); + return ret; + } + + s3c_freq->is_dvs = 0; + + return 0; +} + +static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct s3c2416_data *s3c_freq = &s3c2416_cpufreq; + struct cpufreq_freqs freqs; + int idx, ret, to_dvs = 0; + unsigned int i; + + mutex_lock(&cpufreq_lock); + + pr_debug("cpufreq: to %dKHz, relation %d\n", target_freq, relation); + + ret = cpufreq_frequency_table_target(policy, s3c_freq->freq_table, + target_freq, relation, &i); + if (ret != 0) + goto out; + + idx = s3c_freq->freq_table[i].index; + + if (idx == SOURCE_HCLK) + to_dvs = 1; + + /* switching to dvs when it's not allowed */ + if (to_dvs && s3c_freq->disable_dvs) { + pr_debug("cpufreq: entering dvs mode not allowed\n"); + ret = -EINVAL; + goto out; + } + + freqs.cpu = 0; + freqs.flags = 0; + freqs.old = s3c_freq->is_dvs ? FREQ_DVS + : clk_get_rate(s3c_freq->armclk) / 1000; + + /* When leavin dvs mode, always switch the armdiv to the hclk rate + * The S3C2416 has stability issues when switching directly to + * higher frequencies. + */ + freqs.new = (s3c_freq->is_dvs && !to_dvs) + ? clk_get_rate(s3c_freq->hclk) / 1000 + : s3c_freq->freq_table[i].frequency; + + pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new); + + if (!to_dvs && freqs.old == freqs.new) + goto out; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + if (to_dvs) { + pr_debug("cpufreq: enter dvs\n"); + ret = s3c2416_cpufreq_enter_dvs(s3c_freq, idx); + } else if (s3c_freq->is_dvs) { + pr_debug("cpufreq: leave dvs\n"); + ret = s3c2416_cpufreq_leave_dvs(s3c_freq, idx); + } else { + pr_debug("cpufreq: change armdiv to %dkHz\n", freqs.new); + ret = s3c2416_cpufreq_set_armdiv(s3c_freq, freqs.new); + } + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + +out: + mutex_unlock(&cpufreq_lock); + + return ret; +} + +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE +static void __init s3c2416_cpufreq_cfg_regulator(struct s3c2416_data *s3c_freq) +{ + int count, v, i, found; + struct cpufreq_frequency_table *freq; + struct s3c2416_dvfs *dvfs; + + count = regulator_count_voltages(s3c_freq->vddarm); + if (count < 0) { + pr_err("cpufreq: Unable to check supported voltages\n"); + return; + } + + freq = s3c_freq->freq_table; + while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) { + if (freq->frequency == CPUFREQ_ENTRY_INVALID) + continue; + + dvfs = &s3c2416_dvfs_table[freq->index]; + found = 0; + + /* Check only the min-voltage, more is always ok on S3C2416 */ + for (i = 0; i < count; i++) { + v = regulator_list_voltage(s3c_freq->vddarm, i); + if (v >= dvfs->vddarm_min) + found = 1; + } + + if (!found) { + pr_debug("cpufreq: %dkHz unsupported by regulator\n", + freq->frequency); + freq->frequency = CPUFREQ_ENTRY_INVALID; + } + + freq++; + } + + /* Guessed */ + s3c_freq->regulator_latency = 1 * 1000 * 1000; +} +#endif + +static int s3c2416_cpufreq_reboot_notifier_evt(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct s3c2416_data *s3c_freq = &s3c2416_cpufreq; + int ret; + + mutex_lock(&cpufreq_lock); + + /* disable further changes */ + s3c_freq->disable_dvs = 1; + + mutex_unlock(&cpufreq_lock); + + /* some boards don't reconfigure the regulator on reboot, which + * could lead to undervolting the cpu when the clock is reset. + * Therefore we always leave the DVS mode on reboot. + */ + if (s3c_freq->is_dvs) { + pr_debug("cpufreq: leave dvs on reboot\n"); + ret = cpufreq_driver_target(cpufreq_cpu_get(0), FREQ_SLEEP, 0); + if (ret < 0) + return NOTIFY_BAD; + } + + return NOTIFY_DONE; +} + +static struct notifier_block s3c2416_cpufreq_reboot_notifier = { + .notifier_call = s3c2416_cpufreq_reboot_notifier_evt, +}; + +static int __init s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy) +{ + struct s3c2416_data *s3c_freq = &s3c2416_cpufreq; + struct cpufreq_frequency_table *freq; + struct clk *msysclk; + unsigned long rate; + int ret; + + if (policy->cpu != 0) + return -EINVAL; + + msysclk = clk_get(NULL, "msysclk"); + if (IS_ERR(msysclk)) { + ret = PTR_ERR(msysclk); + pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret); + return ret; + } + + /* + * S3C2416 and S3C2450 share the same processor-ID and also provide no + * other means to distinguish them other than through the rate of + * msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz. + */ + rate = clk_get_rate(msysclk); + if (rate == 800 * 1000 * 1000) { + pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n", + rate / 1000); + s3c_freq->freq_table = s3c2416_freq_table; + policy->cpuinfo.max_freq = 400000; + } else if (rate / 1000 == 534000) { + pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n", + rate / 1000); + s3c_freq->freq_table = s3c2450_freq_table; + policy->cpuinfo.max_freq = 534000; + } + + /* not needed anymore */ + clk_put(msysclk); + + if (s3c_freq->freq_table == NULL) { + pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n", + rate / 1000); + return -ENODEV; + } + + s3c_freq->is_dvs = 0; + + s3c_freq->armdiv = clk_get(NULL, "armdiv"); + if (IS_ERR(s3c_freq->armdiv)) { + ret = PTR_ERR(s3c_freq->armdiv); + pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret); + return ret; + } + + s3c_freq->hclk = clk_get(NULL, "hclk"); + if (IS_ERR(s3c_freq->hclk)) { + ret = PTR_ERR(s3c_freq->hclk); + pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret); + goto err_hclk; + } + + /* chech hclk rate, we only support the common 133MHz for now + * hclk could also run at 66MHz, but this not often used + */ + rate = clk_get_rate(s3c_freq->hclk); + if (rate < 133 * 1000 * 1000) { + pr_err("cpufreq: HCLK not at 133MHz\n"); + clk_put(s3c_freq->hclk); + ret = -EINVAL; + goto err_armclk; + } + + s3c_freq->armclk = clk_get(NULL, "armclk"); + if (IS_ERR(s3c_freq->armclk)) { + ret = PTR_ERR(s3c_freq->armclk); + pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret); + goto err_armclk; + } + +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE + s3c_freq->vddarm = regulator_get(NULL, "vddarm"); + if (IS_ERR(s3c_freq->vddarm)) { + ret = PTR_ERR(s3c_freq->vddarm); + pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret); + goto err_vddarm; + } + + s3c2416_cpufreq_cfg_regulator(s3c_freq); +#else + s3c_freq->regulator_latency = 0; +#endif + + freq = s3c_freq->freq_table; + while (freq->frequency != CPUFREQ_TABLE_END) { + /* special handling for dvs mode */ + if (freq->index == 0) { + if (!s3c_freq->hclk) { + pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n", + freq->frequency); + freq->frequency = CPUFREQ_ENTRY_INVALID; + } else { + freq++; + continue; + } + } + + /* Check for frequencies we can generate */ + rate = clk_round_rate(s3c_freq->armdiv, + freq->frequency * 1000); + rate /= 1000; + if (rate != freq->frequency) { + pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n", + freq->frequency, rate); + freq->frequency = CPUFREQ_ENTRY_INVALID; + } + + freq++; + } + + policy->cur = clk_get_rate(s3c_freq->armclk) / 1000; + + /* Datasheet says PLL stabalisation time must be at least 300us, + * so but add some fudge. (reference in LOCKCON0 register description) + */ + policy->cpuinfo.transition_latency = (500 * 1000) + + s3c_freq->regulator_latency; + + ret = cpufreq_frequency_table_cpuinfo(policy, s3c_freq->freq_table); + if (ret) + goto err_freq_table; + + cpufreq_frequency_table_get_attr(s3c_freq->freq_table, 0); + + register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier); + + return 0; + +err_freq_table: +#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE + regulator_put(s3c_freq->vddarm); +err_vddarm: +#endif + clk_put(s3c_freq->armclk); +err_armclk: + clk_put(s3c_freq->hclk); +err_hclk: + clk_put(s3c_freq->armdiv); + + return ret; +} + +static struct freq_attr *s3c2416_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver s3c2416_cpufreq_driver = { + .owner = THIS_MODULE, + .flags = 0, + .verify = s3c2416_cpufreq_verify_speed, + .target = s3c2416_cpufreq_set_target, + .get = s3c2416_cpufreq_get_speed, + .init = s3c2416_cpufreq_driver_init, + .name = "s3c2416", + .attr = s3c2416_cpufreq_attr, +}; + +static int __init s3c2416_cpufreq_init(void) +{ + return cpufreq_register_driver(&s3c2416_cpufreq_driver); +} +module_init(s3c2416_cpufreq_init); diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c index a5e72cb5f53c..6f9490b3c356 100644 --- a/drivers/cpufreq/s3c64xx-cpufreq.c +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -217,13 +217,6 @@ static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) } else { s3c64xx_cpufreq_config_regulator(); } - - vddint = regulator_get(NULL, "vddint"); - if (IS_ERR(vddint)) { - ret = PTR_ERR(vddint); - pr_err("Failed to obtain VDDINT: %d\n", ret); - vddint = NULL; - } #endif freq = s3c64xx_freq_table; diff --git a/drivers/devfreq/exynos4_bus.c b/drivers/devfreq/exynos4_bus.c index 1a361e99965a..88ddc77a9bb1 100644 --- a/drivers/devfreq/exynos4_bus.c +++ b/drivers/devfreq/exynos4_bus.c @@ -311,51 +311,51 @@ static int exynos4210_set_busclk(struct busfreq_data *data, struct opp *opp) /* Change Divider - DMC0 */ tmp = data->dmc_divtable[index]; - __raw_writel(tmp, S5P_CLKDIV_DMC0); + __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_DMC0); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0); } while (tmp & 0x11111111); /* Change Divider - TOP */ tmp = data->top_divtable[index]; - __raw_writel(tmp, S5P_CLKDIV_TOP); + __raw_writel(tmp, EXYNOS4_CLKDIV_TOP); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_TOP); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP); } while (tmp & 0x11111); /* Change Divider - LEFTBUS */ - tmp = __raw_readl(S5P_CLKDIV_LEFTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS); - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); tmp |= ((exynos4210_clkdiv_lr_bus[index][0] << - S5P_CLKDIV_BUS_GDLR_SHIFT) | + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | (exynos4210_clkdiv_lr_bus[index][1] << - S5P_CLKDIV_BUS_GPLR_SHIFT)); + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_LEFTBUS); + __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_LEFTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS); } while (tmp & 0x11); /* Change Divider - RIGHTBUS */ - tmp = __raw_readl(S5P_CLKDIV_RIGHTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS); - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); tmp |= ((exynos4210_clkdiv_lr_bus[index][0] << - S5P_CLKDIV_BUS_GDLR_SHIFT) | + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | (exynos4210_clkdiv_lr_bus[index][1] << - S5P_CLKDIV_BUS_GPLR_SHIFT)); + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_RIGHTBUS); + __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_RIGHTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS); } while (tmp & 0x11); return 0; @@ -376,137 +376,137 @@ static int exynos4x12_set_busclk(struct busfreq_data *data, struct opp *opp) /* Change Divider - DMC0 */ tmp = data->dmc_divtable[index]; - __raw_writel(tmp, S5P_CLKDIV_DMC0); + __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_DMC0); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0); } while (tmp & 0x11111111); /* Change Divider - DMC1 */ - tmp = __raw_readl(S5P_CLKDIV_DMC1); + tmp = __raw_readl(EXYNOS4_CLKDIV_DMC1); - tmp &= ~(S5P_CLKDIV_DMC1_G2D_ACP_MASK | - S5P_CLKDIV_DMC1_C2C_MASK | - S5P_CLKDIV_DMC1_C2CACLK_MASK); + tmp &= ~(EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK | + EXYNOS4_CLKDIV_DMC1_C2C_MASK | + EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK); tmp |= ((exynos4x12_clkdiv_dmc1[index][0] << - S5P_CLKDIV_DMC1_G2D_ACP_SHIFT) | + EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT) | (exynos4x12_clkdiv_dmc1[index][1] << - S5P_CLKDIV_DMC1_C2C_SHIFT) | + EXYNOS4_CLKDIV_DMC1_C2C_SHIFT) | (exynos4x12_clkdiv_dmc1[index][2] << - S5P_CLKDIV_DMC1_C2CACLK_SHIFT)); + EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_DMC1); + __raw_writel(tmp, EXYNOS4_CLKDIV_DMC1); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_DMC1); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC1); } while (tmp & 0x111111); /* Change Divider - TOP */ - tmp = __raw_readl(S5P_CLKDIV_TOP); + tmp = __raw_readl(EXYNOS4_CLKDIV_TOP); - tmp &= ~(S5P_CLKDIV_TOP_ACLK266_GPS_MASK | - S5P_CLKDIV_TOP_ACLK100_MASK | - S5P_CLKDIV_TOP_ACLK160_MASK | - S5P_CLKDIV_TOP_ACLK133_MASK | - S5P_CLKDIV_TOP_ONENAND_MASK); + tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK | + EXYNOS4_CLKDIV_TOP_ACLK100_MASK | + EXYNOS4_CLKDIV_TOP_ACLK160_MASK | + EXYNOS4_CLKDIV_TOP_ACLK133_MASK | + EXYNOS4_CLKDIV_TOP_ONENAND_MASK); tmp |= ((exynos4x12_clkdiv_top[index][0] << - S5P_CLKDIV_TOP_ACLK266_GPS_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT) | (exynos4x12_clkdiv_top[index][1] << - S5P_CLKDIV_TOP_ACLK100_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) | (exynos4x12_clkdiv_top[index][2] << - S5P_CLKDIV_TOP_ACLK160_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) | (exynos4x12_clkdiv_top[index][3] << - S5P_CLKDIV_TOP_ACLK133_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) | (exynos4x12_clkdiv_top[index][4] << - S5P_CLKDIV_TOP_ONENAND_SHIFT)); + EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_TOP); + __raw_writel(tmp, EXYNOS4_CLKDIV_TOP); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_TOP); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP); } while (tmp & 0x11111); /* Change Divider - LEFTBUS */ - tmp = __raw_readl(S5P_CLKDIV_LEFTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS); - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] << - S5P_CLKDIV_BUS_GDLR_SHIFT) | + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | (exynos4x12_clkdiv_lr_bus[index][1] << - S5P_CLKDIV_BUS_GPLR_SHIFT)); + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_LEFTBUS); + __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_LEFTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS); } while (tmp & 0x11); /* Change Divider - RIGHTBUS */ - tmp = __raw_readl(S5P_CLKDIV_RIGHTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS); - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); + tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] << - S5P_CLKDIV_BUS_GDLR_SHIFT) | + EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | (exynos4x12_clkdiv_lr_bus[index][1] << - S5P_CLKDIV_BUS_GPLR_SHIFT)); + EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_RIGHTBUS); + __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_RIGHTBUS); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS); } while (tmp & 0x11); /* Change Divider - MFC */ - tmp = __raw_readl(S5P_CLKDIV_MFC); + tmp = __raw_readl(EXYNOS4_CLKDIV_MFC); - tmp &= ~(S5P_CLKDIV_MFC_MASK); + tmp &= ~(EXYNOS4_CLKDIV_MFC_MASK); tmp |= ((exynos4x12_clkdiv_sclkip[index][0] << - S5P_CLKDIV_MFC_SHIFT)); + EXYNOS4_CLKDIV_MFC_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_MFC); + __raw_writel(tmp, EXYNOS4_CLKDIV_MFC); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_MFC); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_MFC); } while (tmp & 0x1); /* Change Divider - JPEG */ - tmp = __raw_readl(S5P_CLKDIV_CAM1); + tmp = __raw_readl(EXYNOS4_CLKDIV_CAM1); - tmp &= ~(S5P_CLKDIV_CAM1_JPEG_MASK); + tmp &= ~(EXYNOS4_CLKDIV_CAM1_JPEG_MASK); tmp |= ((exynos4x12_clkdiv_sclkip[index][1] << - S5P_CLKDIV_CAM1_JPEG_SHIFT)); + EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_CAM1); + __raw_writel(tmp, EXYNOS4_CLKDIV_CAM1); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_CAM1); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1); } while (tmp & 0x1); /* Change Divider - FIMC0~3 */ - tmp = __raw_readl(S5P_CLKDIV_CAM); + tmp = __raw_readl(EXYNOS4_CLKDIV_CAM); - tmp &= ~(S5P_CLKDIV_CAM_FIMC0_MASK | S5P_CLKDIV_CAM_FIMC1_MASK | - S5P_CLKDIV_CAM_FIMC2_MASK | S5P_CLKDIV_CAM_FIMC3_MASK); + tmp &= ~(EXYNOS4_CLKDIV_CAM_FIMC0_MASK | EXYNOS4_CLKDIV_CAM_FIMC1_MASK | + EXYNOS4_CLKDIV_CAM_FIMC2_MASK | EXYNOS4_CLKDIV_CAM_FIMC3_MASK); tmp |= ((exynos4x12_clkdiv_sclkip[index][2] << - S5P_CLKDIV_CAM_FIMC0_SHIFT) | + EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT) | (exynos4x12_clkdiv_sclkip[index][2] << - S5P_CLKDIV_CAM_FIMC1_SHIFT) | + EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT) | (exynos4x12_clkdiv_sclkip[index][2] << - S5P_CLKDIV_CAM_FIMC2_SHIFT) | + EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT) | (exynos4x12_clkdiv_sclkip[index][2] << - S5P_CLKDIV_CAM_FIMC3_SHIFT)); + EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT)); - __raw_writel(tmp, S5P_CLKDIV_CAM); + __raw_writel(tmp, EXYNOS4_CLKDIV_CAM); do { - tmp = __raw_readl(S5P_CLKDIV_STAT_CAM1); + tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1); } while (tmp & 0x1111); return 0; @@ -760,55 +760,55 @@ static int exynos4210_init_tables(struct busfreq_data *data) int mgrp; int i, err = 0; - tmp = __raw_readl(S5P_CLKDIV_DMC0); + tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0); for (i = LV_0; i < EX4210_LV_NUM; i++) { - tmp &= ~(S5P_CLKDIV_DMC0_ACP_MASK | - S5P_CLKDIV_DMC0_ACPPCLK_MASK | - S5P_CLKDIV_DMC0_DPHY_MASK | - S5P_CLKDIV_DMC0_DMC_MASK | - S5P_CLKDIV_DMC0_DMCD_MASK | - S5P_CLKDIV_DMC0_DMCP_MASK | - S5P_CLKDIV_DMC0_COPY2_MASK | - S5P_CLKDIV_DMC0_CORETI_MASK); + tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK | + EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK | + EXYNOS4_CLKDIV_DMC0_DPHY_MASK | + EXYNOS4_CLKDIV_DMC0_DMC_MASK | + EXYNOS4_CLKDIV_DMC0_DMCD_MASK | + EXYNOS4_CLKDIV_DMC0_DMCP_MASK | + EXYNOS4_CLKDIV_DMC0_COPY2_MASK | + EXYNOS4_CLKDIV_DMC0_CORETI_MASK); tmp |= ((exynos4210_clkdiv_dmc0[i][0] << - S5P_CLKDIV_DMC0_ACP_SHIFT) | + EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) | (exynos4210_clkdiv_dmc0[i][1] << - S5P_CLKDIV_DMC0_ACPPCLK_SHIFT) | + EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) | (exynos4210_clkdiv_dmc0[i][2] << - S5P_CLKDIV_DMC0_DPHY_SHIFT) | + EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) | (exynos4210_clkdiv_dmc0[i][3] << - S5P_CLKDIV_DMC0_DMC_SHIFT) | + EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) | (exynos4210_clkdiv_dmc0[i][4] << - S5P_CLKDIV_DMC0_DMCD_SHIFT) | + EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) | (exynos4210_clkdiv_dmc0[i][5] << - S5P_CLKDIV_DMC0_DMCP_SHIFT) | + EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT) | (exynos4210_clkdiv_dmc0[i][6] << - S5P_CLKDIV_DMC0_COPY2_SHIFT) | + EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT) | (exynos4210_clkdiv_dmc0[i][7] << - S5P_CLKDIV_DMC0_CORETI_SHIFT)); + EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT)); data->dmc_divtable[i] = tmp; } - tmp = __raw_readl(S5P_CLKDIV_TOP); + tmp = __raw_readl(EXYNOS4_CLKDIV_TOP); for (i = LV_0; i < EX4210_LV_NUM; i++) { - tmp &= ~(S5P_CLKDIV_TOP_ACLK200_MASK | - S5P_CLKDIV_TOP_ACLK100_MASK | - S5P_CLKDIV_TOP_ACLK160_MASK | - S5P_CLKDIV_TOP_ACLK133_MASK | - S5P_CLKDIV_TOP_ONENAND_MASK); + tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK200_MASK | + EXYNOS4_CLKDIV_TOP_ACLK100_MASK | + EXYNOS4_CLKDIV_TOP_ACLK160_MASK | + EXYNOS4_CLKDIV_TOP_ACLK133_MASK | + EXYNOS4_CLKDIV_TOP_ONENAND_MASK); tmp |= ((exynos4210_clkdiv_top[i][0] << - S5P_CLKDIV_TOP_ACLK200_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT) | (exynos4210_clkdiv_top[i][1] << - S5P_CLKDIV_TOP_ACLK100_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) | (exynos4210_clkdiv_top[i][2] << - S5P_CLKDIV_TOP_ACLK160_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) | (exynos4210_clkdiv_top[i][3] << - S5P_CLKDIV_TOP_ACLK133_SHIFT) | + EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) | (exynos4210_clkdiv_top[i][4] << - S5P_CLKDIV_TOP_ONENAND_SHIFT)); + EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)); data->top_divtable[i] = tmp; } @@ -868,32 +868,32 @@ static int exynos4x12_init_tables(struct busfreq_data *data) int ret; /* Enable pause function for DREX2 DVFS */ - tmp = __raw_readl(S5P_DMC_PAUSE_CTRL); - tmp |= DMC_PAUSE_ENABLE; - __raw_writel(tmp, S5P_DMC_PAUSE_CTRL); + tmp = __raw_readl(EXYNOS4_DMC_PAUSE_CTRL); + tmp |= EXYNOS4_DMC_PAUSE_ENABLE; + __raw_writel(tmp, EXYNOS4_DMC_PAUSE_CTRL); - tmp = __raw_readl(S5P_CLKDIV_DMC0); + tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0); for (i = 0; i < EX4x12_LV_NUM; i++) { - tmp &= ~(S5P_CLKDIV_DMC0_ACP_MASK | - S5P_CLKDIV_DMC0_ACPPCLK_MASK | - S5P_CLKDIV_DMC0_DPHY_MASK | - S5P_CLKDIV_DMC0_DMC_MASK | - S5P_CLKDIV_DMC0_DMCD_MASK | - S5P_CLKDIV_DMC0_DMCP_MASK); + tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK | + EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK | + EXYNOS4_CLKDIV_DMC0_DPHY_MASK | + EXYNOS4_CLKDIV_DMC0_DMC_MASK | + EXYNOS4_CLKDIV_DMC0_DMCD_MASK | + EXYNOS4_CLKDIV_DMC0_DMCP_MASK); tmp |= ((exynos4x12_clkdiv_dmc0[i][0] << - S5P_CLKDIV_DMC0_ACP_SHIFT) | + EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) | (exynos4x12_clkdiv_dmc0[i][1] << - S5P_CLKDIV_DMC0_ACPPCLK_SHIFT) | + EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) | (exynos4x12_clkdiv_dmc0[i][2] << - S5P_CLKDIV_DMC0_DPHY_SHIFT) | + EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) | (exynos4x12_clkdiv_dmc0[i][3] << - S5P_CLKDIV_DMC0_DMC_SHIFT) | + EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) | (exynos4x12_clkdiv_dmc0[i][4] << - S5P_CLKDIV_DMC0_DMCD_SHIFT) | + EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) | (exynos4x12_clkdiv_dmc0[i][5] << - S5P_CLKDIV_DMC0_DMCP_SHIFT)); + EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT)); data->dmc_divtable[i] = tmp; } diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index f1a274994bb1..4a6c46dea8a0 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -252,6 +252,15 @@ config EP93XX_DMA help Enable support for the Cirrus Logic EP93xx M2P/M2M DMA controller. +config DMA_SA11X0 + tristate "SA-11x0 DMA support" + depends on ARCH_SA1100 + select DMA_ENGINE + help + Support the DMA engine found on Intel StrongARM SA-1100 and + SA-1110 SoCs. This DMA engine can only be used with on-chip + devices. + config DMA_ENGINE bool diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 009a222e8283..86b795baba98 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_PL330_DMA) += pl330.o obj-$(CONFIG_PCH_DMA) += pch_dma.o obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o obj-$(CONFIG_EP93XX_DMA) += ep93xx_dma.o +obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c index e4383ee2c9ac..38586ba8da91 100644 --- a/drivers/dma/imx-dma.c +++ b/drivers/dma/imx-dma.c @@ -14,7 +14,6 @@ * http://www.gnu.org/copyleft/gpl.html */ #include <linux/init.h> -#include <linux/module.h> #include <linux/types.h> #include <linux/mm.h> #include <linux/interrupt.h> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c index 8bc5acf36ee5..63540d3e2153 100644 --- a/drivers/dma/imx-sdma.c +++ b/drivers/dma/imx-sdma.c @@ -35,7 +35,6 @@ #include <linux/dmaengine.h> #include <linux/of.h> #include <linux/of_device.h> -#include <linux/module.h> #include <asm/irq.h> #include <mach/sdma.h> diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c index b8ec03ee8e22..16b66c827f19 100644 --- a/drivers/dma/pl330.c +++ b/drivers/dma/pl330.c @@ -1035,18 +1035,7 @@ static struct amba_driver pl330_driver = { .remove = pl330_remove, }; -static int __init pl330_init(void) -{ - return amba_driver_register(&pl330_driver); -} -module_init(pl330_init); - -static void __exit pl330_exit(void) -{ - amba_driver_unregister(&pl330_driver); - return; -} -module_exit(pl330_exit); +module_amba_driver(pl330_driver); MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>"); MODULE_DESCRIPTION("API Driver for PL330 DMAC"); diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c new file mode 100644 index 000000000000..16a6b48883cf --- /dev/null +++ b/drivers/dma/sa11x0-dma.c @@ -0,0 +1,1109 @@ +/* + * SA11x0 DMAengine support + * + * Copyright (C) 2012 Russell King + * Derived in part from arch/arm/mach-sa1100/dma.c, + * Copyright (C) 2000, 2001 by Nicolas Pitre + * + * 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/sched.h> +#include <linux/device.h> +#include <linux/dmaengine.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sa11x0-dma.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define NR_PHY_CHAN 6 +#define DMA_ALIGN 3 +#define DMA_MAX_SIZE 0x1fff +#define DMA_CHUNK_SIZE 0x1000 + +#define DMA_DDAR 0x00 +#define DMA_DCSR_S 0x04 +#define DMA_DCSR_C 0x08 +#define DMA_DCSR_R 0x0c +#define DMA_DBSA 0x10 +#define DMA_DBTA 0x14 +#define DMA_DBSB 0x18 +#define DMA_DBTB 0x1c +#define DMA_SIZE 0x20 + +#define DCSR_RUN (1 << 0) +#define DCSR_IE (1 << 1) +#define DCSR_ERROR (1 << 2) +#define DCSR_DONEA (1 << 3) +#define DCSR_STRTA (1 << 4) +#define DCSR_DONEB (1 << 5) +#define DCSR_STRTB (1 << 6) +#define DCSR_BIU (1 << 7) + +#define DDAR_RW (1 << 0) /* 0 = W, 1 = R */ +#define DDAR_E (1 << 1) /* 0 = LE, 1 = BE */ +#define DDAR_BS (1 << 2) /* 0 = BS4, 1 = BS8 */ +#define DDAR_DW (1 << 3) /* 0 = 8b, 1 = 16b */ +#define DDAR_Ser0UDCTr (0x0 << 4) +#define DDAR_Ser0UDCRc (0x1 << 4) +#define DDAR_Ser1SDLCTr (0x2 << 4) +#define DDAR_Ser1SDLCRc (0x3 << 4) +#define DDAR_Ser1UARTTr (0x4 << 4) +#define DDAR_Ser1UARTRc (0x5 << 4) +#define DDAR_Ser2ICPTr (0x6 << 4) +#define DDAR_Ser2ICPRc (0x7 << 4) +#define DDAR_Ser3UARTTr (0x8 << 4) +#define DDAR_Ser3UARTRc (0x9 << 4) +#define DDAR_Ser4MCP0Tr (0xa << 4) +#define DDAR_Ser4MCP0Rc (0xb << 4) +#define DDAR_Ser4MCP1Tr (0xc << 4) +#define DDAR_Ser4MCP1Rc (0xd << 4) +#define DDAR_Ser4SSPTr (0xe << 4) +#define DDAR_Ser4SSPRc (0xf << 4) + +struct sa11x0_dma_sg { + u32 addr; + u32 len; +}; + +struct sa11x0_dma_desc { + struct dma_async_tx_descriptor tx; + u32 ddar; + size_t size; + + /* maybe protected by c->lock */ + struct list_head node; + unsigned sglen; + struct sa11x0_dma_sg sg[0]; +}; + +struct sa11x0_dma_phy; + +struct sa11x0_dma_chan { + struct dma_chan chan; + spinlock_t lock; + dma_cookie_t lc; + + /* protected by c->lock */ + struct sa11x0_dma_phy *phy; + enum dma_status status; + struct list_head desc_submitted; + struct list_head desc_issued; + + /* protected by d->lock */ + struct list_head node; + + u32 ddar; + const char *name; +}; + +struct sa11x0_dma_phy { + void __iomem *base; + struct sa11x0_dma_dev *dev; + unsigned num; + + struct sa11x0_dma_chan *vchan; + + /* Protected by c->lock */ + unsigned sg_load; + struct sa11x0_dma_desc *txd_load; + unsigned sg_done; + struct sa11x0_dma_desc *txd_done; +#ifdef CONFIG_PM_SLEEP + u32 dbs[2]; + u32 dbt[2]; + u32 dcsr; +#endif +}; + +struct sa11x0_dma_dev { + struct dma_device slave; + void __iomem *base; + spinlock_t lock; + struct tasklet_struct task; + struct list_head chan_pending; + struct list_head desc_complete; + struct sa11x0_dma_phy phy[NR_PHY_CHAN]; +}; + +static struct sa11x0_dma_chan *to_sa11x0_dma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct sa11x0_dma_chan, chan); +} + +static struct sa11x0_dma_dev *to_sa11x0_dma(struct dma_device *dmadev) +{ + return container_of(dmadev, struct sa11x0_dma_dev, slave); +} + +static struct sa11x0_dma_desc *to_sa11x0_dma_tx(struct dma_async_tx_descriptor *tx) +{ + return container_of(tx, struct sa11x0_dma_desc, tx); +} + +static struct sa11x0_dma_desc *sa11x0_dma_next_desc(struct sa11x0_dma_chan *c) +{ + if (list_empty(&c->desc_issued)) + return NULL; + + return list_first_entry(&c->desc_issued, struct sa11x0_dma_desc, node); +} + +static void sa11x0_dma_start_desc(struct sa11x0_dma_phy *p, struct sa11x0_dma_desc *txd) +{ + list_del(&txd->node); + p->txd_load = txd; + p->sg_load = 0; + + dev_vdbg(p->dev->slave.dev, "pchan %u: txd %p[%x]: starting: DDAR:%x\n", + p->num, txd, txd->tx.cookie, txd->ddar); +} + +static void noinline sa11x0_dma_start_sg(struct sa11x0_dma_phy *p, + struct sa11x0_dma_chan *c) +{ + struct sa11x0_dma_desc *txd = p->txd_load; + struct sa11x0_dma_sg *sg; + void __iomem *base = p->base; + unsigned dbsx, dbtx; + u32 dcsr; + + if (!txd) + return; + + dcsr = readl_relaxed(base + DMA_DCSR_R); + + /* Don't try to load the next transfer if both buffers are started */ + if ((dcsr & (DCSR_STRTA | DCSR_STRTB)) == (DCSR_STRTA | DCSR_STRTB)) + return; + + if (p->sg_load == txd->sglen) { + struct sa11x0_dma_desc *txn = sa11x0_dma_next_desc(c); + + /* + * We have reached the end of the current descriptor. + * Peek at the next descriptor, and if compatible with + * the current, start processing it. + */ + if (txn && txn->ddar == txd->ddar) { + txd = txn; + sa11x0_dma_start_desc(p, txn); + } else { + p->txd_load = NULL; + return; + } + } + + sg = &txd->sg[p->sg_load++]; + + /* Select buffer to load according to channel status */ + if (((dcsr & (DCSR_BIU | DCSR_STRTB)) == (DCSR_BIU | DCSR_STRTB)) || + ((dcsr & (DCSR_BIU | DCSR_STRTA)) == 0)) { + dbsx = DMA_DBSA; + dbtx = DMA_DBTA; + dcsr = DCSR_STRTA | DCSR_IE | DCSR_RUN; + } else { + dbsx = DMA_DBSB; + dbtx = DMA_DBTB; + dcsr = DCSR_STRTB | DCSR_IE | DCSR_RUN; + } + + writel_relaxed(sg->addr, base + dbsx); + writel_relaxed(sg->len, base + dbtx); + writel(dcsr, base + DMA_DCSR_S); + + dev_dbg(p->dev->slave.dev, "pchan %u: load: DCSR:%02x DBS%c:%08x DBT%c:%08x\n", + p->num, dcsr, + 'A' + (dbsx == DMA_DBSB), sg->addr, + 'A' + (dbtx == DMA_DBTB), sg->len); +} + +static void noinline sa11x0_dma_complete(struct sa11x0_dma_phy *p, + struct sa11x0_dma_chan *c) +{ + struct sa11x0_dma_desc *txd = p->txd_done; + + if (++p->sg_done == txd->sglen) { + struct sa11x0_dma_dev *d = p->dev; + + dev_vdbg(d->slave.dev, "pchan %u: txd %p[%x]: completed\n", + p->num, p->txd_done, p->txd_done->tx.cookie); + + c->lc = txd->tx.cookie; + + spin_lock(&d->lock); + list_add_tail(&txd->node, &d->desc_complete); + spin_unlock(&d->lock); + + p->sg_done = 0; + p->txd_done = p->txd_load; + + tasklet_schedule(&d->task); + } + + sa11x0_dma_start_sg(p, c); +} + +static irqreturn_t sa11x0_dma_irq(int irq, void *dev_id) +{ + struct sa11x0_dma_phy *p = dev_id; + struct sa11x0_dma_dev *d = p->dev; + struct sa11x0_dma_chan *c; + u32 dcsr; + + dcsr = readl_relaxed(p->base + DMA_DCSR_R); + if (!(dcsr & (DCSR_ERROR | DCSR_DONEA | DCSR_DONEB))) + return IRQ_NONE; + + /* Clear reported status bits */ + writel_relaxed(dcsr & (DCSR_ERROR | DCSR_DONEA | DCSR_DONEB), + p->base + DMA_DCSR_C); + + dev_dbg(d->slave.dev, "pchan %u: irq: DCSR:%02x\n", p->num, dcsr); + + if (dcsr & DCSR_ERROR) { + dev_err(d->slave.dev, "pchan %u: error. DCSR:%02x DDAR:%08x DBSA:%08x DBTA:%08x DBSB:%08x DBTB:%08x\n", + p->num, dcsr, + readl_relaxed(p->base + DMA_DDAR), + readl_relaxed(p->base + DMA_DBSA), + readl_relaxed(p->base + DMA_DBTA), + readl_relaxed(p->base + DMA_DBSB), + readl_relaxed(p->base + DMA_DBTB)); + } + + c = p->vchan; + if (c) { + unsigned long flags; + + spin_lock_irqsave(&c->lock, flags); + /* + * Now that we're holding the lock, check that the vchan + * really is associated with this pchan before touching the + * hardware. This should always succeed, because we won't + * change p->vchan or c->phy while the channel is actively + * transferring. + */ + if (c->phy == p) { + if (dcsr & DCSR_DONEA) + sa11x0_dma_complete(p, c); + if (dcsr & DCSR_DONEB) + sa11x0_dma_complete(p, c); + } + spin_unlock_irqrestore(&c->lock, flags); + } + + return IRQ_HANDLED; +} + +static void sa11x0_dma_start_txd(struct sa11x0_dma_chan *c) +{ + struct sa11x0_dma_desc *txd = sa11x0_dma_next_desc(c); + + /* If the issued list is empty, we have no further txds to process */ + if (txd) { + struct sa11x0_dma_phy *p = c->phy; + + sa11x0_dma_start_desc(p, txd); + p->txd_done = txd; + p->sg_done = 0; + + /* The channel should not have any transfers started */ + WARN_ON(readl_relaxed(p->base + DMA_DCSR_R) & + (DCSR_STRTA | DCSR_STRTB)); + + /* Clear the run and start bits before changing DDAR */ + writel_relaxed(DCSR_RUN | DCSR_STRTA | DCSR_STRTB, + p->base + DMA_DCSR_C); + writel_relaxed(txd->ddar, p->base + DMA_DDAR); + + /* Try to start both buffers */ + sa11x0_dma_start_sg(p, c); + sa11x0_dma_start_sg(p, c); + } +} + +static void sa11x0_dma_tasklet(unsigned long arg) +{ + struct sa11x0_dma_dev *d = (struct sa11x0_dma_dev *)arg; + struct sa11x0_dma_phy *p; + struct sa11x0_dma_chan *c; + struct sa11x0_dma_desc *txd, *txn; + LIST_HEAD(head); + unsigned pch, pch_alloc = 0; + + dev_dbg(d->slave.dev, "tasklet enter\n"); + + /* Get the completed tx descriptors */ + spin_lock_irq(&d->lock); + list_splice_init(&d->desc_complete, &head); + spin_unlock_irq(&d->lock); + + list_for_each_entry(txd, &head, node) { + c = to_sa11x0_dma_chan(txd->tx.chan); + + dev_dbg(d->slave.dev, "vchan %p: txd %p[%x] completed\n", + c, txd, txd->tx.cookie); + + spin_lock_irq(&c->lock); + p = c->phy; + if (p) { + if (!p->txd_done) + sa11x0_dma_start_txd(c); + if (!p->txd_done) { + /* No current txd associated with this channel */ + dev_dbg(d->slave.dev, "pchan %u: free\n", p->num); + + /* Mark this channel free */ + c->phy = NULL; + p->vchan = NULL; + } + } + spin_unlock_irq(&c->lock); + } + + spin_lock_irq(&d->lock); + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + p = &d->phy[pch]; + + if (p->vchan == NULL && !list_empty(&d->chan_pending)) { + c = list_first_entry(&d->chan_pending, + struct sa11x0_dma_chan, node); + list_del_init(&c->node); + + pch_alloc |= 1 << pch; + + /* Mark this channel allocated */ + p->vchan = c; + + dev_dbg(d->slave.dev, "pchan %u: alloc vchan %p\n", pch, c); + } + } + spin_unlock_irq(&d->lock); + + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + if (pch_alloc & (1 << pch)) { + p = &d->phy[pch]; + c = p->vchan; + + spin_lock_irq(&c->lock); + c->phy = p; + + sa11x0_dma_start_txd(c); + spin_unlock_irq(&c->lock); + } + } + + /* Now free the completed tx descriptor, and call their callbacks */ + list_for_each_entry_safe(txd, txn, &head, node) { + dma_async_tx_callback callback = txd->tx.callback; + void *callback_param = txd->tx.callback_param; + + dev_dbg(d->slave.dev, "txd %p[%x]: callback and free\n", + txd, txd->tx.cookie); + + kfree(txd); + + if (callback) + callback(callback_param); + } + + dev_dbg(d->slave.dev, "tasklet exit\n"); +} + + +static void sa11x0_dma_desc_free(struct sa11x0_dma_dev *d, struct list_head *head) +{ + struct sa11x0_dma_desc *txd, *txn; + + list_for_each_entry_safe(txd, txn, head, node) { + dev_dbg(d->slave.dev, "txd %p: freeing\n", txd); + kfree(txd); + } +} + +static int sa11x0_dma_alloc_chan_resources(struct dma_chan *chan) +{ + return 0; +} + +static void sa11x0_dma_free_chan_resources(struct dma_chan *chan) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&c->lock, flags); + spin_lock(&d->lock); + list_del_init(&c->node); + spin_unlock(&d->lock); + + list_splice_tail_init(&c->desc_submitted, &head); + list_splice_tail_init(&c->desc_issued, &head); + spin_unlock_irqrestore(&c->lock, flags); + + sa11x0_dma_desc_free(d, &head); +} + +static dma_addr_t sa11x0_dma_pos(struct sa11x0_dma_phy *p) +{ + unsigned reg; + u32 dcsr; + + dcsr = readl_relaxed(p->base + DMA_DCSR_R); + + if ((dcsr & (DCSR_BIU | DCSR_STRTA)) == DCSR_STRTA || + (dcsr & (DCSR_BIU | DCSR_STRTB)) == DCSR_BIU) + reg = DMA_DBSA; + else + reg = DMA_DBSB; + + return readl_relaxed(p->base + reg); +} + +static enum dma_status sa11x0_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *state) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + struct sa11x0_dma_phy *p; + struct sa11x0_dma_desc *txd; + dma_cookie_t last_used, last_complete; + unsigned long flags; + enum dma_status ret; + size_t bytes = 0; + + last_used = c->chan.cookie; + last_complete = c->lc; + + ret = dma_async_is_complete(cookie, last_complete, last_used); + if (ret == DMA_SUCCESS) { + dma_set_tx_state(state, last_complete, last_used, 0); + return ret; + } + + spin_lock_irqsave(&c->lock, flags); + p = c->phy; + ret = c->status; + if (p) { + dma_addr_t addr = sa11x0_dma_pos(p); + + dev_vdbg(d->slave.dev, "tx_status: addr:%x\n", addr); + + txd = p->txd_done; + if (txd) { + unsigned i; + + for (i = 0; i < txd->sglen; i++) { + dev_vdbg(d->slave.dev, "tx_status: [%u] %x+%x\n", + i, txd->sg[i].addr, txd->sg[i].len); + if (addr >= txd->sg[i].addr && + addr < txd->sg[i].addr + txd->sg[i].len) { + unsigned len; + + len = txd->sg[i].len - + (addr - txd->sg[i].addr); + dev_vdbg(d->slave.dev, "tx_status: [%u] +%x\n", + i, len); + bytes += len; + i++; + break; + } + } + for (; i < txd->sglen; i++) { + dev_vdbg(d->slave.dev, "tx_status: [%u] %x+%x ++\n", + i, txd->sg[i].addr, txd->sg[i].len); + bytes += txd->sg[i].len; + } + } + if (txd != p->txd_load && p->txd_load) + bytes += p->txd_load->size; + } + list_for_each_entry(txd, &c->desc_issued, node) { + bytes += txd->size; + } + spin_unlock_irqrestore(&c->lock, flags); + + dma_set_tx_state(state, last_complete, last_used, bytes); + + dev_vdbg(d->slave.dev, "tx_status: bytes 0x%zx\n", bytes); + + return ret; +} + +/* + * Move pending txds to the issued list, and re-init pending list. + * If not already pending, add this channel to the list of pending + * channels and trigger the tasklet to run. + */ +static void sa11x0_dma_issue_pending(struct dma_chan *chan) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + unsigned long flags; + + spin_lock_irqsave(&c->lock, flags); + list_splice_tail_init(&c->desc_submitted, &c->desc_issued); + if (!list_empty(&c->desc_issued)) { + spin_lock(&d->lock); + if (!c->phy && list_empty(&c->node)) { + list_add_tail(&c->node, &d->chan_pending); + tasklet_schedule(&d->task); + dev_dbg(d->slave.dev, "vchan %p: issued\n", c); + } + spin_unlock(&d->lock); + } else + dev_dbg(d->slave.dev, "vchan %p: nothing to issue\n", c); + spin_unlock_irqrestore(&c->lock, flags); +} + +static dma_cookie_t sa11x0_dma_tx_submit(struct dma_async_tx_descriptor *tx) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(tx->chan); + struct sa11x0_dma_desc *txd = to_sa11x0_dma_tx(tx); + unsigned long flags; + + spin_lock_irqsave(&c->lock, flags); + c->chan.cookie += 1; + if (c->chan.cookie < 0) + c->chan.cookie = 1; + txd->tx.cookie = c->chan.cookie; + + list_add_tail(&txd->node, &c->desc_submitted); + spin_unlock_irqrestore(&c->lock, flags); + + dev_dbg(tx->chan->device->dev, "vchan %p: txd %p[%x]: submitted\n", + c, txd, txd->tx.cookie); + + return txd->tx.cookie; +} + +static struct dma_async_tx_descriptor *sa11x0_dma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sg, unsigned int sglen, + enum dma_transfer_direction dir, unsigned long flags) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_desc *txd; + struct scatterlist *sgent; + unsigned i, j = sglen; + size_t size = 0; + + /* SA11x0 channels can only operate in their native direction */ + if (dir != (c->ddar & DDAR_RW ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV)) { + dev_err(chan->device->dev, "vchan %p: bad DMA direction: DDAR:%08x dir:%u\n", + c, c->ddar, dir); + return NULL; + } + + /* Do not allow zero-sized txds */ + if (sglen == 0) + return NULL; + + for_each_sg(sg, sgent, sglen, i) { + dma_addr_t addr = sg_dma_address(sgent); + unsigned int len = sg_dma_len(sgent); + + if (len > DMA_MAX_SIZE) + j += DIV_ROUND_UP(len, DMA_MAX_SIZE & ~DMA_ALIGN) - 1; + if (addr & DMA_ALIGN) { + dev_dbg(chan->device->dev, "vchan %p: bad buffer alignment: %08x\n", + c, addr); + return NULL; + } + } + + txd = kzalloc(sizeof(*txd) + j * sizeof(txd->sg[0]), GFP_ATOMIC); + if (!txd) { + dev_dbg(chan->device->dev, "vchan %p: kzalloc failed\n", c); + return NULL; + } + + j = 0; + for_each_sg(sg, sgent, sglen, i) { + dma_addr_t addr = sg_dma_address(sgent); + unsigned len = sg_dma_len(sgent); + + size += len; + + do { + unsigned tlen = len; + + /* + * Check whether the transfer will fit. If not, try + * to split the transfer up such that we end up with + * equal chunks - but make sure that we preserve the + * alignment. This avoids small segments. + */ + if (tlen > DMA_MAX_SIZE) { + unsigned mult = DIV_ROUND_UP(tlen, + DMA_MAX_SIZE & ~DMA_ALIGN); + + tlen = (tlen / mult) & ~DMA_ALIGN; + } + + txd->sg[j].addr = addr; + txd->sg[j].len = tlen; + + addr += tlen; + len -= tlen; + j++; + } while (len); + } + + dma_async_tx_descriptor_init(&txd->tx, &c->chan); + txd->tx.flags = flags; + txd->tx.tx_submit = sa11x0_dma_tx_submit; + txd->ddar = c->ddar; + txd->size = size; + txd->sglen = j; + + dev_dbg(chan->device->dev, "vchan %p: txd %p: size %u nr %u\n", + c, txd, txd->size, txd->sglen); + + return &txd->tx; +} + +static int sa11x0_dma_slave_config(struct sa11x0_dma_chan *c, struct dma_slave_config *cfg) +{ + u32 ddar = c->ddar & ((0xf << 4) | DDAR_RW); + dma_addr_t addr; + enum dma_slave_buswidth width; + u32 maxburst; + + if (ddar & DDAR_RW) { + addr = cfg->src_addr; + width = cfg->src_addr_width; + maxburst = cfg->src_maxburst; + } else { + addr = cfg->dst_addr; + width = cfg->dst_addr_width; + maxburst = cfg->dst_maxburst; + } + + if ((width != DMA_SLAVE_BUSWIDTH_1_BYTE && + width != DMA_SLAVE_BUSWIDTH_2_BYTES) || + (maxburst != 4 && maxburst != 8)) + return -EINVAL; + + if (width == DMA_SLAVE_BUSWIDTH_2_BYTES) + ddar |= DDAR_DW; + if (maxburst == 8) + ddar |= DDAR_BS; + + dev_dbg(c->chan.device->dev, "vchan %p: dma_slave_config addr %x width %u burst %u\n", + c, addr, width, maxburst); + + c->ddar = ddar | (addr & 0xf0000000) | (addr & 0x003ffffc) << 6; + + return 0; +} + +static int sa11x0_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, + unsigned long arg) +{ + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + struct sa11x0_dma_dev *d = to_sa11x0_dma(chan->device); + struct sa11x0_dma_phy *p; + LIST_HEAD(head); + unsigned long flags; + int ret; + + switch (cmd) { + case DMA_SLAVE_CONFIG: + return sa11x0_dma_slave_config(c, (struct dma_slave_config *)arg); + + case DMA_TERMINATE_ALL: + dev_dbg(d->slave.dev, "vchan %p: terminate all\n", c); + /* Clear the tx descriptor lists */ + spin_lock_irqsave(&c->lock, flags); + list_splice_tail_init(&c->desc_submitted, &head); + list_splice_tail_init(&c->desc_issued, &head); + + p = c->phy; + if (p) { + struct sa11x0_dma_desc *txd, *txn; + + dev_dbg(d->slave.dev, "pchan %u: terminating\n", p->num); + /* vchan is assigned to a pchan - stop the channel */ + writel(DCSR_RUN | DCSR_IE | + DCSR_STRTA | DCSR_DONEA | + DCSR_STRTB | DCSR_DONEB, + p->base + DMA_DCSR_C); + + list_for_each_entry_safe(txd, txn, &d->desc_complete, node) + if (txd->tx.chan == &c->chan) + list_move(&txd->node, &head); + + if (p->txd_load) { + if (p->txd_load != p->txd_done) + list_add_tail(&p->txd_load->node, &head); + p->txd_load = NULL; + } + if (p->txd_done) { + list_add_tail(&p->txd_done->node, &head); + p->txd_done = NULL; + } + c->phy = NULL; + spin_lock(&d->lock); + p->vchan = NULL; + spin_unlock(&d->lock); + tasklet_schedule(&d->task); + } + spin_unlock_irqrestore(&c->lock, flags); + sa11x0_dma_desc_free(d, &head); + ret = 0; + break; + + case DMA_PAUSE: + dev_dbg(d->slave.dev, "vchan %p: pause\n", c); + spin_lock_irqsave(&c->lock, flags); + if (c->status == DMA_IN_PROGRESS) { + c->status = DMA_PAUSED; + + p = c->phy; + if (p) { + writel(DCSR_RUN | DCSR_IE, p->base + DMA_DCSR_C); + } else { + spin_lock(&d->lock); + list_del_init(&c->node); + spin_unlock(&d->lock); + } + } + spin_unlock_irqrestore(&c->lock, flags); + ret = 0; + break; + + case DMA_RESUME: + dev_dbg(d->slave.dev, "vchan %p: resume\n", c); + spin_lock_irqsave(&c->lock, flags); + if (c->status == DMA_PAUSED) { + c->status = DMA_IN_PROGRESS; + + p = c->phy; + if (p) { + writel(DCSR_RUN | DCSR_IE, p->base + DMA_DCSR_S); + } else if (!list_empty(&c->desc_issued)) { + spin_lock(&d->lock); + list_add_tail(&c->node, &d->chan_pending); + spin_unlock(&d->lock); + } + } + spin_unlock_irqrestore(&c->lock, flags); + ret = 0; + break; + + default: + ret = -ENXIO; + break; + } + + return ret; +} + +struct sa11x0_dma_channel_desc { + u32 ddar; + const char *name; +}; + +#define CD(d1, d2) { .ddar = DDAR_##d1 | d2, .name = #d1 } +static const struct sa11x0_dma_channel_desc chan_desc[] = { + CD(Ser0UDCTr, 0), + CD(Ser0UDCRc, DDAR_RW), + CD(Ser1SDLCTr, 0), + CD(Ser1SDLCRc, DDAR_RW), + CD(Ser1UARTTr, 0), + CD(Ser1UARTRc, DDAR_RW), + CD(Ser2ICPTr, 0), + CD(Ser2ICPRc, DDAR_RW), + CD(Ser3UARTTr, 0), + CD(Ser3UARTRc, DDAR_RW), + CD(Ser4MCP0Tr, 0), + CD(Ser4MCP0Rc, DDAR_RW), + CD(Ser4MCP1Tr, 0), + CD(Ser4MCP1Rc, DDAR_RW), + CD(Ser4SSPTr, 0), + CD(Ser4SSPRc, DDAR_RW), +}; + +static int __devinit sa11x0_dma_init_dmadev(struct dma_device *dmadev, + struct device *dev) +{ + unsigned i; + + dmadev->chancnt = ARRAY_SIZE(chan_desc); + INIT_LIST_HEAD(&dmadev->channels); + dmadev->dev = dev; + dmadev->device_alloc_chan_resources = sa11x0_dma_alloc_chan_resources; + dmadev->device_free_chan_resources = sa11x0_dma_free_chan_resources; + dmadev->device_control = sa11x0_dma_control; + dmadev->device_tx_status = sa11x0_dma_tx_status; + dmadev->device_issue_pending = sa11x0_dma_issue_pending; + + for (i = 0; i < dmadev->chancnt; i++) { + struct sa11x0_dma_chan *c; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + dev_err(dev, "no memory for channel %u\n", i); + return -ENOMEM; + } + + c->chan.device = dmadev; + c->status = DMA_IN_PROGRESS; + c->ddar = chan_desc[i].ddar; + c->name = chan_desc[i].name; + spin_lock_init(&c->lock); + INIT_LIST_HEAD(&c->desc_submitted); + INIT_LIST_HEAD(&c->desc_issued); + INIT_LIST_HEAD(&c->node); + list_add_tail(&c->chan.device_node, &dmadev->channels); + } + + return dma_async_device_register(dmadev); +} + +static int sa11x0_dma_request_irq(struct platform_device *pdev, int nr, + void *data) +{ + int irq = platform_get_irq(pdev, nr); + + if (irq <= 0) + return -ENXIO; + + return request_irq(irq, sa11x0_dma_irq, 0, dev_name(&pdev->dev), data); +} + +static void sa11x0_dma_free_irq(struct platform_device *pdev, int nr, + void *data) +{ + int irq = platform_get_irq(pdev, nr); + if (irq > 0) + free_irq(irq, data); +} + +static void sa11x0_dma_free_channels(struct dma_device *dmadev) +{ + struct sa11x0_dma_chan *c, *cn; + + list_for_each_entry_safe(c, cn, &dmadev->channels, chan.device_node) { + list_del(&c->chan.device_node); + kfree(c); + } +} + +static int __devinit sa11x0_dma_probe(struct platform_device *pdev) +{ + struct sa11x0_dma_dev *d; + struct resource *res; + unsigned i; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) { + ret = -ENOMEM; + goto err_alloc; + } + + spin_lock_init(&d->lock); + INIT_LIST_HEAD(&d->chan_pending); + INIT_LIST_HEAD(&d->desc_complete); + + d->base = ioremap(res->start, resource_size(res)); + if (!d->base) { + ret = -ENOMEM; + goto err_ioremap; + } + + tasklet_init(&d->task, sa11x0_dma_tasklet, (unsigned long)d); + + for (i = 0; i < NR_PHY_CHAN; i++) { + struct sa11x0_dma_phy *p = &d->phy[i]; + + p->dev = d; + p->num = i; + p->base = d->base + i * DMA_SIZE; + writel_relaxed(DCSR_RUN | DCSR_IE | DCSR_ERROR | + DCSR_DONEA | DCSR_STRTA | DCSR_DONEB | DCSR_STRTB, + p->base + DMA_DCSR_C); + writel_relaxed(0, p->base + DMA_DDAR); + + ret = sa11x0_dma_request_irq(pdev, i, p); + if (ret) { + while (i) { + i--; + sa11x0_dma_free_irq(pdev, i, &d->phy[i]); + } + goto err_irq; + } + } + + dma_cap_set(DMA_SLAVE, d->slave.cap_mask); + d->slave.device_prep_slave_sg = sa11x0_dma_prep_slave_sg; + ret = sa11x0_dma_init_dmadev(&d->slave, &pdev->dev); + if (ret) { + dev_warn(d->slave.dev, "failed to register slave async device: %d\n", + ret); + goto err_slave_reg; + } + + platform_set_drvdata(pdev, d); + return 0; + + err_slave_reg: + sa11x0_dma_free_channels(&d->slave); + for (i = 0; i < NR_PHY_CHAN; i++) + sa11x0_dma_free_irq(pdev, i, &d->phy[i]); + err_irq: + tasklet_kill(&d->task); + iounmap(d->base); + err_ioremap: + kfree(d); + err_alloc: + return ret; +} + +static int __devexit sa11x0_dma_remove(struct platform_device *pdev) +{ + struct sa11x0_dma_dev *d = platform_get_drvdata(pdev); + unsigned pch; + + dma_async_device_unregister(&d->slave); + + sa11x0_dma_free_channels(&d->slave); + for (pch = 0; pch < NR_PHY_CHAN; pch++) + sa11x0_dma_free_irq(pdev, pch, &d->phy[pch]); + tasklet_kill(&d->task); + iounmap(d->base); + kfree(d); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int sa11x0_dma_suspend(struct device *dev) +{ + struct sa11x0_dma_dev *d = dev_get_drvdata(dev); + unsigned pch; + + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + struct sa11x0_dma_phy *p = &d->phy[pch]; + u32 dcsr, saved_dcsr; + + dcsr = saved_dcsr = readl_relaxed(p->base + DMA_DCSR_R); + if (dcsr & DCSR_RUN) { + writel(DCSR_RUN | DCSR_IE, p->base + DMA_DCSR_C); + dcsr = readl_relaxed(p->base + DMA_DCSR_R); + } + + saved_dcsr &= DCSR_RUN | DCSR_IE; + if (dcsr & DCSR_BIU) { + p->dbs[0] = readl_relaxed(p->base + DMA_DBSB); + p->dbt[0] = readl_relaxed(p->base + DMA_DBTB); + p->dbs[1] = readl_relaxed(p->base + DMA_DBSA); + p->dbt[1] = readl_relaxed(p->base + DMA_DBTA); + saved_dcsr |= (dcsr & DCSR_STRTA ? DCSR_STRTB : 0) | + (dcsr & DCSR_STRTB ? DCSR_STRTA : 0); + } else { + p->dbs[0] = readl_relaxed(p->base + DMA_DBSA); + p->dbt[0] = readl_relaxed(p->base + DMA_DBTA); + p->dbs[1] = readl_relaxed(p->base + DMA_DBSB); + p->dbt[1] = readl_relaxed(p->base + DMA_DBTB); + saved_dcsr |= dcsr & (DCSR_STRTA | DCSR_STRTB); + } + p->dcsr = saved_dcsr; + + writel(DCSR_STRTA | DCSR_STRTB, p->base + DMA_DCSR_C); + } + + return 0; +} + +static int sa11x0_dma_resume(struct device *dev) +{ + struct sa11x0_dma_dev *d = dev_get_drvdata(dev); + unsigned pch; + + for (pch = 0; pch < NR_PHY_CHAN; pch++) { + struct sa11x0_dma_phy *p = &d->phy[pch]; + struct sa11x0_dma_desc *txd = NULL; + u32 dcsr = readl_relaxed(p->base + DMA_DCSR_R); + + WARN_ON(dcsr & (DCSR_BIU | DCSR_STRTA | DCSR_STRTB | DCSR_RUN)); + + if (p->txd_done) + txd = p->txd_done; + else if (p->txd_load) + txd = p->txd_load; + + if (!txd) + continue; + + writel_relaxed(txd->ddar, p->base + DMA_DDAR); + + writel_relaxed(p->dbs[0], p->base + DMA_DBSA); + writel_relaxed(p->dbt[0], p->base + DMA_DBTA); + writel_relaxed(p->dbs[1], p->base + DMA_DBSB); + writel_relaxed(p->dbt[1], p->base + DMA_DBTB); + writel_relaxed(p->dcsr, p->base + DMA_DCSR_S); + } + + return 0; +} +#endif + +static const struct dev_pm_ops sa11x0_dma_pm_ops = { + .suspend_noirq = sa11x0_dma_suspend, + .resume_noirq = sa11x0_dma_resume, + .freeze_noirq = sa11x0_dma_suspend, + .thaw_noirq = sa11x0_dma_resume, + .poweroff_noirq = sa11x0_dma_suspend, + .restore_noirq = sa11x0_dma_resume, +}; + +static struct platform_driver sa11x0_dma_driver = { + .driver = { + .name = "sa11x0-dma", + .owner = THIS_MODULE, + .pm = &sa11x0_dma_pm_ops, + }, + .probe = sa11x0_dma_probe, + .remove = __devexit_p(sa11x0_dma_remove), +}; + +bool sa11x0_dma_filter_fn(struct dma_chan *chan, void *param) +{ + if (chan->device->dev->driver == &sa11x0_dma_driver.driver) { + struct sa11x0_dma_chan *c = to_sa11x0_dma_chan(chan); + const char *p = param; + + return !strcmp(c->name, p); + } + return false; +} +EXPORT_SYMBOL(sa11x0_dma_filter_fn); + +static int __init sa11x0_dma_init(void) +{ + return platform_driver_register(&sa11x0_dma_driver); +} +subsys_initcall(sa11x0_dma_init); + +static void __exit sa11x0_dma_exit(void) +{ + platform_driver_unregister(&sa11x0_dma_driver); +} +module_exit(sa11x0_dma_exit); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("SA-11x0 DMA driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sa11x0-dma"); diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index c9eee6d33e9a..7ef73c919c5d 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -1132,12 +1132,36 @@ static int k8_dbam_to_chip_select(struct amd64_pvt *pvt, u8 dct, return ddr2_cs_size(cs_mode, dclr & WIDTH_128); } else if (pvt->ext_model >= K8_REV_D) { + unsigned diff; WARN_ON(cs_mode > 10); - if (cs_mode == 3 || cs_mode == 8) - return 32 << (cs_mode - 1); - else - return 32 << cs_mode; + /* + * the below calculation, besides trying to win an obfuscated C + * contest, maps cs_mode values to DIMM chip select sizes. The + * mappings are: + * + * cs_mode CS size (mb) + * ======= ============ + * 0 32 + * 1 64 + * 2 128 + * 3 128 + * 4 256 + * 5 512 + * 6 256 + * 7 512 + * 8 1024 + * 9 1024 + * 10 2048 + * + * Basically, it calculates a value with which to shift the + * smallest CS size of 32MB. + * + * ddr[23]_cs_size have a similar purpose. + */ + diff = cs_mode/3 + (unsigned)(cs_mode > 5); + + return 32 << (cs_mode - diff); } else { WARN_ON(cs_mode > 6); @@ -2133,6 +2157,7 @@ static void read_mc_regs(struct amd64_pvt *pvt) static u32 amd64_csrow_nr_pages(struct amd64_pvt *pvt, u8 dct, int csrow_nr) { u32 cs_mode, nr_pages; + u32 dbam = dct ? pvt->dbam1 : pvt->dbam0; /* * The math on this doesn't look right on the surface because x/2*4 can @@ -2141,16 +2166,10 @@ static u32 amd64_csrow_nr_pages(struct amd64_pvt *pvt, u8 dct, int csrow_nr) * number of bits to shift the DBAM register to extract the proper CSROW * field. */ - cs_mode = (pvt->dbam0 >> ((csrow_nr / 2) * 4)) & 0xF; + cs_mode = (dbam >> ((csrow_nr / 2) * 4)) & 0xF; nr_pages = pvt->ops->dbam_to_cs(pvt, dct, cs_mode) << (20 - PAGE_SHIFT); - /* - * If dual channel then double the memory size of single channel. - * Channel count is 1 or 2 - */ - nr_pages <<= (pvt->channel_count - 1); - debugf0(" (csrow=%d) DBAM map index= %d\n", csrow_nr, cs_mode); debugf0(" nr_pages= %u channel-count = %d\n", nr_pages, pvt->channel_count); @@ -2181,7 +2200,7 @@ static int init_csrows(struct mem_ctl_info *mci) for_each_chip_select(i, 0, pvt) { csrow = &mci->csrows[i]; - if (!csrow_enabled(i, 0, pvt)) { + if (!csrow_enabled(i, 0, pvt) && !csrow_enabled(i, 1, pvt)) { debugf1("----CSROW %d EMPTY for node %d\n", i, pvt->mc_node_id); continue; @@ -2191,7 +2210,10 @@ static int init_csrows(struct mem_ctl_info *mci) i, pvt->mc_node_id); empty = 0; - csrow->nr_pages = amd64_csrow_nr_pages(pvt, 0, i); + if (csrow_enabled(i, 0, pvt)) + csrow->nr_pages = amd64_csrow_nr_pages(pvt, 0, i); + if (csrow_enabled(i, 1, pvt)) + csrow->nr_pages += amd64_csrow_nr_pages(pvt, 1, i); find_csrow_limits(mci, i, &input_addr_min, &input_addr_max); sys_addr = input_addr_to_sys_addr(mci, input_addr_min); csrow->first_page = (u32) (sys_addr >> PAGE_SHIFT); @@ -2685,7 +2707,7 @@ static void __devexit amd64_remove_one_instance(struct pci_dev *pdev) * PCI core identifies what devices are on a system during boot, and then * inquiry this table to see if this driver is for a given device found. */ -static const struct pci_device_id amd64_pci_table[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(amd64_pci_table) = { { .vendor = PCI_VENDOR_ID_AMD, .device = PCI_DEVICE_ID_AMD_K8_NB_MEMCTL, diff --git a/drivers/edac/amd76x_edac.c b/drivers/edac/amd76x_edac.c index e47e73bbbcc5..f8fd3c807bde 100644 --- a/drivers/edac/amd76x_edac.c +++ b/drivers/edac/amd76x_edac.c @@ -321,7 +321,7 @@ static void __devexit amd76x_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id amd76x_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(amd76x_pci_tbl) = { { PCI_VEND_DEV(AMD, FE_GATE_700C), PCI_ANY_ID, PCI_ANY_ID, 0, 0, AMD762}, diff --git a/drivers/edac/e752x_edac.c b/drivers/edac/e752x_edac.c index 1af531a11d21..41223261ede9 100644 --- a/drivers/edac/e752x_edac.c +++ b/drivers/edac/e752x_edac.c @@ -1380,7 +1380,7 @@ static void __devexit e752x_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id e752x_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(e752x_pci_tbl) = { { PCI_VEND_DEV(INTEL, 7520_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, E7520}, diff --git a/drivers/edac/e7xxx_edac.c b/drivers/edac/e7xxx_edac.c index 6ffb6d23281f..68dea87b72e6 100644 --- a/drivers/edac/e7xxx_edac.c +++ b/drivers/edac/e7xxx_edac.c @@ -525,7 +525,7 @@ static void __devexit e7xxx_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id e7xxx_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(e7xxx_pci_tbl) = { { PCI_VEND_DEV(INTEL, 7205_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, E7205}, diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c index d56e63477d5c..e9a28f576d14 100644 --- a/drivers/edac/edac_mc_sysfs.c +++ b/drivers/edac/edac_mc_sysfs.c @@ -452,7 +452,7 @@ static ssize_t mci_sdram_scrub_rate_store(struct mem_ctl_info *mci, int new_bw = 0; if (!mci->set_sdram_scrub_rate) - return -EINVAL; + return -ENODEV; if (strict_strtoul(data, 10, &bandwidth) < 0) return -EINVAL; @@ -475,7 +475,7 @@ static ssize_t mci_sdram_scrub_rate_show(struct mem_ctl_info *mci, char *data) int bandwidth = 0; if (!mci->get_sdram_scrub_rate) - return -EINVAL; + return -ENODEV; bandwidth = mci->get_sdram_scrub_rate(mci); if (bandwidth < 0) { diff --git a/drivers/edac/edac_stub.c b/drivers/edac/edac_stub.c index 670c4481453b..6c86f6e54558 100644 --- a/drivers/edac/edac_stub.c +++ b/drivers/edac/edac_stub.c @@ -15,6 +15,7 @@ #include <linux/module.h> #include <linux/edac.h> #include <linux/atomic.h> +#include <linux/device.h> #include <asm/edac.h> int edac_op_state = EDAC_OPSTATE_INVAL; diff --git a/drivers/edac/i3000_edac.c b/drivers/edac/i3000_edac.c index c0510b3d7035..277689a68841 100644 --- a/drivers/edac/i3000_edac.c +++ b/drivers/edac/i3000_edac.c @@ -470,7 +470,7 @@ static void __devexit i3000_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id i3000_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i3000_pci_tbl) = { { PCI_VEND_DEV(INTEL, 3000_HB), PCI_ANY_ID, PCI_ANY_ID, 0, 0, I3000}, diff --git a/drivers/edac/i3200_edac.c b/drivers/edac/i3200_edac.c index 73f55e2008c2..046808c6357d 100644 --- a/drivers/edac/i3200_edac.c +++ b/drivers/edac/i3200_edac.c @@ -445,7 +445,7 @@ static void __devexit i3200_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id i3200_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i3200_pci_tbl) = { { PCI_VEND_DEV(INTEL, 3200_HB), PCI_ANY_ID, PCI_ANY_ID, 0, 0, I3200}, diff --git a/drivers/edac/i5000_edac.c b/drivers/edac/i5000_edac.c index 4dc3ac25a422..a2680d8e744b 100644 --- a/drivers/edac/i5000_edac.c +++ b/drivers/edac/i5000_edac.c @@ -1516,7 +1516,7 @@ static void __devexit i5000_remove_one(struct pci_dev *pdev) * * The "E500P" device is the first device supported. */ -static const struct pci_device_id i5000_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i5000_pci_tbl) = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_I5000_DEV16), .driver_data = I5000P}, diff --git a/drivers/edac/i5100_edac.c b/drivers/edac/i5100_edac.c index bcbdeeca48b8..2e23547b2f24 100644 --- a/drivers/edac/i5100_edac.c +++ b/drivers/edac/i5100_edac.c @@ -1051,7 +1051,7 @@ static void __devexit i5100_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id i5100_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i5100_pci_tbl) = { /* Device 16, Function 0, Channel 0 Memory Map, Error Flag/Mask, ... */ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5100_16) }, { 0, } diff --git a/drivers/edac/i5400_edac.c b/drivers/edac/i5400_edac.c index 74d6ec342afb..67ec9626a330 100644 --- a/drivers/edac/i5400_edac.c +++ b/drivers/edac/i5400_edac.c @@ -1383,7 +1383,7 @@ static void __devexit i5400_remove_one(struct pci_dev *pdev) * * The "E500P" device is the first device supported. */ -static const struct pci_device_id i5400_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i5400_pci_tbl) = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR)}, {0,} /* 0 terminated list. */ }; diff --git a/drivers/edac/i7300_edac.c b/drivers/edac/i7300_edac.c index 6104dba380b6..3bafa3bca148 100644 --- a/drivers/edac/i7300_edac.c +++ b/drivers/edac/i7300_edac.c @@ -1192,7 +1192,7 @@ static void __devexit i7300_remove_one(struct pci_dev *pdev) * * Has only 8086:360c PCI ID */ -static const struct pci_device_id i7300_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i7300_pci_tbl) = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_I7300_MCH_ERR)}, {0,} /* 0 terminated list. */ }; diff --git a/drivers/edac/i7core_edac.c b/drivers/edac/i7core_edac.c index 8568d9b61875..85226ccf5290 100644 --- a/drivers/edac/i7core_edac.c +++ b/drivers/edac/i7core_edac.c @@ -391,7 +391,7 @@ static const struct pci_id_table pci_dev_table[] = { /* * pci_device_id table for which devices we are looking for */ -static const struct pci_device_id i7core_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i7core_pci_tbl) = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_X58_HUB_MGMT)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_LYNNFIELD_QPI_LINK0)}, {0,} /* 0 terminated list. */ diff --git a/drivers/edac/i82443bxgx_edac.c b/drivers/edac/i82443bxgx_edac.c index 4329d39f902c..3bf2b2f490e7 100644 --- a/drivers/edac/i82443bxgx_edac.c +++ b/drivers/edac/i82443bxgx_edac.c @@ -380,7 +380,7 @@ static void __devexit i82443bxgx_edacmc_remove_one(struct pci_dev *pdev) EXPORT_SYMBOL_GPL(i82443bxgx_edacmc_remove_one); -static const struct pci_device_id i82443bxgx_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i82443bxgx_pci_tbl) = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443BX_0)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443BX_2)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443GX_0)}, diff --git a/drivers/edac/i82860_edac.c b/drivers/edac/i82860_edac.c index 931a05775049..c779092d18d1 100644 --- a/drivers/edac/i82860_edac.c +++ b/drivers/edac/i82860_edac.c @@ -270,7 +270,7 @@ static void __devexit i82860_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id i82860_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i82860_pci_tbl) = { { PCI_VEND_DEV(INTEL, 82860_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, I82860}, diff --git a/drivers/edac/i82875p_edac.c b/drivers/edac/i82875p_edac.c index 33864c63c684..10f15d85fb5e 100644 --- a/drivers/edac/i82875p_edac.c +++ b/drivers/edac/i82875p_edac.c @@ -511,7 +511,7 @@ static void __devexit i82875p_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id i82875p_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i82875p_pci_tbl) = { { PCI_VEND_DEV(INTEL, 82875_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, I82875P}, diff --git a/drivers/edac/i82975x_edac.c b/drivers/edac/i82975x_edac.c index 4184e0171f00..0cd8368f88f8 100644 --- a/drivers/edac/i82975x_edac.c +++ b/drivers/edac/i82975x_edac.c @@ -612,7 +612,7 @@ static void __devexit i82975x_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id i82975x_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(i82975x_pci_tbl) = { { PCI_VEND_DEV(INTEL, 82975_0), PCI_ANY_ID, PCI_ANY_ID, 0, 0, I82975X diff --git a/drivers/edac/mce_amd.c b/drivers/edac/mce_amd.c index bd926ea2e00c..36e1486eb9aa 100644 --- a/drivers/edac/mce_amd.c +++ b/drivers/edac/mce_amd.c @@ -39,42 +39,31 @@ EXPORT_SYMBOL_GPL(amd_unregister_ecc_decoder); */ /* transaction type */ -const char *tt_msgs[] = { "INSN", "DATA", "GEN", "RESV" }; +const char * const tt_msgs[] = { "INSN", "DATA", "GEN", "RESV" }; EXPORT_SYMBOL_GPL(tt_msgs); /* cache level */ -const char *ll_msgs[] = { "RESV", "L1", "L2", "L3/GEN" }; +const char * const ll_msgs[] = { "RESV", "L1", "L2", "L3/GEN" }; EXPORT_SYMBOL_GPL(ll_msgs); /* memory transaction type */ -const char *rrrr_msgs[] = { +const char * const rrrr_msgs[] = { "GEN", "RD", "WR", "DRD", "DWR", "IRD", "PRF", "EV", "SNP" }; EXPORT_SYMBOL_GPL(rrrr_msgs); /* participating processor */ -const char *pp_msgs[] = { "SRC", "RES", "OBS", "GEN" }; +const char * const pp_msgs[] = { "SRC", "RES", "OBS", "GEN" }; EXPORT_SYMBOL_GPL(pp_msgs); /* request timeout */ -const char *to_msgs[] = { "no timeout", "timed out" }; +const char * const to_msgs[] = { "no timeout", "timed out" }; EXPORT_SYMBOL_GPL(to_msgs); /* memory or i/o */ -const char *ii_msgs[] = { "MEM", "RESV", "IO", "GEN" }; +const char * const ii_msgs[] = { "MEM", "RESV", "IO", "GEN" }; EXPORT_SYMBOL_GPL(ii_msgs); -static const char *f10h_nb_mce_desc[] = { - "HT link data error", - "Protocol error (link, L3, probe filter, etc.)", - "Parity error in NB-internal arrays", - "Link Retry due to IO link transmission error", - "L3 ECC data cache error", - "ECC error in L3 cache tag", - "L3 LRU parity bits error", - "ECC Error in the Probe Filter directory" -}; - static const char * const f15h_ic_mce_desc[] = { "UC during a demand linefill from L2", "Parity error during data load from IC", @@ -88,7 +77,7 @@ static const char * const f15h_ic_mce_desc[] = { "Parity error for IC probe tag valid bit", "PFB non-cacheable bit parity error", "PFB valid bit parity error", /* xec = 0xd */ - "patch RAM", /* xec = 010 */ + "Microcode Patch Buffer", /* xec = 010 */ "uop queue", "insn buffer", "predecode buffer", @@ -104,7 +93,7 @@ static const char * const f15h_cu_mce_desc[] = { "WCC Tag ECC error", "WCC Data ECC error", "WCB Data parity error", - "VB Data/ECC error", + "VB Data ECC or parity error", "L2 Tag ECC error", /* xec = 0x10 */ "Hard L2 Tag ECC error", "Multiple hits on L2 tag", @@ -112,6 +101,28 @@ static const char * const f15h_cu_mce_desc[] = { "PRB address parity error" }; +static const char * const nb_mce_desc[] = { + "DRAM ECC error detected on the NB", + "CRC error detected on HT link", + "Link-defined sync error packets detected on HT link", + "HT Master abort", + "HT Target abort", + "Invalid GART PTE entry during GART table walk", + "Unsupported atomic RMW received from an IO link", + "Watchdog timeout due to lack of progress", + "DRAM ECC error detected on the NB", + "SVM DMA Exclusion Vector error", + "HT data error detected on link", + "Protocol error (link, L3, probe filter)", + "NB internal arrays parity error", + "DRAM addr/ctl signals parity error", + "IO link transmission error", + "L3 data cache ECC error", /* xec = 0x1c */ + "L3 cache tag error", + "L3 LRU parity bits error", + "ECC Error in the Probe Filter directory" +}; + static const char * const fr_ex_mce_desc[] = { "CPU Watchdog timer expire", "Wakeup array dest tag", @@ -125,7 +136,7 @@ static const char * const fr_ex_mce_desc[] = { "Physical register file AG0 port", "Physical register file AG1 port", "Flag register file", - "DE correctable error could not be corrected" + "DE error occurred" }; static bool f12h_dc_mce(u16 ec, u8 xec) @@ -255,10 +266,9 @@ static bool f15h_dc_mce(u16 ec, u8 xec) } else if (BUS_ERROR(ec)) { if (!xec) - pr_cont("during system linefill.\n"); + pr_cont("System Read Data Error.\n"); else - pr_cont(" Internal %s condition.\n", - ((xec == 1) ? "livelock" : "deadlock")); + pr_cont(" Internal error condition type %d.\n", xec); } else ret = false; @@ -355,7 +365,11 @@ static bool f15h_ic_mce(u16 ec, u8 xec) pr_cont("%s.\n", f15h_ic_mce_desc[xec-2]); break; - case 0x10 ... 0x14: + case 0x10: + pr_cont("%s.\n", f15h_ic_mce_desc[xec-4]); + break; + + case 0x11 ... 0x14: pr_cont("Decoder %s parity error.\n", f15h_ic_mce_desc[xec-4]); break; @@ -496,58 +510,31 @@ wrong_ls_mce: pr_emerg(HW_ERR "Corrupted LS MCE info?\n"); } -static bool k8_nb_mce(u16 ec, u8 xec) +void amd_decode_nb_mce(struct mce *m) { - bool ret = true; - - switch (xec) { - case 0x1: - pr_cont("CRC error detected on HT link.\n"); - break; - - case 0x5: - pr_cont("Invalid GART PTE entry during GART table walk.\n"); - break; - - case 0x6: - pr_cont("Unsupported atomic RMW received from an IO link.\n"); - break; - - case 0x0: - case 0x8: - if (boot_cpu_data.x86 == 0x11) - return false; - - pr_cont("DRAM ECC error detected on the NB.\n"); - break; - - case 0xd: - pr_cont("Parity error on the DRAM addr/ctl signals.\n"); - break; - - default: - ret = false; - break; - } + struct cpuinfo_x86 *c = &boot_cpu_data; + int node_id = amd_get_nb_id(m->extcpu); + u16 ec = EC(m->status); + u8 xec = XEC(m->status, 0x1f); + u8 offset = 0; - return ret; -} + pr_emerg(HW_ERR "Northbridge Error (node %d): ", node_id); -static bool f10h_nb_mce(u16 ec, u8 xec) -{ - bool ret = true; - u8 offset = 0; + switch (xec) { + case 0x0 ... 0xe: - if (k8_nb_mce(ec, xec)) - return true; + /* special handling for DRAM ECCs */ + if (xec == 0x0 || xec == 0x8) { + /* no ECCs on F11h */ + if (c->x86 == 0x11) + goto wrong_nb_mce; - switch(xec) { - case 0xa ... 0xc: - offset = 10; - break; + pr_cont("%s.\n", nb_mce_desc[xec]); - case 0xe: - offset = 11; + if (nb_bus_decoder) + nb_bus_decoder(node_id, m); + return; + } break; case 0xf: @@ -556,83 +543,25 @@ static bool f10h_nb_mce(u16 ec, u8 xec) else if (BUS_ERROR(ec)) pr_cont("DMA Exclusion Vector Table Walk error.\n"); else - ret = false; - - goto out; - break; + goto wrong_nb_mce; + return; case 0x19: if (boot_cpu_data.x86 == 0x15) pr_cont("Compute Unit Data Error.\n"); else - ret = false; - - goto out; - break; + goto wrong_nb_mce; + return; case 0x1c ... 0x1f: - offset = 24; + offset = 13; break; default: - ret = false; - - goto out; - break; - } - - pr_cont("%s.\n", f10h_nb_mce_desc[xec - offset]); - -out: - return ret; -} - -static bool nb_noop_mce(u16 ec, u8 xec) -{ - return false; -} - -void amd_decode_nb_mce(struct mce *m) -{ - struct cpuinfo_x86 *c = &boot_cpu_data; - int node_id = amd_get_nb_id(m->extcpu); - u16 ec = EC(m->status); - u8 xec = XEC(m->status, 0x1f); - - pr_emerg(HW_ERR "Northbridge Error (node %d): ", node_id); - - switch (xec) { - case 0x2: - pr_cont("Sync error (sync packets on HT link detected).\n"); - return; - - case 0x3: - pr_cont("HT Master abort.\n"); - return; - - case 0x4: - pr_cont("HT Target abort.\n"); - return; - - case 0x7: - pr_cont("NB Watchdog timeout.\n"); - return; - - case 0x9: - pr_cont("SVM DMA Exclusion Vector error.\n"); - return; - - default: - break; - } - - if (!fam_ops->nb_mce(ec, xec)) goto wrong_nb_mce; + } - if (c->x86 == 0xf || c->x86 == 0x10 || c->x86 == 0x15) - if ((xec == 0x8 || xec == 0x0) && nb_bus_decoder) - nb_bus_decoder(node_id, m); - + pr_cont("%s.\n", nb_mce_desc[xec - offset]); return; wrong_nb_mce: @@ -648,9 +577,6 @@ static void amd_decode_fr_mce(struct mce *m) if (c->x86 == 0xf || c->x86 == 0x11) goto wrong_fr_mce; - if (c->x86 != 0x15 && xec != 0x0) - goto wrong_fr_mce; - pr_emerg(HW_ERR "%s Error: ", (c->x86 == 0x15 ? "Execution Unit" : "FIROB")); @@ -841,39 +767,33 @@ static int __init mce_amd_init(void) case 0xf: fam_ops->dc_mce = k8_dc_mce; fam_ops->ic_mce = k8_ic_mce; - fam_ops->nb_mce = k8_nb_mce; break; case 0x10: fam_ops->dc_mce = f10h_dc_mce; fam_ops->ic_mce = k8_ic_mce; - fam_ops->nb_mce = f10h_nb_mce; break; case 0x11: fam_ops->dc_mce = k8_dc_mce; fam_ops->ic_mce = k8_ic_mce; - fam_ops->nb_mce = f10h_nb_mce; break; case 0x12: fam_ops->dc_mce = f12h_dc_mce; fam_ops->ic_mce = k8_ic_mce; - fam_ops->nb_mce = nb_noop_mce; break; case 0x14: nb_err_cpumask = 0x3; fam_ops->dc_mce = f14h_dc_mce; fam_ops->ic_mce = f14h_ic_mce; - fam_ops->nb_mce = nb_noop_mce; break; case 0x15: xec_mask = 0x1f; fam_ops->dc_mce = f15h_dc_mce; fam_ops->ic_mce = f15h_ic_mce; - fam_ops->nb_mce = f10h_nb_mce; break; default: diff --git a/drivers/edac/mce_amd.h b/drivers/edac/mce_amd.h index 0106747e240c..c6074c5cd1ef 100644 --- a/drivers/edac/mce_amd.h +++ b/drivers/edac/mce_amd.h @@ -69,12 +69,12 @@ enum rrrr_ids { R4_SNOOP, }; -extern const char *tt_msgs[]; -extern const char *ll_msgs[]; -extern const char *rrrr_msgs[]; -extern const char *pp_msgs[]; -extern const char *to_msgs[]; -extern const char *ii_msgs[]; +extern const char * const tt_msgs[]; +extern const char * const ll_msgs[]; +extern const char * const rrrr_msgs[]; +extern const char * const pp_msgs[]; +extern const char * const to_msgs[]; +extern const char * const ii_msgs[]; /* * per-family decoder ops @@ -82,7 +82,6 @@ extern const char *ii_msgs[]; struct amd_decoder_ops { bool (*dc_mce)(u16, u8); bool (*ic_mce)(u16, u8); - bool (*nb_mce)(u16, u8); }; void amd_report_gart_errors(bool); diff --git a/drivers/edac/mce_amd_inj.c b/drivers/edac/mce_amd_inj.c index 885e8ad8fdcf..66b5151c1080 100644 --- a/drivers/edac/mce_amd_inj.c +++ b/drivers/edac/mce_amd_inj.c @@ -11,6 +11,7 @@ */ #include <linux/kobject.h> +#include <linux/device.h> #include <linux/edac.h> #include <linux/module.h> #include <asm/mce.h> diff --git a/drivers/edac/r82600_edac.c b/drivers/edac/r82600_edac.c index e294e1b3616c..6d908ad72d64 100644 --- a/drivers/edac/r82600_edac.c +++ b/drivers/edac/r82600_edac.c @@ -373,7 +373,7 @@ static void __devexit r82600_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id r82600_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(r82600_pci_tbl) = { { PCI_DEVICE(PCI_VENDOR_ID_RADISYS, R82600_BRIDGE_ID) }, diff --git a/drivers/edac/sb_edac.c b/drivers/edac/sb_edac.c index 1dc118d83cc6..3a605f777712 100644 --- a/drivers/edac/sb_edac.c +++ b/drivers/edac/sb_edac.c @@ -367,7 +367,7 @@ static const struct pci_id_table pci_dev_descr_sbridge_table[] = { /* * pci_device_id table for which devices we are looking for */ -static const struct pci_device_id sbridge_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(sbridge_pci_tbl) = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SBRIDGE_IMC_TA)}, {0,} /* 0 terminated list. */ }; diff --git a/drivers/edac/x38_edac.c b/drivers/edac/x38_edac.c index b6f47de152f3..a438297389e5 100644 --- a/drivers/edac/x38_edac.c +++ b/drivers/edac/x38_edac.c @@ -440,7 +440,7 @@ static void __devexit x38_remove_one(struct pci_dev *pdev) edac_mc_free(mci); } -static const struct pci_device_id x38_pci_tbl[] __devinitdata = { +static DEFINE_PCI_DEVICE_TABLE(x38_pci_tbl) = { { PCI_VEND_DEV(INTEL, X38_HB), PCI_ANY_ID, PCI_ANY_ID, 0, 0, X38}, diff --git a/drivers/gpio/gpio-ep93xx.c b/drivers/gpio/gpio-ep93xx.c index 1c0fc3756cb1..4ca5642e9776 100644 --- a/drivers/gpio/gpio-ep93xx.c +++ b/drivers/gpio/gpio-ep93xx.c @@ -378,13 +378,6 @@ static int __devinit ep93xx_gpio_probe(struct platform_device *pdev) } ep93xx_gpio->mmio_base = mmio; - /* Default all ports to GPIO */ - ep93xx_devcfg_set_bits(EP93XX_SYSCON_DEVCFG_KEYS | - EP93XX_SYSCON_DEVCFG_GONK | - EP93XX_SYSCON_DEVCFG_EONIDE | - EP93XX_SYSCON_DEVCFG_GONIDE | - EP93XX_SYSCON_DEVCFG_HONIDE); - for (i = 0; i < ARRAY_SIZE(ep93xx_gpio_banks); i++) { struct bgpio_chip *bgc = &ep93xx_gpio->bgc[i]; struct ep93xx_gpio_bank *bank = &ep93xx_gpio_banks[i]; diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c index 0b0562979171..f49bd6f47a50 100644 --- a/drivers/gpio/gpio-omap.c +++ b/drivers/gpio/gpio-omap.c @@ -21,6 +21,7 @@ #include <linux/io.h> #include <linux/slab.h> #include <linux/pm_runtime.h> +#include <linux/pm.h> #include <mach/hardware.h> #include <asm/irq.h> @@ -28,19 +29,36 @@ #include <asm/gpio.h> #include <asm/mach/irq.h> +#define OFF_MODE 1 + +static LIST_HEAD(omap_gpio_list); + +struct gpio_regs { + u32 irqenable1; + u32 irqenable2; + u32 wake_en; + u32 ctrl; + u32 oe; + u32 leveldetect0; + u32 leveldetect1; + u32 risingdetect; + u32 fallingdetect; + u32 dataout; + u32 debounce; + u32 debounce_en; +}; + struct gpio_bank { + struct list_head node; unsigned long pbase; void __iomem *base; u16 irq; u16 virtual_irq_start; - int method; u32 suspend_wakeup; -#if defined(CONFIG_ARCH_OMAP16XX) || defined(CONFIG_ARCH_OMAP2PLUS) u32 saved_wakeup; -#endif u32 non_wakeup_gpios; u32 enabled_non_wakeup_gpios; - + struct gpio_regs context; u32 saved_datain; u32 saved_fallingdetect; u32 saved_risingdetect; @@ -51,44 +69,27 @@ struct gpio_bank { struct clk *dbck; u32 mod_usage; u32 dbck_enable_mask; + bool dbck_enabled; struct device *dev; + bool is_mpuio; bool dbck_flag; + bool loses_context; int stride; u32 width; + int context_loss_count; + u16 id; + int power_mode; + bool workaround_enabled; void (*set_dataout)(struct gpio_bank *bank, int gpio, int enable); + int (*get_context_loss_count)(struct device *dev); struct omap_gpio_reg_offs *regs; }; -#ifdef CONFIG_ARCH_OMAP3 -struct omap3_gpio_regs { - u32 irqenable1; - u32 irqenable2; - u32 wake_en; - u32 ctrl; - u32 oe; - u32 leveldetect0; - u32 leveldetect1; - u32 risingdetect; - u32 fallingdetect; - u32 dataout; -}; - -static struct omap3_gpio_regs gpio_context[OMAP34XX_NR_GPIOS]; -#endif - -/* - * TODO: Cleanup gpio_bank usage as it is having information - * related to all instances of the device - */ -static struct gpio_bank *gpio_bank; - -/* TODO: Analyze removing gpio_bank_count usage from driver code */ -int gpio_bank_count; - #define GPIO_INDEX(bank, gpio) (gpio % bank->width) #define GPIO_BIT(bank, gpio) (1 << GPIO_INDEX(bank, gpio)) +#define GPIO_MOD_CTRL_BIT BIT(0) static void _set_gpio_direction(struct gpio_bank *bank, int gpio, int is_input) { @@ -102,6 +103,7 @@ static void _set_gpio_direction(struct gpio_bank *bank, int gpio, int is_input) else l &= ~(1 << gpio); __raw_writel(l, reg); + bank->context.oe = l; } @@ -132,6 +134,7 @@ static void _set_gpio_dataout_mask(struct gpio_bank *bank, int gpio, int enable) else l &= ~gpio_bit; __raw_writel(l, reg); + bank->context.dataout = l; } static int _get_gpio_datain(struct gpio_bank *bank, int gpio) @@ -160,6 +163,22 @@ static inline void _gpio_rmw(void __iomem *base, u32 reg, u32 mask, bool set) __raw_writel(l, base + reg); } +static inline void _gpio_dbck_enable(struct gpio_bank *bank) +{ + if (bank->dbck_enable_mask && !bank->dbck_enabled) { + clk_enable(bank->dbck); + bank->dbck_enabled = true; + } +} + +static inline void _gpio_dbck_disable(struct gpio_bank *bank) +{ + if (bank->dbck_enable_mask && bank->dbck_enabled) { + clk_disable(bank->dbck); + bank->dbck_enabled = false; + } +} + /** * _set_gpio_debounce - low level gpio debounce time * @bank: the gpio bank we're acting upon @@ -188,70 +207,74 @@ static void _set_gpio_debounce(struct gpio_bank *bank, unsigned gpio, l = GPIO_BIT(bank, gpio); + clk_enable(bank->dbck); reg = bank->base + bank->regs->debounce; __raw_writel(debounce, reg); reg = bank->base + bank->regs->debounce_en; val = __raw_readl(reg); - if (debounce) { + if (debounce) val |= l; - clk_enable(bank->dbck); - } else { + else val &= ~l; - clk_disable(bank->dbck); - } bank->dbck_enable_mask = val; __raw_writel(val, reg); + clk_disable(bank->dbck); + /* + * Enable debounce clock per module. + * This call is mandatory because in omap_gpio_request() when + * *_runtime_get_sync() is called, _gpio_dbck_enable() within + * runtime callbck fails to turn on dbck because dbck_enable_mask + * used within _gpio_dbck_enable() is still not initialized at + * that point. Therefore we have to enable dbck here. + */ + _gpio_dbck_enable(bank); + if (bank->dbck_enable_mask) { + bank->context.debounce = debounce; + bank->context.debounce_en = val; + } } -#ifdef CONFIG_ARCH_OMAP2PLUS -static inline void set_24xx_gpio_triggering(struct gpio_bank *bank, int gpio, +static inline void set_gpio_trigger(struct gpio_bank *bank, int gpio, int trigger) { void __iomem *base = bank->base; u32 gpio_bit = 1 << gpio; - if (cpu_is_omap44xx()) { - _gpio_rmw(base, OMAP4_GPIO_LEVELDETECT0, gpio_bit, - trigger & IRQ_TYPE_LEVEL_LOW); - _gpio_rmw(base, OMAP4_GPIO_LEVELDETECT1, gpio_bit, - trigger & IRQ_TYPE_LEVEL_HIGH); - _gpio_rmw(base, OMAP4_GPIO_RISINGDETECT, gpio_bit, - trigger & IRQ_TYPE_EDGE_RISING); - _gpio_rmw(base, OMAP4_GPIO_FALLINGDETECT, gpio_bit, - trigger & IRQ_TYPE_EDGE_FALLING); - } else { - _gpio_rmw(base, OMAP24XX_GPIO_LEVELDETECT0, gpio_bit, - trigger & IRQ_TYPE_LEVEL_LOW); - _gpio_rmw(base, OMAP24XX_GPIO_LEVELDETECT1, gpio_bit, - trigger & IRQ_TYPE_LEVEL_HIGH); - _gpio_rmw(base, OMAP24XX_GPIO_RISINGDETECT, gpio_bit, - trigger & IRQ_TYPE_EDGE_RISING); - _gpio_rmw(base, OMAP24XX_GPIO_FALLINGDETECT, gpio_bit, - trigger & IRQ_TYPE_EDGE_FALLING); - } + _gpio_rmw(base, bank->regs->leveldetect0, gpio_bit, + trigger & IRQ_TYPE_LEVEL_LOW); + _gpio_rmw(base, bank->regs->leveldetect1, gpio_bit, + trigger & IRQ_TYPE_LEVEL_HIGH); + _gpio_rmw(base, bank->regs->risingdetect, gpio_bit, + trigger & IRQ_TYPE_EDGE_RISING); + _gpio_rmw(base, bank->regs->fallingdetect, gpio_bit, + trigger & IRQ_TYPE_EDGE_FALLING); + + bank->context.leveldetect0 = + __raw_readl(bank->base + bank->regs->leveldetect0); + bank->context.leveldetect1 = + __raw_readl(bank->base + bank->regs->leveldetect1); + bank->context.risingdetect = + __raw_readl(bank->base + bank->regs->risingdetect); + bank->context.fallingdetect = + __raw_readl(bank->base + bank->regs->fallingdetect); + if (likely(!(bank->non_wakeup_gpios & gpio_bit))) { - if (cpu_is_omap44xx()) { - _gpio_rmw(base, OMAP4_GPIO_IRQWAKEN0, gpio_bit, - trigger != 0); - } else { - /* - * GPIO wakeup request can only be generated on edge - * transitions - */ - if (trigger & IRQ_TYPE_EDGE_BOTH) - __raw_writel(1 << gpio, bank->base - + OMAP24XX_GPIO_SETWKUENA); - else - __raw_writel(1 << gpio, bank->base - + OMAP24XX_GPIO_CLEARWKUENA); - } + _gpio_rmw(base, bank->regs->wkup_en, gpio_bit, trigger != 0); + bank->context.wake_en = + __raw_readl(bank->base + bank->regs->wkup_en); } + /* This part needs to be executed always for OMAP{34xx, 44xx} */ - if (cpu_is_omap34xx() || cpu_is_omap44xx() || - (bank->non_wakeup_gpios & gpio_bit)) { + if (!bank->regs->irqctrl) { + /* On omap24xx proceed only when valid GPIO bit is set */ + if (bank->non_wakeup_gpios) { + if (!(bank->non_wakeup_gpios & gpio_bit)) + goto exit; + } + /* * Log the edge gpio and manually trigger the IRQ * after resume if the input level changes @@ -264,17 +287,11 @@ static inline void set_24xx_gpio_triggering(struct gpio_bank *bank, int gpio, bank->enabled_non_wakeup_gpios &= ~gpio_bit; } - if (cpu_is_omap44xx()) { - bank->level_mask = - __raw_readl(bank->base + OMAP4_GPIO_LEVELDETECT0) | - __raw_readl(bank->base + OMAP4_GPIO_LEVELDETECT1); - } else { - bank->level_mask = - __raw_readl(bank->base + OMAP24XX_GPIO_LEVELDETECT0) | - __raw_readl(bank->base + OMAP24XX_GPIO_LEVELDETECT1); - } +exit: + bank->level_mask = + __raw_readl(bank->base + bank->regs->leveldetect0) | + __raw_readl(bank->base + bank->regs->leveldetect1); } -#endif #ifdef CONFIG_ARCH_OMAP1 /* @@ -286,23 +303,10 @@ static void _toggle_gpio_edge_triggering(struct gpio_bank *bank, int gpio) void __iomem *reg = bank->base; u32 l = 0; - switch (bank->method) { - case METHOD_MPUIO: - reg += OMAP_MPUIO_GPIO_INT_EDGE / bank->stride; - break; -#ifdef CONFIG_ARCH_OMAP15XX - case METHOD_GPIO_1510: - reg += OMAP1510_GPIO_INT_CONTROL; - break; -#endif -#if defined(CONFIG_ARCH_OMAP730) || defined(CONFIG_ARCH_OMAP850) - case METHOD_GPIO_7XX: - reg += OMAP7XX_GPIO_INT_CONTROL; - break; -#endif - default: + if (!bank->regs->irqctrl) return; - } + + reg += bank->regs->irqctrl; l = __raw_readl(reg); if ((l >> gpio) & 1) @@ -312,31 +316,21 @@ static void _toggle_gpio_edge_triggering(struct gpio_bank *bank, int gpio) __raw_writel(l, reg); } +#else +static void _toggle_gpio_edge_triggering(struct gpio_bank *bank, int gpio) {} #endif static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, int trigger) { void __iomem *reg = bank->base; + void __iomem *base = bank->base; u32 l = 0; - switch (bank->method) { -#ifdef CONFIG_ARCH_OMAP1 - case METHOD_MPUIO: - reg += OMAP_MPUIO_GPIO_INT_EDGE / bank->stride; - l = __raw_readl(reg); - if ((trigger & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) - bank->toggle_mask |= 1 << gpio; - if (trigger & IRQ_TYPE_EDGE_RISING) - l |= 1 << gpio; - else if (trigger & IRQ_TYPE_EDGE_FALLING) - l &= ~(1 << gpio); - else - goto bad; - break; -#endif -#ifdef CONFIG_ARCH_OMAP15XX - case METHOD_GPIO_1510: - reg += OMAP1510_GPIO_INT_CONTROL; + if (bank->regs->leveldetect0 && bank->regs->wkup_en) { + set_gpio_trigger(bank, gpio, trigger); + } else if (bank->regs->irqctrl) { + reg += bank->regs->irqctrl; + l = __raw_readl(reg); if ((trigger & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) bank->toggle_mask |= 1 << gpio; @@ -345,15 +339,15 @@ static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, int trigger) else if (trigger & IRQ_TYPE_EDGE_FALLING) l &= ~(1 << gpio); else - goto bad; - break; -#endif -#ifdef CONFIG_ARCH_OMAP16XX - case METHOD_GPIO_1610: + return -EINVAL; + + __raw_writel(l, reg); + } else if (bank->regs->edgectrl1) { if (gpio & 0x08) - reg += OMAP1610_GPIO_EDGE_CTRL2; + reg += bank->regs->edgectrl2; else - reg += OMAP1610_GPIO_EDGE_CTRL1; + reg += bank->regs->edgectrl1; + gpio &= 0x07; l = __raw_readl(reg); l &= ~(3 << (gpio << 1)); @@ -361,40 +355,14 @@ static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, int trigger) l |= 2 << (gpio << 1); if (trigger & IRQ_TYPE_EDGE_FALLING) l |= 1 << (gpio << 1); - if (trigger) - /* Enable wake-up during idle for dynamic tick */ - __raw_writel(1 << gpio, bank->base + OMAP1610_GPIO_SET_WAKEUPENA); - else - __raw_writel(1 << gpio, bank->base + OMAP1610_GPIO_CLEAR_WAKEUPENA); - break; -#endif -#if defined(CONFIG_ARCH_OMAP730) || defined(CONFIG_ARCH_OMAP850) - case METHOD_GPIO_7XX: - reg += OMAP7XX_GPIO_INT_CONTROL; - l = __raw_readl(reg); - if ((trigger & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) - bank->toggle_mask |= 1 << gpio; - if (trigger & IRQ_TYPE_EDGE_RISING) - l |= 1 << gpio; - else if (trigger & IRQ_TYPE_EDGE_FALLING) - l &= ~(1 << gpio); - else - goto bad; - break; -#endif -#ifdef CONFIG_ARCH_OMAP2PLUS - case METHOD_GPIO_24XX: - case METHOD_GPIO_44XX: - set_24xx_gpio_triggering(bank, gpio, trigger); - return 0; -#endif - default: - goto bad; + + /* Enable wake-up during idle for dynamic tick */ + _gpio_rmw(base, bank->regs->wkup_en, 1 << gpio, trigger); + bank->context.wake_en = + __raw_readl(bank->base + bank->regs->wkup_en); + __raw_writel(l, reg); } - __raw_writel(l, reg); return 0; -bad: - return -EINVAL; } static int gpio_irq_type(struct irq_data *d, unsigned type) @@ -412,12 +380,12 @@ static int gpio_irq_type(struct irq_data *d, unsigned type) if (type & ~IRQ_TYPE_SENSE_MASK) return -EINVAL; - /* OMAP1 allows only only edge triggering */ - if (!cpu_class_is_omap2() - && (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH))) + bank = irq_data_get_irq_chip_data(d); + + if (!bank->regs->leveldetect0 && + (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH))) return -EINVAL; - bank = irq_data_get_irq_chip_data(d); spin_lock_irqsave(&bank->lock, flags); retval = _set_gpio_triggering(bank, GPIO_INDEX(bank, gpio), type); spin_unlock_irqrestore(&bank->lock, flags); @@ -484,6 +452,7 @@ static void _enable_gpio_irqbank(struct gpio_bank *bank, int gpio_mask) } __raw_writel(l, reg); + bank->context.irqenable1 = l; } static void _disable_gpio_irqbank(struct gpio_bank *bank, int gpio_mask) @@ -504,6 +473,7 @@ static void _disable_gpio_irqbank(struct gpio_bank *bank, int gpio_mask) } __raw_writel(l, reg); + bank->context.irqenable1 = l; } static inline void _set_gpio_irqenable(struct gpio_bank *bank, int gpio, int enable) @@ -567,38 +537,39 @@ static int omap_gpio_request(struct gpio_chip *chip, unsigned offset) struct gpio_bank *bank = container_of(chip, struct gpio_bank, chip); unsigned long flags; - spin_lock_irqsave(&bank->lock, flags); + /* + * If this is the first gpio_request for the bank, + * enable the bank module. + */ + if (!bank->mod_usage) + pm_runtime_get_sync(bank->dev); + spin_lock_irqsave(&bank->lock, flags); /* Set trigger to none. You need to enable the desired trigger with * request_irq() or set_irq_type(). */ _set_gpio_triggering(bank, offset, IRQ_TYPE_NONE); -#ifdef CONFIG_ARCH_OMAP15XX - if (bank->method == METHOD_GPIO_1510) { - void __iomem *reg; + if (bank->regs->pinctrl) { + void __iomem *reg = bank->base + bank->regs->pinctrl; /* Claim the pin for MPU */ - reg = bank->base + OMAP1510_GPIO_PIN_CONTROL; __raw_writel(__raw_readl(reg) | (1 << offset), reg); } -#endif - if (!cpu_class_is_omap1()) { - if (!bank->mod_usage) { - void __iomem *reg = bank->base; - u32 ctrl; - - if (cpu_is_omap24xx() || cpu_is_omap34xx()) - reg += OMAP24XX_GPIO_CTRL; - else if (cpu_is_omap44xx()) - reg += OMAP4_GPIO_CTRL; - ctrl = __raw_readl(reg); - /* Module is enabled, clocks are not gated */ - ctrl &= 0xFFFFFFFE; - __raw_writel(ctrl, reg); - } - bank->mod_usage |= 1 << offset; + + if (bank->regs->ctrl && !bank->mod_usage) { + void __iomem *reg = bank->base + bank->regs->ctrl; + u32 ctrl; + + ctrl = __raw_readl(reg); + /* Module is enabled, clocks are not gated */ + ctrl &= ~GPIO_MOD_CTRL_BIT; + __raw_writel(ctrl, reg); + bank->context.ctrl = ctrl; } + + bank->mod_usage |= 1 << offset; + spin_unlock_irqrestore(&bank->lock, flags); return 0; @@ -607,48 +578,40 @@ static int omap_gpio_request(struct gpio_chip *chip, unsigned offset) static void omap_gpio_free(struct gpio_chip *chip, unsigned offset) { struct gpio_bank *bank = container_of(chip, struct gpio_bank, chip); + void __iomem *base = bank->base; unsigned long flags; spin_lock_irqsave(&bank->lock, flags); -#ifdef CONFIG_ARCH_OMAP16XX - if (bank->method == METHOD_GPIO_1610) { - /* Disable wake-up during idle for dynamic tick */ - void __iomem *reg = bank->base + OMAP1610_GPIO_CLEAR_WAKEUPENA; - __raw_writel(1 << offset, reg); - } -#endif -#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3) - if (bank->method == METHOD_GPIO_24XX) { - /* Disable wake-up during idle for dynamic tick */ - void __iomem *reg = bank->base + OMAP24XX_GPIO_CLEARWKUENA; - __raw_writel(1 << offset, reg); - } -#endif -#ifdef CONFIG_ARCH_OMAP4 - if (bank->method == METHOD_GPIO_44XX) { + + if (bank->regs->wkup_en) { /* Disable wake-up during idle for dynamic tick */ - void __iomem *reg = bank->base + OMAP4_GPIO_IRQWAKEN0; - __raw_writel(1 << offset, reg); + _gpio_rmw(base, bank->regs->wkup_en, 1 << offset, 0); + bank->context.wake_en = + __raw_readl(bank->base + bank->regs->wkup_en); } -#endif - if (!cpu_class_is_omap1()) { - bank->mod_usage &= ~(1 << offset); - if (!bank->mod_usage) { - void __iomem *reg = bank->base; - u32 ctrl; - - if (cpu_is_omap24xx() || cpu_is_omap34xx()) - reg += OMAP24XX_GPIO_CTRL; - else if (cpu_is_omap44xx()) - reg += OMAP4_GPIO_CTRL; - ctrl = __raw_readl(reg); - /* Module is disabled, clocks are gated */ - ctrl |= 1; - __raw_writel(ctrl, reg); - } + + bank->mod_usage &= ~(1 << offset); + + if (bank->regs->ctrl && !bank->mod_usage) { + void __iomem *reg = bank->base + bank->regs->ctrl; + u32 ctrl; + + ctrl = __raw_readl(reg); + /* Module is disabled, clocks are gated */ + ctrl |= GPIO_MOD_CTRL_BIT; + __raw_writel(ctrl, reg); + bank->context.ctrl = ctrl; } + _reset_gpio(bank, bank->chip.base + offset); spin_unlock_irqrestore(&bank->lock, flags); + + /* + * If this is the last gpio to be freed in the bank, + * disable the bank module. + */ + if (!bank->mod_usage) + pm_runtime_put(bank->dev); } /* @@ -674,6 +637,7 @@ static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc) bank = irq_get_handler_data(irq); isr_reg = bank->base + bank->regs->irqstatus; + pm_runtime_get_sync(bank->dev); if (WARN_ON(!isr_reg)) goto exit; @@ -685,12 +649,8 @@ static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc) enabled = _get_gpio_irqbank_mask(bank); isr_saved = isr = __raw_readl(isr_reg) & enabled; - if (cpu_is_omap15xx() && (bank->method == METHOD_MPUIO)) - isr &= 0x0000ffff; - - if (cpu_class_is_omap2()) { + if (bank->level_mask) level_mask = bank->level_mask & enabled; - } /* clear edge sensitive interrupts before handler(s) are called so that we don't miss any interrupt occurred while @@ -718,7 +678,6 @@ static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc) if (!(isr & 1)) continue; -#ifdef CONFIG_ARCH_OMAP1 /* * Some chips can't respond to both rising and falling * at the same time. If this irq was requested with @@ -728,7 +687,6 @@ static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc) */ if (bank->toggle_mask & (1 << gpio_index)) _toggle_gpio_edge_triggering(bank, gpio_index); -#endif generic_handle_irq(gpio_irq); } @@ -740,6 +698,7 @@ static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc) exit: if (!unmasked) chained_irq_exit(chip, desc); + pm_runtime_put(bank->dev); } static void gpio_irq_shutdown(struct irq_data *d) @@ -808,14 +767,6 @@ static struct irq_chip gpio_irq_chip = { /*---------------------------------------------------------------------*/ -#ifdef CONFIG_ARCH_OMAP1 - -#define bank_is_mpuio(bank) ((bank)->method == METHOD_MPUIO) - -#ifdef CONFIG_ARCH_OMAP16XX - -#include <linux/platform_device.h> - static int omap_mpuio_suspend_noirq(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -869,32 +820,16 @@ static struct platform_device omap_mpuio_device = { /* could list the /proc/iomem resources */ }; -static inline void mpuio_init(void) +static inline void mpuio_init(struct gpio_bank *bank) { - struct gpio_bank *bank = &gpio_bank[0]; platform_set_drvdata(&omap_mpuio_device, bank); if (platform_driver_register(&omap_mpuio_driver) == 0) (void) platform_device_register(&omap_mpuio_device); } -#else -static inline void mpuio_init(void) {} -#endif /* 16xx */ - -#else - -#define bank_is_mpuio(bank) 0 -static inline void mpuio_init(void) {} - -#endif - /*---------------------------------------------------------------------*/ -/* REVISIT these are stupid implementations! replace by ones that - * don't switch on METHOD_* and which mostly avoid spinlocks - */ - static int gpio_input(struct gpio_chip *chip, unsigned offset) { struct gpio_bank *bank; @@ -1007,78 +942,32 @@ static void __init omap_gpio_show_rev(struct gpio_bank *bank) */ static struct lock_class_key gpio_lock_class; -static inline int init_gpio_info(struct platform_device *pdev) +static void omap_gpio_mod_init(struct gpio_bank *bank) { - /* TODO: Analyze removing gpio_bank_count usage from driver code */ - gpio_bank = kzalloc(gpio_bank_count * sizeof(struct gpio_bank), - GFP_KERNEL); - if (!gpio_bank) { - dev_err(&pdev->dev, "Memory alloc failed for gpio_bank\n"); - return -ENOMEM; - } - return 0; -} + void __iomem *base = bank->base; + u32 l = 0xffffffff; -/* TODO: Cleanup cpu_is_* checks */ -static void omap_gpio_mod_init(struct gpio_bank *bank, int id) -{ - if (cpu_class_is_omap2()) { - if (cpu_is_omap44xx()) { - __raw_writel(0xffffffff, bank->base + - OMAP4_GPIO_IRQSTATUSCLR0); - __raw_writel(0x00000000, bank->base + - OMAP4_GPIO_DEBOUNCENABLE); - /* Initialize interface clk ungated, module enabled */ - __raw_writel(0, bank->base + OMAP4_GPIO_CTRL); - } else if (cpu_is_omap34xx()) { - __raw_writel(0x00000000, bank->base + - OMAP24XX_GPIO_IRQENABLE1); - __raw_writel(0xffffffff, bank->base + - OMAP24XX_GPIO_IRQSTATUS1); - __raw_writel(0x00000000, bank->base + - OMAP24XX_GPIO_DEBOUNCE_EN); - - /* Initialize interface clk ungated, module enabled */ - __raw_writel(0, bank->base + OMAP24XX_GPIO_CTRL); - } else if (cpu_is_omap24xx()) { - static const u32 non_wakeup_gpios[] = { - 0xe203ffc0, 0x08700040 - }; - if (id < ARRAY_SIZE(non_wakeup_gpios)) - bank->non_wakeup_gpios = non_wakeup_gpios[id]; - } - } else if (cpu_class_is_omap1()) { - if (bank_is_mpuio(bank)) - __raw_writew(0xffff, bank->base + - OMAP_MPUIO_GPIO_MASKIT / bank->stride); - if (cpu_is_omap15xx() && bank->method == METHOD_GPIO_1510) { - __raw_writew(0xffff, bank->base - + OMAP1510_GPIO_INT_MASK); - __raw_writew(0x0000, bank->base - + OMAP1510_GPIO_INT_STATUS); - } - if (cpu_is_omap16xx() && bank->method == METHOD_GPIO_1610) { - __raw_writew(0x0000, bank->base - + OMAP1610_GPIO_IRQENABLE1); - __raw_writew(0xffff, bank->base - + OMAP1610_GPIO_IRQSTATUS1); - __raw_writew(0x0014, bank->base - + OMAP1610_GPIO_SYSCONFIG); + if (bank->width == 16) + l = 0xffff; - /* - * Enable system clock for GPIO module. - * The CAM_CLK_CTRL *is* really the right place. - */ - omap_writel(omap_readl(ULPD_CAM_CLK_CTRL) | 0x04, - ULPD_CAM_CLK_CTRL); - } - if (cpu_is_omap7xx() && bank->method == METHOD_GPIO_7XX) { - __raw_writel(0xffffffff, bank->base - + OMAP7XX_GPIO_INT_MASK); - __raw_writel(0x00000000, bank->base - + OMAP7XX_GPIO_INT_STATUS); - } + if (bank->is_mpuio) { + __raw_writel(l, bank->base + bank->regs->irqenable); + return; } + + _gpio_rmw(base, bank->regs->irqenable, l, bank->regs->irqenable_inv); + _gpio_rmw(base, bank->regs->irqstatus, l, + bank->regs->irqenable_inv == false); + _gpio_rmw(base, bank->regs->irqenable, l, bank->regs->debounce_en != 0); + _gpio_rmw(base, bank->regs->irqenable, l, bank->regs->ctrl != 0); + if (bank->regs->debounce_en) + _gpio_rmw(base, bank->regs->debounce_en, 0, 1); + + /* Save OE default value (0xffffffff) in the context */ + bank->context.oe = __raw_readl(bank->base + bank->regs->direction); + /* Initialize interface clk ungated, module enabled */ + if (bank->regs->ctrl) + _gpio_rmw(base, bank->regs->ctrl, 0, 1); } static __init void @@ -1101,8 +990,8 @@ omap_mpuio_alloc_gc(struct gpio_bank *bank, unsigned int irq_start, ct->chip.irq_mask = irq_gc_mask_set_bit; ct->chip.irq_unmask = irq_gc_mask_clr_bit; ct->chip.irq_set_type = gpio_irq_type; - /* REVISIT: assuming only 16xx supports MPUIO wake events */ - if (cpu_is_omap16xx()) + + if (bank->regs->wkup_en) ct->chip.irq_set_wake = gpio_wake_enable, ct->regs.mask = OMAP_MPUIO_GPIO_INT / bank->stride; @@ -1115,7 +1004,6 @@ static void __devinit omap_gpio_chip_init(struct gpio_bank *bank) int j; static int gpio; - bank->mod_usage = 0; /* * REVISIT eventually switch from OMAP-specific gpio structs * over to the generic ones @@ -1128,11 +1016,10 @@ static void __devinit omap_gpio_chip_init(struct gpio_bank *bank) bank->chip.set_debounce = gpio_debounce; bank->chip.set = gpio_set; bank->chip.to_irq = gpio_2irq; - if (bank_is_mpuio(bank)) { + if (bank->is_mpuio) { bank->chip.label = "mpuio"; -#ifdef CONFIG_ARCH_OMAP16XX - bank->chip.dev = &omap_mpuio_device.dev; -#endif + if (bank->regs->wkup_en) + bank->chip.dev = &omap_mpuio_device.dev; bank->chip.base = OMAP_MPUIO(0); } else { bank->chip.label = "gpio"; @@ -1147,7 +1034,7 @@ static void __devinit omap_gpio_chip_init(struct gpio_bank *bank) j < bank->virtual_irq_start + bank->width; j++) { irq_set_lockdep_class(j, &gpio_lock_class); irq_set_chip_data(j, bank); - if (bank_is_mpuio(bank)) { + if (bank->is_mpuio) { omap_mpuio_alloc_gc(bank, j, bank->width); } else { irq_set_chip(j, &gpio_irq_chip); @@ -1161,42 +1048,44 @@ static void __devinit omap_gpio_chip_init(struct gpio_bank *bank) static int __devinit omap_gpio_probe(struct platform_device *pdev) { - static int gpio_init_done; struct omap_gpio_platform_data *pdata; struct resource *res; - int id; struct gpio_bank *bank; + int ret = 0; - if (!pdev->dev.platform_data) - return -EINVAL; - - pdata = pdev->dev.platform_data; - - if (!gpio_init_done) { - int ret; - - ret = init_gpio_info(pdev); - if (ret) - return ret; + if (!pdev->dev.platform_data) { + ret = -EINVAL; + goto err_exit; } - id = pdev->id; - bank = &gpio_bank[id]; + bank = kzalloc(sizeof(struct gpio_bank), GFP_KERNEL); + if (!bank) { + dev_err(&pdev->dev, "Memory alloc failed for gpio_bank\n"); + ret = -ENOMEM; + goto err_exit; + } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (unlikely(!res)) { - dev_err(&pdev->dev, "GPIO Bank %i Invalid IRQ resource\n", id); - return -ENODEV; + dev_err(&pdev->dev, "GPIO Bank %i Invalid IRQ resource\n", + pdev->id); + ret = -ENODEV; + goto err_free; } bank->irq = res->start; + bank->id = pdev->id; + + pdata = pdev->dev.platform_data; bank->virtual_irq_start = pdata->virtual_irq_start; - bank->method = pdata->bank_type; bank->dev = &pdev->dev; bank->dbck_flag = pdata->dbck_flag; bank->stride = pdata->bank_stride; bank->width = pdata->bank_width; - + bank->is_mpuio = pdata->is_mpuio; + bank->non_wakeup_gpios = pdata->non_wakeup_gpios; + bank->loses_context = pdata->loses_context; + bank->get_context_loss_count = pdata->get_context_loss_count; bank->regs = pdata->regs; if (bank->regs->set_dataout && bank->regs->clr_dataout) @@ -1209,369 +1098,310 @@ static int __devinit omap_gpio_probe(struct platform_device *pdev) /* Static mapping, never released */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (unlikely(!res)) { - dev_err(&pdev->dev, "GPIO Bank %i Invalid mem resource\n", id); - return -ENODEV; + dev_err(&pdev->dev, "GPIO Bank %i Invalid mem resource\n", + pdev->id); + ret = -ENODEV; + goto err_free; } bank->base = ioremap(res->start, resource_size(res)); if (!bank->base) { - dev_err(&pdev->dev, "Could not ioremap gpio bank%i\n", id); - return -ENOMEM; + dev_err(&pdev->dev, "Could not ioremap gpio bank%i\n", + pdev->id); + ret = -ENOMEM; + goto err_free; } + platform_set_drvdata(pdev, bank); + pm_runtime_enable(bank->dev); + pm_runtime_irq_safe(bank->dev); pm_runtime_get_sync(bank->dev); - omap_gpio_mod_init(bank, id); + if (bank->is_mpuio) + mpuio_init(bank); + + omap_gpio_mod_init(bank); omap_gpio_chip_init(bank); omap_gpio_show_rev(bank); - if (!gpio_init_done) - gpio_init_done = 1; + pm_runtime_put(bank->dev); - return 0; + list_add_tail(&bank->node, &omap_gpio_list); + + return ret; + +err_free: + kfree(bank); +err_exit: + return ret; } -#if defined(CONFIG_ARCH_OMAP16XX) || defined(CONFIG_ARCH_OMAP2PLUS) -static int omap_gpio_suspend(void) +#ifdef CONFIG_ARCH_OMAP2PLUS + +#if defined(CONFIG_PM_SLEEP) +static int omap_gpio_suspend(struct device *dev) { - int i; + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + void __iomem *base = bank->base; + void __iomem *wakeup_enable; + unsigned long flags; - if (!cpu_class_is_omap2() && !cpu_is_omap16xx()) + if (!bank->mod_usage || !bank->loses_context) return 0; - for (i = 0; i < gpio_bank_count; i++) { - struct gpio_bank *bank = &gpio_bank[i]; - void __iomem *wake_status; - void __iomem *wake_clear; - void __iomem *wake_set; - unsigned long flags; - - switch (bank->method) { -#ifdef CONFIG_ARCH_OMAP16XX - case METHOD_GPIO_1610: - wake_status = bank->base + OMAP1610_GPIO_WAKEUPENABLE; - wake_clear = bank->base + OMAP1610_GPIO_CLEAR_WAKEUPENA; - wake_set = bank->base + OMAP1610_GPIO_SET_WAKEUPENA; - break; -#endif -#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3) - case METHOD_GPIO_24XX: - wake_status = bank->base + OMAP24XX_GPIO_WAKE_EN; - wake_clear = bank->base + OMAP24XX_GPIO_CLEARWKUENA; - wake_set = bank->base + OMAP24XX_GPIO_SETWKUENA; - break; -#endif -#ifdef CONFIG_ARCH_OMAP4 - case METHOD_GPIO_44XX: - wake_status = bank->base + OMAP4_GPIO_IRQWAKEN0; - wake_clear = bank->base + OMAP4_GPIO_IRQWAKEN0; - wake_set = bank->base + OMAP4_GPIO_IRQWAKEN0; - break; -#endif - default: - continue; - } + if (!bank->regs->wkup_en || !bank->suspend_wakeup) + return 0; - spin_lock_irqsave(&bank->lock, flags); - bank->saved_wakeup = __raw_readl(wake_status); - __raw_writel(0xffffffff, wake_clear); - __raw_writel(bank->suspend_wakeup, wake_set); - spin_unlock_irqrestore(&bank->lock, flags); - } + wakeup_enable = bank->base + bank->regs->wkup_en; + + spin_lock_irqsave(&bank->lock, flags); + bank->saved_wakeup = __raw_readl(wakeup_enable); + _gpio_rmw(base, bank->regs->wkup_en, 0xffffffff, 0); + _gpio_rmw(base, bank->regs->wkup_en, bank->suspend_wakeup, 1); + spin_unlock_irqrestore(&bank->lock, flags); return 0; } -static void omap_gpio_resume(void) +static int omap_gpio_resume(struct device *dev) { - int i; + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + void __iomem *base = bank->base; + unsigned long flags; - if (!cpu_class_is_omap2() && !cpu_is_omap16xx()) - return; + if (!bank->mod_usage || !bank->loses_context) + return 0; - for (i = 0; i < gpio_bank_count; i++) { - struct gpio_bank *bank = &gpio_bank[i]; - void __iomem *wake_clear; - void __iomem *wake_set; - unsigned long flags; - - switch (bank->method) { -#ifdef CONFIG_ARCH_OMAP16XX - case METHOD_GPIO_1610: - wake_clear = bank->base + OMAP1610_GPIO_CLEAR_WAKEUPENA; - wake_set = bank->base + OMAP1610_GPIO_SET_WAKEUPENA; - break; -#endif -#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3) - case METHOD_GPIO_24XX: - wake_clear = bank->base + OMAP24XX_GPIO_CLEARWKUENA; - wake_set = bank->base + OMAP24XX_GPIO_SETWKUENA; - break; -#endif -#ifdef CONFIG_ARCH_OMAP4 - case METHOD_GPIO_44XX: - wake_clear = bank->base + OMAP4_GPIO_IRQWAKEN0; - wake_set = bank->base + OMAP4_GPIO_IRQWAKEN0; - break; -#endif - default: - continue; - } + if (!bank->regs->wkup_en || !bank->saved_wakeup) + return 0; - spin_lock_irqsave(&bank->lock, flags); - __raw_writel(0xffffffff, wake_clear); - __raw_writel(bank->saved_wakeup, wake_set); - spin_unlock_irqrestore(&bank->lock, flags); - } -} + spin_lock_irqsave(&bank->lock, flags); + _gpio_rmw(base, bank->regs->wkup_en, 0xffffffff, 0); + _gpio_rmw(base, bank->regs->wkup_en, bank->saved_wakeup, 1); + spin_unlock_irqrestore(&bank->lock, flags); -static struct syscore_ops omap_gpio_syscore_ops = { - .suspend = omap_gpio_suspend, - .resume = omap_gpio_resume, -}; + return 0; +} +#endif /* CONFIG_PM_SLEEP */ -#endif +#if defined(CONFIG_PM_RUNTIME) +static void omap_gpio_restore_context(struct gpio_bank *bank); -#ifdef CONFIG_ARCH_OMAP2PLUS +static int omap_gpio_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + u32 l1 = 0, l2 = 0; + unsigned long flags; -static int workaround_enabled; + spin_lock_irqsave(&bank->lock, flags); + if (bank->power_mode != OFF_MODE) { + bank->power_mode = 0; + goto update_gpio_context_count; + } + /* + * If going to OFF, remove triggering for all + * non-wakeup GPIOs. Otherwise spurious IRQs will be + * generated. See OMAP2420 Errata item 1.101. + */ + if (!(bank->enabled_non_wakeup_gpios)) + goto update_gpio_context_count; -void omap2_gpio_prepare_for_idle(int off_mode) -{ - int i, c = 0; - int min = 0; + bank->saved_datain = __raw_readl(bank->base + + bank->regs->datain); + l1 = __raw_readl(bank->base + bank->regs->fallingdetect); + l2 = __raw_readl(bank->base + bank->regs->risingdetect); - if (cpu_is_omap34xx()) - min = 1; + bank->saved_fallingdetect = l1; + bank->saved_risingdetect = l2; + l1 &= ~bank->enabled_non_wakeup_gpios; + l2 &= ~bank->enabled_non_wakeup_gpios; - for (i = min; i < gpio_bank_count; i++) { - struct gpio_bank *bank = &gpio_bank[i]; - u32 l1 = 0, l2 = 0; - int j; + __raw_writel(l1, bank->base + bank->regs->fallingdetect); + __raw_writel(l2, bank->base + bank->regs->risingdetect); - for (j = 0; j < hweight_long(bank->dbck_enable_mask); j++) - clk_disable(bank->dbck); + bank->workaround_enabled = true; - if (!off_mode) - continue; +update_gpio_context_count: + if (bank->get_context_loss_count) + bank->context_loss_count = + bank->get_context_loss_count(bank->dev); - /* If going to OFF, remove triggering for all - * non-wakeup GPIOs. Otherwise spurious IRQs will be - * generated. See OMAP2420 Errata item 1.101. */ - if (!(bank->enabled_non_wakeup_gpios)) - continue; + _gpio_dbck_disable(bank); + spin_unlock_irqrestore(&bank->lock, flags); - if (cpu_is_omap24xx() || cpu_is_omap34xx()) { - bank->saved_datain = __raw_readl(bank->base + - OMAP24XX_GPIO_DATAIN); - l1 = __raw_readl(bank->base + - OMAP24XX_GPIO_FALLINGDETECT); - l2 = __raw_readl(bank->base + - OMAP24XX_GPIO_RISINGDETECT); - } + return 0; +} - if (cpu_is_omap44xx()) { - bank->saved_datain = __raw_readl(bank->base + - OMAP4_GPIO_DATAIN); - l1 = __raw_readl(bank->base + - OMAP4_GPIO_FALLINGDETECT); - l2 = __raw_readl(bank->base + - OMAP4_GPIO_RISINGDETECT); - } +static int omap_gpio_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_bank *bank = platform_get_drvdata(pdev); + int context_lost_cnt_after; + u32 l = 0, gen, gen0, gen1; + unsigned long flags; - bank->saved_fallingdetect = l1; - bank->saved_risingdetect = l2; - l1 &= ~bank->enabled_non_wakeup_gpios; - l2 &= ~bank->enabled_non_wakeup_gpios; + spin_lock_irqsave(&bank->lock, flags); + _gpio_dbck_enable(bank); + if (!bank->enabled_non_wakeup_gpios || !bank->workaround_enabled) { + spin_unlock_irqrestore(&bank->lock, flags); + return 0; + } - if (cpu_is_omap24xx() || cpu_is_omap34xx()) { - __raw_writel(l1, bank->base + - OMAP24XX_GPIO_FALLINGDETECT); - __raw_writel(l2, bank->base + - OMAP24XX_GPIO_RISINGDETECT); + if (bank->get_context_loss_count) { + context_lost_cnt_after = + bank->get_context_loss_count(bank->dev); + if (context_lost_cnt_after != bank->context_loss_count || + !context_lost_cnt_after) { + omap_gpio_restore_context(bank); + } else { + spin_unlock_irqrestore(&bank->lock, flags); + return 0; } + } - if (cpu_is_omap44xx()) { - __raw_writel(l1, bank->base + OMAP4_GPIO_FALLINGDETECT); - __raw_writel(l2, bank->base + OMAP4_GPIO_RISINGDETECT); - } + __raw_writel(bank->saved_fallingdetect, + bank->base + bank->regs->fallingdetect); + __raw_writel(bank->saved_risingdetect, + bank->base + bank->regs->risingdetect); + l = __raw_readl(bank->base + bank->regs->datain); - c++; - } - if (!c) { - workaround_enabled = 0; - return; - } - workaround_enabled = 1; -} + /* + * Check if any of the non-wakeup interrupt GPIOs have changed + * state. If so, generate an IRQ by software. This is + * horribly racy, but it's the best we can do to work around + * this silicon bug. + */ + l ^= bank->saved_datain; + l &= bank->enabled_non_wakeup_gpios; -void omap2_gpio_resume_after_idle(void) -{ - int i; - int min = 0; + /* + * No need to generate IRQs for the rising edge for gpio IRQs + * configured with falling edge only; and vice versa. + */ + gen0 = l & bank->saved_fallingdetect; + gen0 &= bank->saved_datain; - if (cpu_is_omap34xx()) - min = 1; - for (i = min; i < gpio_bank_count; i++) { - struct gpio_bank *bank = &gpio_bank[i]; - u32 l = 0, gen, gen0, gen1; - int j; + gen1 = l & bank->saved_risingdetect; + gen1 &= ~(bank->saved_datain); - for (j = 0; j < hweight_long(bank->dbck_enable_mask); j++) - clk_enable(bank->dbck); + /* FIXME: Consider GPIO IRQs with level detections properly! */ + gen = l & (~(bank->saved_fallingdetect) & ~(bank->saved_risingdetect)); + /* Consider all GPIO IRQs needed to be updated */ + gen |= gen0 | gen1; - if (!workaround_enabled) - continue; + if (gen) { + u32 old0, old1; - if (!(bank->enabled_non_wakeup_gpios)) - continue; + old0 = __raw_readl(bank->base + bank->regs->leveldetect0); + old1 = __raw_readl(bank->base + bank->regs->leveldetect1); if (cpu_is_omap24xx() || cpu_is_omap34xx()) { - __raw_writel(bank->saved_fallingdetect, - bank->base + OMAP24XX_GPIO_FALLINGDETECT); - __raw_writel(bank->saved_risingdetect, - bank->base + OMAP24XX_GPIO_RISINGDETECT); - l = __raw_readl(bank->base + OMAP24XX_GPIO_DATAIN); + __raw_writel(old0 | gen, bank->base + + bank->regs->leveldetect0); + __raw_writel(old1 | gen, bank->base + + bank->regs->leveldetect1); } if (cpu_is_omap44xx()) { - __raw_writel(bank->saved_fallingdetect, - bank->base + OMAP4_GPIO_FALLINGDETECT); - __raw_writel(bank->saved_risingdetect, - bank->base + OMAP4_GPIO_RISINGDETECT); - l = __raw_readl(bank->base + OMAP4_GPIO_DATAIN); - } - - /* Check if any of the non-wakeup interrupt GPIOs have changed - * state. If so, generate an IRQ by software. This is - * horribly racy, but it's the best we can do to work around - * this silicon bug. */ - l ^= bank->saved_datain; - l &= bank->enabled_non_wakeup_gpios; - - /* - * No need to generate IRQs for the rising edge for gpio IRQs - * configured with falling edge only; and vice versa. - */ - gen0 = l & bank->saved_fallingdetect; - gen0 &= bank->saved_datain; - - gen1 = l & bank->saved_risingdetect; - gen1 &= ~(bank->saved_datain); - - /* FIXME: Consider GPIO IRQs with level detections properly! */ - gen = l & (~(bank->saved_fallingdetect) & - ~(bank->saved_risingdetect)); - /* Consider all GPIO IRQs needed to be updated */ - gen |= gen0 | gen1; - - if (gen) { - u32 old0, old1; - - if (cpu_is_omap24xx() || cpu_is_omap34xx()) { - old0 = __raw_readl(bank->base + - OMAP24XX_GPIO_LEVELDETECT0); - old1 = __raw_readl(bank->base + - OMAP24XX_GPIO_LEVELDETECT1); - __raw_writel(old0 | gen, bank->base + - OMAP24XX_GPIO_LEVELDETECT0); - __raw_writel(old1 | gen, bank->base + - OMAP24XX_GPIO_LEVELDETECT1); - __raw_writel(old0, bank->base + - OMAP24XX_GPIO_LEVELDETECT0); - __raw_writel(old1, bank->base + - OMAP24XX_GPIO_LEVELDETECT1); - } - - if (cpu_is_omap44xx()) { - old0 = __raw_readl(bank->base + - OMAP4_GPIO_LEVELDETECT0); - old1 = __raw_readl(bank->base + - OMAP4_GPIO_LEVELDETECT1); - __raw_writel(old0 | l, bank->base + - OMAP4_GPIO_LEVELDETECT0); - __raw_writel(old1 | l, bank->base + - OMAP4_GPIO_LEVELDETECT1); - __raw_writel(old0, bank->base + - OMAP4_GPIO_LEVELDETECT0); - __raw_writel(old1, bank->base + - OMAP4_GPIO_LEVELDETECT1); - } + __raw_writel(old0 | l, bank->base + + bank->regs->leveldetect0); + __raw_writel(old1 | l, bank->base + + bank->regs->leveldetect1); } + __raw_writel(old0, bank->base + bank->regs->leveldetect0); + __raw_writel(old1, bank->base + bank->regs->leveldetect1); } + bank->workaround_enabled = false; + spin_unlock_irqrestore(&bank->lock, flags); + + return 0; } +#endif /* CONFIG_PM_RUNTIME */ -#endif +void omap2_gpio_prepare_for_idle(int pwr_mode) +{ + struct gpio_bank *bank; + + list_for_each_entry(bank, &omap_gpio_list, node) { + if (!bank->mod_usage || !bank->loses_context) + continue; + + bank->power_mode = pwr_mode; + + pm_runtime_put_sync_suspend(bank->dev); + } +} -#ifdef CONFIG_ARCH_OMAP3 -/* save the registers of bank 2-6 */ -void omap_gpio_save_context(void) +void omap2_gpio_resume_after_idle(void) { - int i; - - /* saving banks from 2-6 only since GPIO1 is in WKUP */ - for (i = 1; i < gpio_bank_count; i++) { - struct gpio_bank *bank = &gpio_bank[i]; - gpio_context[i].irqenable1 = - __raw_readl(bank->base + OMAP24XX_GPIO_IRQENABLE1); - gpio_context[i].irqenable2 = - __raw_readl(bank->base + OMAP24XX_GPIO_IRQENABLE2); - gpio_context[i].wake_en = - __raw_readl(bank->base + OMAP24XX_GPIO_WAKE_EN); - gpio_context[i].ctrl = - __raw_readl(bank->base + OMAP24XX_GPIO_CTRL); - gpio_context[i].oe = - __raw_readl(bank->base + OMAP24XX_GPIO_OE); - gpio_context[i].leveldetect0 = - __raw_readl(bank->base + OMAP24XX_GPIO_LEVELDETECT0); - gpio_context[i].leveldetect1 = - __raw_readl(bank->base + OMAP24XX_GPIO_LEVELDETECT1); - gpio_context[i].risingdetect = - __raw_readl(bank->base + OMAP24XX_GPIO_RISINGDETECT); - gpio_context[i].fallingdetect = - __raw_readl(bank->base + OMAP24XX_GPIO_FALLINGDETECT); - gpio_context[i].dataout = - __raw_readl(bank->base + OMAP24XX_GPIO_DATAOUT); + struct gpio_bank *bank; + + list_for_each_entry(bank, &omap_gpio_list, node) { + if (!bank->mod_usage || !bank->loses_context) + continue; + + pm_runtime_get_sync(bank->dev); } } -/* restore the required registers of bank 2-6 */ -void omap_gpio_restore_context(void) +#if defined(CONFIG_PM_RUNTIME) +static void omap_gpio_restore_context(struct gpio_bank *bank) { - int i; - - for (i = 1; i < gpio_bank_count; i++) { - struct gpio_bank *bank = &gpio_bank[i]; - __raw_writel(gpio_context[i].irqenable1, - bank->base + OMAP24XX_GPIO_IRQENABLE1); - __raw_writel(gpio_context[i].irqenable2, - bank->base + OMAP24XX_GPIO_IRQENABLE2); - __raw_writel(gpio_context[i].wake_en, - bank->base + OMAP24XX_GPIO_WAKE_EN); - __raw_writel(gpio_context[i].ctrl, - bank->base + OMAP24XX_GPIO_CTRL); - __raw_writel(gpio_context[i].oe, - bank->base + OMAP24XX_GPIO_OE); - __raw_writel(gpio_context[i].leveldetect0, - bank->base + OMAP24XX_GPIO_LEVELDETECT0); - __raw_writel(gpio_context[i].leveldetect1, - bank->base + OMAP24XX_GPIO_LEVELDETECT1); - __raw_writel(gpio_context[i].risingdetect, - bank->base + OMAP24XX_GPIO_RISINGDETECT); - __raw_writel(gpio_context[i].fallingdetect, - bank->base + OMAP24XX_GPIO_FALLINGDETECT); - __raw_writel(gpio_context[i].dataout, - bank->base + OMAP24XX_GPIO_DATAOUT); + __raw_writel(bank->context.wake_en, + bank->base + bank->regs->wkup_en); + __raw_writel(bank->context.ctrl, bank->base + bank->regs->ctrl); + __raw_writel(bank->context.leveldetect0, + bank->base + bank->regs->leveldetect0); + __raw_writel(bank->context.leveldetect1, + bank->base + bank->regs->leveldetect1); + __raw_writel(bank->context.risingdetect, + bank->base + bank->regs->risingdetect); + __raw_writel(bank->context.fallingdetect, + bank->base + bank->regs->fallingdetect); + if (bank->regs->set_dataout && bank->regs->clr_dataout) + __raw_writel(bank->context.dataout, + bank->base + bank->regs->set_dataout); + else + __raw_writel(bank->context.dataout, + bank->base + bank->regs->dataout); + __raw_writel(bank->context.oe, bank->base + bank->regs->direction); + + if (bank->dbck_enable_mask) { + __raw_writel(bank->context.debounce, bank->base + + bank->regs->debounce); + __raw_writel(bank->context.debounce_en, + bank->base + bank->regs->debounce_en); } + + __raw_writel(bank->context.irqenable1, + bank->base + bank->regs->irqenable); + __raw_writel(bank->context.irqenable2, + bank->base + bank->regs->irqenable2); } +#endif /* CONFIG_PM_RUNTIME */ +#else +#define omap_gpio_suspend NULL +#define omap_gpio_resume NULL +#define omap_gpio_runtime_suspend NULL +#define omap_gpio_runtime_resume NULL #endif +static const struct dev_pm_ops gpio_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(omap_gpio_suspend, omap_gpio_resume) + SET_RUNTIME_PM_OPS(omap_gpio_runtime_suspend, omap_gpio_runtime_resume, + NULL) +}; + static struct platform_driver omap_gpio_driver = { .probe = omap_gpio_probe, .driver = { .name = "omap_gpio", + .pm = &gpio_pm_ops, }, }; @@ -1585,17 +1415,3 @@ static int __init omap_gpio_drv_reg(void) return platform_driver_register(&omap_gpio_driver); } postcore_initcall(omap_gpio_drv_reg); - -static int __init omap_gpio_sysinit(void) -{ - mpuio_init(); - -#if defined(CONFIG_ARCH_OMAP16XX) || defined(CONFIG_ARCH_OMAP2PLUS) - if (cpu_is_omap16xx() || cpu_class_is_omap2()) - register_syscore_ops(&omap_gpio_syscore_ops); -#endif - - return 0; -} - -arch_initcall(omap_gpio_sysinit); diff --git a/drivers/gpio/gpio-sa1100.c b/drivers/gpio/gpio-sa1100.c index 7eecf69362ee..8ea3b33d4b40 100644 --- a/drivers/gpio/gpio-sa1100.c +++ b/drivers/gpio/gpio-sa1100.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <mach/hardware.h> +#include <mach/irqs.h> static int sa1100_gpio_get(struct gpio_chip *chip, unsigned offset) { diff --git a/drivers/gpio/gpio-samsung.c b/drivers/gpio/gpio-samsung.c index 0a79a1167a25..46277877b7ec 100644 --- a/drivers/gpio/gpio-samsung.c +++ b/drivers/gpio/gpio-samsung.c @@ -169,7 +169,7 @@ int s3c24xx_gpio_setpull_1down(struct samsung_gpio_chip *chip, return s3c24xx_gpio_setpull_1(chip, off, pull, S3C_GPIO_PULL_DOWN); } -static int exynos4_gpio_setpull(struct samsung_gpio_chip *chip, +static int exynos_gpio_setpull(struct samsung_gpio_chip *chip, unsigned int off, samsung_gpio_pull_t pull) { if (pull == S3C_GPIO_PULL_UP) @@ -178,7 +178,7 @@ static int exynos4_gpio_setpull(struct samsung_gpio_chip *chip, return samsung_gpio_setpull_updown(chip, off, pull); } -static samsung_gpio_pull_t exynos4_gpio_getpull(struct samsung_gpio_chip *chip, +static samsung_gpio_pull_t exynos_gpio_getpull(struct samsung_gpio_chip *chip, unsigned int off) { samsung_gpio_pull_t pull; @@ -452,9 +452,9 @@ static struct samsung_gpio_cfg s3c24xx_gpiocfg_banka = { }; #endif -static struct samsung_gpio_cfg exynos4_gpio_cfg = { - .set_pull = exynos4_gpio_setpull, - .get_pull = exynos4_gpio_getpull, +static struct samsung_gpio_cfg exynos_gpio_cfg = { + .set_pull = exynos_gpio_setpull, + .get_pull = exynos_gpio_getpull, .set_config = samsung_gpio_setcfg_4bit, .get_config = samsung_gpio_getcfg_4bit, }; @@ -502,13 +502,13 @@ static struct samsung_gpio_cfg samsung_gpio_cfgs[] = { .get_config = samsung_gpio_getcfg_2bit, }, [8] = { - .set_pull = exynos4_gpio_setpull, - .get_pull = exynos4_gpio_getpull, + .set_pull = exynos_gpio_setpull, + .get_pull = exynos_gpio_getpull, }, [9] = { .cfg_eint = 0x3, - .set_pull = exynos4_gpio_setpull, - .get_pull = exynos4_gpio_getpull, + .set_pull = exynos_gpio_setpull, + .get_pull = exynos_gpio_getpull, } }; @@ -2113,10 +2113,10 @@ static struct samsung_gpio_chip s5pv210_gpios_4bit[] = { }; /* - * Followings are the gpio banks in EXYNOS4210 + * Followings are the gpio banks in EXYNOS SoCs * * The 'config' member when left to NULL, is initialized to the default - * structure samsung_gpio_cfgs[3] in the init function below. + * structure exynos_gpio_cfg in the init function below. * * The 'base' member is also initialized in the init function below. * Note: The initialization of 'base' member of samsung_gpio_chip structure @@ -2331,7 +2331,6 @@ static struct samsung_gpio_chip exynos4_gpios_2[] = { .label = "GPY6", }, }, { - .base = (S5P_VA_GPIO2 + 0xC00), .config = &samsung_gpio_cfgs[9], .irq_base = IRQ_EINT(0), .chip = { @@ -2341,7 +2340,6 @@ static struct samsung_gpio_chip exynos4_gpios_2[] = { .to_irq = samsung_gpiolib_to_irq, }, }, { - .base = (S5P_VA_GPIO2 + 0xC20), .config = &samsung_gpio_cfgs[9], .irq_base = IRQ_EINT(8), .chip = { @@ -2351,7 +2349,6 @@ static struct samsung_gpio_chip exynos4_gpios_2[] = { .to_irq = samsung_gpiolib_to_irq, }, }, { - .base = (S5P_VA_GPIO2 + 0xC40), .config = &samsung_gpio_cfgs[9], .irq_base = IRQ_EINT(16), .chip = { @@ -2361,7 +2358,6 @@ static struct samsung_gpio_chip exynos4_gpios_2[] = { .to_irq = samsung_gpiolib_to_irq, }, }, { - .base = (S5P_VA_GPIO2 + 0xC60), .config = &samsung_gpio_cfgs[9], .irq_base = IRQ_EINT(24), .chip = { @@ -2386,8 +2382,280 @@ static struct samsung_gpio_chip exynos4_gpios_3[] = { #endif }; -#if defined(CONFIG_ARCH_EXYNOS4) && defined(CONFIG_OF) -static int exynos4_gpio_xlate(struct gpio_chip *gc, +static struct samsung_gpio_chip exynos5_gpios_1[] = { +#ifdef CONFIG_ARCH_EXYNOS5 + { + .chip = { + .base = EXYNOS5_GPA0(0), + .ngpio = EXYNOS5_GPIO_A0_NR, + .label = "GPA0", + }, + }, { + .chip = { + .base = EXYNOS5_GPA1(0), + .ngpio = EXYNOS5_GPIO_A1_NR, + .label = "GPA1", + }, + }, { + .chip = { + .base = EXYNOS5_GPA2(0), + .ngpio = EXYNOS5_GPIO_A2_NR, + .label = "GPA2", + }, + }, { + .chip = { + .base = EXYNOS5_GPB0(0), + .ngpio = EXYNOS5_GPIO_B0_NR, + .label = "GPB0", + }, + }, { + .chip = { + .base = EXYNOS5_GPB1(0), + .ngpio = EXYNOS5_GPIO_B1_NR, + .label = "GPB1", + }, + }, { + .chip = { + .base = EXYNOS5_GPB2(0), + .ngpio = EXYNOS5_GPIO_B2_NR, + .label = "GPB2", + }, + }, { + .chip = { + .base = EXYNOS5_GPB3(0), + .ngpio = EXYNOS5_GPIO_B3_NR, + .label = "GPB3", + }, + }, { + .chip = { + .base = EXYNOS5_GPC0(0), + .ngpio = EXYNOS5_GPIO_C0_NR, + .label = "GPC0", + }, + }, { + .chip = { + .base = EXYNOS5_GPC1(0), + .ngpio = EXYNOS5_GPIO_C1_NR, + .label = "GPC1", + }, + }, { + .chip = { + .base = EXYNOS5_GPC2(0), + .ngpio = EXYNOS5_GPIO_C2_NR, + .label = "GPC2", + }, + }, { + .chip = { + .base = EXYNOS5_GPC3(0), + .ngpio = EXYNOS5_GPIO_C3_NR, + .label = "GPC3", + }, + }, { + .chip = { + .base = EXYNOS5_GPD0(0), + .ngpio = EXYNOS5_GPIO_D0_NR, + .label = "GPD0", + }, + }, { + .chip = { + .base = EXYNOS5_GPD1(0), + .ngpio = EXYNOS5_GPIO_D1_NR, + .label = "GPD1", + }, + }, { + .chip = { + .base = EXYNOS5_GPY0(0), + .ngpio = EXYNOS5_GPIO_Y0_NR, + .label = "GPY0", + }, + }, { + .chip = { + .base = EXYNOS5_GPY1(0), + .ngpio = EXYNOS5_GPIO_Y1_NR, + .label = "GPY1", + }, + }, { + .chip = { + .base = EXYNOS5_GPY2(0), + .ngpio = EXYNOS5_GPIO_Y2_NR, + .label = "GPY2", + }, + }, { + .chip = { + .base = EXYNOS5_GPY3(0), + .ngpio = EXYNOS5_GPIO_Y3_NR, + .label = "GPY3", + }, + }, { + .chip = { + .base = EXYNOS5_GPY4(0), + .ngpio = EXYNOS5_GPIO_Y4_NR, + .label = "GPY4", + }, + }, { + .chip = { + .base = EXYNOS5_GPY5(0), + .ngpio = EXYNOS5_GPIO_Y5_NR, + .label = "GPY5", + }, + }, { + .chip = { + .base = EXYNOS5_GPY6(0), + .ngpio = EXYNOS5_GPIO_Y6_NR, + .label = "GPY6", + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(0), + .chip = { + .base = EXYNOS5_GPX0(0), + .ngpio = EXYNOS5_GPIO_X0_NR, + .label = "GPX0", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(8), + .chip = { + .base = EXYNOS5_GPX1(0), + .ngpio = EXYNOS5_GPIO_X1_NR, + .label = "GPX1", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(16), + .chip = { + .base = EXYNOS5_GPX2(0), + .ngpio = EXYNOS5_GPIO_X2_NR, + .label = "GPX2", + .to_irq = samsung_gpiolib_to_irq, + }, + }, { + .config = &samsung_gpio_cfgs[9], + .irq_base = IRQ_EINT(24), + .chip = { + .base = EXYNOS5_GPX3(0), + .ngpio = EXYNOS5_GPIO_X3_NR, + .label = "GPX3", + .to_irq = samsung_gpiolib_to_irq, + }, + }, +#endif +}; + +static struct samsung_gpio_chip exynos5_gpios_2[] = { +#ifdef CONFIG_ARCH_EXYNOS5 + { + .chip = { + .base = EXYNOS5_GPE0(0), + .ngpio = EXYNOS5_GPIO_E0_NR, + .label = "GPE0", + }, + }, { + .chip = { + .base = EXYNOS5_GPE1(0), + .ngpio = EXYNOS5_GPIO_E1_NR, + .label = "GPE1", + }, + }, { + .chip = { + .base = EXYNOS5_GPF0(0), + .ngpio = EXYNOS5_GPIO_F0_NR, + .label = "GPF0", + }, + }, { + .chip = { + .base = EXYNOS5_GPF1(0), + .ngpio = EXYNOS5_GPIO_F1_NR, + .label = "GPF1", + }, + }, { + .chip = { + .base = EXYNOS5_GPG0(0), + .ngpio = EXYNOS5_GPIO_G0_NR, + .label = "GPG0", + }, + }, { + .chip = { + .base = EXYNOS5_GPG1(0), + .ngpio = EXYNOS5_GPIO_G1_NR, + .label = "GPG1", + }, + }, { + .chip = { + .base = EXYNOS5_GPG2(0), + .ngpio = EXYNOS5_GPIO_G2_NR, + .label = "GPG2", + }, + }, { + .chip = { + .base = EXYNOS5_GPH0(0), + .ngpio = EXYNOS5_GPIO_H0_NR, + .label = "GPH0", + }, + }, { + .chip = { + .base = EXYNOS5_GPH1(0), + .ngpio = EXYNOS5_GPIO_H1_NR, + .label = "GPH1", + + }, + }, +#endif +}; + +static struct samsung_gpio_chip exynos5_gpios_3[] = { +#ifdef CONFIG_ARCH_EXYNOS5 + { + .chip = { + .base = EXYNOS5_GPV0(0), + .ngpio = EXYNOS5_GPIO_V0_NR, + .label = "GPV0", + }, + }, { + .chip = { + .base = EXYNOS5_GPV1(0), + .ngpio = EXYNOS5_GPIO_V1_NR, + .label = "GPV1", + }, + }, { + .chip = { + .base = EXYNOS5_GPV2(0), + .ngpio = EXYNOS5_GPIO_V2_NR, + .label = "GPV2", + }, + }, { + .chip = { + .base = EXYNOS5_GPV3(0), + .ngpio = EXYNOS5_GPIO_V3_NR, + .label = "GPV3", + }, + }, { + .chip = { + .base = EXYNOS5_GPV4(0), + .ngpio = EXYNOS5_GPIO_V4_NR, + .label = "GPV4", + }, + }, +#endif +}; + +static struct samsung_gpio_chip exynos5_gpios_4[] = { +#ifdef CONFIG_ARCH_EXYNOS5 + { + .chip = { + .base = EXYNOS5_GPZ(0), + .ngpio = EXYNOS5_GPIO_Z_NR, + .label = "GPZ", + }, + }, +#endif +}; + + +#if defined(CONFIG_ARCH_EXYNOS) && defined(CONFIG_OF) +static int exynos_gpio_xlate(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags) { unsigned int pin; @@ -2413,13 +2681,13 @@ static int exynos4_gpio_xlate(struct gpio_chip *gc, return gpiospec->args[0]; } -static const struct of_device_id exynos4_gpio_dt_match[] __initdata = { +static const struct of_device_id exynos_gpio_dt_match[] __initdata = { { .compatible = "samsung,exynos4-gpio", }, {} }; -static __init void exynos4_gpiolib_attach_ofnode(struct samsung_gpio_chip *chip, - u64 base, u64 offset) +static __init void exynos_gpiolib_attach_ofnode(struct samsung_gpio_chip *chip, + u64 base, u64 offset) { struct gpio_chip *gc = &chip->chip; u64 address; @@ -2429,28 +2697,29 @@ static __init void exynos4_gpiolib_attach_ofnode(struct samsung_gpio_chip *chip, address = chip->base ? base + ((u32)chip->base & 0xfff) : base + offset; gc->of_node = of_find_matching_node_by_address(NULL, - exynos4_gpio_dt_match, address); + exynos_gpio_dt_match, address); if (!gc->of_node) { pr_info("gpio: device tree node not found for gpio controller" " with base address %08llx\n", address); return; } gc->of_gpio_n_cells = 4; - gc->of_xlate = exynos4_gpio_xlate; + gc->of_xlate = exynos_gpio_xlate; } -#elif defined(CONFIG_ARCH_EXYNOS4) -static __init void exynos4_gpiolib_attach_ofnode(struct samsung_gpio_chip *chip, - u64 base, u64 offset) +#elif defined(CONFIG_ARCH_EXYNOS) +static __init void exynos_gpiolib_attach_ofnode(struct samsung_gpio_chip *chip, + u64 base, u64 offset) { return; } -#endif /* defined(CONFIG_ARCH_EXYNOS4) && defined(CONFIG_OF) */ +#endif /* defined(CONFIG_ARCH_EXYNOS) && defined(CONFIG_OF) */ /* TODO: cleanup soc_is_* */ static __init int samsung_gpiolib_init(void) { struct samsung_gpio_chip *chip; int i, nr_chips; + void __iomem *gpio_base1, *gpio_base2, *gpio_base3, *gpio_base4; int group = 0; samsung_gpiolib_set_cfg(samsung_gpio_cfgs, ARRAY_SIZE(samsung_gpio_cfgs)); @@ -2516,66 +2785,200 @@ static __init int samsung_gpiolib_init(void) s5p_register_gpioint_bank(IRQ_GPIOINT, 0, S5P_GPIOINT_GROUP_MAXNR); #endif } else if (soc_is_exynos4210()) { - group = 0; +#ifdef CONFIG_CPU_EXYNOS4210 + void __iomem *gpx_base; /* gpio part1 */ + gpio_base1 = ioremap(EXYNOS4_PA_GPIO1, SZ_4K); + if (gpio_base1 == NULL) { + pr_err("unable to ioremap for gpio_base1\n"); + goto err_ioremap1; + } + chip = exynos4_gpios_1; nr_chips = ARRAY_SIZE(exynos4_gpios_1); for (i = 0; i < nr_chips; i++, chip++) { if (!chip->config) { - chip->config = &exynos4_gpio_cfg; + chip->config = &exynos_gpio_cfg; chip->group = group++; } -#ifdef CONFIG_CPU_EXYNOS4210 - exynos4_gpiolib_attach_ofnode(chip, + exynos_gpiolib_attach_ofnode(chip, EXYNOS4_PA_GPIO1, i * 0x20); -#endif } - samsung_gpiolib_add_4bit_chips(exynos4_gpios_1, nr_chips, S5P_VA_GPIO1); + samsung_gpiolib_add_4bit_chips(exynos4_gpios_1, + nr_chips, gpio_base1); /* gpio part2 */ + gpio_base2 = ioremap(EXYNOS4_PA_GPIO2, SZ_4K); + if (gpio_base2 == NULL) { + pr_err("unable to ioremap for gpio_base2\n"); + goto err_ioremap2; + } + + /* need to set base address for gpx */ + chip = &exynos4_gpios_2[16]; + gpx_base = gpio_base2 + 0xC00; + for (i = 0; i < 4; i++, chip++, gpx_base += 0x20) + chip->base = gpx_base; + chip = exynos4_gpios_2; nr_chips = ARRAY_SIZE(exynos4_gpios_2); for (i = 0; i < nr_chips; i++, chip++) { if (!chip->config) { - chip->config = &exynos4_gpio_cfg; + chip->config = &exynos_gpio_cfg; chip->group = group++; } -#ifdef CONFIG_CPU_EXYNOS4210 - exynos4_gpiolib_attach_ofnode(chip, + exynos_gpiolib_attach_ofnode(chip, EXYNOS4_PA_GPIO2, i * 0x20); -#endif } - samsung_gpiolib_add_4bit_chips(exynos4_gpios_2, nr_chips, S5P_VA_GPIO2); + samsung_gpiolib_add_4bit_chips(exynos4_gpios_2, + nr_chips, gpio_base2); /* gpio part3 */ + gpio_base3 = ioremap(EXYNOS4_PA_GPIO3, SZ_256); + if (gpio_base3 == NULL) { + pr_err("unable to ioremap for gpio_base3\n"); + goto err_ioremap3; + } + chip = exynos4_gpios_3; nr_chips = ARRAY_SIZE(exynos4_gpios_3); for (i = 0; i < nr_chips; i++, chip++) { if (!chip->config) { - chip->config = &exynos4_gpio_cfg; + chip->config = &exynos_gpio_cfg; chip->group = group++; } -#ifdef CONFIG_CPU_EXYNOS4210 - exynos4_gpiolib_attach_ofnode(chip, + exynos_gpiolib_attach_ofnode(chip, EXYNOS4_PA_GPIO3, i * 0x20); -#endif } - samsung_gpiolib_add_4bit_chips(exynos4_gpios_3, nr_chips, S5P_VA_GPIO3); + samsung_gpiolib_add_4bit_chips(exynos4_gpios_3, + nr_chips, gpio_base3); #if defined(CONFIG_CPU_EXYNOS4210) && defined(CONFIG_S5P_GPIO_INT) s5p_register_gpioint_bank(IRQ_GPIO_XA, 0, IRQ_GPIO1_NR_GROUPS); s5p_register_gpioint_bank(IRQ_GPIO_XB, IRQ_GPIO1_NR_GROUPS, IRQ_GPIO2_NR_GROUPS); #endif + +#endif /* CONFIG_CPU_EXYNOS4210 */ + } else if (soc_is_exynos5250()) { +#ifdef CONFIG_SOC_EXYNOS5250 + void __iomem *gpx_base; + + /* gpio part1 */ + gpio_base1 = ioremap(EXYNOS5_PA_GPIO1, SZ_4K); + if (gpio_base1 == NULL) { + pr_err("unable to ioremap for gpio_base1\n"); + goto err_ioremap1; + } + + /* need to set base address for gpx */ + chip = &exynos5_gpios_1[20]; + gpx_base = gpio_base1 + 0xC00; + for (i = 0; i < 4; i++, chip++, gpx_base += 0x20) + chip->base = gpx_base; + + chip = exynos5_gpios_1; + nr_chips = ARRAY_SIZE(exynos5_gpios_1); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO1, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_1, + nr_chips, gpio_base1); + + /* gpio part2 */ + gpio_base2 = ioremap(EXYNOS5_PA_GPIO2, SZ_4K); + if (gpio_base2 == NULL) { + pr_err("unable to ioremap for gpio_base2\n"); + goto err_ioremap2; + } + + chip = exynos5_gpios_2; + nr_chips = ARRAY_SIZE(exynos5_gpios_2); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO2, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_2, + nr_chips, gpio_base2); + + /* gpio part3 */ + gpio_base3 = ioremap(EXYNOS5_PA_GPIO3, SZ_4K); + if (gpio_base3 == NULL) { + pr_err("unable to ioremap for gpio_base3\n"); + goto err_ioremap3; + } + + /* need to set base address for gpv */ + exynos5_gpios_3[0].base = gpio_base3; + exynos5_gpios_3[1].base = gpio_base3 + 0x20; + exynos5_gpios_3[2].base = gpio_base3 + 0x60; + exynos5_gpios_3[3].base = gpio_base3 + 0x80; + exynos5_gpios_3[4].base = gpio_base3 + 0xC0; + + chip = exynos5_gpios_3; + nr_chips = ARRAY_SIZE(exynos5_gpios_3); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO3, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_3, + nr_chips, gpio_base3); + + /* gpio part4 */ + gpio_base4 = ioremap(EXYNOS5_PA_GPIO4, SZ_4K); + if (gpio_base4 == NULL) { + pr_err("unable to ioremap for gpio_base4\n"); + goto err_ioremap4; + } + + chip = exynos5_gpios_4; + nr_chips = ARRAY_SIZE(exynos5_gpios_4); + + for (i = 0; i < nr_chips; i++, chip++) { + if (!chip->config) { + chip->config = &exynos_gpio_cfg; + chip->group = group++; + } + exynos_gpiolib_attach_ofnode(chip, + EXYNOS5_PA_GPIO4, i * 0x20); + } + samsung_gpiolib_add_4bit_chips(exynos5_gpios_4, + nr_chips, gpio_base4); +#endif /* CONFIG_SOC_EXYNOS5250 */ } else { WARN(1, "Unknown SoC in gpio-samsung, no GPIOs added\n"); return -ENODEV; } return 0; + +err_ioremap4: + iounmap(gpio_base3); +err_ioremap3: + iounmap(gpio_base2); +err_ioremap2: + iounmap(gpio_base1); +err_ioremap1: + return -ENOMEM; } core_initcall(samsung_gpiolib_init); diff --git a/drivers/gpio/gpio-tegra.c b/drivers/gpio/gpio-tegra.c index bdc293791590..6f17671260e1 100644 --- a/drivers/gpio/gpio-tegra.c +++ b/drivers/gpio/gpio-tegra.c @@ -25,6 +25,7 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/module.h> +#include <linux/irqdomain.h> #include <asm/mach/irq.h> @@ -74,9 +75,10 @@ struct tegra_gpio_bank { #endif }; - +static struct irq_domain *irq_domain; static void __iomem *regs; -static struct tegra_gpio_bank tegra_gpio_banks[7]; +static u32 tegra_gpio_bank_count; +static struct tegra_gpio_bank *tegra_gpio_banks; static inline void tegra_gpio_writel(u32 val, u32 reg) { @@ -139,7 +141,7 @@ static int tegra_gpio_direction_output(struct gpio_chip *chip, unsigned offset, static int tegra_gpio_to_irq(struct gpio_chip *chip, unsigned offset) { - return TEGRA_GPIO_TO_IRQ(offset); + return irq_find_mapping(irq_domain, offset); } static struct gpio_chip tegra_gpio_chip = { @@ -155,28 +157,28 @@ static struct gpio_chip tegra_gpio_chip = { static void tegra_gpio_irq_ack(struct irq_data *d) { - int gpio = d->irq - INT_GPIO_BASE; + int gpio = d->hwirq; tegra_gpio_writel(1 << GPIO_BIT(gpio), GPIO_INT_CLR(gpio)); } static void tegra_gpio_irq_mask(struct irq_data *d) { - int gpio = d->irq - INT_GPIO_BASE; + int gpio = d->hwirq; tegra_gpio_mask_write(GPIO_MSK_INT_ENB(gpio), gpio, 0); } static void tegra_gpio_irq_unmask(struct irq_data *d) { - int gpio = d->irq - INT_GPIO_BASE; + int gpio = d->hwirq; tegra_gpio_mask_write(GPIO_MSK_INT_ENB(gpio), gpio, 1); } static int tegra_gpio_irq_set_type(struct irq_data *d, unsigned int type) { - int gpio = d->irq - INT_GPIO_BASE; + int gpio = d->hwirq; struct tegra_gpio_bank *bank = irq_data_get_irq_chip_data(d); int port = GPIO_PORT(gpio); int lvl_type; @@ -273,7 +275,7 @@ void tegra_gpio_resume(void) local_irq_save(flags); - for (b = 0; b < ARRAY_SIZE(tegra_gpio_banks); b++) { + for (b = 0; b < tegra_gpio_bank_count; b++) { struct tegra_gpio_bank *bank = &tegra_gpio_banks[b]; for (p = 0; p < ARRAY_SIZE(bank->oe); p++) { @@ -296,7 +298,7 @@ void tegra_gpio_suspend(void) int p; local_irq_save(flags); - for (b = 0; b < ARRAY_SIZE(tegra_gpio_banks); b++) { + for (b = 0; b < tegra_gpio_bank_count; b++) { struct tegra_gpio_bank *bank = &tegra_gpio_banks[b]; for (p = 0; p < ARRAY_SIZE(bank->oe); p++) { @@ -337,13 +339,44 @@ static struct lock_class_key gpio_lock_class; static int __devinit tegra_gpio_probe(struct platform_device *pdev) { + int irq_base; struct resource *res; struct tegra_gpio_bank *bank; int gpio; int i; int j; - for (i = 0; i < ARRAY_SIZE(tegra_gpio_banks); i++) { + for (;;) { + res = platform_get_resource(pdev, IORESOURCE_IRQ, tegra_gpio_bank_count); + if (!res) + break; + tegra_gpio_bank_count++; + } + if (!tegra_gpio_bank_count) { + dev_err(&pdev->dev, "Missing IRQ resource\n"); + return -ENODEV; + } + + tegra_gpio_chip.ngpio = tegra_gpio_bank_count * 32; + + tegra_gpio_banks = devm_kzalloc(&pdev->dev, + tegra_gpio_bank_count * sizeof(*tegra_gpio_banks), + GFP_KERNEL); + if (!tegra_gpio_banks) { + dev_err(&pdev->dev, "Couldn't allocate bank structure\n"); + return -ENODEV; + } + + irq_base = irq_alloc_descs(-1, 0, tegra_gpio_chip.ngpio, 0); + if (irq_base < 0) { + dev_err(&pdev->dev, "Couldn't allocate IRQ numbers\n"); + return -ENODEV; + } + irq_domain = irq_domain_add_legacy(pdev->dev.of_node, + tegra_gpio_chip.ngpio, irq_base, 0, + &irq_domain_simple_ops, NULL); + + for (i = 0; i < tegra_gpio_bank_count; i++) { res = platform_get_resource(pdev, IORESOURCE_IRQ, i); if (!res) { dev_err(&pdev->dev, "Missing IRQ resource\n"); @@ -380,8 +413,8 @@ static int __devinit tegra_gpio_probe(struct platform_device *pdev) gpiochip_add(&tegra_gpio_chip); - for (gpio = 0; gpio < TEGRA_NR_GPIOS; gpio++) { - int irq = TEGRA_GPIO_TO_IRQ(gpio); + for (gpio = 0; gpio < tegra_gpio_chip.ngpio; gpio++) { + int irq = irq_find_mapping(irq_domain, gpio); /* No validity check; all Tegra GPIOs are valid IRQs */ bank = &tegra_gpio_banks[GPIO_BANK(gpio)]; @@ -393,7 +426,7 @@ static int __devinit tegra_gpio_probe(struct platform_device *pdev) set_irq_flags(irq, IRQF_VALID); } - for (i = 0; i < ARRAY_SIZE(tegra_gpio_banks); i++) { + for (i = 0; i < tegra_gpio_bank_count; i++) { bank = &tegra_gpio_banks[i]; irq_set_chained_handler(bank->irq, tegra_gpio_irq_handler); diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_output.c b/drivers/gpu/drm/gma500/mdfld_dsi_output.c index 4c2cb4a8ad98..5675d93b4205 100644 --- a/drivers/gpu/drm/gma500/mdfld_dsi_output.c +++ b/drivers/gpu/drm/gma500/mdfld_dsi_output.c @@ -244,7 +244,6 @@ static int mdfld_dsi_connector_set_property(struct drm_connector *connector, uint64_t value) { struct drm_encoder *encoder = connector->encoder; - struct backlight_device *psb_bd; if (!strcmp(property->name, "scaling mode") && encoder) { struct psb_intel_crtc *psb_crtc = @@ -301,11 +300,15 @@ static int mdfld_dsi_connector_set_property(struct drm_connector *connector, value)) goto set_prop_error; else { +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + struct backlight_device *psb_bd; + psb_bd = mdfld_get_backlight_device(); if (psb_bd) { psb_bd->props.brightness = value; mdfld_set_brightness(psb_bd); } +#endif } } set_prop_done: diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index 8f510fd956b0..fa860358add1 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -654,10 +654,13 @@ nouveau_connector_detect_depth(struct drm_connector *connector) if (nv_connector->edid && connector->display_info.bpc) return; - /* if not, we're out of options unless we're LVDS, default to 6bpc */ - connector->display_info.bpc = 6; - if (nv_encoder->dcb->type != OUTPUT_LVDS) + /* if not, we're out of options unless we're LVDS, default to 8bpc */ + if (nv_encoder->dcb->type != OUTPUT_LVDS) { + connector->display_info.bpc = 8; return; + } + + connector->display_info.bpc = 6; /* LVDS: panel straps */ if (bios->fp_no_ddc) { diff --git a/drivers/gpu/drm/nouveau/nouveau_i2c.c b/drivers/gpu/drm/nouveau/nouveau_i2c.c index 8f4f914d9eab..e2be95af2e52 100644 --- a/drivers/gpu/drm/nouveau/nouveau_i2c.c +++ b/drivers/gpu/drm/nouveau/nouveau_i2c.c @@ -315,8 +315,8 @@ nouveau_i2c_init(struct drm_device *dev) struct drm_nouveau_private *dev_priv = dev->dev_private; struct nvbios *bios = &dev_priv->vbios; struct nouveau_i2c_chan *port; + u8 version = 0x00, entries, recordlen; u8 *i2c, *entry, legacy[2][4] = {}; - u8 version, entries, recordlen; int ret, i; INIT_LIST_HEAD(&dev_priv->i2c_ports); @@ -346,12 +346,12 @@ nouveau_i2c_init(struct drm_device *dev) if (i2c[7]) legacy[1][1] = i2c[7]; } - if (i2c && version >= 0x30) { + if (version >= 0x30) { entry = i2c[1] + i2c; entries = i2c[2]; recordlen = i2c[3]; } else - if (i2c) { + if (version) { entry = i2c; entries = 16; recordlen = 4; diff --git a/drivers/gpu/drm/nouveau/nouveau_state.c b/drivers/gpu/drm/nouveau/nouveau_state.c index a3ae91fa8141..a4886b36d0fa 100644 --- a/drivers/gpu/drm/nouveau/nouveau_state.c +++ b/drivers/gpu/drm/nouveau/nouveau_state.c @@ -852,7 +852,7 @@ nouveau_card_init(struct drm_device *dev) if (ret) goto out_pm; - if (!dev_priv->noaccel) { + if (dev_priv->eng[NVOBJ_ENGINE_GR]) { ret = nouveau_card_channel_init(dev); if (ret) goto out_fence; diff --git a/drivers/gpu/drm/radeon/atombios_crtc.c b/drivers/gpu/drm/radeon/atombios_crtc.c index 083b3eada001..b5ff1f7b6f7e 100644 --- a/drivers/gpu/drm/radeon/atombios_crtc.c +++ b/drivers/gpu/drm/radeon/atombios_crtc.c @@ -588,8 +588,8 @@ static u32 atombios_adjust_pll(struct drm_crtc *crtc, if (encoder->crtc == crtc) { radeon_encoder = to_radeon_encoder(encoder); connector = radeon_get_connector_for_encoder(encoder); - if (connector && connector->display_info.bpc) - bpc = connector->display_info.bpc; + /* if (connector && connector->display_info.bpc) + bpc = connector->display_info.bpc; */ encoder_mode = atombios_get_encoder_mode(encoder); is_duallink = radeon_dig_monitor_is_duallink(encoder, mode->clock); if ((radeon_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT | ATOM_DEVICE_DFP_SUPPORT)) || @@ -965,7 +965,9 @@ static void atombios_crtc_set_pll(struct drm_crtc *crtc, struct drm_display_mode struct radeon_connector_atom_dig *dig_connector = radeon_connector->con_priv; int dp_clock; - bpc = connector->display_info.bpc; + + /* if (connector->display_info.bpc) + bpc = connector->display_info.bpc; */ switch (encoder_mode) { case ATOM_ENCODER_MODE_DP_MST: diff --git a/drivers/gpu/drm/radeon/atombios_dp.c b/drivers/gpu/drm/radeon/atombios_dp.c index 6c62be226804..c57d85664e77 100644 --- a/drivers/gpu/drm/radeon/atombios_dp.c +++ b/drivers/gpu/drm/radeon/atombios_dp.c @@ -405,10 +405,13 @@ static void dp_get_adjust_train(u8 link_status[DP_LINK_STATUS_SIZE], /* get bpc from the EDID */ static int convert_bpc_to_bpp(int bpc) { +#if 0 if (bpc == 0) return 24; else return bpc * 3; +#endif + return 24; } /* get the max pix clock supported by the link rate and lane num */ diff --git a/drivers/gpu/drm/radeon/atombios_encoders.c b/drivers/gpu/drm/radeon/atombios_encoders.c index 468b874336f9..e607c4d7dd98 100644 --- a/drivers/gpu/drm/radeon/atombios_encoders.c +++ b/drivers/gpu/drm/radeon/atombios_encoders.c @@ -541,7 +541,7 @@ atombios_dig_encoder_setup(struct drm_encoder *encoder, int action, int panel_mo dp_clock = dig_connector->dp_clock; dp_lane_count = dig_connector->dp_lane_count; hpd_id = radeon_connector->hpd.hpd; - bpc = connector->display_info.bpc; + /* bpc = connector->display_info.bpc; */ } /* no dig encoder assigned */ @@ -1159,7 +1159,7 @@ atombios_external_encoder_setup(struct drm_encoder *encoder, dp_lane_count = dig_connector->dp_lane_count; connector_object_id = (radeon_connector->connector_object_id & OBJECT_ID_MASK) >> OBJECT_ID_SHIFT; - bpc = connector->display_info.bpc; + /* bpc = connector->display_info.bpc; */ } memset(&args, 0, sizeof(args)); diff --git a/drivers/gpu/drm/radeon/cayman_blit_shaders.c b/drivers/gpu/drm/radeon/cayman_blit_shaders.c index 7b4eeb7b4a8c..19a0114d2e3b 100644 --- a/drivers/gpu/drm/radeon/cayman_blit_shaders.c +++ b/drivers/gpu/drm/radeon/cayman_blit_shaders.c @@ -24,6 +24,7 @@ * Alex Deucher <alexander.deucher@amd.com> */ +#include <linux/bug.h> #include <linux/types.h> #include <linux/kernel.h> diff --git a/drivers/gpu/drm/radeon/evergreen_blit_shaders.c b/drivers/gpu/drm/radeon/evergreen_blit_shaders.c index 3a10399e0066..f85c0af115b5 100644 --- a/drivers/gpu/drm/radeon/evergreen_blit_shaders.c +++ b/drivers/gpu/drm/radeon/evergreen_blit_shaders.c @@ -24,6 +24,7 @@ * Alex Deucher <alexander.deucher@amd.com> */ +#include <linux/bug.h> #include <linux/types.h> #include <linux/kernel.h> diff --git a/drivers/gpu/drm/radeon/evergreen_cs.c b/drivers/gpu/drm/radeon/evergreen_cs.c index a58b37a2e65a..70089d32b80f 100644 --- a/drivers/gpu/drm/radeon/evergreen_cs.c +++ b/drivers/gpu/drm/radeon/evergreen_cs.c @@ -80,6 +80,9 @@ struct evergreen_cs_track { bool cb_dirty; bool db_dirty; bool streamout_dirty; + u32 htile_offset; + u32 htile_surface; + struct radeon_bo *htile_bo; }; static u32 evergreen_cs_get_aray_mode(u32 tiling_flags) @@ -144,6 +147,9 @@ static void evergreen_cs_track_init(struct evergreen_cs_track *track) track->db_s_read_bo = NULL; track->db_s_write_bo = NULL; track->db_dirty = true; + track->htile_bo = NULL; + track->htile_offset = 0xFFFFFFFF; + track->htile_surface = 0; for (i = 0; i < 4; i++) { track->vgt_strmout_size[i] = 0; @@ -444,6 +450,62 @@ static int evergreen_cs_track_validate_cb(struct radeon_cs_parser *p, unsigned i return 0; } +static int evergreen_cs_track_validate_htile(struct radeon_cs_parser *p, + unsigned nbx, unsigned nby) +{ + struct evergreen_cs_track *track = p->track; + unsigned long size; + + if (track->htile_bo == NULL) { + dev_warn(p->dev, "%s:%d htile enabled without htile surface 0x%08x\n", + __func__, __LINE__, track->db_z_info); + return -EINVAL; + } + + if (G_028ABC_LINEAR(track->htile_surface)) { + /* pitch must be 16 htiles aligned == 16 * 8 pixel aligned */ + nbx = round_up(nbx, 16 * 8); + /* height is npipes htiles aligned == npipes * 8 pixel aligned */ + nby = round_up(nby, track->npipes * 8); + } else { + switch (track->npipes) { + case 8: + nbx = round_up(nbx, 64 * 8); + nby = round_up(nby, 64 * 8); + break; + case 4: + nbx = round_up(nbx, 64 * 8); + nby = round_up(nby, 32 * 8); + break; + case 2: + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 32 * 8); + break; + case 1: + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 16 * 8); + break; + default: + dev_warn(p->dev, "%s:%d invalid num pipes %d\n", + __func__, __LINE__, track->npipes); + return -EINVAL; + } + } + /* compute number of htile */ + nbx = nbx / 8; + nby = nby / 8; + size = nbx * nby * 4; + size += track->htile_offset; + + if (size > radeon_bo_size(track->htile_bo)) { + dev_warn(p->dev, "%s:%d htile surface too small %ld for %ld (%d %d)\n", + __func__, __LINE__, radeon_bo_size(track->htile_bo), + size, nbx, nby); + return -EINVAL; + } + return 0; +} + static int evergreen_cs_track_validate_stencil(struct radeon_cs_parser *p) { struct evergreen_cs_track *track = p->track; @@ -530,6 +592,14 @@ static int evergreen_cs_track_validate_stencil(struct radeon_cs_parser *p) return -EINVAL; } + /* hyperz */ + if (G_028040_TILE_SURFACE_ENABLE(track->db_z_info)) { + r = evergreen_cs_track_validate_htile(p, surf.nbx, surf.nby); + if (r) { + return r; + } + } + return 0; } @@ -617,6 +687,14 @@ static int evergreen_cs_track_validate_depth(struct radeon_cs_parser *p) return -EINVAL; } + /* hyperz */ + if (G_028040_TILE_SURFACE_ENABLE(track->db_z_info)) { + r = evergreen_cs_track_validate_htile(p, surf.nbx, surf.nby); + if (r) { + return r; + } + } + return 0; } @@ -850,7 +928,7 @@ static int evergreen_cs_track_check(struct radeon_cs_parser *p) return r; } /* Check depth buffer */ - if (G_028800_Z_WRITE_ENABLE(track->db_depth_control)) { + if (G_028800_Z_ENABLE(track->db_depth_control)) { r = evergreen_cs_track_validate_depth(p); if (r) return r; @@ -1616,6 +1694,23 @@ static int evergreen_cs_check_reg(struct radeon_cs_parser *p, u32 reg, u32 idx) track->cb_color_bo[tmp] = reloc->robj; track->cb_dirty = true; break; + case DB_HTILE_DATA_BASE: + r = evergreen_cs_packet_next_reloc(p, &reloc); + if (r) { + dev_warn(p->dev, "bad SET_CONTEXT_REG " + "0x%04X\n", reg); + return -EINVAL; + } + track->htile_offset = radeon_get_ib_value(p, idx); + ib[idx] += (u32)((reloc->lobj.gpu_offset >> 8) & 0xffffffff); + track->htile_bo = reloc->robj; + track->db_dirty = true; + break; + case DB_HTILE_SURFACE: + /* 8x8 only */ + track->htile_surface = radeon_get_ib_value(p, idx); + track->db_dirty = true; + break; case CB_IMMED0_BASE: case CB_IMMED1_BASE: case CB_IMMED2_BASE: @@ -1628,7 +1723,6 @@ static int evergreen_cs_check_reg(struct radeon_cs_parser *p, u32 reg, u32 idx) case CB_IMMED9_BASE: case CB_IMMED10_BASE: case CB_IMMED11_BASE: - case DB_HTILE_DATA_BASE: case SQ_PGM_START_FS: case SQ_PGM_START_ES: case SQ_PGM_START_VS: diff --git a/drivers/gpu/drm/radeon/evergreend.h b/drivers/gpu/drm/radeon/evergreend.h index eb5708c7159d..b4eefc355f16 100644 --- a/drivers/gpu/drm/radeon/evergreend.h +++ b/drivers/gpu/drm/radeon/evergreend.h @@ -991,6 +991,14 @@ #define G_028008_SLICE_MAX(x) (((x) >> 13) & 0x7FF) #define C_028008_SLICE_MAX 0xFF001FFF #define DB_HTILE_DATA_BASE 0x28014 +#define DB_HTILE_SURFACE 0x28abc +#define S_028ABC_HTILE_WIDTH(x) (((x) & 0x1) << 0) +#define G_028ABC_HTILE_WIDTH(x) (((x) >> 0) & 0x1) +#define C_028ABC_HTILE_WIDTH 0xFFFFFFFE +#define S_028ABC_HTILE_HEIGHT(x) (((x) & 0x1) << 1) +#define G_028ABC_HTILE_HEIGHT(x) (((x) >> 1) & 0x1) +#define C_028ABC_HTILE_HEIGHT 0xFFFFFFFD +#define G_028ABC_LINEAR(x) (((x) >> 2) & 0x1) #define DB_Z_INFO 0x28040 # define Z_ARRAY_MODE(x) ((x) << 4) # define DB_TILE_SPLIT(x) (((x) & 0x7) << 8) diff --git a/drivers/gpu/drm/radeon/r600_blit_shaders.c b/drivers/gpu/drm/radeon/r600_blit_shaders.c index 73e2c7c6edbc..34c8b2340f33 100644 --- a/drivers/gpu/drm/radeon/r600_blit_shaders.c +++ b/drivers/gpu/drm/radeon/r600_blit_shaders.c @@ -24,6 +24,7 @@ * Alex Deucher <alexander.deucher@amd.com> */ +#include <linux/bug.h> #include <linux/types.h> #include <linux/kernel.h> diff --git a/drivers/gpu/drm/radeon/r600_cs.c b/drivers/gpu/drm/radeon/r600_cs.c index 0ec3f205f9c4..b8e12af304a9 100644 --- a/drivers/gpu/drm/radeon/r600_cs.c +++ b/drivers/gpu/drm/radeon/r600_cs.c @@ -78,6 +78,9 @@ struct r600_cs_track { bool cb_dirty; bool db_dirty; bool streamout_dirty; + struct radeon_bo *htile_bo; + u64 htile_offset; + u32 htile_surface; }; #define FMT_8_BIT(fmt, vc) [fmt] = { 1, 1, 1, vc, CHIP_R600 } @@ -321,6 +324,9 @@ static void r600_cs_track_init(struct r600_cs_track *track) track->db_depth_size_idx = 0; track->db_depth_control = 0xFFFFFFFF; track->db_dirty = true; + track->htile_bo = NULL; + track->htile_offset = 0xFFFFFFFF; + track->htile_surface = 0; for (i = 0; i < 4; i++) { track->vgt_strmout_size[i] = 0; @@ -455,12 +461,256 @@ static int r600_cs_track_validate_cb(struct radeon_cs_parser *p, int i) return 0; } +static int r600_cs_track_validate_db(struct radeon_cs_parser *p) +{ + struct r600_cs_track *track = p->track; + u32 nviews, bpe, ntiles, size, slice_tile_max, tmp; + u32 height_align, pitch_align, depth_align; + u32 pitch = 8192; + u32 height = 8192; + u64 base_offset, base_align; + struct array_mode_checker array_check; + int array_mode; + volatile u32 *ib = p->ib->ptr; + + + if (track->db_bo == NULL) { + dev_warn(p->dev, "z/stencil with no depth buffer\n"); + return -EINVAL; + } + switch (G_028010_FORMAT(track->db_depth_info)) { + case V_028010_DEPTH_16: + bpe = 2; + break; + case V_028010_DEPTH_X8_24: + case V_028010_DEPTH_8_24: + case V_028010_DEPTH_X8_24_FLOAT: + case V_028010_DEPTH_8_24_FLOAT: + case V_028010_DEPTH_32_FLOAT: + bpe = 4; + break; + case V_028010_DEPTH_X24_8_32_FLOAT: + bpe = 8; + break; + default: + dev_warn(p->dev, "z/stencil with invalid format %d\n", G_028010_FORMAT(track->db_depth_info)); + return -EINVAL; + } + if ((track->db_depth_size & 0xFFFFFC00) == 0xFFFFFC00) { + if (!track->db_depth_size_idx) { + dev_warn(p->dev, "z/stencil buffer size not set\n"); + return -EINVAL; + } + tmp = radeon_bo_size(track->db_bo) - track->db_offset; + tmp = (tmp / bpe) >> 6; + if (!tmp) { + dev_warn(p->dev, "z/stencil buffer too small (0x%08X %d %d %ld)\n", + track->db_depth_size, bpe, track->db_offset, + radeon_bo_size(track->db_bo)); + return -EINVAL; + } + ib[track->db_depth_size_idx] = S_028000_SLICE_TILE_MAX(tmp - 1) | (track->db_depth_size & 0x3FF); + } else { + size = radeon_bo_size(track->db_bo); + /* pitch in pixels */ + pitch = (G_028000_PITCH_TILE_MAX(track->db_depth_size) + 1) * 8; + slice_tile_max = G_028000_SLICE_TILE_MAX(track->db_depth_size) + 1; + slice_tile_max *= 64; + height = slice_tile_max / pitch; + if (height > 8192) + height = 8192; + base_offset = track->db_bo_mc + track->db_offset; + array_mode = G_028010_ARRAY_MODE(track->db_depth_info); + array_check.array_mode = array_mode; + array_check.group_size = track->group_size; + array_check.nbanks = track->nbanks; + array_check.npipes = track->npipes; + array_check.nsamples = track->nsamples; + array_check.blocksize = bpe; + if (r600_get_array_mode_alignment(&array_check, + &pitch_align, &height_align, &depth_align, &base_align)) { + dev_warn(p->dev, "%s invalid tiling %d (0x%08X)\n", __func__, + G_028010_ARRAY_MODE(track->db_depth_info), + track->db_depth_info); + return -EINVAL; + } + switch (array_mode) { + case V_028010_ARRAY_1D_TILED_THIN1: + /* don't break userspace */ + height &= ~0x7; + break; + case V_028010_ARRAY_2D_TILED_THIN1: + break; + default: + dev_warn(p->dev, "%s invalid tiling %d (0x%08X)\n", __func__, + G_028010_ARRAY_MODE(track->db_depth_info), + track->db_depth_info); + return -EINVAL; + } + + if (!IS_ALIGNED(pitch, pitch_align)) { + dev_warn(p->dev, "%s:%d db pitch (%d, 0x%x, %d) invalid\n", + __func__, __LINE__, pitch, pitch_align, array_mode); + return -EINVAL; + } + if (!IS_ALIGNED(height, height_align)) { + dev_warn(p->dev, "%s:%d db height (%d, 0x%x, %d) invalid\n", + __func__, __LINE__, height, height_align, array_mode); + return -EINVAL; + } + if (!IS_ALIGNED(base_offset, base_align)) { + dev_warn(p->dev, "%s offset 0x%llx, 0x%llx, %d not aligned\n", __func__, + base_offset, base_align, array_mode); + return -EINVAL; + } + + ntiles = G_028000_SLICE_TILE_MAX(track->db_depth_size) + 1; + nviews = G_028004_SLICE_MAX(track->db_depth_view) + 1; + tmp = ntiles * bpe * 64 * nviews; + if ((tmp + track->db_offset) > radeon_bo_size(track->db_bo)) { + dev_warn(p->dev, "z/stencil buffer (%d) too small (0x%08X %d %d %d -> %u have %lu)\n", + array_mode, + track->db_depth_size, ntiles, nviews, bpe, tmp + track->db_offset, + radeon_bo_size(track->db_bo)); + return -EINVAL; + } + } + + /* hyperz */ + if (G_028010_TILE_SURFACE_ENABLE(track->db_depth_info)) { + unsigned long size; + unsigned nbx, nby; + + if (track->htile_bo == NULL) { + dev_warn(p->dev, "%s:%d htile enabled without htile surface 0x%08x\n", + __func__, __LINE__, track->db_depth_info); + return -EINVAL; + } + if ((track->db_depth_size & 0xFFFFFC00) == 0xFFFFFC00) { + dev_warn(p->dev, "%s:%d htile can't be enabled with bogus db_depth_size 0x%08x\n", + __func__, __LINE__, track->db_depth_size); + return -EINVAL; + } + + nbx = pitch; + nby = height; + if (G_028D24_LINEAR(track->htile_surface)) { + /* nbx must be 16 htiles aligned == 16 * 8 pixel aligned */ + nbx = round_up(nbx, 16 * 8); + /* nby is npipes htiles aligned == npipes * 8 pixel aligned */ + nby = round_up(nby, track->npipes * 8); + } else { + /* htile widht & nby (8 or 4) make 2 bits number */ + tmp = track->htile_surface & 3; + /* align is htile align * 8, htile align vary according to + * number of pipe and tile width and nby + */ + switch (track->npipes) { + case 8: + switch (tmp) { + case 3: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 8*/ + nbx = round_up(nbx, 64 * 8); + nby = round_up(nby, 64 * 8); + break; + case 2: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 8*/ + case 1: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 64 * 8); + nby = round_up(nby, 32 * 8); + break; + case 0: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 32 * 8); + break; + default: + return -EINVAL; + } + break; + case 4: + switch (tmp) { + case 3: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 8*/ + nbx = round_up(nbx, 64 * 8); + nby = round_up(nby, 32 * 8); + break; + case 2: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 8*/ + case 1: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 32 * 8); + break; + case 0: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 16 * 8); + break; + default: + return -EINVAL; + } + break; + case 2: + switch (tmp) { + case 3: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 8*/ + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 32 * 8); + break; + case 2: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 8*/ + case 1: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 16 * 8); + break; + case 0: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 16 * 8); + nby = round_up(nby, 16 * 8); + break; + default: + return -EINVAL; + } + break; + case 1: + switch (tmp) { + case 3: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 8*/ + nbx = round_up(nbx, 32 * 8); + nby = round_up(nby, 16 * 8); + break; + case 2: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 8*/ + case 1: /* HTILE_WIDTH = 8 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 16 * 8); + nby = round_up(nby, 16 * 8); + break; + case 0: /* HTILE_WIDTH = 4 & HTILE_HEIGHT = 4*/ + nbx = round_up(nbx, 16 * 8); + nby = round_up(nby, 8 * 8); + break; + default: + return -EINVAL; + } + break; + default: + dev_warn(p->dev, "%s:%d invalid num pipes %d\n", + __func__, __LINE__, track->npipes); + return -EINVAL; + } + } + /* compute number of htile */ + nbx = G_028D24_HTILE_WIDTH(track->htile_surface) ? nbx / 8 : nbx / 4; + nby = G_028D24_HTILE_HEIGHT(track->htile_surface) ? nby / 8 : nby / 4; + size = nbx * nby * 4; + size += track->htile_offset; + + if (size > radeon_bo_size(track->htile_bo)) { + dev_warn(p->dev, "%s:%d htile surface too small %ld for %ld (%d %d)\n", + __func__, __LINE__, radeon_bo_size(track->htile_bo), + size, nbx, nby); + return -EINVAL; + } + } + + track->db_dirty = false; + return 0; +} + static int r600_cs_track_check(struct radeon_cs_parser *p) { struct r600_cs_track *track = p->track; u32 tmp; int r, i; - volatile u32 *ib = p->ib->ptr; /* on legacy kernel we don't perform advanced check */ if (p->rdev == NULL) @@ -513,124 +763,14 @@ static int r600_cs_track_check(struct radeon_cs_parser *p) track->cb_dirty = false; } - if (track->db_dirty) { - /* Check depth buffer */ - if (G_028800_STENCIL_ENABLE(track->db_depth_control) || - G_028800_Z_ENABLE(track->db_depth_control)) { - u32 nviews, bpe, ntiles, size, slice_tile_max; - u32 height, height_align, pitch, pitch_align, depth_align; - u64 base_offset, base_align; - struct array_mode_checker array_check; - int array_mode; - - if (track->db_bo == NULL) { - dev_warn(p->dev, "z/stencil with no depth buffer\n"); - return -EINVAL; - } - if (G_028010_TILE_SURFACE_ENABLE(track->db_depth_info)) { - dev_warn(p->dev, "this kernel doesn't support z/stencil htile\n"); - return -EINVAL; - } - switch (G_028010_FORMAT(track->db_depth_info)) { - case V_028010_DEPTH_16: - bpe = 2; - break; - case V_028010_DEPTH_X8_24: - case V_028010_DEPTH_8_24: - case V_028010_DEPTH_X8_24_FLOAT: - case V_028010_DEPTH_8_24_FLOAT: - case V_028010_DEPTH_32_FLOAT: - bpe = 4; - break; - case V_028010_DEPTH_X24_8_32_FLOAT: - bpe = 8; - break; - default: - dev_warn(p->dev, "z/stencil with invalid format %d\n", G_028010_FORMAT(track->db_depth_info)); - return -EINVAL; - } - if ((track->db_depth_size & 0xFFFFFC00) == 0xFFFFFC00) { - if (!track->db_depth_size_idx) { - dev_warn(p->dev, "z/stencil buffer size not set\n"); - return -EINVAL; - } - tmp = radeon_bo_size(track->db_bo) - track->db_offset; - tmp = (tmp / bpe) >> 6; - if (!tmp) { - dev_warn(p->dev, "z/stencil buffer too small (0x%08X %d %d %ld)\n", - track->db_depth_size, bpe, track->db_offset, - radeon_bo_size(track->db_bo)); - return -EINVAL; - } - ib[track->db_depth_size_idx] = S_028000_SLICE_TILE_MAX(tmp - 1) | (track->db_depth_size & 0x3FF); - } else { - size = radeon_bo_size(track->db_bo); - /* pitch in pixels */ - pitch = (G_028000_PITCH_TILE_MAX(track->db_depth_size) + 1) * 8; - slice_tile_max = G_028000_SLICE_TILE_MAX(track->db_depth_size) + 1; - slice_tile_max *= 64; - height = slice_tile_max / pitch; - if (height > 8192) - height = 8192; - base_offset = track->db_bo_mc + track->db_offset; - array_mode = G_028010_ARRAY_MODE(track->db_depth_info); - array_check.array_mode = array_mode; - array_check.group_size = track->group_size; - array_check.nbanks = track->nbanks; - array_check.npipes = track->npipes; - array_check.nsamples = track->nsamples; - array_check.blocksize = bpe; - if (r600_get_array_mode_alignment(&array_check, - &pitch_align, &height_align, &depth_align, &base_align)) { - dev_warn(p->dev, "%s invalid tiling %d (0x%08X)\n", __func__, - G_028010_ARRAY_MODE(track->db_depth_info), - track->db_depth_info); - return -EINVAL; - } - switch (array_mode) { - case V_028010_ARRAY_1D_TILED_THIN1: - /* don't break userspace */ - height &= ~0x7; - break; - case V_028010_ARRAY_2D_TILED_THIN1: - break; - default: - dev_warn(p->dev, "%s invalid tiling %d (0x%08X)\n", __func__, - G_028010_ARRAY_MODE(track->db_depth_info), - track->db_depth_info); - return -EINVAL; - } - - if (!IS_ALIGNED(pitch, pitch_align)) { - dev_warn(p->dev, "%s:%d db pitch (%d, 0x%x, %d) invalid\n", - __func__, __LINE__, pitch, pitch_align, array_mode); - return -EINVAL; - } - if (!IS_ALIGNED(height, height_align)) { - dev_warn(p->dev, "%s:%d db height (%d, 0x%x, %d) invalid\n", - __func__, __LINE__, height, height_align, array_mode); - return -EINVAL; - } - if (!IS_ALIGNED(base_offset, base_align)) { - dev_warn(p->dev, "%s offset[%d] 0x%llx, 0x%llx, %d not aligned\n", __func__, i, - base_offset, base_align, array_mode); - return -EINVAL; - } - - ntiles = G_028000_SLICE_TILE_MAX(track->db_depth_size) + 1; - nviews = G_028004_SLICE_MAX(track->db_depth_view) + 1; - tmp = ntiles * bpe * 64 * nviews; - if ((tmp + track->db_offset) > radeon_bo_size(track->db_bo)) { - dev_warn(p->dev, "z/stencil buffer (%d) too small (0x%08X %d %d %d -> %u have %lu)\n", - array_mode, - track->db_depth_size, ntiles, nviews, bpe, tmp + track->db_offset, - radeon_bo_size(track->db_bo)); - return -EINVAL; - } - } - } - track->db_dirty = false; + /* Check depth buffer */ + if (track->db_dirty && (G_028800_STENCIL_ENABLE(track->db_depth_control) || + G_028800_Z_ENABLE(track->db_depth_control))) { + r = r600_cs_track_validate_db(p); + if (r) + return r; } + return 0; } @@ -1244,6 +1384,21 @@ static int r600_cs_check_reg(struct radeon_cs_parser *p, u32 reg, u32 idx) track->db_dirty = true; break; case DB_HTILE_DATA_BASE: + r = r600_cs_packet_next_reloc(p, &reloc); + if (r) { + dev_warn(p->dev, "bad SET_CONTEXT_REG " + "0x%04X\n", reg); + return -EINVAL; + } + track->htile_offset = radeon_get_ib_value(p, idx) << 8; + ib[idx] += (u32)((reloc->lobj.gpu_offset >> 8) & 0xffffffff); + track->htile_bo = reloc->robj; + track->db_dirty = true; + break; + case DB_HTILE_SURFACE: + track->htile_surface = radeon_get_ib_value(p, idx); + track->db_dirty = true; + break; case SQ_PGM_START_FS: case SQ_PGM_START_ES: case SQ_PGM_START_VS: diff --git a/drivers/gpu/drm/radeon/r600d.h b/drivers/gpu/drm/radeon/r600d.h index 3568a2e345fa..59f9c993cc31 100644 --- a/drivers/gpu/drm/radeon/r600d.h +++ b/drivers/gpu/drm/radeon/r600d.h @@ -195,6 +195,14 @@ #define PREZ_MUST_WAIT_FOR_POSTZ_DONE (1 << 31) #define DB_DEPTH_BASE 0x2800C #define DB_HTILE_DATA_BASE 0x28014 +#define DB_HTILE_SURFACE 0x28D24 +#define S_028D24_HTILE_WIDTH(x) (((x) & 0x1) << 0) +#define G_028D24_HTILE_WIDTH(x) (((x) >> 0) & 0x1) +#define C_028D24_HTILE_WIDTH 0xFFFFFFFE +#define S_028D24_HTILE_HEIGHT(x) (((x) & 0x1) << 1) +#define G_028D24_HTILE_HEIGHT(x) (((x) >> 1) & 0x1) +#define C_028D24_HTILE_HEIGHT 0xFFFFFFFD +#define G_028D24_LINEAR(x) (((x) >> 2) & 0x1) #define DB_WATERMARKS 0x9838 #define DEPTH_FREE(x) ((x) << 0) #define DEPTH_FLUSH(x) ((x) << 5) diff --git a/drivers/gpu/drm/radeon/radeon_object.c b/drivers/gpu/drm/radeon/radeon_object.c index 91541e63d582..6f70158d34e4 100644 --- a/drivers/gpu/drm/radeon/radeon_object.c +++ b/drivers/gpu/drm/radeon/radeon_object.c @@ -233,7 +233,17 @@ int radeon_bo_pin_restricted(struct radeon_bo *bo, u32 domain, u64 max_offset, bo->pin_count++; if (gpu_addr) *gpu_addr = radeon_bo_gpu_offset(bo); - WARN_ON_ONCE(max_offset != 0); + + if (max_offset != 0) { + u64 domain_start; + + if (domain == RADEON_GEM_DOMAIN_VRAM) + domain_start = bo->rdev->mc.vram_start; + else + domain_start = bo->rdev->mc.gtt_start; + WARN_ON_ONCE((*gpu_addr - domain_start) > max_offset); + } + return 0; } radeon_ttm_placement_from_domain(bo, domain); diff --git a/drivers/gpu/drm/radeon/reg_srcs/cayman b/drivers/gpu/drm/radeon/reg_srcs/cayman index aea63c415852..0f656b111c15 100644 --- a/drivers/gpu/drm/radeon/reg_srcs/cayman +++ b/drivers/gpu/drm/radeon/reg_srcs/cayman @@ -509,7 +509,6 @@ cayman 0x9400 0x00028AA8 IA_MULTI_VGT_PARAM 0x00028AB4 VGT_REUSE_OFF 0x00028AB8 VGT_VTX_CNT_EN -0x00028ABC DB_HTILE_SURFACE 0x00028AC0 DB_SRESULTS_COMPARE_STATE0 0x00028AC4 DB_SRESULTS_COMPARE_STATE1 0x00028AC8 DB_PRELOAD_CONTROL diff --git a/drivers/gpu/drm/radeon/reg_srcs/evergreen b/drivers/gpu/drm/radeon/reg_srcs/evergreen index 77c37202376f..b912a37689bf 100644 --- a/drivers/gpu/drm/radeon/reg_srcs/evergreen +++ b/drivers/gpu/drm/radeon/reg_srcs/evergreen @@ -519,7 +519,6 @@ evergreen 0x9400 0x00028AA4 VGT_INSTANCE_STEP_RATE_1 0x00028AB4 VGT_REUSE_OFF 0x00028AB8 VGT_VTX_CNT_EN -0x00028ABC DB_HTILE_SURFACE 0x00028AC0 DB_SRESULTS_COMPARE_STATE0 0x00028AC4 DB_SRESULTS_COMPARE_STATE1 0x00028AC8 DB_PRELOAD_CONTROL diff --git a/drivers/gpu/drm/radeon/reg_srcs/r600 b/drivers/gpu/drm/radeon/reg_srcs/r600 index 626c24ea0b56..5e659b034d9a 100644 --- a/drivers/gpu/drm/radeon/reg_srcs/r600 +++ b/drivers/gpu/drm/radeon/reg_srcs/r600 @@ -713,7 +713,6 @@ r600 0x9400 0x0000A710 TD_VS_SAMPLER17_BORDER_RED 0x00009508 TA_CNTL_AUX 0x0002802C DB_DEPTH_CLEAR -0x00028D24 DB_HTILE_SURFACE 0x00028D34 DB_PREFETCH_LIMIT 0x00028D30 DB_PRELOAD_CONTROL 0x00028D0C DB_RENDER_CONTROL diff --git a/drivers/gpu/drm/radeon/si_blit_shaders.c b/drivers/gpu/drm/radeon/si_blit_shaders.c index a7124b483adf..ec415e7dfa4b 100644 --- a/drivers/gpu/drm/radeon/si_blit_shaders.c +++ b/drivers/gpu/drm/radeon/si_blit_shaders.c @@ -25,6 +25,7 @@ */ #include <linux/types.h> +#include <linux/bug.h> #include <linux/kernel.h> const u32 si_default_state[] = diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 70ca07f10d5c..4da66b4b977c 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2026,6 +2026,16 @@ static bool hid_ignore(struct hid_device *hdev) if (hdev->product >= USB_DEVICE_ID_LOGITECH_HARMONY_FIRST && hdev->product <= USB_DEVICE_ID_LOGITECH_HARMONY_LAST) return true; + /* + * The Keene FM transmitter USB device has the same USB ID as + * the Logitech AudioHub Speaker, but it should ignore the hid. + * Check if the name is that of the Keene device. + * For reference: the name of the AudioHub is + * "HOLTEK AudioHub Speaker". + */ + if (hdev->product == USB_DEVICE_ID_LOGITECH_AUDIOHUB && + !strcmp(hdev->name, "HOLTEK B-LINK USB Audio ")) + return true; break; case USB_VENDOR_ID_SOUNDGRAPH: if (hdev->product >= USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST && diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 2a5cef2f53a7..e39aecb1f9f2 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -471,6 +471,7 @@ #define USB_DEVICE_ID_LG_MULTITOUCH 0x0064 #define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e #define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101 #define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110 #define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 811e6c47e7e6..5b32d56dbb4d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -648,7 +648,8 @@ config SENSORS_LM90 LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A, Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659, MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008, - Winbond/Nuvoton W83L771W/G/AWG/ASG and Philips SA56004 sensor chips. + Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, and GMT G781 + sensor chips. This driver can also be built as a module. If so, the module will be called lm90. @@ -812,6 +813,16 @@ config SENSORS_MAX6650 This driver can also be built as a module. If so, the module will be called max6650. +config SENSORS_MCP3021 + tristate "Microchip MCP3021" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the MCP3021 chip + that is a A/D converter (ADC) with 10-bit resolution. + + This driver can also be built as a module. If so, the module + will be called mcp3021. + config SENSORS_NTC_THERMISTOR tristate "NTC thermistor support" depends on EXPERIMENTAL @@ -1229,18 +1240,19 @@ config SENSORS_W83795 depends on I2C && EXPERIMENTAL help If you say yes here you get support for the Winbond W83795G and - W83795ADG hardware monitoring chip. + W83795ADG hardware monitoring chip, including manual fan speed + control. This driver can also be built as a module. If so, the module will be called w83795. config SENSORS_W83795_FANCTRL - boolean "Include fan control support (DANGEROUS)" + boolean "Include automatic fan control support (DANGEROUS)" depends on SENSORS_W83795 && EXPERIMENTAL default n help - If you say yes here, support for the both manual and automatic - fan control features will be included in the driver. + If you say yes here, support for automatic fan speed control + will be included in the driver. This part of the code wasn't carefully reviewed and tested yet, so enabling this option is strongly discouraged on production @@ -1358,10 +1370,10 @@ config SENSORS_APPLESMC the awesome power of applesmc. config SENSORS_MC13783_ADC - tristate "Freescale MC13783 ADC" - depends on MFD_MC13783 + tristate "Freescale MC13783/MC13892 ADC" + depends on MFD_MC13XXX help - Support for the A/D converter on MC13783 PMIC. + Support for the A/D converter on MC13783 and MC13892 PMIC. if ACPI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8251ce8cd035..6d3f11f71815 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -95,6 +95,7 @@ obj-$(CONFIG_SENSORS_MAX6639) += max6639.o obj-$(CONFIG_SENSORS_MAX6642) += max6642.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o +obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c index 523f8fb9e7d9..b7494af1e4a9 100644 --- a/drivers/hwmon/fam15h_power.c +++ b/drivers/hwmon/fam15h_power.c @@ -60,15 +60,15 @@ static ssize_t show_power(struct device *dev, pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5), REG_TDP_RUNNING_AVERAGE, &val); running_avg_capture = (val >> 4) & 0x3fffff; - running_avg_capture = sign_extend32(running_avg_capture, 22); - running_avg_range = val & 0xf; + running_avg_capture = sign_extend32(running_avg_capture, 21); + running_avg_range = (val & 0xf) + 1; pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5), REG_TDP_LIMIT3, &val); tdp_limit = val >> 16; - curr_pwr_watts = tdp_limit + data->base_tdp - - (s32)(running_avg_capture >> (running_avg_range + 1)); + curr_pwr_watts = (tdp_limit + data->base_tdp) << running_avg_range; + curr_pwr_watts -= running_avg_capture; curr_pwr_watts *= data->tdp_to_watts; /* @@ -78,7 +78,7 @@ static ssize_t show_power(struct device *dev, * scaling factor 1/(2^16). For conversion we use * (10^6)/(2^16) = 15625/(2^10) */ - curr_pwr_watts = (curr_pwr_watts * 15625) >> 10; + curr_pwr_watts = (curr_pwr_watts * 15625) >> (10 + running_avg_range); return sprintf(buf, "%u\n", (unsigned int) curr_pwr_watts); } static DEVICE_ATTR(power1_input, S_IRUGO, show_power, NULL); diff --git a/drivers/hwmon/lm63.c b/drivers/hwmon/lm63.c index 15c05cc83e2c..602a0f0b0de8 100644 --- a/drivers/hwmon/lm63.c +++ b/drivers/hwmon/lm63.c @@ -148,46 +148,9 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END }; #define UPDATE_INTERVAL(max, rate) \ ((1000 << (LM63_MAX_CONVRATE - (rate))) / (max)) -/* - * Functions declaration - */ - -static int lm63_probe(struct i2c_client *client, - const struct i2c_device_id *id); -static int lm63_remove(struct i2c_client *client); - -static struct lm63_data *lm63_update_device(struct device *dev); - -static int lm63_detect(struct i2c_client *client, struct i2c_board_info *info); -static void lm63_init_client(struct i2c_client *client); - enum chips { lm63, lm64, lm96163 }; /* - * Driver data (common to all clients) - */ - -static const struct i2c_device_id lm63_id[] = { - { "lm63", lm63 }, - { "lm64", lm64 }, - { "lm96163", lm96163 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, lm63_id); - -static struct i2c_driver lm63_driver = { - .class = I2C_CLASS_HWMON, - .driver = { - .name = "lm63", - }, - .probe = lm63_probe, - .remove = lm63_remove, - .id_table = lm63_id, - .detect = lm63_detect, - .address_list = normal_i2c, -}; - -/* * Client data (each client gets its own) */ @@ -242,6 +205,145 @@ static inline int lut_temp_from_reg(struct lm63_data *data, int nr) return data->temp8[nr] * (data->lut_temp_highres ? 500 : 1000); } +static inline int lut_temp_to_reg(struct lm63_data *data, long val) +{ + val -= data->temp2_offset; + if (data->lut_temp_highres) + return DIV_ROUND_CLOSEST(SENSORS_LIMIT(val, 0, 127500), 500); + else + return DIV_ROUND_CLOSEST(SENSORS_LIMIT(val, 0, 127000), 1000); +} + +/* + * Update the lookup table register cache. + * client->update_lock must be held when calling this function. + */ +static void lm63_update_lut(struct i2c_client *client) +{ + struct lm63_data *data = i2c_get_clientdata(client); + int i; + + if (time_after(jiffies, data->lut_last_updated + 5 * HZ) || + !data->lut_valid) { + for (i = 0; i < data->lut_size; i++) { + data->pwm1[1 + i] = i2c_smbus_read_byte_data(client, + LM63_REG_LUT_PWM(i)); + data->temp8[3 + i] = i2c_smbus_read_byte_data(client, + LM63_REG_LUT_TEMP(i)); + } + data->lut_temp_hyst = i2c_smbus_read_byte_data(client, + LM63_REG_LUT_TEMP_HYST); + + data->lut_last_updated = jiffies; + data->lut_valid = 1; + } +} + +static struct lm63_data *lm63_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm63_data *data = i2c_get_clientdata(client); + unsigned long next_update; + + mutex_lock(&data->update_lock); + + next_update = data->last_updated + + msecs_to_jiffies(data->update_interval) + 1; + + if (time_after(jiffies, next_update) || !data->valid) { + if (data->config & 0x04) { /* tachometer enabled */ + /* order matters for fan1_input */ + data->fan[0] = i2c_smbus_read_byte_data(client, + LM63_REG_TACH_COUNT_LSB) & 0xFC; + data->fan[0] |= i2c_smbus_read_byte_data(client, + LM63_REG_TACH_COUNT_MSB) << 8; + data->fan[1] = (i2c_smbus_read_byte_data(client, + LM63_REG_TACH_LIMIT_LSB) & 0xFC) + | (i2c_smbus_read_byte_data(client, + LM63_REG_TACH_LIMIT_MSB) << 8); + } + + data->pwm1_freq = i2c_smbus_read_byte_data(client, + LM63_REG_PWM_FREQ); + if (data->pwm1_freq == 0) + data->pwm1_freq = 1; + data->pwm1[0] = i2c_smbus_read_byte_data(client, + LM63_REG_PWM_VALUE); + + data->temp8[0] = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_TEMP); + data->temp8[1] = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_HIGH); + + /* order matters for temp2_input */ + data->temp11[0] = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TEMP_MSB) << 8; + data->temp11[0] |= i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TEMP_LSB); + data->temp11[1] = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_LOW_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_LOW_LSB); + data->temp11[2] = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_HIGH_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_HIGH_LSB); + data->temp11[3] = (i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_OFFSET_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_OFFSET_LSB); + + if (data->kind == lm96163) + data->temp11u = (i2c_smbus_read_byte_data(client, + LM96163_REG_REMOTE_TEMP_U_MSB) << 8) + | i2c_smbus_read_byte_data(client, + LM96163_REG_REMOTE_TEMP_U_LSB); + + data->temp8[2] = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TCRIT); + data->temp2_crit_hyst = i2c_smbus_read_byte_data(client, + LM63_REG_REMOTE_TCRIT_HYST); + + data->alarms = i2c_smbus_read_byte_data(client, + LM63_REG_ALERT_STATUS) & 0x7F; + + data->last_updated = jiffies; + data->valid = 1; + } + + lm63_update_lut(client); + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Trip points in the lookup table should be in ascending order for both + * temperatures and PWM output values. + */ +static int lm63_lut_looks_bad(struct i2c_client *client) +{ + struct lm63_data *data = i2c_get_clientdata(client); + int i; + + mutex_lock(&data->update_lock); + lm63_update_lut(client); + + for (i = 1; i < data->lut_size; i++) { + if (data->pwm1[1 + i - 1] > data->pwm1[1 + i] + || data->temp8[3 + i - 1] > data->temp8[3 + i]) { + dev_warn(&client->dev, + "Lookup table doesn't look sane (check entries %d and %d)\n", + i, i + 1); + break; + } + } + mutex_unlock(&data->update_lock); + + return i == data->lut_size ? 0 : 1; +} + /* * Sysfs callback functions and files */ @@ -294,13 +396,16 @@ static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr, return sprintf(buf, "%d\n", pwm); } -static ssize_t set_pwm1(struct device *dev, struct device_attribute *dummy, +static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct i2c_client *client = to_i2c_client(dev); struct lm63_data *data = i2c_get_clientdata(client); + int nr = attr->index; unsigned long val; int err; + u8 reg; if (!(data->config_fan & 0x20)) /* register is read-only */ return -EPERM; @@ -309,11 +414,13 @@ static ssize_t set_pwm1(struct device *dev, struct device_attribute *dummy, if (err) return err; + reg = nr ? LM63_REG_LUT_PWM(nr - 1) : LM63_REG_PWM_VALUE; val = SENSORS_LIMIT(val, 0, 255); + mutex_lock(&data->update_lock); - data->pwm1[0] = data->pwm_highres ? val : + data->pwm1[nr] = data->pwm_highres ? val : (val * data->pwm1_freq * 2 + 127) / 255; - i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE, data->pwm1[0]); + i2c_smbus_write_byte_data(client, reg, data->pwm1[nr]); mutex_unlock(&data->update_lock); return count; } @@ -325,6 +432,41 @@ static ssize_t show_pwm1_enable(struct device *dev, return sprintf(buf, "%d\n", data->config_fan & 0x20 ? 1 : 2); } +static ssize_t set_pwm1_enable(struct device *dev, + struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm63_data *data = i2c_get_clientdata(client); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val < 1 || val > 2) + return -EINVAL; + + /* + * Only let the user switch to automatic mode if the lookup table + * looks sane. + */ + if (val == 2 && lm63_lut_looks_bad(client)) + return -EPERM; + + mutex_lock(&data->update_lock); + data->config_fan = i2c_smbus_read_byte_data(client, + LM63_REG_CONFIG_FAN); + if (val == 1) + data->config_fan |= 0x20; + else + data->config_fan &= ~0x20; + i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN, + data->config_fan); + mutex_unlock(&data->update_lock); + return count; +} + /* * There are 8bit registers for both local(temp1) and remote(temp2) sensor. * For remote sensor registers temp2_offset has to be considered, @@ -367,23 +509,31 @@ static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr, struct i2c_client *client = to_i2c_client(dev); struct lm63_data *data = i2c_get_clientdata(client); int nr = attr->index; - int reg = nr == 2 ? LM63_REG_REMOTE_TCRIT : LM63_REG_LOCAL_HIGH; long val; int err; int temp; + u8 reg; err = kstrtol(buf, 10, &val); if (err) return err; mutex_lock(&data->update_lock); - if (nr == 2) { + switch (nr) { + case 2: + reg = LM63_REG_REMOTE_TCRIT; if (data->remote_unsigned) temp = TEMP8U_TO_REG(val - data->temp2_offset); else temp = TEMP8_TO_REG(val - data->temp2_offset); - } else { + break; + case 1: + reg = LM63_REG_LOCAL_HIGH; temp = TEMP8_TO_REG(val); + break; + default: /* lookup table */ + reg = LM63_REG_LUT_TEMP(nr - 3); + temp = lut_temp_to_reg(data, val); } data->temp8[nr] = temp; i2c_smbus_write_byte_data(client, reg, temp); @@ -613,65 +763,78 @@ static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan, set_fan, 1); static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1, 0); -static DEVICE_ATTR(pwm1_enable, S_IRUGO, show_pwm1_enable, NULL); -static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, show_pwm1, NULL, 1); -static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IRUGO, - show_lut_temp, NULL, 3); +static DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + show_pwm1_enable, set_pwm1_enable); +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 1); +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 3); static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 3); -static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IRUGO, show_pwm1, NULL, 2); -static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IRUGO, - show_lut_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 2); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 4); static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 4); -static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, show_pwm1, NULL, 3); -static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp, S_IRUGO, - show_lut_temp, NULL, 5); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 3); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 5); static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 5); -static SENSOR_DEVICE_ATTR(pwm1_auto_point4_pwm, S_IRUGO, show_pwm1, NULL, 4); -static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp, S_IRUGO, - show_lut_temp, NULL, 6); +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 4); +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 6); static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 6); -static SENSOR_DEVICE_ATTR(pwm1_auto_point5_pwm, S_IRUGO, show_pwm1, NULL, 5); -static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp, S_IRUGO, - show_lut_temp, NULL, 7); +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 5); +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 7); static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 7); -static SENSOR_DEVICE_ATTR(pwm1_auto_point6_pwm, S_IRUGO, show_pwm1, NULL, 6); -static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp, S_IRUGO, - show_lut_temp, NULL, 8); +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 6); +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 8); static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 8); -static SENSOR_DEVICE_ATTR(pwm1_auto_point7_pwm, S_IRUGO, show_pwm1, NULL, 7); -static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp, S_IRUGO, - show_lut_temp, NULL, 9); +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 7); +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 9); static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 9); -static SENSOR_DEVICE_ATTR(pwm1_auto_point8_pwm, S_IRUGO, show_pwm1, NULL, 8); -static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp, S_IRUGO, - show_lut_temp, NULL, 10); +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 8); +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 10); static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 10); -static SENSOR_DEVICE_ATTR(pwm1_auto_point9_pwm, S_IRUGO, show_pwm1, NULL, 9); -static SENSOR_DEVICE_ATTR(pwm1_auto_point9_temp, S_IRUGO, - show_lut_temp, NULL, 11); +static SENSOR_DEVICE_ATTR(pwm1_auto_point9_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 9); +static SENSOR_DEVICE_ATTR(pwm1_auto_point9_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 11); static SENSOR_DEVICE_ATTR(pwm1_auto_point9_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 11); -static SENSOR_DEVICE_ATTR(pwm1_auto_point10_pwm, S_IRUGO, show_pwm1, NULL, 10); -static SENSOR_DEVICE_ATTR(pwm1_auto_point10_temp, S_IRUGO, - show_lut_temp, NULL, 12); +static SENSOR_DEVICE_ATTR(pwm1_auto_point10_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 10); +static SENSOR_DEVICE_ATTR(pwm1_auto_point10_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 12); static SENSOR_DEVICE_ATTR(pwm1_auto_point10_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 12); -static SENSOR_DEVICE_ATTR(pwm1_auto_point11_pwm, S_IRUGO, show_pwm1, NULL, 11); -static SENSOR_DEVICE_ATTR(pwm1_auto_point11_temp, S_IRUGO, - show_lut_temp, NULL, 13); +static SENSOR_DEVICE_ATTR(pwm1_auto_point11_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 11); +static SENSOR_DEVICE_ATTR(pwm1_auto_point11_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 13); static SENSOR_DEVICE_ATTR(pwm1_auto_point11_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 13); -static SENSOR_DEVICE_ATTR(pwm1_auto_point12_pwm, S_IRUGO, show_pwm1, NULL, 12); -static SENSOR_DEVICE_ATTR(pwm1_auto_point12_temp, S_IRUGO, - show_lut_temp, NULL, 14); +static SENSOR_DEVICE_ATTR(pwm1_auto_point12_pwm, S_IWUSR | S_IRUGO, + show_pwm1, set_pwm1, 12); +static SENSOR_DEVICE_ATTR(pwm1_auto_point12_temp, S_IWUSR | S_IRUGO, + show_lut_temp, set_temp8, 14); static SENSOR_DEVICE_ATTR(pwm1_auto_point12_temp_hyst, S_IRUGO, show_lut_temp_hyst, NULL, 14); @@ -817,28 +980,25 @@ static const struct attribute_group lm63_group_fan1 = { */ /* Return 0 if detection is successful, -ENODEV otherwise */ -static int lm63_detect(struct i2c_client *new_client, +static int lm63_detect(struct i2c_client *client, struct i2c_board_info *info) { - struct i2c_adapter *adapter = new_client->adapter; + struct i2c_adapter *adapter = client->adapter; u8 man_id, chip_id, reg_config1, reg_config2; u8 reg_alert_status, reg_alert_mask; - int address = new_client->addr; + int address = client->addr; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV; - man_id = i2c_smbus_read_byte_data(new_client, LM63_REG_MAN_ID); - chip_id = i2c_smbus_read_byte_data(new_client, LM63_REG_CHIP_ID); + man_id = i2c_smbus_read_byte_data(client, LM63_REG_MAN_ID); + chip_id = i2c_smbus_read_byte_data(client, LM63_REG_CHIP_ID); - reg_config1 = i2c_smbus_read_byte_data(new_client, - LM63_REG_CONFIG1); - reg_config2 = i2c_smbus_read_byte_data(new_client, - LM63_REG_CONFIG2); - reg_alert_status = i2c_smbus_read_byte_data(new_client, + reg_config1 = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1); + reg_config2 = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG2); + reg_alert_status = i2c_smbus_read_byte_data(client, LM63_REG_ALERT_STATUS); - reg_alert_mask = i2c_smbus_read_byte_data(new_client, - LM63_REG_ALERT_MASK); + reg_alert_mask = i2c_smbus_read_byte_data(client, LM63_REG_ALERT_MASK); if (man_id != 0x01 /* National Semiconductor */ || (reg_config1 & 0x18) != 0x00 @@ -863,74 +1023,6 @@ static int lm63_detect(struct i2c_client *new_client, return 0; } -static int lm63_probe(struct i2c_client *new_client, - const struct i2c_device_id *id) -{ - struct lm63_data *data; - int err; - - data = kzalloc(sizeof(struct lm63_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto exit; - } - - i2c_set_clientdata(new_client, data); - data->valid = 0; - mutex_init(&data->update_lock); - - /* Set the device type */ - data->kind = id->driver_data; - if (data->kind == lm64) - data->temp2_offset = 16000; - - /* Initialize chip */ - lm63_init_client(new_client); - - /* Register sysfs hooks */ - err = sysfs_create_group(&new_client->dev.kobj, &lm63_group); - if (err) - goto exit_free; - if (data->config & 0x04) { /* tachometer enabled */ - err = sysfs_create_group(&new_client->dev.kobj, - &lm63_group_fan1); - if (err) - goto exit_remove_files; - } - if (data->kind == lm96163) { - err = device_create_file(&new_client->dev, - &dev_attr_temp2_type); - if (err) - goto exit_remove_files; - - err = sysfs_create_group(&new_client->dev.kobj, - &lm63_group_extra_lut); - if (err) - goto exit_remove_files; - } - - data->hwmon_dev = hwmon_device_register(&new_client->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - goto exit_remove_files; - } - - return 0; - -exit_remove_files: - sysfs_remove_group(&new_client->dev.kobj, &lm63_group); - sysfs_remove_group(&new_client->dev.kobj, &lm63_group_fan1); - if (data->kind == lm96163) { - device_remove_file(&new_client->dev, &dev_attr_temp2_type); - sysfs_remove_group(&new_client->dev.kobj, - &lm63_group_extra_lut); - } -exit_free: - kfree(data); -exit: - return err; -} - /* * Ideally we shouldn't have to initialize anything, since the BIOS * should have taken care of everything @@ -1010,114 +1102,110 @@ static void lm63_init_client(struct i2c_client *client) (data->config_fan & 0x20) ? "manual" : "auto"); } -static int lm63_remove(struct i2c_client *client) +static int lm63_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - struct lm63_data *data = i2c_get_clientdata(client); + struct lm63_data *data; + int err; - hwmon_device_unregister(data->hwmon_dev); + data = kzalloc(sizeof(struct lm63_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + data->valid = 0; + mutex_init(&data->update_lock); + + /* Set the device type */ + data->kind = id->driver_data; + if (data->kind == lm64) + data->temp2_offset = 16000; + + /* Initialize chip */ + lm63_init_client(client); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &lm63_group); + if (err) + goto exit_free; + if (data->config & 0x04) { /* tachometer enabled */ + err = sysfs_create_group(&client->dev.kobj, &lm63_group_fan1); + if (err) + goto exit_remove_files; + } + if (data->kind == lm96163) { + err = device_create_file(&client->dev, &dev_attr_temp2_type); + if (err) + goto exit_remove_files; + + err = sysfs_create_group(&client->dev.kobj, + &lm63_group_extra_lut); + if (err) + goto exit_remove_files; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: sysfs_remove_group(&client->dev.kobj, &lm63_group); sysfs_remove_group(&client->dev.kobj, &lm63_group_fan1); if (data->kind == lm96163) { device_remove_file(&client->dev, &dev_attr_temp2_type); sysfs_remove_group(&client->dev.kobj, &lm63_group_extra_lut); } - +exit_free: kfree(data); - return 0; +exit: + return err; } -static struct lm63_data *lm63_update_device(struct device *dev) +static int lm63_remove(struct i2c_client *client) { - struct i2c_client *client = to_i2c_client(dev); struct lm63_data *data = i2c_get_clientdata(client); - unsigned long next_update; - int i; - - mutex_lock(&data->update_lock); - - next_update = data->last_updated - + msecs_to_jiffies(data->update_interval) + 1; - - if (time_after(jiffies, next_update) || !data->valid) { - if (data->config & 0x04) { /* tachometer enabled */ - /* order matters for fan1_input */ - data->fan[0] = i2c_smbus_read_byte_data(client, - LM63_REG_TACH_COUNT_LSB) & 0xFC; - data->fan[0] |= i2c_smbus_read_byte_data(client, - LM63_REG_TACH_COUNT_MSB) << 8; - data->fan[1] = (i2c_smbus_read_byte_data(client, - LM63_REG_TACH_LIMIT_LSB) & 0xFC) - | (i2c_smbus_read_byte_data(client, - LM63_REG_TACH_LIMIT_MSB) << 8); - } - - data->pwm1_freq = i2c_smbus_read_byte_data(client, - LM63_REG_PWM_FREQ); - if (data->pwm1_freq == 0) - data->pwm1_freq = 1; - data->pwm1[0] = i2c_smbus_read_byte_data(client, - LM63_REG_PWM_VALUE); - - data->temp8[0] = i2c_smbus_read_byte_data(client, - LM63_REG_LOCAL_TEMP); - data->temp8[1] = i2c_smbus_read_byte_data(client, - LM63_REG_LOCAL_HIGH); - - /* order matters for temp2_input */ - data->temp11[0] = i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_TEMP_MSB) << 8; - data->temp11[0] |= i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_TEMP_LSB); - data->temp11[1] = (i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_LOW_MSB) << 8) - | i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_LOW_LSB); - data->temp11[2] = (i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_HIGH_MSB) << 8) - | i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_HIGH_LSB); - data->temp11[3] = (i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_OFFSET_MSB) << 8) - | i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_OFFSET_LSB); - - if (data->kind == lm96163) - data->temp11u = (i2c_smbus_read_byte_data(client, - LM96163_REG_REMOTE_TEMP_U_MSB) << 8) - | i2c_smbus_read_byte_data(client, - LM96163_REG_REMOTE_TEMP_U_LSB); - - data->temp8[2] = i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_TCRIT); - data->temp2_crit_hyst = i2c_smbus_read_byte_data(client, - LM63_REG_REMOTE_TCRIT_HYST); - - data->alarms = i2c_smbus_read_byte_data(client, - LM63_REG_ALERT_STATUS) & 0x7F; - data->last_updated = jiffies; - data->valid = 1; + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &lm63_group); + sysfs_remove_group(&client->dev.kobj, &lm63_group_fan1); + if (data->kind == lm96163) { + device_remove_file(&client->dev, &dev_attr_temp2_type); + sysfs_remove_group(&client->dev.kobj, &lm63_group_extra_lut); } - if (time_after(jiffies, data->lut_last_updated + 5 * HZ) || - !data->lut_valid) { - for (i = 0; i < data->lut_size; i++) { - data->pwm1[1 + i] = i2c_smbus_read_byte_data(client, - LM63_REG_LUT_PWM(i)); - data->temp8[3 + i] = i2c_smbus_read_byte_data(client, - LM63_REG_LUT_TEMP(i)); - } - data->lut_temp_hyst = i2c_smbus_read_byte_data(client, - LM63_REG_LUT_TEMP_HYST); + kfree(data); + return 0; +} - data->lut_last_updated = jiffies; - data->lut_valid = 1; - } +/* + * Driver data (common to all clients) + */ - mutex_unlock(&data->update_lock); +static const struct i2c_device_id lm63_id[] = { + { "lm63", lm63 }, + { "lm64", lm64 }, + { "lm96163", lm96163 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm63_id); - return data; -} +static struct i2c_driver lm63_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm63", + }, + .probe = lm63_probe, + .remove = lm63_remove, + .id_table = lm63_id, + .detect = lm63_detect, + .address_list = normal_i2c, +}; module_i2c_driver(lm63_driver); diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 248f2b40dfaf..22b14a68e35e 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -57,6 +57,9 @@ * This driver also supports the SA56004 from Philips. This device is * pin-compatible with the LM86, the ED/EDP parts are also address-compatible. * + * This driver also supports the G781 from GMT. This device is compatible + * with the ADM1032. + * * Since the LM90 was the first chipset supported by this driver, most * comments will refer to this chipset, but are actually general and * concern all supported chipsets, unless mentioned otherwise. @@ -107,7 +110,7 @@ static const unsigned short normal_i2c[] = { 0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, - max6646, w83l771, max6696, sa56004 }; + max6646, w83l771, max6696, sa56004, g781 }; /* * The LM90 registers @@ -184,6 +187,7 @@ static const struct i2c_device_id lm90_id[] = { { "adm1032", adm1032 }, { "adt7461", adt7461 }, { "adt7461a", adt7461 }, + { "g781", g781 }, { "lm90", lm90 }, { "lm86", lm86 }, { "lm89", lm86 }, @@ -229,6 +233,12 @@ static const struct lm90_params lm90_params[] = { .alert_alarms = 0x7c, .max_convrate = 10, }, + [g781] = { + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_BROKEN_ALERT, + .alert_alarms = 0x7c, + .max_convrate = 8, + }, [lm86] = { .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, .alert_alarms = 0x7b, @@ -308,22 +318,24 @@ struct lm90_data { /* registers values */ s8 temp8[8]; /* 0: local low limit - 1: local high limit - 2: local critical limit - 3: remote critical limit - 4: local emergency limit (max6659 and max6695/96) - 5: remote emergency limit (max6659 and max6695/96) - 6: remote 2 critical limit (max6695/96 only) - 7: remote 2 emergency limit (max6695/96 only) */ + * 1: local high limit + * 2: local critical limit + * 3: remote critical limit + * 4: local emergency limit (max6659 and max6695/96) + * 5: remote emergency limit (max6659 and max6695/96) + * 6: remote 2 critical limit (max6695/96 only) + * 7: remote 2 emergency limit (max6695/96 only) + */ s16 temp11[8]; /* 0: remote input - 1: remote low limit - 2: remote high limit - 3: remote offset (except max6646, max6657/58/59, - and max6695/96) - 4: local input - 5: remote 2 input (max6695/96 only) - 6: remote 2 low limit (max6695/96 only) - 7: remote 2 high limit (ma6695/96 only) */ + * 1: remote low limit + * 2: remote high limit + * 3: remote offset (except max6646, max6657/58/59, + * and max6695/96) + * 4: local input + * 5: remote 2 input (max6695/96 only) + * 6: remote 2 low limit (max6695/96 only) + * 7: remote 2 high limit (max6695/96 only) + */ u8 temp_hyst; u16 alarms; /* bitvector (upper 8 bits for max6695/96) */ }; @@ -533,8 +545,10 @@ static struct lm90_data *lm90_update_device(struct device *dev) data->alarms |= alarms << 8; } - /* Re-enable ALERT# output if it was originally enabled and - * relevant alarms are all clear */ + /* + * Re-enable ALERT# output if it was originally enabled and + * relevant alarms are all clear + */ if ((data->config_orig & 0x80) == 0 && (data->alarms & data->alert_alarms) == 0) { u8 config; @@ -1162,8 +1176,10 @@ static int lm90_detect(struct i2c_client *client, && (config1 & 0x3F) == 0x00 && convrate <= 0x0A) { name = "adm1032"; - /* The ADM1032 supports PEC, but only if combined - transactions are not used. */ + /* + * The ADM1032 supports PEC, but only if combined + * transactions are not used. + */ if (i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) info->flags |= I2C_CLIENT_PEC; @@ -1283,6 +1299,13 @@ static int lm90_detect(struct i2c_client *client, && convrate <= 0x09) { name = "sa56004"; } + } else + if ((address == 0x4C || address == 0x4D) + && man_id == 0x47) { /* GMT */ + if (chip_id == 0x01 /* G781 */ + && (config1 & 0x3F) == 0x00 + && convrate <= 0x08) + name = "g781"; } if (!name) { /* identification failed */ @@ -1313,6 +1336,15 @@ static void lm90_remove_files(struct i2c_client *client, struct lm90_data *data) sysfs_remove_group(&dev->kobj, &lm90_group); } +static void lm90_restore_conf(struct i2c_client *client, struct lm90_data *data) +{ + /* Restore initial configuration */ + i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE, + data->convrate_orig); + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, + data->config_orig); +} + static void lm90_init_client(struct i2c_client *client) { u8 config, convrate; @@ -1382,8 +1414,10 @@ static int lm90_probe(struct i2c_client *client, client->flags &= ~I2C_CLIENT_PEC; } - /* Different devices have different alarm bits triggering the - * ALERT# output */ + /* + * Different devices have different alarm bits triggering the + * ALERT# output + */ data->alert_alarms = lm90_params[data->kind].alert_alarms; /* Set chip capabilities */ @@ -1399,7 +1433,7 @@ static int lm90_probe(struct i2c_client *client, /* Register sysfs hooks */ err = sysfs_create_group(&dev->kobj, &lm90_group); if (err) - goto exit_free; + goto exit_restore; if (client->flags & I2C_CLIENT_PEC) { err = device_create_file(dev, &dev_attr_pec); if (err) @@ -1438,7 +1472,8 @@ static int lm90_probe(struct i2c_client *client, exit_remove_files: lm90_remove_files(client, data); -exit_free: +exit_restore: + lm90_restore_conf(client, data); kfree(data); exit: return err; @@ -1450,12 +1485,7 @@ static int lm90_remove(struct i2c_client *client) hwmon_device_unregister(data->hwmon_dev); lm90_remove_files(client, data); - - /* Restore initial configuration */ - i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE, - data->convrate_orig); - i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, - data->config_orig); + lm90_restore_conf(client, data); kfree(data); return 0; @@ -1488,9 +1518,11 @@ static void lm90_alert(struct i2c_client *client, unsigned int flag) dev_warn(&client->dev, "temp%d out of range, please check!\n", 3); - /* Disable ALERT# output, because these chips don't implement - SMBus alert correctly; they should only hold the alert line - low briefly. */ + /* + * Disable ALERT# output, because these chips don't implement + * SMBus alert correctly; they should only hold the alert line + * low briefly. + */ if ((data->flags & LM90_HAVE_BROKEN_ALERT) && (alarms & data->alert_alarms)) { dev_dbg(&client->dev, "Disabling ALERT#\n"); diff --git a/drivers/hwmon/mc13783-adc.c b/drivers/hwmon/mc13783-adc.c index ef65ab56b094..6c6b240a782e 100644 --- a/drivers/hwmon/mc13783-adc.c +++ b/drivers/hwmon/mc13783-adc.c @@ -1,5 +1,5 @@ /* - * Driver for the Freescale Semiconductor MC13783 adc. + * Driver for the ADC on Freescale Semiconductor MC13783 and MC13892 PMICs. * * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright (C) 2009 Sascha Hauer, Pengutronix @@ -18,7 +18,7 @@ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <linux/mfd/mc13783.h> +#include <linux/mfd/mc13xxx.h> #include <linux/platform_device.h> #include <linux/hwmon-sysfs.h> #include <linux/kernel.h> @@ -28,24 +28,30 @@ #include <linux/init.h> #include <linux/err.h> -#define MC13783_ADC_NAME "mc13783-adc" +#define DRIVER_NAME "mc13783-adc" + +/* platform device id driver data */ +#define MC13783_ADC_16CHANS 1 +#define MC13783_ADC_BPDIV2 2 struct mc13783_adc_priv { struct mc13xxx *mc13xxx; struct device *hwmon_dev; + char name[10]; }; static ssize_t mc13783_adc_show_name(struct device *dev, struct device_attribute *devattr, char *buf) { - return sprintf(buf, "mc13783_adc\n"); + struct mc13783_adc_priv *priv = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", priv->name); } static int mc13783_adc_read(struct device *dev, struct device_attribute *devattr, unsigned int *val) { - struct platform_device *pdev = to_platform_device(dev); - struct mc13783_adc_priv *priv = platform_get_drvdata(pdev); + struct mc13783_adc_priv *priv = dev_get_drvdata(dev); struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); unsigned int channel = attr->index; unsigned int sample[4]; @@ -68,16 +74,21 @@ static ssize_t mc13783_adc_read_bp(struct device *dev, struct device_attribute *devattr, char *buf) { unsigned val; + struct platform_device *pdev = to_platform_device(dev); + kernel_ulong_t driver_data = platform_get_device_id(pdev)->driver_data; int ret = mc13783_adc_read(dev, devattr, &val); if (ret) return ret; - /* - * BP (channel 2) reports with offset 2.4V to the actual value to fit - * the input range of the ADC. unit = 2.25mV = 9/4 mV. - */ - val = DIV_ROUND_CLOSEST(val * 9, 4) + 2400; + if (driver_data & MC13783_ADC_BPDIV2) + val = DIV_ROUND_CLOSEST(val * 9, 2); + else + /* + * BP (channel 2) reports with offset 2.4V to the actual value + * to fit the input range of the ADC. unit = 2.25mV = 9/4 mV. + */ + val = DIV_ROUND_CLOSEST(val * 9, 4) + 2400; return sprintf(buf, "%u\n", val); } @@ -114,12 +125,21 @@ static SENSOR_DEVICE_ATTR(in13_input, S_IRUGO, mc13783_adc_read_gp, NULL, 13); static SENSOR_DEVICE_ATTR(in14_input, S_IRUGO, mc13783_adc_read_gp, NULL, 14); static SENSOR_DEVICE_ATTR(in15_input, S_IRUGO, mc13783_adc_read_gp, NULL, 15); -static struct attribute *mc13783_attr[] = { +static struct attribute *mc13783_attr_base[] = { &dev_attr_name.attr, &sensor_dev_attr_in2_input.dev_attr.attr, &sensor_dev_attr_in5_input.dev_attr.attr, &sensor_dev_attr_in6_input.dev_attr.attr, &sensor_dev_attr_in7_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group mc13783_group_base = { + .attrs = mc13783_attr_base, +}; + +/* these are only used if MC13783_ADC_16CHANS is provided in driver data */ +static struct attribute *mc13783_attr_16chans[] = { &sensor_dev_attr_in8_input.dev_attr.attr, &sensor_dev_attr_in9_input.dev_attr.attr, &sensor_dev_attr_in10_input.dev_attr.attr, @@ -127,8 +147,8 @@ static struct attribute *mc13783_attr[] = { NULL }; -static const struct attribute_group mc13783_group = { - .attrs = mc13783_attr, +static const struct attribute_group mc13783_group_16chans = { + .attrs = mc13783_attr_16chans, }; /* last four channels may be occupied by the touchscreen */ @@ -156,24 +176,37 @@ static int __init mc13783_adc_probe(struct platform_device *pdev) { struct mc13783_adc_priv *priv; int ret; + const struct platform_device_id *id = platform_get_device_id(pdev); + char *dash; priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->mc13xxx = dev_get_drvdata(pdev->dev.parent); + snprintf(priv->name, ARRAY_SIZE(priv->name), "%s", id->name); + dash = strchr(priv->name, '-'); + if (dash) + *dash = '\0'; platform_set_drvdata(pdev, priv); /* Register sysfs hooks */ - ret = sysfs_create_group(&pdev->dev.kobj, &mc13783_group); + ret = sysfs_create_group(&pdev->dev.kobj, &mc13783_group_base); if (ret) - goto out_err_create1; + goto out_err_create_base; + + if (id->driver_data & MC13783_ADC_16CHANS) { + ret = sysfs_create_group(&pdev->dev.kobj, + &mc13783_group_16chans); + if (ret) + goto out_err_create_16chans; + } if (!mc13783_adc_use_touchscreen(pdev)) { ret = sysfs_create_group(&pdev->dev.kobj, &mc13783_group_ts); if (ret) - goto out_err_create2; + goto out_err_create_ts; } priv->hwmon_dev = hwmon_device_register(&pdev->dev); @@ -184,17 +217,20 @@ static int __init mc13783_adc_probe(struct platform_device *pdev) goto out_err_register; } - return 0; out_err_register: if (!mc13783_adc_use_touchscreen(pdev)) sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_ts); -out_err_create2: +out_err_create_ts: - sysfs_remove_group(&pdev->dev.kobj, &mc13783_group); -out_err_create1: + if (id->driver_data & MC13783_ADC_16CHANS) + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_16chans); +out_err_create_16chans: + + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_base); +out_err_create_base: platform_set_drvdata(pdev, NULL); kfree(priv); @@ -205,13 +241,17 @@ out_err_create1: static int __devexit mc13783_adc_remove(struct platform_device *pdev) { struct mc13783_adc_priv *priv = platform_get_drvdata(pdev); + kernel_ulong_t driver_data = platform_get_device_id(pdev)->driver_data; hwmon_device_unregister(priv->hwmon_dev); if (!mc13783_adc_use_touchscreen(pdev)) sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_ts); - sysfs_remove_group(&pdev->dev.kobj, &mc13783_group); + if (driver_data & MC13783_ADC_16CHANS) + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_16chans); + + sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_base); platform_set_drvdata(pdev, NULL); kfree(priv); @@ -219,12 +259,26 @@ static int __devexit mc13783_adc_remove(struct platform_device *pdev) return 0; } +static const struct platform_device_id mc13783_adc_idtable[] = { + { + .name = "mc13783-adc", + .driver_data = MC13783_ADC_16CHANS, + }, { + .name = "mc13892-adc", + .driver_data = MC13783_ADC_BPDIV2, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, mc13783_adc_idtable); + static struct platform_driver mc13783_adc_driver = { - .remove = __devexit_p(mc13783_adc_remove), + .remove = __devexit_p(mc13783_adc_remove), .driver = { .owner = THIS_MODULE, - .name = MC13783_ADC_NAME, + .name = DRIVER_NAME, }, + .id_table = mc13783_adc_idtable, }; static int __init mc13783_adc_init(void) @@ -243,4 +297,3 @@ module_exit(mc13783_adc_exit); MODULE_DESCRIPTION("MC13783 ADC driver"); MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" MC13783_ADC_NAME); diff --git a/drivers/hwmon/mcp3021.c b/drivers/hwmon/mcp3021.c new file mode 100644 index 000000000000..d0afc0cd3ff4 --- /dev/null +++ b/drivers/hwmon/mcp3021.c @@ -0,0 +1,171 @@ +/* + * mcp3021.c - driver for the Microchip MCP3021 chip + * + * Copyright (C) 2008-2009, 2012 Freescale Semiconductor, Inc. + * Author: Mingkai Hu <Mingkai.hu@freescale.com> + * + * This driver export the value of analog input voltage to sysfs, the + * voltage unit is mV. Through the sysfs interface, lm-sensors tool + * can also display the input voltage. + * + * 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/hwmon.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/device.h> + +/* Vdd info */ +#define MCP3021_VDD_MAX 5500 +#define MCP3021_VDD_MIN 2700 +#define MCP3021_VDD_REF 3300 + +/* output format */ +#define MCP3021_SAR_SHIFT 2 +#define MCP3021_SAR_MASK 0x3ff + +#define MCP3021_OUTPUT_RES 10 /* 10-bit resolution */ +#define MCP3021_OUTPUT_SCALE 4 + +/* + * Client data (each client gets its own) + */ +struct mcp3021_data { + struct device *hwmon_dev; + u32 vdd; /* device power supply */ +}; + +static int mcp3021_read16(struct i2c_client *client) +{ + int ret; + u16 reg; + __be16 buf; + + ret = i2c_master_recv(client, (char *)&buf, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + /* The output code of the MCP3021 is transmitted with MSB first. */ + reg = be16_to_cpu(buf); + + /* + * The ten-bit output code is composed of the lower 4-bit of the + * first byte and the upper 6-bit of the second byte. + */ + reg = (reg >> MCP3021_SAR_SHIFT) & MCP3021_SAR_MASK; + + return reg; +} + +static inline u16 volts_from_reg(u16 vdd, u16 val) +{ + if (val == 0) + return 0; + + val = val * MCP3021_OUTPUT_SCALE - MCP3021_OUTPUT_SCALE / 2; + + return val * DIV_ROUND_CLOSEST(vdd, + (1 << MCP3021_OUTPUT_RES) * MCP3021_OUTPUT_SCALE); +} + +static ssize_t show_in_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mcp3021_data *data = i2c_get_clientdata(client); + int reg, in_input; + + reg = mcp3021_read16(client); + if (reg < 0) + return reg; + + in_input = volts_from_reg(data->vdd, reg); + return sprintf(buf, "%d\n", in_input); +} + +static DEVICE_ATTR(in0_input, S_IRUGO, show_in_input, NULL); + +static int mcp3021_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct mcp3021_data *data = NULL; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + data = kzalloc(sizeof(struct mcp3021_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + + if (client->dev.platform_data) { + data->vdd = *(u32 *)client->dev.platform_data; + if (data->vdd > MCP3021_VDD_MAX || + data->vdd < MCP3021_VDD_MIN) { + err = -EINVAL; + goto exit_free; + } + } else + data->vdd = MCP3021_VDD_REF; + + err = sysfs_create_file(&client->dev.kobj, &dev_attr_in0_input.attr); + if (err) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + sysfs_remove_file(&client->dev.kobj, &dev_attr_in0_input.attr); +exit_free: + kfree(data); + return err; +} + +static int mcp3021_remove(struct i2c_client *client) +{ + struct mcp3021_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_file(&client->dev.kobj, &dev_attr_in0_input.attr); + kfree(data); + + return 0; +} + +static const struct i2c_device_id mcp3021_id[] = { + { "mcp3021", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp3021_id); + +static struct i2c_driver mcp3021_driver = { + .driver = { + .name = "mcp3021", + }, + .probe = mcp3021_probe, + .remove = mcp3021_remove, + .id_table = mcp3021_id, +}; + +module_i2c_driver(mcp3021_driver); + +MODULE_AUTHOR("Mingkai Hu <Mingkai.hu@freescale.com>"); +MODULE_DESCRIPTION("Microchip MCP3021 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/w83795.c b/drivers/hwmon/w83795.c index deb12c982800..d887cb3b72e8 100644 --- a/drivers/hwmon/w83795.c +++ b/drivers/hwmon/w83795.c @@ -72,8 +72,10 @@ MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended"); #define TEMP_CRIT_HYST 2 #define TEMP_WARN 3 #define TEMP_WARN_HYST 4 -/* only crit and crit_hyst affect real-time alarm status - * current crit crit_hyst warn warn_hyst */ +/* + * only crit and crit_hyst affect real-time alarm status + * current crit crit_hyst warn warn_hyst + */ static const u16 W83795_REG_TEMP[][5] = { {0x21, 0x96, 0x97, 0x98, 0x99}, /* TD1/TR1 */ {0x22, 0x9a, 0x9b, 0x9c, 0x9d}, /* TD2/TR2 */ @@ -354,26 +356,34 @@ struct w83795_data { u8 temp_mode; /* Bit vector, 0 = TR, 1 = TD */ u8 temp_src[3]; /* Register value */ - u8 enable_dts; /* Enable PECI and SB-TSI, + u8 enable_dts; /* + * Enable PECI and SB-TSI, * bit 0: =1 enable, =0 disable, - * bit 1: =1 AMD SB-TSI, =0 Intel PECI */ + * bit 1: =1 AMD SB-TSI, =0 Intel PECI + */ u8 has_dts; /* Enable monitor DTS temp */ s8 dts[8]; /* Register value */ u8 dts_read_vrlsb[8]; /* Register value */ s8 dts_ext[4]; /* Register value */ - u8 has_pwm; /* 795g supports 8 pwm, 795adg only supports 2, + u8 has_pwm; /* + * 795g supports 8 pwm, 795adg only supports 2, * no config register, only affected by chip - * type */ - u8 pwm[8][5]; /* Register value, output, freq, start, - * non stop, stop time */ + * type + */ + u8 pwm[8][5]; /* + * Register value, output, freq, start, + * non stop, stop time + */ u16 clkin; /* CLKIN frequency in kHz */ u8 pwm_fcms[2]; /* Register value */ u8 pwm_tfmr[6]; /* Register value */ u8 pwm_fomc; /* Register value */ - u16 target_speed[8]; /* Register value, target speed for speed - * cruise */ + u16 target_speed[8]; /* + * Register value, target speed for speed + * cruise + */ u8 tol_speed; /* tolerance of target speed */ u8 pwm_temp[6][4]; /* TTTI, CTFS, HCT, HOT */ u8 sf4_reg[6][2][7]; /* 6 temp, temp/dcpwm, 7 registers */ @@ -482,8 +492,10 @@ static void w83795_update_limits(struct i2c_client *client) /* Read the fan limits */ lsb = 0; /* Silent false gcc warning */ for (i = 0; i < ARRAY_SIZE(data->fan); i++) { - /* Each register contains LSB for 2 fans, but we want to - * read it only once to save time */ + /* + * Each register contains LSB for 2 fans, but we want to + * read it only once to save time + */ if ((i & 1) == 0 && (data->has_fan & (3 << i))) lsb = w83795_read(client, W83795_REG_FAN_MIN_LSB(i)); @@ -665,9 +677,11 @@ static struct w83795_data *w83795_update_device(struct device *dev) w83795_read(client, W83795_REG_PWM(i, PWM_OUTPUT)); } - /* Update intrusion and alarms + /* + * Update intrusion and alarms * It is important to read intrusion first, because reading from - * register SMI STS6 clears the interrupt status temporarily. */ + * register SMI STS6 clears the interrupt status temporarily. + */ tmp = w83795_read(client, W83795_REG_ALARM_CTRL); /* Switch to interrupt status for intrusion if needed */ if (tmp & ALARM_CTRL_RTSACS) @@ -929,6 +943,14 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, if (val < 1 || val > 2) return -EINVAL; +#ifndef CONFIG_SENSORS_W83795_FANCTRL + if (val > 1) { + dev_warn(dev, "Automatic fan speed control support disabled\n"); + dev_warn(dev, "Build with CONFIG_SENSORS_W83795_FANCTRL=y if you want it\n"); + return -EOPNOTSUPP; + } +#endif + mutex_lock(&data->update_lock); switch (val) { case 1: @@ -1595,8 +1617,10 @@ store_sf_setup(struct device *dev, struct device_attribute *attr, #define NOT_USED -1 -/* Don't change the attribute order, _max, _min and _beep are accessed by index - * somewhere else in the code */ +/* + * Don't change the attribute order, _max, _min and _beep are accessed by index + * somewhere else in the code + */ #define SENSOR_ATTR_IN(index) { \ SENSOR_ATTR_2(in##index##_input, S_IRUGO, show_in, NULL, \ IN_READ, index), \ @@ -1610,8 +1634,10 @@ store_sf_setup(struct device *dev, struct device_attribute *attr, show_alarm_beep, store_beep, BEEP_ENABLE, \ index + ((index > 14) ? 1 : 0)) } -/* Don't change the attribute order, _beep is accessed by index - * somewhere else in the code */ +/* + * Don't change the attribute order, _beep is accessed by index + * somewhere else in the code + */ #define SENSOR_ATTR_FAN(index) { \ SENSOR_ATTR_2(fan##index##_input, S_IRUGO, show_fan, \ NULL, FAN_INPUT, index - 1), \ @@ -1625,23 +1651,25 @@ store_sf_setup(struct device *dev, struct device_attribute *attr, #define SENSOR_ATTR_PWM(index) { \ SENSOR_ATTR_2(pwm##index, S_IWUSR | S_IRUGO, show_pwm, \ store_pwm, PWM_OUTPUT, index - 1), \ + SENSOR_ATTR_2(pwm##index##_enable, S_IWUSR | S_IRUGO, \ + show_pwm_enable, store_pwm_enable, NOT_USED, index - 1), \ + SENSOR_ATTR_2(pwm##index##_mode, S_IRUGO, \ + show_pwm_mode, NULL, NOT_USED, index - 1), \ + SENSOR_ATTR_2(pwm##index##_freq, S_IWUSR | S_IRUGO, \ + show_pwm, store_pwm, PWM_FREQ, index - 1), \ SENSOR_ATTR_2(pwm##index##_nonstop, S_IWUSR | S_IRUGO, \ show_pwm, store_pwm, PWM_NONSTOP, index - 1), \ SENSOR_ATTR_2(pwm##index##_start, S_IWUSR | S_IRUGO, \ show_pwm, store_pwm, PWM_START, index - 1), \ SENSOR_ATTR_2(pwm##index##_stop_time, S_IWUSR | S_IRUGO, \ show_pwm, store_pwm, PWM_STOP_TIME, index - 1), \ - SENSOR_ATTR_2(pwm##index##_freq, S_IWUSR | S_IRUGO, \ - show_pwm, store_pwm, PWM_FREQ, index - 1), \ - SENSOR_ATTR_2(pwm##index##_enable, S_IWUSR | S_IRUGO, \ - show_pwm_enable, store_pwm_enable, NOT_USED, index - 1), \ - SENSOR_ATTR_2(pwm##index##_mode, S_IRUGO, \ - show_pwm_mode, NULL, NOT_USED, index - 1), \ SENSOR_ATTR_2(fan##index##_target, S_IWUSR | S_IRUGO, \ show_fanin, store_fanin, FANIN_TARGET, index - 1) } -/* Don't change the attribute order, _beep is accessed by index - * somewhere else in the code */ +/* + * Don't change the attribute order, _beep is accessed by index + * somewhere else in the code + */ #define SENSOR_ATTR_DTS(index) { \ SENSOR_ATTR_2(temp##index##_type, S_IRUGO , \ show_dts_mode, NULL, NOT_USED, index - 7), \ @@ -1660,8 +1688,10 @@ store_sf_setup(struct device *dev, struct device_attribute *attr, SENSOR_ATTR_2(temp##index##_beep, S_IWUSR | S_IRUGO, \ show_alarm_beep, store_beep, BEEP_ENABLE, index + 17) } -/* Don't change the attribute order, _beep is accessed by index - * somewhere else in the code */ +/* + * Don't change the attribute order, _beep is accessed by index + * somewhere else in the code + */ #define SENSOR_ATTR_TEMP(index) { \ SENSOR_ATTR_2(temp##index##_type, S_IRUGO | (index < 4 ? S_IWUSR : 0), \ show_temp_mode, store_temp_mode, NOT_USED, index - 1), \ @@ -1867,8 +1897,10 @@ static int w83795_get_device_id(struct i2c_client *client) device_id = i2c_smbus_read_byte_data(client, W83795_REG_DEVICEID); - /* Special case for rev. A chips; can't be checked first because later - revisions emulate this for compatibility */ + /* + * Special case for rev. A chips; can't be checked first because later + * revisions emulate this for compatibility + */ if (device_id < 0 || (device_id & 0xf0) != 0x50) { int alt_id; @@ -1920,8 +1952,10 @@ static int w83795_detect(struct i2c_client *client, return -ENODEV; } - /* If Nuvoton chip, address of chip and W83795_REG_I2C_ADDR - should match */ + /* + * If Nuvoton chip, address of chip and W83795_REG_I2C_ADDR + * should match + */ if ((bank & 0x07) == 0) { i2c_addr = i2c_smbus_read_byte_data(client, W83795_REG_I2C_ADDR); @@ -1933,10 +1967,12 @@ static int w83795_detect(struct i2c_client *client, } } - /* Check 795 chip type: 795G or 795ADG - Usually we don't write to chips during detection, but here we don't - quite have the choice; hopefully it's OK, we are about to return - success anyway */ + /* + * Check 795 chip type: 795G or 795ADG + * Usually we don't write to chips during detection, but here we don't + * quite have the choice; hopefully it's OK, we are about to return + * success anyway + */ if ((bank & 0x07) != 0) i2c_smbus_write_byte_data(client, W83795_REG_BANKSEL, bank & ~0x07); @@ -1953,6 +1989,14 @@ static int w83795_detect(struct i2c_client *client, return 0; } +#ifdef CONFIG_SENSORS_W83795_FANCTRL +#define NUM_PWM_ATTRIBUTES ARRAY_SIZE(w83795_pwm[0]) +#define NUM_TEMP_ATTRIBUTES ARRAY_SIZE(w83795_temp[0]) +#else +#define NUM_PWM_ATTRIBUTES 4 +#define NUM_TEMP_ATTRIBUTES 8 +#endif + static int w83795_handle_files(struct device *dev, int (*fn)(struct device *, const struct device_attribute *)) { @@ -2006,24 +2050,18 @@ static int w83795_handle_files(struct device *dev, int (*fn)(struct device *, } } -#ifdef CONFIG_SENSORS_W83795_FANCTRL for (i = 0; i < data->has_pwm; i++) { - for (j = 0; j < ARRAY_SIZE(w83795_pwm[0]); j++) { + for (j = 0; j < NUM_PWM_ATTRIBUTES; j++) { err = fn(dev, &w83795_pwm[i][j].dev_attr); if (err) return err; } } -#endif for (i = 0; i < ARRAY_SIZE(w83795_temp); i++) { if (!(data->has_temp & (1 << i))) continue; -#ifdef CONFIG_SENSORS_W83795_FANCTRL - for (j = 0; j < ARRAY_SIZE(w83795_temp[0]); j++) { -#else - for (j = 0; j < 8; j++) { -#endif + for (j = 0; j < NUM_TEMP_ATTRIBUTES; j++) { if (j == 7 && !data->enable_beep) continue; err = fn(dev, &w83795_temp[i][j].dev_attr); @@ -2183,8 +2221,10 @@ static int w83795_probe(struct i2c_client *client, /* The W83795G has a dedicated BEEP pin */ data->enable_beep = 1; } else { - /* The W83795ADG has a shared pin for OVT# and BEEP, so you - * can't have both */ + /* + * The W83795ADG has a shared pin for OVT# and BEEP, so you + * can't have both + */ tmp = w83795_read(client, W83795_REG_OVT_CFG); if ((tmp & OVT_CFG_SEL) == 0) data->enable_beep = 1; diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 3101dd59e379..71c1b0a7535c 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -369,6 +369,21 @@ config I2C_DESIGNWARE_PCI This driver can also be built as a module. If so, the module will be called i2c-designware-pci. +config I2C_EG20T + tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C" + depends on PCI + help + This driver is for PCH(Platform controller Hub) I2C of EG20T which + is an IOH(Input/Output Hub) for x86 embedded processor. + This driver can access PCH I2C bus device. + + This driver also can be used for LAPIS Semiconductor IOH(Input/ + Output Hub), ML7213, ML7223 and ML7831. + ML7213 IOH is for IVI(In-Vehicle Infotainment) use, ML7223 IOH is + for MP(Media Phone) use and ML7831 IOH is for general purpose use. + ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series. + ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH. + config I2C_GPIO tristate "GPIO-based bitbanging I2C" depends on GENERIC_GPIO @@ -630,6 +645,16 @@ config I2C_SIMTEC This driver can also be built as a module. If so, the module will be called i2c-simtec. +config I2C_SIRF + tristate "CSR SiRFprimaII I2C interface" + depends on ARCH_PRIMA2 + help + If you say yes to this option, support will be included for the + CSR SiRFprimaII I2C interface. + + This driver can also be built as a module. If so, the module + will be called i2c-sirf. + config I2C_STU300 tristate "ST Microelectronics DDC I2C interface" depends on MACH_U300 @@ -681,20 +706,15 @@ config I2C_XILINX This driver can also be built as a module. If so, the module will be called xilinx_i2c. -config I2C_EG20T - tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C" - depends on PCI +config I2C_XLR + tristate "XLR I2C support" + depends on CPU_XLR help - This driver is for PCH(Platform controller Hub) I2C of EG20T which - is an IOH(Input/Output Hub) for x86 embedded processor. - This driver can access PCH I2C bus device. + This driver enables support for the on-chip I2C interface of + the Netlogic XLR/XLS MIPS processors. - This driver also can be used for LAPIS Semiconductor IOH(Input/ - Output Hub), ML7213, ML7223 and ML7831. - ML7213 IOH is for IVI(In-Vehicle Infotainment) use, ML7223 IOH is - for MP(Media Phone) use and ML7831 IOH is for general purpose use. - ML7213/ML7223/ML7831 is companion chip for Intel Atom E6xx series. - ML7213/ML7223/ML7831 is completely compatible for Intel EG20T PCH. + This driver can also be built as a module. If so, the module + will be called i2c-xlr. comment "External I2C/SMBus adapter drivers" diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index fba6da60aa0e..569567b0d027 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_I2C_DESIGNWARE_PLATFORM) += i2c-designware-platform.o i2c-designware-platform-objs := i2c-designware-platdrv.o i2c-designware-core.o obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o i2c-designware-pci-objs := i2c-designware-pcidrv.o i2c-designware-core.o +obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o @@ -63,12 +64,13 @@ obj-$(CONFIG_I2C_S6000) += i2c-s6000.o obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o +obj-$(CONFIG_I2C_SIRF) += i2c-sirf.o obj-$(CONFIG_I2C_STU300) += i2c-stu300.o obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o obj-$(CONFIG_I2C_OCTEON) += i2c-octeon.o obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o -obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o +obj-$(CONFIG_I2C_XLR) += i2c-xlr.o # External I2C/SMBus adapter drivers obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 5244c4724df7..4ba589ab8614 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -214,7 +214,7 @@ static int __init dw_i2c_init_driver(void) { return platform_driver_probe(&dw_i2c_driver, dw_i2c_probe); } -module_init(dw_i2c_init_driver); +subsys_initcall(dw_i2c_init_driver); static void __exit dw_i2c_exit_driver(void) { diff --git a/drivers/i2c/busses/i2c-eg20t.c b/drivers/i2c/busses/i2c-eg20t.c index ca8877641040..f086131cb1c7 100644 --- a/drivers/i2c/busses/i2c-eg20t.c +++ b/drivers/i2c/busses/i2c-eg20t.c @@ -271,30 +271,36 @@ static inline bool ktime_lt(const ktime_t cmp1, const ktime_t cmp2) /** * pch_i2c_wait_for_bus_idle() - check the status of bus. * @adap: Pointer to struct i2c_algo_pch_data. - * @timeout: waiting time counter (us). + * @timeout: waiting time counter (ms). */ static s32 pch_i2c_wait_for_bus_idle(struct i2c_algo_pch_data *adap, s32 timeout) { void __iomem *p = adap->pch_base_address; - ktime_t ns_val; + int schedule = 0; + unsigned long end = jiffies + msecs_to_jiffies(timeout); + + while (ioread32(p + PCH_I2CSR) & I2CMBB_BIT) { + if (time_after(jiffies, end)) { + pch_dbg(adap, "I2CSR = %x\n", ioread32(p + PCH_I2CSR)); + pch_err(adap, "%s: Timeout Error.return%d\n", + __func__, -ETIME); + pch_i2c_init(adap); - if ((ioread32(p + PCH_I2CSR) & I2CMBB_BIT) == 0) - return 0; + return -ETIME; + } - /* MAX timeout value is timeout*1000*1000nsec */ - ns_val = ktime_add_ns(ktime_get(), timeout*1000*1000); - do { - msleep(20); - if ((ioread32(p + PCH_I2CSR) & I2CMBB_BIT) == 0) - return 0; - } while (ktime_lt(ktime_get(), ns_val)); + if (!schedule) + /* Retry after some usecs */ + udelay(5); + else + /* Wait a bit more without consuming CPU */ + usleep_range(20, 1000); - pch_dbg(adap, "I2CSR = %x\n", ioread32(p + PCH_I2CSR)); - pch_err(adap, "%s: Timeout Error.return%d\n", __func__, -ETIME); - pch_i2c_init(adap); + schedule = 1; + } - return -ETIME; + return 0; } /** @@ -778,8 +784,6 @@ static s32 pch_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *pmsg; u32 i = 0; u32 status; - u32 msglen; - u32 subaddrlen; s32 ret; struct i2c_algo_pch_data *adap = i2c_adap->algo_data; @@ -804,12 +808,6 @@ static s32 pch_i2c_xfer(struct i2c_adapter *i2c_adap, status = pmsg->flags; pch_dbg(adap, "After invoking I2C_MODE_SEL :flag= 0x%x\n", status); - /* calculate sub address length and message length */ - /* these are applicable only for buffer mode */ - subaddrlen = pmsg->buf[0]; - /* calculate actual message length excluding - * the sub address fields */ - msglen = (pmsg->len) - (subaddrlen + 1); if ((status & (I2C_M_RD)) != false) { ret = pch_i2c_readbytes(i2c_adap, pmsg, (i + 1 == num), diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index a651779d9ff7..c0330a41db03 100644 --- a/drivers/i2c/busses/i2c-gpio.c +++ b/drivers/i2c/busses/i2c-gpio.c @@ -14,8 +14,15 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/platform_device.h> - -#include <asm/gpio.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/of_i2c.h> + +struct i2c_gpio_private_data { + struct i2c_adapter adap; + struct i2c_algo_bit_data bit_data; + struct i2c_gpio_platform_data pdata; +}; /* Toggle SDA by changing the direction of the pin */ static void i2c_gpio_setsda_dir(void *data, int state) @@ -78,24 +85,62 @@ static int i2c_gpio_getscl(void *data) return gpio_get_value(pdata->scl_pin); } +static int __devinit of_i2c_gpio_probe(struct device_node *np, + struct i2c_gpio_platform_data *pdata) +{ + u32 reg; + + if (of_gpio_count(np) < 2) + return -ENODEV; + + pdata->sda_pin = of_get_gpio(np, 0); + pdata->scl_pin = of_get_gpio(np, 1); + + if (!gpio_is_valid(pdata->sda_pin) || !gpio_is_valid(pdata->scl_pin)) { + pr_err("%s: invalid GPIO pins, sda=%d/scl=%d\n", + np->full_name, pdata->sda_pin, pdata->scl_pin); + return -ENODEV; + } + + of_property_read_u32(np, "i2c-gpio,delay-us", &pdata->udelay); + + if (!of_property_read_u32(np, "i2c-gpio,timeout-ms", ®)) + pdata->timeout = msecs_to_jiffies(reg); + + pdata->sda_is_open_drain = + of_property_read_bool(np, "i2c-gpio,sda-open-drain"); + pdata->scl_is_open_drain = + of_property_read_bool(np, "i2c-gpio,scl-open-drain"); + pdata->scl_is_output_only = + of_property_read_bool(np, "i2c-gpio,scl-output-only"); + + return 0; +} + static int __devinit i2c_gpio_probe(struct platform_device *pdev) { + struct i2c_gpio_private_data *priv; struct i2c_gpio_platform_data *pdata; struct i2c_algo_bit_data *bit_data; struct i2c_adapter *adap; int ret; - pdata = pdev->dev.platform_data; - if (!pdata) - return -ENXIO; - - ret = -ENOMEM; - adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); - if (!adap) - goto err_alloc_adap; - bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL); - if (!bit_data) - goto err_alloc_bit_data; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + adap = &priv->adap; + bit_data = &priv->bit_data; + pdata = &priv->pdata; + + if (pdev->dev.of_node) { + ret = of_i2c_gpio_probe(pdev->dev.of_node, pdata); + if (ret) + return ret; + } else { + if (!pdev->dev.platform_data) + return -ENXIO; + memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); + } ret = gpio_request(pdata->sda_pin, "sda"); if (ret) @@ -143,6 +188,7 @@ static int __devinit i2c_gpio_probe(struct platform_device *pdev) adap->algo_data = bit_data; adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->dev.parent = &pdev->dev; + adap->dev.of_node = pdev->dev.of_node; /* * If "dev->id" is negative we consider it as zero. @@ -154,7 +200,9 @@ static int __devinit i2c_gpio_probe(struct platform_device *pdev) if (ret) goto err_add_bus; - platform_set_drvdata(pdev, adap); + of_i2c_register_devices(adap); + + platform_set_drvdata(pdev, priv); dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n", pdata->sda_pin, pdata->scl_pin, @@ -168,34 +216,40 @@ err_add_bus: err_request_scl: gpio_free(pdata->sda_pin); err_request_sda: - kfree(bit_data); -err_alloc_bit_data: - kfree(adap); -err_alloc_adap: return ret; } static int __devexit i2c_gpio_remove(struct platform_device *pdev) { + struct i2c_gpio_private_data *priv; struct i2c_gpio_platform_data *pdata; struct i2c_adapter *adap; - adap = platform_get_drvdata(pdev); - pdata = pdev->dev.platform_data; + priv = platform_get_drvdata(pdev); + adap = &priv->adap; + pdata = &priv->pdata; i2c_del_adapter(adap); gpio_free(pdata->scl_pin); gpio_free(pdata->sda_pin); - kfree(adap->algo_data); - kfree(adap); return 0; } +#if defined(CONFIG_OF) +static const struct of_device_id i2c_gpio_dt_ids[] = { + { .compatible = "i2c-gpio", }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, i2c_gpio_dt_ids); +#endif + static struct platform_driver i2c_gpio_driver = { .driver = { .name = "i2c-gpio", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(i2c_gpio_dt_ids), }, .probe = i2c_gpio_probe, .remove = __devexit_p(i2c_gpio_remove), diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 58832e578fff..dfb84b7ee550 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -149,11 +149,6 @@ static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy) break; if (!for_busy && !(temp & I2SR_IBB)) break; - if (signal_pending(current)) { - dev_dbg(&i2c_imx->adapter.dev, - "<%s> I2C Interrupted\n", __func__); - return -EINTR; - } if (time_after(jiffies, orig_jiffies + msecs_to_jiffies(500))) { dev_dbg(&i2c_imx->adapter.dev, "<%s> I2C bus is busy\n", __func__); @@ -196,7 +191,7 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx) dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); - clk_enable(i2c_imx->clk); + clk_prepare_enable(i2c_imx->clk); writeb(i2c_imx->ifdr, i2c_imx->base + IMX_I2C_IFDR); /* Enable I2C controller */ writeb(0, i2c_imx->base + IMX_I2C_I2SR); @@ -245,7 +240,7 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx) /* Disable I2C controller */ writeb(0, i2c_imx->base + IMX_I2C_I2CR); - clk_disable(i2c_imx->clk); + clk_disable_unprepare(i2c_imx->clk); } static void __init i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx, diff --git a/drivers/i2c/busses/i2c-mpc.c b/drivers/i2c/busses/i2c-mpc.c index a8ebb84e23f9..206caacd30d7 100644 --- a/drivers/i2c/busses/i2c-mpc.c +++ b/drivers/i2c/busses/i2c-mpc.c @@ -454,7 +454,7 @@ static int mpc_write(struct mpc_i2c *i2c, int target, } static int mpc_read(struct mpc_i2c *i2c, int target, - u8 *data, int length, int restart) + u8 *data, int length, int restart, bool recv_len) { unsigned timeout = i2c->adap.timeout; int i, result; @@ -470,7 +470,7 @@ static int mpc_read(struct mpc_i2c *i2c, int target, return result; if (length) { - if (length == 1) + if (length == 1 && !recv_len) writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA | CCR_TXAK); else writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA); @@ -479,17 +479,46 @@ static int mpc_read(struct mpc_i2c *i2c, int target, } for (i = 0; i < length; i++) { + u8 byte; + result = i2c_wait(i2c, timeout, 0); if (result < 0) return result; - /* Generate txack on next to last byte */ - if (i == length - 2) - writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA | CCR_TXAK); - /* Do not generate stop on last byte */ - if (i == length - 1) - writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA | CCR_MTX); - data[i] = readb(i2c->base + MPC_I2C_DR); + /* + * For block reads, we have to know the total length (1st byte) + * before we can determine if we are done. + */ + if (i || !recv_len) { + /* Generate txack on next to last byte */ + if (i == length - 2) + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA + | CCR_TXAK); + /* Do not generate stop on last byte */ + if (i == length - 1) + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA + | CCR_MTX); + } + + byte = readb(i2c->base + MPC_I2C_DR); + + /* + * Adjust length if first received byte is length. + * The length is 1 length byte plus actually data length + */ + if (i == 0 && recv_len) { + if (byte == 0 || byte > I2C_SMBUS_BLOCK_MAX) + return -EPROTO; + length += byte; + /* + * For block reads, generate txack here if data length + * is 1 byte (total length is 2 bytes). + */ + if (length == 2) + writeccr(i2c, CCR_MIEN | CCR_MEN | CCR_MSTA + | CCR_TXAK); + } + data[i] = byte; } return length; @@ -532,12 +561,17 @@ static int mpc_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) "Doing %s %d bytes to 0x%02x - %d of %d messages\n", pmsg->flags & I2C_M_RD ? "read" : "write", pmsg->len, pmsg->addr, i + 1, num); - if (pmsg->flags & I2C_M_RD) - ret = - mpc_read(i2c, pmsg->addr, pmsg->buf, pmsg->len, i); - else + if (pmsg->flags & I2C_M_RD) { + bool recv_len = pmsg->flags & I2C_M_RECV_LEN; + + ret = mpc_read(i2c, pmsg->addr, pmsg->buf, pmsg->len, i, + recv_len); + if (recv_len && ret > 0) + pmsg->len = ret; + } else { ret = mpc_write(i2c, pmsg->addr, pmsg->buf, pmsg->len, i); + } } mpc_i2c_stop(i2c); return (ret < 0) ? ret : num; @@ -545,7 +579,8 @@ static int mpc_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) static u32 mpc_functionality(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL + | I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL; } static const struct i2c_algorithm mpc_algo = { diff --git a/drivers/i2c/busses/i2c-pxa.c b/drivers/i2c/busses/i2c-pxa.c index d60364650990..f6733267fa9c 100644 --- a/drivers/i2c/busses/i2c-pxa.c +++ b/drivers/i2c/busses/i2c-pxa.c @@ -29,6 +29,8 @@ #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/i2c-pxa.h> +#include <linux/of.h> +#include <linux/of_device.h> #include <linux/of_i2c.h> #include <linux/platform_device.h> #include <linux/err.h> @@ -1044,23 +1046,60 @@ static const struct i2c_algorithm i2c_pxa_pio_algorithm = { .functionality = i2c_pxa_functionality, }; -static int i2c_pxa_probe(struct platform_device *dev) +static struct of_device_id i2c_pxa_dt_ids[] = { + { .compatible = "mrvl,pxa-i2c", .data = (void *)REGS_PXA2XX }, + { .compatible = "mrvl,pwri2c", .data = (void *)REGS_PXA3XX }, + { .compatible = "mrvl,mmp-twsi", .data = (void *)REGS_PXA2XX }, + {} +}; +MODULE_DEVICE_TABLE(of, i2c_pxa_dt_ids); + +static int i2c_pxa_probe_dt(struct platform_device *pdev, struct pxa_i2c *i2c, + enum pxa_i2c_types *i2c_types) { - struct pxa_i2c *i2c; - struct resource *res; - struct i2c_pxa_platform_data *plat = dev->dev.platform_data; - const struct platform_device_id *id = platform_get_device_id(dev); - enum pxa_i2c_types i2c_type = id->driver_data; + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id = + of_match_device(i2c_pxa_dt_ids, &pdev->dev); int ret; - int irq; - res = platform_get_resource(dev, IORESOURCE_MEM, 0); - irq = platform_get_irq(dev, 0); - if (res == NULL || irq < 0) - return -ENODEV; + if (!of_id) + return 1; + ret = of_alias_get_id(np, "i2c"); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); + return ret; + } + pdev->id = ret; + if (of_get_property(np, "mrvl,i2c-polling", NULL)) + i2c->use_pio = 1; + if (of_get_property(np, "mrvl,i2c-fast-mode", NULL)) + i2c->fast_mode = 1; + *i2c_types = (u32)(of_id->data); + return 0; +} - if (!request_mem_region(res->start, resource_size(res), res->name)) - return -ENOMEM; +static int i2c_pxa_probe_pdata(struct platform_device *pdev, + struct pxa_i2c *i2c, + enum pxa_i2c_types *i2c_types) +{ + struct i2c_pxa_platform_data *plat = pdev->dev.platform_data; + const struct platform_device_id *id = platform_get_device_id(pdev); + + *i2c_types = id->driver_data; + if (plat) { + i2c->use_pio = plat->use_pio; + i2c->fast_mode = plat->fast_mode; + } + return 0; +} + +static int i2c_pxa_probe(struct platform_device *dev) +{ + struct i2c_pxa_platform_data *plat = dev->dev.platform_data; + enum pxa_i2c_types i2c_type; + struct pxa_i2c *i2c; + struct resource *res = NULL; + int ret, irq; i2c = kzalloc(sizeof(struct pxa_i2c), GFP_KERNEL); if (!i2c) { @@ -1068,6 +1107,24 @@ static int i2c_pxa_probe(struct platform_device *dev) goto emalloc; } + ret = i2c_pxa_probe_dt(dev, i2c, &i2c_type); + if (ret > 0) + ret = i2c_pxa_probe_pdata(dev, i2c, &i2c_type); + if (ret < 0) + goto eclk; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + irq = platform_get_irq(dev, 0); + if (res == NULL || irq < 0) { + ret = -ENODEV; + goto eclk; + } + + if (!request_mem_region(res->start, resource_size(res), res->name)) { + ret = -ENOMEM; + goto eclk; + } + i2c->adap.owner = THIS_MODULE; i2c->adap.retries = 5; @@ -1109,21 +1166,16 @@ static int i2c_pxa_probe(struct platform_device *dev) i2c->slave_addr = I2C_PXA_SLAVE_ADDR; -#ifdef CONFIG_I2C_PXA_SLAVE if (plat) { +#ifdef CONFIG_I2C_PXA_SLAVE i2c->slave_addr = plat->slave_addr; i2c->slave = plat->slave; - } #endif - - clk_enable(i2c->clk); - - if (plat) { i2c->adap.class = plat->class; - i2c->use_pio = plat->use_pio; - i2c->fast_mode = plat->fast_mode; } + clk_enable(i2c->clk); + if (i2c->use_pio) { i2c->adap.algo = &i2c_pxa_pio_algorithm; } else { @@ -1234,6 +1286,7 @@ static struct platform_driver i2c_pxa_driver = { .name = "pxa2xx-i2c", .owner = THIS_MODULE, .pm = I2C_PXA_DEV_PM_OPS, + .of_match_table = i2c_pxa_dt_ids, }, .id_table = i2c_pxa_id_table, }; diff --git a/drivers/i2c/busses/i2c-s3c2410.c b/drivers/i2c/busses/i2c-s3c2410.c index 4c1718081685..737f7218a32c 100644 --- a/drivers/i2c/busses/i2c-s3c2410.c +++ b/drivers/i2c/busses/i2c-s3c2410.c @@ -31,6 +31,7 @@ #include <linux/errno.h> #include <linux/err.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/clk.h> #include <linux/cpufreq.h> #include <linux/slab.h> @@ -564,6 +565,7 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, int retry; int ret; + pm_runtime_get_sync(&adap->dev); clk_enable(i2c->clk); for (retry = 0; retry < adap->retries; retry++) { @@ -572,6 +574,7 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, if (ret != -EAGAIN) { clk_disable(i2c->clk); + pm_runtime_put_sync(&adap->dev); return ret; } @@ -581,6 +584,7 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, } clk_disable(i2c->clk); + pm_runtime_put_sync(&adap->dev); return -EREMOTEIO; } @@ -890,7 +894,7 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) } } - i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); + i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL); if (!i2c) { dev_err(&pdev->dev, "no memory for state\n"); return -ENOMEM; @@ -1013,6 +1017,9 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) of_i2c_register_devices(&i2c->adap); platform_set_drvdata(pdev, i2c); + pm_runtime_enable(&pdev->dev); + pm_runtime_enable(&i2c->adap.dev); + dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev)); clk_disable(i2c->clk); return 0; @@ -1035,7 +1042,6 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) clk_put(i2c->clk); err_noclk: - kfree(i2c); return ret; } @@ -1048,6 +1054,9 @@ static int s3c24xx_i2c_remove(struct platform_device *pdev) { struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev); + pm_runtime_disable(&i2c->adap.dev); + pm_runtime_disable(&pdev->dev); + s3c24xx_i2c_deregister_cpufreq(i2c); i2c_del_adapter(&i2c->adap); @@ -1061,7 +1070,6 @@ static int s3c24xx_i2c_remove(struct platform_device *pdev) release_resource(i2c->ioarea); s3c24xx_i2c_dt_gpio_free(i2c); kfree(i2c->ioarea); - kfree(i2c); return 0; } diff --git a/drivers/i2c/busses/i2c-sirf.c b/drivers/i2c/busses/i2c-sirf.c new file mode 100644 index 000000000000..5574a47792fb --- /dev/null +++ b/drivers/i2c/busses/i2c-sirf.c @@ -0,0 +1,459 @@ +/* + * I2C bus driver for CSR SiRFprimaII + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> + +#define SIRFSOC_I2C_CLK_CTRL 0x00 +#define SIRFSOC_I2C_STATUS 0x0C +#define SIRFSOC_I2C_CTRL 0x10 +#define SIRFSOC_I2C_IO_CTRL 0x14 +#define SIRFSOC_I2C_SDA_DELAY 0x18 +#define SIRFSOC_I2C_CMD_START 0x1C +#define SIRFSOC_I2C_CMD_BUF 0x30 +#define SIRFSOC_I2C_DATA_BUF 0x80 + +#define SIRFSOC_I2C_CMD_BUF_MAX 16 +#define SIRFSOC_I2C_DATA_BUF_MAX 16 + +#define SIRFSOC_I2C_CMD(x) (SIRFSOC_I2C_CMD_BUF + (x)*0x04) +#define SIRFSOC_I2C_DATA_MASK(x) (0xFF<<(((x)&3)*8)) +#define SIRFSOC_I2C_DATA_SHIFT(x) (((x)&3)*8) + +#define SIRFSOC_I2C_DIV_MASK (0xFFFF) + +/* I2C status flags */ +#define SIRFSOC_I2C_STAT_BUSY BIT(0) +#define SIRFSOC_I2C_STAT_TIP BIT(1) +#define SIRFSOC_I2C_STAT_NACK BIT(2) +#define SIRFSOC_I2C_STAT_TR_INT BIT(4) +#define SIRFSOC_I2C_STAT_STOP BIT(6) +#define SIRFSOC_I2C_STAT_CMD_DONE BIT(8) +#define SIRFSOC_I2C_STAT_ERR BIT(9) +#define SIRFSOC_I2C_CMD_INDEX (0x1F<<16) + +/* I2C control flags */ +#define SIRFSOC_I2C_RESET BIT(0) +#define SIRFSOC_I2C_CORE_EN BIT(1) +#define SIRFSOC_I2C_MASTER_MODE BIT(2) +#define SIRFSOC_I2C_CMD_DONE_EN BIT(11) +#define SIRFSOC_I2C_ERR_INT_EN BIT(12) + +#define SIRFSOC_I2C_SDA_DELAY_MASK (0xFF) +#define SIRFSOC_I2C_SCLF_FILTER (3<<8) + +#define SIRFSOC_I2C_START_CMD BIT(0) + +#define SIRFSOC_I2C_CMD_RP(x) ((x)&0x7) +#define SIRFSOC_I2C_NACK BIT(3) +#define SIRFSOC_I2C_WRITE BIT(4) +#define SIRFSOC_I2C_READ BIT(5) +#define SIRFSOC_I2C_STOP BIT(6) +#define SIRFSOC_I2C_START BIT(7) + +#define SIRFSOC_I2C_DEFAULT_SPEED 100000 + +struct sirfsoc_i2c { + void __iomem *base; + struct clk *clk; + u32 cmd_ptr; /* Current position in CMD buffer */ + u8 *buf; /* Buffer passed by user */ + u32 msg_len; /* Message length */ + u32 finished_len; /* number of bytes read/written */ + u32 read_cmd_len; /* number of read cmd sent */ + int msg_read; /* 1 indicates a read message */ + int err_status; /* 1 indicates an error on bus */ + + u32 sda_delay; /* For suspend/resume */ + u32 clk_div; + int last; /* Last message in transfer, STOP cmd can be sent */ + + struct completion done; /* indicates completion of message transfer */ + struct i2c_adapter adapter; +}; + +static void i2c_sirfsoc_read_data(struct sirfsoc_i2c *siic) +{ + u32 data = 0; + int i; + + for (i = 0; i < siic->read_cmd_len; i++) { + if (!(i & 0x3)) + data = readl(siic->base + SIRFSOC_I2C_DATA_BUF + i); + siic->buf[siic->finished_len++] = + (u8)((data & SIRFSOC_I2C_DATA_MASK(i)) >> + SIRFSOC_I2C_DATA_SHIFT(i)); + } +} + +static void i2c_sirfsoc_queue_cmd(struct sirfsoc_i2c *siic) +{ + u32 regval; + int i = 0; + + if (siic->msg_read) { + while (((siic->finished_len + i) < siic->msg_len) + && (siic->cmd_ptr < SIRFSOC_I2C_CMD_BUF_MAX)) { + regval = SIRFSOC_I2C_READ | SIRFSOC_I2C_CMD_RP(0); + if (((siic->finished_len + i) == + (siic->msg_len - 1)) && siic->last) + regval |= SIRFSOC_I2C_STOP | SIRFSOC_I2C_NACK; + writel(regval, + siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); + i++; + } + + siic->read_cmd_len = i; + } else { + while ((siic->cmd_ptr < SIRFSOC_I2C_CMD_BUF_MAX - 1) + && (siic->finished_len < siic->msg_len)) { + regval = SIRFSOC_I2C_WRITE | SIRFSOC_I2C_CMD_RP(0); + if ((siic->finished_len == (siic->msg_len - 1)) + && siic->last) + regval |= SIRFSOC_I2C_STOP; + writel(regval, + siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); + writel(siic->buf[siic->finished_len++], + siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); + } + } + siic->cmd_ptr = 0; + + /* Trigger the transfer */ + writel(SIRFSOC_I2C_START_CMD, siic->base + SIRFSOC_I2C_CMD_START); +} + +static irqreturn_t i2c_sirfsoc_irq(int irq, void *dev_id) +{ + struct sirfsoc_i2c *siic = (struct sirfsoc_i2c *)dev_id; + u32 i2c_stat = readl(siic->base + SIRFSOC_I2C_STATUS); + + if (i2c_stat & SIRFSOC_I2C_STAT_ERR) { + /* Error conditions */ + siic->err_status = 1; + writel(SIRFSOC_I2C_STAT_ERR, siic->base + SIRFSOC_I2C_STATUS); + + if (i2c_stat & SIRFSOC_I2C_STAT_NACK) + dev_err(&siic->adapter.dev, "ACK not received\n"); + else + dev_err(&siic->adapter.dev, "I2C error\n"); + + complete(&siic->done); + } else if (i2c_stat & SIRFSOC_I2C_STAT_CMD_DONE) { + /* CMD buffer execution complete */ + if (siic->msg_read) + i2c_sirfsoc_read_data(siic); + if (siic->finished_len == siic->msg_len) + complete(&siic->done); + else /* Fill a new CMD buffer for left data */ + i2c_sirfsoc_queue_cmd(siic); + + writel(SIRFSOC_I2C_STAT_CMD_DONE, siic->base + SIRFSOC_I2C_STATUS); + } + + return IRQ_HANDLED; +} + +static void i2c_sirfsoc_set_address(struct sirfsoc_i2c *siic, + struct i2c_msg *msg) +{ + unsigned char addr; + u32 regval = SIRFSOC_I2C_START | SIRFSOC_I2C_CMD_RP(0) | SIRFSOC_I2C_WRITE; + + /* no data and last message -> add STOP */ + if (siic->last && (msg->len == 0)) + regval |= SIRFSOC_I2C_STOP; + + writel(regval, siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); + + addr = msg->addr << 1; /* Generate address */ + if (msg->flags & I2C_M_RD) + addr |= 1; + + writel(addr, siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); +} + +static int i2c_sirfsoc_xfer_msg(struct sirfsoc_i2c *siic, struct i2c_msg *msg) +{ + u32 regval = readl(siic->base + SIRFSOC_I2C_CTRL); + /* timeout waiting for the xfer to finish or fail */ + int timeout = msecs_to_jiffies((msg->len + 1) * 50); + int ret = 0; + + i2c_sirfsoc_set_address(siic, msg); + + writel(regval | SIRFSOC_I2C_CMD_DONE_EN | SIRFSOC_I2C_ERR_INT_EN, + siic->base + SIRFSOC_I2C_CTRL); + i2c_sirfsoc_queue_cmd(siic); + + if (wait_for_completion_timeout(&siic->done, timeout) == 0) { + siic->err_status = 1; + dev_err(&siic->adapter.dev, "Transfer timeout\n"); + } + + writel(regval & ~(SIRFSOC_I2C_CMD_DONE_EN | SIRFSOC_I2C_ERR_INT_EN), + siic->base + SIRFSOC_I2C_CTRL); + writel(0, siic->base + SIRFSOC_I2C_CMD_START); + + if (siic->err_status) { + writel(readl(siic->base + SIRFSOC_I2C_CTRL) | SIRFSOC_I2C_RESET, + siic->base + SIRFSOC_I2C_CTRL); + while (readl(siic->base + SIRFSOC_I2C_CTRL) & SIRFSOC_I2C_RESET) + cpu_relax(); + + ret = -EIO; + } + + return ret; +} + +static u32 i2c_sirfsoc_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static int i2c_sirfsoc_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct sirfsoc_i2c *siic = adap->algo_data; + int i, ret; + + clk_enable(siic->clk); + + for (i = 0; i < num; i++) { + siic->buf = msgs[i].buf; + siic->msg_len = msgs[i].len; + siic->msg_read = !!(msgs[i].flags & I2C_M_RD); + siic->err_status = 0; + siic->cmd_ptr = 0; + siic->finished_len = 0; + siic->last = (i == (num - 1)); + + ret = i2c_sirfsoc_xfer_msg(siic, &msgs[i]); + if (ret) { + clk_disable(siic->clk); + return ret; + } + } + + clk_disable(siic->clk); + return num; +} + +/* I2C algorithms associated with this master controller driver */ +static const struct i2c_algorithm i2c_sirfsoc_algo = { + .master_xfer = i2c_sirfsoc_xfer, + .functionality = i2c_sirfsoc_func, +}; + +static int __devinit i2c_sirfsoc_probe(struct platform_device *pdev) +{ + struct sirfsoc_i2c *siic; + struct i2c_adapter *adap; + struct resource *mem_res; + struct clk *clk; + int bitrate; + int ctrl_speed; + int irq; + + int err; + u32 regval; + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + dev_err(&pdev->dev, "Clock get failed\n"); + goto err_get_clk; + } + + err = clk_prepare(clk); + if (err) { + dev_err(&pdev->dev, "Clock prepare failed\n"); + goto err_clk_prep; + } + + err = clk_enable(clk); + if (err) { + dev_err(&pdev->dev, "Clock enable failed\n"); + goto err_clk_en; + } + + ctrl_speed = clk_get_rate(clk); + + siic = devm_kzalloc(&pdev->dev, sizeof(*siic), GFP_KERNEL); + if (!siic) { + dev_err(&pdev->dev, "Can't allocate driver data\n"); + err = -ENOMEM; + goto out; + } + adap = &siic->adapter; + adap->class = I2C_CLASS_HWMON; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem_res == NULL) { + dev_err(&pdev->dev, "Unable to get MEM resource\n"); + err = -EINVAL; + goto out; + } + + siic->base = devm_request_and_ioremap(&pdev->dev, mem_res); + if (siic->base == NULL) { + dev_err(&pdev->dev, "IO remap failed!\n"); + err = -ENOMEM; + goto out; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = irq; + goto out; + } + err = devm_request_irq(&pdev->dev, irq, i2c_sirfsoc_irq, 0, + dev_name(&pdev->dev), siic); + if (err) + goto out; + + adap->algo = &i2c_sirfsoc_algo; + adap->algo_data = siic; + + adap->dev.parent = &pdev->dev; + adap->nr = pdev->id; + + strlcpy(adap->name, "sirfsoc-i2c", sizeof(adap->name)); + + platform_set_drvdata(pdev, adap); + init_completion(&siic->done); + + /* Controller Initalisation */ + + writel(SIRFSOC_I2C_RESET, siic->base + SIRFSOC_I2C_CTRL); + while (readl(siic->base + SIRFSOC_I2C_CTRL) & SIRFSOC_I2C_RESET) + cpu_relax(); + writel(SIRFSOC_I2C_CORE_EN | SIRFSOC_I2C_MASTER_MODE, + siic->base + SIRFSOC_I2C_CTRL); + + siic->clk = clk; + + err = of_property_read_u32(pdev->dev.of_node, + "clock-frequency", &bitrate); + if (err < 0) + bitrate = SIRFSOC_I2C_DEFAULT_SPEED; + + if (bitrate < 100000) + regval = + (2 * ctrl_speed) / (2 * bitrate * 11); + else + regval = ctrl_speed / (bitrate * 5); + + writel(regval, siic->base + SIRFSOC_I2C_CLK_CTRL); + if (regval > 0xFF) + writel(0xFF, siic->base + SIRFSOC_I2C_SDA_DELAY); + else + writel(regval, siic->base + SIRFSOC_I2C_SDA_DELAY); + + err = i2c_add_numbered_adapter(adap); + if (err < 0) { + dev_err(&pdev->dev, "Can't add new i2c adapter\n"); + goto out; + } + + clk_disable(clk); + + dev_info(&pdev->dev, " I2C adapter ready to operate\n"); + + return 0; + +out: + clk_disable(clk); +err_clk_en: + clk_unprepare(clk); +err_clk_prep: + clk_put(clk); +err_get_clk: + return err; +} + +static int __devexit i2c_sirfsoc_remove(struct platform_device *pdev) +{ + struct i2c_adapter *adapter = platform_get_drvdata(pdev); + struct sirfsoc_i2c *siic = adapter->algo_data; + + writel(SIRFSOC_I2C_RESET, siic->base + SIRFSOC_I2C_CTRL); + i2c_del_adapter(adapter); + clk_unprepare(siic->clk); + clk_put(siic->clk); + return 0; +} + +#ifdef CONFIG_PM +static int i2c_sirfsoc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct i2c_adapter *adapter = platform_get_drvdata(pdev); + struct sirfsoc_i2c *siic = adapter->algo_data; + + clk_enable(siic->clk); + siic->sda_delay = readl(siic->base + SIRFSOC_I2C_SDA_DELAY); + siic->clk_div = readl(siic->base + SIRFSOC_I2C_CLK_CTRL); + clk_disable(siic->clk); + return 0; +} + +static int i2c_sirfsoc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct i2c_adapter *adapter = platform_get_drvdata(pdev); + struct sirfsoc_i2c *siic = adapter->algo_data; + + clk_enable(siic->clk); + writel(SIRFSOC_I2C_RESET, siic->base + SIRFSOC_I2C_CTRL); + writel(SIRFSOC_I2C_CORE_EN | SIRFSOC_I2C_MASTER_MODE, + siic->base + SIRFSOC_I2C_CTRL); + writel(siic->clk_div, siic->base + SIRFSOC_I2C_CLK_CTRL); + writel(siic->sda_delay, siic->base + SIRFSOC_I2C_SDA_DELAY); + clk_disable(siic->clk); + return 0; +} + +static const struct dev_pm_ops i2c_sirfsoc_pm_ops = { + .suspend = i2c_sirfsoc_suspend, + .resume = i2c_sirfsoc_resume, +}; +#endif + +static const struct of_device_id sirfsoc_i2c_of_match[] __devinitconst = { + { .compatible = "sirf,prima2-i2c", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sirfsoc_i2c_of_match); + +static struct platform_driver i2c_sirfsoc_driver = { + .driver = { + .name = "sirfsoc_i2c", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &i2c_sirfsoc_pm_ops, +#endif + .of_match_table = sirfsoc_i2c_of_match, + }, + .probe = i2c_sirfsoc_probe, + .remove = __devexit_p(i2c_sirfsoc_remove), +}; +module_platform_driver(i2c_sirfsoc_driver); + +MODULE_DESCRIPTION("SiRF SoC I2C master controller driver"); +MODULE_AUTHOR("Zhiwu Song <Zhiwu.Song@csr.com>, " + "Xiangzhen Ye <Xiangzhen.Ye@csr.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 0ab4a9548745..e978635e60f0 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -457,7 +457,6 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev, int ret; tegra_i2c_flush_fifos(i2c_dev); - i2c_writel(i2c_dev, 0xFF, I2C_INT_STATUS); if (msg->len == 0) return -EINVAL; diff --git a/drivers/i2c/busses/i2c-versatile.c b/drivers/i2c/busses/i2c-versatile.c index 60556012312f..f585aead50cc 100644 --- a/drivers/i2c/busses/i2c-versatile.c +++ b/drivers/i2c/busses/i2c-versatile.c @@ -16,6 +16,7 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/io.h> +#include <linux/of_i2c.h> #define I2C_CONTROL 0x00 #define I2C_CONTROLS 0x00 @@ -99,6 +100,7 @@ static int i2c_versatile_probe(struct platform_device *dev) strlcpy(i2c->adap.name, "Versatile I2C adapter", sizeof(i2c->adap.name)); i2c->adap.algo_data = &i2c->algo; i2c->adap.dev.parent = &dev->dev; + i2c->adap.dev.of_node = dev->dev.of_node; i2c->algo = i2c_versatile_algo; i2c->algo.data = i2c; @@ -111,6 +113,7 @@ static int i2c_versatile_probe(struct platform_device *dev) ret = i2c_bit_add_bus(&i2c->adap); if (ret >= 0) { platform_set_drvdata(dev, i2c); + of_i2c_register_devices(&i2c->adap); return 0; } @@ -133,12 +136,19 @@ static int i2c_versatile_remove(struct platform_device *dev) return 0; } +static const struct of_device_id i2c_versatile_match[] = { + { .compatible = "arm,versatile-i2c", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_versatile_match); + static struct platform_driver i2c_versatile_driver = { .probe = i2c_versatile_probe, .remove = i2c_versatile_remove, .driver = { .name = "versatile-i2c", .owner = THIS_MODULE, + .of_match_table = i2c_versatile_match, }, }; diff --git a/drivers/i2c/busses/i2c-xlr.c b/drivers/i2c/busses/i2c-xlr.c new file mode 100644 index 000000000000..96d3fabd8883 --- /dev/null +++ b/drivers/i2c/busses/i2c-xlr.c @@ -0,0 +1,278 @@ +/* + * Copyright 2011, Netlogic Microsystems Inc. + * Copyright 2004, Matt Porter <mporter@kernel.crashing.org> + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +/* XLR I2C REGISTERS */ +#define XLR_I2C_CFG 0x00 +#define XLR_I2C_CLKDIV 0x01 +#define XLR_I2C_DEVADDR 0x02 +#define XLR_I2C_ADDR 0x03 +#define XLR_I2C_DATAOUT 0x04 +#define XLR_I2C_DATAIN 0x05 +#define XLR_I2C_STATUS 0x06 +#define XLR_I2C_STARTXFR 0x07 +#define XLR_I2C_BYTECNT 0x08 +#define XLR_I2C_HDSTATIM 0x09 + +/* XLR I2C REGISTERS FLAGS */ +#define XLR_I2C_BUS_BUSY 0x01 +#define XLR_I2C_SDOEMPTY 0x02 +#define XLR_I2C_RXRDY 0x04 +#define XLR_I2C_ACK_ERR 0x08 +#define XLR_I2C_ARB_STARTERR 0x30 + +/* Register Values */ +#define XLR_I2C_CFG_ADDR 0xF8 +#define XLR_I2C_CFG_NOADDR 0xFA +#define XLR_I2C_STARTXFR_ND 0x02 /* No Data */ +#define XLR_I2C_STARTXFR_RD 0x01 /* Read */ +#define XLR_I2C_STARTXFR_WR 0x00 /* Write */ + +#define XLR_I2C_TIMEOUT 10 /* timeout per byte in msec */ + +/* + * On XLR/XLS, we need to use __raw_ IO to read the I2C registers + * because they are in the big-endian MMIO area on the SoC. + * + * The readl/writel implementation on XLR/XLS byteswaps, because + * those are for its little-endian PCI space (see arch/mips/Kconfig). + */ +static inline void xlr_i2c_wreg(u32 __iomem *base, unsigned int reg, u32 val) +{ + __raw_writel(val, base + reg); +} + +static inline u32 xlr_i2c_rdreg(u32 __iomem *base, unsigned int reg) +{ + return __raw_readl(base + reg); +} + +struct xlr_i2c_private { + struct i2c_adapter adap; + u32 __iomem *iobase; +}; + +static int xlr_i2c_tx(struct xlr_i2c_private *priv, u16 len, + u8 *buf, u16 addr) +{ + struct i2c_adapter *adap = &priv->adap; + unsigned long timeout, stoptime, checktime; + u32 i2c_status; + int pos, timedout; + u8 offset, byte; + + offset = buf[0]; + xlr_i2c_wreg(priv->iobase, XLR_I2C_ADDR, offset); + xlr_i2c_wreg(priv->iobase, XLR_I2C_DEVADDR, addr); + xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, XLR_I2C_CFG_ADDR); + xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len - 1); + + timeout = msecs_to_jiffies(XLR_I2C_TIMEOUT); + stoptime = jiffies + timeout; + timedout = 0; + pos = 1; +retry: + if (len == 1) { + xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, + XLR_I2C_STARTXFR_ND); + } else { + xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, buf[pos]); + xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, + XLR_I2C_STARTXFR_WR); + } + + while (!timedout) { + checktime = jiffies; + i2c_status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); + + if (i2c_status & XLR_I2C_SDOEMPTY) { + pos++; + /* need to do a empty dataout after the last byte */ + byte = (pos < len) ? buf[pos] : 0; + xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, byte); + + /* reset timeout on successful xmit */ + stoptime = jiffies + timeout; + } + timedout = time_after(checktime, stoptime); + + if (i2c_status & XLR_I2C_ARB_STARTERR) { + if (timedout) + break; + goto retry; + } + + if (i2c_status & XLR_I2C_ACK_ERR) + return -EIO; + + if ((i2c_status & XLR_I2C_BUS_BUSY) == 0 && pos >= len) + return 0; + } + dev_err(&adap->dev, "I2C transmit timeout\n"); + return -ETIMEDOUT; +} + +static int xlr_i2c_rx(struct xlr_i2c_private *priv, u16 len, u8 *buf, u16 addr) +{ + struct i2c_adapter *adap = &priv->adap; + u32 i2c_status; + unsigned long timeout, stoptime, checktime; + int nbytes, timedout; + u8 byte; + + xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, XLR_I2C_CFG_NOADDR); + xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len); + xlr_i2c_wreg(priv->iobase, XLR_I2C_DEVADDR, addr); + + timeout = msecs_to_jiffies(XLR_I2C_TIMEOUT); + stoptime = jiffies + timeout; + timedout = 0; + nbytes = 0; +retry: + xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, XLR_I2C_STARTXFR_RD); + + while (!timedout) { + checktime = jiffies; + i2c_status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); + if (i2c_status & XLR_I2C_RXRDY) { + if (nbytes > len) + return -EIO; /* should not happen */ + + /* we need to do a dummy datain when nbytes == len */ + byte = xlr_i2c_rdreg(priv->iobase, XLR_I2C_DATAIN); + if (nbytes < len) + buf[nbytes] = byte; + nbytes++; + + /* reset timeout on successful read */ + stoptime = jiffies + timeout; + } + + timedout = time_after(checktime, stoptime); + if (i2c_status & XLR_I2C_ARB_STARTERR) { + if (timedout) + break; + goto retry; + } + + if (i2c_status & XLR_I2C_ACK_ERR) + return -EIO; + + if ((i2c_status & XLR_I2C_BUS_BUSY) == 0) + return 0; + } + + dev_err(&adap->dev, "I2C receive timeout\n"); + return -ETIMEDOUT; +} + +static int xlr_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct i2c_msg *msg; + int i; + int ret = 0; + struct xlr_i2c_private *priv = i2c_get_adapdata(adap); + + for (i = 0; ret == 0 && i < num; i++) { + msg = &msgs[i]; + if (msg->flags & I2C_M_RD) + ret = xlr_i2c_rx(priv, msg->len, &msg->buf[0], + msg->addr); + else + ret = xlr_i2c_tx(priv, msg->len, &msg->buf[0], + msg->addr); + } + + return (ret != 0) ? ret : num; +} + +static u32 xlr_func(struct i2c_adapter *adap) +{ + /* Emulate SMBUS over I2C */ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; +} + +static struct i2c_algorithm xlr_i2c_algo = { + .master_xfer = xlr_i2c_xfer, + .functionality = xlr_func, +}; + +static int __devinit xlr_i2c_probe(struct platform_device *pdev) +{ + struct xlr_i2c_private *priv; + struct resource *res; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->iobase = devm_request_and_ioremap(&pdev->dev, res); + if (!priv->iobase) { + dev_err(&pdev->dev, "devm_request_and_ioremap failed\n"); + return -EBUSY; + } + + priv->adap.dev.parent = &pdev->dev; + priv->adap.owner = THIS_MODULE; + priv->adap.algo_data = priv; + priv->adap.algo = &xlr_i2c_algo; + priv->adap.nr = pdev->id; + priv->adap.class = I2C_CLASS_HWMON; + snprintf(priv->adap.name, sizeof(priv->adap.name), "xlr-i2c"); + + i2c_set_adapdata(&priv->adap, priv); + ret = i2c_add_numbered_adapter(&priv->adap); + if (ret < 0) { + dev_err(&priv->adap.dev, "Failed to add i2c bus.\n"); + return ret; + } + + platform_set_drvdata(pdev, priv); + dev_info(&priv->adap.dev, "Added I2C Bus.\n"); + return 0; +} + +static int __devexit xlr_i2c_remove(struct platform_device *pdev) +{ + struct xlr_i2c_private *priv; + + priv = platform_get_drvdata(pdev); + i2c_del_adapter(&priv->adap); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver xlr_i2c_driver = { + .probe = xlr_i2c_probe, + .remove = __devexit_p(xlr_i2c_remove), + .driver = { + .name = "xlr-i2cbus", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(xlr_i2c_driver); + +MODULE_AUTHOR("Ganesan Ramalingam <ganesanr@netlogicmicro.com>"); +MODULE_DESCRIPTION("XLR/XLS SoC I2C Controller driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:xlr-i2cbus"); diff --git a/drivers/input/keyboard/jornada720_kbd.c b/drivers/input/keyboard/jornada720_kbd.c index eeafc30b207b..9d639fa1afbd 100644 --- a/drivers/input/keyboard/jornada720_kbd.c +++ b/drivers/input/keyboard/jornada720_kbd.c @@ -27,6 +27,7 @@ #include <mach/jornada720.h> #include <mach/hardware.h> +#include <mach/irqs.h> MODULE_AUTHOR("Kristoffer Ericson <Kristoffer.Ericson@gmail.com>"); MODULE_DESCRIPTION("HP Jornada 710/720/728 keyboard driver"); diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c index 8407d5b0ced8..2ffd110bd5bc 100644 --- a/drivers/input/serio/ambakmi.c +++ b/drivers/input/serio/ambakmi.c @@ -208,18 +208,7 @@ static struct amba_driver ambakmi_driver = { .resume = amba_kmi_resume, }; -static int __init amba_kmi_init(void) -{ - return amba_driver_register(&ambakmi_driver); -} - -static void __exit amba_kmi_exit(void) -{ - amba_driver_unregister(&ambakmi_driver); -} - -module_init(amba_kmi_init); -module_exit(amba_kmi_exit); +module_amba_driver(ambakmi_driver); MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); MODULE_DESCRIPTION("AMBA KMI controller driver"); diff --git a/drivers/input/serio/ams_delta_serio.c b/drivers/input/serio/ams_delta_serio.c index d4d08bd9205b..bd5b10eeeb40 100644 --- a/drivers/input/serio/ams_delta_serio.c +++ b/drivers/input/serio/ams_delta_serio.c @@ -92,8 +92,7 @@ static irqreturn_t ams_delta_serio_interrupt(int irq, void *dev_id) static int ams_delta_serio_open(struct serio *serio) { /* enable keyboard */ - ams_delta_latch2_write(AMD_DELTA_LATCH2_KEYBRD_PWR, - AMD_DELTA_LATCH2_KEYBRD_PWR); + gpio_set_value(AMS_DELTA_GPIO_PIN_KEYBRD_PWR, 1); return 0; } @@ -101,9 +100,32 @@ static int ams_delta_serio_open(struct serio *serio) static void ams_delta_serio_close(struct serio *serio) { /* disable keyboard */ - ams_delta_latch2_write(AMD_DELTA_LATCH2_KEYBRD_PWR, 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_KEYBRD_PWR, 0); } +static const struct gpio ams_delta_gpios[] __initconst_or_module = { + { + .gpio = AMS_DELTA_GPIO_PIN_KEYBRD_DATA, + .flags = GPIOF_DIR_IN, + .label = "serio-data", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_KEYBRD_CLK, + .flags = GPIOF_DIR_IN, + .label = "serio-clock", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_KEYBRD_PWR, + .flags = GPIOF_OUT_INIT_LOW, + .label = "serio-power", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_KEYBRD_DATAOUT, + .flags = GPIOF_OUT_INIT_LOW, + .label = "serio-dataout", + }, +}; + static int __init ams_delta_serio_init(void) { int err; @@ -123,19 +145,12 @@ static int __init ams_delta_serio_init(void) strlcpy(ams_delta_serio->phys, "GPIO/serio0", sizeof(ams_delta_serio->phys)); - err = gpio_request(AMS_DELTA_GPIO_PIN_KEYBRD_DATA, "serio-data"); + err = gpio_request_array(ams_delta_gpios, + ARRAY_SIZE(ams_delta_gpios)); if (err) { - pr_err("ams_delta_serio: Couldn't request gpio pin for data\n"); + pr_err("ams_delta_serio: Couldn't request gpio pins\n"); goto serio; } - gpio_direction_input(AMS_DELTA_GPIO_PIN_KEYBRD_DATA); - - err = gpio_request(AMS_DELTA_GPIO_PIN_KEYBRD_CLK, "serio-clock"); - if (err) { - pr_err("ams_delta_serio: couldn't request gpio pin for clock\n"); - goto gpio_data; - } - gpio_direction_input(AMS_DELTA_GPIO_PIN_KEYBRD_CLK); err = request_irq(gpio_to_irq(AMS_DELTA_GPIO_PIN_KEYBRD_CLK), ams_delta_serio_interrupt, IRQ_TYPE_EDGE_RISING, @@ -143,7 +158,7 @@ static int __init ams_delta_serio_init(void) if (err < 0) { pr_err("ams_delta_serio: couldn't request gpio interrupt %d\n", gpio_to_irq(AMS_DELTA_GPIO_PIN_KEYBRD_CLK)); - goto gpio_clk; + goto gpio; } /* * Since GPIO register handling for keyboard clock pin is performed @@ -157,10 +172,9 @@ static int __init ams_delta_serio_init(void) dev_info(&ams_delta_serio->dev, "%s\n", ams_delta_serio->name); return 0; -gpio_clk: - gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_CLK); -gpio_data: - gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_DATA); +gpio: + gpio_free_array(ams_delta_gpios, + ARRAY_SIZE(ams_delta_gpios)); serio: kfree(ams_delta_serio); return err; @@ -171,7 +185,7 @@ static void __exit ams_delta_serio_exit(void) { serio_unregister_port(ams_delta_serio); free_irq(OMAP_GPIO_IRQ(AMS_DELTA_GPIO_PIN_KEYBRD_CLK), 0); - gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_CLK); - gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_DATA); + gpio_free_array(ams_delta_gpios, + ARRAY_SIZE(ams_delta_gpios)); } module_exit(ams_delta_serio_exit); diff --git a/drivers/input/serio/rpckbd.c b/drivers/input/serio/rpckbd.c index 8b44ddc8041c..58b224498b35 100644 --- a/drivers/input/serio/rpckbd.c +++ b/drivers/input/serio/rpckbd.c @@ -36,7 +36,6 @@ #include <linux/io.h> #include <linux/slab.h> -#include <asm/irq.h> #include <mach/hardware.h> #include <asm/hardware/iomd.h> #include <asm/system.h> @@ -46,6 +45,11 @@ MODULE_DESCRIPTION("Acorn RiscPC PS/2 keyboard controller driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:kart"); +struct rpckbd_data { + int tx_irq; + int rx_irq; +}; + static int rpckbd_write(struct serio *port, unsigned char val) { while (!(iomd_readb(IOMD_KCTRL) & (1 << 7))) @@ -78,19 +82,21 @@ static irqreturn_t rpckbd_tx(int irq, void *dev_id) static int rpckbd_open(struct serio *port) { + struct rpckbd_data *rpckbd = port->port_data; + /* Reset the keyboard state machine. */ iomd_writeb(0, IOMD_KCTRL); iomd_writeb(8, IOMD_KCTRL); iomd_readb(IOMD_KARTRX); - if (request_irq(IRQ_KEYBOARDRX, rpckbd_rx, 0, "rpckbd", port) != 0) { + if (request_irq(rpckbd->rx_irq, rpckbd_rx, 0, "rpckbd", port) != 0) { printk(KERN_ERR "rpckbd.c: Could not allocate keyboard receive IRQ\n"); return -EBUSY; } - if (request_irq(IRQ_KEYBOARDTX, rpckbd_tx, 0, "rpckbd", port) != 0) { + if (request_irq(rpckbd->tx_irq, rpckbd_tx, 0, "rpckbd", port) != 0) { printk(KERN_ERR "rpckbd.c: Could not allocate keyboard transmit IRQ\n"); - free_irq(IRQ_KEYBOARDRX, port); + free_irq(rpckbd->rx_irq, port); return -EBUSY; } @@ -99,8 +105,10 @@ static int rpckbd_open(struct serio *port) static void rpckbd_close(struct serio *port) { - free_irq(IRQ_KEYBOARDRX, port); - free_irq(IRQ_KEYBOARDTX, port); + struct rpckbd_data *rpckbd = port->port_data; + + free_irq(rpckbd->rx_irq, port); + free_irq(rpckbd->tx_irq, port); } /* @@ -109,17 +117,35 @@ static void rpckbd_close(struct serio *port) */ static int __devinit rpckbd_probe(struct platform_device *dev) { + struct rpckbd_data *rpckbd; struct serio *serio; + int tx_irq, rx_irq; + + rx_irq = platform_get_irq(dev, 0); + if (rx_irq <= 0) + return rx_irq < 0 ? rx_irq : -ENXIO; + + tx_irq = platform_get_irq(dev, 1); + if (tx_irq <= 0) + return tx_irq < 0 ? tx_irq : -ENXIO; serio = kzalloc(sizeof(struct serio), GFP_KERNEL); - if (!serio) + rpckbd = kzalloc(sizeof(*rpckbd), GFP_KERNEL); + if (!serio || !rpckbd) { + kfree(rpckbd); + kfree(serio); return -ENOMEM; + } + + rpckbd->rx_irq = rx_irq; + rpckbd->tx_irq = tx_irq; serio->id.type = SERIO_8042; serio->write = rpckbd_write; serio->open = rpckbd_open; serio->close = rpckbd_close; serio->dev.parent = &dev->dev; + serio->port_data = rpckbd; strlcpy(serio->name, "RiscPC PS/2 kbd port", sizeof(serio->name)); strlcpy(serio->phys, "rpckbd/serio0", sizeof(serio->phys)); @@ -131,7 +157,11 @@ static int __devinit rpckbd_probe(struct platform_device *dev) static int __devexit rpckbd_remove(struct platform_device *dev) { struct serio *serio = platform_get_drvdata(dev); + struct rpckbd_data *rpckbd = serio->port_data; + serio_unregister_port(serio); + kfree(rpckbd); + return 0; } diff --git a/drivers/input/serio/sa1111ps2.c b/drivers/input/serio/sa1111ps2.c index 44fc8b4bcd81..5ebabe3fc845 100644 --- a/drivers/input/serio/sa1111ps2.c +++ b/drivers/input/serio/sa1111ps2.c @@ -24,6 +24,26 @@ #include <asm/hardware/sa1111.h> +#define PS2CR 0x0000 +#define PS2STAT 0x0004 +#define PS2DATA 0x0008 +#define PS2CLKDIV 0x000c +#define PS2PRECNT 0x0010 + +#define PS2CR_ENA 0x08 +#define PS2CR_FKD 0x02 +#define PS2CR_FKC 0x01 + +#define PS2STAT_STP 0x0100 +#define PS2STAT_TXE 0x0080 +#define PS2STAT_TXB 0x0040 +#define PS2STAT_RXF 0x0020 +#define PS2STAT_RXB 0x0010 +#define PS2STAT_ENA 0x0008 +#define PS2STAT_RXP 0x0004 +#define PS2STAT_KBD 0x0002 +#define PS2STAT_KBC 0x0001 + struct ps2if { struct serio *io; struct sa1111_dev *dev; @@ -45,22 +65,22 @@ static irqreturn_t ps2_rxint(int irq, void *dev_id) struct ps2if *ps2if = dev_id; unsigned int scancode, flag, status; - status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + status = sa1111_readl(ps2if->base + PS2STAT); while (status & PS2STAT_RXF) { if (status & PS2STAT_STP) - sa1111_writel(PS2STAT_STP, ps2if->base + SA1111_PS2STAT); + sa1111_writel(PS2STAT_STP, ps2if->base + PS2STAT); flag = (status & PS2STAT_STP ? SERIO_FRAME : 0) | (status & PS2STAT_RXP ? 0 : SERIO_PARITY); - scancode = sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff; + scancode = sa1111_readl(ps2if->base + PS2DATA) & 0xff; if (hweight8(scancode) & 1) flag ^= SERIO_PARITY; serio_interrupt(ps2if->io, scancode, flag); - status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + status = sa1111_readl(ps2if->base + PS2STAT); } return IRQ_HANDLED; @@ -75,12 +95,12 @@ static irqreturn_t ps2_txint(int irq, void *dev_id) unsigned int status; spin_lock(&ps2if->lock); - status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + status = sa1111_readl(ps2if->base + PS2STAT); if (ps2if->head == ps2if->tail) { disable_irq_nosync(irq); /* done */ } else if (status & PS2STAT_TXE) { - sa1111_writel(ps2if->buf[ps2if->tail], ps2if->base + SA1111_PS2DATA); + sa1111_writel(ps2if->buf[ps2if->tail], ps2if->base + PS2DATA); ps2if->tail = (ps2if->tail + 1) & (sizeof(ps2if->buf) - 1); } spin_unlock(&ps2if->lock); @@ -103,8 +123,8 @@ static int ps2_write(struct serio *io, unsigned char val) /* * If the TX register is empty, we can go straight out. */ - if (sa1111_readl(ps2if->base + SA1111_PS2STAT) & PS2STAT_TXE) { - sa1111_writel(val, ps2if->base + SA1111_PS2DATA); + if (sa1111_readl(ps2if->base + PS2STAT) & PS2STAT_TXE) { + sa1111_writel(val, ps2if->base + PS2DATA); } else { if (ps2if->head == ps2if->tail) enable_irq(ps2if->dev->irq[1]); @@ -124,13 +144,16 @@ static int ps2_open(struct serio *io) struct ps2if *ps2if = io->port_data; int ret; - sa1111_enable_device(ps2if->dev); + ret = sa1111_enable_device(ps2if->dev); + if (ret) + return ret; ret = request_irq(ps2if->dev->irq[0], ps2_rxint, 0, SA1111_DRIVER_NAME(ps2if->dev), ps2if); if (ret) { printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n", ps2if->dev->irq[0], ret); + sa1111_disable_device(ps2if->dev); return ret; } @@ -140,6 +163,7 @@ static int ps2_open(struct serio *io) printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n", ps2if->dev->irq[1], ret); free_irq(ps2if->dev->irq[0], ps2if); + sa1111_disable_device(ps2if->dev); return ret; } @@ -147,7 +171,7 @@ static int ps2_open(struct serio *io) enable_irq_wake(ps2if->dev->irq[0]); - sa1111_writel(PS2CR_ENA, ps2if->base + SA1111_PS2CR); + sa1111_writel(PS2CR_ENA, ps2if->base + PS2CR); return 0; } @@ -155,7 +179,7 @@ static void ps2_close(struct serio *io) { struct ps2if *ps2if = io->port_data; - sa1111_writel(0, ps2if->base + SA1111_PS2CR); + sa1111_writel(0, ps2if->base + PS2CR); disable_irq_wake(ps2if->dev->irq[0]); @@ -175,7 +199,7 @@ static void __devinit ps2_clear_input(struct ps2if *ps2if) int maxread = 100; while (maxread--) { - if ((sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff) == 0xff) + if ((sa1111_readl(ps2if->base + PS2DATA) & 0xff) == 0xff) break; } } @@ -185,11 +209,11 @@ static unsigned int __devinit ps2_test_one(struct ps2if *ps2if, { unsigned int val; - sa1111_writel(PS2CR_ENA | mask, ps2if->base + SA1111_PS2CR); + sa1111_writel(PS2CR_ENA | mask, ps2if->base + PS2CR); udelay(2); - val = sa1111_readl(ps2if->base + SA1111_PS2STAT); + val = sa1111_readl(ps2if->base + PS2STAT); return val & (PS2STAT_KBC | PS2STAT_KBD); } @@ -220,7 +244,7 @@ static int __devinit ps2_test(struct ps2if *ps2if) ret = -ENODEV; } - sa1111_writel(0, ps2if->base + SA1111_PS2CR); + sa1111_writel(0, ps2if->base + PS2CR); return ret; } @@ -274,8 +298,8 @@ static int __devinit ps2_probe(struct sa1111_dev *dev) sa1111_enable_device(ps2if->dev); /* Incoming clock is 8MHz */ - sa1111_writel(0, ps2if->base + SA1111_PS2CLKDIV); - sa1111_writel(127, ps2if->base + SA1111_PS2PRECNT); + sa1111_writel(0, ps2if->base + PS2CLKDIV); + sa1111_writel(127, ps2if->base + PS2PRECNT); /* * Flush any pending input. @@ -330,6 +354,7 @@ static int __devexit ps2_remove(struct sa1111_dev *dev) static struct sa1111_driver ps2_driver = { .drv = { .name = "sa1111-ps2", + .owner = THIS_MODULE, }, .devid = SA1111_DEVID_PS2, .probe = ps2_probe, diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 97b31a0e0525..2a2141915aa0 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -260,7 +260,7 @@ config TOUCHSCREEN_ILI210X config TOUCHSCREEN_S3C2410 tristate "Samsung S3C2410/generic touchscreen input driver" - depends on ARCH_S3C2410 || SAMSUNG_DEV_TS + depends on ARCH_S3C24XX || SAMSUNG_DEV_TS select S3C_ADC help Say Y here if you have the s3c2410 touchscreen. diff --git a/drivers/input/touchscreen/jornada720_ts.c b/drivers/input/touchscreen/jornada720_ts.c index c3848ad2325b..d9be6eac99b1 100644 --- a/drivers/input/touchscreen/jornada720_ts.c +++ b/drivers/input/touchscreen/jornada720_ts.c @@ -22,6 +22,7 @@ #include <mach/hardware.h> #include <mach/jornada720.h> +#include <mach/irqs.h> MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>"); MODULE_DESCRIPTION("HP Jornada 710/720/728 touchscreen driver"); diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 6bea6962f8ee..3bd9fff5c589 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -142,4 +142,24 @@ config OMAP_IOMMU_DEBUG Say N unless you know you need this. +config TEGRA_IOMMU_GART + bool "Tegra GART IOMMU Support" + depends on ARCH_TEGRA_2x_SOC + select IOMMU_API + help + Enables support for remapping discontiguous physical memory + shared with the operating system into contiguous I/O virtual + space through the GART (Graphics Address Relocation Table) + hardware included on Tegra SoCs. + +config TEGRA_IOMMU_SMMU + bool "Tegra SMMU IOMMU Support" + depends on ARCH_TEGRA_3x_SOC + select IOMMU_API + help + Enables support for remapping discontiguous physical memory + shared with the operating system into contiguous I/O virtual + space through the SMMU (System Memory Management Unit) + hardware included on Tegra SoCs. + endif # IOMMU_SUPPORT diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 0e36b4934aff..7ad7a3bc1242 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -8,3 +8,5 @@ obj-$(CONFIG_IRQ_REMAP) += intr_remapping.o obj-$(CONFIG_OMAP_IOMMU) += omap-iommu.o obj-$(CONFIG_OMAP_IOVMM) += omap-iovmm.o obj-$(CONFIG_OMAP_IOMMU_DEBUG) += omap-iommu-debug.o +obj-$(CONFIG_TEGRA_IOMMU_GART) += tegra-gart.o +obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c index a35e98ad9725..c56790375e0f 100644 --- a/drivers/iommu/amd_iommu_init.c +++ b/drivers/iommu/amd_iommu_init.c @@ -196,6 +196,8 @@ static u32 rlookup_table_size; /* size if the rlookup table */ */ extern void iommu_flush_all_caches(struct amd_iommu *iommu); +static int amd_iommu_enable_interrupts(void); + static inline void update_last_devid(u16 devid) { if (devid > amd_iommu_last_bdf) @@ -358,8 +360,6 @@ static void iommu_disable(struct amd_iommu *iommu) */ static u8 * __init iommu_map_mmio_space(u64 address) { - u8 *ret; - if (!request_mem_region(address, MMIO_REGION_LENGTH, "amd_iommu")) { pr_err("AMD-Vi: Can not reserve memory region %llx for mmio\n", address); @@ -367,13 +367,7 @@ static u8 * __init iommu_map_mmio_space(u64 address) return NULL; } - ret = ioremap_nocache(address, MMIO_REGION_LENGTH); - if (ret != NULL) - return ret; - - release_mem_region(address, MMIO_REGION_LENGTH); - - return NULL; + return ioremap_nocache(address, MMIO_REGION_LENGTH); } static void __init iommu_unmap_mmio_space(struct amd_iommu *iommu) @@ -1131,8 +1125,9 @@ static int iommu_setup_msi(struct amd_iommu *iommu) { int r; - if (pci_enable_msi(iommu->dev)) - return 1; + r = pci_enable_msi(iommu->dev); + if (r) + return r; r = request_threaded_irq(iommu->dev->irq, amd_iommu_int_handler, @@ -1142,27 +1137,36 @@ static int iommu_setup_msi(struct amd_iommu *iommu) if (r) { pci_disable_msi(iommu->dev); - return 1; + return r; } iommu->int_enabled = true; - iommu_feature_enable(iommu, CONTROL_EVT_INT_EN); - - if (iommu->ppr_log != NULL) - iommu_feature_enable(iommu, CONTROL_PPFINT_EN); return 0; } static int iommu_init_msi(struct amd_iommu *iommu) { + int ret; + if (iommu->int_enabled) - return 0; + goto enable_faults; if (pci_find_capability(iommu->dev, PCI_CAP_ID_MSI)) - return iommu_setup_msi(iommu); + ret = iommu_setup_msi(iommu); + else + ret = -ENODEV; - return 1; + if (ret) + return ret; + +enable_faults: + iommu_feature_enable(iommu, CONTROL_EVT_INT_EN); + + if (iommu->ppr_log != NULL) + iommu_feature_enable(iommu, CONTROL_PPFINT_EN); + + return 0; } /**************************************************************************** @@ -1381,7 +1385,6 @@ static void enable_iommus(void) iommu_enable_ppr_log(iommu); iommu_enable_gt(iommu); iommu_set_exclusion_range(iommu); - iommu_init_msi(iommu); iommu_enable(iommu); iommu_flush_all_caches(iommu); } @@ -1409,6 +1412,8 @@ static void amd_iommu_resume(void) /* re-load the hardware */ enable_iommus(); + + amd_iommu_enable_interrupts(); } static int amd_iommu_suspend(void) @@ -1424,10 +1429,40 @@ static struct syscore_ops amd_iommu_syscore_ops = { .resume = amd_iommu_resume, }; +static void __init free_on_init_error(void) +{ + amd_iommu_uninit_devices(); + + free_pages((unsigned long)amd_iommu_pd_alloc_bitmap, + get_order(MAX_DOMAIN_ID/8)); + + free_pages((unsigned long)amd_iommu_rlookup_table, + get_order(rlookup_table_size)); + + free_pages((unsigned long)amd_iommu_alias_table, + get_order(alias_table_size)); + + free_pages((unsigned long)amd_iommu_dev_table, + get_order(dev_table_size)); + + free_iommu_all(); + + free_unity_maps(); + +#ifdef CONFIG_GART_IOMMU + /* + * We failed to initialize the AMD IOMMU - try fallback to GART + * if possible. + */ + gart_iommu_init(); + +#endif +} + /* - * This is the core init function for AMD IOMMU hardware in the system. - * This function is called from the generic x86 DMA layer initialization - * code. + * This is the hardware init function for AMD IOMMU in the system. + * This function is called either from amd_iommu_init or from the interrupt + * remapping setup code. * * This function basically parses the ACPI table for AMD IOMMU (IVRS) * three times: @@ -1446,16 +1481,21 @@ static struct syscore_ops amd_iommu_syscore_ops = { * remapping requirements parsed out of the ACPI table in * this last pass. * - * After that the hardware is initialized and ready to go. In the last - * step we do some Linux specific things like registering the driver in - * the dma_ops interface and initializing the suspend/resume support - * functions. Finally it prints some information about AMD IOMMUs and - * the driver state and enables the hardware. + * After everything is set up the IOMMUs are enabled and the necessary + * hotplug and suspend notifiers are registered. */ -static int __init amd_iommu_init(void) +int __init amd_iommu_init_hardware(void) { int i, ret = 0; + if (!amd_iommu_detected) + return -ENODEV; + + if (amd_iommu_dev_table != NULL) { + /* Hardware already initialized */ + return 0; + } + /* * First parse ACPI tables to find the largest Bus/Dev/Func * we need to handle. Upon this information the shared data @@ -1472,9 +1512,8 @@ static int __init amd_iommu_init(void) alias_table_size = tbl_size(ALIAS_TABLE_ENTRY_SIZE); rlookup_table_size = tbl_size(RLOOKUP_TABLE_ENTRY_SIZE); - ret = -ENOMEM; - /* Device table - directly used by all IOMMUs */ + ret = -ENOMEM; amd_iommu_dev_table = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, get_order(dev_table_size)); if (amd_iommu_dev_table == NULL) @@ -1546,20 +1585,65 @@ static int __init amd_iommu_init(void) enable_iommus(); + amd_iommu_init_notifier(); + + register_syscore_ops(&amd_iommu_syscore_ops); + +out: + return ret; + +free: + free_on_init_error(); + + return ret; +} + +static int amd_iommu_enable_interrupts(void) +{ + struct amd_iommu *iommu; + int ret = 0; + + for_each_iommu(iommu) { + ret = iommu_init_msi(iommu); + if (ret) + goto out; + } + +out: + return ret; +} + +/* + * This is the core init function for AMD IOMMU hardware in the system. + * This function is called from the generic x86 DMA layer initialization + * code. + * + * The function calls amd_iommu_init_hardware() to setup and enable the + * IOMMU hardware if this has not happened yet. After that the driver + * registers for the DMA-API and for the IOMMU-API as necessary. + */ +static int __init amd_iommu_init(void) +{ + int ret = 0; + + ret = amd_iommu_init_hardware(); + if (ret) + goto out; + + ret = amd_iommu_enable_interrupts(); + if (ret) + goto free; + if (iommu_pass_through) ret = amd_iommu_init_passthrough(); else ret = amd_iommu_init_dma_ops(); if (ret) - goto free_disable; + goto free; amd_iommu_init_api(); - amd_iommu_init_notifier(); - - register_syscore_ops(&amd_iommu_syscore_ops); - if (iommu_pass_through) goto out; @@ -1569,39 +1653,14 @@ static int __init amd_iommu_init(void) printk(KERN_INFO "AMD-Vi: Lazy IO/TLB flushing enabled\n"); x86_platform.iommu_shutdown = disable_iommus; + out: return ret; -free_disable: - disable_iommus(); - free: - amd_iommu_uninit_devices(); - - free_pages((unsigned long)amd_iommu_pd_alloc_bitmap, - get_order(MAX_DOMAIN_ID/8)); - - free_pages((unsigned long)amd_iommu_rlookup_table, - get_order(rlookup_table_size)); - - free_pages((unsigned long)amd_iommu_alias_table, - get_order(alias_table_size)); - - free_pages((unsigned long)amd_iommu_dev_table, - get_order(dev_table_size)); - - free_iommu_all(); - - free_unity_maps(); - -#ifdef CONFIG_GART_IOMMU - /* - * We failed to initialize the AMD IOMMU - try fallback to GART - * if possible. - */ - gart_iommu_init(); + disable_iommus(); -#endif + free_on_init_error(); goto out; } diff --git a/drivers/iommu/amd_iommu_v2.c b/drivers/iommu/amd_iommu_v2.c index 8add9f125d3e..036fe9bf157e 100644 --- a/drivers/iommu/amd_iommu_v2.c +++ b/drivers/iommu/amd_iommu_v2.c @@ -921,7 +921,16 @@ static int __init amd_iommu_v2_init(void) size_t state_table_size; int ret; - pr_info("AMD IOMMUv2 driver by Joerg Roedel <joerg.roedel@amd.com>"); + pr_info("AMD IOMMUv2 driver by Joerg Roedel <joerg.roedel@amd.com>\n"); + + if (!amd_iommu_v2_supported()) { + pr_info("AMD IOMMUv2 functionality not available on this sytem\n"); + /* + * Load anyway to provide the symbols to other modules + * which may use AMD IOMMUv2 optionally. + */ + return 0; + } spin_lock_init(&state_lock); @@ -961,6 +970,9 @@ static void __exit amd_iommu_v2_exit(void) size_t state_table_size; int i; + if (!amd_iommu_v2_supported()) + return; + profile_event_unregister(PROFILE_TASK_EXIT, &profile_nb); amd_iommu_unregister_ppr_notifier(&ppr_nb); diff --git a/drivers/iommu/tegra-gart.c b/drivers/iommu/tegra-gart.c new file mode 100644 index 000000000000..779306ee7b16 --- /dev/null +++ b/drivers/iommu/tegra-gart.c @@ -0,0 +1,451 @@ +/* + * IOMMU API for GART in Tegra20 + * + * Copyright (c) 2010-2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define pr_fmt(fmt) "%s(): " fmt, __func__ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/list.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iommu.h> + +#include <asm/cacheflush.h> + +/* bitmap of the page sizes currently supported */ +#define GART_IOMMU_PGSIZES (SZ_4K) + +#define GART_CONFIG 0x24 +#define GART_ENTRY_ADDR 0x28 +#define GART_ENTRY_DATA 0x2c +#define GART_ENTRY_PHYS_ADDR_VALID (1 << 31) + +#define GART_PAGE_SHIFT 12 +#define GART_PAGE_SIZE (1 << GART_PAGE_SHIFT) +#define GART_PAGE_MASK \ + (~(GART_PAGE_SIZE - 1) & ~GART_ENTRY_PHYS_ADDR_VALID) + +struct gart_client { + struct device *dev; + struct list_head list; +}; + +struct gart_device { + void __iomem *regs; + u32 *savedata; + u32 page_count; /* total remappable size */ + dma_addr_t iovmm_base; /* offset to vmm_area */ + spinlock_t pte_lock; /* for pagetable */ + struct list_head client; + spinlock_t client_lock; /* for client list */ + struct device *dev; +}; + +static struct gart_device *gart_handle; /* unique for a system */ + +#define GART_PTE(_pfn) \ + (GART_ENTRY_PHYS_ADDR_VALID | ((_pfn) << PAGE_SHIFT)) + +/* + * Any interaction between any block on PPSB and a block on APB or AHB + * must have these read-back to ensure the APB/AHB bus transaction is + * complete before initiating activity on the PPSB block. + */ +#define FLUSH_GART_REGS(gart) ((void)readl((gart)->regs + GART_CONFIG)) + +#define for_each_gart_pte(gart, iova) \ + for (iova = gart->iovmm_base; \ + iova < gart->iovmm_base + GART_PAGE_SIZE * gart->page_count; \ + iova += GART_PAGE_SIZE) + +static inline void gart_set_pte(struct gart_device *gart, + unsigned long offs, u32 pte) +{ + writel(offs, gart->regs + GART_ENTRY_ADDR); + writel(pte, gart->regs + GART_ENTRY_DATA); + + dev_dbg(gart->dev, "%s %08lx:%08x\n", + pte ? "map" : "unmap", offs, pte & GART_PAGE_MASK); +} + +static inline unsigned long gart_read_pte(struct gart_device *gart, + unsigned long offs) +{ + unsigned long pte; + + writel(offs, gart->regs + GART_ENTRY_ADDR); + pte = readl(gart->regs + GART_ENTRY_DATA); + + return pte; +} + +static void do_gart_setup(struct gart_device *gart, const u32 *data) +{ + unsigned long iova; + + for_each_gart_pte(gart, iova) + gart_set_pte(gart, iova, data ? *(data++) : 0); + + writel(1, gart->regs + GART_CONFIG); + FLUSH_GART_REGS(gart); +} + +#ifdef DEBUG +static void gart_dump_table(struct gart_device *gart) +{ + unsigned long iova; + unsigned long flags; + + spin_lock_irqsave(&gart->pte_lock, flags); + for_each_gart_pte(gart, iova) { + unsigned long pte; + + pte = gart_read_pte(gart, iova); + + dev_dbg(gart->dev, "%s %08lx:%08lx\n", + (GART_ENTRY_PHYS_ADDR_VALID & pte) ? "v" : " ", + iova, pte & GART_PAGE_MASK); + } + spin_unlock_irqrestore(&gart->pte_lock, flags); +} +#else +static inline void gart_dump_table(struct gart_device *gart) +{ +} +#endif + +static inline bool gart_iova_range_valid(struct gart_device *gart, + unsigned long iova, size_t bytes) +{ + unsigned long iova_start, iova_end, gart_start, gart_end; + + iova_start = iova; + iova_end = iova_start + bytes - 1; + gart_start = gart->iovmm_base; + gart_end = gart_start + gart->page_count * GART_PAGE_SIZE - 1; + + if (iova_start < gart_start) + return false; + if (iova_end > gart_end) + return false; + return true; +} + +static int gart_iommu_attach_dev(struct iommu_domain *domain, + struct device *dev) +{ + struct gart_device *gart; + struct gart_client *client, *c; + int err = 0; + + gart = dev_get_drvdata(dev->parent); + if (!gart) + return -EINVAL; + domain->priv = gart; + + client = devm_kzalloc(gart->dev, sizeof(*c), GFP_KERNEL); + if (!client) + return -ENOMEM; + client->dev = dev; + + spin_lock(&gart->client_lock); + list_for_each_entry(c, &gart->client, list) { + if (c->dev == dev) { + dev_err(gart->dev, + "%s is already attached\n", dev_name(dev)); + err = -EINVAL; + goto fail; + } + } + list_add(&client->list, &gart->client); + spin_unlock(&gart->client_lock); + dev_dbg(gart->dev, "Attached %s\n", dev_name(dev)); + return 0; + +fail: + devm_kfree(gart->dev, client); + spin_unlock(&gart->client_lock); + return err; +} + +static void gart_iommu_detach_dev(struct iommu_domain *domain, + struct device *dev) +{ + struct gart_device *gart = domain->priv; + struct gart_client *c; + + spin_lock(&gart->client_lock); + + list_for_each_entry(c, &gart->client, list) { + if (c->dev == dev) { + list_del(&c->list); + devm_kfree(gart->dev, c); + dev_dbg(gart->dev, "Detached %s\n", dev_name(dev)); + goto out; + } + } + dev_err(gart->dev, "Couldn't find\n"); +out: + spin_unlock(&gart->client_lock); +} + +static int gart_iommu_domain_init(struct iommu_domain *domain) +{ + return 0; +} + +static void gart_iommu_domain_destroy(struct iommu_domain *domain) +{ + struct gart_device *gart = domain->priv; + + if (!gart) + return; + + spin_lock(&gart->client_lock); + if (!list_empty(&gart->client)) { + struct gart_client *c; + + list_for_each_entry(c, &gart->client, list) + gart_iommu_detach_dev(domain, c->dev); + } + spin_unlock(&gart->client_lock); + domain->priv = NULL; +} + +static int gart_iommu_map(struct iommu_domain *domain, unsigned long iova, + phys_addr_t pa, size_t bytes, int prot) +{ + struct gart_device *gart = domain->priv; + unsigned long flags; + unsigned long pfn; + + if (!gart_iova_range_valid(gart, iova, bytes)) + return -EINVAL; + + spin_lock_irqsave(&gart->pte_lock, flags); + pfn = __phys_to_pfn(pa); + if (!pfn_valid(pfn)) { + dev_err(gart->dev, "Invalid page: %08x\n", pa); + spin_unlock_irqrestore(&gart->pte_lock, flags); + return -EINVAL; + } + gart_set_pte(gart, iova, GART_PTE(pfn)); + FLUSH_GART_REGS(gart); + spin_unlock_irqrestore(&gart->pte_lock, flags); + return 0; +} + +static size_t gart_iommu_unmap(struct iommu_domain *domain, unsigned long iova, + size_t bytes) +{ + struct gart_device *gart = domain->priv; + unsigned long flags; + + if (!gart_iova_range_valid(gart, iova, bytes)) + return 0; + + spin_lock_irqsave(&gart->pte_lock, flags); + gart_set_pte(gart, iova, 0); + FLUSH_GART_REGS(gart); + spin_unlock_irqrestore(&gart->pte_lock, flags); + return 0; +} + +static phys_addr_t gart_iommu_iova_to_phys(struct iommu_domain *domain, + unsigned long iova) +{ + struct gart_device *gart = domain->priv; + unsigned long pte; + phys_addr_t pa; + unsigned long flags; + + if (!gart_iova_range_valid(gart, iova, 0)) + return -EINVAL; + + spin_lock_irqsave(&gart->pte_lock, flags); + pte = gart_read_pte(gart, iova); + spin_unlock_irqrestore(&gart->pte_lock, flags); + + pa = (pte & GART_PAGE_MASK); + if (!pfn_valid(__phys_to_pfn(pa))) { + dev_err(gart->dev, "No entry for %08lx:%08x\n", iova, pa); + gart_dump_table(gart); + return -EINVAL; + } + return pa; +} + +static int gart_iommu_domain_has_cap(struct iommu_domain *domain, + unsigned long cap) +{ + return 0; +} + +static struct iommu_ops gart_iommu_ops = { + .domain_init = gart_iommu_domain_init, + .domain_destroy = gart_iommu_domain_destroy, + .attach_dev = gart_iommu_attach_dev, + .detach_dev = gart_iommu_detach_dev, + .map = gart_iommu_map, + .unmap = gart_iommu_unmap, + .iova_to_phys = gart_iommu_iova_to_phys, + .domain_has_cap = gart_iommu_domain_has_cap, + .pgsize_bitmap = GART_IOMMU_PGSIZES, +}; + +static int tegra_gart_suspend(struct device *dev) +{ + struct gart_device *gart = dev_get_drvdata(dev); + unsigned long iova; + u32 *data = gart->savedata; + unsigned long flags; + + spin_lock_irqsave(&gart->pte_lock, flags); + for_each_gart_pte(gart, iova) + *(data++) = gart_read_pte(gart, iova); + spin_unlock_irqrestore(&gart->pte_lock, flags); + return 0; +} + +static int tegra_gart_resume(struct device *dev) +{ + struct gart_device *gart = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&gart->pte_lock, flags); + do_gart_setup(gart, gart->savedata); + spin_unlock_irqrestore(&gart->pte_lock, flags); + return 0; +} + +static int tegra_gart_probe(struct platform_device *pdev) +{ + struct gart_device *gart; + struct resource *res, *res_remap; + void __iomem *gart_regs; + int err; + struct device *dev = &pdev->dev; + + if (gart_handle) + return -EIO; + + BUILD_BUG_ON(PAGE_SHIFT != GART_PAGE_SHIFT); + + /* the GART memory aperture is required */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res_remap = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res || !res_remap) { + dev_err(dev, "GART memory aperture expected\n"); + return -ENXIO; + } + + gart = devm_kzalloc(dev, sizeof(*gart), GFP_KERNEL); + if (!gart) { + dev_err(dev, "failed to allocate gart_device\n"); + return -ENOMEM; + } + + gart_regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!gart_regs) { + dev_err(dev, "failed to remap GART registers\n"); + err = -ENXIO; + goto fail; + } + + gart->dev = &pdev->dev; + spin_lock_init(&gart->pte_lock); + spin_lock_init(&gart->client_lock); + INIT_LIST_HEAD(&gart->client); + gart->regs = gart_regs; + gart->iovmm_base = (dma_addr_t)res_remap->start; + gart->page_count = (resource_size(res_remap) >> GART_PAGE_SHIFT); + + gart->savedata = vmalloc(sizeof(u32) * gart->page_count); + if (!gart->savedata) { + dev_err(dev, "failed to allocate context save area\n"); + err = -ENOMEM; + goto fail; + } + + platform_set_drvdata(pdev, gart); + do_gart_setup(gart, NULL); + + gart_handle = gart; + return 0; + +fail: + if (gart_regs) + devm_iounmap(dev, gart_regs); + if (gart && gart->savedata) + vfree(gart->savedata); + devm_kfree(dev, gart); + return err; +} + +static int tegra_gart_remove(struct platform_device *pdev) +{ + struct gart_device *gart = platform_get_drvdata(pdev); + struct device *dev = gart->dev; + + writel(0, gart->regs + GART_CONFIG); + if (gart->savedata) + vfree(gart->savedata); + if (gart->regs) + devm_iounmap(dev, gart->regs); + devm_kfree(dev, gart); + gart_handle = NULL; + return 0; +} + +const struct dev_pm_ops tegra_gart_pm_ops = { + .suspend = tegra_gart_suspend, + .resume = tegra_gart_resume, +}; + +static struct platform_driver tegra_gart_driver = { + .probe = tegra_gart_probe, + .remove = tegra_gart_remove, + .driver = { + .owner = THIS_MODULE, + .name = "tegra-gart", + .pm = &tegra_gart_pm_ops, + }, +}; + +static int __devinit tegra_gart_init(void) +{ + bus_set_iommu(&platform_bus_type, &gart_iommu_ops); + return platform_driver_register(&tegra_gart_driver); +} + +static void __exit tegra_gart_exit(void) +{ + platform_driver_unregister(&tegra_gart_driver); +} + +subsys_initcall(tegra_gart_init); +module_exit(tegra_gart_exit); + +MODULE_DESCRIPTION("IOMMU API for GART in Tegra20"); +MODULE_AUTHOR("Hiroshi DOYU <hdoyu@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c new file mode 100644 index 000000000000..eb93c821f592 --- /dev/null +++ b/drivers/iommu/tegra-smmu.c @@ -0,0 +1,1034 @@ +/* + * IOMMU API for SMMU in Tegra30 + * + * Copyright (c) 2011-2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define pr_fmt(fmt) "%s(): " fmt, __func__ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/iommu.h> +#include <linux/io.h> + +#include <asm/page.h> +#include <asm/cacheflush.h> + +#include <mach/iomap.h> +#include <mach/smmu.h> + +/* bitmap of the page sizes currently supported */ +#define SMMU_IOMMU_PGSIZES (SZ_4K) + +#define SMMU_CONFIG 0x10 +#define SMMU_CONFIG_DISABLE 0 +#define SMMU_CONFIG_ENABLE 1 + +#define SMMU_TLB_CONFIG 0x14 +#define SMMU_TLB_CONFIG_STATS__MASK (1 << 31) +#define SMMU_TLB_CONFIG_STATS__ENABLE (1 << 31) +#define SMMU_TLB_CONFIG_HIT_UNDER_MISS__ENABLE (1 << 29) +#define SMMU_TLB_CONFIG_ACTIVE_LINES__VALUE 0x10 +#define SMMU_TLB_CONFIG_RESET_VAL 0x20000010 + +#define SMMU_PTC_CONFIG 0x18 +#define SMMU_PTC_CONFIG_STATS__MASK (1 << 31) +#define SMMU_PTC_CONFIG_STATS__ENABLE (1 << 31) +#define SMMU_PTC_CONFIG_CACHE__ENABLE (1 << 29) +#define SMMU_PTC_CONFIG_INDEX_MAP__PATTERN 0x3f +#define SMMU_PTC_CONFIG_RESET_VAL 0x2000003f + +#define SMMU_PTB_ASID 0x1c +#define SMMU_PTB_ASID_CURRENT_SHIFT 0 + +#define SMMU_PTB_DATA 0x20 +#define SMMU_PTB_DATA_RESET_VAL 0 +#define SMMU_PTB_DATA_ASID_NONSECURE_SHIFT 29 +#define SMMU_PTB_DATA_ASID_WRITABLE_SHIFT 30 +#define SMMU_PTB_DATA_ASID_READABLE_SHIFT 31 + +#define SMMU_TLB_FLUSH 0x30 +#define SMMU_TLB_FLUSH_VA_MATCH_ALL 0 +#define SMMU_TLB_FLUSH_VA_MATCH_SECTION 2 +#define SMMU_TLB_FLUSH_VA_MATCH_GROUP 3 +#define SMMU_TLB_FLUSH_ASID_SHIFT 29 +#define SMMU_TLB_FLUSH_ASID_MATCH_DISABLE 0 +#define SMMU_TLB_FLUSH_ASID_MATCH_ENABLE 1 +#define SMMU_TLB_FLUSH_ASID_MATCH_SHIFT 31 + +#define SMMU_PTC_FLUSH 0x34 +#define SMMU_PTC_FLUSH_TYPE_ALL 0 +#define SMMU_PTC_FLUSH_TYPE_ADR 1 +#define SMMU_PTC_FLUSH_ADR_SHIFT 4 + +#define SMMU_ASID_SECURITY 0x38 + +#define SMMU_STATS_TLB_HIT_COUNT 0x1f0 +#define SMMU_STATS_TLB_MISS_COUNT 0x1f4 +#define SMMU_STATS_PTC_HIT_COUNT 0x1f8 +#define SMMU_STATS_PTC_MISS_COUNT 0x1fc + +#define SMMU_TRANSLATION_ENABLE_0 0x228 +#define SMMU_TRANSLATION_ENABLE_1 0x22c +#define SMMU_TRANSLATION_ENABLE_2 0x230 + +#define SMMU_AFI_ASID 0x238 /* PCIE */ +#define SMMU_AVPC_ASID 0x23c /* AVP */ +#define SMMU_DC_ASID 0x240 /* Display controller */ +#define SMMU_DCB_ASID 0x244 /* Display controller B */ +#define SMMU_EPP_ASID 0x248 /* Encoder pre-processor */ +#define SMMU_G2_ASID 0x24c /* 2D engine */ +#define SMMU_HC_ASID 0x250 /* Host1x */ +#define SMMU_HDA_ASID 0x254 /* High-def audio */ +#define SMMU_ISP_ASID 0x258 /* Image signal processor */ +#define SMMU_MPE_ASID 0x264 /* MPEG encoder */ +#define SMMU_NV_ASID 0x268 /* (3D) */ +#define SMMU_NV2_ASID 0x26c /* (3D) */ +#define SMMU_PPCS_ASID 0x270 /* AHB */ +#define SMMU_SATA_ASID 0x278 /* SATA */ +#define SMMU_VDE_ASID 0x27c /* Video decoder */ +#define SMMU_VI_ASID 0x280 /* Video input */ + +#define SMMU_PDE_NEXT_SHIFT 28 + +/* AHB Arbiter Registers */ +#define AHB_XBAR_CTRL 0xe0 +#define AHB_XBAR_CTRL_SMMU_INIT_DONE_DONE 1 +#define AHB_XBAR_CTRL_SMMU_INIT_DONE_SHIFT 17 + +#define SMMU_NUM_ASIDS 4 +#define SMMU_TLB_FLUSH_VA_SECTION__MASK 0xffc00000 +#define SMMU_TLB_FLUSH_VA_SECTION__SHIFT 12 /* right shift */ +#define SMMU_TLB_FLUSH_VA_GROUP__MASK 0xffffc000 +#define SMMU_TLB_FLUSH_VA_GROUP__SHIFT 12 /* right shift */ +#define SMMU_TLB_FLUSH_VA(iova, which) \ + ((((iova) & SMMU_TLB_FLUSH_VA_##which##__MASK) >> \ + SMMU_TLB_FLUSH_VA_##which##__SHIFT) | \ + SMMU_TLB_FLUSH_VA_MATCH_##which) +#define SMMU_PTB_ASID_CUR(n) \ + ((n) << SMMU_PTB_ASID_CURRENT_SHIFT) +#define SMMU_TLB_FLUSH_ASID_MATCH_disable \ + (SMMU_TLB_FLUSH_ASID_MATCH_DISABLE << \ + SMMU_TLB_FLUSH_ASID_MATCH_SHIFT) +#define SMMU_TLB_FLUSH_ASID_MATCH__ENABLE \ + (SMMU_TLB_FLUSH_ASID_MATCH_ENABLE << \ + SMMU_TLB_FLUSH_ASID_MATCH_SHIFT) + +#define SMMU_PAGE_SHIFT 12 +#define SMMU_PAGE_SIZE (1 << SMMU_PAGE_SHIFT) + +#define SMMU_PDIR_COUNT 1024 +#define SMMU_PDIR_SIZE (sizeof(unsigned long) * SMMU_PDIR_COUNT) +#define SMMU_PTBL_COUNT 1024 +#define SMMU_PTBL_SIZE (sizeof(unsigned long) * SMMU_PTBL_COUNT) +#define SMMU_PDIR_SHIFT 12 +#define SMMU_PDE_SHIFT 12 +#define SMMU_PTE_SHIFT 12 +#define SMMU_PFN_MASK 0x000fffff + +#define SMMU_ADDR_TO_PFN(addr) ((addr) >> 12) +#define SMMU_ADDR_TO_PDN(addr) ((addr) >> 22) +#define SMMU_PDN_TO_ADDR(addr) ((pdn) << 22) + +#define _READABLE (1 << SMMU_PTB_DATA_ASID_READABLE_SHIFT) +#define _WRITABLE (1 << SMMU_PTB_DATA_ASID_WRITABLE_SHIFT) +#define _NONSECURE (1 << SMMU_PTB_DATA_ASID_NONSECURE_SHIFT) +#define _PDE_NEXT (1 << SMMU_PDE_NEXT_SHIFT) +#define _MASK_ATTR (_READABLE | _WRITABLE | _NONSECURE) + +#define _PDIR_ATTR (_READABLE | _WRITABLE | _NONSECURE) + +#define _PDE_ATTR (_READABLE | _WRITABLE | _NONSECURE) +#define _PDE_ATTR_N (_PDE_ATTR | _PDE_NEXT) +#define _PDE_VACANT(pdn) (((pdn) << 10) | _PDE_ATTR) + +#define _PTE_ATTR (_READABLE | _WRITABLE | _NONSECURE) +#define _PTE_VACANT(addr) (((addr) >> SMMU_PAGE_SHIFT) | _PTE_ATTR) + +#define SMMU_MK_PDIR(page, attr) \ + ((page_to_phys(page) >> SMMU_PDIR_SHIFT) | (attr)) +#define SMMU_MK_PDE(page, attr) \ + (unsigned long)((page_to_phys(page) >> SMMU_PDE_SHIFT) | (attr)) +#define SMMU_EX_PTBL_PAGE(pde) \ + pfn_to_page((unsigned long)(pde) & SMMU_PFN_MASK) +#define SMMU_PFN_TO_PTE(pfn, attr) (unsigned long)((pfn) | (attr)) + +#define SMMU_ASID_ENABLE(asid) ((asid) | (1 << 31)) +#define SMMU_ASID_DISABLE 0 +#define SMMU_ASID_ASID(n) ((n) & ~SMMU_ASID_ENABLE(0)) + +#define smmu_client_enable_hwgrp(c, m) smmu_client_set_hwgrp(c, m, 1) +#define smmu_client_disable_hwgrp(c) smmu_client_set_hwgrp(c, 0, 0) +#define __smmu_client_enable_hwgrp(c, m) __smmu_client_set_hwgrp(c, m, 1) +#define __smmu_client_disable_hwgrp(c) __smmu_client_set_hwgrp(c, 0, 0) + +#define HWGRP_INIT(client) [HWGRP_##client] = SMMU_##client##_ASID + +static const u32 smmu_hwgrp_asid_reg[] = { + HWGRP_INIT(AFI), + HWGRP_INIT(AVPC), + HWGRP_INIT(DC), + HWGRP_INIT(DCB), + HWGRP_INIT(EPP), + HWGRP_INIT(G2), + HWGRP_INIT(HC), + HWGRP_INIT(HDA), + HWGRP_INIT(ISP), + HWGRP_INIT(MPE), + HWGRP_INIT(NV), + HWGRP_INIT(NV2), + HWGRP_INIT(PPCS), + HWGRP_INIT(SATA), + HWGRP_INIT(VDE), + HWGRP_INIT(VI), +}; +#define HWGRP_ASID_REG(x) (smmu_hwgrp_asid_reg[x]) + +/* + * Per client for address space + */ +struct smmu_client { + struct device *dev; + struct list_head list; + struct smmu_as *as; + u32 hwgrp; +}; + +/* + * Per address space + */ +struct smmu_as { + struct smmu_device *smmu; /* back pointer to container */ + unsigned int asid; + spinlock_t lock; /* for pagetable */ + struct page *pdir_page; + unsigned long pdir_attr; + unsigned long pde_attr; + unsigned long pte_attr; + unsigned int *pte_count; + + struct list_head client; + spinlock_t client_lock; /* for client list */ +}; + +/* + * Per SMMU device - IOMMU device + */ +struct smmu_device { + void __iomem *regs, *regs_ahbarb; + unsigned long iovmm_base; /* remappable base address */ + unsigned long page_count; /* total remappable size */ + spinlock_t lock; + char *name; + struct device *dev; + int num_as; + struct smmu_as *as; /* Run-time allocated array */ + struct page *avp_vector_page; /* dummy page shared by all AS's */ + + /* + * Register image savers for suspend/resume + */ + unsigned long translation_enable_0; + unsigned long translation_enable_1; + unsigned long translation_enable_2; + unsigned long asid_security; +}; + +static struct smmu_device *smmu_handle; /* unique for a system */ + +/* + * SMMU/AHB register accessors + */ +static inline u32 smmu_read(struct smmu_device *smmu, size_t offs) +{ + return readl(smmu->regs + offs); +} +static inline void smmu_write(struct smmu_device *smmu, u32 val, size_t offs) +{ + writel(val, smmu->regs + offs); +} + +static inline u32 ahb_read(struct smmu_device *smmu, size_t offs) +{ + return readl(smmu->regs_ahbarb + offs); +} +static inline void ahb_write(struct smmu_device *smmu, u32 val, size_t offs) +{ + writel(val, smmu->regs_ahbarb + offs); +} + +#define VA_PAGE_TO_PA(va, page) \ + (page_to_phys(page) + ((unsigned long)(va) & ~PAGE_MASK)) + +#define FLUSH_CPU_DCACHE(va, page, size) \ + do { \ + unsigned long _pa_ = VA_PAGE_TO_PA(va, page); \ + __cpuc_flush_dcache_area((void *)(va), (size_t)(size)); \ + outer_flush_range(_pa_, _pa_+(size_t)(size)); \ + } while (0) + +/* + * Any interaction between any block on PPSB and a block on APB or AHB + * must have these read-back barriers to ensure the APB/AHB bus + * transaction is complete before initiating activity on the PPSB + * block. + */ +#define FLUSH_SMMU_REGS(smmu) smmu_read(smmu, SMMU_CONFIG) + +#define smmu_client_hwgrp(c) (u32)((c)->dev->platform_data) + +static int __smmu_client_set_hwgrp(struct smmu_client *c, + unsigned long map, int on) +{ + int i; + struct smmu_as *as = c->as; + u32 val, offs, mask = SMMU_ASID_ENABLE(as->asid); + struct smmu_device *smmu = as->smmu; + + WARN_ON(!on && map); + if (on && !map) + return -EINVAL; + if (!on) + map = smmu_client_hwgrp(c); + + for_each_set_bit(i, &map, HWGRP_COUNT) { + offs = HWGRP_ASID_REG(i); + val = smmu_read(smmu, offs); + if (on) { + if (WARN_ON(val & mask)) + goto err_hw_busy; + val |= mask; + } else { + WARN_ON((val & mask) == mask); + val &= ~mask; + } + smmu_write(smmu, val, offs); + } + FLUSH_SMMU_REGS(smmu); + c->hwgrp = map; + return 0; + +err_hw_busy: + for_each_set_bit(i, &map, HWGRP_COUNT) { + offs = HWGRP_ASID_REG(i); + val = smmu_read(smmu, offs); + val &= ~mask; + smmu_write(smmu, val, offs); + } + return -EBUSY; +} + +static int smmu_client_set_hwgrp(struct smmu_client *c, u32 map, int on) +{ + u32 val; + unsigned long flags; + struct smmu_as *as = c->as; + struct smmu_device *smmu = as->smmu; + + spin_lock_irqsave(&smmu->lock, flags); + val = __smmu_client_set_hwgrp(c, map, on); + spin_unlock_irqrestore(&smmu->lock, flags); + return val; +} + +/* + * Flush all TLB entries and all PTC entries + * Caller must lock smmu + */ +static void smmu_flush_regs(struct smmu_device *smmu, int enable) +{ + u32 val; + + smmu_write(smmu, SMMU_PTC_FLUSH_TYPE_ALL, SMMU_PTC_FLUSH); + FLUSH_SMMU_REGS(smmu); + val = SMMU_TLB_FLUSH_VA_MATCH_ALL | + SMMU_TLB_FLUSH_ASID_MATCH_disable; + smmu_write(smmu, val, SMMU_TLB_FLUSH); + + if (enable) + smmu_write(smmu, SMMU_CONFIG_ENABLE, SMMU_CONFIG); + FLUSH_SMMU_REGS(smmu); +} + +static void smmu_setup_regs(struct smmu_device *smmu) +{ + int i; + u32 val; + + for (i = 0; i < smmu->num_as; i++) { + struct smmu_as *as = &smmu->as[i]; + struct smmu_client *c; + + smmu_write(smmu, SMMU_PTB_ASID_CUR(as->asid), SMMU_PTB_ASID); + val = as->pdir_page ? + SMMU_MK_PDIR(as->pdir_page, as->pdir_attr) : + SMMU_PTB_DATA_RESET_VAL; + smmu_write(smmu, val, SMMU_PTB_DATA); + + list_for_each_entry(c, &as->client, list) + __smmu_client_set_hwgrp(c, c->hwgrp, 1); + } + + smmu_write(smmu, smmu->translation_enable_0, SMMU_TRANSLATION_ENABLE_0); + smmu_write(smmu, smmu->translation_enable_1, SMMU_TRANSLATION_ENABLE_1); + smmu_write(smmu, smmu->translation_enable_2, SMMU_TRANSLATION_ENABLE_2); + smmu_write(smmu, smmu->asid_security, SMMU_ASID_SECURITY); + smmu_write(smmu, SMMU_TLB_CONFIG_RESET_VAL, SMMU_TLB_CONFIG); + smmu_write(smmu, SMMU_PTC_CONFIG_RESET_VAL, SMMU_PTC_CONFIG); + + smmu_flush_regs(smmu, 1); + + val = ahb_read(smmu, AHB_XBAR_CTRL); + val |= AHB_XBAR_CTRL_SMMU_INIT_DONE_DONE << + AHB_XBAR_CTRL_SMMU_INIT_DONE_SHIFT; + ahb_write(smmu, val, AHB_XBAR_CTRL); +} + +static void flush_ptc_and_tlb(struct smmu_device *smmu, + struct smmu_as *as, dma_addr_t iova, + unsigned long *pte, struct page *page, int is_pde) +{ + u32 val; + unsigned long tlb_flush_va = is_pde + ? SMMU_TLB_FLUSH_VA(iova, SECTION) + : SMMU_TLB_FLUSH_VA(iova, GROUP); + + val = SMMU_PTC_FLUSH_TYPE_ADR | VA_PAGE_TO_PA(pte, page); + smmu_write(smmu, val, SMMU_PTC_FLUSH); + FLUSH_SMMU_REGS(smmu); + val = tlb_flush_va | + SMMU_TLB_FLUSH_ASID_MATCH__ENABLE | + (as->asid << SMMU_TLB_FLUSH_ASID_SHIFT); + smmu_write(smmu, val, SMMU_TLB_FLUSH); + FLUSH_SMMU_REGS(smmu); +} + +static void free_ptbl(struct smmu_as *as, dma_addr_t iova) +{ + unsigned long pdn = SMMU_ADDR_TO_PDN(iova); + unsigned long *pdir = (unsigned long *)page_address(as->pdir_page); + + if (pdir[pdn] != _PDE_VACANT(pdn)) { + dev_dbg(as->smmu->dev, "pdn: %lx\n", pdn); + + ClearPageReserved(SMMU_EX_PTBL_PAGE(pdir[pdn])); + __free_page(SMMU_EX_PTBL_PAGE(pdir[pdn])); + pdir[pdn] = _PDE_VACANT(pdn); + FLUSH_CPU_DCACHE(&pdir[pdn], as->pdir_page, sizeof pdir[pdn]); + flush_ptc_and_tlb(as->smmu, as, iova, &pdir[pdn], + as->pdir_page, 1); + } +} + +static void free_pdir(struct smmu_as *as) +{ + unsigned addr; + int count; + struct device *dev = as->smmu->dev; + + if (!as->pdir_page) + return; + + addr = as->smmu->iovmm_base; + count = as->smmu->page_count; + while (count-- > 0) { + free_ptbl(as, addr); + addr += SMMU_PAGE_SIZE * SMMU_PTBL_COUNT; + } + ClearPageReserved(as->pdir_page); + __free_page(as->pdir_page); + as->pdir_page = NULL; + devm_kfree(dev, as->pte_count); + as->pte_count = NULL; +} + +/* + * Maps PTBL for given iova and returns the PTE address + * Caller must unmap the mapped PTBL returned in *ptbl_page_p + */ +static unsigned long *locate_pte(struct smmu_as *as, + dma_addr_t iova, bool allocate, + struct page **ptbl_page_p, + unsigned int **count) +{ + unsigned long ptn = SMMU_ADDR_TO_PFN(iova); + unsigned long pdn = SMMU_ADDR_TO_PDN(iova); + unsigned long *pdir = page_address(as->pdir_page); + unsigned long *ptbl; + + if (pdir[pdn] != _PDE_VACANT(pdn)) { + /* Mapped entry table already exists */ + *ptbl_page_p = SMMU_EX_PTBL_PAGE(pdir[pdn]); + ptbl = page_address(*ptbl_page_p); + } else if (!allocate) { + return NULL; + } else { + int pn; + unsigned long addr = SMMU_PDN_TO_ADDR(pdn); + + /* Vacant - allocate a new page table */ + dev_dbg(as->smmu->dev, "New PTBL pdn: %lx\n", pdn); + + *ptbl_page_p = alloc_page(GFP_ATOMIC); + if (!*ptbl_page_p) { + dev_err(as->smmu->dev, + "failed to allocate smmu_device page table\n"); + return NULL; + } + SetPageReserved(*ptbl_page_p); + ptbl = (unsigned long *)page_address(*ptbl_page_p); + for (pn = 0; pn < SMMU_PTBL_COUNT; + pn++, addr += SMMU_PAGE_SIZE) { + ptbl[pn] = _PTE_VACANT(addr); + } + FLUSH_CPU_DCACHE(ptbl, *ptbl_page_p, SMMU_PTBL_SIZE); + pdir[pdn] = SMMU_MK_PDE(*ptbl_page_p, + as->pde_attr | _PDE_NEXT); + FLUSH_CPU_DCACHE(&pdir[pdn], as->pdir_page, sizeof pdir[pdn]); + flush_ptc_and_tlb(as->smmu, as, iova, &pdir[pdn], + as->pdir_page, 1); + } + *count = &as->pte_count[pdn]; + + return &ptbl[ptn % SMMU_PTBL_COUNT]; +} + +#ifdef CONFIG_SMMU_SIG_DEBUG +static void put_signature(struct smmu_as *as, + dma_addr_t iova, unsigned long pfn) +{ + struct page *page; + unsigned long *vaddr; + + page = pfn_to_page(pfn); + vaddr = page_address(page); + if (!vaddr) + return; + + vaddr[0] = iova; + vaddr[1] = pfn << PAGE_SHIFT; + FLUSH_CPU_DCACHE(vaddr, page, sizeof(vaddr[0]) * 2); +} +#else +static inline void put_signature(struct smmu_as *as, + unsigned long addr, unsigned long pfn) +{ +} +#endif + +/* + * Caller must lock/unlock as + */ +static int alloc_pdir(struct smmu_as *as) +{ + unsigned long *pdir; + int pdn; + u32 val; + struct smmu_device *smmu = as->smmu; + + if (as->pdir_page) + return 0; + + as->pte_count = devm_kzalloc(smmu->dev, + sizeof(as->pte_count[0]) * SMMU_PDIR_COUNT, GFP_KERNEL); + if (!as->pte_count) { + dev_err(smmu->dev, + "failed to allocate smmu_device PTE cunters\n"); + return -ENOMEM; + } + as->pdir_page = alloc_page(GFP_KERNEL | __GFP_DMA); + if (!as->pdir_page) { + dev_err(smmu->dev, + "failed to allocate smmu_device page directory\n"); + devm_kfree(smmu->dev, as->pte_count); + as->pte_count = NULL; + return -ENOMEM; + } + SetPageReserved(as->pdir_page); + pdir = page_address(as->pdir_page); + + for (pdn = 0; pdn < SMMU_PDIR_COUNT; pdn++) + pdir[pdn] = _PDE_VACANT(pdn); + FLUSH_CPU_DCACHE(pdir, as->pdir_page, SMMU_PDIR_SIZE); + val = SMMU_PTC_FLUSH_TYPE_ADR | VA_PAGE_TO_PA(pdir, as->pdir_page); + smmu_write(smmu, val, SMMU_PTC_FLUSH); + FLUSH_SMMU_REGS(as->smmu); + val = SMMU_TLB_FLUSH_VA_MATCH_ALL | + SMMU_TLB_FLUSH_ASID_MATCH__ENABLE | + (as->asid << SMMU_TLB_FLUSH_ASID_SHIFT); + smmu_write(smmu, val, SMMU_TLB_FLUSH); + FLUSH_SMMU_REGS(as->smmu); + + return 0; +} + +static void __smmu_iommu_unmap(struct smmu_as *as, dma_addr_t iova) +{ + unsigned long *pte; + struct page *page; + unsigned int *count; + + pte = locate_pte(as, iova, false, &page, &count); + if (WARN_ON(!pte)) + return; + + if (WARN_ON(*pte == _PTE_VACANT(iova))) + return; + + *pte = _PTE_VACANT(iova); + FLUSH_CPU_DCACHE(pte, page, sizeof(*pte)); + flush_ptc_and_tlb(as->smmu, as, iova, pte, page, 0); + if (!--(*count)) { + free_ptbl(as, iova); + smmu_flush_regs(as->smmu, 0); + } +} + +static void __smmu_iommu_map_pfn(struct smmu_as *as, dma_addr_t iova, + unsigned long pfn) +{ + struct smmu_device *smmu = as->smmu; + unsigned long *pte; + unsigned int *count; + struct page *page; + + pte = locate_pte(as, iova, true, &page, &count); + if (WARN_ON(!pte)) + return; + + if (*pte == _PTE_VACANT(iova)) + (*count)++; + *pte = SMMU_PFN_TO_PTE(pfn, as->pte_attr); + if (unlikely((*pte == _PTE_VACANT(iova)))) + (*count)--; + FLUSH_CPU_DCACHE(pte, page, sizeof(*pte)); + flush_ptc_and_tlb(smmu, as, iova, pte, page, 0); + put_signature(as, iova, pfn); +} + +static int smmu_iommu_map(struct iommu_domain *domain, unsigned long iova, + phys_addr_t pa, size_t bytes, int prot) +{ + struct smmu_as *as = domain->priv; + unsigned long pfn = __phys_to_pfn(pa); + unsigned long flags; + + dev_dbg(as->smmu->dev, "[%d] %08lx:%08x\n", as->asid, iova, pa); + + if (!pfn_valid(pfn)) + return -ENOMEM; + + spin_lock_irqsave(&as->lock, flags); + __smmu_iommu_map_pfn(as, iova, pfn); + spin_unlock_irqrestore(&as->lock, flags); + return 0; +} + +static size_t smmu_iommu_unmap(struct iommu_domain *domain, unsigned long iova, + size_t bytes) +{ + struct smmu_as *as = domain->priv; + unsigned long flags; + + dev_dbg(as->smmu->dev, "[%d] %08lx\n", as->asid, iova); + + spin_lock_irqsave(&as->lock, flags); + __smmu_iommu_unmap(as, iova); + spin_unlock_irqrestore(&as->lock, flags); + return SMMU_PAGE_SIZE; +} + +static phys_addr_t smmu_iommu_iova_to_phys(struct iommu_domain *domain, + unsigned long iova) +{ + struct smmu_as *as = domain->priv; + unsigned long *pte; + unsigned int *count; + struct page *page; + unsigned long pfn; + unsigned long flags; + + spin_lock_irqsave(&as->lock, flags); + + pte = locate_pte(as, iova, true, &page, &count); + pfn = *pte & SMMU_PFN_MASK; + WARN_ON(!pfn_valid(pfn)); + dev_dbg(as->smmu->dev, + "iova:%08lx pfn:%08lx asid:%d\n", iova, pfn, as->asid); + + spin_unlock_irqrestore(&as->lock, flags); + return PFN_PHYS(pfn); +} + +static int smmu_iommu_domain_has_cap(struct iommu_domain *domain, + unsigned long cap) +{ + return 0; +} + +static int smmu_iommu_attach_dev(struct iommu_domain *domain, + struct device *dev) +{ + struct smmu_as *as = domain->priv; + struct smmu_device *smmu = as->smmu; + struct smmu_client *client, *c; + u32 map; + int err; + + client = devm_kzalloc(smmu->dev, sizeof(*c), GFP_KERNEL); + if (!client) + return -ENOMEM; + client->dev = dev; + client->as = as; + map = (unsigned long)dev->platform_data; + if (!map) + return -EINVAL; + + err = smmu_client_enable_hwgrp(client, map); + if (err) + goto err_hwgrp; + + spin_lock(&as->client_lock); + list_for_each_entry(c, &as->client, list) { + if (c->dev == dev) { + dev_err(smmu->dev, + "%s is already attached\n", dev_name(c->dev)); + err = -EINVAL; + goto err_client; + } + } + list_add(&client->list, &as->client); + spin_unlock(&as->client_lock); + + /* + * Reserve "page zero" for AVP vectors using a common dummy + * page. + */ + if (map & HWG_AVPC) { + struct page *page; + + page = as->smmu->avp_vector_page; + __smmu_iommu_map_pfn(as, 0, page_to_pfn(page)); + + pr_info("Reserve \"page zero\" for AVP vectors using a common dummy\n"); + } + + dev_dbg(smmu->dev, "%s is attached\n", dev_name(c->dev)); + return 0; + +err_client: + smmu_client_disable_hwgrp(client); + spin_unlock(&as->client_lock); +err_hwgrp: + devm_kfree(smmu->dev, client); + return err; +} + +static void smmu_iommu_detach_dev(struct iommu_domain *domain, + struct device *dev) +{ + struct smmu_as *as = domain->priv; + struct smmu_device *smmu = as->smmu; + struct smmu_client *c; + + spin_lock(&as->client_lock); + + list_for_each_entry(c, &as->client, list) { + if (c->dev == dev) { + smmu_client_disable_hwgrp(c); + list_del(&c->list); + devm_kfree(smmu->dev, c); + c->as = NULL; + dev_dbg(smmu->dev, + "%s is detached\n", dev_name(c->dev)); + goto out; + } + } + dev_err(smmu->dev, "Couldn't find %s\n", dev_name(c->dev)); +out: + spin_unlock(&as->client_lock); +} + +static int smmu_iommu_domain_init(struct iommu_domain *domain) +{ + int i; + unsigned long flags; + struct smmu_as *as; + struct smmu_device *smmu = smmu_handle; + + /* Look for a free AS with lock held */ + for (i = 0; i < smmu->num_as; i++) { + struct smmu_as *tmp = &smmu->as[i]; + + spin_lock_irqsave(&tmp->lock, flags); + if (!tmp->pdir_page) { + as = tmp; + goto found; + } + spin_unlock_irqrestore(&tmp->lock, flags); + } + dev_err(smmu->dev, "no free AS\n"); + return -ENODEV; + +found: + if (alloc_pdir(as) < 0) + goto err_alloc_pdir; + + spin_lock(&smmu->lock); + + /* Update PDIR register */ + smmu_write(smmu, SMMU_PTB_ASID_CUR(as->asid), SMMU_PTB_ASID); + smmu_write(smmu, + SMMU_MK_PDIR(as->pdir_page, as->pdir_attr), SMMU_PTB_DATA); + FLUSH_SMMU_REGS(smmu); + + spin_unlock(&smmu->lock); + + spin_unlock_irqrestore(&as->lock, flags); + domain->priv = as; + + dev_dbg(smmu->dev, "smmu_as@%p\n", as); + return 0; + +err_alloc_pdir: + spin_unlock_irqrestore(&as->lock, flags); + return -ENODEV; +} + +static void smmu_iommu_domain_destroy(struct iommu_domain *domain) +{ + struct smmu_as *as = domain->priv; + struct smmu_device *smmu = as->smmu; + unsigned long flags; + + spin_lock_irqsave(&as->lock, flags); + + if (as->pdir_page) { + spin_lock(&smmu->lock); + smmu_write(smmu, SMMU_PTB_ASID_CUR(as->asid), SMMU_PTB_ASID); + smmu_write(smmu, SMMU_PTB_DATA_RESET_VAL, SMMU_PTB_DATA); + FLUSH_SMMU_REGS(smmu); + spin_unlock(&smmu->lock); + + free_pdir(as); + } + + if (!list_empty(&as->client)) { + struct smmu_client *c; + + list_for_each_entry(c, &as->client, list) + smmu_iommu_detach_dev(domain, c->dev); + } + + spin_unlock_irqrestore(&as->lock, flags); + + domain->priv = NULL; + dev_dbg(smmu->dev, "smmu_as@%p\n", as); +} + +static struct iommu_ops smmu_iommu_ops = { + .domain_init = smmu_iommu_domain_init, + .domain_destroy = smmu_iommu_domain_destroy, + .attach_dev = smmu_iommu_attach_dev, + .detach_dev = smmu_iommu_detach_dev, + .map = smmu_iommu_map, + .unmap = smmu_iommu_unmap, + .iova_to_phys = smmu_iommu_iova_to_phys, + .domain_has_cap = smmu_iommu_domain_has_cap, + .pgsize_bitmap = SMMU_IOMMU_PGSIZES, +}; + +static int tegra_smmu_suspend(struct device *dev) +{ + struct smmu_device *smmu = dev_get_drvdata(dev); + + smmu->translation_enable_0 = smmu_read(smmu, SMMU_TRANSLATION_ENABLE_0); + smmu->translation_enable_1 = smmu_read(smmu, SMMU_TRANSLATION_ENABLE_1); + smmu->translation_enable_2 = smmu_read(smmu, SMMU_TRANSLATION_ENABLE_2); + smmu->asid_security = smmu_read(smmu, SMMU_ASID_SECURITY); + return 0; +} + +static int tegra_smmu_resume(struct device *dev) +{ + struct smmu_device *smmu = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&smmu->lock, flags); + smmu_setup_regs(smmu); + spin_unlock_irqrestore(&smmu->lock, flags); + return 0; +} + +static int tegra_smmu_probe(struct platform_device *pdev) +{ + struct smmu_device *smmu; + struct resource *regs, *regs2, *window; + struct device *dev = &pdev->dev; + int i, err = 0; + + if (smmu_handle) + return -EIO; + + BUILD_BUG_ON(PAGE_SHIFT != SMMU_PAGE_SHIFT); + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + window = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (!regs || !regs2 || !window) { + dev_err(dev, "No SMMU resources\n"); + return -ENODEV; + } + + smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL); + if (!smmu) { + dev_err(dev, "failed to allocate smmu_device\n"); + return -ENOMEM; + } + + smmu->dev = dev; + smmu->num_as = SMMU_NUM_ASIDS; + smmu->iovmm_base = (unsigned long)window->start; + smmu->page_count = resource_size(window) >> SMMU_PAGE_SHIFT; + smmu->regs = devm_ioremap(dev, regs->start, resource_size(regs)); + smmu->regs_ahbarb = devm_ioremap(dev, regs2->start, + resource_size(regs2)); + if (!smmu->regs || !smmu->regs_ahbarb) { + dev_err(dev, "failed to remap SMMU registers\n"); + err = -ENXIO; + goto fail; + } + + smmu->translation_enable_0 = ~0; + smmu->translation_enable_1 = ~0; + smmu->translation_enable_2 = ~0; + smmu->asid_security = 0; + + smmu->as = devm_kzalloc(dev, + sizeof(smmu->as[0]) * smmu->num_as, GFP_KERNEL); + if (!smmu->as) { + dev_err(dev, "failed to allocate smmu_as\n"); + err = -ENOMEM; + goto fail; + } + + for (i = 0; i < smmu->num_as; i++) { + struct smmu_as *as = &smmu->as[i]; + + as->smmu = smmu; + as->asid = i; + as->pdir_attr = _PDIR_ATTR; + as->pde_attr = _PDE_ATTR; + as->pte_attr = _PTE_ATTR; + + spin_lock_init(&as->lock); + INIT_LIST_HEAD(&as->client); + } + spin_lock_init(&smmu->lock); + smmu_setup_regs(smmu); + platform_set_drvdata(pdev, smmu); + + smmu->avp_vector_page = alloc_page(GFP_KERNEL); + if (!smmu->avp_vector_page) + goto fail; + + smmu_handle = smmu; + return 0; + +fail: + if (smmu->avp_vector_page) + __free_page(smmu->avp_vector_page); + if (smmu->regs) + devm_iounmap(dev, smmu->regs); + if (smmu->regs_ahbarb) + devm_iounmap(dev, smmu->regs_ahbarb); + if (smmu && smmu->as) { + for (i = 0; i < smmu->num_as; i++) { + if (smmu->as[i].pdir_page) { + ClearPageReserved(smmu->as[i].pdir_page); + __free_page(smmu->as[i].pdir_page); + } + } + devm_kfree(dev, smmu->as); + } + devm_kfree(dev, smmu); + return err; +} + +static int tegra_smmu_remove(struct platform_device *pdev) +{ + struct smmu_device *smmu = platform_get_drvdata(pdev); + struct device *dev = smmu->dev; + + smmu_write(smmu, SMMU_CONFIG_DISABLE, SMMU_CONFIG); + platform_set_drvdata(pdev, NULL); + if (smmu->as) { + int i; + + for (i = 0; i < smmu->num_as; i++) + free_pdir(&smmu->as[i]); + devm_kfree(dev, smmu->as); + } + if (smmu->avp_vector_page) + __free_page(smmu->avp_vector_page); + if (smmu->regs) + devm_iounmap(dev, smmu->regs); + if (smmu->regs_ahbarb) + devm_iounmap(dev, smmu->regs_ahbarb); + devm_kfree(dev, smmu); + smmu_handle = NULL; + return 0; +} + +const struct dev_pm_ops tegra_smmu_pm_ops = { + .suspend = tegra_smmu_suspend, + .resume = tegra_smmu_resume, +}; + +static struct platform_driver tegra_smmu_driver = { + .probe = tegra_smmu_probe, + .remove = tegra_smmu_remove, + .driver = { + .owner = THIS_MODULE, + .name = "tegra-smmu", + .pm = &tegra_smmu_pm_ops, + }, +}; + +static int __devinit tegra_smmu_init(void) +{ + bus_set_iommu(&platform_bus_type, &smmu_iommu_ops); + return platform_driver_register(&tegra_smmu_driver); +} + +static void __exit tegra_smmu_exit(void) +{ + platform_driver_unregister(&tegra_smmu_driver); +} + +subsys_initcall(tegra_smmu_init); +module_exit(tegra_smmu_exit); + +MODULE_DESCRIPTION("IOMMU API for SMMU in Tegra30"); +MODULE_AUTHOR("Hiroshi DOYU <hdoyu@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 8c7a75d53101..ff4b8cfda585 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -17,7 +17,7 @@ menuconfig NEW_LEDS if NEW_LEDS config LEDS_CLASS - bool "LED Class Support" + tristate "LED Class Support" help This option enables the led sysfs class in /sys/class/leds. You'll need this to do anything useful with LEDs. If unsure, say N. @@ -69,18 +69,11 @@ config LEDS_MIKROTIK_RB532 config LEDS_S3C24XX tristate "LED Support for Samsung S3C24XX GPIO LEDs" depends on LEDS_CLASS - depends on ARCH_S3C2410 + depends on ARCH_S3C24XX help This option enables support for LEDs connected to GPIO lines on Samsung S3C24XX series CPUs, such as the S3C2410 and S3C2440. -config LEDS_AMS_DELTA - tristate "LED Support for the Amstrad Delta (E3)" - depends on LEDS_CLASS - depends on MACH_AMS_DELTA - help - This option enables support for the LEDs on Amstrad Delta (E3). - config LEDS_NET48XX tristate "LED Support for Soekris net48xx series Error LED" depends on LEDS_CLASS @@ -234,6 +227,14 @@ config LEDS_PCA955X LED driver chips accessed via the I2C bus. Supported devices include PCA9550, PCA9551, PCA9552, and PCA9553. +config LEDS_PCA9633 + tristate "LED support for PCA9633 I2C chip" + depends on LEDS_CLASS + depends on I2C + help + This option enables support for LEDs connected to the PCA9633 + LED driver chip accessed via the I2C bus. + config LEDS_WM831X_STATUS tristate "LED support for status LEDs on WM831x PMICs" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 6bcf4f695515..890481cb09f6 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -12,7 +12,6 @@ obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o -obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o @@ -30,6 +29,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_OT200) += leds-ot200.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o +obj-$(CONFIG_LEDS_PCA9633) += leds-pca9633.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 0c8739c448b1..5bff8439dc68 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -110,50 +110,6 @@ static void led_timer_function(unsigned long data) mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); } -static void led_stop_software_blink(struct led_classdev *led_cdev) -{ - /* deactivate previous settings */ - del_timer_sync(&led_cdev->blink_timer); - led_cdev->blink_delay_on = 0; - led_cdev->blink_delay_off = 0; -} - -static void led_set_software_blink(struct led_classdev *led_cdev, - unsigned long delay_on, - unsigned long delay_off) -{ - int current_brightness; - - current_brightness = led_get_brightness(led_cdev); - if (current_brightness) - led_cdev->blink_brightness = current_brightness; - if (!led_cdev->blink_brightness) - led_cdev->blink_brightness = led_cdev->max_brightness; - - if (led_get_trigger_data(led_cdev) && - delay_on == led_cdev->blink_delay_on && - delay_off == led_cdev->blink_delay_off) - return; - - led_stop_software_blink(led_cdev); - - led_cdev->blink_delay_on = delay_on; - led_cdev->blink_delay_off = delay_off; - - /* never on - don't blink */ - if (!delay_on) - return; - - /* never off - just set to brightness */ - if (!delay_off) { - led_set_brightness(led_cdev, led_cdev->blink_brightness); - return; - } - - mod_timer(&led_cdev->blink_timer, jiffies + 1); -} - - /** * led_classdev_suspend - suspend an led_classdev. * @led_cdev: the led_classdev to suspend. @@ -262,32 +218,6 @@ void led_classdev_unregister(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_classdev_unregister); -void led_blink_set(struct led_classdev *led_cdev, - unsigned long *delay_on, - unsigned long *delay_off) -{ - del_timer_sync(&led_cdev->blink_timer); - - if (led_cdev->blink_set && - !led_cdev->blink_set(led_cdev, delay_on, delay_off)) - return; - - /* blink with 1 Hz as default if nothing specified */ - if (!*delay_on && !*delay_off) - *delay_on = *delay_off = 500; - - led_set_software_blink(led_cdev, *delay_on, *delay_off); -} -EXPORT_SYMBOL(led_blink_set); - -void led_brightness_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - led_stop_software_blink(led_cdev); - led_cdev->brightness_set(led_cdev, brightness); -} -EXPORT_SYMBOL(led_brightness_set); - static int __init leds_init(void) { leds_class = class_create(THIS_MODULE, "leds"); diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 016d19f5486f..d6860043f6f9 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -23,3 +23,73 @@ EXPORT_SYMBOL_GPL(leds_list_lock); LIST_HEAD(leds_list); EXPORT_SYMBOL_GPL(leds_list); + +static void led_stop_software_blink(struct led_classdev *led_cdev) +{ + /* deactivate previous settings */ + del_timer_sync(&led_cdev->blink_timer); + led_cdev->blink_delay_on = 0; + led_cdev->blink_delay_off = 0; +} + +static void led_set_software_blink(struct led_classdev *led_cdev, + unsigned long delay_on, + unsigned long delay_off) +{ + int current_brightness; + + current_brightness = led_get_brightness(led_cdev); + if (current_brightness) + led_cdev->blink_brightness = current_brightness; + if (!led_cdev->blink_brightness) + led_cdev->blink_brightness = led_cdev->max_brightness; + + if (led_get_trigger_data(led_cdev) && + delay_on == led_cdev->blink_delay_on && + delay_off == led_cdev->blink_delay_off) + return; + + led_stop_software_blink(led_cdev); + + led_cdev->blink_delay_on = delay_on; + led_cdev->blink_delay_off = delay_off; + + /* never on - don't blink */ + if (!delay_on) + return; + + /* never off - just set to brightness */ + if (!delay_off) { + led_set_brightness(led_cdev, led_cdev->blink_brightness); + return; + } + + mod_timer(&led_cdev->blink_timer, jiffies + 1); +} + + +void led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + del_timer_sync(&led_cdev->blink_timer); + + if (led_cdev->blink_set && + !led_cdev->blink_set(led_cdev, delay_on, delay_off)) + return; + + /* blink with 1 Hz as default if nothing specified */ + if (!*delay_on && !*delay_off) + *delay_on = *delay_off = 500; + + led_set_software_blink(led_cdev, *delay_on, *delay_off); +} +EXPORT_SYMBOL(led_blink_set); + +void led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + led_stop_software_blink(led_cdev); + led_cdev->brightness_set(led_cdev, brightness); +} +EXPORT_SYMBOL(led_brightness_set); diff --git a/drivers/leds/leds-ams-delta.c b/drivers/leds/leds-ams-delta.c deleted file mode 100644 index 07428357c83f..000000000000 --- a/drivers/leds/leds-ams-delta.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * LEDs driver for Amstrad Delta (E3) - * - * Copyright (C) 2006 Jonathan McDowell <noodles@earth.li> - * - * 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/kernel.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/leds.h> -#include <plat/board-ams-delta.h> - -/* - * Our context - */ -struct ams_delta_led { - struct led_classdev cdev; - u8 bitmask; -}; - -static void ams_delta_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct ams_delta_led *led_dev = - container_of(led_cdev, struct ams_delta_led, cdev); - - if (value) - ams_delta_latch1_write(led_dev->bitmask, led_dev->bitmask); - else - ams_delta_latch1_write(led_dev->bitmask, 0); -} - -static struct ams_delta_led ams_delta_leds[] = { - { - .cdev = { - .name = "ams-delta::camera", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_CAMERA, - }, - { - .cdev = { - .name = "ams-delta::advert", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_ADVERT, - }, - { - .cdev = { - .name = "ams-delta::email", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_EMAIL, - }, - { - .cdev = { - .name = "ams-delta::handsfree", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_HANDSFREE, - }, - { - .cdev = { - .name = "ams-delta::voicemail", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_VOICEMAIL, - }, - { - .cdev = { - .name = "ams-delta::voice", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_VOICE, - }, -}; - -static int ams_delta_led_probe(struct platform_device *pdev) -{ - int i, ret; - - for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) { - ams_delta_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME; - ret = led_classdev_register(&pdev->dev, - &ams_delta_leds[i].cdev); - if (ret < 0) - goto fail; - } - - return 0; -fail: - while (--i >= 0) - led_classdev_unregister(&ams_delta_leds[i].cdev); - return ret; -} - -static int ams_delta_led_remove(struct platform_device *pdev) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) - led_classdev_unregister(&ams_delta_leds[i].cdev); - - return 0; -} - -static struct platform_driver ams_delta_led_driver = { - .probe = ams_delta_led_probe, - .remove = ams_delta_led_remove, - .driver = { - .name = "ams-delta-led", - .owner = THIS_MODULE, - }, -}; - -module_platform_driver(ams_delta_led_driver); - -MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>"); -MODULE_DESCRIPTION("Amstrad Delta LED driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:ams-delta-led"); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 7df74cb97e70..f4c470a3bc8d 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -13,6 +13,7 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> +#include <linux/gpio.h> #include <linux/leds.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> @@ -20,8 +21,6 @@ #include <linux/workqueue.h> #include <linux/module.h> -#include <asm/gpio.h> - struct gpio_led_data { struct led_classdev cdev; unsigned gpio; diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index e59c166a0ce2..968fd5fef4fc 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -26,7 +26,6 @@ #define LM3530_GEN_CONFIG 0x10 #define LM3530_ALS_CONFIG 0x20 #define LM3530_BRT_RAMP_RATE 0x30 -#define LM3530_ALS_ZONE_REG 0x40 #define LM3530_ALS_IMP_SELECT 0x41 #define LM3530_BRT_CTRL_REG 0xA0 #define LM3530_ALS_ZB0_REG 0x60 @@ -38,7 +37,7 @@ #define LM3530_ALS_Z2T_REG 0x72 #define LM3530_ALS_Z3T_REG 0x73 #define LM3530_ALS_Z4T_REG 0x74 -#define LM3530_REG_MAX 15 +#define LM3530_REG_MAX 14 /* General Control Register */ #define LM3530_EN_I2C_SHIFT (0) @@ -80,6 +79,9 @@ #define LM3530_DEF_ZT_3 (0x33) #define LM3530_DEF_ZT_4 (0x19) +/* 7 bits are used for the brightness : LM3530_BRT_CTRL_REG */ +#define MAX_BRIGHTNESS (127) + struct lm3530_mode_map { const char *mode; enum lm3530_mode mode_val; @@ -115,7 +117,6 @@ static const u8 lm3530_reg[LM3530_REG_MAX] = { LM3530_GEN_CONFIG, LM3530_ALS_CONFIG, LM3530_BRT_RAMP_RATE, - LM3530_ALS_ZONE_REG, LM3530_ALS_IMP_SELECT, LM3530_BRT_CTRL_REG, LM3530_ALS_ZB0_REG, @@ -152,27 +153,35 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) u8 reg_val[LM3530_REG_MAX]; u8 zones[LM3530_ALS_ZB_MAX]; u32 als_vmin, als_vmax, als_vstep; - struct lm3530_platform_data *pltfm = drvdata->pdata; + struct lm3530_platform_data *pdata = drvdata->pdata; struct i2c_client *client = drvdata->client; + struct lm3530_pwm_data *pwm = &pdata->pwm_data; - gen_config = (pltfm->brt_ramp_law << LM3530_RAMP_LAW_SHIFT) | - ((pltfm->max_current & 7) << LM3530_MAX_CURR_SHIFT); + gen_config = (pdata->brt_ramp_law << LM3530_RAMP_LAW_SHIFT) | + ((pdata->max_current & 7) << LM3530_MAX_CURR_SHIFT); - if (drvdata->mode == LM3530_BL_MODE_MANUAL || - drvdata->mode == LM3530_BL_MODE_ALS) - gen_config |= (LM3530_ENABLE_I2C); + switch (drvdata->mode) { + case LM3530_BL_MODE_MANUAL: + case LM3530_BL_MODE_ALS: + gen_config |= LM3530_ENABLE_I2C; + break; + case LM3530_BL_MODE_PWM: + gen_config |= LM3530_ENABLE_PWM | LM3530_ENABLE_PWM_SIMPLE | + (pdata->pwm_pol_hi << LM3530_PWM_POL_SHIFT); + break; + } if (drvdata->mode == LM3530_BL_MODE_ALS) { - if (pltfm->als_vmax == 0) { - pltfm->als_vmin = 0; - pltfm->als_vmax = LM3530_ALS_WINDOW_mV; + if (pdata->als_vmax == 0) { + pdata->als_vmin = 0; + pdata->als_vmax = LM3530_ALS_WINDOW_mV; } - als_vmin = pltfm->als_vmin; - als_vmax = pltfm->als_vmax; + als_vmin = pdata->als_vmin; + als_vmax = pdata->als_vmax; if ((als_vmax - als_vmin) > LM3530_ALS_WINDOW_mV) - pltfm->als_vmax = als_vmax = + pdata->als_vmax = als_vmax = als_vmin + LM3530_ALS_WINDOW_mV; /* n zone boundary makes n+1 zones */ @@ -184,44 +193,41 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) / 1000; als_config = - (pltfm->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) | + (pdata->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) | (LM3530_ENABLE_ALS) | - (pltfm->als_input_mode << LM3530_ALS_SEL_SHIFT); + (pdata->als_input_mode << LM3530_ALS_SEL_SHIFT); als_imp_sel = - (pltfm->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) | - (pltfm->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); + (pdata->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) | + (pdata->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); } - if (drvdata->mode == LM3530_BL_MODE_PWM) - gen_config |= (LM3530_ENABLE_PWM) | - (pltfm->pwm_pol_hi << LM3530_PWM_POL_SHIFT) | - (LM3530_ENABLE_PWM_SIMPLE); - - brt_ramp = (pltfm->brt_ramp_fall << LM3530_BRT_RAMP_FALL_SHIFT) | - (pltfm->brt_ramp_rise << LM3530_BRT_RAMP_RISE_SHIFT); + brt_ramp = (pdata->brt_ramp_fall << LM3530_BRT_RAMP_FALL_SHIFT) | + (pdata->brt_ramp_rise << LM3530_BRT_RAMP_RISE_SHIFT); if (drvdata->brightness) brightness = drvdata->brightness; else - brightness = drvdata->brightness = pltfm->brt_val; + brightness = drvdata->brightness = pdata->brt_val; + + if (brightness > drvdata->led_dev.max_brightness) + brightness = drvdata->led_dev.max_brightness; reg_val[0] = gen_config; /* LM3530_GEN_CONFIG */ reg_val[1] = als_config; /* LM3530_ALS_CONFIG */ reg_val[2] = brt_ramp; /* LM3530_BRT_RAMP_RATE */ - reg_val[3] = 0x00; /* LM3530_ALS_ZONE_REG */ - reg_val[4] = als_imp_sel; /* LM3530_ALS_IMP_SELECT */ - reg_val[5] = brightness; /* LM3530_BRT_CTRL_REG */ - reg_val[6] = zones[0]; /* LM3530_ALS_ZB0_REG */ - reg_val[7] = zones[1]; /* LM3530_ALS_ZB1_REG */ - reg_val[8] = zones[2]; /* LM3530_ALS_ZB2_REG */ - reg_val[9] = zones[3]; /* LM3530_ALS_ZB3_REG */ - reg_val[10] = LM3530_DEF_ZT_0; /* LM3530_ALS_Z0T_REG */ - reg_val[11] = LM3530_DEF_ZT_1; /* LM3530_ALS_Z1T_REG */ - reg_val[12] = LM3530_DEF_ZT_2; /* LM3530_ALS_Z2T_REG */ - reg_val[13] = LM3530_DEF_ZT_3; /* LM3530_ALS_Z3T_REG */ - reg_val[14] = LM3530_DEF_ZT_4; /* LM3530_ALS_Z4T_REG */ + reg_val[3] = als_imp_sel; /* LM3530_ALS_IMP_SELECT */ + reg_val[4] = brightness; /* LM3530_BRT_CTRL_REG */ + reg_val[5] = zones[0]; /* LM3530_ALS_ZB0_REG */ + reg_val[6] = zones[1]; /* LM3530_ALS_ZB1_REG */ + reg_val[7] = zones[2]; /* LM3530_ALS_ZB2_REG */ + reg_val[8] = zones[3]; /* LM3530_ALS_ZB3_REG */ + reg_val[9] = LM3530_DEF_ZT_0; /* LM3530_ALS_Z0T_REG */ + reg_val[10] = LM3530_DEF_ZT_1; /* LM3530_ALS_Z1T_REG */ + reg_val[11] = LM3530_DEF_ZT_2; /* LM3530_ALS_Z2T_REG */ + reg_val[12] = LM3530_DEF_ZT_3; /* LM3530_ALS_Z3T_REG */ + reg_val[13] = LM3530_DEF_ZT_4; /* LM3530_ALS_Z4T_REG */ if (!drvdata->enable) { ret = regulator_enable(drvdata->regulator); @@ -234,6 +240,15 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) } for (i = 0; i < LM3530_REG_MAX; i++) { + /* do not update brightness register when pwm mode */ + if (lm3530_reg[i] == LM3530_BRT_CTRL_REG && + drvdata->mode == LM3530_BL_MODE_PWM) { + if (pwm->pwm_set_intensity) + pwm->pwm_set_intensity(reg_val[i], + drvdata->led_dev.max_brightness); + continue; + } + ret = i2c_smbus_write_byte_data(client, lm3530_reg[i], reg_val[i]); if (ret) @@ -249,6 +264,9 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, int err; struct lm3530_data *drvdata = container_of(led_cdev, struct lm3530_data, led_dev); + struct lm3530_platform_data *pdata = drvdata->pdata; + struct lm3530_pwm_data *pwm = &pdata->pwm_data; + u8 max_brightness = led_cdev->max_brightness; switch (drvdata->mode) { case LM3530_BL_MODE_MANUAL: @@ -264,12 +282,12 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, /* set the brightness in brightness control register*/ err = i2c_smbus_write_byte_data(drvdata->client, - LM3530_BRT_CTRL_REG, brt_val / 2); + LM3530_BRT_CTRL_REG, brt_val); if (err) dev_err(&drvdata->client->dev, "Unable to set brightness: %d\n", err); else - drvdata->brightness = brt_val / 2; + drvdata->brightness = brt_val; if (brt_val == 0) { err = regulator_disable(drvdata->regulator); @@ -282,6 +300,8 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, case LM3530_BL_MODE_ALS: break; case LM3530_BL_MODE_PWM: + if (pwm->pwm_set_intensity) + pwm->pwm_set_intensity(brt_val, max_brightness); break; default: break; @@ -291,11 +311,11 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, static ssize_t lm3530_mode_get(struct device *dev, struct device_attribute *attr, char *buf) { - struct i2c_client *client = container_of( - dev->parent, struct i2c_client, dev); - struct lm3530_data *drvdata = i2c_get_clientdata(client); + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3530_data *drvdata; int i, len = 0; + drvdata = container_of(led_cdev, struct lm3530_data, led_dev); for (i = 0; i < ARRAY_SIZE(mode_map); i++) if (drvdata->mode == mode_map[i].mode_val) len += sprintf(buf + len, "[%s] ", mode_map[i].mode); @@ -310,26 +330,26 @@ static ssize_t lm3530_mode_get(struct device *dev, static ssize_t lm3530_mode_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { - int err; - struct i2c_client *client = container_of( - dev->parent, struct i2c_client, dev); - struct lm3530_data *drvdata = i2c_get_clientdata(client); - int mode; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3530_data *drvdata; + struct lm3530_pwm_data *pwm; + u8 max_brightness; + int mode, err; + drvdata = container_of(led_cdev, struct lm3530_data, led_dev); + pwm = &drvdata->pdata->pwm_data; + max_brightness = led_cdev->max_brightness; mode = lm3530_get_mode_from_str(buf); if (mode < 0) { dev_err(dev, "Invalid mode\n"); return -EINVAL; } - if (mode == LM3530_BL_MODE_MANUAL) - drvdata->mode = LM3530_BL_MODE_MANUAL; - else if (mode == LM3530_BL_MODE_ALS) - drvdata->mode = LM3530_BL_MODE_ALS; - else if (mode == LM3530_BL_MODE_PWM) { - dev_err(dev, "PWM mode not supported\n"); - return -EINVAL; - } + drvdata->mode = mode; + + /* set pwm to low if unnecessary */ + if (mode != LM3530_BL_MODE_PWM && pwm->pwm_set_intensity) + pwm->pwm_set_intensity(0, max_brightness); err = lm3530_init_registers(drvdata); if (err) { @@ -380,6 +400,7 @@ static int __devinit lm3530_probe(struct i2c_client *client, drvdata->enable = false; drvdata->led_dev.name = LM3530_LED_DEV; drvdata->led_dev.brightness_set = lm3530_brightness_set; + drvdata->led_dev.max_brightness = MAX_BRIGHTNESS; i2c_set_clientdata(client, drvdata); diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index d62a7982a5e6..410a723b8691 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -81,18 +81,10 @@ #define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */ #define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ #define LP5521_EXEC_RUN 0x2A - -/* Bits in CONFIG register */ -#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ -#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ -#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ -#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ -#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ -#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ -#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ -#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ -#define LP5521_CLK_INT 1 /* Internal clock */ -#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ +#define LP5521_ENABLE_DEFAULT \ + (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM) +#define LP5521_ENABLE_RUN_PROGRAM \ + (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN) /* Status */ #define LP5521_EXT_CLK_USED 0x08 @@ -100,6 +92,9 @@ /* default R channel current register value */ #define LP5521_REG_R_CURR_DEFAULT 0xAF +/* Pattern Mode */ +#define PATTERN_OFF 0 + struct lp5521_engine { int id; u8 mode; @@ -241,15 +236,16 @@ static int lp5521_configure(struct i2c_client *client) { struct lp5521_chip *chip = i2c_get_clientdata(client); int ret; + u8 cfg; lp5521_init_engine(chip); /* Set all PWMs to direct control mode */ - ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); + ret = lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); - /* Enable auto-powersave, set charge pump to auto, red to battery */ - ret |= lp5521_write(client, LP5521_REG_CONFIG, - LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + cfg = chip->pdata->update_config ? + : (LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + ret |= lp5521_write(client, LP5521_REG_CONFIG, cfg); /* Initialize all channels PWM to zero -> leds off */ ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); @@ -258,8 +254,7 @@ static int lp5521_configure(struct i2c_client *client) /* Set engines are set to run state when OP_MODE enables engines */ ret |= lp5521_write(client, LP5521_REG_ENABLE, - LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM | - LP5521_EXEC_RUN); + LP5521_ENABLE_RUN_PROGRAM); /* enable takes 500us. 1 - 2 ms leaves some margin */ usleep_range(1000, 2000); @@ -310,8 +305,7 @@ static int lp5521_detect(struct i2c_client *client) int ret; u8 buf; - ret = lp5521_write(client, LP5521_REG_ENABLE, - LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM); + ret = lp5521_write(client, LP5521_REG_ENABLE, LP5521_ENABLE_DEFAULT); if (ret) return ret; /* enable takes 500us. 1 - 2 ms leaves some margin */ @@ -319,7 +313,7 @@ static int lp5521_detect(struct i2c_client *client) ret = lp5521_read(client, LP5521_REG_ENABLE, &buf); if (ret) return ret; - if (buf != (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM)) + if (buf != LP5521_ENABLE_DEFAULT) return -ENODEV; return 0; @@ -504,7 +498,7 @@ static ssize_t store_current(struct device *dev, ssize_t ret; unsigned long curr; - if (strict_strtoul(buf, 0, &curr)) + if (kstrtoul(buf, 0, &curr)) return -EINVAL; if (curr > led->max_current) @@ -536,6 +530,97 @@ static ssize_t lp5521_selftest(struct device *dev, return sprintf(buf, "%s\n", ret ? "FAIL" : "OK"); } +static void lp5521_clear_program_memory(struct i2c_client *cl) +{ + int i; + u8 rgb_mem[] = { + LP5521_REG_R_PROG_MEM, + LP5521_REG_G_PROG_MEM, + LP5521_REG_B_PROG_MEM, + }; + + for (i = 0; i < ARRAY_SIZE(rgb_mem); i++) { + lp5521_write(cl, rgb_mem[i], 0); + lp5521_write(cl, rgb_mem[i] + 1, 0); + } +} + +static void lp5521_write_program_memory(struct i2c_client *cl, + u8 base, u8 *rgb, int size) +{ + int i; + + if (!rgb || size <= 0) + return; + + for (i = 0; i < size; i++) + lp5521_write(cl, base + i, *(rgb + i)); + + lp5521_write(cl, base + i, 0); + lp5521_write(cl, base + i + 1, 0); +} + +static inline struct lp5521_led_pattern *lp5521_get_pattern + (struct lp5521_chip *chip, u8 offset) +{ + struct lp5521_led_pattern *ptn; + ptn = chip->pdata->patterns + (offset - 1); + return ptn; +} + +static void lp5521_run_led_pattern(int mode, struct lp5521_chip *chip) +{ + struct lp5521_led_pattern *ptn; + struct i2c_client *cl = chip->client; + int num_patterns = chip->pdata->num_patterns; + + if (mode > num_patterns || !(chip->pdata->patterns)) + return; + + if (mode == PATTERN_OFF) { + lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_DEFAULT); + usleep_range(1000, 2000); + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + } else { + ptn = lp5521_get_pattern(chip, mode); + if (!ptn) + return; + + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); + usleep_range(1000, 2000); + + lp5521_clear_program_memory(cl); + + lp5521_write_program_memory(cl, LP5521_REG_R_PROG_MEM, + ptn->r, ptn->size_r); + lp5521_write_program_memory(cl, LP5521_REG_G_PROG_MEM, + ptn->g, ptn->size_g); + lp5521_write_program_memory(cl, LP5521_REG_B_PROG_MEM, + ptn->b, ptn->size_b); + + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_RUN); + usleep_range(1000, 2000); + lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM); + } +} + +static ssize_t store_led_pattern(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 16, &val); + if (ret) + return ret; + + lp5521_run_led_pattern(val, chip); + + return len; +} + /* led class device attributes */ static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, show_current, store_current); static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); @@ -561,6 +646,7 @@ static DEVICE_ATTR(engine1_load, S_IWUSR, NULL, store_engine1_load); static DEVICE_ATTR(engine2_load, S_IWUSR, NULL, store_engine2_load); static DEVICE_ATTR(engine3_load, S_IWUSR, NULL, store_engine3_load); static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL); +static DEVICE_ATTR(led_pattern, S_IWUSR, NULL, store_led_pattern); static struct attribute *lp5521_attributes[] = { &dev_attr_engine1_mode.attr, @@ -570,6 +656,7 @@ static struct attribute *lp5521_attributes[] = { &dev_attr_engine1_load.attr, &dev_attr_engine2_load.attr, &dev_attr_engine3_load.attr, + &dev_attr_led_pattern.attr, NULL }; @@ -620,10 +707,15 @@ static int __devinit lp5521_init_led(struct lp5521_led *led, return -EINVAL; } - snprintf(name, sizeof(name), "%s:channel%d", - pdata->label ?: client->name, chan); led->cdev.brightness_set = lp5521_set_brightness; - led->cdev.name = name; + if (pdata->led_config[chan].name) { + led->cdev.name = pdata->led_config[chan].name; + } else { + snprintf(name, sizeof(name), "%s:channel%d", + pdata->label ?: client->name, chan); + led->cdev.name = name; + } + res = led_classdev_register(dev, &led->cdev); if (res < 0) { dev_err(dev, "couldn't register led on channel %d\n", chan); @@ -692,9 +784,9 @@ static int __devinit lp5521_probe(struct i2c_client *client, * otherwise further access to the R G B channels in the * LP5521_REG_ENABLE register will not have any effect - strange! */ - lp5521_read(client, LP5521_REG_R_CURRENT, &buf); + ret = lp5521_read(client, LP5521_REG_R_CURRENT, &buf); if (buf != LP5521_REG_R_CURR_DEFAULT) { - dev_err(&client->dev, "error in reseting chip\n"); + dev_err(&client->dev, "error in resetting chip\n"); goto fail2; } usleep_range(10000, 20000); @@ -767,6 +859,7 @@ static int __devexit lp5521_remove(struct i2c_client *client) struct lp5521_chip *chip = i2c_get_clientdata(client); int i; + lp5521_run_led_pattern(PATTERN_OFF, chip); lp5521_unregister_sysfs(client); for (i = 0; i < chip->num_leds; i++) { diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 73e791ae7259..857a3e15f2dd 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -152,7 +152,7 @@ static inline struct lp5523_chip *led_to_lp5523(struct lp5523_led *led) static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode); static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode); -static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern); +static int lp5523_load_program(struct lp5523_engine *engine, const u8 *pattern); static void lp5523_led_brightness_work(struct work_struct *work); @@ -196,7 +196,7 @@ static int lp5523_configure(struct i2c_client *client) u8 status; /* one pattern per engine setting led mux start and stop addresses */ - u8 pattern[][LP5523_PROGRAM_LENGTH] = { + static const u8 pattern[][LP5523_PROGRAM_LENGTH] = { { 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0}, { 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0}, { 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0}, @@ -301,7 +301,7 @@ static int lp5523_load_mux(struct lp5523_engine *engine, u16 mux) return ret; } -static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern) +static int lp5523_load_program(struct lp5523_engine *engine, const u8 *pattern) { struct lp5523_chip *chip = engine_to_lp5523(engine); struct i2c_client *client = chip->client; diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c new file mode 100644 index 000000000000..d8926fd031aa --- /dev/null +++ b/drivers/leds/leds-pca9633.c @@ -0,0 +1,193 @@ +/* + * Copyright 2011 bct electronic GmbH + * + * Author: Peter Meerwald <p.meerwald@bct-electronic.com> + * + * Based on leds-pca955x.c + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) + * + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +/* LED select registers determine the source that drives LED outputs */ +#define PCA9633_LED_OFF 0x0 /* LED driver off */ +#define PCA9633_LED_ON 0x1 /* LED driver on */ +#define PCA9633_LED_PWM 0x2 /* Controlled through PWM */ +#define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ + +#define PCA9633_MODE1 0x00 +#define PCA9633_MODE2 0x01 +#define PCA9633_PWM_BASE 0x02 +#define PCA9633_LEDOUT 0x08 + +static const struct i2c_device_id pca9633_id[] = { + { "pca9633", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca9633_id); + +struct pca9633_led { + struct i2c_client *client; + struct work_struct work; + enum led_brightness brightness; + struct led_classdev led_cdev; + int led_num; /* 0 .. 3 potentially */ + char name[32]; +}; + +static void pca9633_led_work(struct work_struct *work) +{ + struct pca9633_led *pca9633 = container_of(work, + struct pca9633_led, work); + u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); + int shift = 2 * pca9633->led_num; + u8 mask = 0x3 << shift; + + switch (pca9633->brightness) { + case LED_FULL: + i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + (ledout & ~mask) | (PCA9633_LED_ON << shift)); + break; + case LED_OFF: + i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + ledout & ~mask); + break; + default: + i2c_smbus_write_byte_data(pca9633->client, + PCA9633_PWM_BASE + pca9633->led_num, + pca9633->brightness); + i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + (ledout & ~mask) | (PCA9633_LED_PWM << shift)); + break; + } +} + +static void pca9633_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pca9633_led *pca9633; + + pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); + + pca9633->brightness = value; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca9633->work); +} + +static int __devinit pca9633_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca9633_led *pca9633; + struct led_platform_data *pdata; + int i, err; + + pdata = client->dev.platform_data; + + if (pdata) { + if (pdata->num_leds <= 0 || pdata->num_leds > 4) { + dev_err(&client->dev, "board info must claim at most 4 LEDs"); + return -EINVAL; + } + } + + pca9633 = kcalloc(4, sizeof(*pca9633), GFP_KERNEL); + if (!pca9633) + return -ENOMEM; + + i2c_set_clientdata(client, pca9633); + + for (i = 0; i < 4; i++) { + pca9633[i].client = client; + pca9633[i].led_num = i; + + /* Platform data can specify LED names and default triggers */ + if (pdata && i < pdata->num_leds) { + if (pdata->leds[i].name) + snprintf(pca9633[i].name, + sizeof(pca9633[i].name), "pca9633:%s", + pdata->leds[i].name); + if (pdata->leds[i].default_trigger) + pca9633[i].led_cdev.default_trigger = + pdata->leds[i].default_trigger; + } else { + snprintf(pca9633[i].name, sizeof(pca9633[i].name), + "pca9633:%d", i); + } + + pca9633[i].led_cdev.name = pca9633[i].name; + pca9633[i].led_cdev.brightness_set = pca9633_led_set; + + INIT_WORK(&pca9633[i].work, pca9633_led_work); + + err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); + if (err < 0) + goto exit; + } + + /* Disable LED all-call address and set normal mode */ + i2c_smbus_write_byte_data(client, PCA9633_MODE1, 0x00); + + /* Turn off LEDs */ + i2c_smbus_write_byte_data(client, PCA9633_LEDOUT, 0x00); + + return 0; + +exit: + while (i--) { + led_classdev_unregister(&pca9633[i].led_cdev); + cancel_work_sync(&pca9633[i].work); + } + + kfree(pca9633); + + return err; +} + +static int __devexit pca9633_remove(struct i2c_client *client) +{ + struct pca9633_led *pca9633 = i2c_get_clientdata(client); + int i; + + for (i = 0; i < 4; i++) { + led_classdev_unregister(&pca9633[i].led_cdev); + cancel_work_sync(&pca9633[i].work); + } + + kfree(pca9633); + + return 0; +} + +static struct i2c_driver pca9633_driver = { + .driver = { + .name = "leds-pca9633", + .owner = THIS_MODULE, + }, + .probe = pca9633_probe, + .remove = __devexit_p(pca9633_remove), + .id_table = pca9633_id, +}; + +module_i2c_driver(pca9633_driver); + +MODULE_AUTHOR("Peter Meerwald <p.meerwald@bct-electronic.com>"); +MODULE_DESCRIPTION("PCA9633 LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 133f89fb7071..6c1c14f31635 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -687,10 +687,9 @@ static int __devinit tca6507_probe(struct i2c_client *client, NUM_LEDS); return -ENODEV; } - err = -ENOMEM; tca = kzalloc(sizeof(*tca), GFP_KERNEL); if (!tca) - goto exit; + return -ENOMEM; tca->client = client; INIT_WORK(&tca->work, tca6507_work); @@ -724,11 +723,10 @@ static int __devinit tca6507_probe(struct i2c_client *client, return 0; exit: - while (i--) + while (i--) { if (tca->leds[i].led_cdev.name) led_classdev_unregister(&tca->leds[i].led_cdev); - cancel_work_sync(&tca->work); - i2c_set_clientdata(client, NULL); + } kfree(tca); return err; } @@ -745,7 +743,6 @@ static int __devexit tca6507_remove(struct i2c_client *client) } tca6507_remove_gpio(tca); cancel_work_sync(&tca->work); - i2c_set_clientdata(client, NULL); kfree(tca); return 0; diff --git a/drivers/media/common/tuners/Makefile b/drivers/media/common/tuners/Makefile index 8295854ab94b..f80407eb8998 100644 --- a/drivers/media/common/tuners/Makefile +++ b/drivers/media/common/tuners/Makefile @@ -29,5 +29,5 @@ obj-$(CONFIG_MEDIA_TUNER_MAX2165) += max2165.o obj-$(CONFIG_MEDIA_TUNER_TDA18218) += tda18218.o obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o -ccflags-y += -Idrivers/media/dvb/dvb-core -ccflags-y += -Idrivers/media/dvb/frontends +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb/frontends diff --git a/drivers/media/common/tuners/max2165.c b/drivers/media/common/tuners/max2165.c index cb2c98fbad1b..ba84936aafd6 100644 --- a/drivers/media/common/tuners/max2165.c +++ b/drivers/media/common/tuners/max2165.c @@ -168,7 +168,7 @@ int fixpt_div32(u32 dividend, u32 divisor, u32 *quotient, u32 *fraction) int i; if (0 == divisor) - return -1; + return -EINVAL; q = dividend / divisor; remainder = dividend - q * divisor; @@ -194,10 +194,13 @@ static int max2165_set_rf(struct max2165_priv *priv, u32 freq) u8 tf_ntch; u32 t; u32 quotient, fraction; + int ret; /* Set PLL divider according to RF frequency */ - fixpt_div32(freq / 1000, priv->config->osc_clk * 1000, - "ient, &fraction); + ret = fixpt_div32(freq / 1000, priv->config->osc_clk * 1000, + "ient, &fraction); + if (ret != 0) + return ret; /* 20-bit fraction */ fraction >>= 12; diff --git a/drivers/media/common/tuners/mt2063.c b/drivers/media/common/tuners/mt2063.c index c89af3cd5eba..0ed9091ff48e 100644 --- a/drivers/media/common/tuners/mt2063.c +++ b/drivers/media/common/tuners/mt2063.c @@ -350,7 +350,7 @@ static int MT2063_Sleep(struct dvb_frontend *fe) /* * ToDo: Add code here to implement a OS blocking */ - msleep(10); + msleep(100); return 0; } @@ -2226,7 +2226,7 @@ static struct dvb_tuner_ops mt2063_ops = { .info = { .name = "MT2063 Silicon Tuner", .frequency_min = 45000000, - .frequency_max = 850000000, + .frequency_max = 865000000, .frequency_step = 0, }, diff --git a/drivers/media/common/tuners/mt2063.h b/drivers/media/common/tuners/mt2063.h index 62d0e8ec4e99..3f5cfd93713f 100644 --- a/drivers/media/common/tuners/mt2063.h +++ b/drivers/media/common/tuners/mt2063.h @@ -23,10 +23,6 @@ static inline struct dvb_frontend *mt2063_attach(struct dvb_frontend *fe, return NULL; } -int mt2063_setTune(struct dvb_frontend *fe, u32 f_in, - u32 bw_in, - enum MTTune_atv_standard tv_type); - /* FIXME: Should use the standard DVB attachment interfaces */ unsigned int tuner_MT2063_SoftwareShutdown(struct dvb_frontend *fe); unsigned int tuner_MT2063_ClearPowerMaskBits(struct dvb_frontend *fe); diff --git a/drivers/media/common/tuners/tuner-types.c b/drivers/media/common/tuners/tuner-types.c index e13683bab6b3..2da4440c16ee 100644 --- a/drivers/media/common/tuners/tuner-types.c +++ b/drivers/media/common/tuners/tuner-types.c @@ -1868,6 +1868,10 @@ struct tunertype tuners[] = { .params = tuner_tena_tnf_5337_params, .count = ARRAY_SIZE(tuner_tena_tnf_5337_params), }, + [TUNER_XC5000C] = { /* Xceive 5000C */ + .name = "Xceive 5000C tuner", + /* see xc5000.c for details */ + }, }; EXPORT_SYMBOL(tuners); diff --git a/drivers/media/common/tuners/xc5000.c b/drivers/media/common/tuners/xc5000.c index 296df05b8cda..7f98984e4fad 100644 --- a/drivers/media/common/tuners/xc5000.c +++ b/drivers/media/common/tuners/xc5000.c @@ -49,9 +49,6 @@ static LIST_HEAD(hybrid_tuner_instance_list); #define dprintk(level, fmt, arg...) if (debug >= level) \ printk(KERN_INFO "%s: " fmt, "xc5000", ## arg) -#define XC5000_DEFAULT_FIRMWARE "dvb-fe-xc5000-1.6.114.fw" -#define XC5000_DEFAULT_FIRMWARE_SIZE 12401 - struct xc5000_priv { struct tuner_i2c_props i2c_props; struct list_head hybrid_tuner_instance_list; @@ -62,6 +59,8 @@ struct xc5000_priv { u8 video_standard; u8 rf_mode; u8 radio_input; + + int chip_id; }; /* Misc Defines */ @@ -204,6 +203,33 @@ static struct XC_TV_STANDARD XC5000_Standard[MAX_TV_STANDARD] = { {"FM Radio-INPUT1_MONO", 0x0278, 0x9002} }; + +struct xc5000_fw_cfg { + char *name; + u16 size; +}; + +static const struct xc5000_fw_cfg xc5000a_1_6_114 = { + .name = "dvb-fe-xc5000-1.6.114.fw", + .size = 12401, +}; + +static const struct xc5000_fw_cfg xc5000c_41_024_5_31875 = { + .name = "dvb-fe-xc5000c-41.024.5-31875.fw", + .size = 16503, +}; + +static inline const struct xc5000_fw_cfg *xc5000_assign_firmware(int chip_id) +{ + switch (chip_id) { + default: + case XC5000A: + return &xc5000a_1_6_114; + case XC5000C: + return &xc5000c_41_024_5_31875; + } +} + static int xc_load_fw_and_init_tuner(struct dvb_frontend *fe); static int xc5000_is_firmware_loaded(struct dvb_frontend *fe); static int xc5000_readreg(struct xc5000_priv *priv, u16 reg, u16 *val); @@ -552,12 +578,14 @@ static int xc5000_fwupload(struct dvb_frontend *fe) struct xc5000_priv *priv = fe->tuner_priv; const struct firmware *fw; int ret; + const struct xc5000_fw_cfg *desired_fw = + xc5000_assign_firmware(priv->chip_id); /* request the firmware, this will block and timeout */ printk(KERN_INFO "xc5000: waiting for firmware upload (%s)...\n", - XC5000_DEFAULT_FIRMWARE); + desired_fw->name); - ret = request_firmware(&fw, XC5000_DEFAULT_FIRMWARE, + ret = request_firmware(&fw, desired_fw->name, priv->i2c_props.adap->dev.parent); if (ret) { printk(KERN_ERR "xc5000: Upload failed. (file not found?)\n"); @@ -569,7 +597,7 @@ static int xc5000_fwupload(struct dvb_frontend *fe) ret = XC_RESULT_SUCCESS; } - if (fw->size != XC5000_DEFAULT_FIRMWARE_SIZE) { + if (fw->size != desired_fw->size) { printk(KERN_ERR "xc5000: firmware incorrect size\n"); ret = XC_RESULT_RESET_FAILURE; } else { @@ -1139,6 +1167,13 @@ struct dvb_frontend *xc5000_attach(struct dvb_frontend *fe, if (priv->radio_input == 0) priv->radio_input = cfg->radio_input; + /* don't override chip id if it's already been set + unless explicitly specified */ + if ((priv->chip_id == 0) || (cfg->chip_id)) + /* use default chip id if none specified, set to 0 so + it can be overridden if this is a hybrid driver */ + priv->chip_id = (cfg->chip_id) ? cfg->chip_id : 0; + /* Check if firmware has been loaded. It is possible that another instance of the driver has loaded the firmware. */ diff --git a/drivers/media/common/tuners/xc5000.h b/drivers/media/common/tuners/xc5000.h index e2957451b532..3396f8e02b40 100644 --- a/drivers/media/common/tuners/xc5000.h +++ b/drivers/media/common/tuners/xc5000.h @@ -27,10 +27,15 @@ struct dvb_frontend; struct i2c_adapter; +#define XC5000A 1 +#define XC5000C 2 + struct xc5000_config { u8 i2c_address; u32 if_khz; u8 radio_input; + + int chip_id; }; /* xc5000 callback command */ diff --git a/drivers/media/dvb/ddbridge/ddbridge-core.c b/drivers/media/dvb/ddbridge/ddbridge-core.c index ce4f85849e7b..d88c4aa7d24d 100644 --- a/drivers/media/dvb/ddbridge/ddbridge-core.c +++ b/drivers/media/dvb/ddbridge/ddbridge-core.c @@ -578,6 +578,7 @@ static int demod_attach_drxk(struct ddb_input *input) struct drxk_config config; memset(&config, 0, sizeof(config)); + config.microcode_name = "drxk_a3.mc"; config.adr = 0x29 + (input->nr & 1); fe = input->fe = dvb_attach(drxk_attach, &config, i2c); diff --git a/drivers/media/dvb/ddbridge/ddbridge.h b/drivers/media/dvb/ddbridge/ddbridge.h index 6d14893218f4..8b1b41d2a52d 100644 --- a/drivers/media/dvb/ddbridge/ddbridge.h +++ b/drivers/media/dvb/ddbridge/ddbridge.h @@ -32,8 +32,6 @@ #include <asm/dma.h> #include <linux/dvb/frontend.h> #include <linux/dvb/ca.h> -#include <linux/dvb/video.h> -#include <linux/dvb/audio.h> #include <linux/socket.h> #include "dmxdev.h" diff --git a/drivers/media/dvb/dvb-core/dvb_frontend.c b/drivers/media/dvb/dvb-core/dvb_frontend.c index fbbe545a74cb..4555baa383b2 100644 --- a/drivers/media/dvb/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb/dvb-core/dvb_frontend.c @@ -655,6 +655,8 @@ restart: dprintk("%s: Retune requested, FESTATE_RETUNE\n", __func__); re_tune = true; fepriv->state = FESTATE_TUNED; + } else { + re_tune = false; } if (fe->ops.tune) diff --git a/drivers/media/dvb/dvb-usb/Kconfig b/drivers/media/dvb/dvb-usb/Kconfig index 9f203c6767a6..63bf45679f98 100644 --- a/drivers/media/dvb/dvb-usb/Kconfig +++ b/drivers/media/dvb/dvb-usb/Kconfig @@ -361,6 +361,14 @@ config DVB_USB_EC168 help Say Y here to support the E3C EC168 DVB-T USB2.0 receiver. +config DVB_USB_AZ6007 + tristate "AzureWave 6007 and clones DVB-T/C USB2.0 support" + depends on DVB_USB + select DVB_DRXK if !DVB_FE_CUSTOMISE + select MEDIA_TUNER_MT2063 if !DVB_FE_CUSTOMISE + help + Say Y here to support theAfatech AF9005 based DVB-T/DVB-C receivers. + config DVB_USB_AZ6027 tristate "Azurewave DVB-S/S2 USB2.0 AZ6027 support" depends on DVB_USB @@ -378,6 +386,7 @@ config DVB_USB_LME2510 select DVB_IX2505V if !DVB_FE_CUSTOMISE select DVB_STV0299 if !DVB_FE_CUSTOMISE select DVB_PLL if !DVB_FE_CUSTOMISE + select DVB_M88RS2000 if !DVB_FE_CUSTOMISE help Say Y here to support the LME DM04/QQBOX DVB-S USB2.0 . @@ -403,3 +412,13 @@ config DVB_USB_MXL111SF select VIDEO_TVEEPROM help Say Y here to support the MxL111SF USB2.0 DTV receiver. + +config DVB_USB_RTL28XXU + tristate "Realtek RTL28xxU DVB USB support" + depends on DVB_USB && EXPERIMENTAL + select DVB_RTL2830 + select MEDIA_TUNER_QT1010 if !MEDIA_TUNER_CUSTOMISE + select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMISE + select MEDIA_TUNER_MXL5005S if !MEDIA_TUNER_CUSTOMISE + help + Say Y here to support the Realtek RTL28xxU DVB USB receiver. diff --git a/drivers/media/dvb/dvb-usb/Makefile b/drivers/media/dvb/dvb-usb/Makefile index 26c8b9e57050..b76acb5387e6 100644 --- a/drivers/media/dvb/dvb-usb/Makefile +++ b/drivers/media/dvb/dvb-usb/Makefile @@ -54,7 +54,6 @@ obj-$(CONFIG_DVB_USB_DIB0700) += dvb-usb-dib0700.o dvb-usb-opera-objs = opera1.o obj-$(CONFIG_DVB_USB_OPERA1) += dvb-usb-opera.o - dvb-usb-af9005-objs = af9005.o af9005-fe.o obj-$(CONFIG_DVB_USB_AF9005) += dvb-usb-af9005.o @@ -88,6 +87,9 @@ obj-$(CONFIG_DVB_USB_FRIIO) += dvb-usb-friio.o dvb-usb-ec168-objs = ec168.o obj-$(CONFIG_DVB_USB_EC168) += dvb-usb-ec168.o +dvb-usb-az6007-objs = az6007.o +obj-$(CONFIG_DVB_USB_AZ6007) += dvb-usb-az6007.o + dvb-usb-az6027-objs = az6027.o obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-az6027.o @@ -105,8 +107,12 @@ obj-$(CONFIG_DVB_USB_MXL111SF) += dvb-usb-mxl111sf.o obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-demod.o obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o -ccflags-y += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ +dvb-usb-rtl28xxu-objs = rtl28xxu.o +obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o + +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb/frontends/ # due to tuner-xc3028 -ccflags-y += -Idrivers/media/common/tuners -EXTRA_CFLAGS += -Idrivers/media/dvb/ttpci +ccflags-y += -I$(srctree)/drivers/media/common/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb/ttpci diff --git a/drivers/media/dvb/dvb-usb/af9015.c b/drivers/media/dvb/dvb-usb/af9015.c index 282a43d648df..7e70ea50ef26 100644 --- a/drivers/media/dvb/dvb-usb/af9015.c +++ b/drivers/media/dvb/dvb-usb/af9015.c @@ -1164,6 +1164,41 @@ static int af9015_af9013_sleep(struct dvb_frontend *fe) return ret; } +/* override tuner callbacks for resource locking */ +static int af9015_tuner_init(struct dvb_frontend *fe) +{ + int ret; + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct af9015_state *priv = adap->dev->priv; + + if (mutex_lock_interruptible(&adap->dev->usb_mutex)) + return -EAGAIN; + + ret = priv->tuner_init[adap->id](fe); + + mutex_unlock(&adap->dev->usb_mutex); + + return ret; +} + +/* override tuner callbacks for resource locking */ +static int af9015_tuner_sleep(struct dvb_frontend *fe) +{ + int ret; + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct af9015_state *priv = adap->dev->priv; + + if (mutex_lock_interruptible(&adap->dev->usb_mutex)) + return -EAGAIN; + + ret = priv->tuner_sleep[adap->id](fe); + + mutex_unlock(&adap->dev->usb_mutex); + + return ret; +} + + static int af9015_af9013_frontend_attach(struct dvb_usb_adapter *adap) { int ret; @@ -1283,6 +1318,7 @@ static struct mxl5007t_config af9015_mxl5007t_config = { static int af9015_tuner_attach(struct dvb_usb_adapter *adap) { int ret; + struct af9015_state *state = adap->dev->priv; deb_info("%s:\n", __func__); switch (af9015_af9013_config[adap->id].tuner) { @@ -1340,6 +1376,19 @@ static int af9015_tuner_attach(struct dvb_usb_adapter *adap) err("Unknown tuner id:%d", af9015_af9013_config[adap->id].tuner); } + + if (adap->fe_adap[0].fe->ops.tuner_ops.init) { + state->tuner_init[adap->id] = + adap->fe_adap[0].fe->ops.tuner_ops.init; + adap->fe_adap[0].fe->ops.tuner_ops.init = af9015_tuner_init; + } + + if (adap->fe_adap[0].fe->ops.tuner_ops.sleep) { + state->tuner_sleep[adap->id] = + adap->fe_adap[0].fe->ops.tuner_ops.sleep; + adap->fe_adap[0].fe->ops.tuner_ops.sleep = af9015_tuner_sleep; + } + return ret; } diff --git a/drivers/media/dvb/dvb-usb/af9015.h b/drivers/media/dvb/dvb-usb/af9015.h index f619063fa72f..2f68419e899b 100644 --- a/drivers/media/dvb/dvb-usb/af9015.h +++ b/drivers/media/dvb/dvb-usb/af9015.h @@ -108,6 +108,8 @@ struct af9015_state { int (*read_status[2]) (struct dvb_frontend *fe, fe_status_t *status); int (*init[2]) (struct dvb_frontend *fe); int (*sleep[2]) (struct dvb_frontend *fe); + int (*tuner_init[2]) (struct dvb_frontend *fe); + int (*tuner_sleep[2]) (struct dvb_frontend *fe); }; struct af9015_config { diff --git a/drivers/media/dvb/dvb-usb/anysee.c b/drivers/media/dvb/dvb-usb/anysee.c index cf0c318d6989..03c28655af1b 100644 --- a/drivers/media/dvb/dvb-usb/anysee.c +++ b/drivers/media/dvb/dvb-usb/anysee.c @@ -58,7 +58,7 @@ static int anysee_ctrl_msg(struct dvb_usb_device *d, u8 *sbuf, u8 slen, u8 *rbuf, u8 rlen) { struct anysee_state *state = d->priv; - int act_len, ret; + int act_len, ret, i; u8 buf[64]; memcpy(&buf[0], sbuf, slen); @@ -73,26 +73,52 @@ static int anysee_ctrl_msg(struct dvb_usb_device *d, u8 *sbuf, u8 slen, /* We need receive one message more after dvb_usb_generic_rw due to weird transaction flow, which is 1 x send + 2 x receive. */ ret = dvb_usb_generic_rw(d, buf, sizeof(buf), buf, sizeof(buf), 0); - if (!ret) { + if (ret) + goto error_unlock; + + /* TODO FIXME: dvb_usb_generic_rw() fails rarely with error code -32 + * (EPIPE, Broken pipe). Function supports currently msleep() as a + * parameter but I would not like to use it, since according to + * Documentation/timers/timers-howto.txt it should not be used such + * short, under < 20ms, sleeps. Repeating failed message would be + * better choice as not to add unwanted delays... + * Fixing that correctly is one of those or both; + * 1) use repeat if possible + * 2) add suitable delay + */ + + /* get answer, retry few times if error returned */ + for (i = 0; i < 3; i++) { /* receive 2nd answer */ ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint), buf, sizeof(buf), &act_len, 2000); - if (ret) - err("%s: recv bulk message failed: %d", __func__, ret); - else { + + if (ret) { + deb_info("%s: recv bulk message failed: %d", + __func__, ret); + } else { deb_xfer("<<< "); debug_dump(buf, rlen, deb_xfer); if (buf[63] != 0x4f) deb_info("%s: cmd failed\n", __func__); + + break; } } + if (ret) { + /* all retries failed, it is fatal */ + err("%s: recv bulk message failed: %d", __func__, ret); + goto error_unlock; + } + /* read request, copy returned data to return buf */ - if (!ret && rbuf && rlen) + if (rbuf && rlen) memcpy(rbuf, buf, rlen); +error_unlock: mutex_unlock(&anysee_usb_mutex); return ret; diff --git a/drivers/media/dvb/dvb-usb/az6007.c b/drivers/media/dvb/dvb-usb/az6007.c new file mode 100644 index 000000000000..4008b9c50fbd --- /dev/null +++ b/drivers/media/dvb/dvb-usb/az6007.c @@ -0,0 +1,957 @@ +/* + * Driver for AzureWave 6007 DVB-C/T USB2.0 and clones + * + * Copyright (c) Henry Wang <Henry.wang@AzureWave.com> + * + * This driver was made publicly available by Terratec, at: + * http://linux.terratec.de/files/TERRATEC_H7/20110323_TERRATEC_H7_Linux.tar.gz + * The original driver's license is GPL, as declared with MODULE_LICENSE() + * + * Copyright (c) 2010-2011 Mauro Carvalho Chehab <mchehab@redhat.com> + * Driver modified by in order to work with upstream drxk driver, and + * tons of bugs got fixed. + * + * 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 under version 2 of the License. + * + * 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 "drxk.h" +#include "mt2063.h" +#include "dvb_ca_en50221.h" + +#define DVB_USB_LOG_PREFIX "az6007" +#include "dvb-usb.h" + +/* debug */ +int dvb_usb_az6007_debug; +module_param_named(debug, dvb_usb_az6007_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." + DVB_USB_DEBUG_STATUS); + +#define deb_info(args...) dprintk(dvb_usb_az6007_debug, 0x01, args) +#define deb_xfer(args...) dprintk(dvb_usb_az6007_debug, 0x02, args) +#define deb_rc(args...) dprintk(dvb_usb_az6007_debug, 0x04, args) +#define deb_fe(args...) dprintk(dvb_usb_az6007_debug, 0x08, args) + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +/* Known requests (Cypress FX2 firmware + az6007 "private" ones*/ + +#define FX2_OED 0xb5 +#define AZ6007_READ_DATA 0xb7 +#define AZ6007_I2C_RD 0xb9 +#define AZ6007_POWER 0xbc +#define AZ6007_I2C_WR 0xbd +#define FX2_SCON1 0xc0 +#define AZ6007_TS_THROUGH 0xc7 +#define AZ6007_READ_IR 0xb4 + +struct az6007_device_state { + struct mutex mutex; + struct mutex ca_mutex; + struct dvb_ca_en50221 ca; + unsigned warm:1; + int (*gate_ctrl) (struct dvb_frontend *, int); + unsigned char data[4096]; +}; + +static struct drxk_config terratec_h7_drxk = { + .adr = 0x29, + .parallel_ts = true, + .dynamic_clk = true, + .single_master = true, + .enable_merr_cfg = true, + .no_i2c_bridge = false, + .chunk_size = 64, + .mpeg_out_clk_strength = 0x02, + .microcode_name = "dvb-usb-terratec-h7-drxk.fw", +}; + +static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct dvb_usb_adapter *adap = fe->sec_priv; + struct az6007_device_state *st; + int status = 0; + + deb_info("%s: %s\n", __func__, enable ? "enable" : "disable"); + + if (!adap) + return -EINVAL; + + st = adap->dev->priv; + + if (!st) + return -EINVAL; + + if (enable) + status = st->gate_ctrl(fe, 1); + else + status = st->gate_ctrl(fe, 0); + + return status; +} + +static struct mt2063_config az6007_mt2063_config = { + .tuner_address = 0x60, + .refclock = 36125000, +}; + +static int __az6007_read(struct usb_device *udev, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + int ret; + + ret = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + req, + USB_TYPE_VENDOR | USB_DIR_IN, + value, index, b, blen, 5000); + if (ret < 0) { + warn("usb read operation failed. (%d)", ret); + return -EIO; + } + + deb_xfer("in: req. %02x, val: %04x, ind: %04x, buffer: ", req, value, + index); + debug_dump(b, blen, deb_xfer); + + return ret; +} + +static int az6007_read(struct dvb_usb_device *d, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + struct az6007_device_state *st = d->priv; + int ret; + + if (mutex_lock_interruptible(&st->mutex) < 0) + return -EAGAIN; + + ret = __az6007_read(d->udev, req, value, index, b, blen); + + mutex_unlock(&st->mutex); + + return ret; +} + +static int __az6007_write(struct usb_device *udev, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + int ret; + + deb_xfer("out: req. %02x, val: %04x, ind: %04x, buffer: ", req, value, + index); + debug_dump(b, blen, deb_xfer); + + if (blen > 64) { + err("az6007: tried to write %d bytes, but I2C max size is 64 bytes\n", + blen); + return -EOPNOTSUPP; + } + + ret = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + req, + USB_TYPE_VENDOR | USB_DIR_OUT, + value, index, b, blen, 5000); + if (ret != blen) { + err("usb write operation failed. (%d)", ret); + return -EIO; + } + + return 0; +} + +static int az6007_write(struct dvb_usb_device *d, u8 req, u16 value, + u16 index, u8 *b, int blen) +{ + struct az6007_device_state *st = d->priv; + int ret; + + if (mutex_lock_interruptible(&st->mutex) < 0) + return -EAGAIN; + + ret = __az6007_write(d->udev, req, value, index, b, blen); + + mutex_unlock(&st->mutex); + + return ret; +} + +static int az6007_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) +{ + struct dvb_usb_device *d = adap->dev; + + deb_info("%s: %s", __func__, onoff ? "enable" : "disable"); + + return az6007_write(d, 0xbc, onoff, 0, NULL, 0); +} + +/* remote control stuff (does not work with my box) */ +static int az6007_rc_query(struct dvb_usb_device *d) +{ + struct az6007_device_state *st = d->priv; + unsigned code = 0; + + az6007_read(d, AZ6007_READ_IR, 0, 0, st->data, 10); + + if (st->data[1] == 0x44) + return 0; + + if ((st->data[1] ^ st->data[2]) == 0xff) + code = st->data[1]; + else + code = st->data[1] << 8 | st->data[2]; + + if ((st->data[3] ^ st->data[4]) == 0xff) + code = code << 8 | st->data[3]; + else + code = code << 16 | st->data[3] << 8 | st->data[4]; + + rc_keydown(d->rc_dev, code, st->data[5]); + + return 0; +} + +static int az6007_ci_read_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, + int address) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + if (slot != 0) + return -EINVAL; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + + mutex_lock(&state->ca_mutex); + + req = 0xC1; + value = address; + index = 0; + blen = 1; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + warn("usb in operation failed. (%d)", ret); + ret = -EINVAL; + } else { + ret = b[0]; + } + + mutex_unlock(&state->ca_mutex); + kfree(b); + return ret; +} + +static int az6007_ci_write_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, + int address, + u8 value) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + + int ret; + u8 req; + u16 value1; + u16 index; + int blen; + + deb_info("%s %d", __func__, slot); + if (slot != 0) + return -EINVAL; + + mutex_lock(&state->ca_mutex); + req = 0xC2; + value1 = address; + index = value; + blen = 0; + + ret = az6007_write(d, req, value1, index, NULL, blen); + if (ret != 0) + warn("usb out operation failed. (%d)", ret); + + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int az6007_ci_read_cam_control(struct dvb_ca_en50221 *ca, + int slot, + u8 address) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + if (slot != 0) + return -EINVAL; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + + mutex_lock(&state->ca_mutex); + + req = 0xC3; + value = address; + index = 0; + blen = 2; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + warn("usb in operation failed. (%d)", ret); + ret = -EINVAL; + } else { + if (b[0] == 0) + warn("Read CI IO error"); + + ret = b[1]; + deb_info("read cam data = %x from 0x%x", b[1], value); + } + + mutex_unlock(&state->ca_mutex); + kfree(b); + return ret; +} + +static int az6007_ci_write_cam_control(struct dvb_ca_en50221 *ca, + int slot, + u8 address, + u8 value) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + + int ret; + u8 req; + u16 value1; + u16 index; + int blen; + + if (slot != 0) + return -EINVAL; + + mutex_lock(&state->ca_mutex); + req = 0xC4; + value1 = address; + index = value; + blen = 0; + + ret = az6007_write(d, req, value1, index, NULL, blen); + if (ret != 0) { + warn("usb out operation failed. (%d)", ret); + goto failed; + } + +failed: + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int CI_CamReady(struct dvb_ca_en50221 *ca, int slot) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + + req = 0xC8; + value = 0; + index = 0; + blen = 1; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + warn("usb in operation failed. (%d)", ret); + ret = -EIO; + } else{ + ret = b[0]; + } + kfree(b); + return ret; +} + +static int az6007_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + + int ret, i; + u8 req; + u16 value; + u16 index; + int blen; + + mutex_lock(&state->ca_mutex); + + req = 0xC6; + value = 1; + index = 0; + blen = 0; + + ret = az6007_write(d, req, value, index, NULL, blen); + if (ret != 0) { + warn("usb out operation failed. (%d)", ret); + goto failed; + } + + msleep(500); + req = 0xC6; + value = 0; + index = 0; + blen = 0; + + ret = az6007_write(d, req, value, index, NULL, blen); + if (ret != 0) { + warn("usb out operation failed. (%d)", ret); + goto failed; + } + + for (i = 0; i < 15; i++) { + msleep(100); + + if (CI_CamReady(ca, slot)) { + deb_info("CAM Ready"); + break; + } + } + msleep(5000); + +failed: + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int az6007_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + return 0; +} + +static int az6007_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + + int ret; + u8 req; + u16 value; + u16 index; + int blen; + + deb_info("%s", __func__); + mutex_lock(&state->ca_mutex); + req = 0xC7; + value = 1; + index = 0; + blen = 0; + + ret = az6007_write(d, req, value, index, NULL, blen); + if (ret != 0) { + warn("usb out operation failed. (%d)", ret); + goto failed; + } + +failed: + mutex_unlock(&state->ca_mutex); + return ret; +} + +static int az6007_ci_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + int ret; + u8 req; + u16 value; + u16 index; + int blen; + u8 *b; + + b = kmalloc(12, GFP_KERNEL); + if (!b) + return -ENOMEM; + mutex_lock(&state->ca_mutex); + + req = 0xC5; + value = 0; + index = 0; + blen = 1; + + ret = az6007_read(d, req, value, index, b, blen); + if (ret < 0) { + warn("usb in operation failed. (%d)", ret); + ret = -EIO; + } else + ret = 0; + + if (!ret && b[0] == 1) { + ret = DVB_CA_EN50221_POLL_CAM_PRESENT | + DVB_CA_EN50221_POLL_CAM_READY; + } + + mutex_unlock(&state->ca_mutex); + kfree(b); + return ret; +} + + +static void az6007_ci_uninit(struct dvb_usb_device *d) +{ + struct az6007_device_state *state; + + deb_info("%s", __func__); + + if (NULL == d) + return; + + state = (struct az6007_device_state *)d->priv; + if (NULL == state) + return; + + if (NULL == state->ca.data) + return; + + dvb_ca_en50221_release(&state->ca); + + memset(&state->ca, 0, sizeof(state->ca)); +} + + +static int az6007_ci_init(struct dvb_usb_adapter *a) +{ + struct dvb_usb_device *d = a->dev; + struct az6007_device_state *state = (struct az6007_device_state *)d->priv; + int ret; + + deb_info("%s", __func__); + + mutex_init(&state->ca_mutex); + + state->ca.owner = THIS_MODULE; + state->ca.read_attribute_mem = az6007_ci_read_attribute_mem; + state->ca.write_attribute_mem = az6007_ci_write_attribute_mem; + state->ca.read_cam_control = az6007_ci_read_cam_control; + state->ca.write_cam_control = az6007_ci_write_cam_control; + state->ca.slot_reset = az6007_ci_slot_reset; + state->ca.slot_shutdown = az6007_ci_slot_shutdown; + state->ca.slot_ts_enable = az6007_ci_slot_ts_enable; + state->ca.poll_slot_status = az6007_ci_poll_slot_status; + state->ca.data = d; + + ret = dvb_ca_en50221_init(&a->dvb_adap, + &state->ca, + 0, /* flags */ + 1);/* n_slots */ + if (ret != 0) { + err("Cannot initialize CI: Error %d.", ret); + memset(&state->ca, 0, sizeof(state->ca)); + return ret; + } + + deb_info("CI initialized."); + + return 0; +} + +static int az6007_read_mac_addr(struct dvb_usb_device *d, u8 mac[6]) +{ + struct az6007_device_state *st = d->priv; + int ret; + + ret = az6007_read(d, AZ6007_READ_DATA, 6, 0, st->data, 6); + memcpy(mac, st->data, sizeof(mac)); + + if (ret > 0) + deb_info("%s: mac is %02x:%02x:%02x:%02x:%02x:%02x\n", + __func__, mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]); + + return ret; +} + +static int az6007_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct az6007_device_state *st = adap->dev->priv; + + deb_info("attaching demod drxk"); + + adap->fe_adap[0].fe = dvb_attach(drxk_attach, &terratec_h7_drxk, + &adap->dev->i2c_adap); + if (!adap->fe_adap[0].fe) + return -EINVAL; + + adap->fe_adap[0].fe->sec_priv = adap; + st->gate_ctrl = adap->fe_adap[0].fe->ops.i2c_gate_ctrl; + adap->fe_adap[0].fe->ops.i2c_gate_ctrl = drxk_gate_ctrl; + + az6007_ci_init(adap); + + return 0; +} + +static int az6007_tuner_attach(struct dvb_usb_adapter *adap) +{ + deb_info("attaching tuner mt2063"); + + /* Attach mt2063 to DVB-C frontend */ + if (adap->fe_adap[0].fe->ops.i2c_gate_ctrl) + adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, 1); + if (!dvb_attach(mt2063_attach, adap->fe_adap[0].fe, + &az6007_mt2063_config, + &adap->dev->i2c_adap)) + return -EINVAL; + + if (adap->fe_adap[0].fe->ops.i2c_gate_ctrl) + adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, 0); + + return 0; +} + +int az6007_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + struct az6007_device_state *st = d->priv; + int ret; + + deb_info("%s()\n", __func__); + + if (!st->warm) { + mutex_init(&st->mutex); + + ret = az6007_write(d, AZ6007_POWER, 0, 2, NULL, 0); + if (ret < 0) + return ret; + msleep(60); + ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0); + if (ret < 0) + return ret; + msleep(100); + ret = az6007_write(d, AZ6007_POWER, 1, 3, NULL, 0); + if (ret < 0) + return ret; + msleep(20); + ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0); + if (ret < 0) + return ret; + + msleep(400); + ret = az6007_write(d, FX2_SCON1, 0, 3, NULL, 0); + if (ret < 0) + return ret; + msleep(150); + ret = az6007_write(d, FX2_SCON1, 1, 3, NULL, 0); + if (ret < 0) + return ret; + msleep(430); + ret = az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0); + if (ret < 0) + return ret; + + st->warm = true; + + return 0; + } + + if (!onoff) + return 0; + + az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0); + az6007_write(d, AZ6007_TS_THROUGH, 0, 0, NULL, 0); + + return 0; +} + +/* I2C */ +static int az6007_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct az6007_device_state *st = d->priv; + int i, j, len; + int ret = 0; + u16 index; + u16 value; + int length; + u8 req, addr; + + if (mutex_lock_interruptible(&st->mutex) < 0) + return -EAGAIN; + + for (i = 0; i < num; i++) { + addr = msgs[i].addr << 1; + if (((i + 1) < num) + && (msgs[i].len == 1) + && (!msgs[i].flags & I2C_M_RD) + && (msgs[i + 1].flags & I2C_M_RD) + && (msgs[i].addr == msgs[i + 1].addr)) { + /* + * A write + read xfer for the same address, where + * the first xfer has just 1 byte length. + * Need to join both into one operation + */ + if (dvb_usb_az6007_debug & 2) + printk(KERN_DEBUG + "az6007 I2C xfer write+read addr=0x%x len=%d/%d: ", + addr, msgs[i].len, msgs[i + 1].len); + req = AZ6007_I2C_RD; + index = msgs[i].buf[0]; + value = addr | (1 << 8); + length = 6 + msgs[i + 1].len; + len = msgs[i + 1].len; + ret = __az6007_read(d->udev, req, value, index, + st->data, length); + if (ret >= len) { + for (j = 0; j < len; j++) { + msgs[i + 1].buf[j] = st->data[j + 5]; + if (dvb_usb_az6007_debug & 2) + printk(KERN_CONT + "0x%02x ", + msgs[i + 1].buf[j]); + } + } else + ret = -EIO; + i++; + } else if (!(msgs[i].flags & I2C_M_RD)) { + /* write bytes */ + if (dvb_usb_az6007_debug & 2) + printk(KERN_DEBUG + "az6007 I2C xfer write addr=0x%x len=%d: ", + addr, msgs[i].len); + req = AZ6007_I2C_WR; + index = msgs[i].buf[0]; + value = addr | (1 << 8); + length = msgs[i].len - 1; + len = msgs[i].len - 1; + if (dvb_usb_az6007_debug & 2) + printk(KERN_CONT "(0x%02x) ", msgs[i].buf[0]); + for (j = 0; j < len; j++) { + st->data[j] = msgs[i].buf[j + 1]; + if (dvb_usb_az6007_debug & 2) + printk(KERN_CONT "0x%02x ", + st->data[j]); + } + ret = __az6007_write(d->udev, req, value, index, + st->data, length); + } else { + /* read bytes */ + if (dvb_usb_az6007_debug & 2) + printk(KERN_DEBUG + "az6007 I2C xfer read addr=0x%x len=%d: ", + addr, msgs[i].len); + req = AZ6007_I2C_RD; + index = msgs[i].buf[0]; + value = addr; + length = msgs[i].len + 6; + len = msgs[i].len; + ret = __az6007_read(d->udev, req, value, index, + st->data, length); + for (j = 0; j < len; j++) { + msgs[i].buf[j] = st->data[j + 5]; + if (dvb_usb_az6007_debug & 2) + printk(KERN_CONT + "0x%02x ", st->data[j + 5]); + } + } + if (dvb_usb_az6007_debug & 2) + printk(KERN_CONT "\n"); + if (ret < 0) + goto err; + } +err: + mutex_unlock(&st->mutex); + + if (ret < 0) { + info("%s ERROR: %i", __func__, ret); + return ret; + } + return num; +} + +static u32 az6007_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm az6007_i2c_algo = { + .master_xfer = az6007_i2c_xfer, + .functionality = az6007_i2c_func, +}; + +int az6007_identify_state(struct usb_device *udev, + struct dvb_usb_device_properties *props, + struct dvb_usb_device_description **desc, int *cold) +{ + int ret; + u8 *mac; + + mac = kmalloc(6, GFP_ATOMIC); + if (!mac) + return -ENOMEM; + + /* Try to read the mac address */ + ret = __az6007_read(udev, AZ6007_READ_DATA, 6, 0, mac, 6); + if (ret == 6) + *cold = 0; + else + *cold = 1; + + kfree(mac); + + if (*cold) { + __az6007_write(udev, 0x09, 1, 0, NULL, 0); + __az6007_write(udev, 0x00, 0, 0, NULL, 0); + __az6007_write(udev, 0x00, 0, 0, NULL, 0); + } + + deb_info("Device is on %s state\n", *cold ? "warm" : "cold"); + return 0; +} + +static struct dvb_usb_device_properties az6007_properties; + +static void az6007_usb_disconnect(struct usb_interface *intf) +{ + struct dvb_usb_device *d = usb_get_intfdata(intf); + az6007_ci_uninit(d); + dvb_usb_device_exit(intf); +} + +static int az6007_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return dvb_usb_device_init(intf, &az6007_properties, + THIS_MODULE, NULL, adapter_nr); +} + +static struct usb_device_id az6007_usb_table[] = { + {USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_AZUREWAVE_6007)}, + {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7)}, + {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7_2)}, + {0}, +}; + +MODULE_DEVICE_TABLE(usb, az6007_usb_table); + +static struct dvb_usb_device_properties az6007_properties = { + .caps = DVB_USB_IS_AN_I2C_ADAPTER, + .usb_ctrl = CYPRESS_FX2, + .firmware = "dvb-usb-terratec-h7-az6007.fw", + .no_reconnect = 1, + .size_of_priv = sizeof(struct az6007_device_state), + .identify_state = az6007_identify_state, + .num_adapters = 1, + .adapter = { + { + .num_frontends = 1, + .fe = {{ + .streaming_ctrl = az6007_streaming_ctrl, + .tuner_attach = az6007_tuner_attach, + .frontend_attach = az6007_frontend_attach, + + /* parameter for the MPEG2-data transfer */ + .stream = { + .type = USB_BULK, + .count = 10, + .endpoint = 0x02, + .u = { + .bulk = { + .buffersize = 4096, + } + } + }, + } } + } }, + .power_ctrl = az6007_power_ctrl, + .read_mac_address = az6007_read_mac_addr, + + .rc.core = { + .rc_interval = 400, + .rc_codes = RC_MAP_NEC_TERRATEC_CINERGY_XS, + .module_name = "az6007", + .rc_query = az6007_rc_query, + .allowed_protos = RC_TYPE_NEC, + }, + .i2c_algo = &az6007_i2c_algo, + + .num_device_descs = 2, + .devices = { + { .name = "AzureWave DTV StarBox DVB-T/C USB2.0 (az6007)", + .cold_ids = { &az6007_usb_table[0], NULL }, + .warm_ids = { NULL }, + }, + { .name = "TerraTec DTV StarBox DVB-T/C USB2.0 (az6007)", + .cold_ids = { &az6007_usb_table[1], &az6007_usb_table[2], NULL }, + .warm_ids = { NULL }, + }, + { NULL }, + } +}; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver az6007_usb_driver = { + .name = "dvb_usb_az6007", + .probe = az6007_usb_probe, + .disconnect = az6007_usb_disconnect, + .id_table = az6007_usb_table, +}; + +/* module stuff */ +static int __init az6007_usb_module_init(void) +{ + int result; + deb_info("az6007 usb module init\n"); + + result = usb_register(&az6007_usb_driver); + if (result) { + err("usb_register failed. (%d)", result); + return result; + } + + return 0; +} + +static void __exit az6007_usb_module_exit(void) +{ + /* deregister this driver from the USB subsystem */ + deb_info("az6007 usb module exit\n"); + usb_deregister(&az6007_usb_driver); +} + +module_init(az6007_usb_module_init); +module_exit(az6007_usb_module_exit); + +MODULE_AUTHOR("Henry Wang <Henry.wang@AzureWave.com>"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); +MODULE_DESCRIPTION("Driver for AzureWave 6007 DVB-C/T USB2.0 and clones"); +MODULE_VERSION("1.1"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/dib0700_core.c b/drivers/media/dvb/dvb-usb/dib0700_core.c index 070e82aa53f5..02290c60f72f 100644 --- a/drivers/media/dvb/dvb-usb/dib0700_core.c +++ b/drivers/media/dvb/dvb-usb/dib0700_core.c @@ -677,11 +677,9 @@ static void dib0700_rc_urb_completion(struct urb *purb) u8 toggle; deb_info("%s()\n", __func__); - if (d == NULL) - return; - if (d->rc_dev == NULL) { /* This will occur if disable_rc_polling=1 */ + kfree(purb->transfer_buffer); usb_free_urb(purb); return; } @@ -690,6 +688,7 @@ static void dib0700_rc_urb_completion(struct urb *purb) if (purb->status < 0) { deb_info("discontinuing polling\n"); + kfree(purb->transfer_buffer); usb_free_urb(purb); return; } @@ -784,8 +783,11 @@ int dib0700_rc_setup(struct dvb_usb_device *d) dib0700_rc_urb_completion, d); ret = usb_submit_urb(purb, GFP_ATOMIC); - if (ret) + if (ret) { err("rc submit urb failed\n"); + kfree(purb->transfer_buffer); + usb_free_urb(purb); + } return ret; } diff --git a/drivers/media/dvb/dvb-usb/dvb-usb-ids.h b/drivers/media/dvb/dvb-usb/dvb-usb-ids.h index d390ddaa5a53..397d8f232731 100644 --- a/drivers/media/dvb/dvb-usb/dvb-usb-ids.h +++ b/drivers/media/dvb/dvb-usb/dvb-usb-ids.h @@ -51,6 +51,7 @@ #define USB_VID_PINNACLE 0x2304 #define USB_VID_PCTV 0x2013 #define USB_VID_PIXELVIEW 0x1554 +#define USB_VID_REALTEK 0x0bda #define USB_VID_TECHNOTREND 0x0b48 #define USB_VID_TERRATEC 0x0ccd #define USB_VID_TELESTAR 0x10b9 @@ -80,6 +81,7 @@ #define USB_PID_ANSONIC_DVBT_USB 0x6000 #define USB_PID_ANYSEE 0x861f #define USB_PID_AZUREWAVE_AD_TU700 0x3237 +#define USB_PID_AZUREWAVE_6007 0x0ccd #define USB_PID_AVERMEDIA_DVBT_USB_COLD 0x0001 #define USB_PID_AVERMEDIA_DVBT_USB_WARM 0x0002 #define USB_PID_AVERMEDIA_DVBT_USB2_COLD 0xa800 @@ -125,6 +127,8 @@ #define USB_PID_E3C_EC168_3 0xfffb #define USB_PID_E3C_EC168_4 0x1001 #define USB_PID_E3C_EC168_5 0x1002 +#define USB_PID_FREECOM_DVBT 0x0160 +#define USB_PID_FREECOM_DVBT_2 0x0161 #define USB_PID_UNIWILL_STK7700P 0x6003 #define USB_PID_GENIUS_TVGO_DVB_T03 0x4012 #define USB_PID_GRANDTEC_DVBT_USB_COLD 0x0fa0 @@ -226,6 +230,8 @@ #define USB_PID_TERRATEC_CINERGY_T_EXPRESS 0x0062 #define USB_PID_TERRATEC_CINERGY_T_XXS 0x0078 #define USB_PID_TERRATEC_CINERGY_T_XXS_2 0x00ab +#define USB_PID_TERRATEC_H7 0x10b4 +#define USB_PID_TERRATEC_H7_2 0x10a3 #define USB_PID_TERRATEC_T3 0x10a0 #define USB_PID_TERRATEC_T5 0x10a1 #define USB_PID_PINNACLE_EXPRESSCARD_320CX 0x022e @@ -249,6 +255,8 @@ #define USB_PID_PCTV_400E 0x020f #define USB_PID_PCTV_450E 0x0222 #define USB_PID_PCTV_452E 0x021f +#define USB_PID_REALTEK_RTL2831U 0x2831 +#define USB_PID_REALTEK_RTL2832U 0x2832 #define USB_PID_TECHNOTREND_CONNECT_S2_3600 0x3007 #define USB_PID_TECHNOTREND_CONNECT_S2_3650_CI 0x300a #define USB_PID_NEBULA_DIGITV 0x0201 diff --git a/drivers/media/dvb/dvb-usb/it913x.c b/drivers/media/dvb/dvb-usb/it913x.c index 9f01cd7a6e3f..3b7b102f20ae 100644 --- a/drivers/media/dvb/dvb-usb/it913x.c +++ b/drivers/media/dvb/dvb-usb/it913x.c @@ -64,6 +64,7 @@ DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); struct it913x_state { u8 id; struct ite_config it913x_config; + u8 pid_filter_onoff; }; struct ite_config it913x_config; @@ -259,15 +260,16 @@ static u32 it913x_query(struct usb_device *udev, u8 pro) static int it913x_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) { + struct it913x_state *st = adap->dev->priv; struct usb_device *udev = adap->dev->udev; int ret; u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; - if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0) - return -EAGAIN; + mutex_lock(&adap->dev->i2c_mutex); + deb_info(1, "PID_C (%02x)", onoff); - ret = it913x_wr_reg(udev, pro, PID_EN, onoff); + ret = it913x_wr_reg(udev, pro, PID_EN, st->pid_filter_onoff); mutex_unlock(&adap->dev->i2c_mutex); return ret; @@ -276,12 +278,13 @@ static int it913x_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) static int it913x_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, int onoff) { + struct it913x_state *st = adap->dev->priv; struct usb_device *udev = adap->dev->udev; int ret; u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; - if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0) - return -EAGAIN; + mutex_lock(&adap->dev->i2c_mutex); + deb_info(1, "PID_F (%02x)", onoff); ret = it913x_wr_reg(udev, pro, PID_LSB, (u8)(pid & 0xff)); @@ -292,6 +295,13 @@ static int it913x_pid_filter(struct dvb_usb_adapter *adap, ret |= it913x_wr_reg(udev, pro, PID_INX, (u8)(index & 0x1f)); + if (udev->speed == USB_SPEED_HIGH && pid == 0x2000) { + ret |= it913x_wr_reg(udev, pro, PID_EN, !onoff); + st->pid_filter_onoff = !onoff; + } else + st->pid_filter_onoff = + adap->fe_adap[adap->active_fe].pid_filtering; + mutex_unlock(&adap->dev->i2c_mutex); return 0; } @@ -316,8 +326,8 @@ static int it913x_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], int ret; u32 reg; u8 pro; - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; + + mutex_lock(&d->i2c_mutex); debug_data_snipet(1, "Message out", msg[0].buf); deb_info(2, "num of messages %d address %02x", num, msg[0].addr); @@ -358,8 +368,7 @@ static int it913x_rc_query(struct dvb_usb_device *d) int ret; u32 key; /* Avoid conflict with frontends*/ - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; + mutex_lock(&d->i2c_mutex); ret = it913x_io(d->udev, READ_LONG, PRO_LINK, CMD_IR_GET, 0, 0, &ibuf[0], sizeof(ibuf)); @@ -388,19 +397,12 @@ static int ite_firmware_select(struct usb_device *udev, { int sw; /* auto switch */ - if (le16_to_cpu(udev->descriptor.idProduct) == - USB_PID_ITETECH_IT9135) - sw = IT9135_V1_FW; - else if (le16_to_cpu(udev->descriptor.idProduct) == - USB_PID_ITETECH_IT9135_9005) + if (le16_to_cpu(udev->descriptor.idVendor) == USB_VID_KWORLD_2) + sw = IT9137_FW; + else if (it913x_config.chip_ver == 1) sw = IT9135_V1_FW; - else if (le16_to_cpu(udev->descriptor.idProduct) == - USB_PID_ITETECH_IT9135_9006) { + else sw = IT9135_V2_FW; - if (it913x_config.tuner_id_0 == 0) - it913x_config.tuner_id_0 = IT9135_60; - } else - sw = IT9137_FW; /* force switch */ if (dvb_usb_it913x_firmware != IT9135_AUTO) @@ -410,41 +412,103 @@ static int ite_firmware_select(struct usb_device *udev, case IT9135_V1_FW: it913x_config.firmware_ver = 1; it913x_config.adc_x2 = 1; + it913x_config.read_slevel = false; props->firmware = fw_it9135_v1; break; case IT9135_V2_FW: it913x_config.firmware_ver = 1; it913x_config.adc_x2 = 1; + it913x_config.read_slevel = false; props->firmware = fw_it9135_v2; + switch (it913x_config.tuner_id_0) { + case IT9135_61: + case IT9135_62: + break; + default: + info("Unknown tuner ID applying default 0x60"); + case IT9135_60: + it913x_config.tuner_id_0 = IT9135_60; + } break; case IT9137_FW: default: it913x_config.firmware_ver = 0; it913x_config.adc_x2 = 0; + it913x_config.read_slevel = true; props->firmware = fw_it9137; } return 0; } +static void it913x_select_remote(struct usb_device *udev, + struct dvb_usb_device_properties *props) +{ + switch (le16_to_cpu(udev->descriptor.idProduct)) { + case USB_PID_ITETECH_IT9135_9005: + props->rc.core.rc_codes = RC_MAP_IT913X_V2; + return; + default: + props->rc.core.rc_codes = RC_MAP_IT913X_V1; + } + return; +} + #define TS_MPEG_PKT_SIZE 188 #define EP_LOW 21 #define TS_BUFFER_SIZE_PID (EP_LOW*TS_MPEG_PKT_SIZE) #define EP_HIGH 348 #define TS_BUFFER_SIZE_MAX (EP_HIGH*TS_MPEG_PKT_SIZE) -static int it913x_identify_state(struct usb_device *udev, - struct dvb_usb_device_properties *props, - struct dvb_usb_device_description **desc, - int *cold) +static int it913x_select_config(struct usb_device *udev, + struct dvb_usb_device_properties *props) { - int ret = 0, firm_no; - u8 reg, remote; + int ret = 0, reg; + bool proprietary_ir = false; - firm_no = it913x_return_status(udev); + if (it913x_config.chip_ver == 0x02 + && it913x_config.chip_type == 0x9135) + reg = it913x_read_reg(udev, 0x461d); + else + reg = it913x_read_reg(udev, 0x461b); - /* checnk for dual mode */ - it913x_config.dual_mode = it913x_read_reg(udev, 0x49c5); + if (reg < 0) + return reg; + + if (reg == 0) { + it913x_config.dual_mode = 0; + it913x_config.tuner_id_0 = IT9135_38; + proprietary_ir = true; + } else { + /* TS mode */ + reg = it913x_read_reg(udev, 0x49c5); + if (reg < 0) + return reg; + it913x_config.dual_mode = reg; + + /* IR mode type */ + reg = it913x_read_reg(udev, 0x49ac); + if (reg < 0) + return reg; + if (reg == 5) { + info("Remote propriety (raw) mode"); + proprietary_ir = true; + } else if (reg == 1) { + info("Remote HID mode NOT SUPPORTED"); + proprietary_ir = false; + props->rc.core.rc_codes = NULL; + } else + props->rc.core.rc_codes = NULL; + + /* Tuner_id */ + reg = it913x_read_reg(udev, 0x49d0); + if (reg < 0) + return reg; + it913x_config.tuner_id_0 = reg; + } + + if (proprietary_ir) + it913x_select_remote(udev, props); if (udev->speed != USB_SPEED_HIGH) { props->adapter[0].fe[0].pid_filter_count = 5; @@ -459,17 +523,6 @@ static int it913x_identify_state(struct usb_device *udev, if(props->adapter[0].fe[0].pid_filter_count == 5) props->adapter[0].fe[0].pid_filter_count = 31; - /* TODO different remotes */ - remote = it913x_read_reg(udev, 0x49ac); /* Remote */ - if (remote == 0) - props->rc.core.rc_codes = NULL; - - /* TODO at the moment tuner_id is always assigned to 0x38 */ - it913x_config.tuner_id_0 = it913x_read_reg(udev, 0x49d0); - - info("Dual mode=%x Remote=%x Tuner Type=%x", it913x_config.dual_mode - , remote, it913x_config.tuner_id_0); - /* Select Stream Buffer Size and pid filter option*/ if (pid_filter) { props->adapter[0].fe[0].stream.u.bulk.buffersize = @@ -490,8 +543,29 @@ static int it913x_identify_state(struct usb_device *udev, } else props->num_adapters = 1; + info("Dual mode=%x Tuner Type=%x", it913x_config.dual_mode, + it913x_config.tuner_id_0); + ret = ite_firmware_select(udev, props); + return ret; +} + +static int it913x_identify_state(struct usb_device *udev, + struct dvb_usb_device_properties *props, + struct dvb_usb_device_description **desc, + int *cold) +{ + int ret = 0, firm_no; + u8 reg; + + firm_no = it913x_return_status(udev); + + /* Read and select config */ + ret = it913x_select_config(udev, props); + if (ret < 0) + return ret; + if (firm_no > 0) { *cold = 0; return 0; @@ -538,18 +612,22 @@ static int it913x_identify_state(struct usb_device *udev, static int it913x_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) { + struct it913x_state *st = adap->dev->priv; int ret = 0; u8 pro = (adap->id == 0) ? DEV_0_DMOD : DEV_1_DMOD; - if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0) - return -EAGAIN; deb_info(1, "STM (%02x)", onoff); - if (!onoff) + if (!onoff) { + mutex_lock(&adap->dev->i2c_mutex); + ret = it913x_wr_reg(adap->dev->udev, pro, PID_RST, 0x1); + mutex_unlock(&adap->dev->i2c_mutex); + st->pid_filter_onoff = + adap->fe_adap[adap->active_fe].pid_filtering; - mutex_unlock(&adap->dev->i2c_mutex); + } return ret; } @@ -789,7 +867,7 @@ static struct dvb_usb_device_properties it913x_properties = { .rc_query = it913x_rc_query, .rc_interval = IT913X_POLL, .allowed_protos = RC_TYPE_NEC, - .rc_codes = RC_MAP_MSI_DIGIVOX_III, + .rc_codes = RC_MAP_IT913X_V1, }, .i2c_algo = &it913x_i2c_algo, .num_device_descs = 5, @@ -823,5 +901,5 @@ module_usb_driver(it913x_driver); MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>"); MODULE_DESCRIPTION("it913x USB 2 Driver"); -MODULE_VERSION("1.22"); +MODULE_VERSION("1.27"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/lmedm04.c b/drivers/media/dvb/dvb-usb/lmedm04.c index 291f6b110399..5dde06d066ff 100644 --- a/drivers/media/dvb/dvb-usb/lmedm04.c +++ b/drivers/media/dvb/dvb-usb/lmedm04.c @@ -77,6 +77,7 @@ #include "stv0299.h" #include "dvb-pll.h" #include "z0194a.h" +#include "m88rs2000.h" @@ -104,7 +105,7 @@ MODULE_PARM_DESC(firmware, "set default firmware 0=Sharp7395 1=LG"); static int pid_filter; module_param_named(pid, pid_filter, int, 0644); -MODULE_PARM_DESC(pid, "set default 0=on 1=off"); +MODULE_PARM_DESC(pid, "set default 0=default 1=off 2=on"); DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); @@ -113,6 +114,7 @@ DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); #define TUNER_LG 0x1 #define TUNER_S7395 0x2 #define TUNER_S0194 0x3 +#define TUNER_RS2000 0x4 struct lme2510_state { u8 id; @@ -121,6 +123,8 @@ struct lme2510_state { u8 signal_level; u8 signal_sn; u8 time_key; + u8 last_key; + u8 key_timeout; u8 i2c_talk_onoff; u8 i2c_gate; u8 i2c_tuner_gate_w; @@ -128,6 +132,7 @@ struct lme2510_state { u8 i2c_tuner_addr; u8 stream_on; u8 pid_size; + u8 pid_off; void *buffer; struct urb *lme_urb; void *usb_buffer; @@ -178,14 +183,8 @@ static int lme2510_usb_talk(struct dvb_usb_device *d, /* the read/write capped at 64 */ memcpy(buff, wbuf, (wlen < 64) ? wlen : 64); - ret |= usb_clear_halt(d->udev, usb_sndbulkpipe(d->udev, 0x01)); - ret |= lme2510_bulk_write(d->udev, buff, wlen , 0x01); - msleep(10); - - ret |= usb_clear_halt(d->udev, usb_rcvbulkpipe(d->udev, 0x01)); - ret |= lme2510_bulk_read(d->udev, buff, (rlen < 64) ? rlen : 64 , 0x01); @@ -199,9 +198,14 @@ static int lme2510_usb_talk(struct dvb_usb_device *d, static int lme2510_stream_restart(struct dvb_usb_device *d) { - static u8 stream_on[] = LME_ST_ON_W; + struct lme2510_state *st = d->priv; + u8 all_pids[] = LME_ALL_PIDS; + u8 stream_on[] = LME_ST_ON_W; int ret; - u8 rbuff[10]; + u8 rbuff[1]; + if (st->pid_off) + ret = lme2510_usb_talk(d, all_pids, sizeof(all_pids), + rbuff, sizeof(rbuff)); /*Restart Stream Command*/ ret = lme2510_usb_talk(d, stream_on, sizeof(stream_on), rbuff, sizeof(rbuff)); @@ -308,6 +312,14 @@ static void lme2510_int_response(struct urb *lme_urb) ((ibuf[2] & 0x01) << 0x03); } break; + case TUNER_RS2000: + if (ibuf[2] > 0) + st->signal_lock = 0xff; + else + st->signal_lock = 0xf0; + st->signal_level = ibuf[4]; + st->signal_sn = ibuf[5]; + st->time_key = ibuf[7]; default: break; } @@ -359,19 +371,20 @@ static int lme2510_int_read(struct dvb_usb_adapter *adap) static int lme2510_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff) { struct lme2510_state *st = adap->dev->priv; - static u8 clear_pid_reg[] = LME_CLEAR_PID; + static u8 clear_pid_reg[] = LME_ALL_PIDS; static u8 rbuf[1]; int ret; deb_info(1, "PID Clearing Filter"); - ret = mutex_lock_interruptible(&adap->dev->i2c_mutex); - if (ret < 0) - return -EAGAIN; + mutex_lock(&adap->dev->i2c_mutex); - if (!onoff) + if (!onoff) { ret |= lme2510_usb_talk(adap->dev, clear_pid_reg, sizeof(clear_pid_reg), rbuf, sizeof(rbuf)); + st->pid_off = true; + } else + st->pid_off = false; st->pid_size = 0; @@ -389,11 +402,9 @@ static int lme2510_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, pid, index, onoff); if (onoff) { - ret = mutex_lock_interruptible(&adap->dev->i2c_mutex); - if (ret < 0) - return -EAGAIN; - ret |= lme2510_enable_pid(adap->dev, index, pid); - mutex_unlock(&adap->dev->i2c_mutex); + mutex_lock(&adap->dev->i2c_mutex); + ret |= lme2510_enable_pid(adap->dev, index, pid); + mutex_unlock(&adap->dev->i2c_mutex); } @@ -425,9 +436,6 @@ static int lme2510_msg(struct dvb_usb_device *d, int ret = 0; struct lme2510_state *st = d->priv; - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; - if (st->i2c_talk_onoff == 1) { ret = lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); @@ -456,8 +464,6 @@ static int lme2510_msg(struct dvb_usb_device *d, st->i2c_talk_onoff = 0; } } - if ((wbuf[3] != 0x6) & (wbuf[3] != 0x5)) - msleep(5); } break; case TUNER_S0194: @@ -472,10 +478,12 @@ static int lme2510_msg(struct dvb_usb_device *d, } } break; + case TUNER_RS2000: default: break; } } else { + /* TODO rewrite this section */ switch (st->tuner_config) { case TUNER_LG: switch (wbuf[3]) { @@ -559,6 +567,24 @@ static int lme2510_msg(struct dvb_usb_device *d, break; } break; + case TUNER_RS2000: + switch (wbuf[3]) { + case 0x8c: + rbuf[0] = 0x55; + rbuf[1] = 0xff; + if (st->last_key == st->time_key) { + st->key_timeout++; + if (st->key_timeout > 5) + rbuf[1] = 0; + } else + st->key_timeout = 0; + st->last_key = st->time_key; + break; + default: + lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen); + st->i2c_talk_onoff = 1; + break; + } default: break; } @@ -568,8 +594,6 @@ static int lme2510_msg(struct dvb_usb_device *d, } - mutex_unlock(&d->i2c_mutex); - return ret; } @@ -584,6 +608,8 @@ static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], u16 len; u8 gate = st->i2c_gate; + mutex_lock(&d->i2c_mutex); + if (gate == 0) gate = 5; @@ -622,6 +648,7 @@ static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], if (lme2510_msg(d, obuf, len, ibuf, 64) < 0) { deb_info(1, "i2c transfer failed."); + mutex_unlock(&d->i2c_mutex); return -EAGAIN; } @@ -634,6 +661,8 @@ static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], } } } + + mutex_unlock(&d->i2c_mutex); return i; } @@ -653,7 +682,7 @@ static int lme2510_identify_state(struct usb_device *udev, struct dvb_usb_device_description **desc, int *cold) { - if (pid_filter > 0) + if (pid_filter != 2) props->adapter[0].fe[0].caps &= ~DVB_USB_ADAP_NEED_PID_FILTERING; *cold = 0; @@ -663,7 +692,7 @@ static int lme2510_identify_state(struct usb_device *udev, static int lme2510_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) { struct lme2510_state *st = adap->dev->priv; - static u8 clear_reg_3[] = LME_CLEAR_PID; + static u8 clear_reg_3[] = LME_ALL_PIDS; static u8 rbuf[1]; int ret = 0, rlen = sizeof(rbuf); @@ -675,8 +704,7 @@ static int lme2510_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) else { deb_info(1, "STM Steam Off"); /* mutex is here only to avoid collision with I2C */ - if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0) - return -EAGAIN; + mutex_lock(&adap->dev->i2c_mutex); ret = lme2510_usb_talk(adap->dev, clear_reg_3, sizeof(clear_reg_3), rbuf, rlen); @@ -781,16 +809,18 @@ static int lme_firmware_switch(struct usb_device *udev, int cold) const char fw_c_s7395[] = "dvb-usb-lme2510c-s7395.fw"; const char fw_c_lg[] = "dvb-usb-lme2510c-lg.fw"; const char fw_c_s0194[] = "dvb-usb-lme2510c-s0194.fw"; + const char fw_c_rs2000[] = "dvb-usb-lme2510c-rs2000.fw"; const char fw_lg[] = "dvb-usb-lme2510-lg.fw"; const char fw_s0194[] = "dvb-usb-lme2510-s0194.fw"; const char *fw_lme; - int ret, cold_fw; + int ret = 0, cold_fw; cold = (cold > 0) ? (cold & 1) : 0; cold_fw = !cold; - if (le16_to_cpu(udev->descriptor.idProduct) == 0x1122) { + switch (le16_to_cpu(udev->descriptor.idProduct)) { + case 0x1122: switch (dvb_usb_lme2510_firmware) { default: dvb_usb_lme2510_firmware = TUNER_S0194; @@ -813,7 +843,8 @@ static int lme_firmware_switch(struct usb_device *udev, int cold) cold_fw = 0; break; } - } else { + break; + case 0x1120: switch (dvb_usb_lme2510_firmware) { default: dvb_usb_lme2510_firmware = TUNER_S7395; @@ -842,8 +873,17 @@ static int lme_firmware_switch(struct usb_device *udev, int cold) cold_fw = 0; break; } + break; + case 0x22f0: + fw_lme = fw_c_rs2000; + ret = request_firmware(&fw, fw_lme, &udev->dev); + dvb_usb_lme2510_firmware = TUNER_RS2000; + break; + default: + fw_lme = fw_c_s7395; } + if (cold_fw) { info("FRM Loading %s file", fw_lme); ret = lme2510_download_firmware(udev, fw); @@ -906,6 +946,29 @@ static struct stv0299_config sharp_z0194_config = { .set_symbol_rate = sharp_z0194a_set_symbol_rate, }; +static int dm04_rs2000_set_ts_param(struct dvb_frontend *fe, + int caller) +{ + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dvb_usb_device *d = adap->dev; + struct lme2510_state *st = d->priv; + + mutex_lock(&d->i2c_mutex); + if ((st->i2c_talk_onoff == 1) && (st->stream_on & 1)) { + st->i2c_talk_onoff = 0; + lme2510_stream_restart(d); + } + mutex_unlock(&d->i2c_mutex); + + return 0; +} + +static struct m88rs2000_config m88rs2000_config = { + .demod_addr = 0xd0, + .tuner_addr = 0xc0, + .set_ts_params = dm04_rs2000_set_ts_param, +}; + static int dm04_lme2510_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) { @@ -915,8 +978,7 @@ static int dm04_lme2510_set_voltage(struct dvb_frontend *fe, static u8 rbuf[1]; int ret = 0, len = 3, rlen = 1; - if (mutex_lock_interruptible(&adap->dev->i2c_mutex) < 0) - return -EAGAIN; + mutex_lock(&adap->dev->i2c_mutex); switch (voltage) { case SEC_VOLTAGE_18: @@ -937,12 +999,31 @@ static int dm04_lme2510_set_voltage(struct dvb_frontend *fe, return (ret < 0) ? -ENODEV : 0; } +static int dm04_rs2000_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct lme2510_state *st = adap->dev->priv; + + *strength = (u16)((u32)st->signal_level * 0xffff / 0x7f); + return 0; +} + +static int dm04_rs2000_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct lme2510_state *st = adap->dev->priv; + + *snr = (u16)((u32)st->signal_sn * 0xffff / 0xff); + return 0; +} + static int lme_name(struct dvb_usb_adapter *adap) { struct lme2510_state *st = adap->dev->priv; const char *desc = adap->dev->desc->name; char *fe_name[] = {"", " LG TDQY-P001F", " SHARP:BS2F7HZ7395", - " SHARP:BS2F7HZ0194"}; + " SHARP:BS2F7HZ0194", " RS2000"}; char *name = adap->fe_adap[0].fe->ops.info.name; strlcpy(name, desc, 128); @@ -958,60 +1039,82 @@ static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap) int ret = 0; st->i2c_talk_onoff = 1; + switch (le16_to_cpu(adap->dev->udev->descriptor.idProduct)) { + case 0x1122: + case 0x1120: + st->i2c_gate = 4; + adap->fe_adap[0].fe = dvb_attach(tda10086_attach, + &tda10086_config, &adap->dev->i2c_adap); + if (adap->fe_adap[0].fe) { + info("TUN Found Frontend TDA10086"); + st->i2c_tuner_gate_w = 4; + st->i2c_tuner_gate_r = 4; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_LG; + if (dvb_usb_lme2510_firmware != TUNER_LG) { + dvb_usb_lme2510_firmware = TUNER_LG; + ret = lme_firmware_switch(adap->dev->udev, 1); + } + break; + } - st->i2c_gate = 4; - adap->fe_adap[0].fe = dvb_attach(tda10086_attach, &tda10086_config, - &adap->dev->i2c_adap); - - if (adap->fe_adap[0].fe) { - info("TUN Found Frontend TDA10086"); - st->i2c_tuner_gate_w = 4; - st->i2c_tuner_gate_r = 4; - st->i2c_tuner_addr = 0xc0; - st->tuner_config = TUNER_LG; - if (dvb_usb_lme2510_firmware != TUNER_LG) { - dvb_usb_lme2510_firmware = TUNER_LG; - ret = lme_firmware_switch(adap->dev->udev, 1); + st->i2c_gate = 4; + adap->fe_adap[0].fe = dvb_attach(stv0299_attach, + &sharp_z0194_config, &adap->dev->i2c_adap); + if (adap->fe_adap[0].fe) { + info("FE Found Stv0299"); + st->i2c_tuner_gate_w = 4; + st->i2c_tuner_gate_r = 5; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_S0194; + if (dvb_usb_lme2510_firmware != TUNER_S0194) { + dvb_usb_lme2510_firmware = TUNER_S0194; + ret = lme_firmware_switch(adap->dev->udev, 1); + } + break; } - goto end; - } - st->i2c_gate = 4; - adap->fe_adap[0].fe = dvb_attach(stv0299_attach, &sharp_z0194_config, + st->i2c_gate = 5; + adap->fe_adap[0].fe = dvb_attach(stv0288_attach, &lme_config, &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe) { - info("FE Found Stv0299"); - st->i2c_tuner_gate_w = 4; - st->i2c_tuner_gate_r = 5; - st->i2c_tuner_addr = 0xc0; - st->tuner_config = TUNER_S0194; - if (dvb_usb_lme2510_firmware != TUNER_S0194) { - dvb_usb_lme2510_firmware = TUNER_S0194; - ret = lme_firmware_switch(adap->dev->udev, 1); + + if (adap->fe_adap[0].fe) { + info("FE Found Stv0288"); + st->i2c_tuner_gate_w = 4; + st->i2c_tuner_gate_r = 5; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_S7395; + if (dvb_usb_lme2510_firmware != TUNER_S7395) { + dvb_usb_lme2510_firmware = TUNER_S7395; + ret = lme_firmware_switch(adap->dev->udev, 1); + } + break; } - goto end; - } + case 0x22f0: + st->i2c_gate = 5; + adap->fe_adap[0].fe = dvb_attach(m88rs2000_attach, + &m88rs2000_config, &adap->dev->i2c_adap); - st->i2c_gate = 5; - adap->fe_adap[0].fe = dvb_attach(stv0288_attach, &lme_config, - &adap->dev->i2c_adap); - if (adap->fe_adap[0].fe) { - info("FE Found Stv0288"); - st->i2c_tuner_gate_w = 4; - st->i2c_tuner_gate_r = 5; - st->i2c_tuner_addr = 0xc0; - st->tuner_config = TUNER_S7395; - if (dvb_usb_lme2510_firmware != TUNER_S7395) { - dvb_usb_lme2510_firmware = TUNER_S7395; - ret = lme_firmware_switch(adap->dev->udev, 1); + if (adap->fe_adap[0].fe) { + info("FE Found M88RS2000"); + st->i2c_tuner_gate_w = 5; + st->i2c_tuner_gate_r = 5; + st->i2c_tuner_addr = 0xc0; + st->tuner_config = TUNER_RS2000; + adap->fe_adap[0].fe->ops.read_signal_strength = + dm04_rs2000_read_signal_strength; + adap->fe_adap[0].fe->ops.read_snr = + dm04_rs2000_read_snr; } - } else { - info("DM04 Not Supported"); - return -ENODEV; + break; } + if (adap->fe_adap[0].fe == NULL) { + info("DM04/QQBOX Not Powered up or not Supported"); + return -ENODEV; + } -end: if (ret) { + if (ret) { if (adap->fe_adap[0].fe) { dvb_frontend_detach(adap->fe_adap[0].fe); adap->fe_adap[0].fe = NULL; @@ -1028,7 +1131,7 @@ end: if (ret) { static int dm04_lme2510_tuner(struct dvb_usb_adapter *adap) { struct lme2510_state *st = adap->dev->priv; - char *tun_msg[] = {"", "TDA8263", "IX2505V", "DVB_PLL_OPERA"}; + char *tun_msg[] = {"", "TDA8263", "IX2505V", "DVB_PLL_OPERA", "RS2000"}; int ret = 0; switch (st->tuner_config) { @@ -1047,6 +1150,9 @@ static int dm04_lme2510_tuner(struct dvb_usb_adapter *adap) &adap->dev->i2c_adap, DVB_PLL_OPERA1)) ret = st->tuner_config; break; + case TUNER_RS2000: + ret = st->tuner_config; + break; default: break; } @@ -1075,10 +1181,9 @@ static int lme2510_powerup(struct dvb_usb_device *d, int onoff) static u8 lnb_on[] = LNB_ON; static u8 lnb_off[] = LNB_OFF; static u8 rbuf[1]; - int ret, len = 3, rlen = 1; + int ret = 0, len = 3, rlen = 1; - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; + mutex_lock(&d->i2c_mutex); if (onoff) ret = lme2510_usb_talk(d, lnb_on, len, rbuf, rlen); @@ -1136,6 +1241,7 @@ static int lme2510_probe(struct usb_interface *intf, static struct usb_device_id lme2510_table[] = { { USB_DEVICE(0x3344, 0x1122) }, /* LME2510 */ { USB_DEVICE(0x3344, 0x1120) }, /* LME2510C */ + { USB_DEVICE(0x3344, 0x22f0) }, /* LME2510C RS2000 */ {} /* Terminating entry */ }; @@ -1153,7 +1259,7 @@ static struct dvb_usb_device_properties lme2510_properties = { DVB_USB_ADAP_NEED_PID_FILTERING| DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, .streaming_ctrl = lme2510_streaming_ctrl, - .pid_filter_count = 15, + .pid_filter_count = 32, .pid_filter = lme2510_pid_filter, .pid_filter_ctrl = lme2510_pid_filter_ctrl, .frontend_attach = dm04_lme2510_frontend_attach, @@ -1204,7 +1310,7 @@ static struct dvb_usb_device_properties lme2510c_properties = { DVB_USB_ADAP_NEED_PID_FILTERING| DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, .streaming_ctrl = lme2510_streaming_ctrl, - .pid_filter_count = 15, + .pid_filter_count = 32, .pid_filter = lme2510_pid_filter, .pid_filter_ctrl = lme2510_pid_filter_ctrl, .frontend_attach = dm04_lme2510_frontend_attach, @@ -1234,11 +1340,14 @@ static struct dvb_usb_device_properties lme2510c_properties = { .identify_state = lme2510_identify_state, .i2c_algo = &lme2510_i2c_algo, .generic_bulk_ctrl_endpoint = 0, - .num_device_descs = 1, + .num_device_descs = 2, .devices = { { "DM04_LME2510C_DVB-S", { &lme2510_table[1], NULL }, }, + { "DM04_LME2510C_DVB-S RS2000", + { &lme2510_table[2], NULL }, + }, } }; @@ -1295,5 +1404,5 @@ module_usb_driver(lme2510_driver); MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>"); MODULE_DESCRIPTION("LME2510(C) DVB-S USB2.0"); -MODULE_VERSION("1.91"); +MODULE_VERSION("1.99"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/lmedm04.h b/drivers/media/dvb/dvb-usb/lmedm04.h index ab21e2ef53fa..e9c207205c2f 100644 --- a/drivers/media/dvb/dvb-usb/lmedm04.h +++ b/drivers/media/dvb/dvb-usb/lmedm04.h @@ -41,6 +41,7 @@ #define LME_ST_ON_W {0x06, 0x00} #define LME_CLEAR_PID {0x03, 0x02, 0x20, 0xa0} #define LME_ZERO_PID {0x03, 0x06, 0x00, 0x00, 0x01, 0x00, 0x20, 0x9c} +#define LME_ALL_PIDS {0x03, 0x06, 0x00, 0xff, 0x01, 0x1f, 0x20, 0x81} /* LNB Voltage * 07 XX XX diff --git a/drivers/media/dvb/dvb-usb/mxl111sf.c b/drivers/media/dvb/dvb-usb/mxl111sf.c index 38ef0253d3b5..81305de2fea5 100644 --- a/drivers/media/dvb/dvb-usb/mxl111sf.c +++ b/drivers/media/dvb/dvb-usb/mxl111sf.c @@ -351,15 +351,13 @@ static int mxl111sf_ep6_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) adap_state->ep6_clockphase, 0, 0); mxl_fail(ret); +#if 0 } else { ret = mxl111sf_disable_656_port(state); mxl_fail(ret); +#endif } - mxl111sf_read_reg(state, 0x12, &tmp); - tmp &= ~0x04; - mxl111sf_write_reg(state, 0x12, tmp); - return ret; } diff --git a/drivers/media/dvb/dvb-usb/rtl28xxu.c b/drivers/media/dvb/dvb-usb/rtl28xxu.c new file mode 100644 index 000000000000..8f4736a10fc8 --- /dev/null +++ b/drivers/media/dvb/dvb-usb/rtl28xxu.c @@ -0,0 +1,982 @@ +/* + * Realtek RTL28xxU DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "rtl28xxu.h" + +#include "rtl2830.h" + +#include "qt1010.h" +#include "mt2060.h" +#include "mxl5005s.h" + +/* debug */ +static int dvb_usb_rtl28xxu_debug; +module_param_named(debug, dvb_usb_rtl28xxu_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS); +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int rtl28xxu_ctrl_msg(struct dvb_usb_device *d, struct rtl28xxu_req *req) +{ + int ret; + unsigned int pipe; + u8 requesttype; + u8 *buf; + + buf = kmalloc(req->size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err; + } + + if (req->index & CMD_WR_FLAG) { + /* write */ + memcpy(buf, req->data, req->size); + requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); + pipe = usb_sndctrlpipe(d->udev, 0); + } else { + /* read */ + requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); + pipe = usb_rcvctrlpipe(d->udev, 0); + } + + ret = usb_control_msg(d->udev, pipe, 0, requesttype, req->value, + req->index, buf, req->size, 1000); + if (ret > 0) + ret = 0; + + deb_dump(0, requesttype, req->value, req->index, buf, req->size, + deb_xfer); + + /* read request, copy returned data to return buf */ + if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) + memcpy(req->data, buf, req->size); + + kfree(buf); + + if (ret) + goto err; + + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2831_wr_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len) +{ + struct rtl28xxu_req req; + + if (reg < 0x3000) + req.index = CMD_USB_WR; + else if (reg < 0x4000) + req.index = CMD_SYS_WR; + else + req.index = CMD_IR_WR; + + req.value = reg; + req.size = len; + req.data = val; + + return rtl28xxu_ctrl_msg(d, &req); +} + +static int rtl2831_rd_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len) +{ + struct rtl28xxu_req req; + + if (reg < 0x3000) + req.index = CMD_USB_RD; + else if (reg < 0x4000) + req.index = CMD_SYS_RD; + else + req.index = CMD_IR_RD; + + req.value = reg; + req.size = len; + req.data = val; + + return rtl28xxu_ctrl_msg(d, &req); +} + +static int rtl2831_wr_reg(struct dvb_usb_device *d, u16 reg, u8 val) +{ + return rtl2831_wr_regs(d, reg, &val, 1); +} + +static int rtl2831_rd_reg(struct dvb_usb_device *d, u16 reg, u8 *val) +{ + return rtl2831_rd_regs(d, reg, val, 1); +} + +/* I2C */ +static int rtl28xxu_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], + int num) +{ + int ret; + struct dvb_usb_device *d = i2c_get_adapdata(adap); + struct rtl28xxu_priv *priv = d->priv; + struct rtl28xxu_req req; + + /* + * It is not known which are real I2C bus xfer limits, but testing + * with RTL2831U + MT2060 gives max RD 24 and max WR 22 bytes. + * TODO: find out RTL2832U lens + */ + + /* + * I2C adapter logic looks rather complicated due to fact it handles + * three different access methods. Those methods are; + * 1) integrated demod access + * 2) old I2C access + * 3) new I2C access + * + * Used method is selected in order 1, 2, 3. Method 3 can handle all + * requests but there is two reasons why not use it always; + * 1) It is most expensive, usually two USB messages are needed + * 2) At least RTL2831U does not support it + * + * Method 3 is needed in case of I2C write+read (typical register read) + * where write is more than one byte. + */ + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + if (num == 2 && !(msg[0].flags & I2C_M_RD) && + (msg[1].flags & I2C_M_RD)) { + if (msg[0].len > 24 || msg[1].len > 24) { + /* TODO: check msg[0].len max */ + ret = -EOPNOTSUPP; + goto err_mutex_unlock; + } else if (msg[0].addr == 0x10) { + /* method 1 - integrated demod */ + req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); + req.index = CMD_DEMOD_RD | priv->page; + req.size = msg[1].len; + req.data = &msg[1].buf[0]; + ret = rtl28xxu_ctrl_msg(d, &req); + } else if (msg[0].len < 2) { + /* method 2 - old I2C */ + req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); + req.index = CMD_I2C_RD; + req.size = msg[1].len; + req.data = &msg[1].buf[0]; + ret = rtl28xxu_ctrl_msg(d, &req); + } else { + /* method 3 - new I2C */ + req.value = (msg[0].addr << 1); + req.index = CMD_I2C_DA_WR; + req.size = msg[0].len; + req.data = msg[0].buf; + ret = rtl28xxu_ctrl_msg(d, &req); + if (ret) + goto err_mutex_unlock; + + req.value = (msg[0].addr << 1); + req.index = CMD_I2C_DA_RD; + req.size = msg[1].len; + req.data = msg[1].buf; + ret = rtl28xxu_ctrl_msg(d, &req); + } + } else if (num == 1 && !(msg[0].flags & I2C_M_RD)) { + if (msg[0].len > 22) { + /* TODO: check msg[0].len max */ + ret = -EOPNOTSUPP; + goto err_mutex_unlock; + } else if (msg[0].addr == 0x10) { + /* method 1 - integrated demod */ + if (msg[0].buf[0] == 0x00) { + /* save demod page for later demod access */ + priv->page = msg[0].buf[1]; + ret = 0; + } else { + req.value = (msg[0].buf[0] << 8) | + (msg[0].addr << 1); + req.index = CMD_DEMOD_WR | priv->page; + req.size = msg[0].len-1; + req.data = &msg[0].buf[1]; + ret = rtl28xxu_ctrl_msg(d, &req); + } + } else if (msg[0].len < 23) { + /* method 2 - old I2C */ + req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1); + req.index = CMD_I2C_WR; + req.size = msg[0].len-1; + req.data = &msg[0].buf[1]; + ret = rtl28xxu_ctrl_msg(d, &req); + } else { + /* method 3 - new I2C */ + req.value = (msg[0].addr << 1); + req.index = CMD_I2C_DA_WR; + req.size = msg[0].len; + req.data = msg[0].buf; + ret = rtl28xxu_ctrl_msg(d, &req); + } + } else { + ret = -EINVAL; + } + +err_mutex_unlock: + mutex_unlock(&d->i2c_mutex); + + return ret ? ret : num; +} + +static u32 rtl28xxu_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm rtl28xxu_i2c_algo = { + .master_xfer = rtl28xxu_i2c_xfer, + .functionality = rtl28xxu_i2c_func, +}; + +static struct rtl2830_config rtl28xxu_rtl2830_mt2060_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .ts_mode = 0, + .spec_inv = 1, + .if_dvbt = 36150000, + .vtop = 0x20, + .krf = 0x04, + .agc_targ_val = 0x2d, + +}; + +static struct rtl2830_config rtl28xxu_rtl2830_qt1010_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .ts_mode = 0, + .spec_inv = 1, + .if_dvbt = 36125000, + .vtop = 0x20, + .krf = 0x04, + .agc_targ_val = 0x2d, +}; + +static struct rtl2830_config rtl28xxu_rtl2830_mxl5005s_config = { + .i2c_addr = 0x10, /* 0x20 */ + .xtal = 28800000, + .ts_mode = 0, + .spec_inv = 0, + .if_dvbt = 4570000, + .vtop = 0x3f, + .krf = 0x04, + .agc_targ_val = 0x3e, +}; + +static int rtl2831u_frontend_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct rtl28xxu_priv *priv = adap->dev->priv; + u8 buf[1]; + struct rtl2830_config *rtl2830_config; + /* open RTL2831U/RTL2830 I2C gate */ + struct rtl28xxu_req req_gate = { 0x0120, 0x0011, 0x0001, "\x08" }; + /* for MT2060 tuner probe */ + struct rtl28xxu_req req_mt2060 = { 0x00c0, CMD_I2C_RD, 1, buf }; + /* for QT1010 tuner probe */ + struct rtl28xxu_req req_qt1010 = { 0x0fc4, CMD_I2C_RD, 1, buf }; + + deb_info("%s:\n", __func__); + + /* + * RTL2831U GPIOs + * ========================================================= + * GPIO0 | tuner#0 | 0 off | 1 on | MXL5005S (?) + * GPIO2 | LED | 0 off | 1 on | + * GPIO4 | tuner#1 | 0 on | 1 off | MT2060 + */ + + /* GPIO direction */ + ret = rtl2831_wr_reg(adap->dev, SYS_GPIO_DIR, 0x0a); + if (ret) + goto err; + + /* enable as output GPIO0, GPIO2, GPIO4 */ + ret = rtl2831_wr_reg(adap->dev, SYS_GPIO_OUT_EN, 0x15); + if (ret) + goto err; + + /* + * Probe used tuner. We need to know used tuner before demod attach + * since there is some demod params needed to set according to tuner. + */ + + /* open demod I2C gate */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate); + if (ret) + goto err; + + /* check QT1010 ID(?) register; reg=0f val=2c */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_qt1010); + if (ret == 0 && buf[0] == 0x2c) { + priv->tuner = TUNER_RTL2830_QT1010; + rtl2830_config = &rtl28xxu_rtl2830_qt1010_config; + deb_info("%s: QT1010\n", __func__); + goto found; + } else { + deb_info("%s: QT1010 probe failed=%d - %02x\n", + __func__, ret, buf[0]); + } + + /* open demod I2C gate */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate); + if (ret) + goto err; + + /* check MT2060 ID register; reg=00 val=63 */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_mt2060); + if (ret == 0 && buf[0] == 0x63) { + priv->tuner = TUNER_RTL2830_MT2060; + rtl2830_config = &rtl28xxu_rtl2830_mt2060_config; + deb_info("%s: MT2060\n", __func__); + goto found; + } else { + deb_info("%s: MT2060 probe failed=%d - %02x\n", + __func__, ret, buf[0]); + } + + /* assume MXL5005S */ + ret = 0; + priv->tuner = TUNER_RTL2830_MXL5005S; + rtl2830_config = &rtl28xxu_rtl2830_mxl5005s_config; + deb_info("%s: MXL5005S\n", __func__); + goto found; + +found: + /* attach demodulator */ + adap->fe_adap[0].fe = dvb_attach(rtl2830_attach, rtl2830_config, + &adap->dev->i2c_adap); + if (adap->fe_adap[0].fe == NULL) { + ret = -ENODEV; + goto err; + } + + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_frontend_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct rtl28xxu_priv *priv = adap->dev->priv; + u8 buf[1]; + /* open RTL2832U/RTL2832 I2C gate */ + struct rtl28xxu_req req_gate_open = {0x0120, 0x0011, 0x0001, "\x18"}; + /* close RTL2832U/RTL2832 I2C gate */ + struct rtl28xxu_req req_gate_close = {0x0120, 0x0011, 0x0001, "\x10"}; + /* for FC2580 tuner probe */ + struct rtl28xxu_req req_fc2580 = {0x01ac, CMD_I2C_RD, 1, buf}; + + deb_info("%s:\n", __func__); + + /* GPIO direction */ + ret = rtl2831_wr_reg(adap->dev, SYS_GPIO_DIR, 0x0a); + if (ret) + goto err; + + /* enable as output GPIO0, GPIO2, GPIO4 */ + ret = rtl2831_wr_reg(adap->dev, SYS_GPIO_OUT_EN, 0x15); + if (ret) + goto err; + + ret = rtl2831_wr_reg(adap->dev, SYS_DEMOD_CTL, 0xe8); + if (ret) + goto err; + + /* + * Probe used tuner. We need to know used tuner before demod attach + * since there is some demod params needed to set according to tuner. + */ + + /* open demod I2C gate */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_open); + if (ret) + goto err; + + /* check FC2580 ID register; reg=01 val=56 */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_fc2580); + if (ret == 0 && buf[0] == 0x56) { + priv->tuner = TUNER_RTL2832_FC2580; + deb_info("%s: FC2580\n", __func__); + goto found; + } else { + deb_info("%s: FC2580 probe failed=%d - %02x\n", + __func__, ret, buf[0]); + } + + /* close demod I2C gate */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_close); + if (ret) + goto err; + + /* tuner not found */ + ret = -ENODEV; + goto err; + +found: + /* close demod I2C gate */ + ret = rtl28xxu_ctrl_msg(adap->dev, &req_gate_close); + if (ret) + goto err; + + /* attach demodulator */ + /* TODO: */ + + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct qt1010_config rtl28xxu_qt1010_config = { + .i2c_address = 0x62, /* 0xc4 */ +}; + +static struct mt2060_config rtl28xxu_mt2060_config = { + .i2c_address = 0x60, /* 0xc0 */ + .clock_out = 0, +}; + +static struct mxl5005s_config rtl28xxu_mxl5005s_config = { + .i2c_address = 0x63, /* 0xc6 */ + .if_freq = IF_FREQ_4570000HZ, + .xtal_freq = CRYSTAL_FREQ_16000000HZ, + .agc_mode = MXL_SINGLE_AGC, + .tracking_filter = MXL_TF_C_H, + .rssi_enable = MXL_RSSI_ENABLE, + .cap_select = MXL_CAP_SEL_ENABLE, + .div_out = MXL_DIV_OUT_4, + .clock_out = MXL_CLOCK_OUT_DISABLE, + .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, + .top = MXL5005S_TOP_25P2, + .mod_mode = MXL_DIGITAL_MODE, + .if_mode = MXL_ZERO_IF, + .AgcMasterByte = 0x00, +}; + +static int rtl2831u_tuner_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct rtl28xxu_priv *priv = adap->dev->priv; + struct i2c_adapter *rtl2830_tuner_i2c; + struct dvb_frontend *fe; + + deb_info("%s:\n", __func__); + + /* use rtl2830 driver I2C adapter, for more info see rtl2830 driver */ + rtl2830_tuner_i2c = rtl2830_get_tuner_i2c_adapter(adap->fe_adap[0].fe); + + switch (priv->tuner) { + case TUNER_RTL2830_QT1010: + fe = dvb_attach(qt1010_attach, adap->fe_adap[0].fe, + rtl2830_tuner_i2c, &rtl28xxu_qt1010_config); + break; + case TUNER_RTL2830_MT2060: + fe = dvb_attach(mt2060_attach, adap->fe_adap[0].fe, + rtl2830_tuner_i2c, &rtl28xxu_mt2060_config, + 1220); + break; + case TUNER_RTL2830_MXL5005S: + fe = dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe, + rtl2830_tuner_i2c, &rtl28xxu_mxl5005s_config); + break; + default: + fe = NULL; + err("unknown tuner=%d", priv->tuner); + } + + if (fe == NULL) { + ret = -ENODEV; + goto err; + } + + return 0; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_tuner_attach(struct dvb_usb_adapter *adap) +{ + int ret; + struct rtl28xxu_priv *priv = adap->dev->priv; + struct dvb_frontend *fe; + + deb_info("%s:\n", __func__); + + switch (priv->tuner) { + case TUNER_RTL2832_FC2580: + /* TODO: */ + fe = NULL; + break; + default: + fe = NULL; + err("unknown tuner=%d", priv->tuner); + } + + if (fe == NULL) { + ret = -ENODEV; + goto err; + } + + return 0; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl28xxu_streaming_ctrl(struct dvb_usb_adapter *adap , int onoff) +{ + int ret; + u8 buf[2], gpio; + + deb_info("%s: onoff=%d\n", __func__, onoff); + + ret = rtl2831_rd_reg(adap->dev, SYS_GPIO_OUT_VAL, &gpio); + if (ret) + goto err; + + if (onoff) { + buf[0] = 0x00; + buf[1] = 0x00; + gpio |= 0x04; /* LED on */ + } else { + buf[0] = 0x10; /* stall EPA */ + buf[1] = 0x02; /* reset EPA */ + gpio &= (~0x04); /* LED off */ + } + + ret = rtl2831_wr_reg(adap->dev, SYS_GPIO_OUT_VAL, gpio); + if (ret) + goto err; + + ret = rtl2831_wr_regs(adap->dev, USB_EPA_CTL, buf, 2); + if (ret) + goto err; + + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl28xxu_power_ctrl(struct dvb_usb_device *d, int onoff) +{ + int ret; + u8 gpio, sys0; + + deb_info("%s: onoff=%d\n", __func__, onoff); + + /* demod adc */ + ret = rtl2831_rd_reg(d, SYS_SYS0, &sys0); + if (ret) + goto err; + + /* tuner power, read GPIOs */ + ret = rtl2831_rd_reg(d, SYS_GPIO_OUT_VAL, &gpio); + if (ret) + goto err; + + deb_info("%s: RD SYS0=%02x GPIO_OUT_VAL=%02x\n", __func__, sys0, gpio); + + if (onoff) { + gpio |= 0x01; /* GPIO0 = 1 */ + gpio &= (~0x10); /* GPIO4 = 0 */ + sys0 = sys0 & 0x0f; + sys0 |= 0xe0; + } else { + gpio &= (~0x01); /* GPIO0 = 0 */ + gpio |= 0x10; /* GPIO4 = 1 */ + sys0 = sys0 & (~0xc0); + } + + deb_info("%s: WR SYS0=%02x GPIO_OUT_VAL=%02x\n", __func__, sys0, gpio); + + /* demod adc */ + ret = rtl2831_wr_reg(d, SYS_SYS0, sys0); + if (ret) + goto err; + + /* tuner power, write GPIOs */ + ret = rtl2831_wr_reg(d, SYS_GPIO_OUT_VAL, gpio); + if (ret) + goto err; + + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2831u_rc_query(struct dvb_usb_device *d) +{ + int ret, i; + struct rtl28xxu_priv *priv = d->priv; + u8 buf[5]; + u32 rc_code; + struct rtl28xxu_reg_val rc_nec_tab[] = { + { 0x3033, 0x80 }, + { 0x3020, 0x43 }, + { 0x3021, 0x16 }, + { 0x3022, 0x16 }, + { 0x3023, 0x5a }, + { 0x3024, 0x2d }, + { 0x3025, 0x16 }, + { 0x3026, 0x01 }, + { 0x3028, 0xb0 }, + { 0x3029, 0x04 }, + { 0x302c, 0x88 }, + { 0x302e, 0x13 }, + { 0x3030, 0xdf }, + { 0x3031, 0x05 }, + }; + + /* init remote controller */ + if (!priv->rc_active) { + for (i = 0; i < ARRAY_SIZE(rc_nec_tab); i++) { + ret = rtl2831_wr_reg(d, rc_nec_tab[i].reg, + rc_nec_tab[i].val); + if (ret) + goto err; + } + priv->rc_active = true; + } + + ret = rtl2831_rd_regs(d, SYS_IRRC_RP, buf, 5); + if (ret) + goto err; + + if (buf[4] & 0x01) { + if (buf[2] == (u8) ~buf[3]) { + if (buf[0] == (u8) ~buf[1]) { + /* NEC standard (16 bit) */ + rc_code = buf[0] << 8 | buf[2]; + } else { + /* NEC extended (24 bit) */ + rc_code = buf[0] << 16 | + buf[1] << 8 | buf[2]; + } + } else { + /* NEC full (32 bit) */ + rc_code = buf[0] << 24 | buf[1] << 16 | + buf[2] << 8 | buf[3]; + } + + rc_keydown(d->rc_dev, rc_code, 0); + + ret = rtl2831_wr_reg(d, SYS_IRRC_SR, 1); + if (ret) + goto err; + + /* repeated intentionally to avoid extra keypress */ + ret = rtl2831_wr_reg(d, SYS_IRRC_SR, 1); + if (ret) + goto err; + } + + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static int rtl2832u_rc_query(struct dvb_usb_device *d) +{ + int ret, i; + struct rtl28xxu_priv *priv = d->priv; + u8 buf[128]; + int len; + struct rtl28xxu_reg_val rc_nec_tab[] = { + { IR_RX_CTRL, 0x20 }, + { IR_RX_BUF_CTRL, 0x80 }, + { IR_RX_IF, 0xff }, + { IR_RX_IE, 0xff }, + { IR_MAX_DURATION0, 0xd0 }, + { IR_MAX_DURATION1, 0x07 }, + { IR_IDLE_LEN0, 0xc0 }, + { IR_IDLE_LEN1, 0x00 }, + { IR_GLITCH_LEN, 0x03 }, + { IR_RX_CLK, 0x09 }, + { IR_RX_CFG, 0x1c }, + { IR_MAX_H_TOL_LEN, 0x1e }, + { IR_MAX_L_TOL_LEN, 0x1e }, + { IR_RX_CTRL, 0x80 }, + }; + + /* init remote controller */ + if (!priv->rc_active) { + for (i = 0; i < ARRAY_SIZE(rc_nec_tab); i++) { + ret = rtl2831_wr_reg(d, rc_nec_tab[i].reg, + rc_nec_tab[i].val); + if (ret) + goto err; + } + priv->rc_active = true; + } + + ret = rtl2831_rd_reg(d, IR_RX_IF, &buf[0]); + if (ret) + goto err; + + if (buf[0] != 0x83) + goto exit; + + ret = rtl2831_rd_reg(d, IR_RX_BC, &buf[0]); + if (ret) + goto err; + + len = buf[0]; + ret = rtl2831_rd_regs(d, IR_RX_BUF, buf, len); + + /* TODO: pass raw IR to Kernel IR decoder */ + + ret = rtl2831_wr_reg(d, IR_RX_IF, 0x03); + ret = rtl2831_wr_reg(d, IR_RX_BUF_CTRL, 0x80); + ret = rtl2831_wr_reg(d, IR_RX_CTRL, 0x80); + +exit: + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +enum rtl28xxu_usb_table_entry { + RTL2831U_0BDA_2831, + RTL2831U_14AA_0160, + RTL2831U_14AA_0161, +}; + +static struct usb_device_id rtl28xxu_table[] = { + /* RTL2831U */ + [RTL2831U_0BDA_2831] = { + USB_DEVICE(USB_VID_REALTEK, USB_PID_REALTEK_RTL2831U)}, + [RTL2831U_14AA_0160] = { + USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT)}, + [RTL2831U_14AA_0161] = { + USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT_2)}, + + /* RTL2832U */ + {} /* terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, rtl28xxu_table); + +static struct dvb_usb_device_properties rtl28xxu_properties[] = { + { + .caps = DVB_USB_IS_AN_I2C_ADAPTER, + + .usb_ctrl = DEVICE_SPECIFIC, + .no_reconnect = 1, + + .size_of_priv = sizeof(struct rtl28xxu_priv), + + .num_adapters = 1, + .adapter = { + { + .num_frontends = 1, + .fe = { + { + .frontend_attach = rtl2831u_frontend_attach, + .tuner_attach = rtl2831u_tuner_attach, + .streaming_ctrl = rtl28xxu_streaming_ctrl, + .stream = { + .type = USB_BULK, + .count = 6, + .endpoint = 0x81, + .u = { + .bulk = { + .buffersize = 8*512, + } + } + } + } + } + } + }, + + .power_ctrl = rtl28xxu_power_ctrl, + + .rc.core = { + .protocol = RC_TYPE_NEC, + .module_name = "rtl28xxu", + .rc_query = rtl2831u_rc_query, + .rc_interval = 400, + .allowed_protos = RC_TYPE_NEC, + .rc_codes = RC_MAP_EMPTY, + }, + + .i2c_algo = &rtl28xxu_i2c_algo, + + .num_device_descs = 2, + .devices = { + { + .name = "Realtek RTL2831U reference design", + .warm_ids = { + &rtl28xxu_table[RTL2831U_0BDA_2831], + }, + }, + { + .name = "Freecom USB2.0 DVB-T", + .warm_ids = { + &rtl28xxu_table[RTL2831U_14AA_0160], + &rtl28xxu_table[RTL2831U_14AA_0161], + }, + }, + } + }, + { + .caps = DVB_USB_IS_AN_I2C_ADAPTER, + + .usb_ctrl = DEVICE_SPECIFIC, + .no_reconnect = 1, + + .size_of_priv = sizeof(struct rtl28xxu_priv), + + .num_adapters = 1, + .adapter = { + { + .num_frontends = 1, + .fe = { + { + .frontend_attach = rtl2832u_frontend_attach, + .tuner_attach = rtl2832u_tuner_attach, + .streaming_ctrl = rtl28xxu_streaming_ctrl, + .stream = { + .type = USB_BULK, + .count = 6, + .endpoint = 0x81, + .u = { + .bulk = { + .buffersize = 8*512, + } + } + } + } + } + } + }, + + .power_ctrl = rtl28xxu_power_ctrl, + + .rc.core = { + .protocol = RC_TYPE_NEC, + .module_name = "rtl28xxu", + .rc_query = rtl2832u_rc_query, + .rc_interval = 400, + .allowed_protos = RC_TYPE_NEC, + .rc_codes = RC_MAP_EMPTY, + }, + + .i2c_algo = &rtl28xxu_i2c_algo, + + .num_device_descs = 0, /* disabled as no support for RTL2832 */ + .devices = { + { + .name = "Realtek RTL2832U reference design", + }, + } + }, + +}; + +static int rtl28xxu_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret, i; + int properties_count = ARRAY_SIZE(rtl28xxu_properties); + struct dvb_usb_device *d; + + deb_info("%s: interface=%d\n", __func__, + intf->cur_altsetting->desc.bInterfaceNumber); + + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return 0; + + for (i = 0; i < properties_count; i++) { + ret = dvb_usb_device_init(intf, &rtl28xxu_properties[i], + THIS_MODULE, &d, adapter_nr); + if (ret == 0 || ret != -ENODEV) + break; + } + + if (ret) + goto err; + + /* init USB endpoints */ + ret = rtl2831_wr_reg(d, USB_SYSCTL_0, 0x09); + if (ret) + goto err; + + ret = rtl2831_wr_regs(d, USB_EPA_MAXPKT, "\x00\x02\x00\x00", 4); + if (ret) + goto err; + + ret = rtl2831_wr_regs(d, USB_EPA_FIFO_CFG, "\x14\x00\x00\x00", 4); + if (ret) + goto err; + + return ret; +err: + deb_info("%s: failed=%d\n", __func__, ret); + return ret; +} + +static struct usb_driver rtl28xxu_driver = { + .name = "dvb_usb_rtl28xxu", + .probe = rtl28xxu_probe, + .disconnect = dvb_usb_device_exit, + .id_table = rtl28xxu_table, +}; + +/* module stuff */ +static int __init rtl28xxu_module_init(void) +{ + int ret; + + deb_info("%s:\n", __func__); + + ret = usb_register(&rtl28xxu_driver); + if (ret) + err("usb_register failed=%d", ret); + + return ret; +} + +static void __exit rtl28xxu_module_exit(void) +{ + deb_info("%s:\n", __func__); + + /* deregister this driver from the USB subsystem */ + usb_deregister(&rtl28xxu_driver); +} + +module_init(rtl28xxu_module_init); +module_exit(rtl28xxu_module_exit); + +MODULE_DESCRIPTION("Realtek RTL28xxU DVB USB driver"); +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-usb/rtl28xxu.h b/drivers/media/dvb/dvb-usb/rtl28xxu.h new file mode 100644 index 000000000000..90f3bb4f4c0e --- /dev/null +++ b/drivers/media/dvb/dvb-usb/rtl28xxu.h @@ -0,0 +1,264 @@ +/* + * Realtek RTL28xxU DVB USB driver + * + * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RTL28XXU_H +#define RTL28XXU_H + +#define DVB_USB_LOG_PREFIX "rtl28xxu" +#include "dvb-usb.h" + +#define deb_info(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x01, args) +#define deb_rc(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x02, args) +#define deb_xfer(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x04, args) +#define deb_reg(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x08, args) +#define deb_i2c(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x10, args) +#define deb_fw(args...) dprintk(dvb_usb_rtl28xxu_debug, 0x20, args) + +#define deb_dump(r, t, v, i, b, l, func) { \ + int loop_; \ + func("%02x %02x %02x %02x %02x %02x %02x %02x", \ + t, r, v & 0xff, v >> 8, i & 0xff, i >> 8, l & 0xff, l >> 8); \ + if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \ + func(" >>> "); \ + else \ + func(" <<< "); \ + for (loop_ = 0; loop_ < l; loop_++) \ + func("%02x ", b[loop_]); \ + func("\n");\ +} + +/* + * USB commands + * (usb_control_msg() index parameter) + */ + +#define DEMOD 0x0000 +#define USB 0x0100 +#define SYS 0x0200 +#define I2C 0x0300 +#define I2C_DA 0x0600 + +#define CMD_WR_FLAG 0x0010 +#define CMD_DEMOD_RD 0x0000 +#define CMD_DEMOD_WR 0x0010 +#define CMD_USB_RD 0x0100 +#define CMD_USB_WR 0x0110 +#define CMD_SYS_RD 0x0200 +#define CMD_IR_RD 0x0201 +#define CMD_IR_WR 0x0211 +#define CMD_SYS_WR 0x0210 +#define CMD_I2C_RD 0x0300 +#define CMD_I2C_WR 0x0310 +#define CMD_I2C_DA_RD 0x0600 +#define CMD_I2C_DA_WR 0x0610 + + +struct rtl28xxu_priv { + u8 chip_id; + u8 tuner; + u8 page; /* integrated demod active register page */ + bool rc_active; +}; + +enum rtl28xxu_chip_id { + CHIP_ID_NONE, + CHIP_ID_RTL2831U, + CHIP_ID_RTL2832U, +}; + +enum rtl28xxu_tuner { + TUNER_NONE, + + TUNER_RTL2830_QT1010, + TUNER_RTL2830_MT2060, + TUNER_RTL2830_MXL5005S, + + TUNER_RTL2832_MT2266, + TUNER_RTL2832_FC2580, + TUNER_RTL2832_MT2063, + TUNER_RTL2832_MAX3543, + TUNER_RTL2832_TUA9001, + TUNER_RTL2832_MXL5007T, + TUNER_RTL2832_FC0012, + TUNER_RTL2832_E4000, + TUNER_RTL2832_TDA18272, + TUNER_RTL2832_FC0013, +}; + +struct rtl28xxu_req { + u16 value; + u16 index; + u16 size; + u8 *data; +}; + +struct rtl28xxu_reg_val { + u16 reg; + u8 val; +}; + +/* + * memory map + * + * 0x0000 DEMOD : demodulator + * 0x2000 USB : SIE, USB endpoint, debug, DMA + * 0x3000 SYS : system + * 0xfc00 RC : remote controller (not RTL2831U) + */ + +/* + * USB registers + */ +/* SIE Control Registers */ +#define USB_SYSCTL 0x2000 /* USB system control */ +#define USB_SYSCTL_0 0x2000 /* USB system control */ +#define USB_SYSCTL_1 0x2001 /* USB system control */ +#define USB_SYSCTL_2 0x2002 /* USB system control */ +#define USB_SYSCTL_3 0x2003 /* USB system control */ +#define USB_IRQSTAT 0x2008 /* SIE interrupt status */ +#define USB_IRQEN 0x200C /* SIE interrupt enable */ +#define USB_CTRL 0x2010 /* USB control */ +#define USB_STAT 0x2014 /* USB status */ +#define USB_DEVADDR 0x2018 /* USB device address */ +#define USB_TEST 0x201C /* USB test mode */ +#define USB_FRAME_NUMBER 0x2020 /* frame number */ +#define USB_FIFO_ADDR 0x2028 /* address of SIE FIFO RAM */ +#define USB_FIFO_CMD 0x202A /* SIE FIFO RAM access command */ +#define USB_FIFO_DATA 0x2030 /* SIE FIFO RAM data */ +/* Endpoint Registers */ +#define EP0_SETUPA 0x20F8 /* EP 0 setup packet lower byte */ +#define EP0_SETUPB 0x20FC /* EP 0 setup packet higher byte */ +#define USB_EP0_CFG 0x2104 /* EP 0 configure */ +#define USB_EP0_CTL 0x2108 /* EP 0 control */ +#define USB_EP0_STAT 0x210C /* EP 0 status */ +#define USB_EP0_IRQSTAT 0x2110 /* EP 0 interrupt status */ +#define USB_EP0_IRQEN 0x2114 /* EP 0 interrupt enable */ +#define USB_EP0_MAXPKT 0x2118 /* EP 0 max packet size */ +#define USB_EP0_BC 0x2120 /* EP 0 FIFO byte counter */ +#define USB_EPA_CFG 0x2144 /* EP A configure */ +#define USB_EPA_CFG_0 0x2144 /* EP A configure */ +#define USB_EPA_CFG_1 0x2145 /* EP A configure */ +#define USB_EPA_CFG_2 0x2146 /* EP A configure */ +#define USB_EPA_CFG_3 0x2147 /* EP A configure */ +#define USB_EPA_CTL 0x2148 /* EP A control */ +#define USB_EPA_CTL_0 0x2148 /* EP A control */ +#define USB_EPA_CTL_1 0x2149 /* EP A control */ +#define USB_EPA_CTL_2 0x214A /* EP A control */ +#define USB_EPA_CTL_3 0x214B /* EP A control */ +#define USB_EPA_STAT 0x214C /* EP A status */ +#define USB_EPA_IRQSTAT 0x2150 /* EP A interrupt status */ +#define USB_EPA_IRQEN 0x2154 /* EP A interrupt enable */ +#define USB_EPA_MAXPKT 0x2158 /* EP A max packet size */ +#define USB_EPA_MAXPKT_0 0x2158 /* EP A max packet size */ +#define USB_EPA_MAXPKT_1 0x2159 /* EP A max packet size */ +#define USB_EPA_MAXPKT_2 0x215A /* EP A max packet size */ +#define USB_EPA_MAXPKT_3 0x215B /* EP A max packet size */ +#define USB_EPA_FIFO_CFG 0x2160 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_0 0x2160 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_1 0x2161 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_2 0x2162 /* EP A FIFO configure */ +#define USB_EPA_FIFO_CFG_3 0x2163 /* EP A FIFO configure */ +/* Debug Registers */ +#define USB_PHYTSTDIS 0x2F04 /* PHY test disable */ +#define USB_TOUT_VAL 0x2F08 /* USB time-out time */ +#define USB_VDRCTRL 0x2F10 /* UTMI vendor signal control */ +#define USB_VSTAIN 0x2F14 /* UTMI vendor signal status in */ +#define USB_VLOADM 0x2F18 /* UTMI load vendor signal status in */ +#define USB_VSTAOUT 0x2F1C /* UTMI vendor signal status out */ +#define USB_UTMI_TST 0x2F80 /* UTMI test */ +#define USB_UTMI_STATUS 0x2F84 /* UTMI status */ +#define USB_TSTCTL 0x2F88 /* test control */ +#define USB_TSTCTL2 0x2F8C /* test control 2 */ +#define USB_PID_FORCE 0x2F90 /* force PID */ +#define USB_PKTERR_CNT 0x2F94 /* packet error counter */ +#define USB_RXERR_CNT 0x2F98 /* RX error counter */ +#define USB_MEM_BIST 0x2F9C /* MEM BIST test */ +#define USB_SLBBIST 0x2FA0 /* self-loop-back BIST */ +#define USB_CNTTEST 0x2FA4 /* counter test */ +#define USB_PHYTST 0x2FC0 /* USB PHY test */ +#define USB_DBGIDX 0x2FF0 /* select individual block debug signal */ +#define USB_DBGMUX 0x2FF4 /* debug signal module mux */ + +/* + * SYS registers + */ +/* demod control registers */ +#define SYS_SYS0 0x3000 /* include DEMOD_CTL, GPO, GPI, GPOE */ +#define SYS_DEMOD_CTL 0x3000 /* control register for DVB-T demodulator */ +/* GPIO registers */ +#define SYS_GPIO_OUT_VAL 0x3001 /* output value of GPIO */ +#define SYS_GPIO_IN_VAL 0x3002 /* input value of GPIO */ +#define SYS_GPIO_OUT_EN 0x3003 /* output enable of GPIO */ +#define SYS_SYS1 0x3004 /* include GPD, SYSINTE, SYSINTS, GP_CFG0 */ +#define SYS_GPIO_DIR 0x3004 /* direction control for GPIO */ +#define SYS_SYSINTE 0x3005 /* system interrupt enable */ +#define SYS_SYSINTS 0x3006 /* system interrupt status */ +#define SYS_GPIO_CFG0 0x3007 /* PAD configuration for GPIO0-GPIO3 */ +#define SYS_SYS2 0x3008 /* include GP_CFG1 and 3 reserved bytes */ +#define SYS_GPIO_CFG1 0x3008 /* PAD configuration for GPIO4 */ +#define SYS_DEMOD_CTL1 0x300B + +/* IrDA registers */ +#define SYS_IRRC_PSR 0x3020 /* IR protocol selection */ +#define SYS_IRRC_PER 0x3024 /* IR protocol extension */ +#define SYS_IRRC_SF 0x3028 /* IR sampling frequency */ +#define SYS_IRRC_DPIR 0x302C /* IR data package interval */ +#define SYS_IRRC_CR 0x3030 /* IR control */ +#define SYS_IRRC_RP 0x3034 /* IR read port */ +#define SYS_IRRC_SR 0x3038 /* IR status */ +/* I2C master registers */ +#define SYS_I2CCR 0x3040 /* I2C clock */ +#define SYS_I2CMCR 0x3044 /* I2C master control */ +#define SYS_I2CMSTR 0x3048 /* I2C master SCL timing */ +#define SYS_I2CMSR 0x304C /* I2C master status */ +#define SYS_I2CMFR 0x3050 /* I2C master FIFO */ + +/* + * IR registers + */ +#define IR_RX_BUF 0xFC00 +#define IR_RX_IE 0xFD00 +#define IR_RX_IF 0xFD01 +#define IR_RX_CTRL 0xFD02 +#define IR_RX_CFG 0xFD03 +#define IR_MAX_DURATION0 0xFD04 +#define IR_MAX_DURATION1 0xFD05 +#define IR_IDLE_LEN0 0xFD06 +#define IR_IDLE_LEN1 0xFD07 +#define IR_GLITCH_LEN 0xFD08 +#define IR_RX_BUF_CTRL 0xFD09 +#define IR_RX_BUF_DATA 0xFD0A +#define IR_RX_BC 0xFD0B +#define IR_RX_CLK 0xFD0C +#define IR_RX_C_COUNT_L 0xFD0D +#define IR_RX_C_COUNT_H 0xFD0E +#define IR_SUSPEND_CTRL 0xFD10 +#define IR_ERR_TOL_CTRL 0xFD11 +#define IR_UNIT_LEN 0xFD12 +#define IR_ERR_TOL_LEN 0xFD13 +#define IR_MAX_H_TOL_LEN 0xFD14 +#define IR_MAX_L_TOL_LEN 0xFD15 +#define IR_MASK_CTRL 0xFD16 +#define IR_MASK_DATA 0xFD17 +#define IR_RES_MASK_ADDR 0xFD18 +#define IR_RES_MASK_T_LEN 0xFD19 + +#endif diff --git a/drivers/media/dvb/frontends/Kconfig b/drivers/media/dvb/frontends/Kconfig index ebb5ed7a7783..21246707fbfb 100644 --- a/drivers/media/dvb/frontends/Kconfig +++ b/drivers/media/dvb/frontends/Kconfig @@ -425,6 +425,13 @@ config DVB_CXD2820R help Say Y when you want to support this frontend. +config DVB_RTL2830 + tristate "Realtek RTL2830 DVB-T" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + Say Y when you want to support this frontend. + comment "DVB-C (cable) frontends" depends on DVB_CORE @@ -698,6 +705,14 @@ config DVB_IT913X_FE A DVB-T tuner module. Say Y when you want to support this frontend. +config DVB_M88RS2000 + tristate "M88RS2000 DVB-S demodulator and tuner" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S tuner module. + Say Y when you want to support this frontend. + comment "Tools to develop new frontends" config DVB_DUMMY_FE diff --git a/drivers/media/dvb/frontends/Makefile b/drivers/media/dvb/frontends/Makefile index 00a20636df62..86fa808bf589 100644 --- a/drivers/media/dvb/frontends/Makefile +++ b/drivers/media/dvb/frontends/Makefile @@ -2,8 +2,8 @@ # Makefile for the kernel DVB frontend device drivers. # -ccflags-y += -Idrivers/media/dvb/dvb-core/ -ccflags-y += -Idrivers/media/common/tuners/ +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core/ +ccflags-y += -I$(srctree)/drivers/media/common/tuners/ stb0899-objs = stb0899_drv.o stb0899_algo.o stv0900-objs = stv0900_core.o stv0900_sw.o @@ -96,4 +96,6 @@ obj-$(CONFIG_DVB_TDA18271C2DD) += tda18271c2dd.o obj-$(CONFIG_DVB_IT913X_FE) += it913x-fe.o obj-$(CONFIG_DVB_A8293) += a8293.o obj-$(CONFIG_DVB_TDA10071) += tda10071.o +obj-$(CONFIG_DVB_RTL2830) += rtl2830.o +obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o diff --git a/drivers/media/dvb/frontends/au8522_decoder.c b/drivers/media/dvb/frontends/au8522_decoder.c index 2b248c12f404..55b6390198e3 100644 --- a/drivers/media/dvb/frontends/au8522_decoder.c +++ b/drivers/media/dvb/frontends/au8522_decoder.c @@ -839,15 +839,4 @@ static struct i2c_driver au8522_driver = { .id_table = au8522_id, }; -static __init int init_au8522(void) -{ - return i2c_add_driver(&au8522_driver); -} - -static __exit void exit_au8522(void) -{ - i2c_del_driver(&au8522_driver); -} - -module_init(init_au8522); -module_exit(exit_au8522); +module_i2c_driver(au8522_driver); diff --git a/drivers/media/dvb/frontends/au8522_dig.c b/drivers/media/dvb/frontends/au8522_dig.c index c688b95df486..25f650934c73 100644 --- a/drivers/media/dvb/frontends/au8522_dig.c +++ b/drivers/media/dvb/frontends/au8522_dig.c @@ -588,11 +588,6 @@ static int au8522_set_frontend(struct dvb_frontend *fe) (state->current_modulation == c->modulation)) return 0; - au8522_enable_modulation(fe, c->modulation); - - /* Allow the demod to settle */ - msleep(100); - if (fe->ops.tuner_ops.set_params) { if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); @@ -604,6 +599,11 @@ static int au8522_set_frontend(struct dvb_frontend *fe) if (ret < 0) return ret; + /* Allow the tuner to settle */ + msleep(100); + + au8522_enable_modulation(fe, c->modulation); + state->current_frequency = c->frequency; return 0; diff --git a/drivers/media/dvb/frontends/cx22702.c b/drivers/media/dvb/frontends/cx22702.c index faba82485086..edc8eafc5c09 100644 --- a/drivers/media/dvb/frontends/cx22702.c +++ b/drivers/media/dvb/frontends/cx22702.c @@ -502,10 +502,26 @@ static int cx22702_read_signal_strength(struct dvb_frontend *fe, u16 *signal_strength) { struct cx22702_state *state = fe->demodulator_priv; + u8 reg23; - u16 rs_ber; - rs_ber = cx22702_readreg(state, 0x23); - *signal_strength = (rs_ber << 8) | rs_ber; + /* + * Experience suggests that the strength signal register works as + * follows: + * - In the absence of signal, value is 0xff. + * - In the presence of a weak signal, bit 7 is set, not sure what + * the lower 7 bits mean. + * - In the presence of a strong signal, the register holds a 7-bit + * value (bit 7 is cleared), with greater values standing for + * weaker signals. + */ + reg23 = cx22702_readreg(state, 0x23); + if (reg23 & 0x80) { + *signal_strength = 0; + } else { + reg23 = ~reg23 & 0x7f; + /* Scale to 16 bit */ + *signal_strength = (reg23 << 9) | (reg23 << 2) | (reg23 >> 5); + } return 0; } diff --git a/drivers/media/dvb/frontends/dib0090.c b/drivers/media/dvb/frontends/dib0090.c index 224d81e85091..d9fe60b4be48 100644 --- a/drivers/media/dvb/frontends/dib0090.c +++ b/drivers/media/dvb/frontends/dib0090.c @@ -519,7 +519,7 @@ static int dib0090_fw_identify(struct dvb_frontend *fe) return 0; identification_error: - return -EIO;; + return -EIO; } static void dib0090_reset_digital(struct dvb_frontend *fe, const struct dib0090_config *cfg) diff --git a/drivers/media/dvb/frontends/dib9000.c b/drivers/media/dvb/frontends/dib9000.c index 863ef3cfab9f..80848b4c15d4 100644 --- a/drivers/media/dvb/frontends/dib9000.c +++ b/drivers/media/dvb/frontends/dib9000.c @@ -33,7 +33,7 @@ struct i2c_device { /* lock */ #define DIB_LOCK struct mutex -#define DibAcquireLock(lock) do { if (mutex_lock_interruptible(lock) < 0) dprintk("could not get the lock"); } while (0) +#define DibAcquireLock(lock) mutex_lock_interruptible(lock) #define DibReleaseLock(lock) mutex_unlock(lock) #define DibInitLock(lock) mutex_init(lock) #define DibFreeLock(lock) @@ -446,7 +446,10 @@ static int dib9000_risc_mem_read(struct dib9000_state *state, u8 cmd, u8 * b, u1 if (!state->platform.risc.fw_is_running) return -EIO; - DibAcquireLock(&state->platform.risc.mem_lock); + if (DibAcquireLock(&state->platform.risc.mem_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } dib9000_risc_mem_setup(state, cmd | 0x80); dib9000_risc_mem_read_chunks(state, b, len); DibReleaseLock(&state->platform.risc.mem_lock); @@ -459,7 +462,10 @@ static int dib9000_risc_mem_write(struct dib9000_state *state, u8 cmd, const u8 if (!state->platform.risc.fw_is_running) return -EIO; - DibAcquireLock(&state->platform.risc.mem_lock); + if (DibAcquireLock(&state->platform.risc.mem_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } dib9000_risc_mem_setup(state, cmd); dib9000_risc_mem_write_chunks(state, b, m->size); DibReleaseLock(&state->platform.risc.mem_lock); @@ -531,7 +537,10 @@ static int dib9000_mbx_send_attr(struct dib9000_state *state, u8 id, u16 * data, if (!state->platform.risc.fw_is_running) return -EINVAL; - DibAcquireLock(&state->platform.risc.mbx_if_lock); + if (DibAcquireLock(&state->platform.risc.mbx_if_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } tmp = MAX_MAILBOX_TRY; do { size = dib9000_read_word_attr(state, 1043, attr) & 0xff; @@ -593,7 +602,10 @@ static u8 dib9000_mbx_read(struct dib9000_state *state, u16 * data, u8 risc_id, if (!state->platform.risc.fw_is_running) return 0; - DibAcquireLock(&state->platform.risc.mbx_if_lock); + if (DibAcquireLock(&state->platform.risc.mbx_if_lock) < 0) { + dprintk("could not get the lock"); + return 0; + } if (risc_id == 1) mc_base = 16; else @@ -701,7 +713,10 @@ static int dib9000_mbx_process(struct dib9000_state *state, u16 attr) if (!state->platform.risc.fw_is_running) return -1; - DibAcquireLock(&state->platform.risc.mbx_lock); + if (DibAcquireLock(&state->platform.risc.mbx_lock) < 0) { + dprintk("could not get the lock"); + return -1; + } if (dib9000_mbx_count(state, 1, attr)) /* 1=RiscB */ ret = dib9000_mbx_fetch_to_cache(state, attr); @@ -1178,7 +1193,10 @@ static int dib9000_fw_get_channel(struct dvb_frontend *fe) struct dibDVBTChannel *ch; int ret = 0; - DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) { ret = -EIO; goto error; @@ -1660,7 +1678,10 @@ static int dib9000_fw_component_bus_xfer(struct i2c_adapter *i2c_adap, struct i2 p[12] = 0; } - DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) { + dprintk("could not get the lock"); + return 0; + } dib9000_risc_mem_write(state, FE_MM_W_COMPONENT_ACCESS, p); @@ -1768,7 +1789,10 @@ int dib9000_fw_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff) return 0; } - DibAcquireLock(&state->demod_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } val = dib9000_read_word(state, 294 + 1) & 0xffef; val |= (onoff & 0x1) << 4; @@ -1800,7 +1824,10 @@ int dib9000_fw_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff) return 0; } - DibAcquireLock(&state->demod_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } dprintk("Index %x, PID %d, OnOff %d", id, pid, onoff); ret = dib9000_write_word(state, 300 + 1 + id, onoff ? (1 << 13) | pid : 0); @@ -1848,7 +1875,10 @@ static int dib9000_sleep(struct dvb_frontend *fe) u8 index_frontend; int ret = 0; - DibAcquireLock(&state->demod_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { ret = state->fe[index_frontend]->ops.sleep(state->fe[index_frontend]); if (ret < 0) @@ -1874,8 +1904,12 @@ static int dib9000_get_frontend(struct dvb_frontend *fe) fe_status_t stat; int ret = 0; - if (state->get_frontend_internal == 0) - DibAcquireLock(&state->demod_lock); + if (state->get_frontend_internal == 0) { + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } + } for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { state->fe[index_frontend]->ops.read_status(state->fe[index_frontend], &stat); @@ -1978,7 +2012,10 @@ static int dib9000_set_frontend(struct dvb_frontend *fe) } state->pid_ctrl_index = -1; /* postpone the pid filtering cmd */ - DibAcquireLock(&state->demod_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return 0; + } fe->dtv_property_cache.delivery_system = SYS_DVBT; @@ -2138,7 +2175,10 @@ static int dib9000_read_status(struct dvb_frontend *fe, fe_status_t * stat) u8 index_frontend; u16 lock = 0, lock_slave = 0; - DibAcquireLock(&state->demod_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) lock_slave |= dib9000_read_lock(state->fe[index_frontend]); @@ -2168,8 +2208,15 @@ static int dib9000_read_ber(struct dvb_frontend *fe, u32 * ber) u16 *c; int ret = 0; - DibAcquireLock(&state->demod_lock); - DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } + if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) { + dprintk("could not get the lock"); + ret = -EINTR; + goto error; + } if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) { DibReleaseLock(&state->platform.risc.mem_mbx_lock); ret = -EIO; @@ -2196,7 +2243,10 @@ static int dib9000_read_signal_strength(struct dvb_frontend *fe, u16 * strength) u16 val; int ret = 0; - DibAcquireLock(&state->demod_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } *strength = 0; for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { state->fe[index_frontend]->ops.read_signal_strength(state->fe[index_frontend], &val); @@ -2206,8 +2256,13 @@ static int dib9000_read_signal_strength(struct dvb_frontend *fe, u16 * strength) *strength += val; } - DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) { + dprintk("could not get the lock"); + ret = -EINTR; + goto error; + } if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) { + DibReleaseLock(&state->platform.risc.mem_mbx_lock); ret = -EIO; goto error; } @@ -2232,9 +2287,14 @@ static u32 dib9000_get_snr(struct dvb_frontend *fe) u32 n, s, exp; u16 val; - DibAcquireLock(&state->platform.risc.mem_mbx_lock); - if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) - return -EIO; + if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) { + dprintk("could not get the lock"); + return 0; + } + if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) { + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + return 0; + } dib9000_risc_mem_read(state, FE_MM_R_FE_MONITOR, (u8 *) c, 16 * 2); DibReleaseLock(&state->platform.risc.mem_mbx_lock); @@ -2266,7 +2326,10 @@ static int dib9000_read_snr(struct dvb_frontend *fe, u16 * snr) u8 index_frontend; u32 snr_master; - DibAcquireLock(&state->demod_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } snr_master = dib9000_get_snr(fe); for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) snr_master += dib9000_get_snr(state->fe[index_frontend]); @@ -2288,9 +2351,17 @@ static int dib9000_read_unc_blocks(struct dvb_frontend *fe, u32 * unc) u16 *c = (u16 *)state->i2c_read_buffer; int ret = 0; - DibAcquireLock(&state->demod_lock); - DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (DibAcquireLock(&state->demod_lock) < 0) { + dprintk("could not get the lock"); + return -EINTR; + } + if (DibAcquireLock(&state->platform.risc.mem_mbx_lock) < 0) { + dprintk("could not get the lock"); + ret = -EINTR; + goto error; + } if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) { + DibReleaseLock(&state->platform.risc.mem_mbx_lock); ret = -EIO; goto error; } diff --git a/drivers/media/dvb/frontends/drxd_hard.c b/drivers/media/dvb/frontends/drxd_hard.c index 7bf39cda83c5..f380eb43e9d5 100644 --- a/drivers/media/dvb/frontends/drxd_hard.c +++ b/drivers/media/dvb/frontends/drxd_hard.c @@ -101,9 +101,9 @@ struct SCfgAgc { struct SNoiseCal { int cpOpt; - u16 cpNexpOfs; - u16 tdCal2k; - u16 tdCal8k; + short cpNexpOfs; + short tdCal2k; + short tdCal8k; }; enum app_env { diff --git a/drivers/media/dvb/frontends/drxk.h b/drivers/media/dvb/frontends/drxk.h index 020981844a86..9d64e4fea066 100644 --- a/drivers/media/dvb/frontends/drxk.h +++ b/drivers/media/dvb/frontends/drxk.h @@ -7,15 +7,19 @@ /** * struct drxk_config - Configure the initial parameters for DRX-K * - * adr: I2C Address of the DRX-K - * parallel_ts: true means that the device uses parallel TS, + * @adr: I2C Address of the DRX-K + * @parallel_ts: True means that the device uses parallel TS, * Serial otherwise. - * single_master: Device is on the single master mode - * no_i2c_bridge: Don't switch the I2C bridge to talk with tuner - * antenna_gpio: GPIO bit used to control the antenna - * antenna_dvbt: GPIO bit for changing antenna to DVB-C. A value of 1 + * @dynamic_clk: True means that the clock will be dynamically + * adjusted. Static clock otherwise. + * @enable_merr_cfg: Enable SIO_PDR_PERR_CFG/SIO_PDR_MVAL_CFG. + * @single_master: Device is on the single master mode + * @no_i2c_bridge: Don't switch the I2C bridge to talk with tuner + * @antenna_gpio: GPIO bit used to control the antenna + * @antenna_dvbt: GPIO bit for changing antenna to DVB-C. A value of 1 * means that 1=DVBC, 0 = DVBT. Zero means the opposite. - * microcode_name: Name of the firmware file with the microcode + * @mpeg_out_clk_strength: DRXK Mpeg output clock drive strength. + * @microcode_name: Name of the firmware file with the microcode * * On the *_gpio vars, bit 0 is UIO-1, bit 1 is UIO-2 and bit 2 is * UIO-3. @@ -25,11 +29,14 @@ struct drxk_config { bool single_master; bool no_i2c_bridge; bool parallel_ts; + bool dynamic_clk; + bool enable_merr_cfg; bool antenna_dvbt; u16 antenna_gpio; - int chunk_size; + u8 mpeg_out_clk_strength; + int chunk_size; const char *microcode_name; }; diff --git a/drivers/media/dvb/frontends/drxk_hard.c b/drivers/media/dvb/frontends/drxk_hard.c index 5ab53795bd7a..36d11756492f 100644 --- a/drivers/media/dvb/frontends/drxk_hard.c +++ b/drivers/media/dvb/frontends/drxk_hard.c @@ -90,10 +90,6 @@ bool IsA1WithRomCode(struct drxk_state *state) #define DRXK_MPEG_PARALLEL_OUTPUT_PIN_DRIVE_STRENGTH (0x03) #endif -#ifndef DRXK_MPEG_OUTPUT_CLK_DRIVE_STRENGTH -#define DRXK_MPEG_OUTPUT_CLK_DRIVE_STRENGTH (0x06) -#endif - #define DEFAULT_DRXK_MPEG_LOCK_TIMEOUT 700 #define DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT 500 @@ -649,9 +645,6 @@ static int init_state(struct drxk_state *state) u32 ulQual83 = DEFAULT_MER_83; u32 ulQual93 = DEFAULT_MER_93; - u32 ulDVBTStaticTSClock = 1; - u32 ulDVBCStaticTSClock = 1; - u32 ulMpegLockTimeOut = DEFAULT_DRXK_MPEG_LOCK_TIMEOUT; u32 ulDemodLockTimeOut = DEFAULT_DRXK_DEMOD_LOCK_TIMEOUT; @@ -661,7 +654,6 @@ static int init_state(struct drxk_state *state) u32 ulGPIOCfg = 0x0113; u32 ulInvertTSClock = 0; u32 ulTSDataStrength = DRXK_MPEG_SERIAL_OUTPUT_PIN_DRIVE_STRENGTH; - u32 ulTSClockkStrength = DRXK_MPEG_OUTPUT_CLK_DRIVE_STRENGTH; u32 ulDVBTBitrate = 50000000; u32 ulDVBCBitrate = DRXK_QAM_SYMBOLRATE_MAX * 8; @@ -814,8 +806,7 @@ static int init_state(struct drxk_state *state) state->m_invertSTR = false; /* If TRUE; invert STR signals */ state->m_invertVAL = false; /* If TRUE; invert VAL signals */ state->m_invertCLK = (ulInvertTSClock != 0); /* If TRUE; invert CLK signals */ - state->m_DVBTStaticCLK = (ulDVBTStaticTSClock != 0); - state->m_DVBCStaticCLK = (ulDVBCStaticTSClock != 0); + /* If TRUE; static MPEG clockrate will be used; otherwise clockrate will adapt to the bitrate of the TS */ @@ -823,7 +814,6 @@ static int init_state(struct drxk_state *state) state->m_DVBCBitrate = ulDVBCBitrate; state->m_TSDataStrength = (ulTSDataStrength & 0x07); - state->m_TSClockkStrength = (ulTSClockkStrength & 0x07); /* Maximum bitrate in b/s in case static clockrate is selected */ state->m_mpegTsStaticBitrate = 19392658; @@ -1188,6 +1178,7 @@ static int MPEGTSConfigurePins(struct drxk_state *state, bool mpegEnable) int status = -1; u16 sioPdrMclkCfg = 0; u16 sioPdrMdxCfg = 0; + u16 err_cfg = 0; dprintk(1, ": mpeg %s, %s mode\n", mpegEnable ? "enable" : "disable", @@ -1253,12 +1244,17 @@ static int MPEGTSConfigurePins(struct drxk_state *state, bool mpegEnable) status = write16(state, SIO_PDR_MSTRT_CFG__A, sioPdrMdxCfg); if (status < 0) goto error; - status = write16(state, SIO_PDR_MERR_CFG__A, 0x0000); /* Disable */ + + if (state->enable_merr_cfg) + err_cfg = sioPdrMdxCfg; + + status = write16(state, SIO_PDR_MERR_CFG__A, err_cfg); if (status < 0) goto error; - status = write16(state, SIO_PDR_MVAL_CFG__A, 0x0000); /* Disable */ + status = write16(state, SIO_PDR_MVAL_CFG__A, err_cfg); if (status < 0) goto error; + if (state->m_enableParallel == true) { /* paralel -> enable MD1 to MD7 */ status = write16(state, SIO_PDR_MD1_CFG__A, sioPdrMdxCfg); @@ -6069,9 +6065,7 @@ static int init_drxk(struct drxk_state *state) if (status < 0) goto error; - if (!state->microcode_name) - load_microcode(state, "drxk_a3.mc"); - else + if (state->microcode_name) load_microcode(state, state->microcode_name); /* disable token-ring bus through OFDM block for possible ucode upload */ @@ -6322,15 +6316,12 @@ static int drxk_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_t switch (p->delivery_system) { case SYS_DVBC_ANNEX_A: case SYS_DVBC_ANNEX_C: + case SYS_DVBT: sets->min_delay_ms = 3000; sets->max_drift = 0; sets->step_size = 0; return 0; default: - /* - * For DVB-T, let it use the default DVB core way, that is: - * fepriv->step_size = fe->ops.info.frequency_stepsize * 2 - */ return -EINVAL; } } @@ -6390,6 +6381,21 @@ struct dvb_frontend *drxk_attach(const struct drxk_config *config, state->antenna_gpio = config->antenna_gpio; state->antenna_dvbt = config->antenna_dvbt; state->m_ChunkSize = config->chunk_size; + state->enable_merr_cfg = config->enable_merr_cfg; + + if (config->dynamic_clk) { + state->m_DVBTStaticCLK = 0; + state->m_DVBCStaticCLK = 0; + } else { + state->m_DVBTStaticCLK = 1; + state->m_DVBCStaticCLK = 1; + } + + + if (config->mpeg_out_clk_strength) + state->m_TSClockkStrength = config->mpeg_out_clk_strength & 0x07; + else + state->m_TSClockkStrength = 0x06; if (config->parallel_ts) state->m_enableParallel = true; diff --git a/drivers/media/dvb/frontends/drxk_hard.h b/drivers/media/dvb/frontends/drxk_hard.h index 3a58b73eb9b9..4bbf841de83a 100644 --- a/drivers/media/dvb/frontends/drxk_hard.h +++ b/drivers/media/dvb/frontends/drxk_hard.h @@ -332,6 +332,7 @@ struct drxk_state { u16 UIO_mask; /* Bits used by UIO */ + bool enable_merr_cfg; bool single_master; bool no_i2c_bridge; bool antenna_dvbt; diff --git a/drivers/media/dvb/frontends/it913x-fe-priv.h b/drivers/media/dvb/frontends/it913x-fe-priv.h index 93b086ea7e1c..eb6fd8aebdb3 100644 --- a/drivers/media/dvb/frontends/it913x-fe-priv.h +++ b/drivers/media/dvb/frontends/it913x-fe-priv.h @@ -201,6 +201,11 @@ fe_modulation_t fe_con[] = { QAM_64, }; +enum { + PRIORITY_HIGH = 0, /* High-priority stream */ + PRIORITY_LOW, /* Low-priority stream */ +}; + /* Standard demodulator functions */ static struct it913xset set_solo_fe[] = { {PRO_LINK, GPIOH5_EN, {0x01}, 0x01}, diff --git a/drivers/media/dvb/frontends/it913x-fe.c b/drivers/media/dvb/frontends/it913x-fe.c index ccc36bf2deb4..84df03c29179 100644 --- a/drivers/media/dvb/frontends/it913x-fe.c +++ b/drivers/media/dvb/frontends/it913x-fe.c @@ -57,6 +57,7 @@ struct it913x_fe_state { u32 frequency; fe_modulation_t constellation; fe_transmit_mode_t transmission_mode; + u8 priority; u32 crystalFrequency; u32 adcFrequency; u8 tuner_type; @@ -500,19 +501,87 @@ static int it913x_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) return 0; } +/* FEC values based on fe_code_rate_t non supported values 0*/ +int it913x_qpsk_pval[] = {0, -93, -91, -90, 0, -89, -88}; +int it913x_16qam_pval[] = {0, -87, -85, -84, 0, -83, -82}; +int it913x_64qam_pval[] = {0, -82, -80, -78, 0, -77, -76}; + +static int it913x_get_signal_strength(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct it913x_fe_state *state = fe->demodulator_priv; + u8 code_rate; + int ret, temp; + u8 lna_gain_os; + + ret = it913x_read_reg_u8(state, VAR_P_INBAND); + if (ret < 0) + return ret; + + /* VHF/UHF gain offset */ + if (state->frequency < 300000000) + lna_gain_os = 7; + else + lna_gain_os = 14; + + temp = (ret - 100) - lna_gain_os; + + if (state->priority == PRIORITY_HIGH) + code_rate = p->code_rate_HP; + else + code_rate = p->code_rate_LP; + + if (code_rate >= ARRAY_SIZE(it913x_qpsk_pval)) + return -EINVAL; + + deb_info("Reg VAR_P_INBAND:%d Calc Offset Value:%d", ret, temp); + + /* Apply FEC offset values*/ + switch (p->modulation) { + case QPSK: + temp -= it913x_qpsk_pval[code_rate]; + break; + case QAM_16: + temp -= it913x_16qam_pval[code_rate]; + break; + case QAM_64: + temp -= it913x_64qam_pval[code_rate]; + break; + default: + return -EINVAL; + } + + if (temp < -15) + ret = 0; + else if ((-15 <= temp) && (temp < 0)) + ret = (2 * (temp + 15)) / 3; + else if ((0 <= temp) && (temp < 20)) + ret = 4 * temp + 10; + else if ((20 <= temp) && (temp < 35)) + ret = (2 * (temp - 20)) / 3 + 90; + else if (temp >= 35) + ret = 100; + + deb_info("Signal Strength :%d", ret); + + return ret; +} + static int it913x_fe_read_signal_strength(struct dvb_frontend *fe, u16 *strength) { struct it913x_fe_state *state = fe->demodulator_priv; - int ret = it913x_read_reg_u8(state, SIGNAL_LEVEL); - /*SIGNAL_LEVEL always returns 100%! so using FE_HAS_SIGNAL as switch*/ - if (state->it913x_status & FE_HAS_SIGNAL) - ret = (ret * 0xff) / 0x64; - else - ret = 0x0; - ret |= ret << 0x8; - *strength = ret; - return 0; + int ret = 0; + if (state->config->read_slevel) { + if (state->it913x_status & FE_HAS_SIGNAL) + ret = it913x_read_reg_u8(state, SIGNAL_LEVEL); + } else + ret = it913x_get_signal_strength(fe); + + if (ret >= 0) + *strength = (u16)((u32)ret * 0xffff / 0x64); + + return (ret < 0) ? -ENODEV : 0; } static int it913x_fe_read_snr(struct dvb_frontend *fe, u16 *snr) @@ -606,6 +675,8 @@ static int it913x_fe_get_frontend(struct dvb_frontend *fe) if (reg[2] < 4) p->hierarchy = fe_hi[reg[2]]; + state->priority = reg[5]; + p->code_rate_HP = (reg[6] < 6) ? fe_code[reg[6]] : FEC_NONE; p->code_rate_LP = (reg[7] < 6) ? fe_code[reg[7]] : FEC_NONE; @@ -972,5 +1043,5 @@ static struct dvb_frontend_ops it913x_fe_ofdm_ops = { MODULE_DESCRIPTION("it913x Frontend and it9137 tuner"); MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); -MODULE_VERSION("1.13"); +MODULE_VERSION("1.15"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/it913x-fe.h b/drivers/media/dvb/frontends/it913x-fe.h index c4a908e354e0..07fa4594c12b 100644 --- a/drivers/media/dvb/frontends/it913x-fe.h +++ b/drivers/media/dvb/frontends/it913x-fe.h @@ -34,6 +34,8 @@ struct ite_config { u8 tuner_id_1; u8 dual_mode; u8 adf; + /* option to read SIGNAL_LEVEL */ + u8 read_slevel; }; #if defined(CONFIG_DVB_IT913X_FE) || (defined(CONFIG_DVB_IT913X_FE_MODULE) && \ @@ -168,6 +170,8 @@ static inline struct dvb_frontend *it913x_fe_attach( #define EST_SIGNAL_LEVEL 0x004a #define FREE_BAND 0x004b #define SUSPEND_FLAG 0x004c +#define VAR_P_INBAND 0x00f7 + /* Build in tuner types */ #define IT9137 0x38 #define IT9135_38 0x38 diff --git a/drivers/media/dvb/frontends/lgdt330x.c b/drivers/media/dvb/frontends/lgdt330x.c index c990d35a13dc..e046622df0e4 100644 --- a/drivers/media/dvb/frontends/lgdt330x.c +++ b/drivers/media/dvb/frontends/lgdt330x.c @@ -104,8 +104,8 @@ static int i2c_write_demod_bytes (struct lgdt330x_state* state, * then reads the data returned for (len) bytes. */ -static u8 i2c_read_demod_bytes (struct lgdt330x_state* state, - enum I2C_REG reg, u8* buf, int len) +static int i2c_read_demod_bytes(struct lgdt330x_state *state, + enum I2C_REG reg, u8 *buf, int len) { u8 wr [] = { reg }; struct i2c_msg msg [] = { @@ -118,6 +118,8 @@ static u8 i2c_read_demod_bytes (struct lgdt330x_state* state, ret = i2c_transfer(state->i2c, msg, 2); if (ret != 2) { printk(KERN_WARNING "lgdt330x: %s: addr 0x%02x select 0x%02x error (ret == %i)\n", __func__, state->config->demod_address, reg, ret); + if (ret >= 0) + ret = -EIO; } else { ret = 0; } diff --git a/drivers/media/dvb/frontends/m88rs2000.c b/drivers/media/dvb/frontends/m88rs2000.c new file mode 100644 index 000000000000..045ee5a6f7ae --- /dev/null +++ b/drivers/media/dvb/frontends/m88rs2000.c @@ -0,0 +1,904 @@ +/* + Driver for M88RS2000 demodulator and tuner + + Copyright (C) 2012 Malcolm Priestley (tvboxspy@gmail.com) + Beta Driver + + Include various calculation code from DS3000 driver. + Copyright (C) 2009 Konstantin Dimitrov. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/types.h> + + +#include "dvb_frontend.h" +#include "m88rs2000.h" + +struct m88rs2000_state { + struct i2c_adapter *i2c; + const struct m88rs2000_config *config; + struct dvb_frontend frontend; + u8 no_lock_count; + u32 tuner_frequency; + u32 symbol_rate; + fe_code_rate_t fec_inner; + u8 tuner_level; + int errmode; +}; + +static int m88rs2000_debug; + +module_param_named(debug, m88rs2000_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))."); + +#define dprintk(level, args...) do { \ + if (level & m88rs2000_debug) \ + printk(KERN_DEBUG "m88rs2000-fe: " args); \ +} while (0) + +#define deb_info(args...) dprintk(0x01, args) +#define info(format, arg...) \ + printk(KERN_INFO "m88rs2000-fe: " format "\n" , ## arg) + +static int m88rs2000_writereg(struct m88rs2000_state *state, u8 tuner, + u8 reg, u8 data) +{ + int ret; + u8 addr = (tuner == 0) ? state->config->tuner_addr : + state->config->demod_addr; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { + .addr = addr, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + deb_info("%s: writereg error (reg == 0x%02x, val == 0x%02x, " + "ret == %i)\n", __func__, reg, data, ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int m88rs2000_demod_write(struct m88rs2000_state *state, u8 reg, u8 data) +{ + return m88rs2000_writereg(state, 1, reg, data); +} + +static int m88rs2000_tuner_write(struct m88rs2000_state *state, u8 reg, u8 data) +{ + m88rs2000_demod_write(state, 0x81, 0x84); + udelay(10); + return m88rs2000_writereg(state, 0, reg, data); + +} + +static int m88rs2000_write(struct dvb_frontend *fe, const u8 buf[], int len) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return m88rs2000_writereg(state, 1, buf[0], buf[1]); +} + +static u8 m88rs2000_readreg(struct m88rs2000_state *state, u8 tuner, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + u8 addr = (tuner == 0) ? state->config->tuner_addr : + state->config->demod_addr; + struct i2c_msg msg[] = { + { + .addr = addr, + .flags = 0, + .buf = b0, + .len = 1 + }, { + .addr = addr, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + deb_info("%s: readreg error (reg == 0x%02x, ret == %i)\n", + __func__, reg, ret); + + return b1[0]; +} + +static u8 m88rs2000_demod_read(struct m88rs2000_state *state, u8 reg) +{ + return m88rs2000_readreg(state, 1, reg); +} + +static u8 m88rs2000_tuner_read(struct m88rs2000_state *state, u8 reg) +{ + m88rs2000_demod_write(state, 0x81, 0x85); + udelay(10); + return m88rs2000_readreg(state, 0, reg); +} + +static int m88rs2000_set_symbolrate(struct dvb_frontend *fe, u32 srate) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + u32 temp; + u8 b[3]; + + if ((srate < 1000000) || (srate > 45000000)) + return -EINVAL; + + temp = srate / 1000; + temp *= 11831; + temp /= 68; + temp -= 3; + + b[0] = (u8) (temp >> 16) & 0xff; + b[1] = (u8) (temp >> 8) & 0xff; + b[2] = (u8) temp & 0xff; + ret = m88rs2000_demod_write(state, 0x93, b[2]); + ret |= m88rs2000_demod_write(state, 0x94, b[1]); + ret |= m88rs2000_demod_write(state, 0x95, b[0]); + + deb_info("m88rs2000: m88rs2000_set_symbolrate\n"); + return ret; +} + +static int m88rs2000_send_diseqc_msg(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *m) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + + int i; + u8 reg; + deb_info("%s\n", __func__); + m88rs2000_demod_write(state, 0x9a, 0x30); + reg = m88rs2000_demod_read(state, 0xb2); + reg &= 0x3f; + m88rs2000_demod_write(state, 0xb2, reg); + for (i = 0; i < m->msg_len; i++) + m88rs2000_demod_write(state, 0xb3 + i, m->msg[i]); + + reg = m88rs2000_demod_read(state, 0xb1); + reg &= 0x87; + reg |= ((m->msg_len - 1) << 3) | 0x07; + reg &= 0x7f; + m88rs2000_demod_write(state, 0xb1, reg); + + for (i = 0; i < 15; i++) { + if ((m88rs2000_demod_read(state, 0xb1) & 0x40) == 0x0) + break; + msleep(20); + } + + reg = m88rs2000_demod_read(state, 0xb1); + if ((reg & 0x40) > 0x0) { + reg &= 0x7f; + reg |= 0x40; + m88rs2000_demod_write(state, 0xb1, reg); + } + + reg = m88rs2000_demod_read(state, 0xb2); + reg &= 0x3f; + reg |= 0x80; + m88rs2000_demod_write(state, 0xb2, reg); + m88rs2000_demod_write(state, 0x9a, 0xb0); + + + return 0; +} + +static int m88rs2000_send_diseqc_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t burst) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u8 reg0, reg1; + deb_info("%s\n", __func__); + m88rs2000_demod_write(state, 0x9a, 0x30); + msleep(50); + reg0 = m88rs2000_demod_read(state, 0xb1); + reg1 = m88rs2000_demod_read(state, 0xb2); + /* TODO complete this section */ + m88rs2000_demod_write(state, 0xb2, reg1); + m88rs2000_demod_write(state, 0xb1, reg0); + m88rs2000_demod_write(state, 0x9a, 0xb0); + + return 0; +} + +static int m88rs2000_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u8 reg0, reg1; + m88rs2000_demod_write(state, 0x9a, 0x30); + reg0 = m88rs2000_demod_read(state, 0xb1); + reg1 = m88rs2000_demod_read(state, 0xb2); + + reg1 &= 0x3f; + + switch (tone) { + case SEC_TONE_ON: + reg0 |= 0x4; + reg0 &= 0xbc; + break; + case SEC_TONE_OFF: + reg1 |= 0x80; + break; + default: + break; + } + m88rs2000_demod_write(state, 0xb2, reg1); + m88rs2000_demod_write(state, 0xb1, reg0); + m88rs2000_demod_write(state, 0x9a, 0xb0); + return 0; +} + +struct inittab { + u8 cmd; + u8 reg; + u8 val; +}; + +struct inittab m88rs2000_setup[] = { + {DEMOD_WRITE, 0x9a, 0x30}, + {DEMOD_WRITE, 0x00, 0x01}, + {WRITE_DELAY, 0x19, 0x00}, + {DEMOD_WRITE, 0x00, 0x00}, + {DEMOD_WRITE, 0x9a, 0xb0}, + {DEMOD_WRITE, 0x81, 0xc1}, + {TUNER_WRITE, 0x42, 0x73}, + {TUNER_WRITE, 0x05, 0x07}, + {TUNER_WRITE, 0x20, 0x27}, + {TUNER_WRITE, 0x07, 0x02}, + {TUNER_WRITE, 0x11, 0xff}, + {TUNER_WRITE, 0x60, 0xf9}, + {TUNER_WRITE, 0x08, 0x01}, + {TUNER_WRITE, 0x00, 0x41}, + {DEMOD_WRITE, 0x81, 0x81}, + {DEMOD_WRITE, 0x86, 0xc6}, + {DEMOD_WRITE, 0x9a, 0x30}, + {DEMOD_WRITE, 0xf0, 0x22}, + {DEMOD_WRITE, 0xf1, 0xbf}, + {DEMOD_WRITE, 0xb0, 0x45}, + {DEMOD_WRITE, 0xb2, 0x01}, /* set voltage pin always set 1*/ + {DEMOD_WRITE, 0x9a, 0xb0}, + {0xff, 0xaa, 0xff} +}; + +struct inittab m88rs2000_shutdown[] = { + {DEMOD_WRITE, 0x9a, 0x30}, + {DEMOD_WRITE, 0xb0, 0x00}, + {DEMOD_WRITE, 0xf1, 0x89}, + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0x9a, 0xb0}, + {TUNER_WRITE, 0x00, 0x40}, + {DEMOD_WRITE, 0x81, 0x81}, + {0xff, 0xaa, 0xff} +}; + +struct inittab tuner_reset[] = { + {TUNER_WRITE, 0x42, 0x73}, + {TUNER_WRITE, 0x05, 0x07}, + {TUNER_WRITE, 0x20, 0x27}, + {TUNER_WRITE, 0x07, 0x02}, + {TUNER_WRITE, 0x11, 0xff}, + {TUNER_WRITE, 0x60, 0xf9}, + {TUNER_WRITE, 0x08, 0x01}, + {TUNER_WRITE, 0x00, 0x41}, + {0xff, 0xaa, 0xff} +}; + +struct inittab fe_reset[] = { + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0xf1, 0xbf}, + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0x20, 0x81}, + {DEMOD_WRITE, 0x21, 0x80}, + {DEMOD_WRITE, 0x10, 0x33}, + {DEMOD_WRITE, 0x11, 0x44}, + {DEMOD_WRITE, 0x12, 0x07}, + {DEMOD_WRITE, 0x18, 0x20}, + {DEMOD_WRITE, 0x28, 0x04}, + {DEMOD_WRITE, 0x29, 0x8e}, + {DEMOD_WRITE, 0x3b, 0xff}, + {DEMOD_WRITE, 0x32, 0x10}, + {DEMOD_WRITE, 0x33, 0x02}, + {DEMOD_WRITE, 0x34, 0x30}, + {DEMOD_WRITE, 0x35, 0xff}, + {DEMOD_WRITE, 0x38, 0x50}, + {DEMOD_WRITE, 0x39, 0x68}, + {DEMOD_WRITE, 0x3c, 0x7f}, + {DEMOD_WRITE, 0x3d, 0x0f}, + {DEMOD_WRITE, 0x45, 0x20}, + {DEMOD_WRITE, 0x46, 0x24}, + {DEMOD_WRITE, 0x47, 0x7c}, + {DEMOD_WRITE, 0x48, 0x16}, + {DEMOD_WRITE, 0x49, 0x04}, + {DEMOD_WRITE, 0x4a, 0x01}, + {DEMOD_WRITE, 0x4b, 0x78}, + {DEMOD_WRITE, 0X4d, 0xd2}, + {DEMOD_WRITE, 0x4e, 0x6d}, + {DEMOD_WRITE, 0x50, 0x30}, + {DEMOD_WRITE, 0x51, 0x30}, + {DEMOD_WRITE, 0x54, 0x7b}, + {DEMOD_WRITE, 0x56, 0x09}, + {DEMOD_WRITE, 0x58, 0x59}, + {DEMOD_WRITE, 0x59, 0x37}, + {DEMOD_WRITE, 0x63, 0xfa}, + {0xff, 0xaa, 0xff} +}; + +struct inittab fe_trigger[] = { + {DEMOD_WRITE, 0x97, 0x04}, + {DEMOD_WRITE, 0x99, 0x77}, + {DEMOD_WRITE, 0x9b, 0x64}, + {DEMOD_WRITE, 0x9e, 0x00}, + {DEMOD_WRITE, 0x9f, 0xf8}, + {DEMOD_WRITE, 0xa0, 0x20}, + {DEMOD_WRITE, 0xa1, 0xe0}, + {DEMOD_WRITE, 0xa3, 0x38}, + {DEMOD_WRITE, 0x98, 0xff}, + {DEMOD_WRITE, 0xc0, 0x0f}, + {DEMOD_WRITE, 0x89, 0x01}, + {DEMOD_WRITE, 0x00, 0x00}, + {WRITE_DELAY, 0x0a, 0x00}, + {DEMOD_WRITE, 0x00, 0x01}, + {DEMOD_WRITE, 0x00, 0x00}, + {DEMOD_WRITE, 0x9a, 0xb0}, + {0xff, 0xaa, 0xff} +}; + +static int m88rs2000_tab_set(struct m88rs2000_state *state, + struct inittab *tab) +{ + int ret = 0; + u8 i; + if (tab == NULL) + return -EINVAL; + + for (i = 0; i < 255; i++) { + switch (tab[i].cmd) { + case 0x01: + ret = m88rs2000_demod_write(state, tab[i].reg, + tab[i].val); + break; + case 0x02: + ret = m88rs2000_tuner_write(state, tab[i].reg, + tab[i].val); + break; + case 0x10: + if (tab[i].reg > 0) + mdelay(tab[i].reg); + break; + case 0xff: + if (tab[i].reg == 0xaa && tab[i].val == 0xff) + return 0; + case 0x00: + break; + default: + return -EINVAL; + } + if (ret < 0) + return -ENODEV; + } + return 0; +} + +static int m88rs2000_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt) +{ + deb_info("%s: %s\n", __func__, + volt == SEC_VOLTAGE_13 ? "SEC_VOLTAGE_13" : + volt == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "??"); + + return 0; +} + +static int m88rs2000_startup(struct m88rs2000_state *state) +{ + int ret = 0; + u8 reg; + + reg = m88rs2000_tuner_read(state, 0x00); + if ((reg & 0x40) == 0) + ret = -ENODEV; + + return ret; +} + +static int m88rs2000_init(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + + deb_info("m88rs2000: init chip\n"); + /* Setup frontend from shutdown/cold */ + ret = m88rs2000_tab_set(state, m88rs2000_setup); + + return ret; +} + +static int m88rs2000_sleep(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + /* Shutdown the frondend */ + ret = m88rs2000_tab_set(state, m88rs2000_shutdown); + return ret; +} + +static int m88rs2000_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + u8 reg = m88rs2000_demod_read(state, 0x8c); + + *status = 0; + + if ((reg & 0x7) == 0x7) { + *status = FE_HAS_CARRIER | FE_HAS_SIGNAL | FE_HAS_VITERBI + | FE_HAS_LOCK; + if (state->config->set_ts_params) + state->config->set_ts_params(fe, CALL_IS_READ); + } + return 0; +} + +/* Extact code for these unknown but lmedm04 driver uses interupt callbacks */ + +static int m88rs2000_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + deb_info("m88rs2000_read_ber %d\n", *ber); + *ber = 0; + return 0; +} + +static int m88rs2000_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + *strength = 0; + return 0; +} + +static int m88rs2000_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + deb_info("m88rs2000_read_snr %d\n", *snr); + *snr = 0; + return 0; +} + +static int m88rs2000_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + deb_info("m88rs2000_read_ber %d\n", *ucblocks); + *ucblocks = 0; + return 0; +} + +static int m88rs2000_tuner_gate_ctrl(struct m88rs2000_state *state, u8 offset) +{ + int ret; + ret = m88rs2000_tuner_write(state, 0x51, 0x1f - offset); + ret |= m88rs2000_tuner_write(state, 0x51, 0x1f); + ret |= m88rs2000_tuner_write(state, 0x50, offset); + ret |= m88rs2000_tuner_write(state, 0x50, 0x00); + msleep(20); + return ret; +} + +static int m88rs2000_set_tuner_rf(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + int reg; + reg = m88rs2000_tuner_read(state, 0x3d); + reg &= 0x7f; + if (reg < 0x16) + reg = 0xa1; + else if (reg == 0x16) + reg = 0x99; + else + reg = 0xf9; + + m88rs2000_tuner_write(state, 0x60, reg); + reg = m88rs2000_tuner_gate_ctrl(state, 0x08); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + return reg; +} + +static int m88rs2000_set_tuner(struct dvb_frontend *fe, u16 *offset) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct m88rs2000_state *state = fe->demodulator_priv; + int ret; + u32 frequency = c->frequency; + s32 offset_khz; + s32 tmp; + u32 symbol_rate = (c->symbol_rate / 1000); + u32 f3db, gdiv28; + u16 value, ndiv, lpf_coeff; + u8 lpf_mxdiv, mlpf_max, mlpf_min, nlpf; + u8 lo = 0x01, div4 = 0x0; + + /* Reset Tuner */ + ret = m88rs2000_tab_set(state, tuner_reset); + + /* Calculate frequency divider */ + if (frequency < 1060000) { + lo |= 0x10; + div4 = 0x1; + ndiv = (frequency * 14 * 4) / FE_CRYSTAL_KHZ; + } else + ndiv = (frequency * 14 * 2) / FE_CRYSTAL_KHZ; + ndiv = ndiv + ndiv % 2; + ndiv = ndiv - 1024; + + ret = m88rs2000_tuner_write(state, 0x10, 0x80 | lo); + + /* Set frequency divider */ + ret |= m88rs2000_tuner_write(state, 0x01, (ndiv >> 8) & 0xf); + ret |= m88rs2000_tuner_write(state, 0x02, ndiv & 0xff); + + ret |= m88rs2000_tuner_write(state, 0x03, 0x06); + ret |= m88rs2000_tuner_gate_ctrl(state, 0x10); + if (ret < 0) + return -ENODEV; + + /* Tuner Frequency Range */ + ret = m88rs2000_tuner_write(state, 0x10, lo); + + ret |= m88rs2000_tuner_gate_ctrl(state, 0x08); + + /* Tuner RF */ + ret |= m88rs2000_set_tuner_rf(fe); + + gdiv28 = (FE_CRYSTAL_KHZ / 1000 * 1694 + 500) / 1000; + ret |= m88rs2000_tuner_write(state, 0x04, gdiv28 & 0xff); + ret |= m88rs2000_tuner_gate_ctrl(state, 0x04); + if (ret < 0) + return -ENODEV; + + value = m88rs2000_tuner_read(state, 0x26); + + f3db = (symbol_rate * 135) / 200 + 2000; + f3db += FREQ_OFFSET_LOW_SYM_RATE; + if (f3db < 7000) + f3db = 7000; + if (f3db > 40000) + f3db = 40000; + + gdiv28 = gdiv28 * 207 / (value * 2 + 151); + mlpf_max = gdiv28 * 135 / 100; + mlpf_min = gdiv28 * 78 / 100; + if (mlpf_max > 63) + mlpf_max = 63; + + lpf_coeff = 2766; + + nlpf = (f3db * gdiv28 * 2 / lpf_coeff / + (FE_CRYSTAL_KHZ / 1000) + 1) / 2; + if (nlpf > 23) + nlpf = 23; + if (nlpf < 1) + nlpf = 1; + + lpf_mxdiv = (nlpf * (FE_CRYSTAL_KHZ / 1000) + * lpf_coeff * 2 / f3db + 1) / 2; + + if (lpf_mxdiv < mlpf_min) { + nlpf++; + lpf_mxdiv = (nlpf * (FE_CRYSTAL_KHZ / 1000) + * lpf_coeff * 2 / f3db + 1) / 2; + } + + if (lpf_mxdiv > mlpf_max) + lpf_mxdiv = mlpf_max; + + ret = m88rs2000_tuner_write(state, 0x04, lpf_mxdiv); + ret |= m88rs2000_tuner_write(state, 0x06, nlpf); + + ret |= m88rs2000_tuner_gate_ctrl(state, 0x04); + + ret |= m88rs2000_tuner_gate_ctrl(state, 0x01); + + msleep(80); + /* calculate offset assuming 96000kHz*/ + offset_khz = (ndiv - ndiv % 2 + 1024) * FE_CRYSTAL_KHZ + / 14 / (div4 + 1) / 2; + + offset_khz -= frequency; + + tmp = offset_khz; + tmp *= 65536; + + tmp = (2 * tmp + 96000) / (2 * 96000); + if (tmp < 0) + tmp += 65536; + + *offset = tmp & 0xffff; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + return (ret < 0) ? -EINVAL : 0; +} + +static int m88rs2000_set_fec(struct m88rs2000_state *state, + fe_code_rate_t fec) +{ + int ret; + u16 fec_set; + switch (fec) { + /* This is not confirmed kept for reference */ +/* case FEC_1_2: + fec_set = 0x88; + break; + case FEC_2_3: + fec_set = 0x68; + break; + case FEC_3_4: + fec_set = 0x48; + break; + case FEC_5_6: + fec_set = 0x28; + break; + case FEC_7_8: + fec_set = 0x18; + break; */ + case FEC_AUTO: + default: + fec_set = 0x08; + } + ret = m88rs2000_demod_write(state, 0x76, fec_set); + + return 0; +} + + +static fe_code_rate_t m88rs2000_get_fec(struct m88rs2000_state *state) +{ + u8 reg; + m88rs2000_demod_write(state, 0x9a, 0x30); + reg = m88rs2000_demod_read(state, 0x76); + m88rs2000_demod_write(state, 0x9a, 0xb0); + + switch (reg) { + case 0x88: + return FEC_1_2; + case 0x68: + return FEC_2_3; + case 0x48: + return FEC_3_4; + case 0x28: + return FEC_5_6; + case 0x18: + return FEC_7_8; + case 0x08: + default: + break; + } + + return FEC_AUTO; +} + +static int m88rs2000_set_frontend(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + fe_status_t status; + int i, ret; + u16 offset = 0; + u8 reg; + + state->no_lock_count = 0; + + if (c->delivery_system != SYS_DVBS) { + deb_info("%s: unsupported delivery " + "system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + + /* Set Tuner */ + ret = m88rs2000_set_tuner(fe, &offset); + if (ret < 0) + return -ENODEV; + + ret = m88rs2000_demod_write(state, 0x9a, 0x30); + /* Unknown usually 0xc6 sometimes 0xc1 */ + reg = m88rs2000_demod_read(state, 0x86); + ret |= m88rs2000_demod_write(state, 0x86, reg); + /* Offset lower nibble always 0 */ + ret |= m88rs2000_demod_write(state, 0x9c, (offset >> 8)); + ret |= m88rs2000_demod_write(state, 0x9d, offset & 0xf0); + + + /* Reset Demod */ + ret = m88rs2000_tab_set(state, fe_reset); + if (ret < 0) + return -ENODEV; + + /* Unknown */ + reg = m88rs2000_demod_read(state, 0x70); + ret = m88rs2000_demod_write(state, 0x70, reg); + + /* Set FEC */ + ret |= m88rs2000_set_fec(state, c->fec_inner); + ret |= m88rs2000_demod_write(state, 0x85, 0x1); + ret |= m88rs2000_demod_write(state, 0x8a, 0xbf); + ret |= m88rs2000_demod_write(state, 0x8d, 0x1e); + ret |= m88rs2000_demod_write(state, 0x90, 0xf1); + ret |= m88rs2000_demod_write(state, 0x91, 0x08); + + if (ret < 0) + return -ENODEV; + + /* Set Symbol Rate */ + ret = m88rs2000_set_symbolrate(fe, c->symbol_rate); + if (ret < 0) + return -ENODEV; + + /* Set up Demod */ + ret = m88rs2000_tab_set(state, fe_trigger); + if (ret < 0) + return -ENODEV; + + for (i = 0; i < 25; i++) { + u8 reg = m88rs2000_demod_read(state, 0x8c); + if ((reg & 0x7) == 0x7) { + status = FE_HAS_LOCK; + break; + } + state->no_lock_count++; + if (state->no_lock_count > 15) { + reg = m88rs2000_demod_read(state, 0x70); + reg ^= 0x4; + m88rs2000_demod_write(state, 0x70, reg); + state->no_lock_count = 0; + } + if (state->no_lock_count == 20) + m88rs2000_set_tuner_rf(fe); + msleep(20); + } + + if (status & FE_HAS_LOCK) { + state->fec_inner = m88rs2000_get_fec(state); + /* Uknown suspect SNR level */ + reg = m88rs2000_demod_read(state, 0x65); + } + + state->tuner_frequency = c->frequency; + state->symbol_rate = c->symbol_rate; + return 0; +} + +static int m88rs2000_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct m88rs2000_state *state = fe->demodulator_priv; + c->fec_inner = state->fec_inner; + c->frequency = state->tuner_frequency; + c->symbol_rate = state->symbol_rate; + return 0; +} + +static int m88rs2000_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + + if (enable) + m88rs2000_demod_write(state, 0x81, 0x84); + else + m88rs2000_demod_write(state, 0x81, 0x81); + udelay(10); + return 0; +} + +static void m88rs2000_release(struct dvb_frontend *fe) +{ + struct m88rs2000_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops m88rs2000_ops = { + .delsys = { SYS_DVBS }, + .info = { + .name = "M88RS2000 DVB-S", + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1000, /* kHz for QPSK frontends */ + .frequency_tolerance = 5000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .symbol_rate_tolerance = 500, /* ppm */ + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO + }, + + .release = m88rs2000_release, + .init = m88rs2000_init, + .sleep = m88rs2000_sleep, + .write = m88rs2000_write, + .i2c_gate_ctrl = m88rs2000_i2c_gate_ctrl, + .read_status = m88rs2000_read_status, + .read_ber = m88rs2000_read_ber, + .read_signal_strength = m88rs2000_read_signal_strength, + .read_snr = m88rs2000_read_snr, + .read_ucblocks = m88rs2000_read_ucblocks, + .diseqc_send_master_cmd = m88rs2000_send_diseqc_msg, + .diseqc_send_burst = m88rs2000_send_diseqc_burst, + .set_tone = m88rs2000_set_tone, + .set_voltage = m88rs2000_set_voltage, + + .set_frontend = m88rs2000_set_frontend, + .get_frontend = m88rs2000_get_frontend, +}; + +struct dvb_frontend *m88rs2000_attach(const struct m88rs2000_config *config, + struct i2c_adapter *i2c) +{ + struct m88rs2000_state *state = NULL; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct m88rs2000_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->tuner_frequency = 0; + state->symbol_rate = 0; + state->fec_inner = 0; + + if (m88rs2000_startup(state) < 0) + goto error; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &m88rs2000_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + + return NULL; +} +EXPORT_SYMBOL(m88rs2000_attach); + +MODULE_DESCRIPTION("M88RS2000 DVB-S Demodulator driver"); +MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.13"); + diff --git a/drivers/media/dvb/frontends/m88rs2000.h b/drivers/media/dvb/frontends/m88rs2000.h new file mode 100644 index 000000000000..59acdb696873 --- /dev/null +++ b/drivers/media/dvb/frontends/m88rs2000.h @@ -0,0 +1,66 @@ +/* + Driver for M88RS2000 demodulator + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef M88RS2000_H +#define M88RS2000_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +struct m88rs2000_config { + /* Demodulator i2c address */ + u8 demod_addr; + /* Tuner address */ + u8 tuner_addr; + + u8 *inittab; + + /* minimum delay before retuning */ + int min_delay_ms; + + int (*set_ts_params)(struct dvb_frontend *, int); +}; + +enum { + CALL_IS_SET_FRONTEND = 0x0, + CALL_IS_READ, +}; + +#if defined(CONFIG_DVB_M88RS2000) || (defined(CONFIG_DVB_M88RS2000_MODULE) && \ + defined(MODULE)) +extern struct dvb_frontend *m88rs2000_attach( + const struct m88rs2000_config *config, struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *m88rs2000_attach( + const struct m88rs2000_config *config, struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_M88RS2000 */ + +#define FE_CRYSTAL_KHZ 27000 +#define FREQ_OFFSET_LOW_SYM_RATE 3000 + +enum { + DEMOD_WRITE = 0x1, + TUNER_WRITE, + WRITE_DELAY = 0x10, +}; +#endif /* M88RS2000_H */ diff --git a/drivers/media/dvb/frontends/rtl2830.c b/drivers/media/dvb/frontends/rtl2830.c new file mode 100644 index 000000000000..45196c5b0736 --- /dev/null +++ b/drivers/media/dvb/frontends/rtl2830.c @@ -0,0 +1,562 @@ +/* + * Realtek RTL2830 DVB-T demodulator driver + * + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +/* + * Driver implements own I2C-adapter for tuner I2C access. That's since chip + * have unusual I2C-gate control which closes gate automatically after each + * I2C transfer. Using own I2C adapter we can workaround that. + */ + +#include "rtl2830_priv.h" + +int rtl2830_debug; +module_param_named(debug, rtl2830_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +/* write multiple hardware registers */ +static int rtl2830_wr(struct rtl2830_priv *priv, u8 reg, u8 *val, int len) +{ + int ret; + u8 buf[1+len]; + struct i2c_msg msg[1] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = 1+len, + .buf = buf, + } + }; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + ret = i2c_transfer(priv->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + warn("i2c wr failed=%d reg=%02x len=%d", ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* read multiple hardware registers */ +static int rtl2830_rd(struct rtl2830_priv *priv, u8 reg, u8 *val, int len) +{ + int ret; + struct i2c_msg msg[2] = { + { + .addr = priv->cfg.i2c_addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = priv->cfg.i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + ret = i2c_transfer(priv->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + warn("i2c rd failed=%d reg=%02x len=%d", ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* write multiple registers */ +static int rtl2830_wr_regs(struct rtl2830_priv *priv, u16 reg, u8 *val, int len) +{ + int ret; + u8 reg2 = (reg >> 0) & 0xff; + u8 page = (reg >> 8) & 0xff; + + /* switch bank if needed */ + if (page != priv->page) { + ret = rtl2830_wr(priv, 0x00, &page, 1); + if (ret) + return ret; + + priv->page = page; + } + + return rtl2830_wr(priv, reg2, val, len); +} + +/* read multiple registers */ +static int rtl2830_rd_regs(struct rtl2830_priv *priv, u16 reg, u8 *val, int len) +{ + int ret; + u8 reg2 = (reg >> 0) & 0xff; + u8 page = (reg >> 8) & 0xff; + + /* switch bank if needed */ + if (page != priv->page) { + ret = rtl2830_wr(priv, 0x00, &page, 1); + if (ret) + return ret; + + priv->page = page; + } + + return rtl2830_rd(priv, reg2, val, len); +} + +#if 0 /* currently not used */ +/* write single register */ +static int rtl2830_wr_reg(struct rtl2830_priv *priv, u16 reg, u8 val) +{ + return rtl2830_wr_regs(priv, reg, &val, 1); +} +#endif + +/* read single register */ +static int rtl2830_rd_reg(struct rtl2830_priv *priv, u16 reg, u8 *val) +{ + return rtl2830_rd_regs(priv, reg, val, 1); +} + +/* write single register with mask */ +int rtl2830_wr_reg_mask(struct rtl2830_priv *priv, u16 reg, u8 val, u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = rtl2830_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return rtl2830_wr_regs(priv, reg, &val, 1); +} + +/* read single register with mask */ +int rtl2830_rd_reg_mask(struct rtl2830_priv *priv, u16 reg, u8 *val, u8 mask) +{ + int ret, i; + u8 tmp; + + ret = rtl2830_rd_regs(priv, reg, &tmp, 1); + if (ret) + return ret; + + tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = tmp >> i; + + return 0; +} + +static int rtl2830_init(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + int ret, i; + u64 num; + u8 buf[3], tmp; + u32 if_ctl; + struct rtl2830_reg_val_mask tab[] = { + { 0x00d, 0x01, 0x03 }, + { 0x00d, 0x10, 0x10 }, + { 0x104, 0x00, 0x1e }, + { 0x105, 0x80, 0x80 }, + { 0x110, 0x02, 0x03 }, + { 0x110, 0x08, 0x0c }, + { 0x17b, 0x00, 0x40 }, + { 0x17d, 0x05, 0x0f }, + { 0x17d, 0x50, 0xf0 }, + { 0x18c, 0x08, 0x0f }, + { 0x18d, 0x00, 0xc0 }, + { 0x188, 0x05, 0x0f }, + { 0x189, 0x00, 0xfc }, + { 0x2d5, 0x02, 0x02 }, + { 0x2f1, 0x02, 0x06 }, + { 0x2f1, 0x20, 0xf8 }, + { 0x16d, 0x00, 0x01 }, + { 0x1a6, 0x00, 0x80 }, + { 0x106, priv->cfg.vtop, 0x3f }, + { 0x107, priv->cfg.krf, 0x3f }, + { 0x112, 0x28, 0xff }, + { 0x103, priv->cfg.agc_targ_val, 0xff }, + { 0x00a, 0x02, 0x07 }, + { 0x140, 0x0c, 0x3c }, + { 0x140, 0x40, 0xc0 }, + { 0x15b, 0x05, 0x07 }, + { 0x15b, 0x28, 0x38 }, + { 0x15c, 0x05, 0x07 }, + { 0x15c, 0x28, 0x38 }, + { 0x115, priv->cfg.spec_inv, 0x01 }, + { 0x16f, 0x01, 0x07 }, + { 0x170, 0x18, 0x38 }, + { 0x172, 0x0f, 0x0f }, + { 0x173, 0x08, 0x38 }, + { 0x175, 0x01, 0x07 }, + { 0x176, 0x00, 0xc0 }, + }; + + for (i = 0; i < ARRAY_SIZE(tab); i++) { + ret = rtl2830_wr_reg_mask(priv, tab[i].reg, tab[i].val, + tab[i].mask); + if (ret) + goto err; + } + + ret = rtl2830_wr_regs(priv, 0x18f, "\x28\x00", 2); + if (ret) + goto err; + + ret = rtl2830_wr_regs(priv, 0x195, + "\x04\x06\x0a\x12\x0a\x12\x1e\x28", 8); + if (ret) + goto err; + + num = priv->cfg.if_dvbt % priv->cfg.xtal; + num *= 0x400000; + num = div_u64(num, priv->cfg.xtal); + num = -num; + if_ctl = num & 0x3fffff; + dbg("%s: if_ctl=%08x", __func__, if_ctl); + + ret = rtl2830_rd_reg_mask(priv, 0x119, &tmp, 0xc0); /* b[7:6] */ + if (ret) + goto err; + + buf[0] = tmp << 6; + buf[0] = (if_ctl >> 16) & 0x3f; + buf[1] = (if_ctl >> 8) & 0xff; + buf[2] = (if_ctl >> 0) & 0xff; + + ret = rtl2830_wr_regs(priv, 0x119, buf, 3); + if (ret) + goto err; + + /* TODO: spec init */ + + /* soft reset */ + ret = rtl2830_wr_reg_mask(priv, 0x101, 0x04, 0x04); + if (ret) + goto err; + + ret = rtl2830_wr_reg_mask(priv, 0x101, 0x00, 0x04); + if (ret) + goto err; + + priv->sleeping = false; + + return ret; +err: + dbg("%s: failed=%d", __func__, ret); + return ret; +} + +static int rtl2830_sleep(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + priv->sleeping = true; + return 0; +} + +int rtl2830_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 500; + s->step_size = fe->ops.info.frequency_stepsize * 2; + s->max_drift = (fe->ops.info.frequency_stepsize * 2) + 1; + + return 0; +} + +static int rtl2830_set_frontend(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret, i; + static u8 bw_params1[3][34] = { + { + 0x1f, 0xf0, 0x1f, 0xf0, 0x1f, 0xfa, 0x00, 0x17, 0x00, 0x41, + 0x00, 0x64, 0x00, 0x67, 0x00, 0x38, 0x1f, 0xde, 0x1f, 0x7a, + 0x1f, 0x47, 0x1f, 0x7c, 0x00, 0x30, 0x01, 0x4b, 0x02, 0x82, + 0x03, 0x73, 0x03, 0xcf, /* 6 MHz */ + }, { + 0x1f, 0xfa, 0x1f, 0xda, 0x1f, 0xc1, 0x1f, 0xb3, 0x1f, 0xca, + 0x00, 0x07, 0x00, 0x4d, 0x00, 0x6d, 0x00, 0x40, 0x1f, 0xca, + 0x1f, 0x4d, 0x1f, 0x2a, 0x1f, 0xb2, 0x00, 0xec, 0x02, 0x7e, + 0x03, 0xd0, 0x04, 0x53, /* 7 MHz */ + }, { + 0x00, 0x10, 0x00, 0x0e, 0x1f, 0xf7, 0x1f, 0xc9, 0x1f, 0xa0, + 0x1f, 0xa6, 0x1f, 0xec, 0x00, 0x4e, 0x00, 0x7d, 0x00, 0x3a, + 0x1f, 0x98, 0x1f, 0x10, 0x1f, 0x40, 0x00, 0x75, 0x02, 0x5f, + 0x04, 0x24, 0x04, 0xdb, /* 8 MHz */ + }, + }; + static u8 bw_params2[3][6] = { + {0xc3, 0x0c, 0x44, 0x33, 0x33, 0x30,}, /* 6 MHz */ + {0xb8, 0xe3, 0x93, 0x99, 0x99, 0x98,}, /* 7 MHz */ + {0xae, 0xba, 0xf3, 0x26, 0x66, 0x64,}, /* 8 MHz */ + }; + + + dbg("%s: frequency=%d bandwidth_hz=%d inversion=%d", __func__, + c->frequency, c->bandwidth_hz, c->inversion); + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + switch (c->bandwidth_hz) { + case 6000000: + i = 0; + break; + case 7000000: + i = 1; + break; + case 8000000: + i = 2; + break; + default: + dbg("invalid bandwidth"); + return -EINVAL; + } + + ret = rtl2830_wr_reg_mask(priv, 0x008, i << 1, 0x06); + if (ret) + goto err; + + /* 1/2 split I2C write */ + ret = rtl2830_wr_regs(priv, 0x11c, &bw_params1[i][0], 17); + if (ret) + goto err; + + /* 2/2 split I2C write */ + ret = rtl2830_wr_regs(priv, 0x12d, &bw_params1[i][17], 17); + if (ret) + goto err; + + ret = rtl2830_wr_regs(priv, 0x19d, bw_params2[i], 6); + if (ret) + goto err; + + return ret; +err: + dbg("%s: failed=%d", __func__, ret); + return ret; +} + +static int rtl2830_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + int ret; + u8 tmp; + *status = 0; + + if (priv->sleeping) + return 0; + + ret = rtl2830_rd_reg_mask(priv, 0x351, &tmp, 0x78); /* [6:3] */ + if (ret) + goto err; + + if (tmp == 11) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + } else if (tmp == 10) { + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | + FE_HAS_VITERBI; + } + + return ret; +err: + dbg("%s: failed=%d", __func__, ret); + return ret; +} + +static int rtl2830_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + *snr = 0; + return 0; +} + +static int rtl2830_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + *ber = 0; + return 0; +} + +static int rtl2830_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + return 0; +} + +static int rtl2830_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + *strength = 0; + return 0; +} + +static struct dvb_frontend_ops rtl2830_ops; + +static u32 rtl2830_tuner_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static int rtl2830_tuner_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msg[], int num) +{ + struct rtl2830_priv *priv = i2c_get_adapdata(i2c_adap); + int ret; + + /* open i2c-gate */ + ret = rtl2830_wr_reg_mask(priv, 0x101, 0x08, 0x08); + if (ret) + goto err; + + ret = i2c_transfer(priv->i2c, msg, num); + if (ret < 0) + warn("tuner i2c failed=%d", ret); + + return ret; +err: + dbg("%s: failed=%d", __func__, ret); + return ret; +} + +static struct i2c_algorithm rtl2830_tuner_i2c_algo = { + .master_xfer = rtl2830_tuner_i2c_xfer, + .functionality = rtl2830_tuner_i2c_func, +}; + +struct i2c_adapter *rtl2830_get_tuner_i2c_adapter(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + return &priv->tuner_i2c_adapter; +} +EXPORT_SYMBOL(rtl2830_get_tuner_i2c_adapter); + +static void rtl2830_release(struct dvb_frontend *fe) +{ + struct rtl2830_priv *priv = fe->demodulator_priv; + + i2c_del_adapter(&priv->tuner_i2c_adapter); + kfree(priv); +} + +struct dvb_frontend *rtl2830_attach(const struct rtl2830_config *cfg, + struct i2c_adapter *i2c) +{ + struct rtl2830_priv *priv = NULL; + int ret = 0; + u8 tmp; + + /* allocate memory for the internal state */ + priv = kzalloc(sizeof(struct rtl2830_priv), GFP_KERNEL); + if (priv == NULL) + goto err; + + /* setup the priv */ + priv->i2c = i2c; + memcpy(&priv->cfg, cfg, sizeof(struct rtl2830_config)); + + /* check if the demod is there */ + ret = rtl2830_rd_reg(priv, 0x000, &tmp); + if (ret) + goto err; + + /* create dvb_frontend */ + memcpy(&priv->fe.ops, &rtl2830_ops, sizeof(struct dvb_frontend_ops)); + priv->fe.demodulator_priv = priv; + + /* create tuner i2c adapter */ + strlcpy(priv->tuner_i2c_adapter.name, "RTL2830 tuner I2C adapter", + sizeof(priv->tuner_i2c_adapter.name)); + priv->tuner_i2c_adapter.algo = &rtl2830_tuner_i2c_algo; + priv->tuner_i2c_adapter.algo_data = NULL; + i2c_set_adapdata(&priv->tuner_i2c_adapter, priv); + if (i2c_add_adapter(&priv->tuner_i2c_adapter) < 0) { + err("tuner I2C bus could not be initialized"); + goto err; + } + + priv->sleeping = true; + + return &priv->fe; +err: + dbg("%s: failed=%d", __func__, ret); + kfree(priv); + return NULL; +} +EXPORT_SYMBOL(rtl2830_attach); + +static struct dvb_frontend_ops rtl2830_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Realtek RTL2830 (DVB-T)", + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_QAM_16 | + FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS + }, + + .release = rtl2830_release, + + .init = rtl2830_init, + .sleep = rtl2830_sleep, + + .get_tune_settings = rtl2830_get_tune_settings, + + .set_frontend = rtl2830_set_frontend, + + .read_status = rtl2830_read_status, + .read_snr = rtl2830_read_snr, + .read_ber = rtl2830_read_ber, + .read_ucblocks = rtl2830_read_ucblocks, + .read_signal_strength = rtl2830_read_signal_strength, +}; + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Realtek RTL2830 DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/rtl2830.h b/drivers/media/dvb/frontends/rtl2830.h new file mode 100644 index 000000000000..1c6ee91749c2 --- /dev/null +++ b/drivers/media/dvb/frontends/rtl2830.h @@ -0,0 +1,97 @@ +/* + * Realtek RTL2830 DVB-T demodulator driver + * + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RTL2830_H +#define RTL2830_H + +#include <linux/dvb/frontend.h> + +struct rtl2830_config { + /* + * Demodulator I2C address. + */ + u8 i2c_addr; + + /* + * Xtal frequency. + * Hz + * 4000000, 16000000, 25000000, 28800000 + */ + u32 xtal; + + /* + * TS output mode. + */ + u8 ts_mode; + + /* + * Spectrum inversion. + */ + bool spec_inv; + + /* + * IFs for all used modes. + * Hz + * 4570000, 4571429, 36000000, 36125000, 36166667, 44000000 + */ + u32 if_dvbt; + + /* + */ + u8 vtop; + + /* + */ + u8 krf; + + /* + */ + u8 agc_targ_val; +}; + +#if defined(CONFIG_DVB_RTL2830) || \ + (defined(CONFIG_DVB_RTL2830_MODULE) && defined(MODULE)) +extern struct dvb_frontend *rtl2830_attach( + const struct rtl2830_config *config, + struct i2c_adapter *i2c +); + +extern struct i2c_adapter *rtl2830_get_tuner_i2c_adapter( + struct dvb_frontend *fe +); +#else +static inline struct dvb_frontend *rtl2830_attach( + const struct rtl2830_config *config, + struct i2c_adapter *i2c +) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline struct i2c_adapter *rtl2830_get_tuner_i2c_adapter( + struct dvb_frontend *fe +) +{ + return NULL; +} +#endif + +#endif /* RTL2830_H */ diff --git a/drivers/media/dvb/frontends/rtl2830_priv.h b/drivers/media/dvb/frontends/rtl2830_priv.h new file mode 100644 index 000000000000..4a464761b5b8 --- /dev/null +++ b/drivers/media/dvb/frontends/rtl2830_priv.h @@ -0,0 +1,57 @@ +/* + * Realtek RTL2830 DVB-T demodulator driver + * + * Copyright (C) 2011 Antti Palosaari <crope@iki.fi> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RTL2830_PRIV_H +#define RTL2830_PRIV_H + +#include "dvb_frontend.h" +#include "rtl2830.h" + +#define LOG_PREFIX "rtl2830" + +#undef dbg +#define dbg(f, arg...) \ + if (rtl2830_debug) \ + printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) +#undef err +#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) +#undef info +#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) +#undef warn +#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) + +struct rtl2830_priv { + struct i2c_adapter *i2c; + struct dvb_frontend fe; + struct rtl2830_config cfg; + struct i2c_adapter tuner_i2c_adapter; + + bool sleeping; + + u8 page; /* active register page */ +}; + +struct rtl2830_reg_val_mask { + u16 reg; + u8 val; + u8 mask; +}; + +#endif /* RTL2830_PRIV_H */ diff --git a/drivers/media/dvb/frontends/stb0899_drv.c b/drivers/media/dvb/frontends/stb0899_drv.c index 38565beafe23..dd08f4ac64a8 100644 --- a/drivers/media/dvb/frontends/stb0899_drv.c +++ b/drivers/media/dvb/frontends/stb0899_drv.c @@ -67,7 +67,7 @@ static const struct stb0899_tab stb0899_cn_tab[] = { * Crude linear extrapolation below -84.8dBm and above -8.0dBm. */ static const struct stb0899_tab stb0899_dvbsrf_tab[] = { - { -950, -128 }, + { -750, -128 }, { -748, -94 }, { -745, -92 }, { -735, -90 }, @@ -131,7 +131,7 @@ static const struct stb0899_tab stb0899_dvbs2rf_tab[] = { { -730, 13645 }, { -750, 13909 }, { -766, 14153 }, - { -999, 16383 } + { -950, 16383 } }; /* DVB-S2 Es/N0 quant in dB/100 vs read value * 100*/ @@ -964,6 +964,7 @@ static int stb0899_read_signal_strength(struct dvb_frontend *fe, u16 *strength) int val; u32 reg; + *strength = 0; switch (state->delsys) { case SYS_DVBS: case SYS_DSS: @@ -983,11 +984,11 @@ static int stb0899_read_signal_strength(struct dvb_frontend *fe, u16 *strength) break; case SYS_DVBS2: if (internal->lock) { - reg = STB0899_READ_S2REG(STB0899_DEMOD, IF_AGC_GAIN); + reg = STB0899_READ_S2REG(STB0899_S2DEMOD, IF_AGC_GAIN); val = STB0899_GETFIELD(IF_AGC_GAIN, reg); *strength = stb0899_table_lookup(stb0899_dvbs2rf_tab, ARRAY_SIZE(stb0899_dvbs2rf_tab) - 1, val); - *strength += 750; + *strength += 950; dprintk(state->verbose, FE_DEBUG, 1, "IF_AGC_GAIN = 0x%04x, C = %d * 0.1 dBm", val & 0x3fff, *strength); } @@ -1009,6 +1010,7 @@ static int stb0899_read_snr(struct dvb_frontend *fe, u16 *snr) u8 buf[2]; u32 reg; + *snr = 0; reg = stb0899_read_reg(state, STB0899_VSTATUS); switch (state->delsys) { case SYS_DVBS: @@ -1071,7 +1073,7 @@ static int stb0899_read_status(struct dvb_frontend *fe, enum fe_status *status) reg = stb0899_read_reg(state, STB0899_VSTATUS); if (STB0899_GETFIELD(VSTATUS_LOCKEDVIT, reg)) { dprintk(state->verbose, FE_DEBUG, 1, "--------> FE_HAS_CARRIER | FE_HAS_LOCK"); - *status |= FE_HAS_CARRIER | FE_HAS_LOCK; + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK; reg = stb0899_read_reg(state, STB0899_PLPARM); if (STB0899_GETFIELD(VITCURPUN, reg)) { diff --git a/drivers/media/dvb/frontends/stv0288.c b/drivers/media/dvb/frontends/stv0288.c index fb5548a82208..632b25156e4c 100644 --- a/drivers/media/dvb/frontends/stv0288.c +++ b/drivers/media/dvb/frontends/stv0288.c @@ -506,7 +506,7 @@ static int stv0288_set_frontend(struct dvb_frontend *fe) tda[1] = (unsigned char)tm; stv0288_writeregI(state, 0x2b, tda[1]); stv0288_writeregI(state, 0x2c, tda[2]); - udelay(30); + msleep(30); } state->tuner_frequency = c->frequency; state->fec_inner = FEC_AUTO; diff --git a/drivers/media/dvb/frontends/tda10071.c b/drivers/media/dvb/frontends/tda10071.c index a99205026751..c21bc92d2811 100644 --- a/drivers/media/dvb/frontends/tda10071.c +++ b/drivers/media/dvb/frontends/tda10071.c @@ -1215,7 +1215,7 @@ error: EXPORT_SYMBOL(tda10071_attach); static struct dvb_frontend_ops tda10071_ops = { - .delsys = { SYS_DVBT, SYS_DVBT2 }, + .delsys = { SYS_DVBS, SYS_DVBS2 }, .info = { .name = "NXP TDA10071", .frequency_min = 950000, diff --git a/drivers/media/dvb/ngene/ngene-cards.c b/drivers/media/dvb/ngene/ngene-cards.c index 8418c02bcefe..7539a5d71029 100644 --- a/drivers/media/dvb/ngene/ngene-cards.c +++ b/drivers/media/dvb/ngene/ngene-cards.c @@ -216,6 +216,7 @@ static int demod_attach_drxk(struct ngene_channel *chan, struct drxk_config config; memset(&config, 0, sizeof(config)); + config.microcode_name = "drxk_a3.mc"; config.adr = 0x29 + (chan->number ^ 2); chan->fe = dvb_attach(drxk_attach, &config, i2c); diff --git a/drivers/media/dvb/pt1/pt1.c b/drivers/media/dvb/pt1/pt1.c index b81df5fafe26..15b35c4725f1 100644 --- a/drivers/media/dvb/pt1/pt1.c +++ b/drivers/media/dvb/pt1/pt1.c @@ -28,6 +28,7 @@ #include <linux/pci.h> #include <linux/kthread.h> #include <linux/freezer.h> +#include <linux/ratelimit.h> #include "dvbdev.h" #include "dvb_demux.h" @@ -77,6 +78,8 @@ struct pt1 { struct pt1_adapter *adaps[PT1_NR_ADAPS]; struct pt1_table *tables; struct task_struct *kthread; + int table_index; + int buf_index; struct mutex lock; int power; @@ -90,12 +93,12 @@ struct pt1_adapter { u8 *buf; int upacket_count; int packet_count; + int st_count; struct dvb_adapter adap; struct dvb_demux demux; int users; struct dmxdev dmxdev; - struct dvb_net net; struct dvb_frontend *fe; int (*orig_set_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage); @@ -119,7 +122,7 @@ static u32 pt1_read_reg(struct pt1 *pt1, int reg) return readl(pt1->regs + reg * 4); } -static int pt1_nr_tables = 64; +static int pt1_nr_tables = 8; module_param_named(nr_tables, pt1_nr_tables, int, 0); static void pt1_increment_table_count(struct pt1 *pt1) @@ -264,6 +267,7 @@ static int pt1_filter(struct pt1 *pt1, struct pt1_buffer_page *page) struct pt1_adapter *adap; int offset; u8 *buf; + int sc; if (!page->upackets[PT1_NR_UPACKETS - 1]) return 0; @@ -280,6 +284,16 @@ static int pt1_filter(struct pt1 *pt1, struct pt1_buffer_page *page) else if (!adap->upacket_count) continue; + if (upacket >> 24 & 1) + printk_ratelimited(KERN_INFO "earth-pt1: device " + "buffer overflowing. table[%d] buf[%d]\n", + pt1->table_index, pt1->buf_index); + sc = upacket >> 26 & 0x7; + if (adap->st_count != -1 && sc != ((adap->st_count + 1) & 0x7)) + printk_ratelimited(KERN_INFO "earth-pt1: data loss" + " in streamID(adapter)[%d]\n", index); + adap->st_count = sc; + buf = adap->buf; offset = adap->packet_count * 188 + adap->upacket_count * 3; buf[offset] = upacket >> 16; @@ -303,30 +317,25 @@ static int pt1_filter(struct pt1 *pt1, struct pt1_buffer_page *page) static int pt1_thread(void *data) { struct pt1 *pt1; - int table_index; - int buf_index; struct pt1_buffer_page *page; pt1 = data; set_freezable(); - table_index = 0; - buf_index = 0; - while (!kthread_should_stop()) { try_to_freeze(); - page = pt1->tables[table_index].bufs[buf_index].page; + page = pt1->tables[pt1->table_index].bufs[pt1->buf_index].page; if (!pt1_filter(pt1, page)) { schedule_timeout_interruptible((HZ + 999) / 1000); continue; } - if (++buf_index >= PT1_NR_BUFS) { + if (++pt1->buf_index >= PT1_NR_BUFS) { pt1_increment_table_count(pt1); - buf_index = 0; - if (++table_index >= pt1_nr_tables) - table_index = 0; + pt1->buf_index = 0; + if (++pt1->table_index >= pt1_nr_tables) + pt1->table_index = 0; } } @@ -477,21 +486,60 @@ err: return ret; } +static int pt1_start_polling(struct pt1 *pt1) +{ + int ret = 0; + + mutex_lock(&pt1->lock); + if (!pt1->kthread) { + pt1->kthread = kthread_run(pt1_thread, pt1, "earth-pt1"); + if (IS_ERR(pt1->kthread)) { + ret = PTR_ERR(pt1->kthread); + pt1->kthread = NULL; + } + } + mutex_unlock(&pt1->lock); + return ret; +} + static int pt1_start_feed(struct dvb_demux_feed *feed) { struct pt1_adapter *adap; adap = container_of(feed->demux, struct pt1_adapter, demux); - if (!adap->users++) + if (!adap->users++) { + int ret; + + ret = pt1_start_polling(adap->pt1); + if (ret) + return ret; pt1_set_stream(adap->pt1, adap->index, 1); + } return 0; } +static void pt1_stop_polling(struct pt1 *pt1) +{ + int i, count; + + mutex_lock(&pt1->lock); + for (i = 0, count = 0; i < PT1_NR_ADAPS; i++) + count += pt1->adaps[i]->users; + + if (count == 0 && pt1->kthread) { + kthread_stop(pt1->kthread); + pt1->kthread = NULL; + } + mutex_unlock(&pt1->lock); +} + static int pt1_stop_feed(struct dvb_demux_feed *feed) { struct pt1_adapter *adap; adap = container_of(feed->demux, struct pt1_adapter, demux); - if (!--adap->users) + if (!--adap->users) { pt1_set_stream(adap->pt1, adap->index, 0); + pt1_stop_polling(adap->pt1); + } return 0; } @@ -575,7 +623,6 @@ static int pt1_wakeup(struct dvb_frontend *fe) static void pt1_free_adapter(struct pt1_adapter *adap) { - dvb_net_release(&adap->net); adap->demux.dmx.close(&adap->demux.dmx); dvb_dmxdev_release(&adap->dmxdev); dvb_dmx_release(&adap->demux); @@ -616,6 +663,7 @@ pt1_alloc_adapter(struct pt1 *pt1) adap->buf = buf; adap->upacket_count = 0; adap->packet_count = 0; + adap->st_count = -1; dvb_adap = &adap->adap; dvb_adap->priv = adap; @@ -644,8 +692,6 @@ pt1_alloc_adapter(struct pt1 *pt1) if (ret < 0) goto err_dmx_release; - dvb_net_init(dvb_adap, &adap->net, &demux->dmx); - return adap; err_dmx_release: @@ -1020,7 +1066,8 @@ static void __devexit pt1_remove(struct pci_dev *pdev) pt1 = pci_get_drvdata(pdev); regs = pt1->regs; - kthread_stop(pt1->kthread); + if (pt1->kthread) + kthread_stop(pt1->kthread); pt1_cleanup_tables(pt1); pt1_cleanup_frontends(pt1); pt1_disable_ram(pt1); @@ -1043,7 +1090,6 @@ pt1_probe(struct pci_dev *pdev, const struct pci_device_id *ent) void __iomem *regs; struct pt1 *pt1; struct i2c_adapter *i2c_adap; - struct task_struct *kthread; ret = pci_enable_device(pdev); if (ret < 0) @@ -1139,17 +1185,8 @@ pt1_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (ret < 0) goto err_pt1_cleanup_frontends; - kthread = kthread_run(pt1_thread, pt1, "pt1"); - if (IS_ERR(kthread)) { - ret = PTR_ERR(kthread); - goto err_pt1_cleanup_tables; - } - - pt1->kthread = kthread; return 0; -err_pt1_cleanup_tables: - pt1_cleanup_tables(pt1); err_pt1_cleanup_frontends: pt1_cleanup_frontends(pt1); err_pt1_disable_ram: diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c index 7b42ace419d9..421cf73858d3 100644 --- a/drivers/media/media-devnode.c +++ b/drivers/media/media-devnode.c @@ -312,7 +312,7 @@ static void __exit media_devnode_exit(void) unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); } -module_init(media_devnode_init) +subsys_initcall(media_devnode_init); module_exit(media_devnode_exit) MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index e954781c90bf..8db2d7f4b52a 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -43,7 +43,7 @@ config USB_DSBR config RADIO_MAXIRADIO tristate "Guillemot MAXI Radio FM 2000 radio" - depends on VIDEO_V4L2 && PCI + depends on VIDEO_V4L2 && PCI && SND ---help--- Choose Y here if you have this radio card. This card may also be found as Gemtek PCI FM. @@ -80,6 +80,16 @@ config RADIO_SI4713 To compile this driver as a module, choose M here: the module will be called radio-si4713. +config USB_KEENE + tristate "Keene FM Transmitter USB support" + depends on USB && VIDEO_V4L2 + ---help--- + Say Y here if you want to connect this type of FM transmitter + to your computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called radio-keene. + config RADIO_TEA5764 tristate "TEA5764 I2C FM radio support" depends on I2C && VIDEO_V4L2 @@ -167,6 +177,10 @@ menuconfig V4L_RADIO_ISA_DRIVERS if V4L_RADIO_ISA_DRIVERS +config RADIO_ISA + depends on ISA + tristate + config RADIO_CADET tristate "ADS Cadet AM/FM Tuner" depends on ISA && VIDEO_V4L2 @@ -174,20 +188,13 @@ config RADIO_CADET Choose Y here if you have one of these AM/FM radio cards, and then fill in the port address below. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. - - Further documentation on this driver can be found on the WWW at - <http://linux.blackhawke.net/cadet/>. - To compile this driver as a module, choose M here: the module will be called radio-cadet. config RADIO_RTRACK tristate "AIMSlab RadioTrack (aka RadioReveal) support" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address below. @@ -201,11 +208,7 @@ config RADIO_RTRACK You must also pass the module a suitable io parameter, 0x248 has been reported to be used by these cards. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. More information is - contained in the file + More information is contained in the file <file:Documentation/video4linux/radiotrack.txt>. To compile this driver as a module, choose M here: the @@ -214,7 +217,7 @@ config RADIO_RTRACK config RADIO_RTRACK_PORT hex "RadioTrack i/o port (0x20f or 0x30f)" depends on RADIO_RTRACK=y - default "20f" + default "30f" help Enter either 0x30f or 0x20f here. The card default is 0x30f, if you haven't changed the jumper setting on the card. @@ -222,14 +225,14 @@ config RADIO_RTRACK_PORT config RADIO_RTRACK2 tristate "AIMSlab RadioTrack II support" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have this FM radio card, and then fill in the port address below. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-rtrack2. @@ -245,15 +248,11 @@ config RADIO_RTRACK2_PORT config RADIO_AZTECH tristate "Aztech/Packard Bell Radio" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address below. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. - To compile this driver as a module, choose M here: the module will be called radio-aztech. @@ -269,6 +268,7 @@ config RADIO_AZTECH_PORT config RADIO_GEMTEK tristate "GemTek Radio card (or compatible) support" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have this FM radio card, and then fill in the I/O port address and settings below. The following cards either have @@ -278,23 +278,21 @@ config RADIO_GEMTEK - Typhoon Radio card (some models) - Hama Radio card - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. - To compile this driver as a module, choose M here: the module will be called radio-gemtek. config RADIO_GEMTEK_PORT - hex "Fixed I/O port (0x20c, 0x30c, 0x24c, 0x34c, 0c24c or 0x28c)" + hex "Fixed I/O port (0x20c, 0x30c, 0x24c, 0x34c, 0x248 or 0x28c)" depends on RADIO_GEMTEK=y default "34c" help - Enter either 0x20c, 0x30c, 0x24c or 0x34c here. The card default is - 0x34c, if you haven't changed the jumper setting on the card. On - Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O + Enter either 0x20c, 0x30c, 0x24c, 0x34c, 0x248 or 0x28c here. The + card default is 0x34c, if you haven't changed the jumper setting + on the card. + + On Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O port is 0x20c, 0x248 or 0x28c. + If automatic I/O port probing is enabled this port will be used only in case of automatic probing failure, ie. as a fallback. @@ -318,11 +316,6 @@ config RADIO_MIROPCM20 sound card driver "Miro miroSOUND PCM1pro/PCM12/PCM20radio" as this is required for the radio-miropcm20. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. - To compile this driver as a module, choose M here: the module will be called radio-miropcm20. @@ -332,11 +325,6 @@ config RADIO_SF16FMI ---help--- Choose Y here if you have one of these FM radio cards. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. - To compile this driver as a module, choose M here: the module will be called radio-sf16fmi. @@ -346,50 +334,35 @@ config RADIO_SF16FMR2 ---help--- Choose Y here if you have one of these FM radio cards. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found on the WWW at - <http://roadrunner.swansea.uk.linux.org/v4l.shtml>. - To compile this driver as a module, choose M here: the module will be called radio-sf16fmr2. config RADIO_TERRATEC tristate "TerraTec ActiveRadio ISA Standalone" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- - Choose Y here if you have this FM radio card, and then fill in the - port address below. (TODO) + Choose Y here if you have this FM radio card. - Note: This driver is in its early stages. Right now volume and - frequency control and muting works at least for me, but - unfortunately I have not found anybody who wants to use this card - with Linux. So if it is this what YOU are trying to do right now, - PLEASE DROP ME A NOTE!! Rolf Offermanns <rolf@offermanns.de>. - - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-terratec. -config RADIO_TERRATEC_PORT - hex "Terratec i/o port (normally 0x590)" - depends on RADIO_TERRATEC=y - default "590" - help - Fill in the I/O port of your TerraTec FM radio card. If unsure, go - with the default. - config RADIO_TRUST tristate "Trust FM radio card" depends on ISA && VIDEO_V4L2 + select RADIO_ISA help This is a driver for the Trust FM radio cards. Say Y if you have such a card and want to use it under Linux. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. + To compile this driver as a module, choose M here: the module will be called radio-trust. @@ -404,14 +377,14 @@ config RADIO_TRUST_PORT config RADIO_TYPHOON tristate "Typhoon Radio (a.k.a. EcoRadio)" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address and the frequency used for muting below. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-typhoon. @@ -438,14 +411,14 @@ config RADIO_TYPHOON_MUTEFREQ config RADIO_ZOLTRIX tristate "Zoltrix Radio" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address below. - In order to control your radio card, you will need to use programs - that are compatible with the Video For Linux API. Information on - this API and pointers to "v4l" programs may be found at - <file:Documentation/video4linux/API.html>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-zoltrix. diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 390daf94d847..ca8c7d134b95 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -2,6 +2,7 @@ # Makefile for the kernel character device drivers. # +obj-$(CONFIG_RADIO_ISA) += radio-isa.o obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o @@ -20,6 +21,7 @@ obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ obj-$(CONFIG_USB_MR800) += radio-mr800.o +obj-$(CONFIG_USB_KEENE) += radio-keene.o obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o obj-$(CONFIG_RADIO_TEF6862) += tef6862.o diff --git a/drivers/media/radio/radio-aimslab.c b/drivers/media/radio/radio-aimslab.c index 1c3f8440a55c..98e0c8c20312 100644 --- a/drivers/media/radio/radio-aimslab.c +++ b/drivers/media/radio/radio-aimslab.c @@ -1,16 +1,13 @@ -/* radiotrack (radioreveal) driver for Linux radio support - * (c) 1997 M. Kirkwood +/* + * AimsLab RadioTrack (aka RadioVeveal) driver + * + * Copyright 1997 M. Kirkwood + * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> * - * History: - * 1999-02-24 Russell Kroll <rkroll@exploits.org> - * Fine tuning/VIDEO_TUNER_LOW - * Frequency range expanded to start at 87 MHz - * - * TODO: Allow for more than one of these foolish entities :-) - * * Notes on the hardware (reverse engineered from other peoples' * reverse engineering of AIMS' code :-) * @@ -26,6 +23,7 @@ * wait(a_wee_while); * out(port, stop_changing_the_volume); * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -34,401 +32,179 @@ #include <linux/delay.h> /* msleep */ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include "radio-isa.h" -MODULE_AUTHOR("M.Kirkwood"); +MODULE_AUTHOR("M. Kirkwood"); MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("1.0.0"); #ifndef CONFIG_RADIO_RTRACK_PORT #define CONFIG_RADIO_RTRACK_PORT -1 #endif -static int io = CONFIG_RADIO_RTRACK_PORT; -static int radio_nr = -1; +#define RTRACK_MAX 2 -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)"); -module_param(radio_nr, int, 0); +static int io[RTRACK_MAX] = { [0] = CONFIG_RADIO_RTRACK_PORT, + [1 ... (RTRACK_MAX - 1)] = -1 }; +static int radio_nr[RTRACK_MAX] = { [0 ... (RTRACK_MAX - 1)] = -1 }; -struct rtrack -{ - struct v4l2_device v4l2_dev; - struct video_device vdev; - int port; +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + +struct rtrack { + struct radio_isa_card isa; int curvol; - unsigned long curfreq; - int muted; - int io; - struct mutex lock; }; -static struct rtrack rtrack_card; - -/* local things */ - -static void rt_decvol(struct rtrack *rt) -{ - outb(0x58, rt->io); /* volume down + sigstr + on */ - msleep(100); - outb(0xd8, rt->io); /* volume steady + sigstr + on */ -} - -static void rt_incvol(struct rtrack *rt) -{ - outb(0x98, rt->io); /* volume up + sigstr + on */ - msleep(100); - outb(0xd8, rt->io); /* volume steady + sigstr + on */ -} - -static void rt_mute(struct rtrack *rt) -{ - rt->muted = 1; - mutex_lock(&rt->lock); - outb(0xd0, rt->io); /* volume steady, off */ - mutex_unlock(&rt->lock); -} - -static int rt_setvol(struct rtrack *rt, int vol) +static struct radio_isa_card *rtrack_alloc(void) { - int i; - - mutex_lock(&rt->lock); - - if (vol == rt->curvol) { /* requested volume = current */ - if (rt->muted) { /* user is unmuting the card */ - rt->muted = 0; - outb(0xd8, rt->io); /* enable card */ - } - mutex_unlock(&rt->lock); - return 0; - } - - if (vol == 0) { /* volume = 0 means mute the card */ - outb(0x48, rt->io); /* volume down but still "on" */ - msleep(2000); /* make sure it's totally down */ - outb(0xd0, rt->io); /* volume steady, off */ - rt->curvol = 0; /* track the volume state! */ - mutex_unlock(&rt->lock); - return 0; - } + struct rtrack *rt = kzalloc(sizeof(struct rtrack), GFP_KERNEL); - rt->muted = 0; - if (vol > rt->curvol) - for (i = rt->curvol; i < vol; i++) - rt_incvol(rt); - else - for (i = rt->curvol; i > vol; i--) - rt_decvol(rt); - - rt->curvol = vol; - mutex_unlock(&rt->lock); - return 0; + if (rt) + rt->curvol = 0xff; + return rt ? &rt->isa : NULL; } -/* the 128+64 on these outb's is to keep the volume stable while tuning - * without them, the volume _will_ creep up with each frequency change - * and bit 4 (+16) is to keep the signal strength meter enabled +/* The 128+64 on these outb's is to keep the volume stable while tuning. + * Without them, the volume _will_ creep up with each frequency change + * and bit 4 (+16) is to keep the signal strength meter enabled. */ -static void send_0_byte(struct rtrack *rt) +static void send_0_byte(struct radio_isa_card *isa, int on) { - if (rt->curvol == 0 || rt->muted) { - outb_p(128+64+16+ 1, rt->io); /* wr-enable + data low */ - outb_p(128+64+16+2+1, rt->io); /* clock */ - } - else { - outb_p(128+64+16+8+ 1, rt->io); /* on + wr-enable + data low */ - outb_p(128+64+16+8+2+1, rt->io); /* clock */ - } + outb_p(128+64+16+on+1, isa->io); /* wr-enable + data low */ + outb_p(128+64+16+on+2+1, isa->io); /* clock */ msleep(1); } -static void send_1_byte(struct rtrack *rt) +static void send_1_byte(struct radio_isa_card *isa, int on) { - if (rt->curvol == 0 || rt->muted) { - outb_p(128+64+16+4 +1, rt->io); /* wr-enable+data high */ - outb_p(128+64+16+4+2+1, rt->io); /* clock */ - } - else { - outb_p(128+64+16+8+4 +1, rt->io); /* on+wr-enable+data high */ - outb_p(128+64+16+8+4+2+1, rt->io); /* clock */ - } - + outb_p(128+64+16+on+4+1, isa->io); /* wr-enable+data high */ + outb_p(128+64+16+on+4+2+1, isa->io); /* clock */ msleep(1); } -static int rt_setfreq(struct rtrack *rt, unsigned long freq) +static int rtrack_s_frequency(struct radio_isa_card *isa, u32 freq) { + int on = v4l2_ctrl_g_ctrl(isa->mute) ? 0 : 8; int i; - mutex_lock(&rt->lock); /* Stop other ops interfering */ - - rt->curfreq = freq; - - /* now uses VIDEO_TUNER_LOW for fine tuning */ - freq += 171200; /* Add 10.7 MHz IF */ freq /= 800; /* Convert to 50 kHz units */ - send_0_byte(rt); /* 0: LSB of frequency */ + send_0_byte(isa, on); /* 0: LSB of frequency */ for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ if (freq & (1 << i)) - send_1_byte(rt); + send_1_byte(isa, on); else - send_0_byte(rt); - - send_0_byte(rt); /* 14: test bit - always 0 */ - send_0_byte(rt); /* 15: test bit - always 0 */ - - send_0_byte(rt); /* 16: band data 0 - always 0 */ - send_0_byte(rt); /* 17: band data 1 - always 0 */ - send_0_byte(rt); /* 18: band data 2 - always 0 */ - send_0_byte(rt); /* 19: time base - always 0 */ - - send_0_byte(rt); /* 20: spacing (0 = 25 kHz) */ - send_1_byte(rt); /* 21: spacing (1 = 25 kHz) */ - send_0_byte(rt); /* 22: spacing (0 = 25 kHz) */ - send_1_byte(rt); /* 23: AM/FM (FM = 1, always) */ - - if (rt->curvol == 0 || rt->muted) - outb(0xd0, rt->io); /* volume steady + sigstr */ - else - outb(0xd8, rt->io); /* volume steady + sigstr + on */ - - mutex_unlock(&rt->lock); - - return 0; -} - -static int rt_getsigstr(struct rtrack *rt) -{ - int sig = 1; - - mutex_lock(&rt->lock); - if (inb(rt->io) & 2) /* bit set = no signal present */ - sig = 0; - mutex_unlock(&rt->lock); - return sig; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-aimslab", sizeof(v->driver)); - strlcpy(v->card, "RadioTrack", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct rtrack *rt = video_drvdata(file); + send_0_byte(isa, on); - if (v->index > 0) - return -EINVAL; + send_0_byte(isa, on); /* 14: test bit - always 0 */ + send_0_byte(isa, on); /* 15: test bit - always 0 */ - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xffff * rt_getsigstr(rt); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct rtrack *rt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - rt_setfreq(rt, f->frequency); - return 0; -} + send_0_byte(isa, on); /* 16: band data 0 - always 0 */ + send_0_byte(isa, on); /* 17: band data 1 - always 0 */ + send_0_byte(isa, on); /* 18: band data 2 - always 0 */ + send_0_byte(isa, on); /* 19: time base - always 0 */ -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct rtrack *rt = video_drvdata(file); + send_0_byte(isa, on); /* 20: spacing (0 = 25 kHz) */ + send_1_byte(isa, on); /* 21: spacing (1 = 25 kHz) */ + send_0_byte(isa, on); /* 22: spacing (0 = 25 kHz) */ + send_1_byte(isa, on); /* 23: AM/FM (FM = 1, always) */ - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = rt->curfreq; + outb(0xd0 + on, isa->io); /* volume steady + sigstr */ return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) +static u32 rtrack_g_signal(struct radio_isa_card *isa) { - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff); - } - return -EINVAL; + /* bit set = no signal present */ + return 0xffff * !(inb(isa->io) & 2); } -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int rtrack_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - struct rtrack *rt = video_drvdata(file); + struct rtrack *rt = container_of(isa, struct rtrack, isa); + int curvol = rt->curvol; - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = rt->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = rt->curvol; + if (mute) { + outb(0xd0, isa->io); /* volume steady + sigstr + off */ return 0; } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct rtrack *rt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - rt_mute(rt); - else - rt_setvol(rt, rt->curvol); - return 0; - case V4L2_CID_AUDIO_VOLUME: - rt_setvol(rt, ctrl->value); - return 0; + if (vol == 0) { /* volume = 0 means mute the card */ + outb(0x48, isa->io); /* volume down but still "on" */ + msleep(curvol * 3); /* make sure it's totally down */ + } else if (curvol < vol) { + outb(0x98, isa->io); /* volume up + sigstr + on */ + for (; curvol < vol; curvol++) + udelay(3000); + } else if (curvol > vol) { + outb(0x58, isa->io); /* volume down + sigstr + on */ + for (; curvol > vol; curvol--) + udelay(3000); } - return -EINVAL; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; + outb(0xd8, isa->io); /* volume steady + sigstr + on */ + rt->curvol = vol; return 0; } -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) +/* Mute card - prevents noisy bootups */ +static int rtrack_initialize(struct radio_isa_card *isa) { - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; + /* this ensures that the volume is all the way up */ + outb(0x90, isa->io); /* volume up but still "on" */ + msleep(3000); /* make sure it's totally up */ + outb(0xc0, isa->io); /* steady volume, mute card */ return 0; } -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - -static const struct v4l2_file_operations rtrack_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops rtrack_ops = { + .alloc = rtrack_alloc, + .init = rtrack_initialize, + .s_mute_volume = rtrack_s_mute_volume, + .s_frequency = rtrack_s_frequency, + .g_signal = rtrack_g_signal, }; -static const struct v4l2_ioctl_ops rtrack_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, +static const int rtrack_ioports[] = { 0x20f, 0x30f }; + +static struct radio_isa_driver rtrack_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-aimslab", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = rtrack_ioports, + .num_of_io_ports = ARRAY_SIZE(rtrack_ioports), + .region_size = 2, + .card = "AIMSlab RadioTrack/RadioReveal", + .ops = &rtrack_ops, + .has_stereo = true, + .max_volume = 0xff, }; static int __init rtrack_init(void) { - struct rtrack *rt = &rtrack_card; - struct v4l2_device *v4l2_dev = &rt->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "rtrack", sizeof(v4l2_dev->name)); - rt->io = io; - - if (rt->io == -1) { - v4l2_err(v4l2_dev, "you must set an I/O address with io=0x20f or 0x30f\n"); - return -EINVAL; - } - - if (!request_region(rt->io, 2, "rtrack")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", rt->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(rt->io, 2); - v4l2_err(v4l2_dev, "could not register v4l2_device\n"); - return res; - } - - strlcpy(rt->vdev.name, v4l2_dev->name, sizeof(rt->vdev.name)); - rt->vdev.v4l2_dev = v4l2_dev; - rt->vdev.fops = &rtrack_fops; - rt->vdev.ioctl_ops = &rtrack_ioctl_ops; - rt->vdev.release = video_device_release_empty; - video_set_drvdata(&rt->vdev, rt); - - /* Set up the I/O locking */ - - mutex_init(&rt->lock); - - /* mute card - prevents noisy bootups */ - - /* this ensures that the volume is all the way down */ - outb(0x48, rt->io); /* volume down but still "on" */ - msleep(2000); /* make sure it's totally down */ - outb(0xc0, rt->io); /* steady volume, mute card */ - - if (video_register_device(&rt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(&rt->v4l2_dev); - release_region(rt->io, 2); - return -EINVAL; - } - v4l2_info(v4l2_dev, "AIMSlab RadioTrack/RadioReveal card driver.\n"); - - return 0; + return isa_register_driver(&rtrack_driver.driver, RTRACK_MAX); } static void __exit rtrack_exit(void) { - struct rtrack *rt = &rtrack_card; - - video_unregister_device(&rt->vdev); - v4l2_device_unregister(&rt->v4l2_dev); - release_region(rt->io, 2); + isa_unregister_driver(&rtrack_driver.driver); } module_init(rtrack_init); module_exit(rtrack_exit); - diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c index eed7b0840734..177bcbd7a7c1 100644 --- a/drivers/media/radio/radio-aztech.c +++ b/drivers/media/radio/radio-aztech.c @@ -1,5 +1,7 @@ -/* radio-aztech.c - Aztech radio card driver for Linux 2.2 +/* + * radio-aztech.c - Aztech radio card driver * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@xs4all.nl> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> * Adapted to support the Video for Linux API by * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: @@ -10,19 +12,7 @@ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) * William McGrath (wmcgrath@twilight.vtc.vsc.edu) * - * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ - * along with more information on the card itself. - * - * History: - * 1999-02-24 Russell Kroll <rkroll@exploits.org> - * Fine tuning/VIDEO_TUNER_LOW - * Range expanded to 87-108 MHz (from 87.9-107.8) - * - * Notable changes from the original source: - * - includes stripped down to the essentials - * - for loops used as delays replaced with udelay() - * - #defines removed, changed to static values - * - tuning structure changed - no more character arrays, other changes + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -31,126 +21,72 @@ #include <linux/delay.h> /* udelay */ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include "radio-isa.h" MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); MODULE_DESCRIPTION("A driver for the Aztech radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("1.0.0"); /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ - #ifndef CONFIG_RADIO_AZTECH_PORT #define CONFIG_RADIO_AZTECH_PORT -1 #endif -static int io = CONFIG_RADIO_AZTECH_PORT; -static int radio_nr = -1; -static int radio_wait_time = 1000; +#define AZTECH_MAX 2 -module_param(io, int, 0); -module_param(radio_nr, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); +static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT, + [1 ... (AZTECH_MAX - 1)] = -1 }; +static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 }; +static const int radio_wait_time = 1000; -struct aztech -{ - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + +struct aztech { + struct radio_isa_card isa; int curvol; - unsigned long curfreq; - int stereo; - struct mutex lock; }; -static struct aztech aztech_card; - -static int volconvert(int level) -{ - level >>= 14; /* Map 16bits down to 2 bit */ - level &= 3; - - /* convert to card-friendly values */ - switch (level) { - case 0: - return 0; - case 1: - return 1; - case 2: - return 4; - case 3: - return 5; - } - return 0; /* Quieten gcc */ -} - static void send_0_byte(struct aztech *az) { udelay(radio_wait_time); - outb_p(2 + volconvert(az->curvol), az->io); - outb_p(64 + 2 + volconvert(az->curvol), az->io); + outb_p(2 + az->curvol, az->isa.io); + outb_p(64 + 2 + az->curvol, az->isa.io); } static void send_1_byte(struct aztech *az) { - udelay (radio_wait_time); - outb_p(128 + 2 + volconvert(az->curvol), az->io); - outb_p(128 + 64 + 2 + volconvert(az->curvol), az->io); -} - -static int az_setvol(struct aztech *az, int vol) -{ - mutex_lock(&az->lock); - outb(volconvert(vol), az->io); - mutex_unlock(&az->lock); - return 0; -} - -/* thanks to Michael Dwyer for giving me a dose of clues in - * the signal strength department.. - * - * This card has a stereo bit - bit 0 set = mono, not set = stereo - * It also has a "signal" bit - bit 1 set = bad signal, not set = good - * - */ - -static int az_getsigstr(struct aztech *az) -{ - int sig = 1; - - mutex_lock(&az->lock); - if (inb(az->io) & 2) /* bit set = no signal present */ - sig = 0; - mutex_unlock(&az->lock); - return sig; + udelay(radio_wait_time); + outb_p(128 + 2 + az->curvol, az->isa.io); + outb_p(128 + 64 + 2 + az->curvol, az->isa.io); } -static int az_getstereo(struct aztech *az) +static struct radio_isa_card *aztech_alloc(void) { - int stereo = 1; + struct aztech *az = kzalloc(sizeof(*az), GFP_KERNEL); - mutex_lock(&az->lock); - if (inb(az->io) & 1) /* bit set = mono */ - stereo = 0; - mutex_unlock(&az->lock); - return stereo; + return az ? &az->isa : NULL; } -static int az_setfreq(struct aztech *az, unsigned long frequency) +static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq) { + struct aztech *az = container_of(isa, struct aztech, isa); int i; - mutex_lock(&az->lock); - - az->curfreq = frequency; - frequency += 171200; /* Add 10.7 MHz IF */ - frequency /= 800; /* Convert to 50 kHz units */ + freq += 171200; /* Add 10.7 MHz IF */ + freq /= 800; /* Convert to 50 kHz units */ send_0_byte(az); /* 0: LSB of frequency */ for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ - if (frequency & (1 << i)) + if (freq & (1 << i)) send_1_byte(az); else send_0_byte(az); @@ -158,7 +94,7 @@ static int az_setfreq(struct aztech *az, unsigned long frequency) send_0_byte(az); /* 14: test bit - always 0 */ send_0_byte(az); /* 15: test bit - always 0 */ send_0_byte(az); /* 16: band data 0 - always 0 */ - if (az->stereo) /* 17: stereo (1 to enable) */ + if (isa->stereo) /* 17: stereo (1 to enable) */ send_1_byte(az); else send_0_byte(az); @@ -173,225 +109,77 @@ static int az_setfreq(struct aztech *az, unsigned long frequency) /* latch frequency */ udelay(radio_wait_time); - outb_p(128 + 64 + volconvert(az->curvol), az->io); - - mutex_unlock(&az->lock); - - return 0; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-aztech", sizeof(v->driver)); - strlcpy(v->card, "Aztech Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct aztech *az = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - - v->rangelow = 87 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (az_getstereo(az)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * az_getsigstr(az); - - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} + outb_p(128 + 64 + az->curvol, az->isa.io); -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; return 0; } -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) +/* thanks to Michael Dwyer for giving me a dose of clues in + * the signal strength department.. + * + * This card has a stereo bit - bit 0 set = mono, not set = stereo + */ +static u32 aztech_g_rxsubchans(struct radio_isa_card *isa) { - return a->index ? -EINVAL : 0; + if (inb(isa->io) & 1) + return V4L2_TUNER_SUB_MONO; + return V4L2_TUNER_SUB_STEREO; } -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) +static int aztech_s_stereo(struct radio_isa_card *isa, bool stereo) { - struct aztech *az = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - az_setfreq(az, f->frequency); - return 0; + return aztech_s_frequency(isa, isa->freq); } -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) +static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - struct aztech *az = video_drvdata(file); + struct aztech *az = container_of(isa, struct aztech, isa); - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = az->curfreq; + if (mute) + vol = 0; + az->curvol = (vol & 1) + ((vol & 2) << 1); + outb(az->curvol, isa->io); return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct aztech *az = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (az->curvol == 0) - ctrl->value = 1; - else - ctrl->value = 0; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = az->curvol * 6554; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct aztech *az = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - az_setvol(az, 0); - else - az_setvol(az, az->curvol); - return 0; - case V4L2_CID_AUDIO_VOLUME: - az_setvol(az, ctrl->value); - return 0; - } - return -EINVAL; -} - -static const struct v4l2_file_operations aztech_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops aztech_ops = { + .alloc = aztech_alloc, + .s_mute_volume = aztech_s_mute_volume, + .s_frequency = aztech_s_frequency, + .s_stereo = aztech_s_stereo, + .g_rxsubchans = aztech_g_rxsubchans, }; -static const struct v4l2_ioctl_ops aztech_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, +static const int aztech_ioports[] = { 0x350, 0x358 }; + +static struct radio_isa_driver aztech_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-aztech", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = aztech_ioports, + .num_of_io_ports = ARRAY_SIZE(aztech_ioports), + .region_size = 2, + .card = "Aztech Radio", + .ops = &aztech_ops, + .has_stereo = true, + .max_volume = 3, }; static int __init aztech_init(void) { - struct aztech *az = &aztech_card; - struct v4l2_device *v4l2_dev = &az->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "aztech", sizeof(v4l2_dev->name)); - az->io = io; - - if (az->io == -1) { - v4l2_err(v4l2_dev, "you must set an I/O address with io=0x350 or 0x358\n"); - return -EINVAL; - } - - if (!request_region(az->io, 2, "aztech")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", az->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(az->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - mutex_init(&az->lock); - strlcpy(az->vdev.name, v4l2_dev->name, sizeof(az->vdev.name)); - az->vdev.v4l2_dev = v4l2_dev; - az->vdev.fops = &aztech_fops; - az->vdev.ioctl_ops = &aztech_ioctl_ops; - az->vdev.release = video_device_release_empty; - video_set_drvdata(&az->vdev, az); - /* mute card - prevents noisy bootups */ - outb(0, az->io); - - if (video_register_device(&az->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(az->io, 2); - return -EINVAL; - } - - v4l2_info(v4l2_dev, "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); - return 0; + return isa_register_driver(&aztech_driver.driver, AZTECH_MAX); } static void __exit aztech_exit(void) { - struct aztech *az = &aztech_card; - - video_unregister_device(&az->vdev); - v4l2_device_unregister(&az->v4l2_dev); - release_region(az->io, 2); + isa_unregister_driver(&aztech_driver.driver); } module_init(aztech_init); diff --git a/drivers/media/radio/radio-gemtek.c b/drivers/media/radio/radio-gemtek.c index 36ce0611c037..2e639ce6f256 100644 --- a/drivers/media/radio/radio-gemtek.c +++ b/drivers/media/radio/radio-gemtek.c @@ -1,4 +1,7 @@ -/* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi> +/* + * GemTek radio card driver + * + * Copyright 1998 Jonas Munsin <jmunsin@iki.fi> * * GemTek hasn't released any specs on the card, so the protocol had to * be reverse engineered with dosemu. @@ -11,9 +14,12 @@ * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> * - * TODO: Allow for more than one of these foolish entities :-) - * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> + * + * Note: this card seems to swap the left and right audio channels! + * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -23,8 +29,10 @@ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/mutex.h> #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> +#include "radio-isa.h" /* * Module info. @@ -33,7 +41,7 @@ MODULE_AUTHOR("Jonas Munsin, Pekka Seppänen <pexu@kapsi.fi>"); MODULE_DESCRIPTION("A driver for the GemTek Radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.4"); +MODULE_VERSION("1.0.0"); /* * Module params. @@ -46,45 +54,29 @@ MODULE_VERSION("0.0.4"); #define CONFIG_RADIO_GEMTEK_PROBE 1 #endif -static int io = CONFIG_RADIO_GEMTEK_PORT; -static bool probe = CONFIG_RADIO_GEMTEK_PROBE; -static bool hardmute; -static bool shutdown = 1; -static bool keepmuted = 1; -static bool initmute = 1; -static int radio_nr = -1; +#define GEMTEK_MAX 4 -module_param(io, int, 0444); -MODULE_PARM_DESC(io, "Force I/O port for the GemTek Radio card if automatic " - "probing is disabled or fails. The most common I/O ports are: 0x20c " - "0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to " - "work for the combined sound/radiocard)."); +static bool probe = CONFIG_RADIO_GEMTEK_PROBE; +static bool hardmute; +static int io[GEMTEK_MAX] = { [0] = CONFIG_RADIO_GEMTEK_PORT, + [1 ... (GEMTEK_MAX - 1)] = -1 }; +static int radio_nr[GEMTEK_MAX] = { [0 ... (GEMTEK_MAX - 1)] = -1 }; module_param(probe, bool, 0444); -MODULE_PARM_DESC(probe, "Enable automatic device probing. Note: only the most " - "common I/O ports used by the card are probed."); +MODULE_PARM_DESC(probe, "Enable automatic device probing."); module_param(hardmute, bool, 0644); -MODULE_PARM_DESC(hardmute, "Enable `hard muting' by shutting down PLL, may " +MODULE_PARM_DESC(hardmute, "Enable 'hard muting' by shutting down PLL, may " "reduce static noise."); -module_param(shutdown, bool, 0644); -MODULE_PARM_DESC(shutdown, "Enable shutting down PLL and muting line when " - "module is unloaded."); - -module_param(keepmuted, bool, 0644); -MODULE_PARM_DESC(keepmuted, "Keep card muted even when frequency is changed."); - -module_param(initmute, bool, 0444); -MODULE_PARM_DESC(initmute, "Mute card when module is loaded."); - -module_param(radio_nr, int, 0444); +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "Force I/O ports for the GemTek Radio card if automatic " + "probing is disabled or fails. The most common I/O ports are: 0x20c " + "0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to " + "work for the combined sound/radiocard)."); -/* - * Functions for controlling the card. - */ -#define GEMTEK_LOWFREQ (87*16000) -#define GEMTEK_HIGHFREQ (108*16000) +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); /* * Frequency calculation constants. Intermediate frequency 10.52 MHz (nominal @@ -108,18 +100,11 @@ module_param(radio_nr, int, 0444); #define LONG_DELAY 75 /* usec */ struct gemtek { - struct v4l2_device v4l2_dev; - struct video_device vdev; - struct mutex lock; - unsigned long lastfreq; - int muted; - int verified; - int io; + struct radio_isa_card isa; + bool muted; u32 bu2614data; }; -static struct gemtek gemtek_card; - #define BU2614_FREQ_BITS 16 /* D0..D15, Frequency data */ #define BU2614_PORT_BITS 3 /* P0..P2, Output port control data */ #define BU2614_VOID_BITS 4 /* unused */ @@ -166,31 +151,24 @@ static struct gemtek gemtek_card; */ static void gemtek_bu2614_transmit(struct gemtek *gt) { + struct radio_isa_card *isa = >->isa; int i, bit, q, mute; - mutex_lock(>->lock); - mute = gt->muted ? GEMTEK_MT : 0x00; - outb_p(mute | GEMTEK_DA | GEMTEK_CK, gt->io); - udelay(SHORT_DELAY); - outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, gt->io); + outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, isa->io); udelay(LONG_DELAY); for (i = 0, q = gt->bu2614data; i < 32; i++, q >>= 1) { bit = (q & 1) ? GEMTEK_DA : 0; - outb_p(mute | GEMTEK_CE | bit, gt->io); + outb_p(mute | GEMTEK_CE | bit, isa->io); udelay(SHORT_DELAY); - outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, gt->io); + outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, isa->io); udelay(SHORT_DELAY); } - outb_p(mute | GEMTEK_DA | GEMTEK_CK, gt->io); + outb_p(mute | GEMTEK_DA | GEMTEK_CK, isa->io); udelay(SHORT_DELAY); - outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, gt->io); - udelay(LONG_DELAY); - - mutex_unlock(>->lock); } /* @@ -198,21 +176,27 @@ static void gemtek_bu2614_transmit(struct gemtek *gt) */ static unsigned long gemtek_convfreq(unsigned long freq) { - return ((freq<<FSCALE) + IF_OFFSET + REF_FREQ/2) / REF_FREQ; + return ((freq << FSCALE) + IF_OFFSET + REF_FREQ / 2) / REF_FREQ; +} + +static struct radio_isa_card *gemtek_alloc(void) +{ + struct gemtek *gt = kzalloc(sizeof(*gt), GFP_KERNEL); + + if (gt) + gt->muted = true; + return gt ? >->isa : NULL; } /* * Set FM-frequency. */ -static void gemtek_setfreq(struct gemtek *gt, unsigned long freq) +static int gemtek_s_frequency(struct radio_isa_card *isa, u32 freq) { - if (keepmuted && hardmute && gt->muted) - return; + struct gemtek *gt = container_of(isa, struct gemtek, isa); - freq = clamp_val(freq, GEMTEK_LOWFREQ, GEMTEK_HIGHFREQ); - - gt->lastfreq = freq; - gt->muted = 0; + if (hardmute && gt->muted) + return 0; gemtek_bu2614_set(gt, BU2614_PORT, 0); gemtek_bu2614_set(gt, BU2614_FMES, 0); @@ -220,23 +204,25 @@ static void gemtek_setfreq(struct gemtek *gt, unsigned long freq) gemtek_bu2614_set(gt, BU2614_SWAL, 0); gemtek_bu2614_set(gt, BU2614_FMUN, 1); /* GT bit set */ gemtek_bu2614_set(gt, BU2614_TEST, 0); - gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_STDF_3_125_KHZ); gemtek_bu2614_set(gt, BU2614_FREQ, gemtek_convfreq(freq)); - gemtek_bu2614_transmit(gt); + return 0; } /* * Set mute flag. */ -static void gemtek_mute(struct gemtek *gt) +static int gemtek_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { + struct gemtek *gt = container_of(isa, struct gemtek, isa); int i; - gt->muted = 1; - + gt->muted = mute; if (hardmute) { + if (!mute) + return gemtek_s_frequency(isa, isa->freq); + /* Turn off PLL, disable data output */ gemtek_bu2614_set(gt, BU2614_PORT, 0); gemtek_bu2614_set(gt, BU2614_FMES, 0); /* CT bit off */ @@ -247,367 +233,85 @@ static void gemtek_mute(struct gemtek *gt) gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_PLL_OFF); gemtek_bu2614_set(gt, BU2614_FREQ, 0); gemtek_bu2614_transmit(gt); - return; + return 0; } - mutex_lock(>->lock); - /* Read bus contents (CE, CK and DA). */ - i = inb_p(gt->io); + i = inb_p(isa->io); /* Write it back with mute flag set. */ - outb_p((i >> 5) | GEMTEK_MT, gt->io); + outb_p((i >> 5) | (mute ? GEMTEK_MT : 0), isa->io); udelay(SHORT_DELAY); - - mutex_unlock(>->lock); -} - -/* - * Unset mute flag. - */ -static void gemtek_unmute(struct gemtek *gt) -{ - int i; - - gt->muted = 0; - if (hardmute) { - /* Turn PLL back on. */ - gemtek_setfreq(gt, gt->lastfreq); - return; - } - mutex_lock(>->lock); - - i = inb_p(gt->io); - outb_p(i >> 5, gt->io); - udelay(SHORT_DELAY); - - mutex_unlock(>->lock); + return 0; } -/* - * Get signal strength (= stereo status). - */ -static inline int gemtek_getsigstr(struct gemtek *gt) +static u32 gemtek_g_rxsubchans(struct radio_isa_card *isa) { - int sig; - - mutex_lock(>->lock); - sig = inb_p(gt->io) & GEMTEK_NS ? 0 : 1; - mutex_unlock(>->lock); - return sig; + if (inb_p(isa->io) & GEMTEK_NS) + return V4L2_TUNER_SUB_MONO; + return V4L2_TUNER_SUB_STEREO; } /* * Check if requested card acts like GemTek Radio card. */ -static int gemtek_verify(struct gemtek *gt, int port) +static bool gemtek_probe(struct radio_isa_card *isa, int io) { int i, q; - if (gt->verified == port) - return 1; - - mutex_lock(>->lock); - - q = inb_p(port); /* Read bus contents before probing. */ + q = inb_p(io); /* Read bus contents before probing. */ /* Try to turn on CE, CK and DA respectively and check if card responds properly. */ for (i = 0; i < 3; ++i) { - outb_p(1 << i, port); + outb_p(1 << i, io); udelay(SHORT_DELAY); - if ((inb_p(port) & (~GEMTEK_NS)) != (0x17 | (1 << (i + 5)))) { - mutex_unlock(>->lock); - return 0; - } + if ((inb_p(io) & ~GEMTEK_NS) != (0x17 | (1 << (i + 5)))) + return false; } - outb_p(q >> 5, port); /* Write bus contents back. */ + outb_p(q >> 5, io); /* Write bus contents back. */ udelay(SHORT_DELAY); - - mutex_unlock(>->lock); - gt->verified = port; - - return 1; -} - -/* - * Automatic probing for card. - */ -static int gemtek_probe(struct gemtek *gt) -{ - struct v4l2_device *v4l2_dev = >->v4l2_dev; - int ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c }; - int i; - - if (!probe) { - v4l2_info(v4l2_dev, "Automatic device probing disabled.\n"); - return -1; - } - - v4l2_info(v4l2_dev, "Automatic device probing enabled.\n"); - - for (i = 0; i < ARRAY_SIZE(ioports); ++i) { - v4l2_info(v4l2_dev, "Trying I/O port 0x%x...\n", ioports[i]); - - if (!request_region(ioports[i], 1, "gemtek-probe")) { - v4l2_warn(v4l2_dev, "I/O port 0x%x busy!\n", - ioports[i]); - continue; - } - - if (gemtek_verify(gt, ioports[i])) { - v4l2_info(v4l2_dev, "Card found from I/O port " - "0x%x!\n", ioports[i]); - - release_region(ioports[i], 1); - gt->io = ioports[i]; - return gt->io; - } - - release_region(ioports[i], 1); - } - - v4l2_err(v4l2_dev, "Automatic probing failed!\n"); - return -1; + return true; } -/* - * Video 4 Linux stuff. - */ - -static const struct v4l2_file_operations gemtek_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops gemtek_ops = { + .alloc = gemtek_alloc, + .probe = gemtek_probe, + .s_mute_volume = gemtek_s_mute_volume, + .s_frequency = gemtek_s_frequency, + .g_rxsubchans = gemtek_g_rxsubchans, }; -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-gemtek", sizeof(v->driver)); - strlcpy(v->card, "GemTek", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) -{ - struct gemtek *gt = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = GEMTEK_LOWFREQ; - v->rangehigh = GEMTEK_HIGHFREQ; - v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; - v->signal = 0xffff * gemtek_getsigstr(gt); - if (v->signal) { - v->audmode = V4L2_TUNER_MODE_STEREO; - v->rxsubchans = V4L2_TUNER_SUB_STEREO; - } else { - v->audmode = V4L2_TUNER_MODE_MONO; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - } - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *v) -{ - return (v->index != 0) ? -EINVAL : 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct gemtek *gt = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = gt->lastfreq; - return 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct gemtek *gt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - gemtek_setfreq(gt, f->frequency); - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); - default: - return -EINVAL; - } -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct gemtek *gt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = gt->muted; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct gemtek *gt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - gemtek_mute(gt); - else - gemtek_unmute(gt); - return 0; - } - return -EINVAL; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return (i != 0) ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *a) -{ - return (a->index != 0) ? -EINVAL : 0; -} - -static const struct v4l2_ioctl_ops gemtek_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl +static const int gemtek_ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c }; + +static struct radio_isa_driver gemtek_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-gemtek", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = gemtek_ioports, + .num_of_io_ports = ARRAY_SIZE(gemtek_ioports), + .region_size = 1, + .card = "GemTek Radio", + .ops = &gemtek_ops, + .has_stereo = true, }; -/* - * Initialization / cleanup related stuff. - */ - static int __init gemtek_init(void) { - struct gemtek *gt = &gemtek_card; - struct v4l2_device *v4l2_dev = >->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "gemtek", sizeof(v4l2_dev->name)); - - v4l2_info(v4l2_dev, "GemTek Radio card driver: v0.0.3\n"); - - mutex_init(>->lock); - - gt->verified = -1; - gt->io = io; - gemtek_probe(gt); - if (gt->io) { - if (!request_region(gt->io, 1, "gemtek")) { - v4l2_err(v4l2_dev, "I/O port 0x%x already in use.\n", gt->io); - return -EBUSY; - } - - if (!gemtek_verify(gt, gt->io)) - v4l2_warn(v4l2_dev, "Card at I/O port 0x%x does not " - "respond properly, check your " - "configuration.\n", gt->io); - else - v4l2_info(v4l2_dev, "Using I/O port 0x%x.\n", gt->io); - } else if (probe) { - v4l2_err(v4l2_dev, "Automatic probing failed and no " - "fixed I/O port defined.\n"); - return -ENODEV; - } else { - v4l2_err(v4l2_dev, "Automatic probing disabled but no fixed " - "I/O port defined."); - return -EINVAL; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - release_region(gt->io, 1); - return res; - } - - strlcpy(gt->vdev.name, v4l2_dev->name, sizeof(gt->vdev.name)); - gt->vdev.v4l2_dev = v4l2_dev; - gt->vdev.fops = &gemtek_fops; - gt->vdev.ioctl_ops = &gemtek_ioctl_ops; - gt->vdev.release = video_device_release_empty; - video_set_drvdata(>->vdev, gt); - - /* Set defaults */ - gt->lastfreq = GEMTEK_LOWFREQ; - gt->bu2614data = 0; - - if (initmute) - gemtek_mute(gt); - - if (video_register_device(>->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(gt->io, 1); - return -EBUSY; - } - - return 0; + gemtek_driver.probe = probe; + return isa_register_driver(&gemtek_driver.driver, GEMTEK_MAX); } -/* - * Module cleanup - */ static void __exit gemtek_exit(void) { - struct gemtek *gt = &gemtek_card; - struct v4l2_device *v4l2_dev = >->v4l2_dev; - - if (shutdown) { - hardmute = 1; /* Turn off PLL */ - gemtek_mute(gt); - } else { - v4l2_info(v4l2_dev, "Module unloaded but card not muted!\n"); - } - - video_unregister_device(>->vdev); - v4l2_device_unregister(>->v4l2_dev); - release_region(gt->io, 1); + hardmute = 1; /* Turn off PLL */ + isa_unregister_driver(&gemtek_driver.driver); } module_init(gemtek_init); diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c new file mode 100644 index 000000000000..06f906351fad --- /dev/null +++ b/drivers/media/radio/radio-isa.c @@ -0,0 +1,340 @@ +/* + * Framework for ISA radio drivers. + * This takes care of all the V4L2 scaffolding, allowing the ISA drivers + * to concentrate on the actual hardware operation. + * + * Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> + +#include "radio-isa.h" + +MODULE_AUTHOR("Hans Verkuil"); +MODULE_DESCRIPTION("A framework for ISA radio drivers."); +MODULE_LICENSE("GPL"); + +#define FREQ_LOW (87U * 16000U) +#define FREQ_HIGH (108U * 16000U) + +static int radio_isa_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct radio_isa_card *isa = video_drvdata(file); + + strlcpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver)); + strlcpy(v->card, isa->drv->card, sizeof(v->card)); + snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name); + + v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->device_caps = v->capabilities | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int radio_isa_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct radio_isa_card *isa = video_drvdata(file); + const struct radio_isa_ops *ops = isa->drv->ops; + + if (v->index > 0) + return -EINVAL; + + strlcpy(v->name, "FM", sizeof(v->name)); + v->type = V4L2_TUNER_RADIO; + v->rangelow = FREQ_LOW; + v->rangehigh = FREQ_HIGH; + v->capability = V4L2_TUNER_CAP_LOW; + if (isa->drv->has_stereo) + v->capability |= V4L2_TUNER_CAP_STEREO; + + if (ops->g_rxsubchans) + v->rxsubchans = ops->g_rxsubchans(isa); + else + v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO; + if (ops->g_signal) + v->signal = ops->g_signal(isa); + else + v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ? + 0xffff : 0; + return 0; +} + +static int radio_isa_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct radio_isa_card *isa = video_drvdata(file); + const struct radio_isa_ops *ops = isa->drv->ops; + + if (v->index) + return -EINVAL; + if (ops->s_stereo) { + isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO); + return ops->s_stereo(isa, isa->stereo); + } + return 0; +} + +static int radio_isa_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct radio_isa_card *isa = video_drvdata(file); + int res; + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + f->frequency = clamp(f->frequency, FREQ_LOW, FREQ_HIGH); + res = isa->drv->ops->s_frequency(isa, f->frequency); + if (res == 0) + isa->freq = f->frequency; + return res; +} + +static int radio_isa_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct radio_isa_card *isa = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = isa->freq; + return 0; +} + +static int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct radio_isa_card *isa = + container_of(ctrl->handler, struct radio_isa_card, hdl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + return isa->drv->ops->s_mute_volume(isa, ctrl->val, + isa->volume ? isa->volume->val : 0); + } + return -EINVAL; +} + +static int radio_isa_log_status(struct file *file, void *priv) +{ + struct radio_isa_card *isa = video_drvdata(file); + + v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n", isa->io); + v4l2_ctrl_handler_log_status(&isa->hdl, isa->v4l2_dev.name); + return 0; +} + +static int radio_isa_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type == V4L2_EVENT_CTRL) + return v4l2_event_subscribe(fh, sub, 0); + return -EINVAL; +} + +static const struct v4l2_ctrl_ops radio_isa_ctrl_ops = { + .s_ctrl = radio_isa_s_ctrl, +}; + +static const struct v4l2_file_operations radio_isa_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops radio_isa_ioctl_ops = { + .vidioc_querycap = radio_isa_querycap, + .vidioc_g_tuner = radio_isa_g_tuner, + .vidioc_s_tuner = radio_isa_s_tuner, + .vidioc_g_frequency = radio_isa_g_frequency, + .vidioc_s_frequency = radio_isa_s_frequency, + .vidioc_log_status = radio_isa_log_status, + .vidioc_subscribe_event = radio_isa_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +int radio_isa_match(struct device *pdev, unsigned int dev) +{ + struct radio_isa_driver *drv = pdev->platform_data; + + return drv->probe || drv->io_params[dev] >= 0; +} +EXPORT_SYMBOL_GPL(radio_isa_match); + +static bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io) +{ + int i; + + for (i = 0; i < drv->num_of_io_ports; i++) + if (drv->io_ports[i] == io) + return true; + return false; +} + +int radio_isa_probe(struct device *pdev, unsigned int dev) +{ + struct radio_isa_driver *drv = pdev->platform_data; + const struct radio_isa_ops *ops = drv->ops; + struct v4l2_device *v4l2_dev; + struct radio_isa_card *isa; + int res; + + isa = drv->ops->alloc(); + if (isa == NULL) + return -ENOMEM; + dev_set_drvdata(pdev, isa); + isa->drv = drv; + isa->io = drv->io_params[dev]; + v4l2_dev = &isa->v4l2_dev; + strlcpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name)); + + if (drv->probe && ops->probe) { + int i; + + for (i = 0; i < drv->num_of_io_ports; ++i) { + int io = drv->io_ports[i]; + + if (request_region(io, drv->region_size, v4l2_dev->name)) { + bool found = ops->probe(isa, io); + + release_region(io, drv->region_size); + if (found) { + isa->io = io; + break; + } + } + } + } + + if (!radio_isa_valid_io(drv, isa->io)) { + int i; + + if (isa->io < 0) + return -ENODEV; + v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x", + drv->io_ports[0]); + for (i = 1; i < drv->num_of_io_ports; i++) + printk(KERN_CONT "/0x%03x", drv->io_ports[i]); + printk(KERN_CONT ".\n"); + kfree(isa); + return -EINVAL; + } + + if (!request_region(isa->io, drv->region_size, v4l2_dev->name)) { + v4l2_err(v4l2_dev, "port 0x%x already in use\n", isa->io); + kfree(isa); + return -EBUSY; + } + + res = v4l2_device_register(pdev, v4l2_dev); + if (res < 0) { + v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); + goto err_dev_reg; + } + + v4l2_ctrl_handler_init(&isa->hdl, 1); + isa->mute = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + if (drv->max_volume) + isa->volume = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, drv->max_volume, 1, + drv->max_volume); + v4l2_dev->ctrl_handler = &isa->hdl; + if (isa->hdl.error) { + res = isa->hdl.error; + v4l2_err(v4l2_dev, "Could not register controls\n"); + goto err_hdl; + } + if (drv->max_volume) + v4l2_ctrl_cluster(2, &isa->mute); + v4l2_dev->ctrl_handler = &isa->hdl; + + mutex_init(&isa->lock); + isa->vdev.lock = &isa->lock; + strlcpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name)); + isa->vdev.v4l2_dev = v4l2_dev; + isa->vdev.fops = &radio_isa_fops; + isa->vdev.ioctl_ops = &radio_isa_ioctl_ops; + isa->vdev.release = video_device_release_empty; + set_bit(V4L2_FL_USE_FH_PRIO, &isa->vdev.flags); + video_set_drvdata(&isa->vdev, isa); + isa->freq = FREQ_LOW; + isa->stereo = drv->has_stereo; + + if (ops->init) + res = ops->init(isa); + if (!res) + res = v4l2_ctrl_handler_setup(&isa->hdl); + if (!res) + res = ops->s_frequency(isa, isa->freq); + if (!res && ops->s_stereo) + res = ops->s_stereo(isa, isa->stereo); + if (res < 0) { + v4l2_err(v4l2_dev, "Could not setup card\n"); + goto err_node_reg; + } + res = video_register_device(&isa->vdev, VFL_TYPE_RADIO, + drv->radio_nr_params[dev]); + if (res < 0) { + v4l2_err(v4l2_dev, "Could not register device node\n"); + goto err_node_reg; + } + + v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n", + drv->card, isa->io); + return 0; + +err_node_reg: + v4l2_ctrl_handler_free(&isa->hdl); +err_hdl: + v4l2_device_unregister(&isa->v4l2_dev); +err_dev_reg: + release_region(isa->io, drv->region_size); + kfree(isa); + return res; +} +EXPORT_SYMBOL_GPL(radio_isa_probe); + +int radio_isa_remove(struct device *pdev, unsigned int dev) +{ + struct radio_isa_card *isa = dev_get_drvdata(pdev); + const struct radio_isa_ops *ops = isa->drv->ops; + + ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0); + video_unregister_device(&isa->vdev); + v4l2_ctrl_handler_free(&isa->hdl); + v4l2_device_unregister(&isa->v4l2_dev); + release_region(isa->io, isa->drv->region_size); + v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n", isa->drv->card); + kfree(isa); + return 0; +} +EXPORT_SYMBOL_GPL(radio_isa_remove); diff --git a/drivers/media/radio/radio-isa.h b/drivers/media/radio/radio-isa.h new file mode 100644 index 000000000000..8a0ea84d86de --- /dev/null +++ b/drivers/media/radio/radio-isa.h @@ -0,0 +1,105 @@ +/* + * Framework for ISA radio drivers. + * This takes care of all the V4L2 scaffolding, allowing the ISA drivers + * to concentrate on the actual hardware operation. + * + * Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef _RADIO_ISA_H_ +#define _RADIO_ISA_H_ + +#include <linux/isa.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +struct radio_isa_driver; +struct radio_isa_ops; + +/* Core structure for radio ISA cards */ +struct radio_isa_card { + const struct radio_isa_driver *drv; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct video_device vdev; + struct mutex lock; + const struct radio_isa_ops *ops; + struct { /* mute/volume cluster */ + struct v4l2_ctrl *mute; + struct v4l2_ctrl *volume; + }; + /* I/O port */ + int io; + + /* Card is in stereo audio mode */ + bool stereo; + /* Current frequency */ + u32 freq; +}; + +struct radio_isa_ops { + /* Allocate and initialize a radio_isa_card struct */ + struct radio_isa_card *(*alloc)(void); + /* Probe whether a card is present at the given port */ + bool (*probe)(struct radio_isa_card *isa, int io); + /* Special card initialization can be done here, this is called after + * the standard controls are registered, but before they are setup, + * thus allowing drivers to add their own controls here. */ + int (*init)(struct radio_isa_card *isa); + /* Set mute and volume. */ + int (*s_mute_volume)(struct radio_isa_card *isa, bool mute, int volume); + /* Set frequency */ + int (*s_frequency)(struct radio_isa_card *isa, u32 freq); + /* Set stereo/mono audio mode */ + int (*s_stereo)(struct radio_isa_card *isa, bool stereo); + /* Get rxsubchans value for VIDIOC_G_TUNER */ + u32 (*g_rxsubchans)(struct radio_isa_card *isa); + /* Get the signal strength for VIDIOC_G_TUNER */ + u32 (*g_signal)(struct radio_isa_card *isa); +}; + +/* Top level structure needed to instantiate the cards */ +struct radio_isa_driver { + struct isa_driver driver; + const struct radio_isa_ops *ops; + /* The module_param_array with the specified I/O ports */ + int *io_params; + /* The module_param_array with the radio_nr values */ + int *radio_nr_params; + /* Whether we should probe for possible cards */ + bool probe; + /* The list of possible I/O ports */ + const int *io_ports; + /* The size of that list */ + int num_of_io_ports; + /* The region size to request */ + unsigned region_size; + /* The name of the card */ + const char *card; + /* Card can capture stereo audio */ + bool has_stereo; + /* The maximum volume for the volume control. If 0, then there + is no volume control possible. */ + int max_volume; +}; + +int radio_isa_match(struct device *pdev, unsigned int dev); +int radio_isa_probe(struct device *pdev, unsigned int dev); +int radio_isa_remove(struct device *pdev, unsigned int dev); + +#endif diff --git a/drivers/media/radio/radio-keene.c b/drivers/media/radio/radio-keene.c new file mode 100644 index 000000000000..55bd1d2937c8 --- /dev/null +++ b/drivers/media/radio/radio-keene.c @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2012 Hans Verkuil <hverkuil@xs4all.nl> + * + * 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 + */ + +/* kernel includes */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <linux/usb.h> +#include <linux/version.h> +#include <linux/mutex.h> + +/* driver and module definitions */ +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); +MODULE_DESCRIPTION("Keene FM Transmitter driver"); +MODULE_LICENSE("GPL"); + +/* Actually, it advertises itself as a Logitech */ +#define USB_KEENE_VENDOR 0x046d +#define USB_KEENE_PRODUCT 0x0a0e + +/* Probably USB_TIMEOUT should be modified in module parameter */ +#define BUFFER_LENGTH 8 +#define USB_TIMEOUT 500 + +/* Frequency limits in MHz */ +#define FREQ_MIN 76U +#define FREQ_MAX 108U +#define FREQ_MUL 16000U + +/* USB Device ID List */ +static struct usb_device_id usb_keene_device_table[] = { + {USB_DEVICE_AND_INTERFACE_INFO(USB_KEENE_VENDOR, USB_KEENE_PRODUCT, + USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_keene_device_table); + +struct keene_device { + struct usb_device *usbdev; + struct usb_interface *intf; + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct mutex lock; + + u8 *buffer; + unsigned curfreq; + u8 tx; + u8 pa; + bool stereo; + bool muted; + bool preemph_75_us; +}; + +static inline struct keene_device *to_keene_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct keene_device, v4l2_dev); +} + +/* Set frequency (if non-0), PA, mute and turn on/off the FM transmitter. */ +static int keene_cmd_main(struct keene_device *radio, unsigned freq, bool play) +{ + unsigned short freq_send = freq ? (freq - 76 * 16000) / 800 : 0; + int ret; + + radio->buffer[0] = 0x00; + radio->buffer[1] = 0x50; + radio->buffer[2] = (freq_send >> 8) & 0xff; + radio->buffer[3] = freq_send & 0xff; + radio->buffer[4] = radio->pa; + /* If bit 4 is set, then tune to the frequency. + If bit 3 is set, then unmute; if bit 2 is set, then mute. + If bit 1 is set, then enter idle mode; if bit 0 is set, + then enter transit mode. + */ + radio->buffer[5] = (radio->muted ? 4 : 8) | (play ? 1 : 2) | + (freq ? 0x10 : 0); + radio->buffer[6] = 0x00; + radio->buffer[7] = 0x00; + + ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + if (freq) + radio->curfreq = freq; + return 0; +} + +/* Set TX, stereo and preemphasis mode (50 us vs 75 us). */ +static int keene_cmd_set(struct keene_device *radio) +{ + int ret; + + radio->buffer[0] = 0x00; + radio->buffer[1] = 0x51; + radio->buffer[2] = radio->tx; + /* If bit 0 is set, then transmit mono, otherwise stereo. + If bit 2 is set, then enable 75 us preemphasis, otherwise + it is 50 us. */ + radio->buffer[3] = (!radio->stereo) | (radio->preemph_75_us ? 4 : 0); + radio->buffer[4] = 0x00; + radio->buffer[5] = 0x00; + radio->buffer[6] = 0x00; + radio->buffer[7] = 0x00; + + ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + return 0; +} + +/* Handle unplugging the device. + * We call video_unregister_device in any case. + * The last function called in this procedure is + * usb_keene_device_release. + */ +static void usb_keene_disconnect(struct usb_interface *intf) +{ + struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); + + v4l2_device_get(&radio->v4l2_dev); + mutex_lock(&radio->lock); + usb_set_intfdata(intf, NULL); + video_unregister_device(&radio->vdev); + v4l2_device_disconnect(&radio->v4l2_dev); + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct keene_device *radio = video_drvdata(file); + + strlcpy(v->driver, "radio-keene", sizeof(v->driver)); + strlcpy(v->card, "Keene FM Transmitter", sizeof(v->card)); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_MODULATOR; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int vidioc_g_modulator(struct file *file, void *priv, + struct v4l2_modulator *v) +{ + struct keene_device *radio = video_drvdata(file); + + if (v->index > 0) + return -EINVAL; + + strlcpy(v->name, "FM", sizeof(v->name)); + v->rangelow = FREQ_MIN * FREQ_MUL; + v->rangehigh = FREQ_MAX * FREQ_MUL; + v->txsubchans = radio->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; + return 0; +} + +static int vidioc_s_modulator(struct file *file, void *priv, + struct v4l2_modulator *v) +{ + struct keene_device *radio = video_drvdata(file); + + if (v->index > 0) + return -EINVAL; + + radio->stereo = (v->txsubchans == V4L2_TUNER_SUB_STEREO); + return keene_cmd_set(radio); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct keene_device *radio = video_drvdata(file); + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + f->frequency = clamp(f->frequency, + FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); + return keene_cmd_main(radio, f->frequency, true); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct keene_device *radio = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = radio->curfreq; + return 0; +} + +static int keene_s_ctrl(struct v4l2_ctrl *ctrl) +{ + static const u8 db2tx[] = { + /* -15, -12, -9, -6, -3, 0 dB */ + 0x03, 0x13, 0x02, 0x12, 0x22, 0x32, + /* 3, 6, 9, 12, 15, 18 dB */ + 0x21, 0x31, 0x20, 0x30, 0x40, 0x50 + }; + struct keene_device *radio = + container_of(ctrl->handler, struct keene_device, hdl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + radio->muted = ctrl->val; + return keene_cmd_main(radio, 0, true); + + case V4L2_CID_TUNE_POWER_LEVEL: + /* To go from dBuV to the register value we apply the + following formula: */ + radio->pa = (ctrl->val - 71) * 100 / 62; + return keene_cmd_main(radio, 0, true); + + case V4L2_CID_TUNE_PREEMPHASIS: + radio->preemph_75_us = ctrl->val == V4L2_PREEMPHASIS_75_uS; + return keene_cmd_set(radio); + + case V4L2_CID_AUDIO_COMPRESSION_GAIN: + radio->tx = db2tx[(ctrl->val - ctrl->minimum) / ctrl->step]; + return keene_cmd_set(radio); + } + return -EINVAL; +} + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_CTRL: + return v4l2_event_subscribe(fh, sub, 0); + default: + return -EINVAL; + } +} + + +/* File system interface */ +static const struct v4l2_file_operations usb_keene_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ctrl_ops keene_ctrl_ops = { + .s_ctrl = keene_s_ctrl, +}; + +static const struct v4l2_ioctl_ops usb_keene_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_modulator = vidioc_g_modulator, + .vidioc_s_modulator = vidioc_s_modulator, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void usb_keene_video_device_release(struct v4l2_device *v4l2_dev) +{ + struct keene_device *radio = to_keene_dev(v4l2_dev); + + /* free rest memory */ + v4l2_ctrl_handler_free(&radio->hdl); + kfree(radio->buffer); + kfree(radio); +} + +/* check if the device is present and register with v4l and usb if it is */ +static int usb_keene_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct keene_device *radio; + struct v4l2_ctrl_handler *hdl; + int retval = 0; + + /* + * The Keene FM transmitter USB device has the same USB ID as + * the Logitech AudioHub Speaker, but it should ignore the hid. + * Check if the name is that of the Keene device. + * If not, then someone connected the AudioHub and we shouldn't + * attempt to handle this driver. + * For reference: the product name of the AudioHub is + * "AudioHub Speaker". + */ + if (dev->product && strcmp(dev->product, "B-LINK USB Audio ")) + return -ENODEV; + + radio = kzalloc(sizeof(struct keene_device), GFP_KERNEL); + if (radio) + radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); + + if (!radio || !radio->buffer) { + dev_err(&intf->dev, "kmalloc for keene_device failed\n"); + kfree(radio); + retval = -ENOMEM; + goto err; + } + + hdl = &radio->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + v4l2_ctrl_new_std_menu(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS, + V4L2_PREEMPHASIS_75_uS, 1, V4L2_PREEMPHASIS_50_uS); + v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_POWER_LEVEL, + 84, 118, 1, 118); + v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_COMPRESSION_GAIN, + -15, 18, 3, 0); + radio->pa = 118; + radio->tx = 0x32; + radio->stereo = true; + radio->curfreq = 95.16 * FREQ_MUL; + if (hdl->error) { + retval = hdl->error; + + v4l2_ctrl_handler_free(hdl); + goto err_v4l2; + } + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); + goto err_v4l2; + } + + mutex_init(&radio->lock); + + radio->v4l2_dev.ctrl_handler = hdl; + radio->v4l2_dev.release = usb_keene_video_device_release; + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_keene_fops; + radio->vdev.ioctl_ops = &usb_keene_ioctl_ops; + radio->vdev.lock = &radio->lock; + radio->vdev.release = video_device_release_empty; + + radio->usbdev = interface_to_usbdev(intf); + radio->intf = intf; + usb_set_intfdata(intf, &radio->v4l2_dev); + + video_set_drvdata(&radio->vdev, radio); + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); + + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); + if (retval < 0) { + dev_err(&intf->dev, "could not register video device\n"); + goto err_vdev; + } + v4l2_ctrl_handler_setup(hdl); + dev_info(&intf->dev, "V4L2 device registered as %s\n", + video_device_node_name(&radio->vdev)); + return 0; + +err_vdev: + v4l2_device_unregister(&radio->v4l2_dev); +err_v4l2: + kfree(radio->buffer); + kfree(radio); +err: + return retval; +} + +/* USB subsystem interface */ +static struct usb_driver usb_keene_driver = { + .name = "radio-keene", + .probe = usb_keene_probe, + .disconnect = usb_keene_disconnect, + .id_table = usb_keene_device_table, +}; + +static int __init keene_init(void) +{ + int retval = usb_register(&usb_keene_driver); + + if (retval) + pr_err(KBUILD_MODNAME + ": usb_register failed. Error number %d\n", retval); + + return retval; +} + +static void __exit keene_exit(void) +{ + usb_deregister(&usb_keene_driver); +} + +module_init(keene_init); +module_exit(keene_exit); + diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c index f872a54cf3d9..740a3d5520c7 100644 --- a/drivers/media/radio/radio-maxiradio.c +++ b/drivers/media/radio/radio-maxiradio.c @@ -42,67 +42,37 @@ #include <linux/videodev2.h> #include <linux/io.h> #include <linux/slab.h> +#include <sound/tea575x-tuner.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> - -#define DRIVER_VERSION "0.7.8" - +#include <media/v4l2-fh.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); -MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio."); +MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000."); MODULE_LICENSE("GPL"); -MODULE_VERSION(DRIVER_VERSION); +MODULE_VERSION("1.0.0"); static int radio_nr = -1; -module_param(radio_nr, int, 0); - -static int debug; - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "activates debug info"); - -#define dprintk(dev, num, fmt, arg...) \ - v4l2_dbg(num, debug, &dev->v4l2_dev, fmt, ## arg) - -#ifndef PCI_VENDOR_ID_GUILLEMOT -#define PCI_VENDOR_ID_GUILLEMOT 0x5046 -#endif - -#ifndef PCI_DEVICE_ID_GUILLEMOT -#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 -#endif - +module_param(radio_nr, int, 0644); +MODULE_PARM_DESC(radio_nr, "Radio device number"); /* TEA5757 pin mappings */ static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16; -#define FREQ_LO (87 * 16000) -#define FREQ_HI (108 * 16000) - -#define FREQ_IF 171200 /* 10.7*16000 */ -#define FREQ_STEP 200 /* 12.5*16 */ - -/* (x==fmhz*16*1000) -> bits */ -#define FREQ2BITS(x) \ - ((((unsigned int)(x) + FREQ_IF + (FREQ_STEP << 1)) / (FREQ_STEP << 2)) << 2) - -#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) +static atomic_t maxiradio_instance = ATOMIC_INIT(0); +#define PCI_VENDOR_ID_GUILLEMOT 0x5046 +#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 struct maxiradio { + struct snd_tea575x tea; struct v4l2_device v4l2_dev; - struct video_device vdev; struct pci_dev *pdev; u16 io; /* base of radio io */ - u16 muted; /* VIDEO_AUDIO_MUTE */ - u16 stereo; /* VIDEO_TUNER_STEREO_ON */ - u16 tuned; /* signal strength (0 or 0xffff) */ - - unsigned long freq; - - struct mutex lock; }; static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev) @@ -110,259 +80,41 @@ static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev) return container_of(v4l2_dev, struct maxiradio, v4l2_dev); } -static void outbit(unsigned long bit, u16 io) -{ - int val = power | wren | (bit ? data : 0); - - outb(val, io); - udelay(4); - outb(val | clk, io); - udelay(4); - outb(val, io); - udelay(4); -} - -static void turn_power(struct maxiradio *dev, int p) -{ - if (p != 0) { - dprintk(dev, 1, "Radio powered on\n"); - outb(power, dev->io); - } else { - dprintk(dev, 1, "Radio powered off\n"); - outb(0, dev->io); - } -} - -static void set_freq(struct maxiradio *dev, u32 freq) -{ - unsigned long int si; - int bl; - int io = dev->io; - int val = FREQ2BITS(freq); - - /* TEA5757 shift register bits (see pdf) */ - - outbit(0, io); /* 24 search */ - outbit(1, io); /* 23 search up/down */ - - outbit(0, io); /* 22 stereo/mono */ - - outbit(0, io); /* 21 band */ - outbit(0, io); /* 20 band (only 00=FM works I think) */ - - outbit(0, io); /* 19 port ? */ - outbit(0, io); /* 18 port ? */ - - outbit(0, io); /* 17 search level */ - outbit(0, io); /* 16 search level */ - - si = 0x8000; - for (bl = 1; bl <= 16; bl++) { - outbit(val & si, io); - si >>= 1; - } - - dprintk(dev, 1, "Radio freq set to %d.%02d MHz\n", - freq / 16000, - freq % 16000 * 100 / 16000); - - turn_power(dev, 1); -} - -static int get_stereo(u16 io) +static void maxiradio_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) { - outb(power,io); - udelay(4); + struct maxiradio *dev = tea->private_data; + u8 bits = 0; - return !(inb(io) & mo_st); -} + bits |= (pins & TEA575X_DATA) ? data : 0; + bits |= (pins & TEA575X_CLK) ? clk : 0; + bits |= (pins & TEA575X_WREN) ? wren : 0; + bits |= power; -static int get_tune(u16 io) -{ - outb(power+clk,io); - udelay(4); - - return !(inb(io) & mo_st); -} - - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - struct maxiradio *dev = video_drvdata(file); - - strlcpy(v->driver, "radio-maxiradio", sizeof(v->driver)); - strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof(v->card)); - snprintf(v->bus_info, sizeof(v->bus_info), "PCI:%s", pci_name(dev->pdev)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; + outb(bits, dev->io); } -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) +/* Note: this card cannot read out the data of the shift registers, + only the mono/stereo pin works. */ +static u8 maxiradio_tea575x_get_pins(struct snd_tea575x *tea) { - struct maxiradio *dev = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - mutex_lock(&dev->lock); - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = FREQ_LO; - v->rangehigh = FREQ_HI; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (get_stereo(dev->io)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xffff * get_tune(dev->io); - mutex_unlock(&dev->lock); + struct maxiradio *dev = tea->private_data; + u8 bits = inb(dev->io); - return 0; + return ((bits & data) ? TEA575X_DATA : 0) | + ((bits & mo_st) ? TEA575X_MOST : 0); } -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) +static void maxiradio_tea575x_set_direction(struct snd_tea575x *tea, bool output) { - return v->index ? -EINVAL : 0; } -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - - -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct maxiradio *dev = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) { - dprintk(dev, 1, "radio freq (%d.%02d MHz) out of range (%d-%d)\n", - f->frequency / 16000, - f->frequency % 16000 * 100 / 16000, - FREQ_LO / 16000, FREQ_HI / 16000); - - return -EINVAL; - } - - mutex_lock(&dev->lock); - dev->freq = f->frequency; - set_freq(dev, dev->freq); - msleep(125); - mutex_unlock(&dev->lock); - - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct maxiradio *dev = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = dev->freq; - - dprintk(dev, 4, "radio freq is %d.%02d MHz", - f->frequency / 16000, - f->frequency % 16000 * 100 / 16000); - - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct maxiradio *dev = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = dev->muted; - return 0; - } - - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct maxiradio *dev = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - mutex_lock(&dev->lock); - dev->muted = ctrl->value; - if (dev->muted) - turn_power(dev, 0); - else - set_freq(dev, dev->freq); - mutex_unlock(&dev->lock); - return 0; - } - - return -EINVAL; -} - -static const struct v4l2_file_operations maxiradio_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static struct snd_tea575x_ops maxiradio_tea_ops = { + .set_pins = maxiradio_tea575x_set_pins, + .get_pins = maxiradio_tea575x_get_pins, + .set_direction = maxiradio_tea575x_set_direction, }; -static const struct v4l2_ioctl_ops maxiradio_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, -}; - -static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +static int __devinit maxiradio_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct maxiradio *dev; struct v4l2_device *v4l2_dev; @@ -375,63 +127,60 @@ static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_d } v4l2_dev = &dev->v4l2_dev; - mutex_init(&dev->lock); - dev->pdev = pdev; - dev->muted = 1; - dev->freq = FREQ_LO; - - strlcpy(v4l2_dev->name, "maxiradio", sizeof(v4l2_dev->name)); + v4l2_device_set_name(v4l2_dev, "maxiradio", &maxiradio_instance); retval = v4l2_device_register(&pdev->dev, v4l2_dev); if (retval < 0) { v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); goto errfr; } + dev->tea.private_data = dev; + dev->tea.ops = &maxiradio_tea_ops; + /* The data pin cannot be read. This may be a hardware limitation, or + we just don't know how to read it. */ + dev->tea.cannot_read_data = true; + dev->tea.v4l2_dev = v4l2_dev; + dev->tea.radio_nr = radio_nr; + strlcpy(dev->tea.card, "Maxi Radio FM2000", sizeof(dev->tea.card)); + snprintf(dev->tea.bus_info, sizeof(dev->tea.bus_info), + "PCI:%s", pci_name(pdev)); + + retval = -ENODEV; if (!request_region(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) { - v4l2_err(v4l2_dev, "can't reserve I/O ports\n"); - goto err_out; + pci_resource_len(pdev, 0), v4l2_dev->name)) { + dev_err(&pdev->dev, "can't reserve I/O ports\n"); + goto err_hdl; } if (pci_enable_device(pdev)) goto err_out_free_region; dev->io = pci_resource_start(pdev, 0); - strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); - dev->vdev.v4l2_dev = v4l2_dev; - dev->vdev.fops = &maxiradio_fops; - dev->vdev.ioctl_ops = &maxiradio_ioctl_ops; - dev->vdev.release = video_device_release_empty; - video_set_drvdata(&dev->vdev, dev); - - if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_err(v4l2_dev, "can't register device!"); + if (snd_tea575x_init(&dev->tea)) { + printk(KERN_ERR "radio-maxiradio: Unable to detect TEA575x tuner\n"); goto err_out_free_region; } - - v4l2_info(v4l2_dev, "version " DRIVER_VERSION "\n"); - - v4l2_info(v4l2_dev, "found Guillemot MAXI Radio device (io = 0x%x)\n", - dev->io); return 0; err_out_free_region: release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); -err_out: +err_hdl: v4l2_device_unregister(v4l2_dev); errfr: kfree(dev); - return -ENODEV; + return retval; } -static void __devexit maxiradio_remove_one(struct pci_dev *pdev) +static void __devexit maxiradio_remove(struct pci_dev *pdev) { struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); struct maxiradio *dev = to_maxiradio(v4l2_dev); - video_unregister_device(&dev->vdev); - v4l2_device_unregister(&dev->v4l2_dev); + snd_tea575x_exit(&dev->tea); + /* Turn off power */ + outb(0, dev->io); + v4l2_device_unregister(v4l2_dev); release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); } @@ -446,19 +195,19 @@ MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); static struct pci_driver maxiradio_driver = { .name = "radio-maxiradio", .id_table = maxiradio_pci_tbl, - .probe = maxiradio_init_one, - .remove = __devexit_p(maxiradio_remove_one), + .probe = maxiradio_probe, + .remove = __devexit_p(maxiradio_remove), }; -static int __init maxiradio_radio_init(void) +static int __init maxiradio_init(void) { return pci_register_driver(&maxiradio_driver); } -static void __exit maxiradio_radio_exit(void) +static void __exit maxiradio_exit(void) { pci_unregister_driver(&maxiradio_driver); } -module_init(maxiradio_radio_init); -module_exit(maxiradio_radio_exit); +module_init(maxiradio_init); +module_exit(maxiradio_exit); diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c index 3628be617ee9..b275c5d0fe9a 100644 --- a/drivers/media/radio/radio-rtrack2.c +++ b/drivers/media/radio/radio-rtrack2.c @@ -1,11 +1,12 @@ -/* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff +/* + * RadioTrack II driver + * Copyright 1998 Ben Pfaff * * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> * - * TODO: Allow for more than one of these foolish entities :-) - * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> */ @@ -18,323 +19,120 @@ #include <linux/io.h> /* outb, outb_p */ #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" MODULE_AUTHOR("Ben Pfaff"); MODULE_DESCRIPTION("A driver for the RadioTrack II radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("0.1.99"); #ifndef CONFIG_RADIO_RTRACK2_PORT #define CONFIG_RADIO_RTRACK2_PORT -1 #endif -static int io = CONFIG_RADIO_RTRACK2_PORT; -static int radio_nr = -1; - -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)"); -module_param(radio_nr, int, 0); - -struct rtrack2 -{ - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; - unsigned long curfreq; - int muted; - struct mutex lock; -}; +#define RTRACK2_MAX 2 -static struct rtrack2 rtrack2_card; +static int io[RTRACK2_MAX] = { [0] = CONFIG_RADIO_RTRACK2_PORT, + [1 ... (RTRACK2_MAX - 1)] = -1 }; +static int radio_nr[RTRACK2_MAX] = { [0 ... (RTRACK2_MAX - 1)] = -1 }; +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); -/* local things */ - -static void rt_mute(struct rtrack2 *dev) -{ - if (dev->muted) - return; - mutex_lock(&dev->lock); - outb(1, dev->io); - mutex_unlock(&dev->lock); - dev->muted = 1; -} - -static void rt_unmute(struct rtrack2 *dev) +static struct radio_isa_card *rtrack2_alloc(void) { - if(dev->muted == 0) - return; - mutex_lock(&dev->lock); - outb(0, dev->io); - mutex_unlock(&dev->lock); - dev->muted = 0; + return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL); } -static void zero(struct rtrack2 *dev) +static void zero(struct radio_isa_card *isa) { - outb_p(1, dev->io); - outb_p(3, dev->io); - outb_p(1, dev->io); + outb_p(1, isa->io); + outb_p(3, isa->io); + outb_p(1, isa->io); } -static void one(struct rtrack2 *dev) +static void one(struct radio_isa_card *isa) { - outb_p(5, dev->io); - outb_p(7, dev->io); - outb_p(5, dev->io); + outb_p(5, isa->io); + outb_p(7, isa->io); + outb_p(5, isa->io); } -static int rt_setfreq(struct rtrack2 *dev, unsigned long freq) +static int rtrack2_s_frequency(struct radio_isa_card *isa, u32 freq) { int i; - mutex_lock(&dev->lock); - dev->curfreq = freq; freq = freq / 200 + 856; - outb_p(0xc8, dev->io); - outb_p(0xc9, dev->io); - outb_p(0xc9, dev->io); + outb_p(0xc8, isa->io); + outb_p(0xc9, isa->io); + outb_p(0xc9, isa->io); for (i = 0; i < 10; i++) - zero(dev); + zero(isa); for (i = 14; i >= 0; i--) if (freq & (1 << i)) - one(dev); + one(isa); else - zero(dev); - - outb_p(0xc8, dev->io); - if (!dev->muted) - outb_p(0, dev->io); - - mutex_unlock(&dev->lock); - return 0; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-rtrack2", sizeof(v->driver)); - strlcpy(v->card, "RadioTrack II", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} + zero(isa); -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int rt_getsigstr(struct rtrack2 *dev) -{ - int sig = 1; - - mutex_lock(&dev->lock); - if (inb(dev->io) & 2) /* bit set = no signal present */ - sig = 0; - mutex_unlock(&dev->lock); - return sig; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct rtrack2 *rt = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 88 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * rt_getsigstr(rt); + outb_p(0xc8, isa->io); + if (!v4l2_ctrl_g_ctrl(isa->mute)) + outb_p(0, isa->io); return 0; } -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) +static u32 rtrack2_g_signal(struct radio_isa_card *isa) { - struct rtrack2 *rt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - rt_setfreq(rt, f->frequency); - return 0; + /* bit set = no signal present */ + return (inb(isa->io) & 2) ? 0 : 0xffff; } -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) +static int rtrack2_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - struct rtrack2 *rt = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = rt->curfreq; + outb(mute, isa->io); return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 65535, 65535, 65535); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct rtrack2 *rt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = rt->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - if (rt->muted) - ctrl->value = 0; - else - ctrl->value = 65535; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct rtrack2 *rt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - rt_mute(rt); - else - rt_unmute(rt); - return 0; - case V4L2_CID_AUDIO_VOLUME: - if (ctrl->value) - rt_unmute(rt); - else - rt_mute(rt); - return 0; - } - return -EINVAL; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - -static const struct v4l2_file_operations rtrack2_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops rtrack2_ops = { + .alloc = rtrack2_alloc, + .s_mute_volume = rtrack2_s_mute_volume, + .s_frequency = rtrack2_s_frequency, + .g_signal = rtrack2_g_signal, }; -static const struct v4l2_ioctl_ops rtrack2_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, +static const int rtrack2_ioports[] = { 0x20f, 0x30f }; + +static struct radio_isa_driver rtrack2_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-rtrack2", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = rtrack2_ioports, + .num_of_io_ports = ARRAY_SIZE(rtrack2_ioports), + .region_size = 4, + .card = "AIMSlab RadioTrack II", + .ops = &rtrack2_ops, + .has_stereo = true, }; static int __init rtrack2_init(void) { - struct rtrack2 *dev = &rtrack2_card; - struct v4l2_device *v4l2_dev = &dev->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "rtrack2", sizeof(v4l2_dev->name)); - dev->io = io; - if (dev->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or io=0x30c\n"); - return -EINVAL; - } - if (!request_region(dev->io, 4, "rtrack2")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", dev->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(dev->io, 4); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); - dev->vdev.v4l2_dev = v4l2_dev; - dev->vdev.fops = &rtrack2_fops; - dev->vdev.ioctl_ops = &rtrack2_ioctl_ops; - dev->vdev.release = video_device_release_empty; - video_set_drvdata(&dev->vdev, dev); - - /* mute card - prevents noisy bootups */ - outb(1, dev->io); - dev->muted = 1; - - mutex_init(&dev->lock); - if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(dev->io, 4); - return -EINVAL; - } - - v4l2_info(v4l2_dev, "AIMSlab Radiotrack II card driver.\n"); - - return 0; + return isa_register_driver(&rtrack2_driver.driver, RTRACK2_MAX); } static void __exit rtrack2_exit(void) { - struct rtrack2 *dev = &rtrack2_card; - - video_unregister_device(&dev->vdev); - v4l2_device_unregister(&dev->v4l2_dev); - release_region(dev->io, 4); + isa_unregister_driver(&rtrack2_driver.driver); } module_init(rtrack2_init); diff --git a/drivers/media/radio/radio-sf16fmr2.c b/drivers/media/radio/radio-sf16fmr2.c index 7ab9afadf29b..7c69214334bf 100644 --- a/drivers/media/radio/radio-sf16fmr2.c +++ b/drivers/media/radio/radio-sf16fmr2.c @@ -9,16 +9,23 @@ #include <linux/delay.h> #include <linux/module.h> /* Modules */ #include <linux/init.h> /* Initdata */ +#include <linux/slab.h> #include <linux/ioport.h> /* request_region */ #include <linux/io.h> /* outb, outb_p */ +#include <linux/isa.h> #include <sound/tea575x-tuner.h> MODULE_AUTHOR("Ondrej Zary"); MODULE_DESCRIPTION("MediaForte SF16-FMR2 FM radio card driver"); MODULE_LICENSE("GPL"); +static int radio_nr = -1; +module_param(radio_nr, int, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device number"); + struct fmr2 { int io; + struct v4l2_device v4l2_dev; struct snd_tea575x tea; struct v4l2_ctrl *volume; struct v4l2_ctrl *balance; @@ -26,7 +33,6 @@ struct fmr2 { /* the port is hardwired so no need to support multiple cards */ #define FMR2_PORT 0x384 -static struct fmr2 fmr2_card; /* TEA575x tuner pins */ #define STR_DATA (1 << 0) @@ -180,26 +186,46 @@ static int fmr2_tea_ext_init(struct snd_tea575x *tea) return 0; } -static int __init fmr2_init(void) +static int __devinit fmr2_probe(struct device *pdev, unsigned int dev) { - struct fmr2 *fmr2 = &fmr2_card; + struct fmr2 *fmr2; + int err; + + fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL); + if (fmr2 == NULL) + return -ENOMEM; + strlcpy(fmr2->v4l2_dev.name, dev_name(pdev), + sizeof(fmr2->v4l2_dev.name)); fmr2->io = FMR2_PORT; - if (!request_region(fmr2->io, 2, "SF16-FMR2")) { + if (!request_region(fmr2->io, 2, fmr2->v4l2_dev.name)) { printk(KERN_ERR "radio-sf16fmr2: I/O port 0x%x already in use\n", fmr2->io); + kfree(fmr2); return -EBUSY; } + dev_set_drvdata(pdev, fmr2); + err = v4l2_device_register(pdev, &fmr2->v4l2_dev); + if (err < 0) { + v4l2_err(&fmr2->v4l2_dev, "Could not register v4l2_device\n"); + release_region(fmr2->io, 2); + kfree(fmr2); + return err; + } + fmr2->tea.v4l2_dev = &fmr2->v4l2_dev; fmr2->tea.private_data = fmr2; + fmr2->tea.radio_nr = radio_nr; fmr2->tea.ops = &fmr2_tea_ops; fmr2->tea.ext_init = fmr2_tea_ext_init; strlcpy(fmr2->tea.card, "SF16-FMR2", sizeof(fmr2->tea.card)); - strcpy(fmr2->tea.bus_info, "ISA"); + snprintf(fmr2->tea.bus_info, sizeof(fmr2->tea.bus_info), "ISA:%s", + fmr2->v4l2_dev.name); if (snd_tea575x_init(&fmr2->tea)) { printk(KERN_ERR "radio-sf16fmr2: Unable to detect TEA575x tuner\n"); release_region(fmr2->io, 2); + kfree(fmr2); return -ENODEV; } @@ -207,12 +233,33 @@ static int __init fmr2_init(void) return 0; } -static void __exit fmr2_exit(void) +static int __exit fmr2_remove(struct device *pdev, unsigned int dev) { - struct fmr2 *fmr2 = &fmr2_card; + struct fmr2 *fmr2 = dev_get_drvdata(pdev); snd_tea575x_exit(&fmr2->tea); release_region(fmr2->io, 2); + v4l2_device_unregister(&fmr2->v4l2_dev); + kfree(fmr2); + return 0; +} + +struct isa_driver fmr2_driver = { + .probe = fmr2_probe, + .remove = fmr2_remove, + .driver = { + .name = "radio-sf16fmr2", + }, +}; + +static int __init fmr2_init(void) +{ + return isa_register_driver(&fmr2_driver, 1); +} + +static void __exit fmr2_exit(void) +{ + isa_unregister_driver(&fmr2_driver); } module_init(fmr2_init); diff --git a/drivers/media/radio/radio-tea5764.c b/drivers/media/radio/radio-tea5764.c index db20904d01f0..6b1fae32b483 100644 --- a/drivers/media/radio/radio-tea5764.c +++ b/drivers/media/radio/radio-tea5764.c @@ -575,21 +575,7 @@ static struct i2c_driver tea5764_i2c_driver = { .id_table = tea5764_id, }; -/* init the driver */ -static int __init tea5764_init(void) -{ - int ret = i2c_add_driver(&tea5764_i2c_driver); - - printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ": " - DRIVER_DESC "\n"); - return ret; -} - -/* cleanup the driver */ -static void __exit tea5764_exit(void) -{ - i2c_del_driver(&tea5764_i2c_driver); -} +module_i2c_driver(tea5764_i2c_driver); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); @@ -600,6 +586,3 @@ module_param(use_xtal, int, 0); MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board"); module_param(radio_nr, int, 0); MODULE_PARM_DESC(radio_nr, "video4linux device number to use"); - -module_init(tea5764_init); -module_exit(tea5764_exit); diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c index f2ed9cc3cf3b..be10a802e3a9 100644 --- a/drivers/media/radio/radio-terratec.c +++ b/drivers/media/radio/radio-terratec.c @@ -16,11 +16,7 @@ * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); * Volume Control is done digitally * - * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday - * (as soon i have understand how to get started :) - * If you can help me out with that, please contact me!! - * - * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> */ @@ -30,43 +26,24 @@ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/mutex.h> #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" -MODULE_AUTHOR("R.OFFERMANNS & others"); +MODULE_AUTHOR("R. Offermans & others"); MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); - -#ifndef CONFIG_RADIO_TERRATEC_PORT -#define CONFIG_RADIO_TERRATEC_PORT 0x590 -#endif +MODULE_VERSION("0.1.99"); -static int io = CONFIG_RADIO_TERRATEC_PORT; +/* Note: there seems to be only one possible port (0x590), but without + hardware this is hard to verify. For now, this is the only one we will + support. */ +static int io = 0x590; static int radio_nr = -1; -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); -module_param(radio_nr, int, 0); - -static struct v4l2_queryctrl radio_qctrl[] = { - { - .id = V4L2_CID_AUDIO_MUTE, - .name = "Mute", - .minimum = 0, - .maximum = 1, - .default_value = 1, - .type = V4L2_CTRL_TYPE_BOOLEAN, - },{ - .id = V4L2_CID_AUDIO_VOLUME, - .name = "Volume", - .minimum = 0, - .maximum = 0xff, - .step = 1, - .default_value = 0xff, - .type = V4L2_CTRL_TYPE_INTEGER, - } -}; +module_param(radio_nr, int, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device number"); #define WRT_DIS 0x00 #define CLK_OFF 0x00 @@ -76,63 +53,24 @@ static struct v4l2_queryctrl radio_qctrl[] = { #define CLK_ON 0x08 #define WRT_EN 0x10 -struct terratec +static struct radio_isa_card *terratec_alloc(void) { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; - int curvol; - unsigned long curfreq; - int muted; - struct mutex lock; -}; - -static struct terratec terratec_card; - -/* local things */ + return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL); +} -static void tt_write_vol(struct terratec *tt, int volume) +static int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { int i; - volume = volume + (volume * 32); /* change both channels */ - mutex_lock(&tt->lock); + if (mute) + vol = 0; + vol = vol + (vol * 32); /* change both channels */ for (i = 0; i < 8; i++) { - if (volume & (0x80 >> i)) - outb(0x80, tt->io + 1); + if (vol & (0x80 >> i)) + outb(0x80, isa->io + 1); else - outb(0x00, tt->io + 1); - } - mutex_unlock(&tt->lock); -} - - - -static void tt_mute(struct terratec *tt) -{ - tt->muted = 1; - tt_write_vol(tt, 0); -} - -static int tt_setvol(struct terratec *tt, int vol) -{ - if (vol == tt->curvol) { /* requested volume = current */ - if (tt->muted) { /* user is unmuting the card */ - tt->muted = 0; - tt_write_vol(tt, vol); /* enable card */ - } - return 0; - } - - if (vol == 0) { /* volume = 0 means mute the card */ - tt_write_vol(tt, 0); /* "turn off card" by setting vol to 0 */ - tt->curvol = vol; /* track the volume state! */ - return 0; + outb(0x00, isa->io + 1); } - - tt->muted = 0; - tt_write_vol(tt, vol); - tt->curvol = vol; return 0; } @@ -140,20 +78,15 @@ static int tt_setvol(struct terratec *tt, int vol) /* this is the worst part in this driver */ /* many more or less strange things are going on here, but hey, it works :) */ -static int tt_setfreq(struct terratec *tt, unsigned long freq1) +static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) { - int freq; int i; int p; - int temp; + int temp; long rest; unsigned char buffer[25]; /* we have to bit shift 25 registers */ - mutex_lock(&tt->lock); - - tt->curfreq = freq1; - - freq = freq1 / 160; /* convert the freq. to a nice to handle value */ + freq = freq / 160; /* convert the freq. to a nice to handle value */ memset(buffer, 0, sizeof(buffer)); rest = freq * 10 + 10700; /* I once had understood what is going on here */ @@ -175,239 +108,61 @@ static int tt_setfreq(struct terratec *tt, unsigned long freq1) for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */ if (buffer[i] == 1) { - outb(WRT_EN | DATA, tt->io); - outb(WRT_EN | DATA | CLK_ON, tt->io); - outb(WRT_EN | DATA, tt->io); + outb(WRT_EN | DATA, isa->io); + outb(WRT_EN | DATA | CLK_ON, isa->io); + outb(WRT_EN | DATA, isa->io); } else { - outb(WRT_EN | 0x00, tt->io); - outb(WRT_EN | 0x00 | CLK_ON, tt->io); - } - } - outb(0x00, tt->io); - - mutex_unlock(&tt->lock); - - return 0; -} - -static int tt_getsigstr(struct terratec *tt) -{ - if (inb(tt->io) & 2) /* bit set = no signal present */ - return 0; - return 1; /* signal present */ -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-terratec", sizeof(v->driver)); - strlcpy(v->card, "ActiveRadio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct terratec *tt = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * tt_getsigstr(tt); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct terratec *tt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - tt_setfreq(tt, f->frequency); - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct terratec *tt = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = tt->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { - if (qc->id && qc->id == radio_qctrl[i].id) { - memcpy(qc, &(radio_qctrl[i]), sizeof(*qc)); - return 0; + outb(WRT_EN | 0x00, isa->io); + outb(WRT_EN | 0x00 | CLK_ON, isa->io); } } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct terratec *tt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (tt->muted) - ctrl->value = 1; - else - ctrl->value = 0; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = tt->curvol * 6554; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct terratec *tt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - tt_mute(tt); - else - tt_setvol(tt,tt->curvol); - return 0; - case V4L2_CID_AUDIO_VOLUME: - tt_setvol(tt,ctrl->value); - return 0; - } - return -EINVAL; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; + outb(0x00, isa->io); return 0; } -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) +static u32 terratec_g_signal(struct radio_isa_card *isa) { - return a->index ? -EINVAL : 0; + /* bit set = no signal present */ + return (inb(isa->io) & 2) ? 0 : 0xffff; } -static const struct v4l2_file_operations terratec_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops terratec_ops = { + .alloc = terratec_alloc, + .s_mute_volume = terratec_s_mute_volume, + .s_frequency = terratec_s_frequency, + .g_signal = terratec_g_signal, }; -static const struct v4l2_ioctl_ops terratec_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, +static const int terratec_ioports[] = { 0x590 }; + +static struct radio_isa_driver terratec_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-terratec", + }, + }, + .io_params = &io, + .radio_nr_params = &radio_nr, + .io_ports = terratec_ioports, + .num_of_io_ports = ARRAY_SIZE(terratec_ioports), + .region_size = 2, + .card = "TerraTec ActiveRadio", + .ops = &terratec_ops, + .has_stereo = true, + .max_volume = 10, }; static int __init terratec_init(void) { - struct terratec *tt = &terratec_card; - struct v4l2_device *v4l2_dev = &tt->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "terratec", sizeof(v4l2_dev->name)); - tt->io = io; - if (tt->io == -1) { - v4l2_err(v4l2_dev, "you must set an I/O address with io=0x590 or 0x591\n"); - return -EINVAL; - } - if (!request_region(tt->io, 2, "terratec")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(tt->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - strlcpy(tt->vdev.name, v4l2_dev->name, sizeof(tt->vdev.name)); - tt->vdev.v4l2_dev = v4l2_dev; - tt->vdev.fops = &terratec_fops; - tt->vdev.ioctl_ops = &terratec_ioctl_ops; - tt->vdev.release = video_device_release_empty; - video_set_drvdata(&tt->vdev, tt); - - mutex_init(&tt->lock); - - /* mute card - prevents noisy bootups */ - tt_write_vol(tt, 0); - - if (video_register_device(&tt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(&tt->v4l2_dev); - release_region(tt->io, 2); - return -EINVAL; - } - - v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver.\n"); - return 0; + return isa_register_driver(&terratec_driver.driver, 1); } static void __exit terratec_exit(void) { - struct terratec *tt = &terratec_card; - struct v4l2_device *v4l2_dev = &tt->v4l2_dev; - - video_unregister_device(&tt->vdev); - v4l2_device_unregister(&tt->v4l2_dev); - release_region(tt->io, 2); - v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver unloaded.\n"); + isa_unregister_driver(&terratec_driver.driver); } module_init(terratec_init); diff --git a/drivers/media/radio/radio-trust.c b/drivers/media/radio/radio-trust.c index b3f45a019d82..26a8c6002121 100644 --- a/drivers/media/radio/radio-trust.c +++ b/drivers/media/radio/radio-trust.c @@ -21,13 +21,15 @@ #include <linux/ioport.h> #include <linux/videodev2.h> #include <linux/io.h> +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); MODULE_DESCRIPTION("A driver for the Trust FM Radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("0.1.99"); /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ @@ -35,39 +37,38 @@ MODULE_VERSION("0.0.3"); #define CONFIG_RADIO_TRUST_PORT -1 #endif -static int io = CONFIG_RADIO_TRUST_PORT; -static int radio_nr = -1; +#define TRUST_MAX 2 -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)"); -module_param(radio_nr, int, 0); +static int io[TRUST_MAX] = { [0] = CONFIG_RADIO_TRUST_PORT, + [1 ... (TRUST_MAX - 1)] = -1 }; +static int radio_nr[TRUST_MAX] = { [0 ... (TRUST_MAX - 1)] = -1 }; + +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Trust FM Radio card (0x350 or 0x358)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); struct trust { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; + struct radio_isa_card isa; int ioval; - __u16 curvol; - __u16 curbass; - __u16 curtreble; - int muted; - unsigned long curfreq; - int curstereo; - int curmute; - struct mutex lock; }; -static struct trust trust_card; +static struct radio_isa_card *trust_alloc(void) +{ + struct trust *tr = kzalloc(sizeof(*tr), GFP_KERNEL); + + return tr ? &tr->isa : NULL; +} /* i2c addresses */ #define TDA7318_ADDR 0x88 #define TSA6060T_ADDR 0xc4 -#define TR_DELAY do { inb(tr->io); inb(tr->io); inb(tr->io); } while (0) -#define TR_SET_SCL outb(tr->ioval |= 2, tr->io) -#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->io) -#define TR_SET_SDA outb(tr->ioval |= 1, tr->io) -#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->io) +#define TR_DELAY do { inb(tr->isa.io); inb(tr->isa.io); inb(tr->isa.io); } while (0) +#define TR_SET_SCL outb(tr->ioval |= 2, tr->isa.io) +#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->isa.io) +#define TR_SET_SDA outb(tr->ioval |= 1, tr->isa.io) +#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->isa.io) static void write_i2c(struct trust *tr, int n, ...) { @@ -84,10 +85,10 @@ static void write_i2c(struct trust *tr, int n, ...) TR_CLR_SCL; TR_DELAY; - for(; n; n--) { + for (; n; n--) { val = va_arg(args, unsigned); - for(mask = 0x80; mask; mask >>= 1) { - if(val & mask) + for (mask = 0x80; mask; mask >>= 1) { + if (val & mask) TR_SET_SDA; else TR_CLR_SDA; @@ -115,317 +116,128 @@ static void write_i2c(struct trust *tr, int n, ...) va_end(args); } -static void tr_setvol(struct trust *tr, __u16 vol) +static int trust_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - mutex_lock(&tr->lock); - tr->curvol = vol / 2048; - write_i2c(tr, 2, TDA7318_ADDR, tr->curvol ^ 0x1f); - mutex_unlock(&tr->lock); -} + struct trust *tr = container_of(isa, struct trust, isa); -static int basstreble2chip[15] = { - 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 -}; - -static void tr_setbass(struct trust *tr, __u16 bass) -{ - mutex_lock(&tr->lock); - tr->curbass = bass / 4370; - write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[tr->curbass]); - mutex_unlock(&tr->lock); -} - -static void tr_settreble(struct trust *tr, __u16 treble) -{ - mutex_lock(&tr->lock); - tr->curtreble = treble / 4370; - write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[tr->curtreble]); - mutex_unlock(&tr->lock); + tr->ioval = (tr->ioval & 0xf7) | (mute << 3); + outb(tr->ioval, isa->io); + write_i2c(tr, 2, TDA7318_ADDR, vol ^ 0x1f); + return 0; } -static void tr_setstereo(struct trust *tr, int stereo) +static int trust_s_stereo(struct radio_isa_card *isa, bool stereo) { - mutex_lock(&tr->lock); - tr->curstereo = !!stereo; - tr->ioval = (tr->ioval & 0xfb) | (!tr->curstereo << 2); - outb(tr->ioval, tr->io); - mutex_unlock(&tr->lock); -} + struct trust *tr = container_of(isa, struct trust, isa); -static void tr_setmute(struct trust *tr, int mute) -{ - mutex_lock(&tr->lock); - tr->curmute = !!mute; - tr->ioval = (tr->ioval & 0xf7) | (tr->curmute << 3); - outb(tr->ioval, tr->io); - mutex_unlock(&tr->lock); + tr->ioval = (tr->ioval & 0xfb) | (!stereo << 2); + outb(tr->ioval, isa->io); + return 0; } -static int tr_getsigstr(struct trust *tr) +static u32 trust_g_signal(struct radio_isa_card *isa) { int i, v; - mutex_lock(&tr->lock); for (i = 0, v = 0; i < 100; i++) - v |= inb(tr->io); - mutex_unlock(&tr->lock); + v |= inb(isa->io); return (v & 1) ? 0 : 0xffff; } -static int tr_getstereo(struct trust *tr) -{ - /* don't know how to determine it, just return the setting */ - return tr->curstereo; -} - -static void tr_setfreq(struct trust *tr, unsigned long f) +static int trust_s_frequency(struct radio_isa_card *isa, u32 freq) { - mutex_lock(&tr->lock); - tr->curfreq = f; - f /= 160; /* Convert to 10 kHz units */ - f += 1070; /* Add 10.7 MHz IF */ - write_i2c(tr, 5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0); - mutex_unlock(&tr->lock); -} + struct trust *tr = container_of(isa, struct trust, isa); -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-trust", sizeof(v->driver)); - strlcpy(v->card, "Trust FM Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + freq /= 160; /* Convert to 10 kHz units */ + freq += 1070; /* Add 10.7 MHz IF */ + write_i2c(tr, 5, TSA6060T_ADDR, (freq << 1) | 1, + freq >> 7, 0x60 | ((freq >> 15) & 1), 0); return 0; } -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct trust *tr = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87.5 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (tr_getstereo(tr)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = tr_getsigstr(tr); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct trust *tr = video_drvdata(file); - - if (v->index) - return -EINVAL; - tr_setstereo(tr, v->audmode == V4L2_TUNER_MODE_STEREO); - return 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct trust *tr = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - tr_setfreq(tr, f->frequency); - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct trust *tr = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = tr->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 65535, 2048, 65535); - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - return v4l2_ctrl_query_fill(qc, 0, 65535, 4370, 32768); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct trust *tr = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = tr->curmute; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = tr->curvol * 2048; - return 0; - case V4L2_CID_AUDIO_BASS: - ctrl->value = tr->curbass * 4370; - return 0; - case V4L2_CID_AUDIO_TREBLE: - ctrl->value = tr->curtreble * 4370; - return 0; - } - return -EINVAL; -} +static int basstreble2chip[15] = { + 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 +}; -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int trust_s_ctrl(struct v4l2_ctrl *ctrl) { - struct trust *tr = video_drvdata(file); + struct radio_isa_card *isa = + container_of(ctrl->handler, struct radio_isa_card, hdl); + struct trust *tr = container_of(isa, struct trust, isa); switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - tr_setmute(tr, ctrl->value); - return 0; - case V4L2_CID_AUDIO_VOLUME: - tr_setvol(tr, ctrl->value); - return 0; case V4L2_CID_AUDIO_BASS: - tr_setbass(tr, ctrl->value); + write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[ctrl->val]); return 0; case V4L2_CID_AUDIO_TREBLE: - tr_settreble(tr, ctrl->value); + write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[ctrl->val]); return 0; } return -EINVAL; } -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - -static const struct v4l2_file_operations trust_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, -}; - -static const struct v4l2_ioctl_ops trust_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, +static const struct v4l2_ctrl_ops trust_ctrl_ops = { + .s_ctrl = trust_s_ctrl, }; -static int __init trust_init(void) +static int trust_initialize(struct radio_isa_card *isa) { - struct trust *tr = &trust_card; - struct v4l2_device *v4l2_dev = &tr->v4l2_dev; - int res; + struct trust *tr = container_of(isa, struct trust, isa); - strlcpy(v4l2_dev->name, "trust", sizeof(v4l2_dev->name)); - tr->io = io; tr->ioval = 0xf; - mutex_init(&tr->lock); - - if (tr->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x0x350 or 0x358\n"); - return -EINVAL; - } - if (!request_region(tr->io, 2, "Trust FM Radio")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", tr->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(tr->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - strlcpy(tr->vdev.name, v4l2_dev->name, sizeof(tr->vdev.name)); - tr->vdev.v4l2_dev = v4l2_dev; - tr->vdev.fops = &trust_fops; - tr->vdev.ioctl_ops = &trust_ioctl_ops; - tr->vdev.release = video_device_release_empty; - video_set_drvdata(&tr->vdev, tr); - write_i2c(tr, 2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */ - tr_setvol(tr, 0xffff); - tr_setbass(tr, 0x8000); - tr_settreble(tr, 0x8000); - tr_setstereo(tr, 1); - - /* mute card - prevents noisy bootups */ - tr_setmute(tr, 1); + v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops, + V4L2_CID_AUDIO_BASS, 0, 15, 1, 8); + v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, 0, 15, 1, 8); + return isa->hdl.error; +} - if (video_register_device(&tr->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(tr->io, 2); - return -EINVAL; - } +static const struct radio_isa_ops trust_ops = { + .init = trust_initialize, + .alloc = trust_alloc, + .s_mute_volume = trust_s_mute_volume, + .s_frequency = trust_s_frequency, + .s_stereo = trust_s_stereo, + .g_signal = trust_g_signal, +}; - v4l2_info(v4l2_dev, "Trust FM Radio card driver v1.0.\n"); +static const int trust_ioports[] = { 0x350, 0x358 }; + +static struct radio_isa_driver trust_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-trust", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = trust_ioports, + .num_of_io_ports = ARRAY_SIZE(trust_ioports), + .region_size = 2, + .card = "Trust FM Radio", + .ops = &trust_ops, + .has_stereo = true, + .max_volume = 31, +}; - return 0; +static int __init trust_init(void) +{ + return isa_register_driver(&trust_driver.driver, TRUST_MAX); } -static void __exit cleanup_trust_module(void) +static void __exit trust_exit(void) { - struct trust *tr = &trust_card; - - video_unregister_device(&tr->vdev); - v4l2_device_unregister(&tr->v4l2_dev); - release_region(tr->io, 2); + isa_unregister_driver(&trust_driver.driver); } module_init(trust_init); -module_exit(cleanup_trust_module); +module_exit(trust_exit); diff --git a/drivers/media/radio/radio-typhoon.c b/drivers/media/radio/radio-typhoon.c index 398726abc0c8..eb72a4d13758 100644 --- a/drivers/media/radio/radio-typhoon.c +++ b/drivers/media/radio/radio-typhoon.c @@ -33,63 +33,53 @@ #include <linux/ioport.h> /* request_region */ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" #define DRIVER_VERSION "0.1.2" MODULE_AUTHOR("Dr. Henrik Seidel"); MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); MODULE_LICENSE("GPL"); -MODULE_VERSION(DRIVER_VERSION); +MODULE_VERSION("0.1.99"); #ifndef CONFIG_RADIO_TYPHOON_PORT #define CONFIG_RADIO_TYPHOON_PORT -1 #endif #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ -#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0 +#define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000 #endif -static int io = CONFIG_RADIO_TYPHOON_PORT; -static int radio_nr = -1; - -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)"); - -module_param(radio_nr, int, 0); +#define TYPHOON_MAX 2 +static int io[TYPHOON_MAX] = { [0] = CONFIG_RADIO_TYPHOON_PORT, + [1 ... (TYPHOON_MAX - 1)] = -1 }; +static int radio_nr[TYPHOON_MAX] = { [0 ... (TYPHOON_MAX - 1)] = -1 }; static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ; + +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Typhoon card (0x316 or 0x336)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); module_param(mutefreq, ulong, 0); MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); -#define BANNER "Typhoon Radio Card driver v" DRIVER_VERSION "\n" - struct typhoon { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; - int curvol; + struct radio_isa_card isa; int muted; - unsigned long curfreq; - unsigned long mutefreq; - struct mutex lock; }; -static struct typhoon typhoon_card; - -static void typhoon_setvol_generic(struct typhoon *dev, int vol) +static struct radio_isa_card *typhoon_alloc(void) { - mutex_lock(&dev->lock); - vol >>= 14; /* Map 16 bit to 2 bit */ - vol &= 3; - outb_p(vol / 2, dev->io); /* Set the volume, high bit. */ - outb_p(vol % 2, dev->io + 2); /* Set the volume, low bit. */ - mutex_unlock(&dev->lock); + struct typhoon *ty = kzalloc(sizeof(*ty), GFP_KERNEL); + + return ty ? &ty->isa : NULL; } -static int typhoon_setfreq_generic(struct typhoon *dev, - unsigned long frequency) +static int typhoon_s_frequency(struct radio_isa_card *isa, u32 freq) { unsigned long outval; unsigned long x; @@ -105,302 +95,86 @@ static int typhoon_setfreq_generic(struct typhoon *dev, * */ - mutex_lock(&dev->lock); - x = frequency / 160; + x = freq / 160; outval = (x * x + 2500) / 5000; outval = (outval * x + 5000) / 10000; outval -= (10 * x * x + 10433) / 20866; outval += 4 * x - 11505; - outb_p((outval >> 8) & 0x01, dev->io + 4); - outb_p(outval >> 9, dev->io + 6); - outb_p(outval & 0xff, dev->io + 8); - mutex_unlock(&dev->lock); - - return 0; -} - -static int typhoon_setfreq(struct typhoon *dev, unsigned long frequency) -{ - typhoon_setfreq_generic(dev, frequency); - dev->curfreq = frequency; - return 0; -} - -static void typhoon_mute(struct typhoon *dev) -{ - if (dev->muted == 1) - return; - typhoon_setvol_generic(dev, 0); - typhoon_setfreq_generic(dev, dev->mutefreq); - dev->muted = 1; -} - -static void typhoon_unmute(struct typhoon *dev) -{ - if (dev->muted == 0) - return; - typhoon_setfreq_generic(dev, dev->curfreq); - typhoon_setvol_generic(dev, dev->curvol); - dev->muted = 0; -} - -static int typhoon_setvol(struct typhoon *dev, int vol) -{ - if (dev->muted && vol != 0) { /* user is unmuting the card */ - dev->curvol = vol; - typhoon_unmute(dev); - return 0; - } - if (vol == dev->curvol) /* requested volume == current */ - return 0; - - if (vol == 0) { /* volume == 0 means mute the card */ - typhoon_mute(dev); - dev->curvol = vol; - return 0; - } - typhoon_setvol_generic(dev, vol); - dev->curvol = vol; - return 0; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-typhoon", sizeof(v->driver)); - strlcpy(v->card, "Typhoon Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87.5 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF; /* We can't get the signal strength */ - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct typhoon *dev = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = dev->curfreq; - return 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct typhoon *dev = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - dev->curfreq = f->frequency; - typhoon_setfreq(dev, dev->curfreq); + outb_p((outval >> 8) & 0x01, isa->io + 4); + outb_p(outval >> 9, isa->io + 6); + outb_p(outval & 0xff, isa->io + 8); return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) +static int typhoon_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 65535, 16384, 65535); - } - return -EINVAL; -} + struct typhoon *ty = container_of(isa, struct typhoon, isa); -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct typhoon *dev = video_drvdata(file); + if (mute) + vol = 0; + vol >>= 14; /* Map 16 bit to 2 bit */ + vol &= 3; + outb_p(vol / 2, isa->io); /* Set the volume, high bit. */ + outb_p(vol % 2, isa->io + 2); /* Set the volume, low bit. */ - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = dev->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = dev->curvol; - return 0; + if (vol == 0 && !ty->muted) { + ty->muted = true; + return typhoon_s_frequency(isa, mutefreq << 4); } - return -EINVAL; -} - -static int vidioc_s_ctrl (struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct typhoon *dev = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - typhoon_mute(dev); - else - typhoon_unmute(dev); - return 0; - case V4L2_CID_AUDIO_VOLUME: - typhoon_setvol(dev, ctrl->value); - return 0; + if (vol && ty->muted) { + ty->muted = false; + return typhoon_s_frequency(isa, isa->freq); } - return -EINVAL; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; return 0; } -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - -static int vidioc_log_status(struct file *file, void *priv) -{ - struct typhoon *dev = video_drvdata(file); - struct v4l2_device *v4l2_dev = &dev->v4l2_dev; - - v4l2_info(v4l2_dev, BANNER); -#ifdef MODULE - v4l2_info(v4l2_dev, "Load type: Driver loaded as a module\n\n"); -#else - v4l2_info(v4l2_dev, "Load type: Driver compiled into kernel\n\n"); -#endif - v4l2_info(v4l2_dev, "frequency = %lu kHz\n", dev->curfreq >> 4); - v4l2_info(v4l2_dev, "volume = %d\n", dev->curvol); - v4l2_info(v4l2_dev, "mute = %s\n", dev->muted ? "on" : "off"); - v4l2_info(v4l2_dev, "io = 0x%x\n", dev->io); - v4l2_info(v4l2_dev, "mute frequency = %lu kHz\n", dev->mutefreq >> 4); - return 0; -} - -static const struct v4l2_file_operations typhoon_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops typhoon_ops = { + .alloc = typhoon_alloc, + .s_mute_volume = typhoon_s_mute_volume, + .s_frequency = typhoon_s_frequency, }; -static const struct v4l2_ioctl_ops typhoon_ioctl_ops = { - .vidioc_log_status = vidioc_log_status, - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, +static const int typhoon_ioports[] = { 0x316, 0x336 }; + +static struct radio_isa_driver typhoon_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-typhoon", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = typhoon_ioports, + .num_of_io_ports = ARRAY_SIZE(typhoon_ioports), + .region_size = 8, + .card = "Typhoon Radio", + .ops = &typhoon_ops, + .has_stereo = true, + .max_volume = 3, }; static int __init typhoon_init(void) { - struct typhoon *dev = &typhoon_card; - struct v4l2_device *v4l2_dev = &dev->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "typhoon", sizeof(v4l2_dev->name)); - dev->io = io; - - if (dev->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x316 or io=0x336\n"); - return -EINVAL; - } - - if (mutefreq < 87000 || mutefreq > 108500) { - v4l2_err(v4l2_dev, "You must set a frequency (in kHz) used when muting the card,\n"); - v4l2_err(v4l2_dev, "e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n"); - return -EINVAL; - } - dev->curfreq = dev->mutefreq = mutefreq << 4; - - mutex_init(&dev->lock); - if (!request_region(dev->io, 8, "typhoon")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", - dev->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(dev->io, 8); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; + if (mutefreq < 87000 || mutefreq > 108000) { + printk(KERN_ERR "%s: You must set a frequency (in kHz) used when muting the card,\n", + typhoon_driver.driver.driver.name); + printk(KERN_ERR "%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n", + typhoon_driver.driver.driver.name); + return -ENODEV; } - v4l2_info(v4l2_dev, BANNER); - - strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); - dev->vdev.v4l2_dev = v4l2_dev; - dev->vdev.fops = &typhoon_fops; - dev->vdev.ioctl_ops = &typhoon_ioctl_ops; - dev->vdev.release = video_device_release_empty; - video_set_drvdata(&dev->vdev, dev); - - /* mute card - prevents noisy bootups */ - typhoon_mute(dev); - - if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(&dev->v4l2_dev); - release_region(dev->io, 8); - return -EINVAL; - } - v4l2_info(v4l2_dev, "port 0x%x.\n", dev->io); - v4l2_info(v4l2_dev, "mute frequency is %lu kHz.\n", mutefreq); - - return 0; + return isa_register_driver(&typhoon_driver.driver, TYPHOON_MAX); } static void __exit typhoon_exit(void) { - struct typhoon *dev = &typhoon_card; - - video_unregister_device(&dev->vdev); - v4l2_device_unregister(&dev->v4l2_dev); - release_region(dev->io, 8); + isa_unregister_driver(&typhoon_driver.driver); } + module_init(typhoon_init); module_exit(typhoon_exit); diff --git a/drivers/media/radio/radio-zoltrix.c b/drivers/media/radio/radio-zoltrix.c index f5613b948203..026e88eef29c 100644 --- a/drivers/media/radio/radio-zoltrix.c +++ b/drivers/media/radio/radio-zoltrix.c @@ -1,5 +1,6 @@ -/* zoltrix radio plus driver for Linux radio support - * (c) 1998 C. van Schaik <carl@leg.uct.ac.za> +/* + * Zoltrix Radio Plus driver + * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za> * * BUGS * Due to the inconsistency in reading from the signal flags @@ -27,6 +28,14 @@ * * 2006-07-24 - Converted to V4L2 API * by Mauro Carvalho Chehab <mchehab@infradead.org> + * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> + * + * Note that this is the driver for the Zoltrix Radio Plus. + * This driver does not work for the Zoltrix Radio Plus 108 or the + * Zoltrix Radio Plus for Windows. + * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -36,82 +45,70 @@ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/mutex.h> #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" -MODULE_AUTHOR("C.van Schaik"); +MODULE_AUTHOR("C. van Schaik"); MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("0.1.99"); #ifndef CONFIG_RADIO_ZOLTRIX_PORT #define CONFIG_RADIO_ZOLTRIX_PORT -1 #endif -static int io = CONFIG_RADIO_ZOLTRIX_PORT; -static int radio_nr = -1; +#define ZOLTRIX_MAX 2 + +static int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT, + [1 ... (ZOLTRIX_MAX - 1)] = -1 }; +static int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 }; -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)"); -module_param(radio_nr, int, 0); +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); struct zoltrix { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; + struct radio_isa_card isa; int curvol; - unsigned long curfreq; - int muted; - unsigned int stereo; - struct mutex lock; + bool muted; }; -static struct zoltrix zoltrix_card; +static struct radio_isa_card *zoltrix_alloc(void) +{ + struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL); + + return zol ? &zol->isa : NULL; +} -static int zol_setvol(struct zoltrix *zol, int vol) +static int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - zol->curvol = vol; - if (zol->muted) - return 0; + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); - mutex_lock(&zol->lock); - if (vol == 0) { - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); /* Zoltrix needs to be read to confirm */ - mutex_unlock(&zol->lock); + zol->curvol = vol; + zol->muted = mute; + if (mute || vol == 0) { + outb(0, isa->io); + outb(0, isa->io); + inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ return 0; } - outb(zol->curvol-1, zol->io); + outb(vol - 1, isa->io); msleep(10); - inb(zol->io + 2); - mutex_unlock(&zol->lock); + inb(isa->io + 2); return 0; } -static void zol_mute(struct zoltrix *zol) -{ - zol->muted = 1; - mutex_lock(&zol->lock); - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); /* Zoltrix needs to be read to confirm */ - mutex_unlock(&zol->lock); -} - -static void zol_unmute(struct zoltrix *zol) -{ - zol->muted = 0; - zol_setvol(zol, zol->curvol); -} - -static int zol_setfreq(struct zoltrix *zol, unsigned long freq) +/* tunes the radio to the desired frequency */ +static int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq) { - /* tunes the radio to the desired frequency */ - struct v4l2_device *v4l2_dev = &zol->v4l2_dev; + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + struct v4l2_device *v4l2_dev = &isa->v4l2_dev; unsigned long long bitmask, f, m; - unsigned int stereo = zol->stereo; + bool stereo = isa->stereo; int i; if (freq == 0) { @@ -125,340 +122,125 @@ static int zol_setfreq(struct zoltrix *zol, unsigned long freq) bitmask = 0xc480402c10080000ull; i = 45; - mutex_lock(&zol->lock); - - zol->curfreq = freq; - - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); /* Zoltrix needs to be read to confirm */ + outb(0, isa->io); + outb(0, isa->io); + inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ - outb(0x40, zol->io); - outb(0xc0, zol->io); + outb(0x40, isa->io); + outb(0xc0, isa->io); bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31)); while (i--) { if ((bitmask & 0x8000000000000000ull) != 0) { - outb(0x80, zol->io); + outb(0x80, isa->io); udelay(50); - outb(0x00, zol->io); + outb(0x00, isa->io); udelay(50); - outb(0x80, zol->io); + outb(0x80, isa->io); udelay(50); } else { - outb(0xc0, zol->io); + outb(0xc0, isa->io); udelay(50); - outb(0x40, zol->io); + outb(0x40, isa->io); udelay(50); - outb(0xc0, zol->io); + outb(0xc0, isa->io); udelay(50); } bitmask *= 2; } /* termination sequence */ - outb(0x80, zol->io); - outb(0xc0, zol->io); - outb(0x40, zol->io); + outb(0x80, isa->io); + outb(0xc0, isa->io); + outb(0x40, isa->io); udelay(1000); - inb(zol->io + 2); - + inb(isa->io + 2); udelay(1000); - if (zol->muted) { - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); - udelay(1000); - } - - mutex_unlock(&zol->lock); - - if (!zol->muted) - zol_setvol(zol, zol->curvol); - return 0; + return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol); } /* Get signal strength */ -static int zol_getsigstr(struct zoltrix *zol) +static u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa) { + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); int a, b; - mutex_lock(&zol->lock); - outb(0x00, zol->io); /* This stuff I found to do nothing */ - outb(zol->curvol, zol->io); + outb(0x00, isa->io); /* This stuff I found to do nothing */ + outb(zol->curvol, isa->io); msleep(20); - a = inb(zol->io); + a = inb(isa->io); msleep(10); - b = inb(zol->io); + b = inb(isa->io); - mutex_unlock(&zol->lock); - - if (a != b) - return 0; - - /* I found this out by playing with a binary scanner on the card io */ - return a == 0xcf || a == 0xdf || a == 0xef; + return (a == b && a == 0xcf) ? + V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; } -static int zol_is_stereo(struct zoltrix *zol) +static u32 zoltrix_g_signal(struct radio_isa_card *isa) { - int x1, x2; - - mutex_lock(&zol->lock); + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + int a, b; - outb(0x00, zol->io); - outb(zol->curvol, zol->io); + outb(0x00, isa->io); /* This stuff I found to do nothing */ + outb(zol->curvol, isa->io); msleep(20); - x1 = inb(zol->io); + a = inb(isa->io); msleep(10); - x2 = inb(zol->io); - - mutex_unlock(&zol->lock); - - return x1 == x2 && x1 == 0xcf; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver)); - strlcpy(v->card, "Zoltrix Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct zoltrix *zol = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 88 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (zol_is_stereo(zol)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * zol_getsigstr(zol); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct zoltrix *zol = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - if (zol_setfreq(zol, f->frequency) != 0) - return -EINVAL; - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct zoltrix *zol = video_drvdata(file); + b = inb(isa->io); - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = zol->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 65535, 4096, 65535); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct zoltrix *zol = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = zol->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = zol->curvol * 4096; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct zoltrix *zol = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - zol_mute(zol); - else { - zol_unmute(zol); - zol_setvol(zol, zol->curvol); - } - return 0; - case V4L2_CID_AUDIO_VOLUME: - zol_setvol(zol, ctrl->value / 4096); + if (a != b) return 0; - } - zol->stereo = 1; - if (zol_setfreq(zol, zol->curfreq) != 0) - return -EINVAL; -#if 0 -/* FIXME: Implement stereo/mono switch on V4L2 */ - if (v->mode & VIDEO_SOUND_STEREO) { - zol->stereo = 1; - zol_setfreq(zol, zol->curfreq); - } - if (v->mode & VIDEO_SOUND_MONO) { - zol->stereo = 0; - zol_setfreq(zol, zol->curfreq); - } -#endif - return -EINVAL; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; + /* I found this out by playing with a binary scanner on the card io */ + return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0; } -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) +static int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo) { - return a->index ? -EINVAL : 0; + return zoltrix_s_frequency(isa, isa->freq); } -static const struct v4l2_file_operations zoltrix_fops = -{ - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops zoltrix_ops = { + .alloc = zoltrix_alloc, + .s_mute_volume = zoltrix_s_mute_volume, + .s_frequency = zoltrix_s_frequency, + .s_stereo = zoltrix_s_stereo, + .g_rxsubchans = zoltrix_g_rxsubchans, + .g_signal = zoltrix_g_signal, }; -static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, - .vidioc_g_frequency = vidioc_g_frequency, - .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, +static const int zoltrix_ioports[] = { 0x20c, 0x30c }; + +static struct radio_isa_driver zoltrix_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-zoltrix", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = zoltrix_ioports, + .num_of_io_ports = ARRAY_SIZE(zoltrix_ioports), + .region_size = 2, + .card = "Zoltrix Radio Plus", + .ops = &zoltrix_ops, + .has_stereo = true, + .max_volume = 15, }; static int __init zoltrix_init(void) { - struct zoltrix *zol = &zoltrix_card; - struct v4l2_device *v4l2_dev = &zol->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "zoltrix", sizeof(v4l2_dev->name)); - zol->io = io; - if (zol->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or 0x30c\n"); - return -EINVAL; - } - if (zol->io != 0x20c && zol->io != 0x30c) { - v4l2_err(v4l2_dev, "invalid port, try 0x20c or 0x30c\n"); - return -ENXIO; - } - - if (!request_region(zol->io, 2, "zoltrix")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", zol->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(zol->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - mutex_init(&zol->lock); - - /* mute card - prevents noisy bootups */ - - /* this ensures that the volume is all the way down */ - - outb(0, zol->io); - outb(0, zol->io); - msleep(20); - inb(zol->io + 3); - - zol->curvol = 0; - zol->stereo = 1; - - strlcpy(zol->vdev.name, v4l2_dev->name, sizeof(zol->vdev.name)); - zol->vdev.v4l2_dev = v4l2_dev; - zol->vdev.fops = &zoltrix_fops; - zol->vdev.ioctl_ops = &zoltrix_ioctl_ops; - zol->vdev.release = video_device_release_empty; - video_set_drvdata(&zol->vdev, zol); - - if (video_register_device(&zol->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(zol->io, 2); - return -EINVAL; - } - v4l2_info(v4l2_dev, "Zoltrix Radio Plus card driver.\n"); - - return 0; + return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX); } static void __exit zoltrix_exit(void) { - struct zoltrix *zol = &zoltrix_card; - - video_unregister_device(&zol->vdev); - v4l2_device_unregister(&zol->v4l2_dev); - release_region(zol->io, 2); + isa_unregister_driver(&zoltrix_driver.driver); } module_init(zoltrix_init); diff --git a/drivers/media/radio/saa7706h.c b/drivers/media/radio/saa7706h.c index b1193dfc5087..9474706350f8 100644 --- a/drivers/media/radio/saa7706h.c +++ b/drivers/media/radio/saa7706h.c @@ -434,18 +434,7 @@ static struct i2c_driver saa7706h_driver = { .id_table = saa7706h_id, }; -static __init int saa7706h_init(void) -{ - return i2c_add_driver(&saa7706h_driver); -} - -static __exit void saa7706h_exit(void) -{ - i2c_del_driver(&saa7706h_driver); -} - -module_init(saa7706h_init); -module_exit(saa7706h_exit); +module_i2c_driver(saa7706h_driver); MODULE_DESCRIPTION("SAA7706H Car Radio DSP driver"); MODULE_AUTHOR("Mocean Laboratories"); diff --git a/drivers/media/radio/si470x/radio-si470x-i2c.c b/drivers/media/radio/si470x/radio-si470x-i2c.c index fd3541b0e91c..9b546a5523f3 100644 --- a/drivers/media/radio/si470x/radio-si470x-i2c.c +++ b/drivers/media/radio/si470x/radio-si470x-i2c.c @@ -539,33 +539,7 @@ static struct i2c_driver si470x_i2c_driver = { .id_table = si470x_i2c_id, }; - - -/************************************************************************** - * Module Interface - **************************************************************************/ - -/* - * si470x_i2c_init - module init - */ -static int __init si470x_i2c_init(void) -{ - printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n"); - return i2c_add_driver(&si470x_i2c_driver); -} - - -/* - * si470x_i2c_exit - module exit - */ -static void __exit si470x_i2c_exit(void) -{ - i2c_del_driver(&si470x_i2c_driver); -} - - -module_init(si470x_i2c_init); -module_exit(si470x_i2c_exit); +module_i2c_driver(si470x_i2c_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713-i2c.c index 27aba936fb2b..b898c8925ab7 100644 --- a/drivers/media/radio/si4713-i2c.c +++ b/drivers/media/radio/si4713-i2c.c @@ -2106,17 +2106,4 @@ static struct i2c_driver si4713_i2c_driver = { .id_table = si4713_id, }; -/* Module Interface */ -static int __init si4713_module_init(void) -{ - return i2c_add_driver(&si4713_i2c_driver); -} - -static void __exit si4713_module_exit(void) -{ - i2c_del_driver(&si4713_i2c_driver); -} - -module_init(si4713_module_init); -module_exit(si4713_module_exit); - +module_i2c_driver(si4713_i2c_driver); diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c index 3408685b690c..6418c4c9faf1 100644 --- a/drivers/media/radio/tef6862.c +++ b/drivers/media/radio/tef6862.c @@ -215,20 +215,8 @@ static struct i2c_driver tef6862_driver = { .id_table = tef6862_id, }; -static __init int tef6862_init(void) -{ - return i2c_add_driver(&tef6862_driver); -} - -static __exit void tef6862_exit(void) -{ - i2c_del_driver(&tef6862_driver); -} - -module_init(tef6862_init); -module_exit(tef6862_exit); +module_i2c_driver(tef6862_driver); MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner"); MODULE_AUTHOR("Mocean Laboratories"); MODULE_LICENSE("GPL v2"); - diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig index 4df4affeea5f..a3fbb21350e9 100644 --- a/drivers/media/rc/Kconfig +++ b/drivers/media/rc/Kconfig @@ -266,4 +266,13 @@ config RC_LOOPBACK To compile this driver as a module, choose M here: the module will be called rc_loopback. +config IR_GPIO_CIR + tristate "GPIO IR remote control" + depends on RC_CORE + ---help--- + Say Y if you want to use GPIO based IR Receiver. + + To compile this driver as a module, choose M here: the module will + be called gpio-ir-recv. + endif #RC_CORE diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile index fb3dee2dd845..29f364f88a94 100644 --- a/drivers/media/rc/Makefile +++ b/drivers/media/rc/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_IR_REDRAT3) += redrat3.o obj-$(CONFIG_IR_STREAMZAP) += streamzap.o obj-$(CONFIG_IR_WINBOND_CIR) += winbond-cir.o obj-$(CONFIG_RC_LOOPBACK) += rc-loopback.o +obj-$(CONFIG_IR_GPIO_CIR) += gpio-ir-recv.o diff --git a/drivers/media/rc/fintek-cir.c b/drivers/media/rc/fintek-cir.c index 7f7079b12f23..392d4be91f8f 100644 --- a/drivers/media/rc/fintek-cir.c +++ b/drivers/media/rc/fintek-cir.c @@ -117,7 +117,7 @@ static u8 fintek_cir_reg_read(struct fintek_dev *fintek, u8 offset) static void cir_dump_regs(struct fintek_dev *fintek) { fintek_config_mode_enable(fintek); - fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_select_logical_dev(fintek, 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", @@ -143,7 +143,7 @@ static int fintek_hw_detect(struct fintek_dev *fintek) u8 chip_major, chip_minor; u8 vendor_major, vendor_minor; u8 portsel, ir_class; - u16 vendor; + u16 vendor, chip; int ret = 0; fintek_config_mode_enable(fintek); @@ -176,6 +176,7 @@ static int fintek_hw_detect(struct fintek_dev *fintek) chip_major = fintek_cr_read(fintek, GCR_CHIP_ID_HI); chip_minor = fintek_cr_read(fintek, GCR_CHIP_ID_LO); + chip = chip_major << 8 | chip_minor; vendor_major = fintek_cr_read(fintek, GCR_VENDOR_ID_HI); vendor_minor = fintek_cr_read(fintek, GCR_VENDOR_ID_LO); @@ -192,6 +193,15 @@ static int fintek_hw_detect(struct fintek_dev *fintek) fintek->chip_major = chip_major; fintek->chip_minor = chip_minor; fintek->chip_vendor = vendor; + + /* + * Newer reviews of this chipset uses port 8 instead of 5 + */ + if ((chip != 0x0408) || (chip != 0x0804)) + fintek->logical_dev_cir = LOGICAL_DEV_CIR_REV2; + else + fintek->logical_dev_cir = LOGICAL_DEV_CIR_REV1; + spin_unlock_irqrestore(&fintek->fintek_lock, flags); return ret; @@ -200,7 +210,7 @@ static int fintek_hw_detect(struct fintek_dev *fintek) 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_select_logical_dev(fintek, fintek->logical_dev_cir); fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN); /* Write allocated CIR address and IRQ information to hardware */ @@ -381,7 +391,7 @@ static irqreturn_t fintek_cir_isr(int irq, void *data) fit_dbg_verbose("%s firing", __func__); fintek_config_mode_enable(fintek); - fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_select_logical_dev(fintek, fintek->logical_dev_cir); fintek_config_mode_disable(fintek); /* @@ -422,7 +432,7 @@ static void fintek_enable_cir(struct fintek_dev *fintek) fintek_config_mode_enable(fintek); /* enable the CIR logical device */ - fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_select_logical_dev(fintek, fintek->logical_dev_cir); fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN); fintek_config_mode_disable(fintek); @@ -439,7 +449,7 @@ 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_select_logical_dev(fintek, fintek->logical_dev_cir); fintek_cr_write(fintek, LOGICAL_DEV_DISABLE, CIR_CR_DEV_EN); fintek_config_mode_disable(fintek); @@ -611,7 +621,7 @@ static int fintek_suspend(struct pnp_dev *pdev, pm_message_t state) fintek_config_mode_enable(fintek); /* disable cir logical dev */ - fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_select_logical_dev(fintek, fintek->logical_dev_cir); fintek_cr_write(fintek, LOGICAL_DEV_DISABLE, CIR_CR_DEV_EN); fintek_config_mode_disable(fintek); @@ -634,7 +644,7 @@ static int fintek_resume(struct pnp_dev *pdev) /* Enable CIR logical device */ fintek_config_mode_enable(fintek); - fintek_select_logical_dev(fintek, LOGICAL_DEV_CIR); + fintek_select_logical_dev(fintek, fintek->logical_dev_cir); fintek_cr_write(fintek, LOGICAL_DEV_ENABLE, CIR_CR_DEV_EN); fintek_config_mode_disable(fintek); diff --git a/drivers/media/rc/fintek-cir.h b/drivers/media/rc/fintek-cir.h index 1b10b2011f5e..82516a1d39b0 100644 --- a/drivers/media/rc/fintek-cir.h +++ b/drivers/media/rc/fintek-cir.h @@ -88,6 +88,7 @@ struct fintek_dev { u8 chip_major; u8 chip_minor; u16 chip_vendor; + u8 logical_dev_cir; /* hardware features */ bool hw_learning_capable; @@ -172,7 +173,8 @@ struct fintek_dev { #define LOGICAL_DEV_ENABLE 0x01 /* Logical device number of the CIR function */ -#define LOGICAL_DEV_CIR 0x05 +#define LOGICAL_DEV_CIR_REV1 0x05 +#define LOGICAL_DEV_CIR_REV2 0x08 /* CIR Logical Device (LDN 0x08) config registers */ #define CIR_CR_COMMAND_INDEX 0x04 diff --git a/drivers/media/rc/gpio-ir-recv.c b/drivers/media/rc/gpio-ir-recv.c new file mode 100644 index 000000000000..0d875450c5ce --- /dev/null +++ b/drivers/media/rc/gpio-ir-recv.c @@ -0,0 +1,205 @@ +/* Copyright (c) 2012, 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/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <media/rc-core.h> +#include <media/gpio-ir-recv.h> + +#define GPIO_IR_DRIVER_NAME "gpio-rc-recv" +#define GPIO_IR_DEVICE_NAME "gpio_ir_recv" + +struct gpio_rc_dev { + struct rc_dev *rcdev; + int gpio_nr; + bool active_low; +}; + +static irqreturn_t gpio_ir_recv_irq(int irq, void *dev_id) +{ + struct gpio_rc_dev *gpio_dev = dev_id; + int gval; + int rc = 0; + enum raw_event_type type = IR_SPACE; + + gval = gpio_get_value_cansleep(gpio_dev->gpio_nr); + + if (gval < 0) + goto err_get_value; + + if (gpio_dev->active_low) + gval = !gval; + + if (gval == 1) + type = IR_PULSE; + + rc = ir_raw_event_store_edge(gpio_dev->rcdev, type); + if (rc < 0) + goto err_get_value; + + ir_raw_event_handle(gpio_dev->rcdev); + +err_get_value: + return IRQ_HANDLED; +} + +static int __devinit gpio_ir_recv_probe(struct platform_device *pdev) +{ + struct gpio_rc_dev *gpio_dev; + struct rc_dev *rcdev; + const struct gpio_ir_recv_platform_data *pdata = + pdev->dev.platform_data; + int rc; + + if (!pdata) + return -EINVAL; + + if (pdata->gpio_nr < 0) + return -EINVAL; + + gpio_dev = kzalloc(sizeof(struct gpio_rc_dev), GFP_KERNEL); + if (!gpio_dev) + return -ENOMEM; + + rcdev = rc_allocate_device(); + if (!rcdev) { + rc = -ENOMEM; + goto err_allocate_device; + } + + rcdev->driver_type = RC_DRIVER_IR_RAW; + rcdev->allowed_protos = RC_TYPE_ALL; + rcdev->input_name = GPIO_IR_DEVICE_NAME; + rcdev->input_id.bustype = BUS_HOST; + rcdev->driver_name = GPIO_IR_DRIVER_NAME; + rcdev->map_name = RC_MAP_EMPTY; + + gpio_dev->rcdev = rcdev; + gpio_dev->gpio_nr = pdata->gpio_nr; + gpio_dev->active_low = pdata->active_low; + + rc = gpio_request(pdata->gpio_nr, "gpio-ir-recv"); + if (rc < 0) + goto err_gpio_request; + rc = gpio_direction_input(pdata->gpio_nr); + if (rc < 0) + goto err_gpio_direction_input; + + rc = rc_register_device(rcdev); + if (rc < 0) { + dev_err(&pdev->dev, "failed to register rc device\n"); + goto err_register_rc_device; + } + + platform_set_drvdata(pdev, gpio_dev); + + rc = request_any_context_irq(gpio_to_irq(pdata->gpio_nr), + gpio_ir_recv_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "gpio-ir-recv-irq", gpio_dev); + if (rc < 0) + goto err_request_irq; + + return 0; + +err_request_irq: + platform_set_drvdata(pdev, NULL); + rc_unregister_device(rcdev); +err_register_rc_device: +err_gpio_direction_input: + gpio_free(pdata->gpio_nr); +err_gpio_request: + rc_free_device(rcdev); + rcdev = NULL; +err_allocate_device: + kfree(gpio_dev); + return rc; +} + +static int __devexit gpio_ir_recv_remove(struct platform_device *pdev) +{ + struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev); + + free_irq(gpio_to_irq(gpio_dev->gpio_nr), gpio_dev); + platform_set_drvdata(pdev, NULL); + rc_unregister_device(gpio_dev->rcdev); + gpio_free(gpio_dev->gpio_nr); + rc_free_device(gpio_dev->rcdev); + kfree(gpio_dev); + return 0; +} + +#ifdef CONFIG_PM +static int gpio_ir_recv_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev); + + if (device_may_wakeup(dev)) + enable_irq_wake(gpio_to_irq(gpio_dev->gpio_nr)); + else + disable_irq(gpio_to_irq(gpio_dev->gpio_nr)); + + return 0; +} + +static int gpio_ir_recv_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev); + + if (device_may_wakeup(dev)) + disable_irq_wake(gpio_to_irq(gpio_dev->gpio_nr)); + else + enable_irq(gpio_to_irq(gpio_dev->gpio_nr)); + + return 0; +} + +static const struct dev_pm_ops gpio_ir_recv_pm_ops = { + .suspend = gpio_ir_recv_suspend, + .resume = gpio_ir_recv_resume, +}; +#endif + +static struct platform_driver gpio_ir_recv_driver = { + .probe = gpio_ir_recv_probe, + .remove = __devexit_p(gpio_ir_recv_remove), + .driver = { + .name = GPIO_IR_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &gpio_ir_recv_pm_ops, +#endif + }, +}; + +static int __init gpio_ir_recv_init(void) +{ + return platform_driver_register(&gpio_ir_recv_driver); +} +module_init(gpio_ir_recv_init); + +static void __exit gpio_ir_recv_exit(void) +{ + platform_driver_unregister(&gpio_ir_recv_driver); +} +module_exit(gpio_ir_recv_exit); + +MODULE_DESCRIPTION("GPIO IR Receiver driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/rc/ir-sony-decoder.c b/drivers/media/rc/ir-sony-decoder.c index d5e2b50aff1f..dab98b37621a 100644 --- a/drivers/media/rc/ir-sony-decoder.c +++ b/drivers/media/rc/ir-sony-decoder.c @@ -130,7 +130,7 @@ static int ir_sony_decode(struct rc_dev *dev, struct ir_raw_event ev) case 15: device = bitrev8((data->bits >> 0) & 0xFF); subdevice = 0; - function = bitrev8((data->bits >> 7) & 0xFD); + function = bitrev8((data->bits >> 7) & 0xFE); break; case 20: device = bitrev8((data->bits >> 5) & 0xF8); diff --git a/drivers/media/rc/keymaps/Makefile b/drivers/media/rc/keymaps/Makefile index 36e4d5e8dd6a..49ce2662f56b 100644 --- a/drivers/media/rc/keymaps/Makefile +++ b/drivers/media/rc/keymaps/Makefile @@ -41,8 +41,11 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-imon-mce.o \ rc-imon-pad.o \ rc-iodata-bctv7e.o \ + rc-it913x-v1.o \ + rc-it913x-v2.o \ rc-kaiomy.o \ rc-kworld-315u.o \ + rc-kworld-pc150u.o \ rc-kworld-plus-tv-analog.o \ rc-leadtek-y04g0051.o \ rc-lirc.o \ diff --git a/drivers/media/rc/keymaps/rc-it913x-v1.c b/drivers/media/rc/keymaps/rc-it913x-v1.c new file mode 100644 index 000000000000..0ac775fd109d --- /dev/null +++ b/drivers/media/rc/keymaps/rc-it913x-v1.c @@ -0,0 +1,95 @@ +/* ITE Generic remotes Version 1 + * + * Copyright (C) 2012 Malcolm Priestley (tvboxspy@gmail.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 <media/rc-map.h> +#include <linux/module.h> + + +static struct rc_map_table it913x_v1_rc[] = { + /* Type 1 */ + { 0x61d601, KEY_VIDEO }, /* Source */ + { 0x61d602, KEY_3 }, + { 0x61d603, KEY_POWER }, /* ShutDown */ + { 0x61d604, KEY_1 }, + { 0x61d605, KEY_5 }, + { 0x61d606, KEY_6 }, + { 0x61d607, KEY_CHANNELDOWN }, /* CH- */ + { 0x61d608, KEY_2 }, + { 0x61d609, KEY_CHANNELUP }, /* CH+ */ + { 0x61d60a, KEY_9 }, + { 0x61d60b, KEY_ZOOM }, /* Zoom */ + { 0x61d60c, KEY_7 }, + { 0x61d60d, KEY_8 }, + { 0x61d60e, KEY_VOLUMEUP }, /* Vol+ */ + { 0x61d60f, KEY_4 }, + { 0x61d610, KEY_ESC }, /* [back up arrow] */ + { 0x61d611, KEY_0 }, + { 0x61d612, KEY_OK }, /* [enter arrow] */ + { 0x61d613, KEY_VOLUMEDOWN }, /* Vol- */ + { 0x61d614, KEY_RECORD }, /* Rec */ + { 0x61d615, KEY_STOP }, /* Stop */ + { 0x61d616, KEY_PLAY }, /* Play */ + { 0x61d617, KEY_MUTE }, /* Mute */ + { 0x61d618, KEY_UP }, + { 0x61d619, KEY_DOWN }, + { 0x61d61a, KEY_LEFT }, + { 0x61d61b, KEY_RIGHT }, + { 0x61d61c, KEY_RED }, + { 0x61d61d, KEY_GREEN }, + { 0x61d61e, KEY_YELLOW }, + { 0x61d61f, KEY_BLUE }, + { 0x61d643, KEY_POWER2 }, /* [red power button] */ + /* Type 2 - 20 buttons */ + { 0x807f0d, KEY_0 }, + { 0x807f04, KEY_1 }, + { 0x807f05, KEY_2 }, + { 0x807f06, KEY_3 }, + { 0x807f07, KEY_4 }, + { 0x807f08, KEY_5 }, + { 0x807f09, KEY_6 }, + { 0x807f0a, KEY_7 }, + { 0x807f1b, KEY_8 }, + { 0x807f1f, KEY_9 }, + { 0x807f12, KEY_POWER }, + { 0x807f01, KEY_MEDIA_REPEAT}, /* Recall */ + { 0x807f19, KEY_PAUSE }, /* Timeshift */ + { 0x807f1e, KEY_VOLUMEUP }, /* 2 x -/+ Keys not marked */ + { 0x807f03, KEY_VOLUMEDOWN }, /* Volume defined as right hand*/ + { 0x807f1a, KEY_CHANNELUP }, + { 0x807f02, KEY_CHANNELDOWN }, + { 0x807f0c, KEY_ZOOM }, + { 0x807f00, KEY_RECORD }, + { 0x807f0e, KEY_STOP }, +}; + +static struct rc_map_list it913x_v1_map = { + .map = { + .scan = it913x_v1_rc, + .size = ARRAY_SIZE(it913x_v1_rc), + .rc_type = RC_TYPE_NEC, + .name = RC_MAP_IT913X_V1, + } +}; + +static int __init init_rc_it913x_v1_map(void) +{ + return rc_map_register(&it913x_v1_map); +} + +static void __exit exit_rc_it913x_v1_map(void) +{ + rc_map_unregister(&it913x_v1_map); +} + +module_init(init_rc_it913x_v1_map) +module_exit(exit_rc_it913x_v1_map) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); diff --git a/drivers/media/rc/keymaps/rc-it913x-v2.c b/drivers/media/rc/keymaps/rc-it913x-v2.c new file mode 100644 index 000000000000..28e376e18b99 --- /dev/null +++ b/drivers/media/rc/keymaps/rc-it913x-v2.c @@ -0,0 +1,94 @@ +/* ITE Generic remotes Version 2 + * + * Copyright (C) 2012 Malcolm Priestley (tvboxspy@gmail.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 <media/rc-map.h> +#include <linux/module.h> + + +static struct rc_map_table it913x_v2_rc[] = { + /* Type 1 */ + /* 9005 remote */ + { 0x807f12, KEY_POWER2 }, /* Power (RED POWER BUTTON)*/ + { 0x807f1a, KEY_VIDEO }, /* Source */ + { 0x807f1e, KEY_MUTE }, /* Mute */ + { 0x807f01, KEY_RECORD }, /* Record */ + { 0x807f02, KEY_CHANNELUP }, /* Channel+ */ + { 0x807f03, KEY_TIME }, /* TimeShift */ + { 0x807f04, KEY_VOLUMEUP }, /* Volume- */ + { 0x807f05, KEY_SCREEN }, /* FullScreen */ + { 0x807f06, KEY_VOLUMEDOWN }, /* Volume- */ + { 0x807f07, KEY_0 }, /* 0 */ + { 0x807f08, KEY_CHANNELDOWN }, /* Channel- */ + { 0x807f09, KEY_PREVIOUS }, /* Recall */ + { 0x807f0a, KEY_1 }, /* 1 */ + { 0x807f1b, KEY_2 }, /* 2 */ + { 0x807f1f, KEY_3 }, /* 3 */ + { 0x807f0c, KEY_4 }, /* 4 */ + { 0x807f0d, KEY_5 }, /* 5 */ + { 0x807f0e, KEY_6 }, /* 6 */ + { 0x807f00, KEY_7 }, /* 7 */ + { 0x807f0f, KEY_8 }, /* 8 */ + { 0x807f19, KEY_9 }, /* 9 */ + + /* Type 2 */ + /* keys stereo, snapshot unassigned */ + { 0x866b00, KEY_0 }, + { 0x866b1b, KEY_1 }, + { 0x866b02, KEY_2 }, + { 0x866b03, KEY_3 }, + { 0x866b04, KEY_4 }, + { 0x866b05, KEY_5 }, + { 0x866b06, KEY_6 }, + { 0x866b07, KEY_7 }, + { 0x866b08, KEY_8 }, + { 0x866b09, KEY_9 }, + { 0x866b12, KEY_POWER }, + { 0x866b13, KEY_MUTE }, + { 0x866b0a, KEY_PREVIOUS }, /* Recall */ + { 0x866b1e, KEY_PAUSE }, + { 0x866b0c, KEY_VOLUMEUP }, + { 0x866b18, KEY_VOLUMEDOWN }, + { 0x866b0b, KEY_CHANNELUP }, + { 0x866b18, KEY_CHANNELDOWN }, + { 0x866b10, KEY_ZOOM }, + { 0x866b1d, KEY_RECORD }, + { 0x866b0e, KEY_STOP }, + { 0x866b11, KEY_EPG}, + { 0x866b1a, KEY_FASTFORWARD }, + { 0x866b0f, KEY_REWIND }, + { 0x866b1c, KEY_TV }, + { 0x866b1b, KEY_TEXT }, + +}; + +static struct rc_map_list it913x_v2_map = { + .map = { + .scan = it913x_v2_rc, + .size = ARRAY_SIZE(it913x_v2_rc), + .rc_type = RC_TYPE_NEC, + .name = RC_MAP_IT913X_V2, + } +}; + +static int __init init_rc_it913x_v2_map(void) +{ + return rc_map_register(&it913x_v2_map); +} + +static void __exit exit_rc_it913x_v2_map(void) +{ + rc_map_unregister(&it913x_v2_map); +} + +module_init(init_rc_it913x_v2_map) +module_exit(exit_rc_it913x_v2_map) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Malcolm Priestley tvboxspy@gmail.com"); diff --git a/drivers/media/rc/keymaps/rc-kworld-pc150u.c b/drivers/media/rc/keymaps/rc-kworld-pc150u.c new file mode 100644 index 000000000000..233bb5ee087f --- /dev/null +++ b/drivers/media/rc/keymaps/rc-kworld-pc150u.c @@ -0,0 +1,102 @@ +/* kworld-pc150u.c - Keytable for kworld_pc150u Remote Controller + * + * keymap imported from ir-keymaps.c + * + * Copyright (c) 2010 by Kyle Strickland + * (based on kworld-plus-tv-analog.c by + * Mauro Carvalho Chehab <mchehab@redhat.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 <media/rc-map.h> +#include <linux/module.h> + +/* Kworld PC150-U + Kyle Strickland <kyle@kyle.strickland.name> + */ + +static struct rc_map_table kworld_pc150u[] = { + { 0x0c, KEY_MEDIA }, /* Kworld key */ + { 0x16, KEY_EJECTCLOSECD }, /* -> ) */ + { 0x1d, KEY_POWER2 }, + + { 0x00, KEY_1 }, + { 0x01, KEY_2 }, + { 0x02, KEY_3 }, + { 0x03, KEY_4 }, + { 0x04, KEY_5 }, + { 0x05, KEY_6 }, + { 0x06, KEY_7 }, + { 0x07, KEY_8 }, + { 0x08, KEY_9 }, + { 0x0a, KEY_0 }, + + { 0x09, KEY_AGAIN }, + { 0x14, KEY_MUTE }, + + { 0x1e, KEY_LAST }, + { 0x17, KEY_ZOOM }, + { 0x1f, KEY_HOMEPAGE }, + { 0x0e, KEY_ESC }, + + { 0x20, KEY_UP }, + { 0x21, KEY_DOWN }, + { 0x42, KEY_LEFT }, + { 0x43, KEY_RIGHT }, + { 0x0b, KEY_ENTER }, + + { 0x10, KEY_CHANNELUP }, + { 0x11, KEY_CHANNELDOWN }, + + { 0x13, KEY_VOLUMEUP }, + { 0x12, KEY_VOLUMEDOWN }, + + { 0x19, KEY_TIME}, /* Timeshift */ + { 0x1a, KEY_STOP}, + { 0x1b, KEY_RECORD}, + { 0x4b, KEY_EMAIL}, + + { 0x40, KEY_REWIND}, + { 0x44, KEY_PLAYPAUSE}, + { 0x41, KEY_FORWARD}, + { 0x22, KEY_TEXT}, + + { 0x15, KEY_AUDIO}, /* ((*)) */ + { 0x0f, KEY_MODE}, /* display ratio */ + { 0x1c, KEY_SYSRQ}, /* snapshot */ + { 0x4a, KEY_SLEEP}, /* sleep timer */ + + { 0x48, KEY_SOUND}, /* switch theater mode */ + { 0x49, KEY_BLUE}, /* A */ + { 0x18, KEY_RED}, /* B */ + { 0x23, KEY_GREEN}, /* C */ +}; + +static struct rc_map_list kworld_pc150u_map = { + .map = { + .scan = kworld_pc150u, + .size = ARRAY_SIZE(kworld_pc150u), + .rc_type = RC_TYPE_UNKNOWN, /* Legacy IR type */ + .name = RC_MAP_KWORLD_PC150U, + } +}; + +static int __init init_rc_map_kworld_pc150u(void) +{ + return rc_map_register(&kworld_pc150u_map); +} + +static void __exit exit_rc_map_kworld_pc150u(void) +{ + rc_map_unregister(&kworld_pc150u_map); +} + +module_init(init_rc_map_kworld_pc150u) +module_exit(exit_rc_map_kworld_pc150u) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyle Strickland <kyle@kyle.strickland.name>"); diff --git a/drivers/media/rc/keymaps/rc-nec-terratec-cinergy-xs.c b/drivers/media/rc/keymaps/rc-nec-terratec-cinergy-xs.c index f3b86c8db679..8d4dae2e2ece 100644 --- a/drivers/media/rc/keymaps/rc-nec-terratec-cinergy-xs.c +++ b/drivers/media/rc/keymaps/rc-nec-terratec-cinergy-xs.c @@ -18,6 +18,8 @@ */ static struct rc_map_table nec_terratec_cinergy_xs[] = { + + /* Terratec Grey IR, with most keys in orange */ { 0x1441, KEY_HOME}, { 0x1401, KEY_POWER2}, @@ -78,6 +80,56 @@ static struct rc_map_table nec_terratec_cinergy_xs[] = { { 0x144e, KEY_REWIND}, { 0x144f, KEY_FASTFORWARD}, { 0x145c, KEY_NEXT}, + + /* Terratec Black IR, with most keys in black */ + { 0x04eb01, KEY_POWER2}, + + { 0x04eb02, KEY_1}, + { 0x04eb03, KEY_2}, + { 0x04eb04, KEY_3}, + { 0x04eb05, KEY_4}, + { 0x04eb06, KEY_5}, + { 0x04eb07, KEY_6}, + { 0x04eb08, KEY_7}, + { 0x04eb09, KEY_8}, + { 0x04eb0a, KEY_9}, + { 0x04eb0c, KEY_0}, + + { 0x04eb0b, KEY_TEXT}, /* TXT */ + { 0x04eb0d, KEY_REFRESH}, /* Refresh */ + + { 0x04eb0e, KEY_HOME}, + { 0x04eb0f, KEY_EPG}, + + { 0x04eb10, KEY_UP}, + { 0x04eb11, KEY_LEFT}, + { 0x04eb12, KEY_OK}, + { 0x04eb13, KEY_RIGHT}, + { 0x04eb14, KEY_DOWN}, + + { 0x04eb15, KEY_BACKSPACE}, + { 0x04eb16, KEY_INFO}, + + { 0x04eb17, KEY_RED}, + { 0x04eb18, KEY_GREEN}, + { 0x04eb19, KEY_YELLOW}, + { 0x04eb1a, KEY_BLUE}, + + { 0x04eb1c, KEY_VOLUMEUP}, + { 0x04eb1e, KEY_VOLUMEDOWN}, + + { 0x04eb1d, KEY_MUTE}, + + { 0x04eb1b, KEY_CHANNELUP}, + { 0x04eb1f, KEY_CHANNELDOWN}, + + { 0x04eb40, KEY_RECORD}, + { 0x04eb4c, KEY_PLAY}, + { 0x04eb58, KEY_PAUSE}, + + { 0x04eb54, KEY_REWIND}, + { 0x04eb48, KEY_STOP}, + { 0x04eb5c, KEY_NEXT}, }; static struct rc_map_list nec_terratec_cinergy_xs_map = { diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c index 21105bf9594d..e150a2e29a4b 100644 --- a/drivers/media/rc/mceusb.c +++ b/drivers/media/rc/mceusb.c @@ -361,6 +361,8 @@ static struct usb_device_id mceusb_dev_table[] = { { USB_DEVICE(VENDOR_FORMOSA, 0xe03c) }, /* Formosa Industrial Computing */ { USB_DEVICE(VENDOR_FORMOSA, 0xe03e) }, + /* Formosa Industrial Computing */ + { USB_DEVICE(VENDOR_FORMOSA, 0xe042) }, /* Fintek eHome Infrared Transceiver (HP branded) */ { USB_DEVICE(VENDOR_FINTEK, 0x5168) }, /* Fintek eHome Infrared Transceiver */ diff --git a/drivers/media/rc/rc-core-priv.h b/drivers/media/rc/rc-core-priv.h index b72f8580e317..96f0a8bb39ea 100644 --- a/drivers/media/rc/rc-core-priv.h +++ b/drivers/media/rc/rc-core-priv.h @@ -35,7 +35,7 @@ struct ir_raw_event_ctrl { struct list_head list; /* to keep track of raw clients */ struct task_struct *thread; spinlock_t lock; - struct kfifo kfifo; /* fifo for the pulse/space durations */ + struct kfifo_rec_ptr_1 kfifo; /* fifo for the pulse/space durations */ ktime_t last_event; /* when last event occurred */ enum raw_event_type last_type; /* last event type */ struct rc_dev *dev; /* pointer to the parent rc_dev */ diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index f6a930b70c69..6e16b09c24a9 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -1029,6 +1029,7 @@ EXPORT_SYMBOL_GPL(rc_free_device); int rc_register_device(struct rc_dev *dev) { + static bool raw_init = false; /* raw decoders loaded? */ static atomic_t devno = ATOMIC_INIT(0); struct rc_map *rc_map; const char *path; @@ -1103,6 +1104,12 @@ int rc_register_device(struct rc_dev *dev) kfree(path); if (dev->driver_type == RC_DRIVER_IR_RAW) { + /* Load raw decoders, if they aren't already */ + if (!raw_init) { + IR_dprintk(1, "Loading raw decoders\n"); + ir_raw_init(); + raw_init = true; + } rc = ir_raw_event_register(dev); if (rc < 0) goto out_input; @@ -1176,8 +1183,6 @@ static int __init rc_core_init(void) return rc; } - /* Initialize/load the decoders/keymap code that will be used */ - ir_raw_init(); rc_map_register(&empty_map); return 0; diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 9adada0d7447..f2479c5c0eb2 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -273,6 +273,16 @@ config VIDEO_ADV7180 To compile this driver as a module, choose M here: the module will be called adv7180. +config VIDEO_ADV7183 + tristate "Analog Devices ADV7183 decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + V4l2 subdevice driver for the Analog Devices + ADV7183 video decoder. + + To compile this driver as a module, choose M here: the + module will be called adv7183. + config VIDEO_BT819 tristate "BT819A VideoStream decoder" depends on VIDEO_V4L2 && I2C @@ -459,6 +469,9 @@ config VIDEO_AK881X comment "Camera sensor devices" +config VIDEO_APTINA_PLL + tristate + config VIDEO_OV7670 tristate "OmniVision OV7670 sensor support" depends on I2C && VIDEO_V4L2 @@ -467,9 +480,28 @@ config VIDEO_OV7670 OV7670 VGA camera. It currently only works with the M88ALP01 controller. +config VIDEO_VS6624 + tristate "ST VS6624 sensor support" + depends on VIDEO_V4L2 && I2C + ---help--- + This is a Video4Linux2 sensor-level driver for the ST VS6624 + camera. + + To compile this driver as a module, choose M here: the + module will be called vs6624. + +config VIDEO_MT9M032 + tristate "MT9M032 camera sensor support" + depends on I2C && VIDEO_V4L2 + select VIDEO_APTINA_PLL + ---help--- + This driver supports MT9M032 camera sensors from Aptina, monochrome + models only. + config VIDEO_MT9P031 tristate "Aptina MT9P031 support" depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + select VIDEO_APTINA_PLL ---help--- This is a Video4Linux2 sensor-level driver for the Aptina (Micron) mt9p031 5 Mpixel camera. @@ -851,6 +883,8 @@ source "drivers/media/video/davinci/Kconfig" source "drivers/media/video/omap/Kconfig" +source "drivers/media/video/blackfin/Kconfig" + config VIDEO_SH_VOU tristate "SuperH VOU video output driver" depends on VIDEO_DEV && ARCH_SHMOBILE @@ -1087,7 +1121,7 @@ config VIDEO_MX2_HOSTSUPPORT config VIDEO_MX2 tristate "i.MX27/i.MX25 Camera Sensor Interface driver" depends on VIDEO_DEV && SOC_CAMERA && (MACH_MX27 || ARCH_MX25) - select VIDEOBUF_DMA_CONTIG + select VIDEOBUF2_DMA_CONTIG select VIDEO_MX2_HOSTSUPPORT ---help--- This is a v4l2 driver for the i.MX27 and the i.MX25 Camera Sensor @@ -1116,7 +1150,8 @@ config VIDEO_ATMEL_ISI config VIDEO_S5P_MIPI_CSIS tristate "Samsung S5P and EXYNOS4 MIPI CSI receiver driver" - depends on VIDEO_V4L2 && PM_RUNTIME && PLAT_S5P && VIDEO_V4L2_SUBDEV_API + depends on VIDEO_V4L2 && PM_RUNTIME && PLAT_S5P + depends on VIDEO_V4L2_SUBDEV_API && REGULATOR ---help--- This is a v4l2 driver for Samsung S5P/EXYNOS4 MIPI-CSI receiver. @@ -1176,4 +1211,14 @@ config VIDEO_SAMSUNG_S5P_MFC help MFC 5.1 driver for V4L2. +config VIDEO_MX2_EMMAPRP + tristate "MX2 eMMa-PrP support" + depends on VIDEO_DEV && VIDEO_V4L2 && SOC_IMX27 + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + MX2X chips have a PrP that can be used to process buffers from + memory to memory. Operations include resizing and format + conversion. + endif # V4L_MEM2MEM_DRIVERS diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 354138804cda..a6282a3a6a82 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -12,16 +12,19 @@ omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ v4l2-event.o v4l2-ctrls.o v4l2-subdev.o +ifeq ($(CONFIG_COMPAT),y) + videodev-objs += v4l2-compat-ioctl32.o +endif # V4L2 core modules obj-$(CONFIG_VIDEO_DEV) += videodev.o v4l2-int-device.o -ifeq ($(CONFIG_COMPAT),y) - obj-$(CONFIG_VIDEO_DEV) += v4l2-compat-ioctl32.o -endif - obj-$(CONFIG_VIDEO_V4L2_COMMON) += v4l2-common.o +# Helper modules + +obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o + # All i2c modules must come first: obj-$(CONFIG_VIDEO_TUNER) += tuner.o @@ -40,8 +43,10 @@ obj-$(CONFIG_VIDEO_SAA7191) += saa7191.o obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o +obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o +obj-$(CONFIG_VIDEO_VS6624) += vs6624.o obj-$(CONFIG_VIDEO_BT819) += bt819.o obj-$(CONFIG_VIDEO_BT856) += bt856.o obj-$(CONFIG_VIDEO_BT866) += bt866.o @@ -65,6 +70,7 @@ obj-$(CONFIG_VIDEO_UPD64083) += upd64083.o obj-$(CONFIG_VIDEO_OV7670) += ov7670.o obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o +obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o obj-$(CONFIG_VIDEO_MT9P031) += mt9p031.o obj-$(CONFIG_VIDEO_MT9T001) += mt9t001.o obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o @@ -177,6 +183,8 @@ obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o +obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o + obj-$(CONFIG_VIDEO_SAMSUNG_S5P_FIMC) += s5p-fimc/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_MFC) += s5p-mfc/ @@ -184,6 +192,8 @@ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_TV) += s5p-tv/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_G2D) += s5p-g2d/ +obj-$(CONFIG_BLACKFIN) += blackfin/ + obj-$(CONFIG_ARCH_DAVINCI) += davinci/ obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o @@ -199,6 +209,6 @@ obj-y += davinci/ obj-$(CONFIG_ARCH_OMAP) += omap/ -ccflags-y += -Idrivers/media/dvb/dvb-core -ccflags-y += -Idrivers/media/dvb/frontends -ccflags-y += -Idrivers/media/common/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb/frontends +ccflags-y += -I$(srctree)/drivers/media/common/tuners diff --git a/drivers/media/video/adp1653.c b/drivers/media/video/adp1653.c index 12eedf4d515a..5b045b4a66fe 100644 --- a/drivers/media/video/adp1653.c +++ b/drivers/media/video/adp1653.c @@ -33,7 +33,6 @@ #include <linux/delay.h> #include <linux/module.h> #include <linux/i2c.h> -#include <linux/module.h> #include <linux/slab.h> #include <linux/version.h> #include <media/adp1653.h> @@ -482,24 +481,7 @@ static struct i2c_driver adp1653_i2c_driver = { .id_table = adp1653_id_table, }; -static int __init adp1653_init(void) -{ - int rval; - - rval = i2c_add_driver(&adp1653_i2c_driver); - if (rval) - printk(KERN_ALERT "%s: failed at i2c_add_driver\n", __func__); - - return rval; -} - -static void __exit adp1653_exit(void) -{ - i2c_del_driver(&adp1653_i2c_driver); -} - -module_init(adp1653_init); -module_exit(adp1653_exit); +module_i2c_driver(adp1653_i2c_driver); MODULE_AUTHOR("Sakari Ailus <sakari.ailus@nokia.com>"); MODULE_DESCRIPTION("Analog Devices ADP1653 LED flash driver"); diff --git a/drivers/media/video/adv7170.c b/drivers/media/video/adv7170.c index 879f1d839760..6bc01fb98ff8 100644 --- a/drivers/media/video/adv7170.c +++ b/drivers/media/video/adv7170.c @@ -407,15 +407,4 @@ static struct i2c_driver adv7170_driver = { .id_table = adv7170_id, }; -static __init int init_adv7170(void) -{ - return i2c_add_driver(&adv7170_driver); -} - -static __exit void exit_adv7170(void) -{ - i2c_del_driver(&adv7170_driver); -} - -module_init(init_adv7170); -module_exit(exit_adv7170); +module_i2c_driver(adv7170_driver); diff --git a/drivers/media/video/adv7175.c b/drivers/media/video/adv7175.c index 206078eca853..c7640fab5730 100644 --- a/drivers/media/video/adv7175.c +++ b/drivers/media/video/adv7175.c @@ -457,15 +457,4 @@ static struct i2c_driver adv7175_driver = { .id_table = adv7175_id, }; -static __init int init_adv7175(void) -{ - return i2c_add_driver(&adv7175_driver); -} - -static __exit void exit_adv7175(void) -{ - i2c_del_driver(&adv7175_driver); -} - -module_init(init_adv7175); -module_exit(exit_adv7175); +module_i2c_driver(adv7175_driver); diff --git a/drivers/media/video/adv7180.c b/drivers/media/video/adv7180.c index d2138d06bcad..b8b6c4b0cad4 100644 --- a/drivers/media/video/adv7180.c +++ b/drivers/media/video/adv7180.c @@ -444,20 +444,8 @@ static struct i2c_driver adv7180_driver = { .id_table = adv7180_id, }; -static __init int adv7180_init(void) -{ - return i2c_add_driver(&adv7180_driver); -} - -static __exit void adv7180_exit(void) -{ - i2c_del_driver(&adv7180_driver); -} - -module_init(adv7180_init); -module_exit(adv7180_exit); +module_i2c_driver(adv7180_driver); MODULE_DESCRIPTION("Analog Devices ADV7180 video decoder driver"); MODULE_AUTHOR("Mocean Laboratories"); MODULE_LICENSE("GPL v2"); - diff --git a/drivers/media/video/adv7183.c b/drivers/media/video/adv7183.c new file mode 100644 index 000000000000..e1d4c89d7140 --- /dev/null +++ b/drivers/media/video/adv7183.c @@ -0,0 +1,699 @@ +/* + * adv7183.c Analog Devices ADV7183 video decoder driver + * + * Copyright (c) 2011 Analog Devices Inc. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/adv7183.h> +#include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#include "adv7183_regs.h" + +struct adv7183 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + + v4l2_std_id std; /* Current set standard */ + u32 input; + u32 output; + unsigned reset_pin; + unsigned oe_pin; + struct v4l2_mbus_framefmt fmt; +}; + +/* EXAMPLES USING 27 MHz CLOCK + * Mode 1 CVBS Input (Composite Video on AIN5) + * All standards are supported through autodetect, 8-bit, 4:2:2, ITU-R BT.656 output on P15 to P8. + */ +static const unsigned char adv7183_init_regs[] = { + ADV7183_IN_CTRL, 0x04, /* CVBS input on AIN5 */ + ADV7183_DIGI_CLAMP_CTRL_1, 0x00, /* Slow down digital clamps */ + ADV7183_SHAP_FILT_CTRL, 0x41, /* Set CSFM to SH1 */ + ADV7183_ADC_CTRL, 0x16, /* Power down ADC 1 and ADC 2 */ + ADV7183_CTI_DNR_CTRL_4, 0x04, /* Set DNR threshold to 4 for flat response */ + /* ADI recommended programming sequence */ + ADV7183_ADI_CTRL, 0x80, + ADV7183_CTI_DNR_CTRL_4, 0x20, + 0x52, 0x18, + 0x58, 0xED, + 0x77, 0xC5, + 0x7C, 0x93, + 0x7D, 0x00, + 0xD0, 0x48, + 0xD5, 0xA0, + 0xD7, 0xEA, + ADV7183_SD_SATURATION_CR, 0x3E, + ADV7183_PAL_V_END, 0x3E, + ADV7183_PAL_F_TOGGLE, 0x0F, + ADV7183_ADI_CTRL, 0x00, +}; + +static inline struct adv7183 *to_adv7183(struct v4l2_subdev *sd) +{ + return container_of(sd, struct adv7183, sd); +} +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7183, hdl)->sd; +} + +static inline int adv7183_read(struct v4l2_subdev *sd, unsigned char reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_read_byte_data(client, reg); +} + +static inline int adv7183_write(struct v4l2_subdev *sd, unsigned char reg, + unsigned char value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int adv7183_writeregs(struct v4l2_subdev *sd, + const unsigned char *regs, unsigned int num) +{ + unsigned char reg, data; + unsigned int cnt = 0; + + if (num & 0x1) { + v4l2_err(sd, "invalid regs array\n"); + return -1; + } + + while (cnt < num) { + reg = *regs++; + data = *regs++; + cnt += 2; + + adv7183_write(sd, reg, data); + } + return 0; +} + +static int adv7183_log_status(struct v4l2_subdev *sd) +{ + struct adv7183 *decoder = to_adv7183(sd); + + v4l2_info(sd, "adv7183: Input control = 0x%02x\n", + adv7183_read(sd, ADV7183_IN_CTRL)); + v4l2_info(sd, "adv7183: Video selection = 0x%02x\n", + adv7183_read(sd, ADV7183_VD_SEL)); + v4l2_info(sd, "adv7183: Output control = 0x%02x\n", + adv7183_read(sd, ADV7183_OUT_CTRL)); + v4l2_info(sd, "adv7183: Extended output control = 0x%02x\n", + adv7183_read(sd, ADV7183_EXT_OUT_CTRL)); + v4l2_info(sd, "adv7183: Autodetect enable = 0x%02x\n", + adv7183_read(sd, ADV7183_AUTO_DET_EN)); + v4l2_info(sd, "adv7183: Contrast = 0x%02x\n", + adv7183_read(sd, ADV7183_CONTRAST)); + v4l2_info(sd, "adv7183: Brightness = 0x%02x\n", + adv7183_read(sd, ADV7183_BRIGHTNESS)); + v4l2_info(sd, "adv7183: Hue = 0x%02x\n", + adv7183_read(sd, ADV7183_HUE)); + v4l2_info(sd, "adv7183: Default value Y = 0x%02x\n", + adv7183_read(sd, ADV7183_DEF_Y)); + v4l2_info(sd, "adv7183: Default value C = 0x%02x\n", + adv7183_read(sd, ADV7183_DEF_C)); + v4l2_info(sd, "adv7183: ADI control = 0x%02x\n", + adv7183_read(sd, ADV7183_ADI_CTRL)); + v4l2_info(sd, "adv7183: Power Management = 0x%02x\n", + adv7183_read(sd, ADV7183_POW_MANAGE)); + v4l2_info(sd, "adv7183: Status 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_STATUS_1), + adv7183_read(sd, ADV7183_STATUS_2), + adv7183_read(sd, ADV7183_STATUS_3)); + v4l2_info(sd, "adv7183: Ident = 0x%02x\n", + adv7183_read(sd, ADV7183_IDENT)); + v4l2_info(sd, "adv7183: Analog clamp control = 0x%02x\n", + adv7183_read(sd, ADV7183_ANAL_CLAMP_CTRL)); + v4l2_info(sd, "adv7183: Digital clamp control 1 = 0x%02x\n", + adv7183_read(sd, ADV7183_DIGI_CLAMP_CTRL_1)); + v4l2_info(sd, "adv7183: Shaping filter control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SHAP_FILT_CTRL), + adv7183_read(sd, ADV7183_SHAP_FILT_CTRL_2)); + v4l2_info(sd, "adv7183: Comb filter control = 0x%02x\n", + adv7183_read(sd, ADV7183_COMB_FILT_CTRL)); + v4l2_info(sd, "adv7183: ADI control 2 = 0x%02x\n", + adv7183_read(sd, ADV7183_ADI_CTRL_2)); + v4l2_info(sd, "adv7183: Pixel delay control = 0x%02x\n", + adv7183_read(sd, ADV7183_PIX_DELAY_CTRL)); + v4l2_info(sd, "adv7183: Misc gain control = 0x%02x\n", + adv7183_read(sd, ADV7183_MISC_GAIN_CTRL)); + v4l2_info(sd, "adv7183: AGC mode control = 0x%02x\n", + adv7183_read(sd, ADV7183_AGC_MODE_CTRL)); + v4l2_info(sd, "adv7183: Chroma gain control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_CHRO_GAIN_CTRL_1), + adv7183_read(sd, ADV7183_CHRO_GAIN_CTRL_2)); + v4l2_info(sd, "adv7183: Luma gain control 1 and 2 = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_LUMA_GAIN_CTRL_1), + adv7183_read(sd, ADV7183_LUMA_GAIN_CTRL_2)); + v4l2_info(sd, "adv7183: Vsync field control 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_1), + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_2), + adv7183_read(sd, ADV7183_VS_FIELD_CTRL_3)); + v4l2_info(sd, "adv7183: Hsync positon control 1 2 and 3 = 0x%02x 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_HS_POS_CTRL_1), + adv7183_read(sd, ADV7183_HS_POS_CTRL_2), + adv7183_read(sd, ADV7183_HS_POS_CTRL_3)); + v4l2_info(sd, "adv7183: Polarity = 0x%02x\n", + adv7183_read(sd, ADV7183_POLARITY)); + v4l2_info(sd, "adv7183: ADC control = 0x%02x\n", + adv7183_read(sd, ADV7183_ADC_CTRL)); + v4l2_info(sd, "adv7183: SD offset Cb and Cr = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SD_OFFSET_CB), + adv7183_read(sd, ADV7183_SD_OFFSET_CR)); + v4l2_info(sd, "adv7183: SD saturation Cb and Cr = 0x%02x 0x%02x\n", + adv7183_read(sd, ADV7183_SD_SATURATION_CB), + adv7183_read(sd, ADV7183_SD_SATURATION_CR)); + v4l2_info(sd, "adv7183: Drive strength = 0x%02x\n", + adv7183_read(sd, ADV7183_DRIVE_STR)); + v4l2_ctrl_handler_log_status(&decoder->hdl, sd->name); + return 0; +} + +static int adv7183_g_std(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7183 *decoder = to_adv7183(sd); + + *std = decoder->std; + return 0; +} + +static int adv7183_s_std(struct v4l2_subdev *sd, v4l2_std_id std) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF; + if (std == V4L2_STD_PAL_60) + reg |= 0x60; + else if (std == V4L2_STD_NTSC_443) + reg |= 0x70; + else if (std == V4L2_STD_PAL_N) + reg |= 0x90; + else if (std == V4L2_STD_PAL_M) + reg |= 0xA0; + else if (std == V4L2_STD_PAL_Nc) + reg |= 0xC0; + else if (std & V4L2_STD_PAL) + reg |= 0x80; + else if (std & V4L2_STD_NTSC) + reg |= 0x50; + else if (std & V4L2_STD_SECAM) + reg |= 0xE0; + else + return -EINVAL; + adv7183_write(sd, ADV7183_IN_CTRL, reg); + + decoder->std = std; + + return 0; +} + +static int adv7183_reset(struct v4l2_subdev *sd, u32 val) +{ + int reg; + + reg = adv7183_read(sd, ADV7183_POW_MANAGE) | 0x80; + adv7183_write(sd, ADV7183_POW_MANAGE, reg); + /* wait 5ms before any further i2c writes are performed */ + usleep_range(5000, 10000); + return 0; +} + +static int adv7183_s_routing(struct v4l2_subdev *sd, + u32 input, u32 output, u32 config) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + if ((input > ADV7183_COMPONENT1) || (output > ADV7183_16BIT_OUT)) + return -EINVAL; + + if (input != decoder->input) { + decoder->input = input; + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF0; + switch (input) { + case ADV7183_COMPOSITE1: + reg |= 0x1; + break; + case ADV7183_COMPOSITE2: + reg |= 0x2; + break; + case ADV7183_COMPOSITE3: + reg |= 0x3; + break; + case ADV7183_COMPOSITE4: + reg |= 0x4; + break; + case ADV7183_COMPOSITE5: + reg |= 0x5; + break; + case ADV7183_COMPOSITE6: + reg |= 0xB; + break; + case ADV7183_COMPOSITE7: + reg |= 0xC; + break; + case ADV7183_COMPOSITE8: + reg |= 0xD; + break; + case ADV7183_COMPOSITE9: + reg |= 0xE; + break; + case ADV7183_COMPOSITE10: + reg |= 0xF; + break; + case ADV7183_SVIDEO0: + reg |= 0x6; + break; + case ADV7183_SVIDEO1: + reg |= 0x7; + break; + case ADV7183_SVIDEO2: + reg |= 0x8; + break; + case ADV7183_COMPONENT0: + reg |= 0x9; + break; + case ADV7183_COMPONENT1: + reg |= 0xA; + break; + default: + break; + } + adv7183_write(sd, ADV7183_IN_CTRL, reg); + } + + if (output != decoder->output) { + decoder->output = output; + reg = adv7183_read(sd, ADV7183_OUT_CTRL) & 0xC0; + switch (output) { + case ADV7183_16BIT_OUT: + reg |= 0x9; + break; + default: + reg |= 0xC; + break; + } + adv7183_write(sd, ADV7183_OUT_CTRL, reg); + } + + return 0; +} + +static int adv7183_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + int val = ctrl->val; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (val < 0) + val = 127 - val; + adv7183_write(sd, ADV7183_BRIGHTNESS, val); + break; + case V4L2_CID_CONTRAST: + adv7183_write(sd, ADV7183_CONTRAST, val); + break; + case V4L2_CID_SATURATION: + adv7183_write(sd, ADV7183_SD_SATURATION_CB, val >> 8); + adv7183_write(sd, ADV7183_SD_SATURATION_CR, (val & 0xFF)); + break; + case V4L2_CID_HUE: + adv7183_write(sd, ADV7183_SD_OFFSET_CB, val >> 8); + adv7183_write(sd, ADV7183_SD_OFFSET_CR, (val & 0xFF)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int adv7183_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct adv7183 *decoder = to_adv7183(sd); + int reg; + + /* enable autodetection block */ + reg = adv7183_read(sd, ADV7183_IN_CTRL) & 0xF; + adv7183_write(sd, ADV7183_IN_CTRL, reg); + + /* wait autodetection switch */ + mdelay(10); + + /* get autodetection result */ + reg = adv7183_read(sd, ADV7183_STATUS_1); + switch ((reg >> 0x4) & 0x7) { + case 0: + *std = V4L2_STD_NTSC; + break; + case 1: + *std = V4L2_STD_NTSC_443; + break; + case 2: + *std = V4L2_STD_PAL_M; + break; + case 3: + *std = V4L2_STD_PAL_60; + break; + case 4: + *std = V4L2_STD_PAL; + break; + case 5: + *std = V4L2_STD_SECAM; + break; + case 6: + *std = V4L2_STD_PAL_Nc; + break; + case 7: + *std = V4L2_STD_SECAM; + break; + default: + *std = V4L2_STD_UNKNOWN; + break; + } + + /* after std detection, write back user set std */ + adv7183_s_std(sd, decoder->std); + return 0; +} + +static int adv7183_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + int reg; + + *status = V4L2_IN_ST_NO_SIGNAL; + reg = adv7183_read(sd, ADV7183_STATUS_1); + if (reg < 0) + return reg; + if (reg & 0x1) + *status = 0; + return 0; +} + +static int adv7183_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned index, + enum v4l2_mbus_pixelcode *code) +{ + if (index > 0) + return -EINVAL; + + *code = V4L2_MBUS_FMT_UYVY8_2X8; + return 0; +} + +static int adv7183_try_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct adv7183 *decoder = to_adv7183(sd); + + fmt->code = V4L2_MBUS_FMT_UYVY8_2X8; + fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; + if (decoder->std & V4L2_STD_525_60) { + fmt->field = V4L2_FIELD_SEQ_TB; + fmt->width = 720; + fmt->height = 480; + } else { + fmt->field = V4L2_FIELD_SEQ_BT; + fmt->width = 720; + fmt->height = 576; + } + return 0; +} + +static int adv7183_s_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct adv7183 *decoder = to_adv7183(sd); + + adv7183_try_mbus_fmt(sd, fmt); + decoder->fmt = *fmt; + return 0; +} + +static int adv7183_g_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct adv7183 *decoder = to_adv7183(sd); + + *fmt = decoder->fmt; + return 0; +} + +static int adv7183_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct adv7183 *decoder = to_adv7183(sd); + + if (enable) + gpio_direction_output(decoder->oe_pin, 0); + else + gpio_direction_output(decoder->oe_pin, 1); + udelay(1); + return 0; +} + +static int adv7183_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + int rev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* 0x11 for adv7183, 0x13 for adv7183b */ + rev = adv7183_read(sd, ADV7183_IDENT); + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_ADV7183, rev); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int adv7183_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + reg->val = adv7183_read(sd, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int adv7183_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + adv7183_write(sd, reg->reg & 0xff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_ctrl_ops adv7183_ctrl_ops = { + .s_ctrl = adv7183_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops adv7183_core_ops = { + .log_status = adv7183_log_status, + .g_std = adv7183_g_std, + .s_std = adv7183_s_std, + .reset = adv7183_reset, + .g_chip_ident = adv7183_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7183_g_register, + .s_register = adv7183_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops adv7183_video_ops = { + .s_routing = adv7183_s_routing, + .querystd = adv7183_querystd, + .g_input_status = adv7183_g_input_status, + .enum_mbus_fmt = adv7183_enum_mbus_fmt, + .try_mbus_fmt = adv7183_try_mbus_fmt, + .s_mbus_fmt = adv7183_s_mbus_fmt, + .g_mbus_fmt = adv7183_g_mbus_fmt, + .s_stream = adv7183_s_stream, +}; + +static const struct v4l2_subdev_ops adv7183_ops = { + .core = &adv7183_core_ops, + .video = &adv7183_video_ops, +}; + +static int adv7183_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adv7183 *decoder; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + int ret; + struct v4l2_mbus_framefmt fmt; + const unsigned *pin_array; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + pin_array = client->dev.platform_data; + if (pin_array == NULL) + return -EINVAL; + + decoder = kzalloc(sizeof(struct adv7183), GFP_KERNEL); + if (decoder == NULL) + return -ENOMEM; + + decoder->reset_pin = pin_array[0]; + decoder->oe_pin = pin_array[1]; + + if (gpio_request(decoder->reset_pin, "ADV7183 Reset")) { + v4l_err(client, "failed to request GPIO %d\n", decoder->reset_pin); + ret = -EBUSY; + goto err_free_decoder; + } + + if (gpio_request(decoder->oe_pin, "ADV7183 Output Enable")) { + v4l_err(client, "failed to request GPIO %d\n", decoder->oe_pin); + ret = -EBUSY; + goto err_free_reset; + } + + sd = &decoder->sd; + v4l2_i2c_subdev_init(sd, client, &adv7183_ops); + + hdl = &decoder->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_CONTRAST, 0, 0xFF, 1, 0x80); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xFFFF, 1, 0x8080); + v4l2_ctrl_new_std(hdl, &adv7183_ctrl_ops, + V4L2_CID_HUE, 0, 0xFFFF, 1, 0x8080); + /* hook the control handler into the driver */ + sd->ctrl_handler = hdl; + if (hdl->error) { + ret = hdl->error; + + v4l2_ctrl_handler_free(hdl); + goto err_free_oe; + } + + /* v4l2 doesn't support an autodetect standard, pick PAL as default */ + decoder->std = V4L2_STD_PAL; + decoder->input = ADV7183_COMPOSITE4; + decoder->output = ADV7183_8BIT_OUT; + + gpio_direction_output(decoder->oe_pin, 1); + /* reset chip */ + gpio_direction_output(decoder->reset_pin, 0); + /* reset pulse width at least 5ms */ + mdelay(10); + gpio_direction_output(decoder->reset_pin, 1); + /* wait 5ms before any further i2c writes are performed */ + mdelay(5); + + adv7183_writeregs(sd, adv7183_init_regs, ARRAY_SIZE(adv7183_init_regs)); + adv7183_s_std(sd, decoder->std); + fmt.width = 720; + fmt.height = 576; + adv7183_s_mbus_fmt(sd, &fmt); + + /* initialize the hardware to the default control values */ + ret = v4l2_ctrl_handler_setup(hdl); + if (ret) { + v4l2_ctrl_handler_free(hdl); + goto err_free_oe; + } + + return 0; +err_free_oe: + gpio_free(decoder->oe_pin); +err_free_reset: + gpio_free(decoder->reset_pin); +err_free_decoder: + kfree(decoder); + return ret; +} + +static int adv7183_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7183 *decoder = to_adv7183(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + gpio_free(decoder->oe_pin); + gpio_free(decoder->reset_pin); + kfree(decoder); + return 0; +} + +static const struct i2c_device_id adv7183_id[] = { + {"adv7183", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, adv7183_id); + +static struct i2c_driver adv7183_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7183", + }, + .probe = adv7183_probe, + .remove = __devexit_p(adv7183_remove), + .id_table = adv7183_id, +}; + +static __init int adv7183_init(void) +{ + return i2c_add_driver(&adv7183_driver); +} + +static __exit void adv7183_exit(void) +{ + i2c_del_driver(&adv7183_driver); +} + +module_init(adv7183_init); +module_exit(adv7183_exit); + +MODULE_DESCRIPTION("Analog Devices ADV7183 video decoder driver"); +MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/adv7183_regs.h b/drivers/media/video/adv7183_regs.h new file mode 100644 index 000000000000..4a5b7d211d2f --- /dev/null +++ b/drivers/media/video/adv7183_regs.h @@ -0,0 +1,107 @@ +/* + * adv7183 - Analog Devices ADV7183 video decoder registers + * + * Copyright (c) 2011 Analog Devices Inc. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _ADV7183_REGS_H_ +#define _ADV7183_REGS_H_ + +#define ADV7183_IN_CTRL 0x00 /* Input control */ +#define ADV7183_VD_SEL 0x01 /* Video selection */ +#define ADV7183_OUT_CTRL 0x03 /* Output control */ +#define ADV7183_EXT_OUT_CTRL 0x04 /* Extended output control */ +#define ADV7183_AUTO_DET_EN 0x07 /* Autodetect enable */ +#define ADV7183_CONTRAST 0x08 /* Contrast */ +#define ADV7183_BRIGHTNESS 0x0A /* Brightness */ +#define ADV7183_HUE 0x0B /* Hue */ +#define ADV7183_DEF_Y 0x0C /* Default value Y */ +#define ADV7183_DEF_C 0x0D /* Default value C */ +#define ADV7183_ADI_CTRL 0x0E /* ADI control */ +#define ADV7183_POW_MANAGE 0x0F /* Power Management */ +#define ADV7183_STATUS_1 0x10 /* Status 1 */ +#define ADV7183_IDENT 0x11 /* Ident */ +#define ADV7183_STATUS_2 0x12 /* Status 2 */ +#define ADV7183_STATUS_3 0x13 /* Status 3 */ +#define ADV7183_ANAL_CLAMP_CTRL 0x14 /* Analog clamp control */ +#define ADV7183_DIGI_CLAMP_CTRL_1 0x15 /* Digital clamp control 1 */ +#define ADV7183_SHAP_FILT_CTRL 0x17 /* Shaping filter control */ +#define ADV7183_SHAP_FILT_CTRL_2 0x18 /* Shaping filter control 2 */ +#define ADV7183_COMB_FILT_CTRL 0x19 /* Comb filter control */ +#define ADV7183_ADI_CTRL_2 0x1D /* ADI control 2 */ +#define ADV7183_PIX_DELAY_CTRL 0x27 /* Pixel delay control */ +#define ADV7183_MISC_GAIN_CTRL 0x2B /* Misc gain control */ +#define ADV7183_AGC_MODE_CTRL 0x2C /* AGC mode control */ +#define ADV7183_CHRO_GAIN_CTRL_1 0x2D /* Chroma gain control 1 */ +#define ADV7183_CHRO_GAIN_CTRL_2 0x2E /* Chroma gain control 2 */ +#define ADV7183_LUMA_GAIN_CTRL_1 0x2F /* Luma gain control 1 */ +#define ADV7183_LUMA_GAIN_CTRL_2 0x30 /* Luma gain control 2 */ +#define ADV7183_VS_FIELD_CTRL_1 0x31 /* Vsync field control 1 */ +#define ADV7183_VS_FIELD_CTRL_2 0x32 /* Vsync field control 2 */ +#define ADV7183_VS_FIELD_CTRL_3 0x33 /* Vsync field control 3 */ +#define ADV7183_HS_POS_CTRL_1 0x34 /* Hsync positon control 1 */ +#define ADV7183_HS_POS_CTRL_2 0x35 /* Hsync positon control 2 */ +#define ADV7183_HS_POS_CTRL_3 0x36 /* Hsync positon control 3 */ +#define ADV7183_POLARITY 0x37 /* Polarity */ +#define ADV7183_NTSC_COMB_CTRL 0x38 /* NTSC comb control */ +#define ADV7183_PAL_COMB_CTRL 0x39 /* PAL comb control */ +#define ADV7183_ADC_CTRL 0x3A /* ADC control */ +#define ADV7183_MAN_WIN_CTRL 0x3D /* Manual window control */ +#define ADV7183_RESAMPLE_CTRL 0x41 /* Resample control */ +#define ADV7183_GEMSTAR_CTRL_1 0x48 /* Gemstar ctrl 1 */ +#define ADV7183_GEMSTAR_CTRL_2 0x49 /* Gemstar ctrl 2 */ +#define ADV7183_GEMSTAR_CTRL_3 0x4A /* Gemstar ctrl 3 */ +#define ADV7183_GEMSTAR_CTRL_4 0x4B /* Gemstar ctrl 4 */ +#define ADV7183_GEMSTAR_CTRL_5 0x4C /* Gemstar ctrl 5 */ +#define ADV7183_CTI_DNR_CTRL_1 0x4D /* CTI DNR ctrl 1 */ +#define ADV7183_CTI_DNR_CTRL_2 0x4E /* CTI DNR ctrl 2 */ +#define ADV7183_CTI_DNR_CTRL_4 0x50 /* CTI DNR ctrl 4 */ +#define ADV7183_LOCK_CNT 0x51 /* Lock count */ +#define ADV7183_FREE_LINE_LEN 0x8F /* Free-Run line length 1 */ +#define ADV7183_VBI_INFO 0x90 /* VBI info */ +#define ADV7183_WSS_1 0x91 /* WSS 1 */ +#define ADV7183_WSS_2 0x92 /* WSS 2 */ +#define ADV7183_EDTV_1 0x93 /* EDTV 1 */ +#define ADV7183_EDTV_2 0x94 /* EDTV 2 */ +#define ADV7183_EDTV_3 0x95 /* EDTV 3 */ +#define ADV7183_CGMS_1 0x96 /* CGMS 1 */ +#define ADV7183_CGMS_2 0x97 /* CGMS 2 */ +#define ADV7183_CGMS_3 0x98 /* CGMS 3 */ +#define ADV7183_CCAP_1 0x99 /* CCAP 1 */ +#define ADV7183_CCAP_2 0x9A /* CCAP 2 */ +#define ADV7183_LETTERBOX_1 0x9B /* Letterbox 1 */ +#define ADV7183_LETTERBOX_2 0x9C /* Letterbox 2 */ +#define ADV7183_LETTERBOX_3 0x9D /* Letterbox 3 */ +#define ADV7183_CRC_EN 0xB2 /* CRC enable */ +#define ADV7183_ADC_SWITCH_1 0xC3 /* ADC switch 1 */ +#define ADV7183_ADC_SWITCH_2 0xC4 /* ADC swithc 2 */ +#define ADV7183_LETTERBOX_CTRL_1 0xDC /* Letterbox control 1 */ +#define ADV7183_LETTERBOX_CTRL_2 0xDD /* Letterbox control 2 */ +#define ADV7183_SD_OFFSET_CB 0xE1 /* SD offset Cb */ +#define ADV7183_SD_OFFSET_CR 0xE2 /* SD offset Cr */ +#define ADV7183_SD_SATURATION_CB 0xE3 /* SD saturation Cb */ +#define ADV7183_SD_SATURATION_CR 0xE4 /* SD saturation Cr */ +#define ADV7183_NTSC_V_BEGIN 0xE5 /* NTSC V bit begin */ +#define ADV7183_NTSC_V_END 0xE6 /* NTSC V bit end */ +#define ADV7183_NTSC_F_TOGGLE 0xE7 /* NTSC F bit toggle */ +#define ADV7183_PAL_V_BEGIN 0xE8 /* PAL V bit begin */ +#define ADV7183_PAL_V_END 0xE9 /* PAL V bit end */ +#define ADV7183_PAL_F_TOGGLE 0xEA /* PAL F bit toggle */ +#define ADV7183_DRIVE_STR 0xF4 /* Drive strength */ +#define ADV7183_IF_COMP_CTRL 0xF8 /* IF comp control */ +#define ADV7183_VS_MODE_CTRL 0xF9 /* VS mode control */ + +#endif diff --git a/drivers/media/video/adv7343.c b/drivers/media/video/adv7343.c index 021fab23070d..119b60401bf3 100644 --- a/drivers/media/video/adv7343.c +++ b/drivers/media/video/adv7343.c @@ -475,15 +475,4 @@ static struct i2c_driver adv7343_driver = { .id_table = adv7343_id, }; -static __init int init_adv7343(void) -{ - return i2c_add_driver(&adv7343_driver); -} - -static __exit void exit_adv7343(void) -{ - i2c_del_driver(&adv7343_driver); -} - -module_init(init_adv7343); -module_exit(exit_adv7343); +module_i2c_driver(adv7343_driver); diff --git a/drivers/media/video/ak881x.c b/drivers/media/video/ak881x.c index 53c496c00fb6..ba674656b10d 100644 --- a/drivers/media/video/ak881x.c +++ b/drivers/media/video/ak881x.c @@ -352,18 +352,7 @@ static struct i2c_driver ak881x_i2c_driver = { .id_table = ak881x_id, }; -static int __init ak881x_module_init(void) -{ - return i2c_add_driver(&ak881x_i2c_driver); -} - -static void __exit ak881x_module_exit(void) -{ - i2c_del_driver(&ak881x_i2c_driver); -} - -module_init(ak881x_module_init); -module_exit(ak881x_module_exit); +module_i2c_driver(ak881x_i2c_driver); MODULE_DESCRIPTION("TV-output driver for ak8813/ak8814"); MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); diff --git a/drivers/media/video/aptina-pll.c b/drivers/media/video/aptina-pll.c new file mode 100644 index 000000000000..0bd3813bb59d --- /dev/null +++ b/drivers/media/video/aptina-pll.c @@ -0,0 +1,174 @@ +/* + * Aptina Sensor PLL Configuration + * + * Copyright (C) 2012 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 + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/device.h> +#include <linux/gcd.h> +#include <linux/kernel.h> +#include <linux/lcm.h> +#include <linux/module.h> + +#include "aptina-pll.h" + +int aptina_pll_calculate(struct device *dev, + const struct aptina_pll_limits *limits, + struct aptina_pll *pll) +{ + unsigned int mf_min; + unsigned int mf_max; + unsigned int p1_min; + unsigned int p1_max; + unsigned int p1; + unsigned int div; + + dev_dbg(dev, "PLL: ext clock %u pix clock %u\n", + pll->ext_clock, pll->pix_clock); + + if (pll->ext_clock < limits->ext_clock_min || + pll->ext_clock > limits->ext_clock_max) { + dev_err(dev, "pll: invalid external clock frequency.\n"); + return -EINVAL; + } + + if (pll->pix_clock == 0 || pll->pix_clock > limits->pix_clock_max) { + dev_err(dev, "pll: invalid pixel clock frequency.\n"); + return -EINVAL; + } + + /* Compute the multiplier M and combined N*P1 divisor. */ + div = gcd(pll->pix_clock, pll->ext_clock); + pll->m = pll->pix_clock / div; + div = pll->ext_clock / div; + + /* We now have the smallest M and N*P1 values that will result in the + * desired pixel clock frequency, but they might be out of the valid + * range. Compute the factor by which we should multiply them given the + * following constraints: + * + * - minimum/maximum multiplier + * - minimum/maximum multiplier output clock frequency assuming the + * minimum/maximum N value + * - minimum/maximum combined N*P1 divisor + */ + mf_min = DIV_ROUND_UP(limits->m_min, pll->m); + mf_min = max(mf_min, limits->out_clock_min / + (pll->ext_clock / limits->n_min * pll->m)); + mf_min = max(mf_min, limits->n_min * limits->p1_min / div); + mf_max = limits->m_max / pll->m; + mf_max = min(mf_max, limits->out_clock_max / + (pll->ext_clock / limits->n_max * pll->m)); + mf_max = min(mf_max, DIV_ROUND_UP(limits->n_max * limits->p1_max, div)); + + dev_dbg(dev, "pll: mf min %u max %u\n", mf_min, mf_max); + if (mf_min > mf_max) { + dev_err(dev, "pll: no valid combined N*P1 divisor.\n"); + return -EINVAL; + } + + /* + * We're looking for the highest acceptable P1 value for which a + * multiplier factor MF exists that fulfills the following conditions: + * + * 1. p1 is in the [p1_min, p1_max] range given by the limits and is + * even + * 2. mf is in the [mf_min, mf_max] range computed above + * 3. div * mf is a multiple of p1, in order to compute + * n = div * mf / p1 + * m = pll->m * mf + * 4. the internal clock frequency, given by ext_clock / n, is in the + * [int_clock_min, int_clock_max] range given by the limits + * 5. the output clock frequency, given by ext_clock / n * m, is in the + * [out_clock_min, out_clock_max] range given by the limits + * + * The first naive approach is to iterate over all p1 values acceptable + * according to (1) and all mf values acceptable according to (2), and + * stop at the first combination that fulfills (3), (4) and (5). This + * has a O(n^2) complexity. + * + * Instead of iterating over all mf values in the [mf_min, mf_max] range + * we can compute the mf increment between two acceptable values + * according to (3) with + * + * mf_inc = p1 / gcd(div, p1) (6) + * + * and round the minimum up to the nearest multiple of mf_inc. This will + * restrict the number of mf values to be checked. + * + * Furthermore, conditions (4) and (5) only restrict the range of + * acceptable p1 and mf values by modifying the minimum and maximum + * limits. (5) can be expressed as + * + * ext_clock / (div * mf / p1) * m * mf >= out_clock_min + * ext_clock / (div * mf / p1) * m * mf <= out_clock_max + * + * or + * + * p1 >= out_clock_min * div / (ext_clock * m) (7) + * p1 <= out_clock_max * div / (ext_clock * m) + * + * Similarly, (4) can be expressed as + * + * mf >= ext_clock * p1 / (int_clock_max * div) (8) + * mf <= ext_clock * p1 / (int_clock_min * div) + * + * We can thus iterate over the restricted p1 range defined by the + * combination of (1) and (7), and then compute the restricted mf range + * defined by the combination of (2), (6) and (8). If the resulting mf + * range is not empty, any value in the mf range is acceptable. We thus + * select the mf lwoer bound and the corresponding p1 value. + */ + if (limits->p1_min == 0) { + dev_err(dev, "pll: P1 minimum value must be >0.\n"); + return -EINVAL; + } + + p1_min = max(limits->p1_min, DIV_ROUND_UP(limits->out_clock_min * div, + pll->ext_clock * pll->m)); + p1_max = min(limits->p1_max, limits->out_clock_max * div / + (pll->ext_clock * pll->m)); + + for (p1 = p1_max & ~1; p1 >= p1_min; p1 -= 2) { + unsigned int mf_inc = p1 / gcd(div, p1); + unsigned int mf_high; + unsigned int mf_low; + + mf_low = max(roundup(mf_min, mf_inc), + DIV_ROUND_UP(pll->ext_clock * p1, + limits->int_clock_max * div)); + mf_high = min(mf_max, pll->ext_clock * p1 / + (limits->int_clock_min * div)); + + if (mf_low > mf_high) + continue; + + pll->n = div * mf_low / p1; + pll->m *= mf_low; + pll->p1 = p1; + dev_dbg(dev, "PLL: N %u M %u P1 %u\n", pll->n, pll->m, pll->p1); + return 0; + } + + dev_err(dev, "pll: no valid N and P1 divisors found.\n"); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(aptina_pll_calculate); + +MODULE_DESCRIPTION("Aptina PLL Helpers"); +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/aptina-pll.h b/drivers/media/video/aptina-pll.h new file mode 100644 index 000000000000..b370e341e75d --- /dev/null +++ b/drivers/media/video/aptina-pll.h @@ -0,0 +1,56 @@ +/* + * Aptina Sensor PLL Configuration + * + * Copyright (C) 2012 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 + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __APTINA_PLL_H +#define __APTINA_PLL_H + +struct aptina_pll { + unsigned int ext_clock; + unsigned int pix_clock; + + unsigned int n; + unsigned int m; + unsigned int p1; +}; + +struct aptina_pll_limits { + unsigned int ext_clock_min; + unsigned int ext_clock_max; + unsigned int int_clock_min; + unsigned int int_clock_max; + unsigned int out_clock_min; + unsigned int out_clock_max; + unsigned int pix_clock_max; + + unsigned int n_min; + unsigned int n_max; + unsigned int m_min; + unsigned int m_max; + unsigned int p1_min; + unsigned int p1_max; +}; + +struct device; + +int aptina_pll_calculate(struct device *dev, + const struct aptina_pll_limits *limits, + struct aptina_pll *pll); + +#endif /* __APTINA_PLL_H */ diff --git a/drivers/media/video/as3645a.c b/drivers/media/video/as3645a.c index f241702a0f36..7a3371f044fc 100644 --- a/drivers/media/video/as3645a.c +++ b/drivers/media/video/as3645a.c @@ -881,24 +881,7 @@ static struct i2c_driver as3645a_i2c_driver = { .id_table = as3645a_id_table, }; -static int __init as3645a_init(void) -{ - int rval; - - rval = i2c_add_driver(&as3645a_i2c_driver); - if (rval) - pr_err("%s: Failed to register the driver\n", AS3645A_NAME); - - return rval; -} - -static void __exit as3645a_exit(void) -{ - i2c_del_driver(&as3645a_i2c_driver); -} - -module_init(as3645a_init); -module_exit(as3645a_exit); +module_i2c_driver(as3645a_i2c_driver); MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); MODULE_DESCRIPTION("LED flash driver for AS3645A, LM3555 and their clones"); diff --git a/drivers/media/video/blackfin/Kconfig b/drivers/media/video/blackfin/Kconfig new file mode 100644 index 000000000000..ecd5323768b7 --- /dev/null +++ b/drivers/media/video/blackfin/Kconfig @@ -0,0 +1,10 @@ +config VIDEO_BLACKFIN_CAPTURE + tristate "Blackfin Video Capture Driver" + depends on VIDEO_V4L2 && BLACKFIN && I2C + select VIDEOBUF2_DMA_CONTIG + help + V4L2 bridge driver for Blackfin video capture device. + Choose PPI or EPPI as its interface. + + To compile this driver as a module, choose M here: the + module will be called bfin_video_capture. diff --git a/drivers/media/video/blackfin/Makefile b/drivers/media/video/blackfin/Makefile new file mode 100644 index 000000000000..aa3a0a216387 --- /dev/null +++ b/drivers/media/video/blackfin/Makefile @@ -0,0 +1,2 @@ +bfin_video_capture-objs := bfin_capture.o ppi.o +obj-$(CONFIG_VIDEO_BLACKFIN_CAPTURE) += bfin_video_capture.o diff --git a/drivers/media/video/blackfin/bfin_capture.c b/drivers/media/video/blackfin/bfin_capture.c new file mode 100644 index 000000000000..514fcf742f5a --- /dev/null +++ b/drivers/media/video/blackfin/bfin_capture.c @@ -0,0 +1,1059 @@ +/* + * Analog Devices video capture driver + * + * Copyright (c) 2011 Analog Devices Inc. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/types.h> + +#include <media/v4l2-chip-ident.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include <asm/dma.h> + +#include <media/blackfin/bfin_capture.h> +#include <media/blackfin/ppi.h> + +#define CAPTURE_DRV_NAME "bfin_capture" +#define BCAP_MIN_NUM_BUF 2 + +struct bcap_format { + char *desc; + u32 pixelformat; + enum v4l2_mbus_pixelcode mbus_code; + int bpp; /* bits per pixel */ +}; + +struct bcap_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +struct bcap_device { + /* capture device instance */ + struct v4l2_device v4l2_dev; + /* v4l2 control handler */ + struct v4l2_ctrl_handler ctrl_handler; + /* device node data */ + struct video_device *video_dev; + /* sub device instance */ + struct v4l2_subdev *sd; + /* capture config */ + struct bfin_capture_config *cfg; + /* ppi interface */ + struct ppi_if *ppi; + /* current input */ + unsigned int cur_input; + /* current selected standard */ + v4l2_std_id std; + /* used to store pixel format */ + struct v4l2_pix_format fmt; + /* bits per pixel*/ + int bpp; + /* used to store sensor supported format */ + struct bcap_format *sensor_formats; + /* number of sensor formats array */ + int num_sensor_formats; + /* pointing to current video buffer */ + struct bcap_buffer *cur_frm; + /* pointing to next video buffer */ + struct bcap_buffer *next_frm; + /* buffer queue used in videobuf2 */ + struct vb2_queue buffer_queue; + /* allocator-specific contexts for each plane */ + struct vb2_alloc_ctx *alloc_ctx; + /* queue of filled frames */ + struct list_head dma_queue; + /* used in videobuf2 callback */ + spinlock_t lock; + /* used to access capture device */ + struct mutex mutex; + /* used to wait ppi to complete one transfer */ + struct completion comp; + /* prepare to stop */ + bool stop; +}; + +struct bcap_fh { + struct v4l2_fh fh; + /* indicates whether this file handle is doing IO */ + bool io_allowed; +}; + +static const struct bcap_format bcap_formats[] = { + { + .desc = "YCbCr 4:2:2 Interleaved UYVY", + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = V4L2_MBUS_FMT_UYVY8_2X8, + .bpp = 16, + }, + { + .desc = "YCbCr 4:2:2 Interleaved YUYV", + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .bpp = 16, + }, + { + .desc = "RGB 565", + .pixelformat = V4L2_PIX_FMT_RGB565, + .mbus_code = V4L2_MBUS_FMT_RGB565_2X8_LE, + .bpp = 16, + }, + { + .desc = "RGB 444", + .pixelformat = V4L2_PIX_FMT_RGB444, + .mbus_code = V4L2_MBUS_FMT_RGB444_2X8_PADHI_LE, + .bpp = 16, + }, + +}; +#define BCAP_MAX_FMTS ARRAY_SIZE(bcap_formats) + +static irqreturn_t bcap_isr(int irq, void *dev_id); + +static struct bcap_buffer *to_bcap_vb(struct vb2_buffer *vb) +{ + return container_of(vb, struct bcap_buffer, vb); +} + +static int bcap_init_sensor_formats(struct bcap_device *bcap_dev) +{ + enum v4l2_mbus_pixelcode code; + struct bcap_format *sf; + unsigned int num_formats = 0; + int i, j; + + while (!v4l2_subdev_call(bcap_dev->sd, video, + enum_mbus_fmt, num_formats, &code)) + num_formats++; + if (!num_formats) + return -ENXIO; + + sf = kzalloc(num_formats * sizeof(*sf), GFP_KERNEL); + if (!sf) + return -ENOMEM; + + for (i = 0; i < num_formats; i++) { + v4l2_subdev_call(bcap_dev->sd, video, + enum_mbus_fmt, i, &code); + for (j = 0; j < BCAP_MAX_FMTS; j++) + if (code == bcap_formats[j].mbus_code) + break; + if (j == BCAP_MAX_FMTS) { + /* we don't allow this sensor working with our bridge */ + kfree(sf); + return -EINVAL; + } + sf[i] = bcap_formats[j]; + } + bcap_dev->sensor_formats = sf; + bcap_dev->num_sensor_formats = num_formats; + return 0; +} + +static void bcap_free_sensor_formats(struct bcap_device *bcap_dev) +{ + bcap_dev->num_sensor_formats = 0; + kfree(bcap_dev->sensor_formats); + bcap_dev->sensor_formats = NULL; +} + +static int bcap_open(struct file *file) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct video_device *vfd = bcap_dev->video_dev; + struct bcap_fh *bcap_fh; + + if (!bcap_dev->sd) { + v4l2_err(&bcap_dev->v4l2_dev, "No sub device registered\n"); + return -ENODEV; + } + + bcap_fh = kzalloc(sizeof(*bcap_fh), GFP_KERNEL); + if (!bcap_fh) { + v4l2_err(&bcap_dev->v4l2_dev, + "unable to allocate memory for file handle object\n"); + return -ENOMEM; + } + + v4l2_fh_init(&bcap_fh->fh, vfd); + + /* store pointer to v4l2_fh in private_data member of file */ + file->private_data = &bcap_fh->fh; + v4l2_fh_add(&bcap_fh->fh); + bcap_fh->io_allowed = false; + return 0; +} + +static int bcap_release(struct file *file) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + /* if this instance is doing IO */ + if (bcap_fh->io_allowed) + vb2_queue_release(&bcap_dev->buffer_queue); + + file->private_data = NULL; + v4l2_fh_del(&bcap_fh->fh); + v4l2_fh_exit(&bcap_fh->fh); + kfree(bcap_fh); + return 0; +} + +static int bcap_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return vb2_mmap(&bcap_dev->buffer_queue, vma); +} + +#ifndef CONFIG_MMU +static unsigned long bcap_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return vb2_get_unmapped_area(&bcap_dev->buffer_queue, + addr, + len, + pgoff, + flags); +} +#endif + +static unsigned int bcap_poll(struct file *file, poll_table *wait) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return vb2_poll(&bcap_dev->buffer_queue, file, wait); +} + +static int bcap_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + + if (*nbuffers < BCAP_MIN_NUM_BUF) + *nbuffers = BCAP_MIN_NUM_BUF; + + *nplanes = 1; + sizes[0] = bcap_dev->fmt.sizeimage; + alloc_ctxs[0] = bcap_dev->alloc_ctx; + + return 0; +} + +static int bcap_buffer_init(struct vb2_buffer *vb) +{ + struct bcap_buffer *buf = to_bcap_vb(vb); + + INIT_LIST_HEAD(&buf->list); + return 0; +} + +static int bcap_buffer_prepare(struct vb2_buffer *vb) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vb->vb2_queue); + struct bcap_buffer *buf = to_bcap_vb(vb); + unsigned long size; + + size = bcap_dev->fmt.sizeimage; + if (vb2_plane_size(vb, 0) < size) { + v4l2_err(&bcap_dev->v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + vb2_set_plane_payload(&buf->vb, 0, size); + + return 0; +} + +static void bcap_buffer_queue(struct vb2_buffer *vb) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vb->vb2_queue); + struct bcap_buffer *buf = to_bcap_vb(vb); + unsigned long flags; + + spin_lock_irqsave(&bcap_dev->lock, flags); + list_add_tail(&buf->list, &bcap_dev->dma_queue); + spin_unlock_irqrestore(&bcap_dev->lock, flags); +} + +static void bcap_buffer_cleanup(struct vb2_buffer *vb) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vb->vb2_queue); + struct bcap_buffer *buf = to_bcap_vb(vb); + unsigned long flags; + + spin_lock_irqsave(&bcap_dev->lock, flags); + list_del_init(&buf->list); + spin_unlock_irqrestore(&bcap_dev->lock, flags); +} + +static void bcap_lock(struct vb2_queue *vq) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + mutex_lock(&bcap_dev->mutex); +} + +static void bcap_unlock(struct vb2_queue *vq) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + mutex_unlock(&bcap_dev->mutex); +} + +static int bcap_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + struct ppi_if *ppi = bcap_dev->ppi; + struct ppi_params params; + int ret; + + /* enable streamon on the sub device */ + ret = v4l2_subdev_call(bcap_dev->sd, video, s_stream, 1); + if (ret && (ret != -ENOIOCTLCMD)) { + v4l2_err(&bcap_dev->v4l2_dev, "stream on failed in subdev\n"); + return ret; + } + + /* set ppi params */ + params.width = bcap_dev->fmt.width; + params.height = bcap_dev->fmt.height; + params.bpp = bcap_dev->bpp; + params.ppi_control = bcap_dev->cfg->ppi_control; + params.int_mask = bcap_dev->cfg->int_mask; + params.blank_clocks = bcap_dev->cfg->blank_clocks; + ret = ppi->ops->set_params(ppi, ¶ms); + if (ret < 0) { + v4l2_err(&bcap_dev->v4l2_dev, + "Error in setting ppi params\n"); + return ret; + } + + /* attach ppi DMA irq handler */ + ret = ppi->ops->attach_irq(ppi, bcap_isr); + if (ret < 0) { + v4l2_err(&bcap_dev->v4l2_dev, + "Error in attaching interrupt handler\n"); + return ret; + } + + INIT_COMPLETION(bcap_dev->comp); + bcap_dev->stop = false; + return 0; +} + +static int bcap_stop_streaming(struct vb2_queue *vq) +{ + struct bcap_device *bcap_dev = vb2_get_drv_priv(vq); + struct ppi_if *ppi = bcap_dev->ppi; + int ret; + + if (!vb2_is_streaming(vq)) + return 0; + + bcap_dev->stop = true; + wait_for_completion(&bcap_dev->comp); + ppi->ops->stop(ppi); + ppi->ops->detach_irq(ppi); + ret = v4l2_subdev_call(bcap_dev->sd, video, s_stream, 0); + if (ret && (ret != -ENOIOCTLCMD)) + v4l2_err(&bcap_dev->v4l2_dev, + "stream off failed in subdev\n"); + + /* release all active buffers */ + while (!list_empty(&bcap_dev->dma_queue)) { + bcap_dev->next_frm = list_entry(bcap_dev->dma_queue.next, + struct bcap_buffer, list); + list_del(&bcap_dev->next_frm->list); + vb2_buffer_done(&bcap_dev->next_frm->vb, VB2_BUF_STATE_ERROR); + } + return 0; +} + +static struct vb2_ops bcap_video_qops = { + .queue_setup = bcap_queue_setup, + .buf_init = bcap_buffer_init, + .buf_prepare = bcap_buffer_prepare, + .buf_cleanup = bcap_buffer_cleanup, + .buf_queue = bcap_buffer_queue, + .wait_prepare = bcap_unlock, + .wait_finish = bcap_lock, + .start_streaming = bcap_start_streaming, + .stop_streaming = bcap_stop_streaming, +}; + +static int bcap_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req_buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct vb2_queue *vq = &bcap_dev->buffer_queue; + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + if (vb2_is_busy(vq)) + return -EBUSY; + + bcap_fh->io_allowed = true; + + return vb2_reqbufs(vq, req_buf); +} + +static int bcap_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return vb2_querybuf(&bcap_dev->buffer_queue, buf); +} + +static int bcap_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + if (!bcap_fh->io_allowed) + return -EBUSY; + + return vb2_qbuf(&bcap_dev->buffer_queue, buf); +} + +static int bcap_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_fh *fh = file->private_data; + struct bcap_fh *bcap_fh = container_of(fh, struct bcap_fh, fh); + + if (!bcap_fh->io_allowed) + return -EBUSY; + + return vb2_dqbuf(&bcap_dev->buffer_queue, + buf, file->f_flags & O_NONBLOCK); +} + +static irqreturn_t bcap_isr(int irq, void *dev_id) +{ + struct ppi_if *ppi = dev_id; + struct bcap_device *bcap_dev = ppi->priv; + struct timeval timevalue; + struct vb2_buffer *vb = &bcap_dev->cur_frm->vb; + dma_addr_t addr; + + spin_lock(&bcap_dev->lock); + + if (bcap_dev->cur_frm != bcap_dev->next_frm) { + do_gettimeofday(&timevalue); + vb->v4l2_buf.timestamp = timevalue; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + bcap_dev->cur_frm = bcap_dev->next_frm; + } + + ppi->ops->stop(ppi); + + if (bcap_dev->stop) { + complete(&bcap_dev->comp); + } else { + if (!list_empty(&bcap_dev->dma_queue)) { + bcap_dev->next_frm = list_entry(bcap_dev->dma_queue.next, + struct bcap_buffer, list); + list_del(&bcap_dev->next_frm->list); + addr = vb2_dma_contig_plane_dma_addr(&bcap_dev->next_frm->vb, 0); + ppi->ops->update_addr(ppi, (unsigned long)addr); + } + ppi->ops->start(ppi); + } + + spin_unlock(&bcap_dev->lock); + + return IRQ_HANDLED; +} + +static int bcap_streamon(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bcap_fh *fh = file->private_data; + struct ppi_if *ppi = bcap_dev->ppi; + dma_addr_t addr; + int ret; + + if (!fh->io_allowed) + return -EBUSY; + + /* call streamon to start streaming in videobuf */ + ret = vb2_streamon(&bcap_dev->buffer_queue, buf_type); + if (ret) + return ret; + + /* if dma queue is empty, return error */ + if (list_empty(&bcap_dev->dma_queue)) { + v4l2_err(&bcap_dev->v4l2_dev, "dma queue is empty\n"); + ret = -EINVAL; + goto err; + } + + /* get the next frame from the dma queue */ + bcap_dev->next_frm = list_entry(bcap_dev->dma_queue.next, + struct bcap_buffer, list); + bcap_dev->cur_frm = bcap_dev->next_frm; + /* remove buffer from the dma queue */ + list_del(&bcap_dev->cur_frm->list); + addr = vb2_dma_contig_plane_dma_addr(&bcap_dev->cur_frm->vb, 0); + /* update DMA address */ + ppi->ops->update_addr(ppi, (unsigned long)addr); + /* enable ppi */ + ppi->ops->start(ppi); + + return 0; +err: + vb2_streamoff(&bcap_dev->buffer_queue, buf_type); + return ret; +} + +static int bcap_streamoff(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bcap_fh *fh = file->private_data; + + if (!fh->io_allowed) + return -EBUSY; + + return vb2_streamoff(&bcap_dev->buffer_queue, buf_type); +} + +static int bcap_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return v4l2_subdev_call(bcap_dev->sd, video, querystd, std); +} + +static int bcap_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + *std = bcap_dev->std; + return 0; +} + +static int bcap_s_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + int ret; + + if (vb2_is_busy(&bcap_dev->buffer_queue)) + return -EBUSY; + + ret = v4l2_subdev_call(bcap_dev->sd, core, s_std, *std); + if (ret < 0) + return ret; + + bcap_dev->std = *std; + return 0; +} + +static int bcap_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bfin_capture_config *config = bcap_dev->cfg; + int ret; + u32 status; + + if (input->index >= config->num_inputs) + return -EINVAL; + + *input = config->inputs[input->index]; + /* get input status */ + ret = v4l2_subdev_call(bcap_dev->sd, video, g_input_status, &status); + if (!ret) + input->status = status; + return 0; +} + +static int bcap_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + *index = bcap_dev->cur_input; + return 0; +} + +static int bcap_s_input(struct file *file, void *priv, unsigned int index) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bfin_capture_config *config = bcap_dev->cfg; + struct bcap_route *route; + int ret; + + if (vb2_is_busy(&bcap_dev->buffer_queue)) + return -EBUSY; + + if (index >= config->num_inputs) + return -EINVAL; + + route = &config->routes[index]; + ret = v4l2_subdev_call(bcap_dev->sd, video, s_routing, + route->input, route->output, 0); + if ((ret < 0) && (ret != -ENOIOCTLCMD)) { + v4l2_err(&bcap_dev->v4l2_dev, "Failed to set input\n"); + return ret; + } + bcap_dev->cur_input = index; + return 0; +} + +static int bcap_try_format(struct bcap_device *bcap, + struct v4l2_pix_format *pixfmt, + enum v4l2_mbus_pixelcode *mbus_code, + int *bpp) +{ + struct bcap_format *sf = bcap->sensor_formats; + struct bcap_format *fmt = NULL; + struct v4l2_mbus_framefmt mbus_fmt; + int ret, i; + + for (i = 0; i < bcap->num_sensor_formats; i++) { + fmt = &sf[i]; + if (pixfmt->pixelformat == fmt->pixelformat) + break; + } + if (i == bcap->num_sensor_formats) + fmt = &sf[0]; + + if (mbus_code) + *mbus_code = fmt->mbus_code; + if (bpp) + *bpp = fmt->bpp; + v4l2_fill_mbus_format(&mbus_fmt, pixfmt, fmt->mbus_code); + ret = v4l2_subdev_call(bcap->sd, video, + try_mbus_fmt, &mbus_fmt); + if (ret < 0) + return ret; + v4l2_fill_pix_format(pixfmt, &mbus_fmt); + pixfmt->bytesperline = pixfmt->width * fmt->bpp / 8; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + return 0; +} + +static int bcap_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct bcap_format *sf = bcap_dev->sensor_formats; + + if (fmt->index >= bcap_dev->num_sensor_formats) + return -EINVAL; + + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + strlcpy(fmt->description, + sf[fmt->index].desc, + sizeof(fmt->description)); + fmt->pixelformat = sf[fmt->index].pixelformat; + return 0; +} + +static int bcap_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + return bcap_try_format(bcap_dev, pixfmt, NULL, NULL); +} + +static int bcap_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + fmt->fmt.pix = bcap_dev->fmt; + return 0; +} + +static int bcap_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + struct v4l2_mbus_framefmt mbus_fmt; + enum v4l2_mbus_pixelcode mbus_code; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + int ret, bpp; + + if (vb2_is_busy(&bcap_dev->buffer_queue)) + return -EBUSY; + + /* see if format works */ + ret = bcap_try_format(bcap_dev, pixfmt, &mbus_code, &bpp); + if (ret < 0) + return ret; + + v4l2_fill_mbus_format(&mbus_fmt, pixfmt, mbus_code); + ret = v4l2_subdev_call(bcap_dev->sd, video, s_mbus_fmt, &mbus_fmt); + if (ret < 0) + return ret; + bcap_dev->fmt = *pixfmt; + bcap_dev->bpp = bpp; + return 0; +} + +static int bcap_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + strlcpy(cap->driver, CAPTURE_DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->bus_info, "Blackfin Platform", sizeof(cap->bus_info)); + strlcpy(cap->card, bcap_dev->cfg->card_name, sizeof(cap->card)); + return 0; +} + +static int bcap_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + return v4l2_subdev_call(bcap_dev->sd, video, g_parm, a); +} + +static int bcap_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + return v4l2_subdev_call(bcap_dev->sd, video, s_parm, a); +} + +static int bcap_g_chip_ident(struct file *file, void *priv, + struct v4l2_dbg_chip_ident *chip) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + if (chip->match.type != V4L2_CHIP_MATCH_I2C_DRIVER && + chip->match.type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + return v4l2_subdev_call(bcap_dev->sd, core, + g_chip_ident, chip); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int bcap_dbg_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return v4l2_subdev_call(bcap_dev->sd, core, + g_register, reg); +} + +static int bcap_dbg_s_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + + return v4l2_subdev_call(bcap_dev->sd, core, + s_register, reg); +} +#endif + +static int bcap_log_status(struct file *file, void *priv) +{ + struct bcap_device *bcap_dev = video_drvdata(file); + /* status for sub devices */ + v4l2_device_call_all(&bcap_dev->v4l2_dev, 0, core, log_status); + return 0; +} + +static const struct v4l2_ioctl_ops bcap_ioctl_ops = { + .vidioc_querycap = bcap_querycap, + .vidioc_g_fmt_vid_cap = bcap_g_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = bcap_enum_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = bcap_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = bcap_try_fmt_vid_cap, + .vidioc_enum_input = bcap_enum_input, + .vidioc_g_input = bcap_g_input, + .vidioc_s_input = bcap_s_input, + .vidioc_querystd = bcap_querystd, + .vidioc_s_std = bcap_s_std, + .vidioc_g_std = bcap_g_std, + .vidioc_reqbufs = bcap_reqbufs, + .vidioc_querybuf = bcap_querybuf, + .vidioc_qbuf = bcap_qbuf, + .vidioc_dqbuf = bcap_dqbuf, + .vidioc_streamon = bcap_streamon, + .vidioc_streamoff = bcap_streamoff, + .vidioc_g_parm = bcap_g_parm, + .vidioc_s_parm = bcap_s_parm, + .vidioc_g_chip_ident = bcap_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = bcap_dbg_g_register, + .vidioc_s_register = bcap_dbg_s_register, +#endif + .vidioc_log_status = bcap_log_status, +}; + +static struct v4l2_file_operations bcap_fops = { + .owner = THIS_MODULE, + .open = bcap_open, + .release = bcap_release, + .unlocked_ioctl = video_ioctl2, + .mmap = bcap_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = bcap_get_unmapped_area, +#endif + .poll = bcap_poll +}; + +static int __devinit bcap_probe(struct platform_device *pdev) +{ + struct bcap_device *bcap_dev; + struct video_device *vfd; + struct i2c_adapter *i2c_adap; + struct bfin_capture_config *config; + struct vb2_queue *q; + int ret; + + config = pdev->dev.platform_data; + if (!config) { + v4l2_err(pdev->dev.driver, "Unable to get board config\n"); + return -ENODEV; + } + + bcap_dev = kzalloc(sizeof(*bcap_dev), GFP_KERNEL); + if (!bcap_dev) { + v4l2_err(pdev->dev.driver, "Unable to alloc bcap_dev\n"); + return -ENOMEM; + } + + bcap_dev->cfg = config; + + bcap_dev->ppi = ppi_create_instance(config->ppi_info); + if (!bcap_dev->ppi) { + v4l2_err(pdev->dev.driver, "Unable to create ppi\n"); + ret = -ENODEV; + goto err_free_dev; + } + bcap_dev->ppi->priv = bcap_dev; + + bcap_dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(bcap_dev->alloc_ctx)) { + ret = PTR_ERR(bcap_dev->alloc_ctx); + goto err_free_ppi; + } + + vfd = video_device_alloc(); + if (!vfd) { + ret = -ENOMEM; + v4l2_err(pdev->dev.driver, "Unable to alloc video device\n"); + goto err_cleanup_ctx; + } + + /* initialize field of video device */ + vfd->release = video_device_release; + vfd->fops = &bcap_fops; + vfd->ioctl_ops = &bcap_ioctl_ops; + vfd->tvnorms = 0; + vfd->v4l2_dev = &bcap_dev->v4l2_dev; + set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); + strncpy(vfd->name, CAPTURE_DRV_NAME, sizeof(vfd->name)); + bcap_dev->video_dev = vfd; + + ret = v4l2_device_register(&pdev->dev, &bcap_dev->v4l2_dev); + if (ret) { + v4l2_err(pdev->dev.driver, + "Unable to register v4l2 device\n"); + goto err_release_vdev; + } + v4l2_info(&bcap_dev->v4l2_dev, "v4l2 device registered\n"); + + bcap_dev->v4l2_dev.ctrl_handler = &bcap_dev->ctrl_handler; + ret = v4l2_ctrl_handler_init(&bcap_dev->ctrl_handler, 0); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to init control handler\n"); + goto err_unreg_v4l2; + } + + spin_lock_init(&bcap_dev->lock); + /* initialize queue */ + q = &bcap_dev->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP; + q->drv_priv = bcap_dev; + q->buf_struct_size = sizeof(struct bcap_buffer); + q->ops = &bcap_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + + vb2_queue_init(q); + + mutex_init(&bcap_dev->mutex); + init_completion(&bcap_dev->comp); + + /* init video dma queues */ + INIT_LIST_HEAD(&bcap_dev->dma_queue); + + vfd->lock = &bcap_dev->mutex; + + /* register video device */ + ret = video_register_device(bcap_dev->video_dev, VFL_TYPE_GRABBER, -1); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to register video device\n"); + goto err_free_handler; + } + video_set_drvdata(bcap_dev->video_dev, bcap_dev); + v4l2_info(&bcap_dev->v4l2_dev, "video device registered as: %s\n", + video_device_node_name(vfd)); + + /* load up the subdevice */ + i2c_adap = i2c_get_adapter(config->i2c_adapter_id); + if (!i2c_adap) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to find i2c adapter\n"); + goto err_unreg_vdev; + + } + bcap_dev->sd = v4l2_i2c_new_subdev_board(&bcap_dev->v4l2_dev, + i2c_adap, + &config->board_info, + NULL); + if (bcap_dev->sd) { + int i; + /* update tvnorms from the sub devices */ + for (i = 0; i < config->num_inputs; i++) + vfd->tvnorms |= config->inputs[i].std; + } else { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to register sub device\n"); + goto err_unreg_vdev; + } + + v4l2_info(&bcap_dev->v4l2_dev, "v4l2 sub device registered\n"); + + /* now we can probe the default state */ + if (vfd->tvnorms) { + v4l2_std_id std; + ret = v4l2_subdev_call(bcap_dev->sd, core, g_std, &std); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to get std\n"); + goto err_unreg_vdev; + } + bcap_dev->std = std; + } + ret = bcap_init_sensor_formats(bcap_dev); + if (ret) { + v4l2_err(&bcap_dev->v4l2_dev, + "Unable to create sensor formats table\n"); + goto err_unreg_vdev; + } + return 0; +err_unreg_vdev: + video_unregister_device(bcap_dev->video_dev); + bcap_dev->video_dev = NULL; +err_free_handler: + v4l2_ctrl_handler_free(&bcap_dev->ctrl_handler); +err_unreg_v4l2: + v4l2_device_unregister(&bcap_dev->v4l2_dev); +err_release_vdev: + if (bcap_dev->video_dev) + video_device_release(bcap_dev->video_dev); +err_cleanup_ctx: + vb2_dma_contig_cleanup_ctx(bcap_dev->alloc_ctx); +err_free_ppi: + ppi_delete_instance(bcap_dev->ppi); +err_free_dev: + kfree(bcap_dev); + return ret; +} + +static int __devexit bcap_remove(struct platform_device *pdev) +{ + struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); + struct bcap_device *bcap_dev = container_of(v4l2_dev, + struct bcap_device, v4l2_dev); + + bcap_free_sensor_formats(bcap_dev); + video_unregister_device(bcap_dev->video_dev); + v4l2_ctrl_handler_free(&bcap_dev->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + vb2_dma_contig_cleanup_ctx(bcap_dev->alloc_ctx); + ppi_delete_instance(bcap_dev->ppi); + kfree(bcap_dev); + return 0; +} + +static struct platform_driver bcap_driver = { + .driver = { + .name = CAPTURE_DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = bcap_probe, + .remove = __devexit_p(bcap_remove), +}; + +static __init int bcap_init(void) +{ + return platform_driver_register(&bcap_driver); +} + +static __exit void bcap_exit(void) +{ + platform_driver_unregister(&bcap_driver); +} + +module_init(bcap_init); +module_exit(bcap_exit); + +MODULE_DESCRIPTION("Analog Devices blackfin video capture driver"); +MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/blackfin/ppi.c b/drivers/media/video/blackfin/ppi.c new file mode 100644 index 000000000000..d29592186b02 --- /dev/null +++ b/drivers/media/video/blackfin/ppi.c @@ -0,0 +1,271 @@ +/* + * ppi.c Analog Devices Parallel Peripheral Interface driver + * + * Copyright (c) 2011 Analog Devices Inc. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/slab.h> + +#include <asm/bfin_ppi.h> +#include <asm/blackfin.h> +#include <asm/cacheflush.h> +#include <asm/dma.h> +#include <asm/portmux.h> + +#include <media/blackfin/ppi.h> + +static int ppi_attach_irq(struct ppi_if *ppi, irq_handler_t handler); +static void ppi_detach_irq(struct ppi_if *ppi); +static int ppi_start(struct ppi_if *ppi); +static int ppi_stop(struct ppi_if *ppi); +static int ppi_set_params(struct ppi_if *ppi, struct ppi_params *params); +static void ppi_update_addr(struct ppi_if *ppi, unsigned long addr); + +static const struct ppi_ops ppi_ops = { + .attach_irq = ppi_attach_irq, + .detach_irq = ppi_detach_irq, + .start = ppi_start, + .stop = ppi_stop, + .set_params = ppi_set_params, + .update_addr = ppi_update_addr, +}; + +static irqreturn_t ppi_irq_err(int irq, void *dev_id) +{ + struct ppi_if *ppi = dev_id; + const struct ppi_info *info = ppi->info; + + switch (info->type) { + case PPI_TYPE_PPI: + { + struct bfin_ppi_regs *reg = info->base; + unsigned short status; + + /* register on bf561 is cleared when read + * others are W1C + */ + status = bfin_read16(®->status); + bfin_write16(®->status, 0xff00); + break; + } + case PPI_TYPE_EPPI: + { + struct bfin_eppi_regs *reg = info->base; + bfin_write16(®->status, 0xffff); + break; + } + default: + break; + } + + return IRQ_HANDLED; +} + +static int ppi_attach_irq(struct ppi_if *ppi, irq_handler_t handler) +{ + const struct ppi_info *info = ppi->info; + int ret; + + ret = request_dma(info->dma_ch, "PPI_DMA"); + + if (ret) { + pr_err("Unable to allocate DMA channel for PPI\n"); + return ret; + } + set_dma_callback(info->dma_ch, handler, ppi); + + if (ppi->err_int) { + ret = request_irq(info->irq_err, ppi_irq_err, 0, "PPI ERROR", ppi); + if (ret) { + pr_err("Unable to allocate IRQ for PPI\n"); + free_dma(info->dma_ch); + } + } + return ret; +} + +static void ppi_detach_irq(struct ppi_if *ppi) +{ + const struct ppi_info *info = ppi->info; + + if (ppi->err_int) + free_irq(info->irq_err, ppi); + free_dma(info->dma_ch); +} + +static int ppi_start(struct ppi_if *ppi) +{ + const struct ppi_info *info = ppi->info; + + /* enable DMA */ + enable_dma(info->dma_ch); + + /* enable PPI */ + ppi->ppi_control |= PORT_EN; + switch (info->type) { + case PPI_TYPE_PPI: + { + struct bfin_ppi_regs *reg = info->base; + bfin_write16(®->control, ppi->ppi_control); + break; + } + case PPI_TYPE_EPPI: + { + struct bfin_eppi_regs *reg = info->base; + bfin_write32(®->control, ppi->ppi_control); + break; + } + default: + return -EINVAL; + } + + SSYNC(); + return 0; +} + +static int ppi_stop(struct ppi_if *ppi) +{ + const struct ppi_info *info = ppi->info; + + /* disable PPI */ + ppi->ppi_control &= ~PORT_EN; + switch (info->type) { + case PPI_TYPE_PPI: + { + struct bfin_ppi_regs *reg = info->base; + bfin_write16(®->control, ppi->ppi_control); + break; + } + case PPI_TYPE_EPPI: + { + struct bfin_eppi_regs *reg = info->base; + bfin_write32(®->control, ppi->ppi_control); + break; + } + default: + return -EINVAL; + } + + /* disable DMA */ + clear_dma_irqstat(info->dma_ch); + disable_dma(info->dma_ch); + + SSYNC(); + return 0; +} + +static int ppi_set_params(struct ppi_if *ppi, struct ppi_params *params) +{ + const struct ppi_info *info = ppi->info; + int dma32 = 0; + int dma_config, bytes_per_line, lines_per_frame; + + bytes_per_line = params->width * params->bpp / 8; + lines_per_frame = params->height; + if (params->int_mask == 0xFFFFFFFF) + ppi->err_int = false; + else + ppi->err_int = true; + + dma_config = (DMA_FLOW_STOP | WNR | RESTART | DMA2D | DI_EN); + ppi->ppi_control = params->ppi_control & ~PORT_EN; + switch (info->type) { + case PPI_TYPE_PPI: + { + struct bfin_ppi_regs *reg = info->base; + + if (params->ppi_control & DMA32) + dma32 = 1; + + bfin_write16(®->control, ppi->ppi_control); + bfin_write16(®->count, bytes_per_line - 1); + bfin_write16(®->frame, lines_per_frame); + break; + } + case PPI_TYPE_EPPI: + { + struct bfin_eppi_regs *reg = info->base; + + if ((params->ppi_control & PACK_EN) + || (params->ppi_control & 0x38000) > DLEN_16) + dma32 = 1; + + bfin_write32(®->control, ppi->ppi_control); + bfin_write16(®->line, bytes_per_line + params->blank_clocks); + bfin_write16(®->frame, lines_per_frame); + bfin_write16(®->hdelay, 0); + bfin_write16(®->vdelay, 0); + bfin_write16(®->hcount, bytes_per_line); + bfin_write16(®->vcount, lines_per_frame); + break; + } + default: + return -EINVAL; + } + + if (dma32) { + dma_config |= WDSIZE_32; + set_dma_x_count(info->dma_ch, bytes_per_line >> 2); + set_dma_x_modify(info->dma_ch, 4); + set_dma_y_modify(info->dma_ch, 4); + } else { + dma_config |= WDSIZE_16; + set_dma_x_count(info->dma_ch, bytes_per_line >> 1); + set_dma_x_modify(info->dma_ch, 2); + set_dma_y_modify(info->dma_ch, 2); + } + set_dma_y_count(info->dma_ch, lines_per_frame); + set_dma_config(info->dma_ch, dma_config); + + SSYNC(); + return 0; +} + +static void ppi_update_addr(struct ppi_if *ppi, unsigned long addr) +{ + set_dma_start_addr(ppi->info->dma_ch, addr); +} + +struct ppi_if *ppi_create_instance(const struct ppi_info *info) +{ + struct ppi_if *ppi; + + if (!info || !info->pin_req) + return NULL; + + if (peripheral_request_list(info->pin_req, KBUILD_MODNAME)) { + pr_err("request peripheral failed\n"); + return NULL; + } + + ppi = kzalloc(sizeof(*ppi), GFP_KERNEL); + if (!ppi) { + peripheral_free_list(info->pin_req); + pr_err("unable to allocate memory for ppi handle\n"); + return NULL; + } + ppi->ops = &ppi_ops; + ppi->info = info; + + pr_info("ppi probe success\n"); + return ppi; +} + +void ppi_delete_instance(struct ppi_if *ppi) +{ + peripheral_free_list(ppi->info->pin_req); + kfree(ppi); +} diff --git a/drivers/media/video/bt819.c b/drivers/media/video/bt819.c index 859eabf57978..377bf05b1efd 100644 --- a/drivers/media/video/bt819.c +++ b/drivers/media/video/bt819.c @@ -514,15 +514,4 @@ static struct i2c_driver bt819_driver = { .id_table = bt819_id, }; -static __init int init_bt819(void) -{ - return i2c_add_driver(&bt819_driver); -} - -static __exit void exit_bt819(void) -{ - i2c_del_driver(&bt819_driver); -} - -module_init(init_bt819); -module_exit(exit_bt819); +module_i2c_driver(bt819_driver); diff --git a/drivers/media/video/bt856.c b/drivers/media/video/bt856.c index a43059d4c799..7e5bd365c239 100644 --- a/drivers/media/video/bt856.c +++ b/drivers/media/video/bt856.c @@ -270,15 +270,4 @@ static struct i2c_driver bt856_driver = { .id_table = bt856_id, }; -static __init int init_bt856(void) -{ - return i2c_add_driver(&bt856_driver); -} - -static __exit void exit_bt856(void) -{ - i2c_del_driver(&bt856_driver); -} - -module_init(init_bt856); -module_exit(exit_bt856); +module_i2c_driver(bt856_driver); diff --git a/drivers/media/video/bt866.c b/drivers/media/video/bt866.c index 4e5dcea0501d..905320b67a1c 100644 --- a/drivers/media/video/bt866.c +++ b/drivers/media/video/bt866.c @@ -240,15 +240,4 @@ static struct i2c_driver bt866_driver = { .id_table = bt866_id, }; -static __init int init_bt866(void) -{ - return i2c_add_driver(&bt866_driver); -} - -static __exit void exit_bt866(void) -{ - i2c_del_driver(&bt866_driver); -} - -module_init(init_bt866); -module_exit(exit_bt866); +module_i2c_driver(bt866_driver); diff --git a/drivers/media/video/bt8xx/bttv-driver.c b/drivers/media/video/bt8xx/bttv-driver.c index 76c301f05095..e581b37be789 100644 --- a/drivers/media/video/bt8xx/bttv-driver.c +++ b/drivers/media/video/bt8xx/bttv-driver.c @@ -2035,11 +2035,7 @@ static int bttv_log_status(struct file *file, void *f) struct bttv_fh *fh = f; struct bttv *btv = fh->btv; - pr_info("%d: ======== START STATUS CARD #%d ========\n", - btv->c.nr, btv->c.nr); bttv_call_all(btv, core, log_status); - pr_info("%d: ======== END STATUS CARD #%d ========\n", - btv->c.nr, btv->c.nr); return 0; } diff --git a/drivers/media/video/cs5345.c b/drivers/media/video/cs5345.c index 1d64af9adf71..c8581e26fa9c 100644 --- a/drivers/media/video/cs5345.c +++ b/drivers/media/video/cs5345.c @@ -249,15 +249,4 @@ static struct i2c_driver cs5345_driver = { .id_table = cs5345_id, }; -static __init int init_cs5345(void) -{ - return i2c_add_driver(&cs5345_driver); -} - -static __exit void exit_cs5345(void) -{ - i2c_del_driver(&cs5345_driver); -} - -module_init(init_cs5345); -module_exit(exit_cs5345); +module_i2c_driver(cs5345_driver); diff --git a/drivers/media/video/cs53l32a.c b/drivers/media/video/cs53l32a.c index 51c5b9ad67d8..b293912206eb 100644 --- a/drivers/media/video/cs53l32a.c +++ b/drivers/media/video/cs53l32a.c @@ -248,15 +248,4 @@ static struct i2c_driver cs53l32a_driver = { .id_table = cs53l32a_id, }; -static __init int init_cs53l32a(void) -{ - return i2c_add_driver(&cs53l32a_driver); -} - -static __exit void exit_cs53l32a(void) -{ - i2c_del_driver(&cs53l32a_driver); -} - -module_init(init_cs53l32a); -module_exit(exit_cs53l32a); +module_i2c_driver(cs53l32a_driver); diff --git a/drivers/media/video/cx18/cx18-driver.c b/drivers/media/video/cx18/cx18-driver.c index 349bd9c2aff5..b55d57cc1a1c 100644 --- a/drivers/media/video/cx18/cx18-driver.c +++ b/drivers/media/video/cx18/cx18-driver.c @@ -38,7 +38,7 @@ #include "cx18-ioctl.h" #include "cx18-controls.h" #include "tuner-xc2028.h" - +#include <linux/dma-mapping.h> #include <media/tveeprom.h> /* If you have already X v4l cards, then set this to X. This way @@ -75,7 +75,7 @@ static int radio[CX18_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static unsigned cardtype_c = 1; static unsigned tuner_c = 1; -static bool radio_c = 1; +static unsigned radio_c = 1; static char pal[] = "--"; static char secam[] = "--"; static char ntsc[] = "-"; @@ -110,7 +110,7 @@ static int retry_mmio = 1; int cx18_debug; module_param_array(tuner, int, &tuner_c, 0644); -module_param_array(radio, bool, &radio_c, 0644); +module_param_array(radio, int, &radio_c, 0644); module_param_array(cardtype, int, &cardtype_c, 0644); module_param_string(pal, pal, sizeof(pal), 0644); module_param_string(secam, secam, sizeof(secam), 0644); @@ -812,7 +812,7 @@ static int cx18_setup_pci(struct cx18 *cx, struct pci_dev *pci_dev, CX18_ERR("Can't enable device %d!\n", cx->instance); return -EIO; } - if (pci_set_dma_mask(pci_dev, 0xffffffff)) { + if (pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32))) { CX18_ERR("No suitable DMA available, card %d\n", cx->instance); return -EIO; } diff --git a/drivers/media/video/cx18/cx18-driver.h b/drivers/media/video/cx18/cx18-driver.h index b9a94fc5146d..7a37e0ee136f 100644 --- a/drivers/media/video/cx18/cx18-driver.h +++ b/drivers/media/video/cx18/cx18-driver.h @@ -44,8 +44,6 @@ #include <linux/slab.h> #include <asm/byteorder.h> -#include <linux/dvb/video.h> -#include <linux/dvb/audio.h> #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> diff --git a/drivers/media/video/cx18/cx18-ioctl.c b/drivers/media/video/cx18/cx18-ioctl.c index 66b1c15c3541..be49f68ddf37 100644 --- a/drivers/media/video/cx18/cx18-ioctl.c +++ b/drivers/media/video/cx18/cx18-ioctl.c @@ -1085,8 +1085,6 @@ static int cx18_log_status(struct file *file, void *fh) struct v4l2_audio audin; int i; - CX18_INFO("================= START STATUS CARD #%d " - "=================\n", cx->instance); CX18_INFO("Version: %s Card: %s\n", CX18_VERSION, cx->card_name); if (cx->hw_flags & CX18_HW_TVEEPROM) { struct tveeprom tv; @@ -1120,8 +1118,6 @@ static int cx18_log_status(struct file *file, void *fh) CX18_INFO("Read MPEG/VBI: %lld/%lld bytes\n", (long long)cx->mpg_data_received, (long long)cx->vbi_data_inserted); - CX18_INFO("================== END STATUS CARD #%d " - "==================\n", cx->instance); return 0; } diff --git a/drivers/media/video/cx231xx/cx231xx-417.c b/drivers/media/video/cx231xx/cx231xx-417.c index f8f0e59cd583..d4327dab5a36 100644 --- a/drivers/media/video/cx231xx/cx231xx-417.c +++ b/drivers/media/video/cx231xx/cx231xx-417.c @@ -1686,7 +1686,6 @@ static struct v4l2_capability pvr_capability = { .capabilities = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | V4L2_CAP_AUDIO | V4L2_CAP_RADIO | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE), - .reserved = {0, 0, 0, 0} }; static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) diff --git a/drivers/media/video/cx231xx/cx231xx-cards.c b/drivers/media/video/cx231xx/cx231xx-cards.c index 875a7ce94736..8ed460d692e0 100644 --- a/drivers/media/video/cx231xx/cx231xx-cards.c +++ b/drivers/media/video/cx231xx/cx231xx-cards.c @@ -861,7 +861,6 @@ void cx231xx_release_resources(struct cx231xx *dev) kfree(dev->sliced_cc_mode.alt_max_pkt_size); kfree(dev->ts1_mode.alt_max_pkt_size); kfree(dev); - dev = NULL; } /* diff --git a/drivers/media/video/cx231xx/cx231xx-video.c b/drivers/media/video/cx231xx/cx231xx-video.c index 829a41b0c9ef..7f916f0685e9 100644 --- a/drivers/media/video/cx231xx/cx231xx-video.c +++ b/drivers/media/video/cx231xx/cx231xx-video.c @@ -2319,8 +2319,7 @@ static int cx231xx_v4l2_close(struct file *filp) if (dev->state & DEV_DISCONNECTED) { if (atomic_read(&dev->devlist_count) > 0) { cx231xx_release_resources(dev); - kfree(dev); - dev = NULL; + fh->dev = NULL; return 0; } return 0; @@ -2350,8 +2349,7 @@ static int cx231xx_v4l2_close(struct file *filp) free the remaining resources */ if (dev->state & DEV_DISCONNECTED) { cx231xx_release_resources(dev); - kfree(dev); - dev = NULL; + fh->dev = NULL; return 0; } diff --git a/drivers/media/video/cx25821/cx25821-core.c b/drivers/media/video/cx25821/cx25821-core.c index f617474f9073..7930ca58349f 100644 --- a/drivers/media/video/cx25821/cx25821-core.c +++ b/drivers/media/video/cx25821/cx25821-core.c @@ -1474,8 +1474,13 @@ static DEFINE_PCI_DEVICE_TABLE(cx25821_pci_tbl) = { .device = 0x8210, .subvendor = 0x14f1, .subdevice = 0x0920, - }, - { + }, { + /* CX25821 No Brand */ + .vendor = 0x14f1, + .device = 0x8210, + .subvendor = 0x0000, + .subdevice = 0x0000, + }, { /* --- end of list --- */ } }; diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/video/cx25840/cx25840-core.c index 05247d4c340a..fc1ff69cffd0 100644 --- a/drivers/media/video/cx25840/cx25840-core.c +++ b/drivers/media/video/cx25840/cx25840-core.c @@ -5301,15 +5301,4 @@ static struct i2c_driver cx25840_driver = { .id_table = cx25840_id, }; -static __init int init_cx25840(void) -{ - return i2c_add_driver(&cx25840_driver); -} - -static __exit void exit_cx25840(void) -{ - i2c_del_driver(&cx25840_driver); -} - -module_init(init_cx25840); -module_exit(exit_cx25840); +module_i2c_driver(cx25840_driver); diff --git a/drivers/media/video/davinci/vpif.h b/drivers/media/video/davinci/vpif.h index 25036cb11bea..8bcac65f9294 100644 --- a/drivers/media/video/davinci/vpif.h +++ b/drivers/media/video/davinci/vpif.h @@ -18,8 +18,6 @@ #include <linux/io.h> #include <linux/videodev2.h> -#include <mach/hardware.h> -#include <mach/dm646x.h> #include <media/davinci/vpif_types.h> /* Maximum channel allowed */ diff --git a/drivers/media/video/davinci/vpif_display.c b/drivers/media/video/davinci/vpif_display.c index 286f02910044..7fa34b4fae26 100644 --- a/drivers/media/video/davinci/vpif_display.c +++ b/drivers/media/video/davinci/vpif_display.c @@ -39,8 +39,6 @@ #include <media/v4l2-ioctl.h> #include <media/v4l2-chip-ident.h> -#include <mach/dm646x.h> - #include "vpif_display.h" #include "vpif.h" diff --git a/drivers/media/video/em28xx/em28xx-cards.c b/drivers/media/video/em28xx/em28xx-cards.c index 4561cd89938d..9fd8cc7dbb23 100644 --- a/drivers/media/video/em28xx/em28xx-cards.c +++ b/drivers/media/video/em28xx/em28xx-cards.c @@ -353,6 +353,44 @@ static struct em28xx_reg_seq hauppauge_930c_digital[] = { }; #endif +/* 1b80:e425 MaxMedia UB425-TC + * GPIO_6 - demod reset, 0=active + * GPIO_7 - LED, 0=active + */ +static struct em28xx_reg_seq maxmedia_ub425_tc[] = { + {EM2874_R80_GPIO, 0x83, 0xff, 100}, + {EM2874_R80_GPIO, 0xc3, 0xff, 100}, /* GPIO_6 = 1 */ + {EM2874_R80_GPIO, 0x43, 0xff, 000}, /* GPIO_7 = 0 */ + {-1, -1, -1, -1}, +}; + +/* 2304:0242 PCTV QuatroStick (510e) + * GPIO_2: decoder reset, 0=active + * GPIO_4: decoder suspend, 0=active + * GPIO_6: demod reset, 0=active + * GPIO_7: LED, 1=active + */ +static struct em28xx_reg_seq pctv_510e[] = { + {EM2874_R80_GPIO, 0x10, 0xff, 100}, + {EM2874_R80_GPIO, 0x14, 0xff, 100}, /* GPIO_2 = 1 */ + {EM2874_R80_GPIO, 0x54, 0xff, 050}, /* GPIO_6 = 1 */ + { -1, -1, -1, -1}, +}; + +/* 2013:0251 PCTV QuatroStick nano (520e) + * GPIO_2: decoder reset, 0=active + * GPIO_4: decoder suspend, 0=active + * GPIO_6: demod reset, 0=active + * GPIO_7: LED, 1=active + */ +static struct em28xx_reg_seq pctv_520e[] = { + {EM2874_R80_GPIO, 0x10, 0xff, 100}, + {EM2874_R80_GPIO, 0x14, 0xff, 100}, /* GPIO_2 = 1 */ + {EM2874_R80_GPIO, 0x54, 0xff, 050}, /* GPIO_6 = 1 */ + {EM2874_R80_GPIO, 0xd4, 0xff, 000}, /* GPIO_7 = 1 */ + { -1, -1, -1, -1}, +}; + /* * Board definitions */ @@ -1908,6 +1946,41 @@ struct em28xx_board em28xx_boards[] = { .amux = EM28XX_AMUX_LINE_IN, } }, }, + /* 1b80:e425 MaxMedia UB425-TC + * Empia EM2874B + Micronas DRX 3913KA2 + NXP TDA18271HDC2 */ + [EM2874_BOARD_MAXMEDIA_UB425_TC] = { + .name = "MaxMedia UB425-TC", + .tuner_type = TUNER_ABSENT, + .tuner_gpio = maxmedia_ub425_tc, + .has_dvb = 1, + .i2c_speed = EM2874_I2C_SECONDARY_BUS_SELECT | + EM28XX_I2C_CLK_WAIT_ENABLE | + EM28XX_I2C_FREQ_400_KHZ, + }, + /* 2304:0242 PCTV QuatroStick (510e) + * Empia EM2884 + Micronas DRX 3926K + NXP TDA18271HDC2 */ + [EM2884_BOARD_PCTV_510E] = { + .name = "PCTV QuatroStick (510e)", + .tuner_type = TUNER_ABSENT, + .tuner_gpio = pctv_510e, + .has_dvb = 1, + .ir_codes = RC_MAP_PINNACLE_PCTV_HD, + .i2c_speed = EM2874_I2C_SECONDARY_BUS_SELECT | + EM28XX_I2C_CLK_WAIT_ENABLE | + EM28XX_I2C_FREQ_400_KHZ, + }, + /* 2013:0251 PCTV QuatroStick nano (520e) + * Empia EM2884 + Micronas DRX 3926K + NXP TDA18271HDC2 */ + [EM2884_BOARD_PCTV_520E] = { + .name = "PCTV QuatroStick nano (520e)", + .tuner_type = TUNER_ABSENT, + .tuner_gpio = pctv_520e, + .has_dvb = 1, + .ir_codes = RC_MAP_PINNACLE_PCTV_HD, + .i2c_speed = EM2874_I2C_SECONDARY_BUS_SELECT | + EM28XX_I2C_CLK_WAIT_ENABLE | + EM28XX_I2C_FREQ_400_KHZ, + }, }; const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards); @@ -2059,6 +2132,12 @@ struct usb_device_id em28xx_id_table[] = { .driver_info = EM2860_BOARD_HT_VIDBOX_NW03 }, { USB_DEVICE(0x1b80, 0xe309), /* Sveon STV40 */ .driver_info = EM2860_BOARD_EASYCAP }, + { USB_DEVICE(0x1b80, 0xe425), + .driver_info = EM2874_BOARD_MAXMEDIA_UB425_TC }, + { USB_DEVICE(0x2304, 0x0242), + .driver_info = EM2884_BOARD_PCTV_510E }, + { USB_DEVICE(0x2013, 0x0251), + .driver_info = EM2884_BOARD_PCTV_520E }, { }, }; MODULE_DEVICE_TABLE(usb, em28xx_id_table); @@ -3122,7 +3201,6 @@ static int em28xx_usb_probe(struct usb_interface *interface, int i, nr; const int ifnum = interface->altsetting[0].desc.bInterfaceNumber; char *speed; - char descr[255] = ""; udev = usb_get_dev(interface_to_usbdev(interface)); @@ -3227,21 +3305,11 @@ static int em28xx_usb_probe(struct usb_interface *interface, speed = "unknown"; } - if (udev->manufacturer) - strlcpy(descr, udev->manufacturer, sizeof(descr)); - - if (udev->product) { - if (*descr) - strlcat(descr, " ", sizeof(descr)); - strlcat(descr, udev->product, sizeof(descr)); - } - - if (*descr) - strlcat(descr, " ", sizeof(descr)); - printk(KERN_INFO DRIVER_NAME - ": New device %s@ %s Mbps (%04x:%04x, interface %d, class %d)\n", - descr, + ": New device %s %s @ %s Mbps " + "(%04x:%04x, interface %d, class %d)\n", + udev->manufacturer ? udev->manufacturer : "", + udev->product ? udev->product : "", speed, le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct), @@ -3307,6 +3375,17 @@ static int em28xx_usb_probe(struct usb_interface *interface, goto unlock_and_free; } + if (has_dvb) { + /* pre-allocate DVB isoc transfer buffers */ + retval = em28xx_alloc_isoc(dev, EM28XX_DIGITAL_MODE, + EM28XX_DVB_MAX_PACKETS, + EM28XX_DVB_NUM_BUFS, + dev->dvb_max_pkt_size); + if (retval) { + goto unlock_and_free; + } + } + request_modules(dev); /* Should be the last thing to do, to avoid newer udev's to @@ -3379,7 +3458,7 @@ static void em28xx_usb_disconnect(struct usb_interface *interface) video_device_node_name(dev->vdev)); dev->state |= DEV_MISCONFIGURED; - em28xx_uninit_isoc(dev); + em28xx_uninit_isoc(dev, dev->mode); dev->state |= DEV_DISCONNECTED; wake_up_interruptible(&dev->wait_frame); wake_up_interruptible(&dev->wait_stream); @@ -3388,6 +3467,9 @@ static void em28xx_usb_disconnect(struct usb_interface *interface) em28xx_release_resources(dev); } + /* free DVB isoc buffers */ + em28xx_uninit_isoc(dev, EM28XX_DIGITAL_MODE); + mutex_unlock(&dev->lock); em28xx_close_extension(dev); diff --git a/drivers/media/video/em28xx/em28xx-core.c b/drivers/media/video/em28xx/em28xx-core.c index 0aacc96f9a23..53a9fb91e97e 100644 --- a/drivers/media/video/em28xx/em28xx-core.c +++ b/drivers/media/video/em28xx/em28xx-core.c @@ -666,6 +666,7 @@ int em28xx_capture_start(struct em28xx *dev, int start) return rc; } +EXPORT_SYMBOL_GPL(em28xx_capture_start); int em28xx_vbi_supported(struct em28xx *dev) { @@ -961,146 +962,192 @@ static void em28xx_irq_callback(struct urb *urb) /* * Stop and Deallocate URBs */ -void em28xx_uninit_isoc(struct em28xx *dev) +void em28xx_uninit_isoc(struct em28xx *dev, enum em28xx_mode mode) { struct urb *urb; + struct em28xx_usb_isoc_bufs *isoc_bufs; int i; - em28xx_isocdbg("em28xx: called em28xx_uninit_isoc\n"); + em28xx_isocdbg("em28xx: called em28xx_uninit_isoc in mode %d\n", mode); + + if (mode == EM28XX_DIGITAL_MODE) + isoc_bufs = &dev->isoc_ctl.digital_bufs; + else + isoc_bufs = &dev->isoc_ctl.analog_bufs; dev->isoc_ctl.nfields = -1; - for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { - urb = dev->isoc_ctl.urb[i]; + for (i = 0; i < isoc_bufs->num_bufs; i++) { + urb = isoc_bufs->urb[i]; if (urb) { if (!irqs_disabled()) usb_kill_urb(urb); else usb_unlink_urb(urb); - if (dev->isoc_ctl.transfer_buffer[i]) { + if (isoc_bufs->transfer_buffer[i]) { usb_free_coherent(dev->udev, urb->transfer_buffer_length, - dev->isoc_ctl.transfer_buffer[i], + isoc_bufs->transfer_buffer[i], urb->transfer_dma); } usb_free_urb(urb); - dev->isoc_ctl.urb[i] = NULL; + isoc_bufs->urb[i] = NULL; } - dev->isoc_ctl.transfer_buffer[i] = NULL; + isoc_bufs->transfer_buffer[i] = NULL; } - kfree(dev->isoc_ctl.urb); - kfree(dev->isoc_ctl.transfer_buffer); + kfree(isoc_bufs->urb); + kfree(isoc_bufs->transfer_buffer); - dev->isoc_ctl.urb = NULL; - dev->isoc_ctl.transfer_buffer = NULL; - dev->isoc_ctl.num_bufs = 0; + isoc_bufs->urb = NULL; + isoc_bufs->transfer_buffer = NULL; + isoc_bufs->num_bufs = 0; em28xx_capture_start(dev, 0); } EXPORT_SYMBOL_GPL(em28xx_uninit_isoc); /* - * Allocate URBs and start IRQ + * Allocate URBs */ -int em28xx_init_isoc(struct em28xx *dev, int max_packets, - int num_bufs, int max_pkt_size, - int (*isoc_copy) (struct em28xx *dev, struct urb *urb)) +int em28xx_alloc_isoc(struct em28xx *dev, enum em28xx_mode mode, + int max_packets, int num_bufs, int max_pkt_size) { - struct em28xx_dmaqueue *dma_q = &dev->vidq; - struct em28xx_dmaqueue *vbi_dma_q = &dev->vbiq; + struct em28xx_usb_isoc_bufs *isoc_bufs; int i; int sb_size, pipe; struct urb *urb; int j, k; - int rc; - em28xx_isocdbg("em28xx: called em28xx_prepare_isoc\n"); + em28xx_isocdbg("em28xx: called em28xx_alloc_isoc in mode %d\n", mode); + + if (mode == EM28XX_DIGITAL_MODE) + isoc_bufs = &dev->isoc_ctl.digital_bufs; + else + isoc_bufs = &dev->isoc_ctl.analog_bufs; /* De-allocates all pending stuff */ - em28xx_uninit_isoc(dev); + em28xx_uninit_isoc(dev, mode); - dev->isoc_ctl.isoc_copy = isoc_copy; - dev->isoc_ctl.num_bufs = num_bufs; + isoc_bufs->num_bufs = num_bufs; - dev->isoc_ctl.urb = kzalloc(sizeof(void *)*num_bufs, GFP_KERNEL); - if (!dev->isoc_ctl.urb) { + isoc_bufs->urb = kzalloc(sizeof(void *)*num_bufs, GFP_KERNEL); + if (!isoc_bufs->urb) { em28xx_errdev("cannot alloc memory for usb buffers\n"); return -ENOMEM; } - dev->isoc_ctl.transfer_buffer = kzalloc(sizeof(void *)*num_bufs, - GFP_KERNEL); - if (!dev->isoc_ctl.transfer_buffer) { + isoc_bufs->transfer_buffer = kzalloc(sizeof(void *)*num_bufs, + GFP_KERNEL); + if (!isoc_bufs->transfer_buffer) { em28xx_errdev("cannot allocate memory for usb transfer\n"); - kfree(dev->isoc_ctl.urb); + kfree(isoc_bufs->urb); return -ENOMEM; } - dev->isoc_ctl.max_pkt_size = max_pkt_size; + isoc_bufs->max_pkt_size = max_pkt_size; + isoc_bufs->num_packets = max_packets; dev->isoc_ctl.vid_buf = NULL; dev->isoc_ctl.vbi_buf = NULL; - sb_size = max_packets * dev->isoc_ctl.max_pkt_size; + sb_size = isoc_bufs->num_packets * isoc_bufs->max_pkt_size; /* allocate urbs and transfer buffers */ - for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { - urb = usb_alloc_urb(max_packets, GFP_KERNEL); + for (i = 0; i < isoc_bufs->num_bufs; i++) { + urb = usb_alloc_urb(isoc_bufs->num_packets, GFP_KERNEL); if (!urb) { em28xx_err("cannot alloc isoc_ctl.urb %i\n", i); - em28xx_uninit_isoc(dev); + em28xx_uninit_isoc(dev, mode); return -ENOMEM; } - dev->isoc_ctl.urb[i] = urb; + isoc_bufs->urb[i] = urb; - dev->isoc_ctl.transfer_buffer[i] = usb_alloc_coherent(dev->udev, + isoc_bufs->transfer_buffer[i] = usb_alloc_coherent(dev->udev, sb_size, GFP_KERNEL, &urb->transfer_dma); - if (!dev->isoc_ctl.transfer_buffer[i]) { + if (!isoc_bufs->transfer_buffer[i]) { em28xx_err("unable to allocate %i bytes for transfer" " buffer %i%s\n", sb_size, i, in_interrupt() ? " while in int" : ""); - em28xx_uninit_isoc(dev); + em28xx_uninit_isoc(dev, mode); return -ENOMEM; } - memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size); + memset(isoc_bufs->transfer_buffer[i], 0, sb_size); /* FIXME: this is a hack - should be 'desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK' should also be using 'desc.bInterval' */ pipe = usb_rcvisocpipe(dev->udev, - dev->mode == EM28XX_ANALOG_MODE ? + mode == EM28XX_ANALOG_MODE ? EM28XX_EP_ANALOG : EM28XX_EP_DIGITAL); usb_fill_int_urb(urb, dev->udev, pipe, - dev->isoc_ctl.transfer_buffer[i], sb_size, + isoc_bufs->transfer_buffer[i], sb_size, em28xx_irq_callback, dev, 1); - urb->number_of_packets = max_packets; + urb->number_of_packets = isoc_bufs->num_packets; urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; k = 0; - for (j = 0; j < max_packets; j++) { + for (j = 0; j < isoc_bufs->num_packets; j++) { urb->iso_frame_desc[j].offset = k; urb->iso_frame_desc[j].length = - dev->isoc_ctl.max_pkt_size; - k += dev->isoc_ctl.max_pkt_size; + isoc_bufs->max_pkt_size; + k += isoc_bufs->max_pkt_size; } } + return 0; +} +EXPORT_SYMBOL_GPL(em28xx_alloc_isoc); + +/* + * Allocate URBs and start IRQ + */ +int em28xx_init_isoc(struct em28xx *dev, enum em28xx_mode mode, + int max_packets, int num_bufs, int max_pkt_size, + int (*isoc_copy) (struct em28xx *dev, struct urb *urb)) +{ + struct em28xx_dmaqueue *dma_q = &dev->vidq; + struct em28xx_dmaqueue *vbi_dma_q = &dev->vbiq; + struct em28xx_usb_isoc_bufs *isoc_bufs; + int i; + int rc; + int alloc; + + em28xx_isocdbg("em28xx: called em28xx_init_isoc in mode %d\n", mode); + + dev->isoc_ctl.isoc_copy = isoc_copy; + + if (mode == EM28XX_DIGITAL_MODE) { + isoc_bufs = &dev->isoc_ctl.digital_bufs; + /* no need to free/alloc isoc buffers in digital mode */ + alloc = 0; + } else { + isoc_bufs = &dev->isoc_ctl.analog_bufs; + alloc = 1; + } + + if (alloc) { + rc = em28xx_alloc_isoc(dev, mode, max_packets, + num_bufs, max_pkt_size); + if (rc) + return rc; + } + init_waitqueue_head(&dma_q->wq); init_waitqueue_head(&vbi_dma_q->wq); em28xx_capture_start(dev, 1); /* submit urbs and enables IRQ */ - for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { - rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC); + for (i = 0; i < isoc_bufs->num_bufs; i++) { + rc = usb_submit_urb(isoc_bufs->urb[i], GFP_ATOMIC); if (rc) { em28xx_err("submit of urb %i failed (error=%i)\n", i, rc); - em28xx_uninit_isoc(dev); + em28xx_uninit_isoc(dev, mode); return rc; } } diff --git a/drivers/media/video/em28xx/em28xx-dvb.c b/drivers/media/video/em28xx/em28xx-dvb.c index aabbf4854f66..503a8d5b5382 100644 --- a/drivers/media/video/em28xx/em28xx-dvb.c +++ b/drivers/media/video/em28xx/em28xx-dvb.c @@ -61,9 +61,6 @@ if (debug >= level) \ printk(KERN_DEBUG "%s/2-dvb: " fmt, dev->name, ## arg); \ } while (0) -#define EM28XX_DVB_NUM_BUFS 5 -#define EM28XX_DVB_MAX_PACKETS 64 - struct em28xx_dvb { struct dvb_frontend *fe[2]; @@ -172,20 +169,21 @@ static int em28xx_start_streaming(struct em28xx_dvb *dvb) max_dvb_packet_size = dev->dvb_max_pkt_size; if (max_dvb_packet_size < 0) return max_dvb_packet_size; - dprintk(1, "Using %d buffers each with %d bytes\n", + dprintk(1, "Using %d buffers each with %d x %d bytes\n", EM28XX_DVB_NUM_BUFS, + EM28XX_DVB_MAX_PACKETS, max_dvb_packet_size); - return em28xx_init_isoc(dev, EM28XX_DVB_MAX_PACKETS, - EM28XX_DVB_NUM_BUFS, max_dvb_packet_size, - em28xx_dvb_isoc_copy); + return em28xx_init_isoc(dev, EM28XX_DIGITAL_MODE, + EM28XX_DVB_MAX_PACKETS, EM28XX_DVB_NUM_BUFS, + max_dvb_packet_size, em28xx_dvb_isoc_copy); } static int em28xx_stop_streaming(struct em28xx_dvb *dvb) { struct em28xx *dev = dvb->adapter.priv; - em28xx_uninit_isoc(dev); + em28xx_capture_start(dev, 0); em28xx_set_mode(dev, EM28XX_SUSPEND); @@ -327,6 +325,19 @@ struct drxk_config hauppauge_930c_drxk = { .chunk_size = 56, }; +struct drxk_config maxmedia_ub425_tc_drxk = { + .adr = 0x29, + .single_master = 1, + .no_i2c_bridge = 1, +}; + +struct drxk_config pctv_520e_drxk = { + .adr = 0x29, + .single_master = 1, + .microcode_name = "dvb-demod-drxk-pctv.fw", + .chunk_size = 58, +}; + static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable) { struct em28xx_dvb *dvb = fe->sec_priv; @@ -460,6 +471,33 @@ static void terratec_h5_init(struct em28xx *dev) em28xx_gpio_set(dev, terratec_h5_end); }; +static void pctv_520e_init(struct em28xx *dev) +{ + /* + * Init TDA8295(?) analog demodulator. Looks like I2C traffic to + * digital demodulator and tuner are routed via TDA8295. + */ + int i; + struct { + unsigned char r[4]; + int len; + } regs[] = { + {{ 0x06, 0x02, 0x00, 0x31 }, 4}, + {{ 0x01, 0x02 }, 2}, + {{ 0x01, 0x02, 0x00, 0xc6 }, 4}, + {{ 0x01, 0x00 }, 2}, + {{ 0x01, 0x00, 0xff, 0xaf }, 4}, + {{ 0x01, 0x00, 0x03, 0xa0 }, 4}, + {{ 0x01, 0x00 }, 2}, + {{ 0x01, 0x00, 0x73, 0xaf }, 4}, + }; + + dev->i2c_client.addr = 0x82 >> 1; /* 0x41 */ + + for (i = 0; i < ARRAY_SIZE(regs); i++) + i2c_master_send(&dev->i2c_client, regs[i].r, regs[i].len); +}; + static int em28xx_mt352_terratec_xs_init(struct dvb_frontend *fe) { /* Values extracted from a USB trace of the Terratec Windows driver */ @@ -938,6 +976,48 @@ static int em28xx_dvb_init(struct em28xx *dev) dvb_attach(a8293_attach, dvb->fe[0], &dev->i2c_adap, &em28xx_a8293_config); break; + case EM2874_BOARD_MAXMEDIA_UB425_TC: + /* attach demodulator */ + dvb->fe[0] = dvb_attach(drxk_attach, &maxmedia_ub425_tc_drxk, + &dev->i2c_adap); + + if (dvb->fe[0]) { + /* disable I2C-gate */ + dvb->fe[0]->ops.i2c_gate_ctrl = NULL; + + /* attach tuner */ + if (!dvb_attach(tda18271c2dd_attach, dvb->fe[0], + &dev->i2c_adap, 0x60)) { + dvb_frontend_detach(dvb->fe[0]); + result = -EINVAL; + goto out_free; + } + } + + /* TODO: we need drx-3913k firmware in order to support DVB-T */ + em28xx_info("MaxMedia UB425-TC: only DVB-C supported by that " \ + "driver version\n"); + + break; + case EM2884_BOARD_PCTV_510E: + case EM2884_BOARD_PCTV_520E: + pctv_520e_init(dev); + + /* attach demodulator */ + dvb->fe[0] = dvb_attach(drxk_attach, &pctv_520e_drxk, + &dev->i2c_adap); + + if (dvb->fe[0]) { + /* attach tuner */ + if (!dvb_attach(tda18271_attach, dvb->fe[0], 0x60, + &dev->i2c_adap, + &em28xx_cxd2820r_tda18271_config)) { + dvb_frontend_detach(dvb->fe[0]); + result = -EINVAL; + goto out_free; + } + } + break; default: em28xx_errdev("/2: The frontend of your DVB/ATSC card" " isn't supported yet\n"); diff --git a/drivers/media/video/em28xx/em28xx-i2c.c b/drivers/media/video/em28xx/em28xx-i2c.c index 36f5a9bc8b76..a88e169dba23 100644 --- a/drivers/media/video/em28xx/em28xx-i2c.c +++ b/drivers/media/video/em28xx/em28xx-i2c.c @@ -41,14 +41,6 @@ static unsigned int i2c_debug; module_param(i2c_debug, int, 0644); MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); - -#define dprintk1(lvl, fmt, args...) \ -do { \ - if (i2c_debug >= lvl) { \ - printk(fmt, ##args); \ - } \ -} while (0) - #define dprintk2(lvl, fmt, args...) \ do { \ if (i2c_debug >= lvl) { \ diff --git a/drivers/media/video/em28xx/em28xx-video.c b/drivers/media/video/em28xx/em28xx-video.c index 613300b51a9e..324b695c0724 100644 --- a/drivers/media/video/em28xx/em28xx-video.c +++ b/drivers/media/video/em28xx/em28xx-video.c @@ -760,17 +760,19 @@ buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, goto fail; } - if (!dev->isoc_ctl.num_bufs) + if (!dev->isoc_ctl.analog_bufs.num_bufs) urb_init = 1; if (urb_init) { if (em28xx_vbi_supported(dev) == 1) - rc = em28xx_init_isoc(dev, EM28XX_NUM_PACKETS, + rc = em28xx_init_isoc(dev, EM28XX_ANALOG_MODE, + EM28XX_NUM_PACKETS, EM28XX_NUM_BUFS, dev->max_pkt_size, em28xx_isoc_copy_vbi); else - rc = em28xx_init_isoc(dev, EM28XX_NUM_PACKETS, + rc = em28xx_init_isoc(dev, EM28XX_ANALOG_MODE, + EM28XX_NUM_PACKETS, EM28XX_NUM_BUFS, dev->max_pkt_size, em28xx_isoc_copy); @@ -2267,7 +2269,7 @@ static int em28xx_v4l2_close(struct file *filp) v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0); /* do this before setting alternate! */ - em28xx_uninit_isoc(dev); + em28xx_uninit_isoc(dev, EM28XX_ANALOG_MODE); em28xx_set_mode(dev, EM28XX_SUSPEND); /* set alternate 0 */ diff --git a/drivers/media/video/em28xx/em28xx.h b/drivers/media/video/em28xx/em28xx.h index 22e252bcc41e..2868b19f8b54 100644 --- a/drivers/media/video/em28xx/em28xx.h +++ b/drivers/media/video/em28xx/em28xx.h @@ -125,6 +125,9 @@ #define EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C 81 #define EM2884_BOARD_CINERGY_HTC_STICK 82 #define EM2860_BOARD_HT_VIDBOX_NW03 83 +#define EM2874_BOARD_MAXMEDIA_UB425_TC 84 +#define EM2884_BOARD_PCTV_510E 85 +#define EM2884_BOARD_PCTV_520E 86 /* Limits minimum and default number of buffers */ #define EM28XX_MIN_BUF 4 @@ -151,12 +154,14 @@ /* number of buffers for isoc transfers */ #define EM28XX_NUM_BUFS 5 +#define EM28XX_DVB_NUM_BUFS 5 /* number of packets for each buffer windows requests only 64 packets .. so we better do the same this is what I found out for all alternate numbers there! */ #define EM28XX_NUM_PACKETS 64 +#define EM28XX_DVB_MAX_PACKETS 64 #define EM28XX_INTERLACED_DEFAULT 1 @@ -197,10 +202,13 @@ enum em28xx_mode { struct em28xx; -struct em28xx_usb_isoc_ctl { +struct em28xx_usb_isoc_bufs { /* max packet size of isoc transaction */ int max_pkt_size; + /* number of packets in each buffer */ + int num_packets; + /* number of allocated urbs */ int num_bufs; @@ -209,6 +217,14 @@ struct em28xx_usb_isoc_ctl { /* transfer buffers for isoc transfer */ char **transfer_buffer; +}; + +struct em28xx_usb_isoc_ctl { + /* isoc transfer buffers for analog mode */ + struct em28xx_usb_isoc_bufs analog_bufs; + + /* isoc transfer buffers for digital mode */ + struct em28xx_usb_isoc_bufs digital_bufs; /* Last buffer command and region */ u8 cmd; @@ -600,9 +616,6 @@ struct em28xx { unsigned int *alt_max_pkt_size; /* array of wMaxPacketSize */ int dvb_alt; /* alternate for DVB */ unsigned int dvb_max_pkt_size; /* wMaxPacketSize for DVB */ - struct urb *urb[EM28XX_NUM_BUFS]; /* urb for isoc transfers */ - char *transfer_buffer[EM28XX_NUM_BUFS]; /* transfer buffers for isoc - transfer */ char urb_buf[URB_MAX_CTRL_SIZE]; /* urb control msg buffer */ /* helper funcs that call usb_control_msg */ @@ -676,10 +689,12 @@ int em28xx_vbi_supported(struct em28xx *dev); int em28xx_set_outfmt(struct em28xx *dev); int em28xx_resolution_set(struct em28xx *dev); int em28xx_set_alternate(struct em28xx *dev); -int em28xx_init_isoc(struct em28xx *dev, int max_packets, - int num_bufs, int max_pkt_size, +int em28xx_alloc_isoc(struct em28xx *dev, enum em28xx_mode mode, + int max_packets, int num_bufs, int max_pkt_size); +int em28xx_init_isoc(struct em28xx *dev, enum em28xx_mode mode, + int max_packets, int num_bufs, int max_pkt_size, int (*isoc_copy) (struct em28xx *dev, struct urb *urb)); -void em28xx_uninit_isoc(struct em28xx *dev); +void em28xx_uninit_isoc(struct em28xx *dev, enum em28xx_mode mode); int em28xx_isoc_dvb_max_packetsize(struct em28xx *dev); int em28xx_set_mode(struct em28xx *dev, enum em28xx_mode set_mode); int em28xx_gpio_set(struct em28xx *dev, struct em28xx_reg_seq *gpio); diff --git a/drivers/media/video/gspca/gl860/Makefile b/drivers/media/video/gspca/gl860/Makefile index f511eccdfd9c..773ea3426561 100644 --- a/drivers/media/video/gspca/gl860/Makefile +++ b/drivers/media/video/gspca/gl860/Makefile @@ -6,5 +6,5 @@ gspca_gl860-objs := gl860.o \ gl860-ov9655.o \ gl860-mi2020.o -ccflags-y += -Idrivers/media/video/gspca +ccflags-y += -I$(srctree)/drivers/media/video/gspca diff --git a/drivers/media/video/gspca/m5602/Makefile b/drivers/media/video/gspca/m5602/Makefile index 7f52961f439c..575b75bacb62 100644 --- a/drivers/media/video/gspca/m5602/Makefile +++ b/drivers/media/video/gspca/m5602/Makefile @@ -8,4 +8,4 @@ gspca_m5602-objs := m5602_core.o \ m5602_s5k83a.o \ m5602_s5k4aa.o -ccflags-y += -Idrivers/media/video/gspca +ccflags-y += -I$(srctree)/drivers/media/video/gspca diff --git a/drivers/media/video/gspca/ov534_9.c b/drivers/media/video/gspca/ov534_9.c index fbfa02affa13..e6601b886032 100644 --- a/drivers/media/video/gspca/ov534_9.c +++ b/drivers/media/video/gspca/ov534_9.c @@ -1107,16 +1107,34 @@ static void setbrightness(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; u8 val; + s8 sval; if (gspca_dev->ctrl_dis & (1 << BRIGHTNESS)) return; - val = sd->ctrls[BRIGHTNESS].val; - if (val < 8) - val = 15 - val; /* f .. 8 */ - else - val = val - 8; /* 0 .. 7 */ - sccb_write(gspca_dev, 0x55, /* brtn - brightness adjustment */ - 0x0f | (val << 4)); + if (sd->sensor == SENSOR_OV562x) { + sval = sd->ctrls[BRIGHTNESS].val; + val = 0x76; + val += sval; + sccb_write(gspca_dev, 0x24, val); + val = 0x6a; + val += sval; + sccb_write(gspca_dev, 0x25, val); + if (sval < -40) + val = 0x71; + else if (sval < 20) + val = 0x94; + else + val = 0xe6; + sccb_write(gspca_dev, 0x26, val); + } else { + val = sd->ctrls[BRIGHTNESS].val; + if (val < 8) + val = 15 - val; /* f .. 8 */ + else + val = val - 8; /* 0 .. 7 */ + sccb_write(gspca_dev, 0x55, /* brtn - brightness adjustment */ + 0x0f | (val << 4)); + } } static void setcontrast(struct gspca_dev *gspca_dev) @@ -1339,7 +1357,16 @@ static int sd_init(struct gspca_dev *gspca_dev) reg_w(gspca_dev, 0x56, 0x17); } else if ((sensor_id & 0xfff0) == 0x5620) { sd->sensor = SENSOR_OV562x; - + gspca_dev->ctrl_dis = (1 << CONTRAST) | + (1 << AUTOGAIN) | + (1 << EXPOSURE) | + (1 << SHARPNESS) | + (1 << SATUR) | + (1 << LIGHTFREQ); + + sd->ctrls[BRIGHTNESS].min = -90; + sd->ctrls[BRIGHTNESS].max = 90; + sd->ctrls[BRIGHTNESS].def = 0; gspca_dev->cam.cam_mode = ov562x_mode; gspca_dev->cam.nmodes = ARRAY_SIZE(ov562x_mode); @@ -1360,8 +1387,12 @@ static int sd_start(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - if (sd->sensor == SENSOR_OV971x || sd->sensor == SENSOR_OV562x) + if (sd->sensor == SENSOR_OV971x) return gspca_dev->usb_err; + else if (sd->sensor == SENSOR_OV562x) { + setbrightness(gspca_dev); + return gspca_dev->usb_err; + } switch (gspca_dev->curr_mode) { case QVGA_MODE: /* 320x240 */ sccb_w_array(gspca_dev, ov965x_start_1_vga, diff --git a/drivers/media/video/gspca/pac7302.c b/drivers/media/video/gspca/pac7302.c index 9db2b34d172c..30662fccb0cf 100644 --- a/drivers/media/video/gspca/pac7302.c +++ b/drivers/media/video/gspca/pac7302.c @@ -1,8 +1,8 @@ /* - * Pixart PAC7302 library - * Copyright (C) 2005 Thomas Kaiser thomas@kaiser-linux.li + * Pixart PAC7302 driver * - * V4L2 by Jean-Francois Moine <http://moinejf.free.fr> + * Copyright (C) 2008-2012 Jean-Francois Moine <http://moinejf.free.fr> + * Copyright (C) 2005 Thomas Kaiser thomas@kaiser-linux.li * * Separated from Pixart PAC7311 library by Márton Németh * Camera button input handling by Márton Németh <nm127@freemail.hu> @@ -63,67 +63,61 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define MODULE_NAME "pac7302" - #include <linux/input.h> #include <media/v4l2-chip-ident.h> #include "gspca.h" +/* Include pac common sof detection functions */ +#include "pac_common.h" -MODULE_AUTHOR("Thomas Kaiser thomas@kaiser-linux.li"); +MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>, " + "Thomas Kaiser thomas@kaiser-linux.li"); MODULE_DESCRIPTION("Pixart PAC7302"); MODULE_LICENSE("GPL"); +enum e_ctrl { + BRIGHTNESS, + CONTRAST, + COLORS, + WHITE_BALANCE, + RED_BALANCE, + BLUE_BALANCE, + GAIN, + AUTOGAIN, + EXPOSURE, + VFLIP, + HFLIP, + NCTRLS /* number of controls */ +}; + /* specific webcam descriptor for pac7302 */ struct sd { struct gspca_dev gspca_dev; /* !! must be the first item */ - unsigned char brightness; - unsigned char contrast; - unsigned char colors; - unsigned char white_balance; - unsigned char red_balance; - unsigned char blue_balance; - unsigned char gain; - unsigned char autogain; - unsigned short exposure; - __u8 hflip; - __u8 vflip; + struct gspca_ctrl ctrls[NCTRLS]; + u8 flags; #define FL_HFLIP 0x01 /* mirrored by default */ #define FL_VFLIP 0x02 /* vertical flipped by default */ u8 sof_read; - u8 autogain_ignore_frames; + s8 autogain_ignore_frames; atomic_t avg_lum; }; /* V4L2 controls supported by the driver */ -static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setcolors(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getcolors(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setwhitebalance(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getwhitebalance(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setredbalance(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getredbalance(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setbluebalance(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getbluebalance(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getautogain(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_sethflip(struct gspca_dev *gspca_dev, __s32 val); -static int sd_gethflip(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setvflip(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getvflip(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getgain(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val); +static void setbrightcont(struct gspca_dev *gspca_dev); +static void setcolors(struct gspca_dev *gspca_dev); +static void setwhitebalance(struct gspca_dev *gspca_dev); +static void setredbalance(struct gspca_dev *gspca_dev); +static void setbluebalance(struct gspca_dev *gspca_dev); +static void setgain(struct gspca_dev *gspca_dev); +static void setexposure(struct gspca_dev *gspca_dev); +static void setautogain(struct gspca_dev *gspca_dev); +static void sethvflip(struct gspca_dev *gspca_dev); static const struct ctrl sd_ctrls[] = { - { +[BRIGHTNESS] = { { .id = V4L2_CID_BRIGHTNESS, .type = V4L2_CTRL_TYPE_INTEGER, @@ -132,13 +126,11 @@ static const struct ctrl sd_ctrls[] = { #define BRIGHTNESS_MAX 0x20 .maximum = BRIGHTNESS_MAX, .step = 1, -#define BRIGHTNESS_DEF 0x10 - .default_value = BRIGHTNESS_DEF, + .default_value = 0x10, }, - .set = sd_setbrightness, - .get = sd_getbrightness, + .set_control = setbrightcont }, - { +[CONTRAST] = { { .id = V4L2_CID_CONTRAST, .type = V4L2_CTRL_TYPE_INTEGER, @@ -147,13 +139,11 @@ static const struct ctrl sd_ctrls[] = { #define CONTRAST_MAX 255 .maximum = CONTRAST_MAX, .step = 1, -#define CONTRAST_DEF 127 - .default_value = CONTRAST_DEF, + .default_value = 127, }, - .set = sd_setcontrast, - .get = sd_getcontrast, + .set_control = setbrightcont }, - { +[COLORS] = { { .id = V4L2_CID_SATURATION, .type = V4L2_CTRL_TYPE_INTEGER, @@ -162,13 +152,11 @@ static const struct ctrl sd_ctrls[] = { #define COLOR_MAX 255 .maximum = COLOR_MAX, .step = 1, -#define COLOR_DEF 127 - .default_value = COLOR_DEF, + .default_value = 127 }, - .set = sd_setcolors, - .get = sd_getcolors, + .set_control = setcolors }, - { +[WHITE_BALANCE] = { { .id = V4L2_CID_WHITE_BALANCE_TEMPERATURE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -176,13 +164,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 255, .step = 1, -#define WHITEBALANCE_DEF 4 - .default_value = WHITEBALANCE_DEF, + .default_value = 4, }, - .set = sd_setwhitebalance, - .get = sd_getwhitebalance, + .set_control = setwhitebalance }, - { +[RED_BALANCE] = { { .id = V4L2_CID_RED_BALANCE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -190,13 +176,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 3, .step = 1, -#define REDBALANCE_DEF 1 - .default_value = REDBALANCE_DEF, + .default_value = 1, }, - .set = sd_setredbalance, - .get = sd_getredbalance, + .set_control = setredbalance }, - { +[BLUE_BALANCE] = { { .id = V4L2_CID_BLUE_BALANCE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -204,29 +188,25 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 3, .step = 1, -#define BLUEBALANCE_DEF 1 - .default_value = BLUEBALANCE_DEF, + .default_value = 1, }, - .set = sd_setbluebalance, - .get = sd_getbluebalance, + .set_control = setbluebalance }, - { +[GAIN] = { { .id = V4L2_CID_GAIN, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Gain", .minimum = 0, -#define GAIN_MAX 255 - .maximum = GAIN_MAX, + .maximum = 255, .step = 1, #define GAIN_DEF 127 #define GAIN_KNEE 255 /* Gain seems to cause little noise on the pac73xx */ .default_value = GAIN_DEF, }, - .set = sd_setgain, - .get = sd_getgain, + .set_control = setgain }, - { +[EXPOSURE] = { { .id = V4L2_CID_EXPOSURE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -238,10 +218,9 @@ static const struct ctrl sd_ctrls[] = { #define EXPOSURE_KNEE 133 /* 66 ms / 15 fps */ .default_value = EXPOSURE_DEF, }, - .set = sd_setexposure, - .get = sd_getexposure, + .set_control = setexposure }, - { +[AUTOGAIN] = { { .id = V4L2_CID_AUTOGAIN, .type = V4L2_CTRL_TYPE_BOOLEAN, @@ -252,10 +231,9 @@ static const struct ctrl sd_ctrls[] = { #define AUTOGAIN_DEF 1 .default_value = AUTOGAIN_DEF, }, - .set = sd_setautogain, - .get = sd_getautogain, + .set_control = setautogain, }, - { +[HFLIP] = { { .id = V4L2_CID_HFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, @@ -263,13 +241,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 1, .step = 1, -#define HFLIP_DEF 0 - .default_value = HFLIP_DEF, + .default_value = 0, }, - .set = sd_sethflip, - .get = sd_gethflip, + .set_control = sethvflip, }, - { +[VFLIP] = { { .id = V4L2_CID_VFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, @@ -277,11 +253,9 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 1, .step = 1, -#define VFLIP_DEF 0 - .default_value = VFLIP_DEF, + .default_value = 0, }, - .set = sd_setvflip, - .get = sd_getvflip, + .set_control = sethvflip }, }; @@ -290,21 +264,21 @@ static const struct v4l2_pix_format vga_mode[] = { .bytesperline = 640, .sizeimage = 640 * 480 * 3 / 8 + 590, .colorspace = V4L2_COLORSPACE_JPEG, - .priv = 0}, + }, }; #define LOAD_PAGE3 255 #define END_OF_SEQUENCE 0 /* pac 7302 */ -static const __u8 init_7302[] = { +static const u8 init_7302[] = { /* index,value */ 0xff, 0x01, /* page 1 */ 0x78, 0x00, /* deactivate */ 0xff, 0x01, 0x78, 0x40, /* led off */ }; -static const __u8 start_7302[] = { +static const u8 start_7302[] = { /* index, len, [value]* */ 0xff, 1, 0x00, /* page 0 */ 0x00, 12, 0x01, 0x40, 0x40, 0x40, 0x01, 0xe0, 0x02, 0x80, @@ -319,7 +293,7 @@ static const __u8 start_7302[] = { 0x43, 11, 0x00, 0x0a, 0x18, 0x11, 0x01, 0x2c, 0x88, 0x11, 0x00, 0x54, 0x11, 0x55, 1, 0x00, - 0x62, 4, 0x10, 0x1e, 0x1e, 0x18, + 0x62, 4, 0x10, 0x1e, 0x1e, 0x18, 0x6b, 1, 0x00, 0x6e, 3, 0x08, 0x06, 0x00, 0x72, 3, 0x00, 0xff, 0x00, @@ -370,7 +344,7 @@ static const __u8 start_7302[] = { #define SKIP 0xaa /* page 3 - the value SKIP says skip the index - see reg_w_page() */ -static const __u8 page3_7302[] = { +static const u8 page3_7302[] = { 0x90, 0x40, 0x03, 0x00, 0xc0, 0x01, 0x14, 0x16, 0x14, 0x12, 0x00, 0x00, 0x00, 0x02, 0x33, 0x00, 0x0f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -394,7 +368,7 @@ static const __u8 page3_7302[] = { }; static void reg_w_buf(struct gspca_dev *gspca_dev, - __u8 index, + u8 index, const u8 *buffer, int len) { int ret; @@ -410,7 +384,7 @@ static void reg_w_buf(struct gspca_dev *gspca_dev, index, gspca_dev->usb_buf, len, 500); if (ret < 0) { - pr_err("reg_w_buf failed index 0x%02x, error %d\n", + pr_err("reg_w_buf failed i: %02x error %d\n", index, ret); gspca_dev->usb_err = ret; } @@ -418,8 +392,8 @@ static void reg_w_buf(struct gspca_dev *gspca_dev, static void reg_w(struct gspca_dev *gspca_dev, - __u8 index, - __u8 value) + u8 index, + u8 value) { int ret; @@ -433,14 +407,14 @@ static void reg_w(struct gspca_dev *gspca_dev, 0, index, gspca_dev->usb_buf, 1, 500); if (ret < 0) { - pr_err("reg_w() failed index 0x%02x, value 0x%02x, error %d\n", + pr_err("reg_w() failed i: %02x v: %02x error %d\n", index, value, ret); gspca_dev->usb_err = ret; } } static void reg_w_seq(struct gspca_dev *gspca_dev, - const __u8 *seq, int len) + const u8 *seq, int len) { while (--len >= 0) { reg_w(gspca_dev, seq[0], seq[1]); @@ -450,7 +424,7 @@ static void reg_w_seq(struct gspca_dev *gspca_dev, /* load the beginning of a page */ static void reg_w_page(struct gspca_dev *gspca_dev, - const __u8 *page, int len) + const u8 *page, int len) { int index; int ret = 0; @@ -468,7 +442,7 @@ static void reg_w_page(struct gspca_dev *gspca_dev, 0, index, gspca_dev->usb_buf, 1, 500); if (ret < 0) { - pr_err("reg_w_page() failed index 0x%02x, value 0x%02x, error %d\n", + pr_err("reg_w_page() failed i: %02x v: %02x error %d\n", index, page[index], ret); gspca_dev->usb_err = ret; break; @@ -478,8 +452,8 @@ static void reg_w_page(struct gspca_dev *gspca_dev, /* output a variable sequence */ static void reg_w_var(struct gspca_dev *gspca_dev, - const __u8 *seq, - const __u8 *page3, unsigned int page3_len) + const u8 *seq, + const u8 *page3, unsigned int page3_len) { int index, len; @@ -493,11 +467,13 @@ static void reg_w_var(struct gspca_dev *gspca_dev, reg_w_page(gspca_dev, page3, page3_len); break; default: +#ifdef GSPCA_DEBUG if (len > USB_BUF_SZ) { PDEBUG(D_ERR|D_STREAM, "Incorrect variable sequence"); return; } +#endif while (len > 0) { if (len < 8) { reg_w_buf(gspca_dev, @@ -524,21 +500,11 @@ static int sd_config(struct gspca_dev *gspca_dev, cam = &gspca_dev->cam; - PDEBUG(D_CONF, "Find Sensor PAC7302"); cam->cam_mode = vga_mode; /* only 640x480 */ cam->nmodes = ARRAY_SIZE(vga_mode); - sd->brightness = BRIGHTNESS_DEF; - sd->contrast = CONTRAST_DEF; - sd->colors = COLOR_DEF; - sd->white_balance = WHITEBALANCE_DEF; - sd->red_balance = REDBALANCE_DEF; - sd->blue_balance = BLUEBALANCE_DEF; - sd->gain = GAIN_DEF; - sd->exposure = EXPOSURE_DEF; - sd->autogain = AUTOGAIN_DEF; - sd->hflip = HFLIP_DEF; - sd->vflip = VFLIP_DEF; + gspca_dev->cam.ctrls = sd->ctrls; + sd->flags = id->driver_info; return 0; } @@ -548,19 +514,19 @@ static void setbrightcont(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; int i, v; - static const __u8 max[10] = + static const u8 max[10] = {0x29, 0x33, 0x42, 0x5a, 0x6e, 0x80, 0x9f, 0xbb, 0xd4, 0xec}; - static const __u8 delta[10] = + static const u8 delta[10] = {0x35, 0x33, 0x33, 0x2f, 0x2a, 0x25, 0x1e, 0x17, 0x11, 0x0b}; reg_w(gspca_dev, 0xff, 0x00); /* page 0 */ for (i = 0; i < 10; i++) { v = max[i]; - v += (sd->brightness - BRIGHTNESS_MAX) + v += (sd->ctrls[BRIGHTNESS].val - BRIGHTNESS_MAX) * 150 / BRIGHTNESS_MAX; /* 200 ? */ - v -= delta[i] * sd->contrast / CONTRAST_MAX; + v -= delta[i] * sd->ctrls[CONTRAST].val / CONTRAST_MAX; if (v < 0) v = 0; else if (v > 0xff) @@ -584,12 +550,11 @@ static void setcolors(struct gspca_dev *gspca_dev) reg_w(gspca_dev, 0x11, 0x01); reg_w(gspca_dev, 0xff, 0x00); /* page 0 */ for (i = 0; i < 9; i++) { - v = a[i] * sd->colors / COLOR_MAX + b[i]; + v = a[i] * sd->ctrls[COLORS].val / COLOR_MAX + b[i]; reg_w(gspca_dev, 0x0f + 2 * i, (v >> 8) & 0x07); reg_w(gspca_dev, 0x0f + 2 * i + 1, v); } reg_w(gspca_dev, 0xdc, 0x01); - PDEBUG(D_CONF|D_STREAM, "color: %i", sd->colors); } static void setwhitebalance(struct gspca_dev *gspca_dev) @@ -597,10 +562,9 @@ static void setwhitebalance(struct gspca_dev *gspca_dev) struct sd *sd = (struct sd *) gspca_dev; reg_w(gspca_dev, 0xff, 0x00); /* page 0 */ - reg_w(gspca_dev, 0xc6, sd->white_balance); + reg_w(gspca_dev, 0xc6, sd->ctrls[WHITE_BALANCE].val); reg_w(gspca_dev, 0xdc, 0x01); - PDEBUG(D_CONF|D_STREAM, "white_balance: %i", sd->white_balance); } static void setredbalance(struct gspca_dev *gspca_dev) @@ -608,10 +572,9 @@ static void setredbalance(struct gspca_dev *gspca_dev) struct sd *sd = (struct sd *) gspca_dev; reg_w(gspca_dev, 0xff, 0x00); /* page 0 */ - reg_w(gspca_dev, 0xc5, sd->red_balance); + reg_w(gspca_dev, 0xc5, sd->ctrls[RED_BALANCE].val); reg_w(gspca_dev, 0xdc, 0x01); - PDEBUG(D_CONF|D_STREAM, "red_balance: %i", sd->red_balance); } static void setbluebalance(struct gspca_dev *gspca_dev) @@ -619,10 +582,9 @@ static void setbluebalance(struct gspca_dev *gspca_dev) struct sd *sd = (struct sd *) gspca_dev; reg_w(gspca_dev, 0xff, 0x00); /* page 0 */ - reg_w(gspca_dev, 0xc7, sd->blue_balance); + reg_w(gspca_dev, 0xc7, sd->ctrls[BLUE_BALANCE].val); reg_w(gspca_dev, 0xdc, 0x01); - PDEBUG(D_CONF|D_STREAM, "blue_balance: %i", sd->blue_balance); } static void setgain(struct gspca_dev *gspca_dev) @@ -630,7 +592,7 @@ static void setgain(struct gspca_dev *gspca_dev) struct sd *sd = (struct sd *) gspca_dev; reg_w(gspca_dev, 0xff, 0x03); /* page 3 */ - reg_w(gspca_dev, 0x10, sd->gain >> 3); + reg_w(gspca_dev, 0x10, sd->ctrls[GAIN].val >> 3); /* load registers to sensor (Bit 0, auto clear) */ reg_w(gspca_dev, 0x11, 0x01); @@ -639,13 +601,13 @@ static void setgain(struct gspca_dev *gspca_dev) static void setexposure(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - __u8 clockdiv; - __u16 exposure; + u8 clockdiv; + u16 exposure; /* register 2 of frame 3 contains the clock divider configuring the no fps according to the formula: 90 / reg. sd->exposure is the desired exposure time in 0.5 ms. */ - clockdiv = (90 * sd->exposure + 1999) / 2000; + clockdiv = (90 * sd->ctrls[EXPOSURE].val + 1999) / 2000; /* Note clockdiv = 3 also works, but when running at 30 fps, depending on the scene being recorded, the camera switches to another @@ -664,7 +626,7 @@ static void setexposure(struct gspca_dev *gspca_dev) /* frame exposure time in ms = 1000 * clockdiv / 90 -> exposure = (sd->exposure / 2) * 448 / (1000 * clockdiv / 90) */ - exposure = (sd->exposure * 45 * 448) / (1000 * clockdiv); + exposure = (sd->ctrls[EXPOSURE].val * 45 * 448) / (1000 * clockdiv); /* 0 = use full frametime, 448 = no exposure, reverse it */ exposure = 448 - exposure; @@ -677,15 +639,35 @@ static void setexposure(struct gspca_dev *gspca_dev) reg_w(gspca_dev, 0x11, 0x01); } +static void setautogain(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + + /* when switching to autogain set defaults to make sure + we are on a valid point of the autogain gain / + exposure knee graph, and give this change time to + take effect before doing autogain. */ + if (sd->ctrls[AUTOGAIN].val) { + sd->ctrls[EXPOSURE].val = EXPOSURE_DEF; + sd->ctrls[GAIN].val = GAIN_DEF; + sd->autogain_ignore_frames = + PAC_AUTOGAIN_IGNORE_FRAMES; + } else { + sd->autogain_ignore_frames = -1; + } + setexposure(gspca_dev); + setgain(gspca_dev); +} + static void sethvflip(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; u8 data, hflip, vflip; - hflip = sd->hflip; + hflip = sd->ctrls[HFLIP].val; if (sd->flags & FL_HFLIP) hflip = !hflip; - vflip = sd->vflip; + vflip = sd->ctrls[VFLIP].val; if (sd->flags & FL_VFLIP) vflip = !vflip; @@ -708,8 +690,6 @@ static int sd_start(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - sd->sof_read = 0; - reg_w_var(gspca_dev, start_7302, page3_7302, sizeof(page3_7302)); setbrightcont(gspca_dev); @@ -717,15 +697,13 @@ static int sd_start(struct gspca_dev *gspca_dev) setwhitebalance(gspca_dev); setredbalance(gspca_dev); setbluebalance(gspca_dev); - setgain(gspca_dev); - setexposure(gspca_dev); + setautogain(gspca_dev); sethvflip(gspca_dev); /* only resolution 640x480 is supported for pac7302 */ sd->sof_read = 0; - sd->autogain_ignore_frames = 0; - atomic_set(&sd->avg_lum, -1); + atomic_set(&sd->avg_lum, 270 + sd->ctrls[BRIGHTNESS].val); /* start stream */ reg_w(gspca_dev, 0xff, 0x01); @@ -751,8 +729,10 @@ static void sd_stop0(struct gspca_dev *gspca_dev) reg_w(gspca_dev, 0x78, 0x40); } -/* Include pac common sof detection functions */ -#include "pac_common.h" +/* !! coarse_grained_expo_autogain is not used !! */ +#define exp_too_low_cnt flags +#define exp_too_high_cnt sof_read +#include "autogain_functions.h" static void do_autogain(struct gspca_dev *gspca_dev) { @@ -761,65 +741,44 @@ static void do_autogain(struct gspca_dev *gspca_dev) int desired_lum; const int deadzone = 30; - if (avg_lum == -1) + if (sd->autogain_ignore_frames < 0) return; - desired_lum = 270 + sd->brightness; - - if (sd->autogain_ignore_frames > 0) + if (sd->autogain_ignore_frames > 0) { sd->autogain_ignore_frames--; - else if (gspca_auto_gain_n_exposure(gspca_dev, avg_lum, desired_lum, - deadzone, GAIN_KNEE, EXPOSURE_KNEE)) + } else { + desired_lum = 270 + sd->ctrls[BRIGHTNESS].val; + + auto_gain_n_exposure(gspca_dev, avg_lum, desired_lum, + deadzone, GAIN_KNEE, EXPOSURE_KNEE); sd->autogain_ignore_frames = PAC_AUTOGAIN_IGNORE_FRAMES; + } } -/* JPEG header, part 1 */ -static const unsigned char pac_jpeg_header1[] = { - 0xff, 0xd8, /* SOI: Start of Image */ - - 0xff, 0xc0, /* SOF0: Start of Frame (Baseline DCT) */ - 0x00, 0x11, /* length = 17 bytes (including this length field) */ - 0x08 /* Precision: 8 */ - /* 2 bytes is placed here: number of image lines */ - /* 2 bytes is placed here: samples per line */ -}; - -/* JPEG header, continued */ -static const unsigned char pac_jpeg_header2[] = { - 0x03, /* Number of image components: 3 */ - 0x01, 0x21, 0x00, /* ID=1, Subsampling 1x1, Quantization table: 0 */ - 0x02, 0x11, 0x01, /* ID=2, Subsampling 2x1, Quantization table: 1 */ - 0x03, 0x11, 0x01, /* ID=3, Subsampling 2x1, Quantization table: 1 */ - - 0xff, 0xda, /* SOS: Start Of Scan */ - 0x00, 0x0c, /* length = 12 bytes (including this length field) */ - 0x03, /* number of components: 3 */ - 0x01, 0x00, /* selector 1, table 0x00 */ - 0x02, 0x11, /* selector 2, table 0x11 */ - 0x03, 0x11, /* selector 3, table 0x11 */ - 0x00, 0x3f, /* Spectral selection: 0 .. 63 */ - 0x00 /* Successive approximation: 0 */ +/* JPEG header */ +static const u8 jpeg_header[] = { + 0xff, 0xd8, /* SOI: Start of Image */ + + 0xff, 0xc0, /* SOF0: Start of Frame (Baseline DCT) */ + 0x00, 0x11, /* length = 17 bytes (including this length field) */ + 0x08, /* Precision: 8 */ + 0x02, 0x80, /* height = 640 (image rotated) */ + 0x01, 0xe0, /* width = 480 */ + 0x03, /* Number of image components: 3 */ + 0x01, 0x21, 0x00, /* ID=1, Subsampling 1x1, Quantization table: 0 */ + 0x02, 0x11, 0x01, /* ID=2, Subsampling 2x1, Quantization table: 1 */ + 0x03, 0x11, 0x01, /* ID=3, Subsampling 2x1, Quantization table: 1 */ + + 0xff, 0xda, /* SOS: Start Of Scan */ + 0x00, 0x0c, /* length = 12 bytes (including this length field) */ + 0x03, /* number of components: 3 */ + 0x01, 0x00, /* selector 1, table 0x00 */ + 0x02, 0x11, /* selector 2, table 0x11 */ + 0x03, 0x11, /* selector 3, table 0x11 */ + 0x00, 0x3f, /* Spectral selection: 0 .. 63 */ + 0x00 /* Successive approximation: 0 */ }; -static void pac_start_frame(struct gspca_dev *gspca_dev, - __u16 lines, __u16 samples_per_line) -{ - unsigned char tmpbuf[4]; - - gspca_frame_add(gspca_dev, FIRST_PACKET, - pac_jpeg_header1, sizeof(pac_jpeg_header1)); - - tmpbuf[0] = lines >> 8; - tmpbuf[1] = lines & 0xff; - tmpbuf[2] = samples_per_line >> 8; - tmpbuf[3] = samples_per_line & 0xff; - - gspca_frame_add(gspca_dev, INTER_PACKET, - tmpbuf, sizeof(tmpbuf)); - gspca_frame_add(gspca_dev, INTER_PACKET, - pac_jpeg_header2, sizeof(pac_jpeg_header2)); -} - /* this function is run at interrupt level */ static void sd_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, /* isoc packet */ @@ -827,7 +786,7 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, { struct sd *sd = (struct sd *) gspca_dev; u8 *image; - unsigned char *sof; + u8 *sof; sof = pac_find_sof(&sd->sof_read, data, len); if (sof) { @@ -864,234 +823,21 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, n >= lum_offset) atomic_set(&sd->avg_lum, data[-lum_offset] + data[-lum_offset + 1]); - else - atomic_set(&sd->avg_lum, -1); /* Start the new frame with the jpeg header */ /* The PAC7302 has the image rotated 90 degrees */ - pac_start_frame(gspca_dev, - gspca_dev->width, gspca_dev->height); + gspca_frame_add(gspca_dev, FIRST_PACKET, + jpeg_header, sizeof jpeg_header); } gspca_frame_add(gspca_dev, INTER_PACKET, data, len); } -static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->brightness = val; - if (gspca_dev->streaming) - setbrightcont(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->brightness; - return 0; -} - -static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->contrast = val; - if (gspca_dev->streaming) - setbrightcont(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->contrast; - return 0; -} - -static int sd_setcolors(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->colors = val; - if (gspca_dev->streaming) - setcolors(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getcolors(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->colors; - return 0; -} - -static int sd_setwhitebalance(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->white_balance = val; - if (gspca_dev->streaming) - setwhitebalance(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getwhitebalance(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->white_balance; - return 0; -} - -static int sd_setredbalance(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->red_balance = val; - if (gspca_dev->streaming) - setredbalance(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getredbalance(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->red_balance; - return 0; -} - -static int sd_setbluebalance(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->blue_balance = val; - if (gspca_dev->streaming) - setbluebalance(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getbluebalance(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->blue_balance; - return 0; -} - -static int sd_setgain(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->gain = val; - if (gspca_dev->streaming) - setgain(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getgain(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->gain; - return 0; -} - -static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->exposure = val; - if (gspca_dev->streaming) - setexposure(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->exposure; - return 0; -} - -static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->autogain = val; - /* when switching to autogain set defaults to make sure - we are on a valid point of the autogain gain / - exposure knee graph, and give this change time to - take effect before doing autogain. */ - if (sd->autogain) { - sd->exposure = EXPOSURE_DEF; - sd->gain = GAIN_DEF; - if (gspca_dev->streaming) { - sd->autogain_ignore_frames = - PAC_AUTOGAIN_IGNORE_FRAMES; - setexposure(gspca_dev); - setgain(gspca_dev); - } - } - - return gspca_dev->usb_err; -} - -static int sd_getautogain(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->autogain; - return 0; -} - -static int sd_sethflip(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->hflip = val; - if (gspca_dev->streaming) - sethvflip(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_gethflip(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->hflip; - return 0; -} - -static int sd_setvflip(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->vflip = val; - if (gspca_dev->streaming) - sethvflip(gspca_dev); - return gspca_dev->usb_err; -} - -static int sd_getvflip(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->vflip; - return 0; -} - #ifdef CONFIG_VIDEO_ADV_DEBUG static int sd_dbg_s_register(struct gspca_dev *gspca_dev, struct v4l2_dbg_register *reg) { - __u8 index; - __u8 value; + u8 index; + u8 value; /* reg->reg: bit0..15: reserved for register index (wIndex is 16bit long on the USB bus) @@ -1103,8 +849,8 @@ static int sd_dbg_s_register(struct gspca_dev *gspca_dev, ) { /* Currently writing to page 0 is only supported. */ /* reg_w() only supports 8bit index */ - index = reg->reg & 0x000000ff; - value = reg->val & 0x000000ff; + index = reg->reg; + value = reg->val; /* Note that there shall be no access to other page by any other function between the page swith and @@ -1165,7 +911,7 @@ static int sd_int_pkt_scan(struct gspca_dev *gspca_dev, /* sub-driver description for pac7302 */ static const struct sd_desc sd_desc = { - .name = MODULE_NAME, + .name = KBUILD_MODNAME, .ctrls = sd_ctrls, .nctrls = ARRAY_SIZE(sd_ctrls), .config = sd_config, @@ -1187,6 +933,7 @@ static const struct sd_desc sd_desc = { /* -- module initialisation -- */ static const struct usb_device_id device_table[] = { {USB_DEVICE(0x06f8, 0x3009)}, + {USB_DEVICE(0x06f8, 0x301b)}, {USB_DEVICE(0x093a, 0x2620)}, {USB_DEVICE(0x093a, 0x2621)}, {USB_DEVICE(0x093a, 0x2622), .driver_info = FL_VFLIP}, @@ -1211,7 +958,7 @@ static int sd_probe(struct usb_interface *intf, } static struct usb_driver sd_driver = { - .name = MODULE_NAME, + .name = KBUILD_MODNAME, .id_table = device_table, .probe = sd_probe, .disconnect = gspca_disconnect, diff --git a/drivers/media/video/gspca/sn9c20x.c b/drivers/media/video/gspca/sn9c20x.c index 9e198b45c3c8..7e71aa2d2522 100644 --- a/drivers/media/video/gspca/sn9c20x.c +++ b/drivers/media/video/gspca/sn9c20x.c @@ -1,5 +1,7 @@ /* * Sonix sn9c201 sn9c202 library + * + * Copyright (C) 2012 Jean-Francois Moine <http://moinejf.free.fr> * Copyright (C) 2008-2009 microdia project <microdia@googlegroups.com> * Copyright (C) 2009 Brian Johnson <brijohn@gmail.com> * @@ -33,8 +35,6 @@ MODULE_AUTHOR("Brian Johnson <brijohn@gmail.com>, " MODULE_DESCRIPTION("GSPCA/SN9C20X USB Camera Driver"); MODULE_LICENSE("GPL"); -#define MODULE_NAME "sn9c20x" - /* * Pixel format private data */ @@ -66,10 +66,37 @@ MODULE_LICENSE("GPL"); #define LED_REVERSE 0x2 /* some cameras unset gpio to turn on leds */ #define FLIP_DETECT 0x4 +enum e_ctrl { + BRIGHTNESS, + CONTRAST, + SATURATION, + HUE, + GAMMA, + BLUE, + RED, + VFLIP, + HFLIP, + EXPOSURE, + GAIN, + AUTOGAIN, + QUALITY, + NCTRLS /* number of controls */ +}; + /* specific webcam descriptor */ struct sd { struct gspca_dev gspca_dev; + struct gspca_ctrl ctrls[NCTRLS]; + + struct work_struct work; + struct workqueue_struct *work_thread; + + u32 pktsz; /* (used by pkt_scan) */ + u16 npkt; + s8 nchg; + u8 fmt; /* (used for JPEG QTAB update */ + #define MIN_AVG_LUM 80 #define MAX_AVG_LUM 130 atomic_t avg_lum; @@ -77,31 +104,18 @@ struct sd { u8 older_step; u8 exposure_step; - u8 brightness; - u8 contrast; - u8 saturation; - s16 hue; - u8 gamma; - u8 red; - u8 blue; - - u8 hflip; - u8 vflip; - u8 gain; - u16 exposure; - u8 auto_exposure; - u8 i2c_addr; u8 sensor; u8 hstart; u8 vstart; u8 jpeg_hdr[JPEG_HDR_SZ]; - u8 quality; u8 flags; }; +static void qual_upd(struct work_struct *work); + struct i2c_reg_u8 { u8 reg; u8 val; @@ -112,31 +126,6 @@ struct i2c_reg_u16 { u16 val; }; -static int sd_setbrightness(struct gspca_dev *gspca_dev, s32 val); -static int sd_getbrightness(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setcontrast(struct gspca_dev *gspca_dev, s32 val); -static int sd_getcontrast(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setsaturation(struct gspca_dev *gspca_dev, s32 val); -static int sd_getsaturation(struct gspca_dev *gspca_dev, s32 *val); -static int sd_sethue(struct gspca_dev *gspca_dev, s32 val); -static int sd_gethue(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setgamma(struct gspca_dev *gspca_dev, s32 val); -static int sd_getgamma(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setredbalance(struct gspca_dev *gspca_dev, s32 val); -static int sd_getredbalance(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setbluebalance(struct gspca_dev *gspca_dev, s32 val); -static int sd_getbluebalance(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setvflip(struct gspca_dev *gspca_dev, s32 val); -static int sd_getvflip(struct gspca_dev *gspca_dev, s32 *val); -static int sd_sethflip(struct gspca_dev *gspca_dev, s32 val); -static int sd_gethflip(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setgain(struct gspca_dev *gspca_dev, s32 val); -static int sd_getgain(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setexposure(struct gspca_dev *gspca_dev, s32 val); -static int sd_getexposure(struct gspca_dev *gspca_dev, s32 *val); -static int sd_setautoexposure(struct gspca_dev *gspca_dev, s32 val); -static int sd_getautoexposure(struct gspca_dev *gspca_dev, s32 *val); - static const struct dmi_system_id flip_dmi_table[] = { { .ident = "MSI MS-1034", @@ -177,9 +166,16 @@ static const struct dmi_system_id flip_dmi_table[] = { {} }; -static const struct ctrl sd_ctrls[] = { - { -#define BRIGHTNESS_IDX 0 +static void set_cmatrix(struct gspca_dev *gspca_dev); +static void set_gamma(struct gspca_dev *gspca_dev); +static void set_redblue(struct gspca_dev *gspca_dev); +static void set_hvflip(struct gspca_dev *gspca_dev); +static void set_exposure(struct gspca_dev *gspca_dev); +static void set_gain(struct gspca_dev *gspca_dev); +static void set_quality(struct gspca_dev *gspca_dev); + +static const struct ctrl sd_ctrls[NCTRLS] = { +[BRIGHTNESS] = { { .id = V4L2_CID_BRIGHTNESS, .type = V4L2_CTRL_TYPE_INTEGER, @@ -187,14 +183,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 0xff, .step = 1, -#define BRIGHTNESS_DEFAULT 0x7f - .default_value = BRIGHTNESS_DEFAULT, + .default_value = 0x7f }, - .set = sd_setbrightness, - .get = sd_getbrightness, + .set_control = set_cmatrix }, - { -#define CONTRAST_IDX 1 +[CONTRAST] = { { .id = V4L2_CID_CONTRAST, .type = V4L2_CTRL_TYPE_INTEGER, @@ -202,14 +195,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 0xff, .step = 1, -#define CONTRAST_DEFAULT 0x7f - .default_value = CONTRAST_DEFAULT, + .default_value = 0x7f }, - .set = sd_setcontrast, - .get = sd_getcontrast, + .set_control = set_cmatrix }, - { -#define SATURATION_IDX 2 +[SATURATION] = { { .id = V4L2_CID_SATURATION, .type = V4L2_CTRL_TYPE_INTEGER, @@ -217,14 +207,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 0xff, .step = 1, -#define SATURATION_DEFAULT 0x7f - .default_value = SATURATION_DEFAULT, + .default_value = 0x7f }, - .set = sd_setsaturation, - .get = sd_getsaturation, + .set_control = set_cmatrix }, - { -#define HUE_IDX 3 +[HUE] = { { .id = V4L2_CID_HUE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -232,14 +219,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = -180, .maximum = 180, .step = 1, -#define HUE_DEFAULT 0 - .default_value = HUE_DEFAULT, + .default_value = 0 }, - .set = sd_sethue, - .get = sd_gethue, + .set_control = set_cmatrix }, - { -#define GAMMA_IDX 4 +[GAMMA] = { { .id = V4L2_CID_GAMMA, .type = V4L2_CTRL_TYPE_INTEGER, @@ -247,14 +231,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 0xff, .step = 1, -#define GAMMA_DEFAULT 0x10 - .default_value = GAMMA_DEFAULT, + .default_value = 0x10 }, - .set = sd_setgamma, - .get = sd_getgamma, + .set_control = set_gamma }, - { -#define BLUE_IDX 5 +[BLUE] = { { .id = V4L2_CID_BLUE_BALANCE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -262,14 +243,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 0x7f, .step = 1, -#define BLUE_DEFAULT 0x28 - .default_value = BLUE_DEFAULT, + .default_value = 0x28 }, - .set = sd_setbluebalance, - .get = sd_getbluebalance, + .set_control = set_redblue }, - { -#define RED_IDX 6 +[RED] = { { .id = V4L2_CID_RED_BALANCE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -277,14 +255,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 0x7f, .step = 1, -#define RED_DEFAULT 0x28 - .default_value = RED_DEFAULT, + .default_value = 0x28 }, - .set = sd_setredbalance, - .get = sd_getredbalance, + .set_control = set_redblue }, - { -#define HFLIP_IDX 7 +[HFLIP] = { { .id = V4L2_CID_HFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, @@ -292,14 +267,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 1, .step = 1, -#define HFLIP_DEFAULT 0 - .default_value = HFLIP_DEFAULT, + .default_value = 0, }, - .set = sd_sethflip, - .get = sd_gethflip, + .set_control = set_hvflip }, - { -#define VFLIP_IDX 8 +[VFLIP] = { { .id = V4L2_CID_VFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, @@ -307,14 +279,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 1, .step = 1, -#define VFLIP_DEFAULT 0 - .default_value = VFLIP_DEFAULT, + .default_value = 0, }, - .set = sd_setvflip, - .get = sd_getvflip, + .set_control = set_hvflip }, - { -#define EXPOSURE_IDX 9 +[EXPOSURE] = { { .id = V4L2_CID_EXPOSURE, .type = V4L2_CTRL_TYPE_INTEGER, @@ -322,14 +291,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 0x1780, .step = 1, -#define EXPOSURE_DEFAULT 0x33 - .default_value = EXPOSURE_DEFAULT, + .default_value = 0x33, }, - .set = sd_setexposure, - .get = sd_getexposure, + .set_control = set_exposure }, - { -#define GAIN_IDX 10 +[GAIN] = { { .id = V4L2_CID_GAIN, .type = V4L2_CTRL_TYPE_INTEGER, @@ -337,14 +303,11 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 28, .step = 1, -#define GAIN_DEFAULT 0x00 - .default_value = GAIN_DEFAULT, + .default_value = 0, }, - .set = sd_setgain, - .get = sd_getgain, + .set_control = set_gain }, - { -#define AUTOGAIN_IDX 11 +[AUTOGAIN] = { { .id = V4L2_CID_AUTOGAIN, .type = V4L2_CTRL_TYPE_BOOLEAN, @@ -352,11 +315,23 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 1, .step = 1, -#define AUTO_EXPOSURE_DEFAULT 1 - .default_value = AUTO_EXPOSURE_DEFAULT, + .default_value = 1, + }, + }, +[QUALITY] = { + { + .id = V4L2_CID_JPEG_COMPRESSION_QUALITY, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Compression Quality", +#define QUALITY_MIN 50 +#define QUALITY_MAX 90 +#define QUALITY_DEF 80 + .minimum = QUALITY_MIN, + .maximum = QUALITY_MAX, + .step = 1, + .default_value = QUALITY_DEF, }, - .set = sd_setautoexposure, - .get = sd_getautoexposure, + .set_control = set_quality }, }; @@ -876,7 +851,7 @@ static u8 hv7131r_gain[] = { }; static struct i2c_reg_u8 soi968_init[] = { - {0x12, 0x80}, {0x0c, 0x00}, {0x0f, 0x1f}, + {0x0c, 0x00}, {0x0f, 0x1f}, {0x11, 0x80}, {0x38, 0x52}, {0x1e, 0x00}, {0x33, 0x08}, {0x35, 0x8c}, {0x36, 0x0c}, {0x37, 0x04}, {0x45, 0x04}, {0x47, 0xff}, @@ -902,7 +877,7 @@ static struct i2c_reg_u8 ov7660_init[] = { }; static struct i2c_reg_u8 ov7670_init[] = { - {0x12, 0x80}, {0x11, 0x80}, {0x3a, 0x04}, {0x12, 0x01}, + {0x11, 0x80}, {0x3a, 0x04}, {0x12, 0x01}, {0x32, 0xb6}, {0x03, 0x0a}, {0x0c, 0x00}, {0x3e, 0x00}, {0x70, 0x3a}, {0x71, 0x35}, {0x72, 0x11}, {0x73, 0xf0}, {0xa2, 0x02}, {0x13, 0xe0}, {0x00, 0x00}, {0x10, 0x00}, @@ -959,7 +934,7 @@ static struct i2c_reg_u8 ov7670_init[] = { }; static struct i2c_reg_u8 ov9650_init[] = { - {0x12, 0x80}, {0x00, 0x00}, {0x01, 0x78}, + {0x00, 0x00}, {0x01, 0x78}, {0x02, 0x78}, {0x03, 0x36}, {0x04, 0x03}, {0x05, 0x00}, {0x06, 0x00}, {0x08, 0x00}, {0x09, 0x01}, {0x0c, 0x00}, {0x0d, 0x00}, @@ -989,7 +964,7 @@ static struct i2c_reg_u8 ov9650_init[] = { }; static struct i2c_reg_u8 ov9655_init[] = { - {0x12, 0x80}, {0x0e, 0x61}, {0x11, 0x80}, {0x13, 0xba}, + {0x0e, 0x61}, {0x11, 0x80}, {0x13, 0xba}, {0x14, 0x2e}, {0x16, 0x24}, {0x1e, 0x04}, {0x27, 0x08}, {0x28, 0x08}, {0x29, 0x15}, {0x2c, 0x08}, {0x34, 0x3d}, {0x35, 0x00}, {0x38, 0x12}, {0x0f, 0x42}, {0x39, 0x57}, @@ -1112,10 +1087,13 @@ static struct i2c_reg_u8 hv7131r_init[] = { {0x23, 0x09}, {0x01, 0x08}, }; -static int reg_r(struct gspca_dev *gspca_dev, u16 reg, u16 length) +static void reg_r(struct gspca_dev *gspca_dev, u16 reg, u16 length) { struct usb_device *dev = gspca_dev->dev; int result; + + if (gspca_dev->usb_err < 0) + return; result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), 0x00, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, @@ -1125,17 +1103,19 @@ static int reg_r(struct gspca_dev *gspca_dev, u16 reg, u16 length) length, 500); if (unlikely(result < 0 || result != length)) { - pr_err("Read register failed 0x%02X\n", reg); - return -EIO; + pr_err("Read register %02x failed %d\n", reg, result); + gspca_dev->usb_err = result; } - return 0; } -static int reg_w(struct gspca_dev *gspca_dev, u16 reg, +static void reg_w(struct gspca_dev *gspca_dev, u16 reg, const u8 *buffer, int length) { struct usb_device *dev = gspca_dev->dev; int result; + + if (gspca_dev->usb_err < 0) + return; memcpy(gspca_dev->usb_buf, buffer, length); result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 0x08, @@ -1146,38 +1126,41 @@ static int reg_w(struct gspca_dev *gspca_dev, u16 reg, length, 500); if (unlikely(result < 0 || result != length)) { - pr_err("Write register failed index 0x%02X\n", reg); - return -EIO; + pr_err("Write register %02x failed %d\n", reg, result); + gspca_dev->usb_err = result; } - return 0; } -static int reg_w1(struct gspca_dev *gspca_dev, u16 reg, const u8 value) +static void reg_w1(struct gspca_dev *gspca_dev, u16 reg, const u8 value) { - u8 data[1] = {value}; - return reg_w(gspca_dev, reg, data, 1); + reg_w(gspca_dev, reg, &value, 1); } -static int i2c_w(struct gspca_dev *gspca_dev, const u8 *buffer) +static void i2c_w(struct gspca_dev *gspca_dev, const u8 *buffer) { int i; + reg_w(gspca_dev, 0x10c0, buffer, 8); for (i = 0; i < 5; i++) { reg_r(gspca_dev, 0x10c0, 1); + if (gspca_dev->usb_err < 0) + return; if (gspca_dev->usb_buf[0] & 0x04) { - if (gspca_dev->usb_buf[0] & 0x08) - return -EIO; - return 0; + if (gspca_dev->usb_buf[0] & 0x08) { + pr_err("i2c_w error\n"); + gspca_dev->usb_err = -EIO; + } + return; } - msleep(1); + msleep(10); } - return -EIO; + pr_err("i2c_w reg %02x no response\n", buffer[2]); +/* gspca_dev->usb_err = -EIO; fixme: may occur */ } -static int i2c_w1(struct gspca_dev *gspca_dev, u8 reg, u8 val) +static void i2c_w1(struct gspca_dev *gspca_dev, u8 reg, u8 val) { struct sd *sd = (struct sd *) gspca_dev; - u8 row[8]; /* @@ -1193,10 +1176,19 @@ static int i2c_w1(struct gspca_dev *gspca_dev, u8 reg, u8 val) row[6] = 0x00; row[7] = 0x10; - return i2c_w(gspca_dev, row); + i2c_w(gspca_dev, row); +} + +static void i2c_w1_buf(struct gspca_dev *gspca_dev, + struct i2c_reg_u8 *buf, int sz) +{ + while (--sz >= 0) { + i2c_w1(gspca_dev, buf->reg, buf->val); + buf++; + } } -static int i2c_w2(struct gspca_dev *gspca_dev, u8 reg, u16 val) +static void i2c_w2(struct gspca_dev *gspca_dev, u8 reg, u16 val) { struct sd *sd = (struct sd *) gspca_dev; u8 row[8]; @@ -1208,16 +1200,25 @@ static int i2c_w2(struct gspca_dev *gspca_dev, u8 reg, u16 val) row[0] = 0x81 | (3 << 4); row[1] = sd->i2c_addr; row[2] = reg; - row[3] = (val >> 8) & 0xff; - row[4] = val & 0xff; + row[3] = val >> 8; + row[4] = val; row[5] = 0x00; row[6] = 0x00; row[7] = 0x10; - return i2c_w(gspca_dev, row); + i2c_w(gspca_dev, row); +} + +static void i2c_w2_buf(struct gspca_dev *gspca_dev, + struct i2c_reg_u16 *buf, int sz) +{ + while (--sz >= 0) { + i2c_w2(gspca_dev, buf->reg, buf->val); + buf++; + } } -static int i2c_r1(struct gspca_dev *gspca_dev, u8 reg, u8 *val) +static void i2c_r1(struct gspca_dev *gspca_dev, u8 reg, u8 *val) { struct sd *sd = (struct sd *) gspca_dev; u8 row[8]; @@ -1230,19 +1231,15 @@ static int i2c_r1(struct gspca_dev *gspca_dev, u8 reg, u8 *val) row[5] = 0; row[6] = 0; row[7] = 0x10; - if (i2c_w(gspca_dev, row) < 0) - return -EIO; + i2c_w(gspca_dev, row); row[0] = 0x81 | (1 << 4) | 0x02; row[2] = 0; - if (i2c_w(gspca_dev, row) < 0) - return -EIO; - if (reg_r(gspca_dev, 0x10c2, 5) < 0) - return -EIO; + i2c_w(gspca_dev, row); + reg_r(gspca_dev, 0x10c2, 5); *val = gspca_dev->usb_buf[4]; - return 0; } -static int i2c_r2(struct gspca_dev *gspca_dev, u8 reg, u16 *val) +static void i2c_r2(struct gspca_dev *gspca_dev, u8 reg, u16 *val) { struct sd *sd = (struct sd *) gspca_dev; u8 row[8]; @@ -1255,233 +1252,204 @@ static int i2c_r2(struct gspca_dev *gspca_dev, u8 reg, u16 *val) row[5] = 0; row[6] = 0; row[7] = 0x10; - if (i2c_w(gspca_dev, row) < 0) - return -EIO; + i2c_w(gspca_dev, row); row[0] = 0x81 | (2 << 4) | 0x02; row[2] = 0; - if (i2c_w(gspca_dev, row) < 0) - return -EIO; - if (reg_r(gspca_dev, 0x10c2, 5) < 0) - return -EIO; + i2c_w(gspca_dev, row); + reg_r(gspca_dev, 0x10c2, 5); *val = (gspca_dev->usb_buf[3] << 8) | gspca_dev->usb_buf[4]; - return 0; } -static int ov9650_init_sensor(struct gspca_dev *gspca_dev) +static void ov9650_init_sensor(struct gspca_dev *gspca_dev) { - int i; u16 id; struct sd *sd = (struct sd *) gspca_dev; - if (i2c_r2(gspca_dev, 0x1c, &id) < 0) - return -EINVAL; + i2c_r2(gspca_dev, 0x1c, &id); + if (gspca_dev->usb_err < 0) + return; if (id != 0x7fa2) { pr_err("sensor id for ov9650 doesn't match (0x%04x)\n", id); - return -ENODEV; + gspca_dev->usb_err = -ENODEV; + return; } - for (i = 0; i < ARRAY_SIZE(ov9650_init); i++) { - if (i2c_w1(gspca_dev, ov9650_init[i].reg, - ov9650_init[i].val) < 0) { - pr_err("OV9650 sensor initialization failed\n"); - return -ENODEV; - } - } + i2c_w1(gspca_dev, 0x12, 0x80); /* sensor reset */ + msleep(200); + i2c_w1_buf(gspca_dev, ov9650_init, ARRAY_SIZE(ov9650_init)); + if (gspca_dev->usb_err < 0) + pr_err("OV9650 sensor initialization failed\n"); sd->hstart = 1; sd->vstart = 7; - return 0; } -static int ov9655_init_sensor(struct gspca_dev *gspca_dev) +static void ov9655_init_sensor(struct gspca_dev *gspca_dev) { - int i; struct sd *sd = (struct sd *) gspca_dev; - for (i = 0; i < ARRAY_SIZE(ov9655_init); i++) { - if (i2c_w1(gspca_dev, ov9655_init[i].reg, - ov9655_init[i].val) < 0) { - pr_err("OV9655 sensor initialization failed\n"); - return -ENODEV; - } - } + i2c_w1(gspca_dev, 0x12, 0x80); /* sensor reset */ + msleep(200); + i2c_w1_buf(gspca_dev, ov9655_init, ARRAY_SIZE(ov9655_init)); + if (gspca_dev->usb_err < 0) + pr_err("OV9655 sensor initialization failed\n"); + /* disable hflip and vflip */ - gspca_dev->ctrl_dis = (1 << HFLIP_IDX) | (1 << VFLIP_IDX); + gspca_dev->ctrl_dis = (1 << HFLIP) | (1 << VFLIP); sd->hstart = 1; sd->vstart = 2; - return 0; } -static int soi968_init_sensor(struct gspca_dev *gspca_dev) +static void soi968_init_sensor(struct gspca_dev *gspca_dev) { - int i; struct sd *sd = (struct sd *) gspca_dev; - for (i = 0; i < ARRAY_SIZE(soi968_init); i++) { - if (i2c_w1(gspca_dev, soi968_init[i].reg, - soi968_init[i].val) < 0) { - pr_err("SOI968 sensor initialization failed\n"); - return -ENODEV; - } - } + i2c_w1(gspca_dev, 0x12, 0x80); /* sensor reset */ + msleep(200); + i2c_w1_buf(gspca_dev, soi968_init, ARRAY_SIZE(soi968_init)); + if (gspca_dev->usb_err < 0) + pr_err("SOI968 sensor initialization failed\n"); + /* disable hflip and vflip */ - gspca_dev->ctrl_dis = (1 << HFLIP_IDX) | (1 << VFLIP_IDX) - | (1 << EXPOSURE_IDX); + gspca_dev->ctrl_dis = (1 << HFLIP) | (1 << VFLIP) + | (1 << EXPOSURE); sd->hstart = 60; sd->vstart = 11; - return 0; } -static int ov7660_init_sensor(struct gspca_dev *gspca_dev) +static void ov7660_init_sensor(struct gspca_dev *gspca_dev) { - int i; struct sd *sd = (struct sd *) gspca_dev; - for (i = 0; i < ARRAY_SIZE(ov7660_init); i++) { - if (i2c_w1(gspca_dev, ov7660_init[i].reg, - ov7660_init[i].val) < 0) { - pr_err("OV7660 sensor initialization failed\n"); - return -ENODEV; - } - } + i2c_w1(gspca_dev, 0x12, 0x80); /* sensor reset */ + msleep(200); + i2c_w1_buf(gspca_dev, ov7660_init, ARRAY_SIZE(ov7660_init)); + if (gspca_dev->usb_err < 0) + pr_err("OV7660 sensor initialization failed\n"); sd->hstart = 3; sd->vstart = 3; - return 0; } -static int ov7670_init_sensor(struct gspca_dev *gspca_dev) +static void ov7670_init_sensor(struct gspca_dev *gspca_dev) { - int i; struct sd *sd = (struct sd *) gspca_dev; - for (i = 0; i < ARRAY_SIZE(ov7670_init); i++) { - if (i2c_w1(gspca_dev, ov7670_init[i].reg, - ov7670_init[i].val) < 0) { - pr_err("OV7670 sensor initialization failed\n"); - return -ENODEV; - } - } + i2c_w1(gspca_dev, 0x12, 0x80); /* sensor reset */ + msleep(200); + i2c_w1_buf(gspca_dev, ov7670_init, ARRAY_SIZE(ov7670_init)); + if (gspca_dev->usb_err < 0) + pr_err("OV7670 sensor initialization failed\n"); + /* disable hflip and vflip */ - gspca_dev->ctrl_dis = (1 << HFLIP_IDX) | (1 << VFLIP_IDX); + gspca_dev->ctrl_dis = (1 << HFLIP) | (1 << VFLIP); sd->hstart = 0; sd->vstart = 1; - return 0; } -static int mt9v_init_sensor(struct gspca_dev *gspca_dev) +static void mt9v_init_sensor(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - int i; u16 value; - int ret; sd->i2c_addr = 0x5d; - ret = i2c_r2(gspca_dev, 0xff, &value); - if ((ret == 0) && (value == 0x8243)) { - for (i = 0; i < ARRAY_SIZE(mt9v011_init); i++) { - if (i2c_w2(gspca_dev, mt9v011_init[i].reg, - mt9v011_init[i].val) < 0) { - pr_err("MT9V011 sensor initialization failed\n"); - return -ENODEV; - } + i2c_r2(gspca_dev, 0xff, &value); + if (gspca_dev->usb_err >= 0 + && value == 0x8243) { + i2c_w2_buf(gspca_dev, mt9v011_init, ARRAY_SIZE(mt9v011_init)); + if (gspca_dev->usb_err < 0) { + pr_err("MT9V011 sensor initialization failed\n"); + return; } sd->hstart = 2; sd->vstart = 2; sd->sensor = SENSOR_MT9V011; pr_info("MT9V011 sensor detected\n"); - return 0; + return; } + gspca_dev->usb_err = 0; sd->i2c_addr = 0x5c; i2c_w2(gspca_dev, 0x01, 0x0004); - ret = i2c_r2(gspca_dev, 0xff, &value); - if ((ret == 0) && (value == 0x823a)) { - for (i = 0; i < ARRAY_SIZE(mt9v111_init); i++) { - if (i2c_w2(gspca_dev, mt9v111_init[i].reg, - mt9v111_init[i].val) < 0) { - pr_err("MT9V111 sensor initialization failed\n"); - return -ENODEV; - } + i2c_r2(gspca_dev, 0xff, &value); + if (gspca_dev->usb_err >= 0 + && value == 0x823a) { + i2c_w2_buf(gspca_dev, mt9v111_init, ARRAY_SIZE(mt9v111_init)); + if (gspca_dev->usb_err < 0) { + pr_err("MT9V111 sensor initialization failed\n"); + return; } - gspca_dev->ctrl_dis = (1 << EXPOSURE_IDX) - | (1 << AUTOGAIN_IDX) - | (1 << GAIN_IDX); + gspca_dev->ctrl_dis = (1 << EXPOSURE) + | (1 << AUTOGAIN) + | (1 << GAIN); sd->hstart = 2; sd->vstart = 2; sd->sensor = SENSOR_MT9V111; pr_info("MT9V111 sensor detected\n"); - return 0; + return; } + gspca_dev->usb_err = 0; sd->i2c_addr = 0x5d; - ret = i2c_w2(gspca_dev, 0xf0, 0x0000); - if (ret < 0) { + i2c_w2(gspca_dev, 0xf0, 0x0000); + if (gspca_dev->usb_err < 0) { + gspca_dev->usb_err = 0; sd->i2c_addr = 0x48; i2c_w2(gspca_dev, 0xf0, 0x0000); } - ret = i2c_r2(gspca_dev, 0x00, &value); - if ((ret == 0) && (value == 0x1229)) { - for (i = 0; i < ARRAY_SIZE(mt9v112_init); i++) { - if (i2c_w2(gspca_dev, mt9v112_init[i].reg, - mt9v112_init[i].val) < 0) { - pr_err("MT9V112 sensor initialization failed\n"); - return -ENODEV; - } + i2c_r2(gspca_dev, 0x00, &value); + if (gspca_dev->usb_err >= 0 + && value == 0x1229) { + i2c_w2_buf(gspca_dev, mt9v112_init, ARRAY_SIZE(mt9v112_init)); + if (gspca_dev->usb_err < 0) { + pr_err("MT9V112 sensor initialization failed\n"); + return; } sd->hstart = 6; sd->vstart = 2; sd->sensor = SENSOR_MT9V112; pr_info("MT9V112 sensor detected\n"); - return 0; + return; } - return -ENODEV; + gspca_dev->usb_err = -ENODEV; } -static int mt9m112_init_sensor(struct gspca_dev *gspca_dev) +static void mt9m112_init_sensor(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - int i; - for (i = 0; i < ARRAY_SIZE(mt9m112_init); i++) { - if (i2c_w2(gspca_dev, mt9m112_init[i].reg, - mt9m112_init[i].val) < 0) { - pr_err("MT9M112 sensor initialization failed\n"); - return -ENODEV; - } - } - gspca_dev->ctrl_dis = (1 << EXPOSURE_IDX) | (1 << AUTOGAIN_IDX) - | (1 << GAIN_IDX); + + i2c_w2_buf(gspca_dev, mt9m112_init, ARRAY_SIZE(mt9m112_init)); + if (gspca_dev->usb_err < 0) + pr_err("MT9M112 sensor initialization failed\n"); + + gspca_dev->ctrl_dis = (1 << EXPOSURE) | (1 << AUTOGAIN) + | (1 << GAIN); sd->hstart = 0; sd->vstart = 2; - return 0; } -static int mt9m111_init_sensor(struct gspca_dev *gspca_dev) +static void mt9m111_init_sensor(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - int i; - for (i = 0; i < ARRAY_SIZE(mt9m111_init); i++) { - if (i2c_w2(gspca_dev, mt9m111_init[i].reg, - mt9m111_init[i].val) < 0) { - pr_err("MT9M111 sensor initialization failed\n"); - return -ENODEV; - } - } - gspca_dev->ctrl_dis = (1 << EXPOSURE_IDX) | (1 << AUTOGAIN_IDX) - | (1 << GAIN_IDX); + + i2c_w2_buf(gspca_dev, mt9m111_init, ARRAY_SIZE(mt9m111_init)); + if (gspca_dev->usb_err < 0) + pr_err("MT9M111 sensor initialization failed\n"); + + gspca_dev->ctrl_dis = (1 << EXPOSURE) | (1 << AUTOGAIN) + | (1 << GAIN); sd->hstart = 0; sd->vstart = 2; - return 0; } -static int mt9m001_init_sensor(struct gspca_dev *gspca_dev) +static void mt9m001_init_sensor(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - int i; u16 id; - if (i2c_r2(gspca_dev, 0x00, &id) < 0) - return -EINVAL; + i2c_r2(gspca_dev, 0x00, &id); + if (gspca_dev->usb_err < 0) + return; /* must be 0x8411 or 0x8421 for colour sensor and 8431 for bw */ switch (id) { @@ -1494,85 +1462,78 @@ static int mt9m001_init_sensor(struct gspca_dev *gspca_dev) break; default: pr_err("No MT9M001 chip detected, ID = %x\n\n", id); - return -ENODEV; + gspca_dev->usb_err = -ENODEV; + return; } - for (i = 0; i < ARRAY_SIZE(mt9m001_init); i++) { - if (i2c_w2(gspca_dev, mt9m001_init[i].reg, - mt9m001_init[i].val) < 0) { - pr_err("MT9M001 sensor initialization failed\n"); - return -ENODEV; - } - } + i2c_w2_buf(gspca_dev, mt9m001_init, ARRAY_SIZE(mt9m001_init)); + if (gspca_dev->usb_err < 0) + pr_err("MT9M001 sensor initialization failed\n"); + /* disable hflip and vflip */ - gspca_dev->ctrl_dis = (1 << HFLIP_IDX) | (1 << VFLIP_IDX); + gspca_dev->ctrl_dis = (1 << HFLIP) | (1 << VFLIP); sd->hstart = 1; sd->vstart = 1; - return 0; } -static int hv7131r_init_sensor(struct gspca_dev *gspca_dev) +static void hv7131r_init_sensor(struct gspca_dev *gspca_dev) { - int i; struct sd *sd = (struct sd *) gspca_dev; - for (i = 0; i < ARRAY_SIZE(hv7131r_init); i++) { - if (i2c_w1(gspca_dev, hv7131r_init[i].reg, - hv7131r_init[i].val) < 0) { - pr_err("HV7131R Sensor initialization failed\n"); - return -ENODEV; - } - } + i2c_w1_buf(gspca_dev, hv7131r_init, ARRAY_SIZE(hv7131r_init)); + if (gspca_dev->usb_err < 0) + pr_err("HV7131R Sensor initialization failed\n"); + sd->hstart = 0; sd->vstart = 1; - return 0; } -static int set_cmatrix(struct gspca_dev *gspca_dev) +static void set_cmatrix(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - s32 hue_coord, hue_index = 180 + sd->hue; + int satur; + s32 hue_coord, hue_index = 180 + sd->ctrls[HUE].val; u8 cmatrix[21]; memset(cmatrix, 0, sizeof cmatrix); - cmatrix[2] = (sd->contrast * 0x25 / 0x100) + 0x26; + cmatrix[2] = (sd->ctrls[CONTRAST].val * 0x25 / 0x100) + 0x26; cmatrix[0] = 0x13 + (cmatrix[2] - 0x26) * 0x13 / 0x25; cmatrix[4] = 0x07 + (cmatrix[2] - 0x26) * 0x07 / 0x25; - cmatrix[18] = sd->brightness - 0x80; + cmatrix[18] = sd->ctrls[BRIGHTNESS].val - 0x80; - hue_coord = (hsv_red_x[hue_index] * sd->saturation) >> 8; + satur = sd->ctrls[SATURATION].val; + hue_coord = (hsv_red_x[hue_index] * satur) >> 8; cmatrix[6] = hue_coord; cmatrix[7] = (hue_coord >> 8) & 0x0f; - hue_coord = (hsv_red_y[hue_index] * sd->saturation) >> 8; + hue_coord = (hsv_red_y[hue_index] * satur) >> 8; cmatrix[8] = hue_coord; cmatrix[9] = (hue_coord >> 8) & 0x0f; - hue_coord = (hsv_green_x[hue_index] * sd->saturation) >> 8; + hue_coord = (hsv_green_x[hue_index] * satur) >> 8; cmatrix[10] = hue_coord; cmatrix[11] = (hue_coord >> 8) & 0x0f; - hue_coord = (hsv_green_y[hue_index] * sd->saturation) >> 8; + hue_coord = (hsv_green_y[hue_index] * satur) >> 8; cmatrix[12] = hue_coord; cmatrix[13] = (hue_coord >> 8) & 0x0f; - hue_coord = (hsv_blue_x[hue_index] * sd->saturation) >> 8; + hue_coord = (hsv_blue_x[hue_index] * satur) >> 8; cmatrix[14] = hue_coord; cmatrix[15] = (hue_coord >> 8) & 0x0f; - hue_coord = (hsv_blue_y[hue_index] * sd->saturation) >> 8; + hue_coord = (hsv_blue_y[hue_index] * satur) >> 8; cmatrix[16] = hue_coord; cmatrix[17] = (hue_coord >> 8) & 0x0f; - return reg_w(gspca_dev, 0x10e1, cmatrix, 21); + reg_w(gspca_dev, 0x10e1, cmatrix, 21); } -static int set_gamma(struct gspca_dev *gspca_dev) +static void set_gamma(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; u8 gamma[17]; - u8 gval = sd->gamma * 0xb8 / 0x100; - + u8 gval = sd->ctrls[GAMMA].val * 0xb8 / 0x100; gamma[0] = 0x0a; gamma[1] = 0x13 + (gval * (0xcb - 0x13) / 0xb8); @@ -1592,29 +1553,29 @@ static int set_gamma(struct gspca_dev *gspca_dev) gamma[15] = 0xea + (gval * (0xf9 - 0xea) / 0xb8); gamma[16] = 0xf5; - return reg_w(gspca_dev, 0x1190, gamma, 17); + reg_w(gspca_dev, 0x1190, gamma, 17); } -static int set_redblue(struct gspca_dev *gspca_dev) +static void set_redblue(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - reg_w1(gspca_dev, 0x118c, sd->red); - reg_w1(gspca_dev, 0x118f, sd->blue); - return 0; + + reg_w1(gspca_dev, 0x118c, sd->ctrls[RED].val); + reg_w1(gspca_dev, 0x118f, sd->ctrls[BLUE].val); } -static int set_hvflip(struct gspca_dev *gspca_dev) +static void set_hvflip(struct gspca_dev *gspca_dev) { u8 value, tslb, hflip, vflip; u16 value2; struct sd *sd = (struct sd *) gspca_dev; if ((sd->flags & FLIP_DETECT) && dmi_check_system(flip_dmi_table)) { - hflip = !sd->hflip; - vflip = !sd->vflip; + hflip = !sd->ctrls[HFLIP].val; + vflip = !sd->ctrls[VFLIP].val; } else { - hflip = sd->hflip; - vflip = sd->vflip; + hflip = sd->ctrls[HFLIP].val; + vflip = sd->ctrls[VFLIP].val; } switch (sd->sensor) { @@ -1625,8 +1586,9 @@ static int set_hvflip(struct gspca_dev *gspca_dev) if (vflip) { value |= 0x10; sd->vstart = 2; - } else + } else { sd->vstart = 3; + } reg_w1(gspca_dev, 0x1182, sd->vstart); i2c_w1(gspca_dev, 0x1e, value); break; @@ -1674,13 +1636,15 @@ static int set_hvflip(struct gspca_dev *gspca_dev) i2c_w1(gspca_dev, 0x01, value); break; } - return 0; } -static int set_exposure(struct gspca_dev *gspca_dev) +static void set_exposure(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; u8 exp[8] = {0x81, sd->i2c_addr, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e}; + int expo; + + expo = sd->ctrls[EXPOSURE].val; switch (sd->sensor) { case SENSOR_OV7660: case SENSOR_OV7670: @@ -1688,35 +1652,37 @@ static int set_exposure(struct gspca_dev *gspca_dev) case SENSOR_OV9650: exp[0] |= (3 << 4); exp[2] = 0x2d; - exp[3] = sd->exposure & 0xff; - exp[4] = sd->exposure >> 8; + exp[3] = expo; + exp[4] = expo >> 8; break; case SENSOR_MT9M001: case SENSOR_MT9V112: case SENSOR_MT9V011: exp[0] |= (3 << 4); exp[2] = 0x09; - exp[3] = sd->exposure >> 8; - exp[4] = sd->exposure & 0xff; + exp[3] = expo >> 8; + exp[4] = expo; break; case SENSOR_HV7131R: exp[0] |= (4 << 4); exp[2] = 0x25; - exp[3] = (sd->exposure >> 5) & 0xff; - exp[4] = (sd->exposure << 3) & 0xff; + exp[3] = expo >> 5; + exp[4] = expo << 3; exp[5] = 0; break; default: - return 0; + return; } i2c_w(gspca_dev, exp); - return 0; } -static int set_gain(struct gspca_dev *gspca_dev) +static void set_gain(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; u8 gain[8] = {0x81, sd->i2c_addr, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d}; + int g; + + g = sd->ctrls[GAIN].val; switch (sd->sensor) { case SENSOR_OV7660: case SENSOR_OV7670: @@ -1724,238 +1690,50 @@ static int set_gain(struct gspca_dev *gspca_dev) case SENSOR_OV9655: case SENSOR_OV9650: gain[0] |= (2 << 4); - gain[3] = ov_gain[sd->gain]; + gain[3] = ov_gain[g]; break; case SENSOR_MT9V011: gain[0] |= (3 << 4); gain[2] = 0x35; - gain[3] = micron1_gain[sd->gain] >> 8; - gain[4] = micron1_gain[sd->gain] & 0xff; + gain[3] = micron1_gain[g] >> 8; + gain[4] = micron1_gain[g]; break; case SENSOR_MT9V112: gain[0] |= (3 << 4); gain[2] = 0x2f; - gain[3] = micron1_gain[sd->gain] >> 8; - gain[4] = micron1_gain[sd->gain] & 0xff; + gain[3] = micron1_gain[g] >> 8; + gain[4] = micron1_gain[g]; break; case SENSOR_MT9M001: gain[0] |= (3 << 4); gain[2] = 0x2f; - gain[3] = micron2_gain[sd->gain] >> 8; - gain[4] = micron2_gain[sd->gain] & 0xff; + gain[3] = micron2_gain[g] >> 8; + gain[4] = micron2_gain[g]; break; case SENSOR_HV7131R: gain[0] |= (2 << 4); gain[2] = 0x30; - gain[3] = hv7131r_gain[sd->gain]; + gain[3] = hv7131r_gain[g]; break; default: - return 0; + return; } i2c_w(gspca_dev, gain); - return 0; -} - -static int sd_setbrightness(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->brightness = val; - if (gspca_dev->streaming) - return set_cmatrix(gspca_dev); - return 0; -} - -static int sd_getbrightness(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->brightness; - return 0; -} - - -static int sd_setcontrast(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->contrast = val; - if (gspca_dev->streaming) - return set_cmatrix(gspca_dev); - return 0; -} - -static int sd_getcontrast(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->contrast; - return 0; -} - -static int sd_setsaturation(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->saturation = val; - if (gspca_dev->streaming) - return set_cmatrix(gspca_dev); - return 0; } -static int sd_getsaturation(struct gspca_dev *gspca_dev, s32 *val) +static void set_quality(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - *val = sd->saturation; - return 0; -} - -static int sd_sethue(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->hue = val; - if (gspca_dev->streaming) - return set_cmatrix(gspca_dev); - return 0; -} - -static int sd_gethue(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->hue; - return 0; -} - -static int sd_setgamma(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->gamma = val; - if (gspca_dev->streaming) - return set_gamma(gspca_dev); - return 0; -} - -static int sd_getgamma(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->gamma; - return 0; -} - -static int sd_setredbalance(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->red = val; - if (gspca_dev->streaming) - return set_redblue(gspca_dev); - return 0; -} - -static int sd_getredbalance(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->red; - return 0; -} - -static int sd_setbluebalance(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->blue = val; - if (gspca_dev->streaming) - return set_redblue(gspca_dev); - return 0; -} - -static int sd_getbluebalance(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->blue; - return 0; -} - -static int sd_sethflip(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->hflip = val; - if (gspca_dev->streaming) - return set_hvflip(gspca_dev); - return 0; -} - -static int sd_gethflip(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->hflip; - return 0; -} - -static int sd_setvflip(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->vflip = val; - if (gspca_dev->streaming) - return set_hvflip(gspca_dev); - return 0; -} - -static int sd_getvflip(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->vflip; - return 0; -} - -static int sd_setexposure(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->exposure = val; - if (gspca_dev->streaming) - return set_exposure(gspca_dev); - return 0; -} - -static int sd_getexposure(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->exposure; - return 0; -} - -static int sd_setgain(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->gain = val; - if (gspca_dev->streaming) - return set_gain(gspca_dev); - return 0; -} - -static int sd_getgain(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->gain; - return 0; -} - -static int sd_setautoexposure(struct gspca_dev *gspca_dev, s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - sd->auto_exposure = val; - return 0; -} -static int sd_getautoexposure(struct gspca_dev *gspca_dev, s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - *val = sd->auto_exposure; - return 0; + jpeg_set_qual(sd->jpeg_hdr, sd->ctrls[QUALITY].val); + reg_w1(gspca_dev, 0x1061, 0x01); /* stop transfer */ + reg_w1(gspca_dev, 0x10e0, sd->fmt | 0x20); /* write QTAB */ + reg_w(gspca_dev, 0x1100, &sd->jpeg_hdr[JPEG_QT0_OFFSET], 64); + reg_w(gspca_dev, 0x1140, &sd->jpeg_hdr[JPEG_QT1_OFFSET], 64); + reg_w1(gspca_dev, 0x1061, 0x03); /* restart transfer */ + reg_w1(gspca_dev, 0x10e0, sd->fmt); + sd->fmt ^= 0x0c; /* invert QTAB use + write */ + reg_w1(gspca_dev, 0x10e0, sd->fmt); } #ifdef CONFIG_VIDEO_ADV_DEBUG @@ -1963,28 +1741,26 @@ static int sd_dbg_g_register(struct gspca_dev *gspca_dev, struct v4l2_dbg_register *reg) { struct sd *sd = (struct sd *) gspca_dev; + switch (reg->match.type) { case V4L2_CHIP_MATCH_HOST: if (reg->match.addr != 0) return -EINVAL; if (reg->reg < 0x1000 || reg->reg > 0x11ff) return -EINVAL; - if (reg_r(gspca_dev, reg->reg, 1) < 0) - return -EINVAL; + reg_r(gspca_dev, reg->reg, 1); reg->val = gspca_dev->usb_buf[0]; - return 0; + return gspca_dev->usb_err; case V4L2_CHIP_MATCH_I2C_ADDR: if (reg->match.addr != sd->i2c_addr) return -EINVAL; if (sd->sensor >= SENSOR_MT9V011 && sd->sensor <= SENSOR_MT9M112) { - if (i2c_r2(gspca_dev, reg->reg, (u16 *)®->val) < 0) - return -EINVAL; + i2c_r2(gspca_dev, reg->reg, (u16 *) ®->val); } else { - if (i2c_r1(gspca_dev, reg->reg, (u8 *)®->val) < 0) - return -EINVAL; + i2c_r1(gspca_dev, reg->reg, (u8 *) ®->val); } - return 0; + return gspca_dev->usb_err; } return -EINVAL; } @@ -1993,27 +1769,25 @@ static int sd_dbg_s_register(struct gspca_dev *gspca_dev, struct v4l2_dbg_register *reg) { struct sd *sd = (struct sd *) gspca_dev; + switch (reg->match.type) { case V4L2_CHIP_MATCH_HOST: if (reg->match.addr != 0) return -EINVAL; if (reg->reg < 0x1000 || reg->reg > 0x11ff) return -EINVAL; - if (reg_w1(gspca_dev, reg->reg, reg->val) < 0) - return -EINVAL; - return 0; + reg_w1(gspca_dev, reg->reg, reg->val); + return gspca_dev->usb_err; case V4L2_CHIP_MATCH_I2C_ADDR: if (reg->match.addr != sd->i2c_addr) return -EINVAL; if (sd->sensor >= SENSOR_MT9V011 && sd->sensor <= SENSOR_MT9M112) { - if (i2c_w2(gspca_dev, reg->reg, reg->val) < 0) - return -EINVAL; + i2c_w2(gspca_dev, reg->reg, reg->val); } else { - if (i2c_w1(gspca_dev, reg->reg, reg->val) < 0) - return -EINVAL; + i2c_w1(gspca_dev, reg->reg, reg->val); } - return 0; + return gspca_dev->usb_err; } return -EINVAL; } @@ -2050,9 +1824,9 @@ static int sd_config(struct gspca_dev *gspca_dev, cam = &gspca_dev->cam; cam->needs_full_bandwidth = 1; - sd->sensor = (id->driver_info >> 8) & 0xff; - sd->i2c_addr = id->driver_info & 0xff; - sd->flags = (id->driver_info >> 16) & 0xff; + sd->sensor = id->driver_info >> 8; + sd->i2c_addr = id->driver_info; + sd->flags = id->driver_info >> 16; switch (sd->sensor) { case SENSOR_MT9M112: @@ -2076,21 +1850,9 @@ static int sd_config(struct gspca_dev *gspca_dev, sd->older_step = 0; sd->exposure_step = 16; - sd->brightness = BRIGHTNESS_DEFAULT; - sd->contrast = CONTRAST_DEFAULT; - sd->saturation = SATURATION_DEFAULT; - sd->hue = HUE_DEFAULT; - sd->gamma = GAMMA_DEFAULT; - sd->red = RED_DEFAULT; - sd->blue = BLUE_DEFAULT; + gspca_dev->cam.ctrls = sd->ctrls; - sd->hflip = HFLIP_DEFAULT; - sd->vflip = VFLIP_DEFAULT; - sd->exposure = EXPOSURE_DEFAULT; - sd->gain = GAIN_DEFAULT; - sd->auto_exposure = AUTO_EXPOSURE_DEFAULT; - - sd->quality = 95; + INIT_WORK(&sd->work, qual_upd); return 0; } @@ -2105,9 +1867,10 @@ static int sd_init(struct gspca_dev *gspca_dev) for (i = 0; i < ARRAY_SIZE(bridge_init); i++) { value = bridge_init[i][1]; - if (reg_w(gspca_dev, bridge_init[i][0], &value, 1) < 0) { + reg_w(gspca_dev, bridge_init[i][0], &value, 1); + if (gspca_dev->usb_err < 0) { pr_err("Device initialization failed\n"); - return -ENODEV; + return gspca_dev->usb_err; } } @@ -2116,72 +1879,85 @@ static int sd_init(struct gspca_dev *gspca_dev) else reg_w1(gspca_dev, 0x1006, 0x20); - if (reg_w(gspca_dev, 0x10c0, i2c_init, 9) < 0) { + reg_w(gspca_dev, 0x10c0, i2c_init, 9); + if (gspca_dev->usb_err < 0) { pr_err("Device initialization failed\n"); - return -ENODEV; + return gspca_dev->usb_err; } switch (sd->sensor) { case SENSOR_OV9650: - if (ov9650_init_sensor(gspca_dev) < 0) - return -ENODEV; + ov9650_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("OV9650 sensor detected\n"); break; case SENSOR_OV9655: - if (ov9655_init_sensor(gspca_dev) < 0) - return -ENODEV; + ov9655_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("OV9655 sensor detected\n"); break; case SENSOR_SOI968: - if (soi968_init_sensor(gspca_dev) < 0) - return -ENODEV; + soi968_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("SOI968 sensor detected\n"); break; case SENSOR_OV7660: - if (ov7660_init_sensor(gspca_dev) < 0) - return -ENODEV; + ov7660_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("OV7660 sensor detected\n"); break; case SENSOR_OV7670: - if (ov7670_init_sensor(gspca_dev) < 0) - return -ENODEV; + ov7670_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("OV7670 sensor detected\n"); break; case SENSOR_MT9VPRB: - if (mt9v_init_sensor(gspca_dev) < 0) - return -ENODEV; + mt9v_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; + pr_info("MT9VPRB sensor detected\n"); break; case SENSOR_MT9M111: - if (mt9m111_init_sensor(gspca_dev) < 0) - return -ENODEV; + mt9m111_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("MT9M111 sensor detected\n"); break; case SENSOR_MT9M112: - if (mt9m112_init_sensor(gspca_dev) < 0) - return -ENODEV; + mt9m112_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("MT9M112 sensor detected\n"); break; case SENSOR_MT9M001: - if (mt9m001_init_sensor(gspca_dev) < 0) - return -ENODEV; + mt9m001_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; break; case SENSOR_HV7131R: - if (hv7131r_init_sensor(gspca_dev) < 0) - return -ENODEV; + hv7131r_init_sensor(gspca_dev); + if (gspca_dev->usb_err < 0) + break; pr_info("HV7131R sensor detected\n"); break; default: - pr_info("Unsupported Sensor\n"); - return -ENODEV; + pr_err("Unsupported sensor\n"); + gspca_dev->usb_err = -ENODEV; } - return 0; + return gspca_dev->usb_err; } static void configure_sensor_output(struct gspca_dev *gspca_dev, int mode) { struct sd *sd = (struct sd *) gspca_dev; u8 value; + switch (sd->sensor) { case SENSOR_SOI968: if (mode & MODE_SXGA) { @@ -2264,6 +2040,7 @@ static int sd_isoc_init(struct gspca_dev *gspca_dev) break; default: /* >= 640x480 */ gspca_dev->alt = 9; + break; } } @@ -2290,14 +2067,15 @@ static int sd_start(struct gspca_dev *gspca_dev) jpeg_define(sd->jpeg_hdr, height, width, 0x21); - jpeg_set_qual(sd->jpeg_hdr, sd->quality); + jpeg_set_qual(sd->jpeg_hdr, sd->ctrls[QUALITY].val); if (mode & MODE_RAW) fmt = 0x2d; else if (mode & MODE_JPEG) - fmt = 0x2c; + fmt = 0x24; else fmt = 0x2f; /* YUV 420 */ + sd->fmt = fmt; switch (mode & SCALE_MASK) { case SCALE_1280x1024: @@ -2334,18 +2112,37 @@ static int sd_start(struct gspca_dev *gspca_dev) set_hvflip(gspca_dev); reg_w1(gspca_dev, 0x1007, 0x20); + reg_w1(gspca_dev, 0x1061, 0x03); + + /* if JPEG, prepare the compression quality update */ + if (mode & MODE_JPEG) { + sd->pktsz = sd->npkt = 0; + sd->nchg = 0; + sd->work_thread = + create_singlethread_workqueue(KBUILD_MODNAME); + } - reg_r(gspca_dev, 0x1061, 1); - reg_w1(gspca_dev, 0x1061, gspca_dev->usb_buf[0] | 0x02); - return 0; + return gspca_dev->usb_err; } static void sd_stopN(struct gspca_dev *gspca_dev) { reg_w1(gspca_dev, 0x1007, 0x00); + reg_w1(gspca_dev, 0x1061, 0x01); +} + +/* called on streamoff with alt==0 and on disconnect */ +/* the usb_lock is held at entry - restore on exit */ +static void sd_stop0(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; - reg_r(gspca_dev, 0x1061, 1); - reg_w1(gspca_dev, 0x1061, gspca_dev->usb_buf[0] & ~0x02); + if (sd->work_thread != NULL) { + mutex_unlock(&gspca_dev->usb_lock); + destroy_workqueue(sd->work_thread); + mutex_lock(&gspca_dev->usb_lock); + sd->work_thread = NULL; + } } static void do_autoexposure(struct gspca_dev *gspca_dev, u16 avg_lum) @@ -2359,15 +2156,15 @@ static void do_autoexposure(struct gspca_dev *gspca_dev, u16 avg_lum) * and exposure steps */ if (avg_lum < MIN_AVG_LUM) { - if (sd->exposure > 0x1770) + if (sd->ctrls[EXPOSURE].val > 0x1770) return; - new_exp = sd->exposure + sd->exposure_step; + new_exp = sd->ctrls[EXPOSURE].val + sd->exposure_step; if (new_exp > 0x1770) new_exp = 0x1770; if (new_exp < 0x10) new_exp = 0x10; - sd->exposure = new_exp; + sd->ctrls[EXPOSURE].val = new_exp; set_exposure(gspca_dev); sd->older_step = sd->old_step; @@ -2379,14 +2176,14 @@ static void do_autoexposure(struct gspca_dev *gspca_dev, u16 avg_lum) sd->exposure_step += 2; } if (avg_lum > MAX_AVG_LUM) { - if (sd->exposure < 0x10) + if (sd->ctrls[EXPOSURE].val < 0x10) return; - new_exp = sd->exposure - sd->exposure_step; + new_exp = sd->ctrls[EXPOSURE].val - sd->exposure_step; if (new_exp > 0x1700) new_exp = 0x1770; if (new_exp < 0x10) new_exp = 0x10; - sd->exposure = new_exp; + sd->ctrls[EXPOSURE].val = new_exp; set_exposure(gspca_dev); sd->older_step = sd->old_step; sd->old_step = 0; @@ -2403,14 +2200,14 @@ static void do_autogain(struct gspca_dev *gspca_dev, u16 avg_lum) struct sd *sd = (struct sd *) gspca_dev; if (avg_lum < MIN_AVG_LUM) { - if (sd->gain + 1 <= 28) { - sd->gain++; + if (sd->ctrls[GAIN].val + 1 <= 28) { + sd->ctrls[GAIN].val++; set_gain(gspca_dev); } } if (avg_lum > MAX_AVG_LUM) { - if (sd->gain > 0) { - sd->gain--; + if (sd->ctrls[GAIN].val > 0) { + sd->ctrls[GAIN].val--; set_gain(gspca_dev); } } @@ -2421,7 +2218,7 @@ static void sd_dqcallback(struct gspca_dev *gspca_dev) struct sd *sd = (struct sd *) gspca_dev; int avg_lum; - if (!sd->auto_exposure) + if (!sd->ctrls[AUTOGAIN].val) return; avg_lum = atomic_read(&sd->avg_lum); @@ -2431,33 +2228,92 @@ static void sd_dqcallback(struct gspca_dev *gspca_dev) do_autoexposure(gspca_dev, avg_lum); } +/* JPEG quality update */ +/* This function is executed from a work queue. */ +static void qual_upd(struct work_struct *work) +{ + struct sd *sd = container_of(work, struct sd, work); + struct gspca_dev *gspca_dev = &sd->gspca_dev; + + mutex_lock(&gspca_dev->usb_lock); + PDEBUG(D_STREAM, "qual_upd %d%%", sd->ctrls[QUALITY].val); + set_quality(gspca_dev); + mutex_unlock(&gspca_dev->usb_lock); +} + #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) static int sd_int_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, /* interrupt packet */ int len) /* interrupt packet length */ { struct sd *sd = (struct sd *) gspca_dev; - int ret = -EINVAL; + if (!(sd->flags & HAS_NO_BUTTON) && len == 1) { - input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1); - input_sync(gspca_dev->input_dev); - input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0); - input_sync(gspca_dev->input_dev); - ret = 0; + input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1); + input_sync(gspca_dev->input_dev); + input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0); + input_sync(gspca_dev->input_dev); + return 0; } - return ret; + return -EINVAL; } #endif +/* check the JPEG compression */ +static void transfer_check(struct gspca_dev *gspca_dev, + u8 *data) +{ + struct sd *sd = (struct sd *) gspca_dev; + int new_qual, r; + + new_qual = 0; + + /* if USB error, discard the frame and decrease the quality */ + if (data[6] & 0x08) { /* USB FIFO full */ + gspca_dev->last_packet_type = DISCARD_PACKET; + new_qual = -5; + } else { + + /* else, compute the filling rate and a new JPEG quality */ + r = (sd->pktsz * 100) / + (sd->npkt * + gspca_dev->urb[0]->iso_frame_desc[0].length); + if (r >= 85) + new_qual = -3; + else if (r < 75) + new_qual = 2; + } + if (new_qual != 0) { + sd->nchg += new_qual; + if (sd->nchg < -6 || sd->nchg >= 12) { + sd->nchg = 0; + new_qual += sd->ctrls[QUALITY].val; + if (new_qual < QUALITY_MIN) + new_qual = QUALITY_MIN; + else if (new_qual > QUALITY_MAX) + new_qual = QUALITY_MAX; + if (new_qual != sd->ctrls[QUALITY].val) { + sd->ctrls[QUALITY].val = new_qual; + queue_work(sd->work_thread, &sd->work); + } + } + } else { + sd->nchg = 0; + } + sd->pktsz = sd->npkt = 0; +} + static void sd_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, /* isoc packet */ int len) /* iso packet length */ { struct sd *sd = (struct sd *) gspca_dev; - int avg_lum; + int avg_lum, is_jpeg; static u8 frame_header[] = {0xff, 0xff, 0x00, 0xc4, 0xc4, 0x96}; - if (len == 64 && memcmp(data, frame_header, 6) == 0) { + + is_jpeg = (sd->fmt & 0x03) == 0; + if (len >= 64 && memcmp(data, frame_header, 6) == 0) { avg_lum = ((data[35] >> 2) & 3) | (data[20] << 2) | (data[19] << 10); @@ -2484,12 +2340,18 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, (data[33] << 10); avg_lum >>= 9; atomic_set(&sd->avg_lum, avg_lum); + + if (is_jpeg) + transfer_check(gspca_dev, data); + gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0); - return; + len -= 64; + if (len == 0) + return; + data += 64; } if (gspca_dev->last_packet_type == LAST_PACKET) { - if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv - & MODE_JPEG) { + if (is_jpeg) { gspca_frame_add(gspca_dev, FIRST_PACKET, sd->jpeg_hdr, JPEG_HDR_SZ); gspca_frame_add(gspca_dev, INTER_PACKET, @@ -2499,13 +2361,18 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, data, len); } } else { + /* if JPEG, count the packets and their size */ + if (is_jpeg) { + sd->npkt++; + sd->pktsz += len; + } gspca_frame_add(gspca_dev, INTER_PACKET, data, len); } } /* sub-driver description */ static const struct sd_desc sd_desc = { - .name = MODULE_NAME, + .name = KBUILD_MODNAME, .ctrls = sd_ctrls, .nctrls = ARRAY_SIZE(sd_ctrls), .config = sd_config, @@ -2513,6 +2380,7 @@ static const struct sd_desc sd_desc = { .isoc_init = sd_isoc_init, .start = sd_start, .stopN = sd_stopN, + .stop0 = sd_stop0, .pkt_scan = sd_pkt_scan, #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) .int_pkt_scan = sd_int_pkt_scan, @@ -2581,7 +2449,7 @@ static int sd_probe(struct usb_interface *intf, } static struct usb_driver sd_driver = { - .name = MODULE_NAME, + .name = KBUILD_MODNAME, .id_table = device_table, .probe = sd_probe, .disconnect = gspca_disconnect, diff --git a/drivers/media/video/gspca/sonixj.c b/drivers/media/video/gspca/sonixj.c index 0c9e6ddabd2c..db8e5084df06 100644 --- a/drivers/media/video/gspca/sonixj.c +++ b/drivers/media/video/gspca/sonixj.c @@ -39,7 +39,9 @@ enum e_ctrl { BLUE, RED, GAMMA, + EXPOSURE, AUTOGAIN, + GAIN, HFLIP, VFLIP, SHARPNESS, @@ -131,7 +133,9 @@ static void setcontrast(struct gspca_dev *gspca_dev); static void setcolors(struct gspca_dev *gspca_dev); static void setredblue(struct gspca_dev *gspca_dev); static void setgamma(struct gspca_dev *gspca_dev); -static void setautogain(struct gspca_dev *gspca_dev); +static void setexposure(struct gspca_dev *gspca_dev); +static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val); +static void setgain(struct gspca_dev *gspca_dev); static void sethvflip(struct gspca_dev *gspca_dev); static void setsharpness(struct gspca_dev *gspca_dev); static void setillum(struct gspca_dev *gspca_dev); @@ -213,6 +217,18 @@ static const struct ctrl sd_ctrls[NCTRLS] = { }, .set_control = setgamma }, +[EXPOSURE] = { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure", + .minimum = 500, + .maximum = 1500, + .step = 1, + .default_value = 1024 + }, + .set_control = setexposure + }, [AUTOGAIN] = { { .id = V4L2_CID_AUTOGAIN, @@ -223,7 +239,19 @@ static const struct ctrl sd_ctrls[NCTRLS] = { .step = 1, .default_value = 1 }, - .set_control = setautogain + .set = sd_setautogain, + }, +[GAIN] = { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = 4, + .maximum = 49, + .step = 1, + .default_value = 15 + }, + .set_control = setgain }, [HFLIP] = { { @@ -290,60 +318,87 @@ static const struct ctrl sd_ctrls[NCTRLS] = { /* table of the disabled controls */ static const __u32 ctrl_dis[] = { -[SENSOR_ADCM1700] = (1 << AUTOGAIN) | +[SENSOR_ADCM1700] = (1 << EXPOSURE) | + (1 << AUTOGAIN) | + (1 << GAIN) | (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_GC0307] = (1 << HFLIP) | +[SENSOR_GC0307] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_HV7131R] = (1 << HFLIP) | +[SENSOR_HV7131R] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP) | (1 << FREQ), -[SENSOR_MI0360] = (1 << HFLIP) | +[SENSOR_MI0360] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_MI0360B] = (1 << HFLIP) | +[SENSOR_MI0360B] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_MO4000] = (1 << HFLIP) | +[SENSOR_MO4000] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_MT9V111] = (1 << HFLIP) | +[SENSOR_MT9V111] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_OM6802] = (1 << HFLIP) | +[SENSOR_OM6802] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_OV7630] = (1 << HFLIP), +[SENSOR_OV7630] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP), -[SENSOR_OV7648] = (1 << HFLIP), +[SENSOR_OV7648] = (1 << EXPOSURE) | + (1 << GAIN) | + (1 << HFLIP), -[SENSOR_OV7660] = (1 << AUTOGAIN) | +[SENSOR_OV7660] = (1 << EXPOSURE) | + (1 << AUTOGAIN) | + (1 << GAIN) | (1 << HFLIP) | (1 << VFLIP), -[SENSOR_PO1030] = (1 << AUTOGAIN) | +[SENSOR_PO1030] = (1 << EXPOSURE) | + (1 << AUTOGAIN) | + (1 << GAIN) | (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_PO2030N] = (1 << AUTOGAIN) | - (1 << FREQ), +[SENSOR_PO2030N] = (1 << FREQ), -[SENSOR_SOI768] = (1 << AUTOGAIN) | +[SENSOR_SOI768] = (1 << EXPOSURE) | + (1 << AUTOGAIN) | + (1 << GAIN) | (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), -[SENSOR_SP80708] = (1 << AUTOGAIN) | +[SENSOR_SP80708] = (1 << EXPOSURE) | + (1 << AUTOGAIN) | + (1 << GAIN) | (1 << HFLIP) | (1 << VFLIP) | (1 << FREQ), @@ -1242,14 +1297,6 @@ static const u8 po2030n_sensor_param1[][8] = { {0xa1, 0x6e, 0x05, 0x6f, 0x00, 0x00, 0x00, 0x10}, {0xa1, 0x6e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x10}, {0xa1, 0x6e, 0x07, 0x25, 0x00, 0x00, 0x00, 0x10}, - {0xa1, 0x6e, 0x15, 0x04, 0x00, 0x00, 0x00, 0x10}, - {0xc1, 0x6e, 0x16, 0x52, 0x40, 0x48, 0x00, 0x10}, -/*after start*/ - {0xa1, 0x6e, 0x15, 0x0f, 0x00, 0x00, 0x00, 0x10}, - {DELAY, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 5ms */ - {0xa1, 0x6e, 0x1a, 0x05, 0x00, 0x00, 0x00, 0x10}, - {DELAY, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 5ms */ - {0xa1, 0x6e, 0x1b, 0x53, 0x00, 0x00, 0x00, 0x10}, {} }; @@ -1858,7 +1905,7 @@ static int sd_init(struct gspca_dev *gspca_dev) return gspca_dev->usb_err; } -static u32 setexposure(struct gspca_dev *gspca_dev, +static u32 expo_adjust(struct gspca_dev *gspca_dev, u32 expo) { struct sd *sd = (struct sd *) gspca_dev; @@ -1982,28 +2029,28 @@ static void setbrightness(struct gspca_dev *gspca_dev) expo = 0x002dc6c0; else if (expo < 0x02a0) expo = 0x02a0; - sd->exposure = setexposure(gspca_dev, expo); + sd->exposure = expo_adjust(gspca_dev, expo); break; case SENSOR_MI0360: case SENSOR_MO4000: expo = brightness << 4; - sd->exposure = setexposure(gspca_dev, expo); + sd->exposure = expo_adjust(gspca_dev, expo); break; case SENSOR_MI0360B: expo = brightness << 2; - sd->exposure = setexposure(gspca_dev, expo); + sd->exposure = expo_adjust(gspca_dev, expo); break; case SENSOR_GC0307: expo = brightness; - sd->exposure = setexposure(gspca_dev, expo); + sd->exposure = expo_adjust(gspca_dev, expo); return; /* don't set the Y offset */ case SENSOR_MT9V111: expo = brightness << 2; - sd->exposure = setexposure(gspca_dev, expo); + sd->exposure = expo_adjust(gspca_dev, expo); return; /* don't set the Y offset */ case SENSOR_OM6802: expo = brightness << 2; - sd->exposure = setexposure(gspca_dev, expo); + sd->exposure = expo_adjust(gspca_dev, expo); return; /* Y offset already set */ } @@ -2112,6 +2159,23 @@ static void setgamma(struct gspca_dev *gspca_dev) reg_w(gspca_dev, 0x20, gamma, sizeof gamma); } +static void setexposure(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + + if (sd->sensor == SENSOR_PO2030N) { + u8 rexpo[] = /* 1a: expo H, 1b: expo M */ + {0xa1, 0x6e, 0x1a, 0x00, 0x40, 0x00, 0x00, 0x10}; + + rexpo[3] = sd->ctrls[EXPOSURE].val >> 8; + i2c_w8(gspca_dev, rexpo); + msleep(6); + rexpo[2] = 0x1b; + rexpo[3] = sd->ctrls[EXPOSURE].val; + i2c_w8(gspca_dev, rexpo); + } +} + static void setautogain(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; @@ -2139,6 +2203,19 @@ static void setautogain(struct gspca_dev *gspca_dev) sd->ag_cnt = -1; } +static void setgain(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + + if (sd->sensor == SENSOR_PO2030N) { + u8 rgain[] = /* 15: gain */ + {0xa1, 0x6e, 0x15, 0x00, 0x40, 0x00, 0x00, 0x15}; + + rgain[3] = sd->ctrls[GAIN].val; + i2c_w8(gspca_dev, rgain); + } +} + static void sethvflip(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; @@ -2623,6 +2700,10 @@ static int sd_start(struct gspca_dev *gspca_dev) setcontrast(gspca_dev); setcolors(gspca_dev); setautogain(gspca_dev); + if (!(gspca_dev->ctrl_inac & ((1 << EXPOSURE) | (1 << GAIN)))) { + setexposure(gspca_dev); + setgain(gspca_dev); + } setfreq(gspca_dev); sd->pktsz = sd->npkt = 0; @@ -2719,6 +2800,12 @@ static void sd_stop0(struct gspca_dev *gspca_dev) } } +/* !! coarse_grained_expo_autogain is not used !! */ +#define exp_too_low_cnt bridge +#define exp_too_high_cnt sensor + +#include "autogain_functions.h" + static void do_autogain(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; @@ -2736,6 +2823,13 @@ static void do_autogain(struct gspca_dev *gspca_dev) delta = atomic_read(&sd->avg_lum); PDEBUG(D_FRAM, "mean lum %d", delta); + + if (sd->sensor == SENSOR_PO2030N) { + auto_gain_n_exposure(gspca_dev, delta, luma_mean, luma_delta, + 15, 1024); + return; + } + if (delta < luma_mean - luma_delta || delta > luma_mean + luma_delta) { switch (sd->sensor) { @@ -2744,7 +2838,7 @@ static void do_autogain(struct gspca_dev *gspca_dev) expotimes += (luma_mean - delta) >> 6; if (expotimes < 0) expotimes = 0; - sd->exposure = setexposure(gspca_dev, + sd->exposure = expo_adjust(gspca_dev, (unsigned int) expotimes); break; case SENSOR_HV7131R: @@ -2752,7 +2846,7 @@ static void do_autogain(struct gspca_dev *gspca_dev) expotimes += (luma_mean - delta) >> 4; if (expotimes < 0) expotimes = 0; - sd->exposure = setexposure(gspca_dev, + sd->exposure = expo_adjust(gspca_dev, (unsigned int) (expotimes << 8)); break; case SENSOR_OM6802: @@ -2761,7 +2855,7 @@ static void do_autogain(struct gspca_dev *gspca_dev) expotimes += (luma_mean - delta) >> 2; if (expotimes < 0) expotimes = 0; - sd->exposure = setexposure(gspca_dev, + sd->exposure = expo_adjust(gspca_dev, (unsigned int) expotimes); setredblue(gspca_dev); break; @@ -2773,7 +2867,7 @@ static void do_autogain(struct gspca_dev *gspca_dev) expotimes += (luma_mean - delta) >> 6; if (expotimes < 0) expotimes = 0; - sd->exposure = setexposure(gspca_dev, + sd->exposure = expo_adjust(gspca_dev, (unsigned int) expotimes); setredblue(gspca_dev); break; @@ -2948,16 +3042,18 @@ marker_found: } } -static int sd_get_jcomp(struct gspca_dev *gspca_dev, - struct v4l2_jpegcompression *jcomp) +static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val) { struct sd *sd = (struct sd *) gspca_dev; - memset(jcomp, 0, sizeof *jcomp); - jcomp->quality = sd->quality; - jcomp->jpeg_markers = V4L2_JPEG_MARKER_DHT - | V4L2_JPEG_MARKER_DQT; - return 0; + sd->ctrls[AUTOGAIN].val = val; + if (val) + gspca_dev->ctrl_inac |= (1 << EXPOSURE) | (1 << GAIN); + else + gspca_dev->ctrl_inac &= ~(1 << EXPOSURE) & ~(1 << GAIN); + if (gspca_dev->streaming) + setautogain(gspca_dev); + return gspca_dev->usb_err; } static int sd_querymenu(struct gspca_dev *gspca_dev, @@ -3012,7 +3108,6 @@ static const struct sd_desc sd_desc = { .stop0 = sd_stop0, .pkt_scan = sd_pkt_scan, .dq_callback = do_autogain, - .get_jcomp = sd_get_jcomp, .querymenu = sd_querymenu, #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) .int_pkt_scan = sd_int_pkt_scan, diff --git a/drivers/media/video/gspca/stv06xx/Makefile b/drivers/media/video/gspca/stv06xx/Makefile index 5b318faf9aa8..38bc41061d83 100644 --- a/drivers/media/video/gspca/stv06xx/Makefile +++ b/drivers/media/video/gspca/stv06xx/Makefile @@ -6,5 +6,5 @@ gspca_stv06xx-objs := stv06xx.o \ stv06xx_pb0100.o \ stv06xx_st6422.o -ccflags-y += -Idrivers/media/video/gspca +ccflags-y += -I$(srctree)/drivers/media/video/gspca diff --git a/drivers/media/video/gspca/zc3xx.c b/drivers/media/video/gspca/zc3xx.c index b9e15bb0328b..7d9a4f1be9dc 100644 --- a/drivers/media/video/gspca/zc3xx.c +++ b/drivers/media/video/gspca/zc3xx.c @@ -1,7 +1,7 @@ /* - * Z-Star/Vimicro zc301/zc302p/vc30x library + * Z-Star/Vimicro zc301/zc302p/vc30x driver * - * Copyright (C) 2009-2011 Jean-Francois Moine <http://moinejf.free.fr> + * Copyright (C) 2009-2012 Jean-Francois Moine <http://moinejf.free.fr> * Copyright (C) 2004 2005 2006 Michel Xhaard mxhaard@magic.fr * * This program is free software; you can redistribute it and/or modify @@ -21,8 +21,6 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define MODULE_NAME "zc3xx" - #include <linux/input.h> #include "gspca.h" #include "jpeg.h" @@ -34,7 +32,7 @@ MODULE_LICENSE("GPL"); static int force_sensor = -1; -#define QUANT_VAL 1 /* quantization table */ +#define REG08_DEF 3 /* default JPEG compression (70%) */ #include "zc3xx-reg.h" /* controls */ @@ -46,6 +44,7 @@ enum e_ctrl { AUTOGAIN, LIGHTFREQ, SHARPNESS, + QUALITY, NCTRLS /* number of controls */ }; @@ -57,10 +56,10 @@ struct sd { struct gspca_ctrl ctrls[NCTRLS]; - u8 quality; /* image quality */ -#define QUALITY_MIN 50 -#define QUALITY_MAX 80 -#define QUALITY_DEF 70 + struct work_struct work; + struct workqueue_struct *work_thread; + + u8 reg08; /* webcam compression quality */ u8 bridge; u8 sensor; /* Type of image sensor chip */ @@ -101,6 +100,7 @@ static void setexposure(struct gspca_dev *gspca_dev); static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val); static void setlightfreq(struct gspca_dev *gspca_dev); static void setsharpness(struct gspca_dev *gspca_dev); +static int sd_setquality(struct gspca_dev *gspca_dev, __s32 val); static const struct ctrl sd_ctrls[NCTRLS] = { [BRIGHTNESS] = { @@ -188,6 +188,18 @@ static const struct ctrl sd_ctrls[NCTRLS] = { }, .set_control = setsharpness }, +[QUALITY] = { + { + .id = V4L2_CID_JPEG_COMPRESSION_QUALITY, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Compression Quality", + .minimum = 40, + .maximum = 70, + .step = 1, + .default_value = 70 /* updated in sd_init() */ + }, + .set = sd_setquality + }, }; static const struct v4l2_pix_format vga_mode[] = { @@ -229,6 +241,9 @@ static const struct v4l2_pix_format sif_mode[] = { .priv = 0}, }; +/* bridge reg08 -> JPEG quality conversion table */ +static u8 jpeg_qual[] = {40, 50, 60, 70, /*80*/}; + /* usb exchanges */ struct usb_action { u8 req; @@ -3894,7 +3909,6 @@ static const struct usb_action pas106b_Initial[] = { /* 352x288 */ /* Gains */ {0xa0, 0x20, ZC3XX_R1A9_DIGITALLIMITDIFF}, {0xa0, 0x26, ZC3XX_R1AA_DIGITALGAINSTEP}, - {0xa0, 0xa0, ZC3XX_R11D_GLOBALGAIN}, {0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN}, /* Auto correction */ {0xa0, 0x40, ZC3XX_R180_AUTOCORRECTENABLE}, @@ -5640,7 +5654,7 @@ static const struct usb_action gc0303_NoFlikerScale[] = { {} }; -static u8 reg_r_i(struct gspca_dev *gspca_dev, +static u8 reg_r(struct gspca_dev *gspca_dev, u16 index) { int ret; @@ -5655,24 +5669,14 @@ static u8 reg_r_i(struct gspca_dev *gspca_dev, index, gspca_dev->usb_buf, 1, 500); if (ret < 0) { - pr_err("reg_r_i err %d\n", ret); + pr_err("reg_r err %d\n", ret); gspca_dev->usb_err = ret; return 0; } return gspca_dev->usb_buf[0]; } -static u8 reg_r(struct gspca_dev *gspca_dev, - u16 index) -{ - u8 ret; - - ret = reg_r_i(gspca_dev, index); - PDEBUG(D_USBI, "reg r [%04x] -> %02x", index, ret); - return ret; -} - -static void reg_w_i(struct gspca_dev *gspca_dev, +static void reg_w(struct gspca_dev *gspca_dev, u8 value, u16 index) { @@ -5692,14 +5696,6 @@ static void reg_w_i(struct gspca_dev *gspca_dev, } } -static void reg_w(struct gspca_dev *gspca_dev, - u8 value, - u16 index) -{ - PDEBUG(D_USBO, "reg w [%04x] = %02x", index, value); - reg_w_i(gspca_dev, value, index); -} - static u16 i2c_read(struct gspca_dev *gspca_dev, u8 reg) { @@ -5708,16 +5704,14 @@ static u16 i2c_read(struct gspca_dev *gspca_dev, if (gspca_dev->usb_err < 0) return 0; - reg_w_i(gspca_dev, reg, 0x0092); - reg_w_i(gspca_dev, 0x02, 0x0090); /* <- read command */ + reg_w(gspca_dev, reg, 0x0092); + reg_w(gspca_dev, 0x02, 0x0090); /* <- read command */ msleep(20); - retbyte = reg_r_i(gspca_dev, 0x0091); /* read status */ + retbyte = reg_r(gspca_dev, 0x0091); /* read status */ if (retbyte != 0x00) pr_err("i2c_r status error %02x\n", retbyte); - retval = reg_r_i(gspca_dev, 0x0095); /* read Lowbyte */ - retval |= reg_r_i(gspca_dev, 0x0096) << 8; /* read Hightbyte */ - PDEBUG(D_USBI, "i2c r [%02x] -> %04x (%02x)", - reg, retval, retbyte); + retval = reg_r(gspca_dev, 0x0095); /* read Lowbyte */ + retval |= reg_r(gspca_dev, 0x0096) << 8; /* read Hightbyte */ return retval; } @@ -5730,16 +5724,14 @@ static u8 i2c_write(struct gspca_dev *gspca_dev, if (gspca_dev->usb_err < 0) return 0; - reg_w_i(gspca_dev, reg, 0x92); - reg_w_i(gspca_dev, valL, 0x93); - reg_w_i(gspca_dev, valH, 0x94); - reg_w_i(gspca_dev, 0x01, 0x90); /* <- write command */ + reg_w(gspca_dev, reg, 0x92); + reg_w(gspca_dev, valL, 0x93); + reg_w(gspca_dev, valH, 0x94); + reg_w(gspca_dev, 0x01, 0x90); /* <- write command */ msleep(1); - retbyte = reg_r_i(gspca_dev, 0x0091); /* read status */ + retbyte = reg_r(gspca_dev, 0x0091); /* read status */ if (retbyte != 0x00) pr_err("i2c_w status error %02x\n", retbyte); - PDEBUG(D_USBO, "i2c w [%02x] = %02x%02x (%02x)", - reg, valH, valL, retbyte); return retbyte; } @@ -5906,6 +5898,8 @@ static void getexposure(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; + if (sd->sensor != SENSOR_HV7131R) + return; sd->ctrls[EXPOSURE].val = (i2c_read(gspca_dev, 0x25) << 9) | (i2c_read(gspca_dev, 0x26) << 1) | (i2c_read(gspca_dev, 0x27) >> 7); @@ -5916,6 +5910,8 @@ static void setexposure(struct gspca_dev *gspca_dev) struct sd *sd = (struct sd *) gspca_dev; int val; + if (sd->sensor != SENSOR_HV7131R) + return; val = sd->ctrls[EXPOSURE].val; i2c_write(gspca_dev, 0x25, val >> 9, 0x00); i2c_write(gspca_dev, 0x26, val >> 1, 0x00); @@ -5925,32 +5921,20 @@ static void setexposure(struct gspca_dev *gspca_dev) static void setquality(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; - u8 frxt; + s8 reg07; + reg07 = 0; switch (sd->sensor) { - case SENSOR_ADCM2700: - case SENSOR_GC0305: - case SENSOR_HV7131B: - case SENSOR_HV7131R: case SENSOR_OV7620: + reg07 = 0x30; + break; + case SENSOR_HV7131R: case SENSOR_PAS202B: - case SENSOR_PO2030: - return; + return; /* done by work queue */ } -/*fixme: is it really 0008 0007 0018 for all other sensors? */ - reg_w(gspca_dev, QUANT_VAL, 0x0008); - frxt = 0x30; - reg_w(gspca_dev, frxt, 0x0007); -#if QUANT_VAL == 0 || QUANT_VAL == 1 || QUANT_VAL == 2 - frxt = 0xff; -#elif QUANT_VAL == 3 - frxt = 0xf0; -#elif QUANT_VAL == 4 - frxt = 0xe0; -#else - frxt = 0x20; -#endif - reg_w(gspca_dev, frxt, 0x0018); + reg_w(gspca_dev, sd->reg08, ZC3XX_R008_CLOCKSETTING); + if (reg07 != 0) + reg_w(gspca_dev, reg07, 0x0007); } /* Matches the sensor's internal frame rate to the lighting frequency. @@ -6084,6 +6068,115 @@ static void setautogain(struct gspca_dev *gspca_dev) reg_w(gspca_dev, autoval, 0x0180); } +/* update the transfer parameters */ +/* This function is executed from a work queue. */ +/* The exact use of the bridge registers 07 and 08 is not known. + * The following algorithm has been adapted from ms-win traces */ +static void transfer_update(struct work_struct *work) +{ + struct sd *sd = container_of(work, struct sd, work); + struct gspca_dev *gspca_dev = &sd->gspca_dev; + int change, good; + u8 reg07, reg11; + + /* synchronize with the main driver and initialize the registers */ + mutex_lock(&gspca_dev->usb_lock); + reg07 = 0; /* max */ + reg_w(gspca_dev, reg07, 0x0007); + reg_w(gspca_dev, sd->reg08, ZC3XX_R008_CLOCKSETTING); + mutex_unlock(&gspca_dev->usb_lock); + + good = 0; + for (;;) { + msleep(100); + + /* get the transfer status */ + /* the bit 0 of the bridge register 11 indicates overflow */ + mutex_lock(&gspca_dev->usb_lock); + if (!gspca_dev->present || !gspca_dev->streaming) + goto err; + reg11 = reg_r(gspca_dev, 0x0011); + if (gspca_dev->usb_err < 0 + || !gspca_dev->present || !gspca_dev->streaming) + goto err; + + change = reg11 & 0x01; + if (change) { /* overflow */ + switch (reg07) { + case 0: /* max */ + reg07 = sd->sensor == SENSOR_HV7131R + ? 0x30 : 0x32; + if (sd->reg08 != 0) { + change = 3; + sd->reg08--; + } + break; + case 0x32: + reg07 -= 4; + break; + default: + reg07 -= 2; + break; + case 2: + change = 0; /* already min */ + break; + } + good = 0; + } else { /* no overflow */ + if (reg07 != 0) { /* if not max */ + good++; + if (good >= 10) { + good = 0; + change = 1; + reg07 += 2; + switch (reg07) { + case 0x30: + if (sd->sensor == SENSOR_PAS202B) + reg07 += 2; + break; + case 0x32: + case 0x34: + reg07 = 0; + break; + } + } + } else { /* reg07 max */ + if (sd->reg08 < sizeof jpeg_qual - 1) { + good++; + if (good > 10) { + sd->reg08++; + change = 2; + } + } + } + } + if (change) { + if (change & 1) { + reg_w(gspca_dev, reg07, 0x0007); + if (gspca_dev->usb_err < 0 + || !gspca_dev->present + || !gspca_dev->streaming) + goto err; + } + if (change & 2) { + reg_w(gspca_dev, sd->reg08, + ZC3XX_R008_CLOCKSETTING); + if (gspca_dev->usb_err < 0 + || !gspca_dev->present + || !gspca_dev->streaming) + goto err; + sd->ctrls[QUALITY].val = jpeg_qual[sd->reg08]; + jpeg_set_qual(sd->jpeg_hdr, + jpeg_qual[sd->reg08]); + } + } + mutex_unlock(&gspca_dev->usb_lock); + } + return; +err: + mutex_unlock(&gspca_dev->usb_lock); +} + static void send_unknown(struct gspca_dev *gspca_dev, int sensor) { reg_w(gspca_dev, 0x01, 0x0000); /* bridge reset */ @@ -6411,7 +6504,9 @@ static int sd_config(struct gspca_dev *gspca_dev, sd->sensor = id->driver_info; gspca_dev->cam.ctrls = sd->ctrls; - sd->quality = QUALITY_DEF; + sd->reg08 = REG08_DEF; + + INIT_WORK(&sd->work, transfer_update); return 0; } @@ -6464,6 +6559,27 @@ static int sd_init(struct gspca_dev *gspca_dev) [SENSOR_PO2030] = 1, [SENSOR_TAS5130C] = 1, }; + static const u8 reg08_tb[SENSOR_MAX] = { + [SENSOR_ADCM2700] = 1, + [SENSOR_CS2102] = 3, + [SENSOR_CS2102K] = 3, + [SENSOR_GC0303] = 2, + [SENSOR_GC0305] = 3, + [SENSOR_HDCS2020] = 1, + [SENSOR_HV7131B] = 3, + [SENSOR_HV7131R] = 3, + [SENSOR_ICM105A] = 3, + [SENSOR_MC501CB] = 3, + [SENSOR_MT9V111_1] = 3, + [SENSOR_MT9V111_3] = 3, + [SENSOR_OV7620] = 1, + [SENSOR_OV7630C] = 3, + [SENSOR_PAS106] = 3, + [SENSOR_PAS202B] = 3, + [SENSOR_PB0330] = 3, + [SENSOR_PO2030] = 2, + [SENSOR_TAS5130C] = 3, + }; sensor = zcxx_probeSensor(gspca_dev); if (sensor >= 0) @@ -6528,7 +6644,6 @@ static int sd_init(struct gspca_dev *gspca_dev) case 0x0e: PDEBUG(D_PROBE, "Find Sensor PAS202B"); sd->sensor = SENSOR_PAS202B; -/* sd->sharpness = 1; */ break; case 0x0f: PDEBUG(D_PROBE, "Find Sensor PAS106"); @@ -6616,13 +6731,21 @@ static int sd_init(struct gspca_dev *gspca_dev) } sd->ctrls[GAMMA].def = gamma[sd->sensor]; + sd->reg08 = reg08_tb[sd->sensor]; + sd->ctrls[QUALITY].def = jpeg_qual[sd->reg08]; + sd->ctrls[QUALITY].min = jpeg_qual[0]; + sd->ctrls[QUALITY].max = jpeg_qual[ARRAY_SIZE(jpeg_qual) - 1]; switch (sd->sensor) { case SENSOR_HV7131R: + gspca_dev->ctrl_dis = (1 << QUALITY); break; case SENSOR_OV7630C: gspca_dev->ctrl_dis = (1 << LIGHTFREQ) | (1 << EXPOSURE); break; + case SENSOR_PAS202B: + gspca_dev->ctrl_dis = (1 << QUALITY) | (1 << EXPOSURE); + break; default: gspca_dev->ctrl_dis = (1 << EXPOSURE); break; @@ -6685,7 +6808,6 @@ static int sd_start(struct gspca_dev *gspca_dev) /* create the JPEG header */ jpeg_define(sd->jpeg_hdr, gspca_dev->height, gspca_dev->width, 0x21); /* JPEG 422 */ - jpeg_set_qual(sd->jpeg_hdr, sd->quality); mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv; switch (sd->sensor) { @@ -6761,10 +6883,9 @@ static int sd_start(struct gspca_dev *gspca_dev) reg_r(gspca_dev, 0x0180); /* from win */ reg_w(gspca_dev, 0x00, 0x0180); break; - default: - setquality(gspca_dev); - break; } + setquality(gspca_dev); + jpeg_set_qual(sd->jpeg_hdr, jpeg_qual[sd->reg08]); setlightfreq(gspca_dev); switch (sd->sensor) { @@ -6776,8 +6897,7 @@ static int sd_start(struct gspca_dev *gspca_dev) reg_w(gspca_dev, 0x40, 0x0117); break; case SENSOR_HV7131R: - if (!sd->ctrls[AUTOGAIN].val) - setexposure(gspca_dev); + setexposure(gspca_dev); reg_w(gspca_dev, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN); break; case SENSOR_GC0305: @@ -6802,13 +6922,19 @@ static int sd_start(struct gspca_dev *gspca_dev) } setautogain(gspca_dev); - switch (sd->sensor) { - case SENSOR_PO2030: - msleep(50); - reg_w(gspca_dev, 0x00, 0x0007); /* (from win traces) */ - reg_w(gspca_dev, 0x02, ZC3XX_R008_CLOCKSETTING); - break; + + /* start the transfer update thread if needed */ + if (gspca_dev->usb_err >= 0) { + switch (sd->sensor) { + case SENSOR_HV7131R: + case SENSOR_PAS202B: + sd->work_thread = + create_singlethread_workqueue(KBUILD_MODNAME); + queue_work(sd->work_thread, &sd->work); + break; + } } + return gspca_dev->usb_err; } @@ -6817,6 +6943,12 @@ static void sd_stop0(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; + if (sd->work_thread != NULL) { + mutex_unlock(&gspca_dev->usb_lock); + destroy_workqueue(sd->work_thread); + mutex_lock(&gspca_dev->usb_lock); + sd->work_thread = NULL; + } if (!gspca_dev->present) return; send_unknown(gspca_dev, sd->sensor); @@ -6893,19 +7025,33 @@ static int sd_querymenu(struct gspca_dev *gspca_dev, return -EINVAL; } +static int sd_setquality(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + int i; + + for (i = 0; i < ARRAY_SIZE(jpeg_qual) - 1; i++) { + if (val <= jpeg_qual[i]) + break; + } + if (i > 0 + && i == sd->reg08 + && val < jpeg_qual[sd->reg08]) + i--; + sd->reg08 = i; + sd->ctrls[QUALITY].val = jpeg_qual[i]; + if (gspca_dev->streaming) + jpeg_set_qual(sd->jpeg_hdr, sd->ctrls[QUALITY].val); + return gspca_dev->usb_err; +} + static int sd_set_jcomp(struct gspca_dev *gspca_dev, struct v4l2_jpegcompression *jcomp) { struct sd *sd = (struct sd *) gspca_dev; - if (jcomp->quality < QUALITY_MIN) - sd->quality = QUALITY_MIN; - else if (jcomp->quality > QUALITY_MAX) - sd->quality = QUALITY_MAX; - else - sd->quality = jcomp->quality; - if (gspca_dev->streaming) - jpeg_set_qual(sd->jpeg_hdr, sd->quality); + sd_setquality(gspca_dev, jcomp->quality); + jcomp->quality = sd->ctrls[QUALITY].val; return gspca_dev->usb_err; } @@ -6915,7 +7061,7 @@ static int sd_get_jcomp(struct gspca_dev *gspca_dev, struct sd *sd = (struct sd *) gspca_dev; memset(jcomp, 0, sizeof *jcomp); - jcomp->quality = sd->quality; + jcomp->quality = sd->ctrls[QUALITY].val; jcomp->jpeg_markers = V4L2_JPEG_MARKER_DHT | V4L2_JPEG_MARKER_DQT; return 0; @@ -6938,7 +7084,7 @@ static int sd_int_pkt_scan(struct gspca_dev *gspca_dev, #endif static const struct sd_desc sd_desc = { - .name = MODULE_NAME, + .name = KBUILD_MODNAME, .ctrls = sd_ctrls, .nctrls = ARRAY_SIZE(sd_ctrls), .config = sd_config, @@ -7023,7 +7169,7 @@ static int sd_probe(struct usb_interface *intf, /* USB driver */ static struct usb_driver sd_driver = { - .name = MODULE_NAME, + .name = KBUILD_MODNAME, .id_table = device_table, .probe = sd_probe, .disconnect = gspca_disconnect, diff --git a/drivers/media/video/imx074.c b/drivers/media/video/imx074.c index eec75bb57203..351e9bafe8fe 100644 --- a/drivers/media/video/imx074.c +++ b/drivers/media/video/imx074.c @@ -468,18 +468,7 @@ static struct i2c_driver imx074_i2c_driver = { .id_table = imx074_id, }; -static int __init imx074_mod_init(void) -{ - return i2c_add_driver(&imx074_i2c_driver); -} - -static void __exit imx074_mod_exit(void) -{ - i2c_del_driver(&imx074_i2c_driver); -} - -module_init(imx074_mod_init); -module_exit(imx074_mod_exit); +module_i2c_driver(imx074_i2c_driver); MODULE_DESCRIPTION("Sony IMX074 Camera driver"); MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); diff --git a/drivers/media/video/indycam.c b/drivers/media/video/indycam.c index e5ed4db32e7b..548236333cce 100644 --- a/drivers/media/video/indycam.c +++ b/drivers/media/video/indycam.c @@ -387,15 +387,4 @@ static struct i2c_driver indycam_driver = { .id_table = indycam_id, }; -static __init int init_indycam(void) -{ - return i2c_add_driver(&indycam_driver); -} - -static __exit void exit_indycam(void) -{ - i2c_del_driver(&indycam_driver); -} - -module_init(init_indycam); -module_exit(exit_indycam); +module_i2c_driver(indycam_driver); diff --git a/drivers/media/video/ir-kbd-i2c.c b/drivers/media/video/ir-kbd-i2c.c index a7c41d32f414..04f192a0398a 100644 --- a/drivers/media/video/ir-kbd-i2c.c +++ b/drivers/media/video/ir-kbd-i2c.c @@ -471,7 +471,7 @@ static const struct i2c_device_id ir_kbd_id[] = { { } }; -static struct i2c_driver driver = { +static struct i2c_driver ir_kbd_driver = { .driver = { .name = "ir-kbd-i2c", }, @@ -480,21 +480,10 @@ static struct i2c_driver driver = { .id_table = ir_kbd_id, }; +module_i2c_driver(ir_kbd_driver); + /* ----------------------------------------------------------------------- */ MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, Ulrich Mueller"); MODULE_DESCRIPTION("input driver for i2c IR remote controls"); MODULE_LICENSE("GPL"); - -static int __init ir_init(void) -{ - return i2c_add_driver(&driver); -} - -static void __exit ir_fini(void) -{ - i2c_del_driver(&driver); -} - -module_init(ir_init); -module_exit(ir_fini); diff --git a/drivers/media/video/ivtv/Makefile b/drivers/media/video/ivtv/Makefile index 71ab76a5ab26..77de8a45b46f 100644 --- a/drivers/media/video/ivtv/Makefile +++ b/drivers/media/video/ivtv/Makefile @@ -7,8 +7,8 @@ ivtv-objs := ivtv-routing.o ivtv-cards.o ivtv-controls.o \ obj-$(CONFIG_VIDEO_IVTV) += ivtv.o obj-$(CONFIG_VIDEO_FB_IVTV) += ivtvfb.o -ccflags-y += -Idrivers/media/video -ccflags-y += -Idrivers/media/common/tuners -ccflags-y += -Idrivers/media/dvb/dvb-core -ccflags-y += -Idrivers/media/dvb/frontends +ccflags-y += -I$(srctree)/drivers/media/video +ccflags-y += -I$(srctree)/drivers/media/common/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb/frontends diff --git a/drivers/media/video/ivtv/ivtv-controls.c b/drivers/media/video/ivtv/ivtv-controls.c index b31ee1bceef8..c60424601cb9 100644 --- a/drivers/media/video/ivtv/ivtv-controls.c +++ b/drivers/media/video/ivtv/ivtv-controls.c @@ -21,6 +21,7 @@ #include "ivtv-driver.h" #include "ivtv-ioctl.h" #include "ivtv-controls.h" +#include "ivtv-mailbox.h" static int ivtv_s_stream_vbi_fmt(struct cx2341x_handler *cxhdl, u32 fmt) { @@ -99,3 +100,64 @@ struct cx2341x_handler_ops ivtv_cxhdl_ops = { .s_video_encoding = ivtv_s_video_encoding, .s_stream_vbi_fmt = ivtv_s_stream_vbi_fmt, }; + +int ivtv_g_pts_frame(struct ivtv *itv, s64 *pts, s64 *frame) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + + if (test_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags)) { + *pts = (s64)((u64)itv->last_dec_timing[2] << 32) | + (u64)itv->last_dec_timing[1]; + *frame = itv->last_dec_timing[0]; + return 0; + } + *pts = 0; + *frame = 0; + if (atomic_read(&itv->decoding)) { + if (ivtv_api(itv, CX2341X_DEC_GET_TIMING_INFO, 5, data)) { + IVTV_DEBUG_WARN("GET_TIMING: couldn't read clock\n"); + return -EIO; + } + memcpy(itv->last_dec_timing, data, sizeof(itv->last_dec_timing)); + set_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); + *pts = (s64)((u64) data[2] << 32) | (u64) data[1]; + *frame = data[0]; + /*timing->scr = (u64) (((u64) data[4] << 32) | (u64) (data[3]));*/ + } + return 0; +} + +static int ivtv_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ivtv *itv = container_of(ctrl->handler, struct ivtv, cxhdl.hdl); + + switch (ctrl->id) { + /* V4L2_CID_MPEG_VIDEO_DEC_PTS and V4L2_CID_MPEG_VIDEO_DEC_FRAME + control cluster */ + case V4L2_CID_MPEG_VIDEO_DEC_PTS: + return ivtv_g_pts_frame(itv, &itv->ctrl_pts->val64, + &itv->ctrl_frame->val64); + } + return 0; +} + +static int ivtv_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ivtv *itv = container_of(ctrl->handler, struct ivtv, cxhdl.hdl); + + switch (ctrl->id) { + /* V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK and MULTILINGUAL_PLAYBACK + control cluster */ + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: + itv->audio_stereo_mode = itv->ctrl_audio_playback->val - 1; + itv->audio_bilingual_mode = itv->ctrl_audio_multilingual_playback->val - 1; + ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); + break; + } + return 0; +} + +const struct v4l2_ctrl_ops ivtv_hdl_out_ops = { + .s_ctrl = ivtv_s_ctrl, + .g_volatile_ctrl = ivtv_g_volatile_ctrl, +}; diff --git a/drivers/media/video/ivtv/ivtv-controls.h b/drivers/media/video/ivtv/ivtv-controls.h index d12893dd0183..3999e6358312 100644 --- a/drivers/media/video/ivtv/ivtv-controls.h +++ b/drivers/media/video/ivtv/ivtv-controls.h @@ -22,5 +22,7 @@ #define IVTV_CONTROLS_H extern struct cx2341x_handler_ops ivtv_cxhdl_ops; +extern const struct v4l2_ctrl_ops ivtv_hdl_out_ops; +int ivtv_g_pts_frame(struct ivtv *itv, s64 *pts, s64 *frame); #endif diff --git a/drivers/media/video/ivtv/ivtv-driver.c b/drivers/media/video/ivtv/ivtv-driver.c index 3949b7dc2368..679262ed13bc 100644 --- a/drivers/media/video/ivtv/ivtv-driver.c +++ b/drivers/media/video/ivtv/ivtv-driver.c @@ -55,7 +55,7 @@ #include "ivtv-routing.h" #include "ivtv-controls.h" #include "ivtv-gpio.h" - +#include <linux/dma-mapping.h> #include <media/tveeprom.h> #include <media/saa7115.h> #include <media/v4l2-chip-ident.h> @@ -99,7 +99,7 @@ static int i2c_clock_period[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, static unsigned int cardtype_c = 1; static unsigned int tuner_c = 1; -static bool radio_c = 1; +static int radio_c = 1; static unsigned int i2c_clock_period_c = 1; static char pal[] = "---"; static char secam[] = "--"; @@ -139,7 +139,7 @@ static int tunertype = -1; static int newi2c = -1; module_param_array(tuner, int, &tuner_c, 0644); -module_param_array(radio, bool, &radio_c, 0644); +module_param_array(radio, int, &radio_c, 0644); module_param_array(cardtype, int, &cardtype_c, 0644); module_param_string(pal, pal, sizeof(pal), 0644); module_param_string(secam, secam, sizeof(secam), 0644); @@ -744,8 +744,6 @@ static int __devinit ivtv_init_struct1(struct ivtv *itv) itv->cur_dma_stream = -1; itv->cur_pio_stream = -1; - itv->audio_stereo_mode = AUDIO_STEREO; - itv->audio_bilingual_mode = AUDIO_MONO_LEFT; /* Ctrls */ itv->speed = 1000; @@ -815,7 +813,7 @@ static int ivtv_setup_pci(struct ivtv *itv, struct pci_dev *pdev, IVTV_ERR("Can't enable device!\n"); return -EIO; } - if (pci_set_dma_mask(pdev, 0xffffffff)) { + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { IVTV_ERR("No suitable DMA available.\n"); return -EIO; } @@ -1200,6 +1198,32 @@ static int __devinit ivtv_probe(struct pci_dev *pdev, itv->tuner_std = itv->std; if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + struct v4l2_ctrl_handler *hdl = itv->v4l2_dev.ctrl_handler; + + itv->ctrl_pts = v4l2_ctrl_new_std(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_VIDEO_DEC_PTS, 0, 0, 1, 0); + itv->ctrl_frame = v4l2_ctrl_new_std(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_VIDEO_DEC_FRAME, 0, 0x7fffffff, 1, 0); + /* Note: V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO is not supported, + mask that menu item. */ + itv->ctrl_audio_playback = + v4l2_ctrl_new_std_menu(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO, + 1 << V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_STEREO); + itv->ctrl_audio_multilingual_playback = + v4l2_ctrl_new_std_menu(hdl, &ivtv_hdl_out_ops, + V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_SWAPPED_STEREO, + 1 << V4L2_MPEG_AUDIO_DEC_PLAYBACK_AUTO, + V4L2_MPEG_AUDIO_DEC_PLAYBACK_LEFT); + if (hdl->error) { + retval = hdl->error; + goto free_i2c; + } + v4l2_ctrl_cluster(2, &itv->ctrl_pts); + v4l2_ctrl_cluster(2, &itv->ctrl_audio_playback); ivtv_call_all(itv, video, s_std_output, itv->std); /* Turn off the output signal. The mpeg decoder is not yet active so without this you would get a green image until the @@ -1236,6 +1260,7 @@ free_streams: free_irq: free_irq(itv->pdev->irq, (void *)itv); free_i2c: + v4l2_ctrl_handler_free(&itv->cxhdl.hdl); exit_ivtv_i2c(itv); free_io: ivtv_iounmap(itv); @@ -1375,7 +1400,7 @@ static void ivtv_remove(struct pci_dev *pdev) else type = IVTV_DEC_STREAM_TYPE_MPG; ivtv_stop_v4l2_decode_stream(&itv->streams[type], - VIDEO_CMD_STOP_TO_BLACK | VIDEO_CMD_STOP_IMMEDIATELY, 0); + V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY, 0); } ivtv_halt_firmware(itv); } @@ -1391,6 +1416,8 @@ static void ivtv_remove(struct pci_dev *pdev) ivtv_streams_cleanup(itv, 1); ivtv_udma_free(itv); + v4l2_ctrl_handler_free(&itv->cxhdl.hdl); + exit_ivtv_i2c(itv); free_irq(itv->pdev->irq, (void *)itv); diff --git a/drivers/media/video/ivtv/ivtv-driver.h b/drivers/media/video/ivtv/ivtv-driver.h index 06f3d78389bf..f767df943954 100644 --- a/drivers/media/video/ivtv/ivtv-driver.h +++ b/drivers/media/video/ivtv/ivtv-driver.h @@ -331,6 +331,7 @@ struct ivtv_stream { struct ivtv *itv; /* for ease of use */ const char *name; /* name of the stream */ int type; /* stream type */ + u32 caps; /* V4L2 capabilities */ struct v4l2_fh *fh; /* pointer to the streaming filehandle */ spinlock_t qlock; /* locks access to the queues */ @@ -630,6 +631,16 @@ struct ivtv { struct v4l2_device v4l2_dev; struct cx2341x_handler cxhdl; + struct { + /* PTS/Frame count control cluster */ + struct v4l2_ctrl *ctrl_pts; + struct v4l2_ctrl *ctrl_frame; + }; + struct { + /* Audio Playback control cluster */ + struct v4l2_ctrl *ctrl_audio_playback; + struct v4l2_ctrl *ctrl_audio_multilingual_playback; + }; struct v4l2_ctrl_handler hdl_gpio; struct v4l2_subdev sd_gpio; /* GPIO sub-device */ u16 instance; @@ -649,7 +660,6 @@ struct ivtv { u8 audio_stereo_mode; /* decoder setting how to handle stereo MPEG audio */ u8 audio_bilingual_mode; /* decoder setting how to handle bilingual MPEG audio */ - /* Locking */ spinlock_t lock; /* lock access to this struct */ struct mutex serialize_lock; /* mutex used to serialize open/close/start/stop/ioctl operations */ diff --git a/drivers/media/video/ivtv/ivtv-fileops.c b/drivers/media/video/ivtv/ivtv-fileops.c index 2cd6c89b7d91..c9663e885b9f 100644 --- a/drivers/media/video/ivtv/ivtv-fileops.c +++ b/drivers/media/video/ivtv/ivtv-fileops.c @@ -900,7 +900,7 @@ int ivtv_v4l2_close(struct file *filp) if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) { struct ivtv_stream *s_vout = &itv->streams[IVTV_DEC_STREAM_TYPE_VOUT]; - ivtv_stop_decoding(id, VIDEO_CMD_STOP_TO_BLACK | VIDEO_CMD_STOP_IMMEDIATELY, 0); + ivtv_stop_decoding(id, V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY, 0); /* If all output streams are closed, and if the user doesn't have IVTV_DEC_STREAM_TYPE_VOUT open, then disable CC on TV-out. */ diff --git a/drivers/media/video/ivtv/ivtv-ioctl.c b/drivers/media/video/ivtv/ivtv-ioctl.c index c4bc48143098..5452beef8e11 100644 --- a/drivers/media/video/ivtv/ivtv-ioctl.c +++ b/drivers/media/video/ivtv/ivtv-ioctl.c @@ -246,34 +246,40 @@ static int ivtv_validate_speed(int cur_speed, int new_speed) } static int ivtv_video_command(struct ivtv *itv, struct ivtv_open_id *id, - struct video_command *vc, int try) + struct v4l2_decoder_cmd *dc, int try) { struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) return -EINVAL; - switch (vc->cmd) { - case VIDEO_CMD_PLAY: { - vc->flags = 0; - vc->play.speed = ivtv_validate_speed(itv->speed, vc->play.speed); - if (vc->play.speed < 0) - vc->play.format = VIDEO_PLAY_FMT_GOP; + switch (dc->cmd) { + case V4L2_DEC_CMD_START: { + dc->flags &= V4L2_DEC_CMD_START_MUTE_AUDIO; + dc->start.speed = ivtv_validate_speed(itv->speed, dc->start.speed); + if (dc->start.speed < 0) + dc->start.format = V4L2_DEC_START_FMT_GOP; + else + dc->start.format = V4L2_DEC_START_FMT_NONE; + if (dc->start.speed != 500 && dc->start.speed != 1500) + dc->flags = dc->start.speed == 1000 ? 0 : + V4L2_DEC_CMD_START_MUTE_AUDIO; if (try) break; + itv->speed_mute_audio = dc->flags & V4L2_DEC_CMD_START_MUTE_AUDIO; if (ivtv_set_output_mode(itv, OUT_MPG) != OUT_MPG) return -EBUSY; if (test_and_clear_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags)) { /* forces ivtv_set_speed to be called */ itv->speed = 0; } - return ivtv_start_decoding(id, vc->play.speed); + return ivtv_start_decoding(id, dc->start.speed); } - case VIDEO_CMD_STOP: - vc->flags &= VIDEO_CMD_STOP_IMMEDIATELY|VIDEO_CMD_STOP_TO_BLACK; - if (vc->flags & VIDEO_CMD_STOP_IMMEDIATELY) - vc->stop.pts = 0; + case V4L2_DEC_CMD_STOP: + dc->flags &= V4L2_DEC_CMD_STOP_IMMEDIATELY | V4L2_DEC_CMD_STOP_TO_BLACK; + if (dc->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) + dc->stop.pts = 0; if (try) break; if (atomic_read(&itv->decoding) == 0) return 0; @@ -281,22 +287,22 @@ static int ivtv_video_command(struct ivtv *itv, struct ivtv_open_id *id, return -EBUSY; itv->output_mode = OUT_NONE; - return ivtv_stop_v4l2_decode_stream(s, vc->flags, vc->stop.pts); + return ivtv_stop_v4l2_decode_stream(s, dc->flags, dc->stop.pts); - case VIDEO_CMD_FREEZE: - vc->flags &= VIDEO_CMD_FREEZE_TO_BLACK; + case V4L2_DEC_CMD_PAUSE: + dc->flags &= V4L2_DEC_CMD_PAUSE_TO_BLACK; if (try) break; if (itv->output_mode != OUT_MPG) return -EBUSY; if (atomic_read(&itv->decoding) > 0) { ivtv_vapi(itv, CX2341X_DEC_PAUSE_PLAYBACK, 1, - (vc->flags & VIDEO_CMD_FREEZE_TO_BLACK) ? 1 : 0); + (dc->flags & V4L2_DEC_CMD_PAUSE_TO_BLACK) ? 1 : 0); set_bit(IVTV_F_I_DEC_PAUSED, &itv->i_flags); } break; - case VIDEO_CMD_CONTINUE: - vc->flags = 0; + case V4L2_DEC_CMD_RESUME: + dc->flags = 0; if (try) break; if (itv->output_mode != OUT_MPG) return -EBUSY; @@ -754,12 +760,15 @@ static int ivtv_s_register(struct file *file, void *fh, struct v4l2_dbg_register static int ivtv_querycap(struct file *file, void *fh, struct v4l2_capability *vcap) { - struct ivtv *itv = fh2id(fh)->itv; + struct ivtv_open_id *id = fh2id(file->private_data); + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; strlcpy(vcap->driver, IVTV_DRIVER_NAME, sizeof(vcap->driver)); strlcpy(vcap->card, itv->card_name, sizeof(vcap->card)); snprintf(vcap->bus_info, sizeof(vcap->bus_info), "PCI:%s", pci_name(itv->pdev)); - vcap->capabilities = itv->v4l2_cap; /* capabilities */ + vcap->capabilities = itv->v4l2_cap | V4L2_CAP_DEVICE_CAPS; + vcap->device_caps = s->caps; return 0; } @@ -1476,8 +1485,6 @@ static int ivtv_log_status(struct file *file, void *fh) struct v4l2_audio audin; int i; - IVTV_INFO("================= START STATUS CARD #%d =================\n", - itv->instance); IVTV_INFO("Version: %s Card: %s\n", IVTV_VERSION, itv->card_name); if (itv->hw_flags & IVTV_HW_TVEEPROM) { struct tveeprom tv; @@ -1501,13 +1508,6 @@ static int ivtv_log_status(struct file *file, void *fh) "YUV Frames", "Passthrough", }; - static const char * const audio_modes[5] = { - "Stereo", - "Left", - "Right", - "Mono", - "Swapped" - }; static const char * const alpha_mode[4] = { "None", "Global", @@ -1536,9 +1536,6 @@ static int ivtv_log_status(struct file *file, void *fh) ivtv_get_output(itv, itv->active_output, &vidout); ivtv_get_audio_output(itv, 0, &audout); IVTV_INFO("Video Output: %s\n", vidout.name); - IVTV_INFO("Audio Output: %s (Stereo/Bilingual: %s/%s)\n", audout.name, - audio_modes[itv->audio_stereo_mode], - audio_modes[itv->audio_bilingual_mode]); if (mode < 0 || mode > OUT_PASSTHROUGH) mode = OUT_NONE; IVTV_INFO("Output Mode: %s\n", output_modes[mode]); @@ -1566,12 +1563,27 @@ static int ivtv_log_status(struct file *file, void *fh) IVTV_INFO("Read MPG/VBI: %lld/%lld bytes\n", (long long)itv->mpg_data_received, (long long)itv->vbi_data_inserted); - IVTV_INFO("================== END STATUS CARD #%d ==================\n", - itv->instance); - return 0; } +static int ivtv_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *dec) +{ + struct ivtv_open_id *id = fh2id(file->private_data); + struct ivtv *itv = id->itv; + + IVTV_DEBUG_IOCTL("VIDIOC_DECODER_CMD %d\n", dec->cmd); + return ivtv_video_command(itv, id, dec, false); +} + +static int ivtv_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *dec) +{ + struct ivtv_open_id *id = fh2id(file->private_data); + struct ivtv *itv = id->itv; + + IVTV_DEBUG_IOCTL("VIDIOC_TRY_DECODER_CMD %d\n", dec->cmd); + return ivtv_video_command(itv, id, dec, true); +} + static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) { struct ivtv_open_id *id = fh2id(filp->private_data); @@ -1605,9 +1617,15 @@ static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) return ivtv_yuv_prep_frame(itv, args); } + case IVTV_IOC_PASSTHROUGH_MODE: + IVTV_DEBUG_IOCTL("IVTV_IOC_PASSTHROUGH_MODE\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + return ivtv_passthrough_mode(itv, *(int *)arg != 0); + case VIDEO_GET_PTS: { - u32 data[CX2341X_MBOX_MAX_DATA]; - u64 *pts = arg; + s64 *pts = arg; + s64 frame; IVTV_DEBUG_IOCTL("VIDEO_GET_PTS\n"); if (s->type < IVTV_DEC_STREAM_TYPE_MPG) { @@ -1616,29 +1634,12 @@ static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) } if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) return -EINVAL; - - if (test_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags)) { - *pts = (u64) ((u64)itv->last_dec_timing[2] << 32) | - (u64)itv->last_dec_timing[1]; - break; - } - *pts = 0; - if (atomic_read(&itv->decoding)) { - if (ivtv_api(itv, CX2341X_DEC_GET_TIMING_INFO, 5, data)) { - IVTV_DEBUG_WARN("GET_TIMING: couldn't read clock\n"); - return -EIO; - } - memcpy(itv->last_dec_timing, data, sizeof(itv->last_dec_timing)); - set_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); - *pts = (u64) ((u64) data[2] << 32) | (u64) data[1]; - /*timing->scr = (u64) (((u64) data[4] << 32) | (u64) (data[3]));*/ - } - break; + return ivtv_g_pts_frame(itv, pts, &frame); } case VIDEO_GET_FRAME_COUNT: { - u32 data[CX2341X_MBOX_MAX_DATA]; - u64 *frame = arg; + s64 *frame = arg; + s64 pts; IVTV_DEBUG_IOCTL("VIDEO_GET_FRAME_COUNT\n"); if (s->type < IVTV_DEC_STREAM_TYPE_MPG) { @@ -1647,71 +1648,58 @@ static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) } if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) return -EINVAL; - - if (test_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags)) { - *frame = itv->last_dec_timing[0]; - break; - } - *frame = 0; - if (atomic_read(&itv->decoding)) { - if (ivtv_api(itv, CX2341X_DEC_GET_TIMING_INFO, 5, data)) { - IVTV_DEBUG_WARN("GET_TIMING: couldn't read clock\n"); - return -EIO; - } - memcpy(itv->last_dec_timing, data, sizeof(itv->last_dec_timing)); - set_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); - *frame = data[0]; - } - break; + return ivtv_g_pts_frame(itv, &pts, frame); } case VIDEO_PLAY: { - struct video_command vc; + struct v4l2_decoder_cmd dc; IVTV_DEBUG_IOCTL("VIDEO_PLAY\n"); - memset(&vc, 0, sizeof(vc)); - vc.cmd = VIDEO_CMD_PLAY; - return ivtv_video_command(itv, id, &vc, 0); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_START; + return ivtv_video_command(itv, id, &dc, 0); } case VIDEO_STOP: { - struct video_command vc; + struct v4l2_decoder_cmd dc; IVTV_DEBUG_IOCTL("VIDEO_STOP\n"); - memset(&vc, 0, sizeof(vc)); - vc.cmd = VIDEO_CMD_STOP; - vc.flags = VIDEO_CMD_STOP_TO_BLACK | VIDEO_CMD_STOP_IMMEDIATELY; - return ivtv_video_command(itv, id, &vc, 0); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_STOP; + dc.flags = V4L2_DEC_CMD_STOP_TO_BLACK | V4L2_DEC_CMD_STOP_IMMEDIATELY; + return ivtv_video_command(itv, id, &dc, 0); } case VIDEO_FREEZE: { - struct video_command vc; + struct v4l2_decoder_cmd dc; IVTV_DEBUG_IOCTL("VIDEO_FREEZE\n"); - memset(&vc, 0, sizeof(vc)); - vc.cmd = VIDEO_CMD_FREEZE; - return ivtv_video_command(itv, id, &vc, 0); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_PAUSE; + return ivtv_video_command(itv, id, &dc, 0); } case VIDEO_CONTINUE: { - struct video_command vc; + struct v4l2_decoder_cmd dc; IVTV_DEBUG_IOCTL("VIDEO_CONTINUE\n"); - memset(&vc, 0, sizeof(vc)); - vc.cmd = VIDEO_CMD_CONTINUE; - return ivtv_video_command(itv, id, &vc, 0); + memset(&dc, 0, sizeof(dc)); + dc.cmd = V4L2_DEC_CMD_RESUME; + return ivtv_video_command(itv, id, &dc, 0); } case VIDEO_COMMAND: case VIDEO_TRY_COMMAND: { - struct video_command *vc = arg; + /* Note: struct v4l2_decoder_cmd has the same layout as + struct video_command */ + struct v4l2_decoder_cmd *dc = arg; int try = (cmd == VIDEO_TRY_COMMAND); if (try) - IVTV_DEBUG_IOCTL("VIDEO_TRY_COMMAND %d\n", vc->cmd); + IVTV_DEBUG_IOCTL("VIDEO_TRY_COMMAND %d\n", dc->cmd); else - IVTV_DEBUG_IOCTL("VIDEO_COMMAND %d\n", vc->cmd); - return ivtv_video_command(itv, id, vc, try); + IVTV_DEBUG_IOCTL("VIDEO_COMMAND %d\n", dc->cmd); + return ivtv_video_command(itv, id, dc, try); } case VIDEO_GET_EVENT: { @@ -1775,17 +1763,13 @@ static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) IVTV_DEBUG_IOCTL("AUDIO_CHANNEL_SELECT\n"); if (iarg > AUDIO_STEREO_SWAPPED) return -EINVAL; - itv->audio_stereo_mode = iarg; - ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); - return 0; + return v4l2_ctrl_s_ctrl(itv->ctrl_audio_playback, iarg); case AUDIO_BILINGUAL_CHANNEL_SELECT: IVTV_DEBUG_IOCTL("AUDIO_BILINGUAL_CHANNEL_SELECT\n"); if (iarg > AUDIO_STEREO_SWAPPED) return -EINVAL; - itv->audio_bilingual_mode = iarg; - ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); - return 0; + return v4l2_ctrl_s_ctrl(itv->ctrl_audio_multilingual_playback, iarg); default: return -EINVAL; @@ -1800,6 +1784,7 @@ static long ivtv_default(struct file *file, void *fh, bool valid_prio, if (!valid_prio) { switch (cmd) { + case IVTV_IOC_PASSTHROUGH_MODE: case VIDEO_PLAY: case VIDEO_STOP: case VIDEO_FREEZE: @@ -1825,6 +1810,7 @@ static long ivtv_default(struct file *file, void *fh, bool valid_prio, } case IVTV_IOC_DMA_FRAME: + case IVTV_IOC_PASSTHROUGH_MODE: case VIDEO_GET_PTS: case VIDEO_GET_FRAME_COUNT: case VIDEO_GET_EVENT: @@ -1889,6 +1875,8 @@ static const struct v4l2_ioctl_ops ivtv_ioctl_ops = { .vidioc_enum_fmt_vid_cap = ivtv_enum_fmt_vid_cap, .vidioc_encoder_cmd = ivtv_encoder_cmd, .vidioc_try_encoder_cmd = ivtv_try_encoder_cmd, + .vidioc_decoder_cmd = ivtv_decoder_cmd, + .vidioc_try_decoder_cmd = ivtv_try_decoder_cmd, .vidioc_enum_fmt_vid_out = ivtv_enum_fmt_vid_out, .vidioc_g_fmt_vid_cap = ivtv_g_fmt_vid_cap, .vidioc_g_fmt_vbi_cap = ivtv_g_fmt_vbi_cap, diff --git a/drivers/media/video/ivtv/ivtv-streams.c b/drivers/media/video/ivtv/ivtv-streams.c index c6e28b4ebbed..7ea5ca7f012b 100644 --- a/drivers/media/video/ivtv/ivtv-streams.c +++ b/drivers/media/video/ivtv/ivtv-streams.c @@ -78,60 +78,73 @@ static struct { int num_offset; int dma, pio; enum v4l2_buf_type buf_type; + u32 v4l2_caps; const struct v4l2_file_operations *fops; } ivtv_stream_info[] = { { /* IVTV_ENC_STREAM_TYPE_MPG */ "encoder MPG", VFL_TYPE_GRABBER, 0, PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, &ivtv_v4l2_enc_fops }, { /* IVTV_ENC_STREAM_TYPE_YUV */ "encoder YUV", VFL_TYPE_GRABBER, IVTV_V4L2_ENC_YUV_OFFSET, PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, &ivtv_v4l2_enc_fops }, { /* IVTV_ENC_STREAM_TYPE_VBI */ "encoder VBI", VFL_TYPE_VBI, 0, PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, &ivtv_v4l2_enc_fops }, { /* IVTV_ENC_STREAM_TYPE_PCM */ "encoder PCM", VFL_TYPE_GRABBER, IVTV_V4L2_ENC_PCM_OFFSET, PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_PRIVATE, + V4L2_CAP_TUNER | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, &ivtv_v4l2_enc_fops }, { /* IVTV_ENC_STREAM_TYPE_RAD */ "encoder radio", VFL_TYPE_RADIO, 0, PCI_DMA_NONE, 1, V4L2_BUF_TYPE_PRIVATE, + V4L2_CAP_RADIO | V4L2_CAP_TUNER, &ivtv_v4l2_enc_fops }, { /* IVTV_DEC_STREAM_TYPE_MPG */ "decoder MPG", VFL_TYPE_GRABBER, IVTV_V4L2_DEC_MPG_OFFSET, PCI_DMA_TODEVICE, 0, V4L2_BUF_TYPE_VIDEO_OUTPUT, + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, &ivtv_v4l2_dec_fops }, { /* IVTV_DEC_STREAM_TYPE_VBI */ "decoder VBI", VFL_TYPE_VBI, IVTV_V4L2_DEC_VBI_OFFSET, PCI_DMA_NONE, 1, V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_CAP_SLICED_VBI_CAPTURE | V4L2_CAP_READWRITE, &ivtv_v4l2_enc_fops }, { /* IVTV_DEC_STREAM_TYPE_VOUT */ "decoder VOUT", VFL_TYPE_VBI, IVTV_V4L2_DEC_VOUT_OFFSET, PCI_DMA_NONE, 1, V4L2_BUF_TYPE_VBI_OUTPUT, + V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, &ivtv_v4l2_dec_fops }, { /* IVTV_DEC_STREAM_TYPE_YUV */ "decoder YUV", VFL_TYPE_GRABBER, IVTV_V4L2_DEC_YUV_OFFSET, PCI_DMA_TODEVICE, 0, V4L2_BUF_TYPE_VIDEO_OUTPUT, + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_AUDIO | V4L2_CAP_READWRITE, &ivtv_v4l2_dec_fops } }; @@ -149,6 +162,7 @@ static void ivtv_stream_init(struct ivtv *itv, int type) s->itv = itv; s->type = type; s->name = ivtv_stream_info[type].name; + s->caps = ivtv_stream_info[type].v4l2_caps; if (ivtv_stream_info[type].pio) s->dma = PCI_DMA_NONE; @@ -209,8 +223,8 @@ static int ivtv_prep_dev(struct ivtv *itv, int type) s->vdev->num = num; s->vdev->v4l2_dev = &itv->v4l2_dev; - s->vdev->ctrl_handler = itv->v4l2_dev.ctrl_handler; s->vdev->fops = ivtv_stream_info[type].fops; + s->vdev->ctrl_handler = itv->v4l2_dev.ctrl_handler; s->vdev->release = video_device_release; s->vdev->tvnorms = V4L2_STD_ALL; s->vdev->lock = &itv->serialize_lock; @@ -891,7 +905,7 @@ int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts) IVTV_DEBUG_INFO("Stop Decode at %llu, flags: %x\n", (unsigned long long)pts, flags); /* Stop Decoder */ - if (!(flags & VIDEO_CMD_STOP_IMMEDIATELY) || pts) { + if (!(flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) || pts) { u32 tmp = 0; /* Wait until the decoder is no longer running */ @@ -911,7 +925,7 @@ int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts) break; } } - ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, flags & VIDEO_CMD_STOP_TO_BLACK, 0, 0); + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, flags & V4L2_DEC_CMD_STOP_TO_BLACK, 0, 0); /* turn off notification of dual/stereo mode change */ ivtv_vapi(itv, CX2341X_DEC_SET_EVENT_NOTIFICATION, 4, 0, 0, IVTV_IRQ_DEC_AUD_MODE_CHG, -1); diff --git a/drivers/media/video/ks0127.c b/drivers/media/video/ks0127.c index afa91182b448..ee7ca2dcca2f 100644 --- a/drivers/media/video/ks0127.c +++ b/drivers/media/video/ks0127.c @@ -721,15 +721,4 @@ static struct i2c_driver ks0127_driver = { .id_table = ks0127_id, }; -static __init int init_ks0127(void) -{ - return i2c_add_driver(&ks0127_driver); -} - -static __exit void exit_ks0127(void) -{ - i2c_del_driver(&ks0127_driver); -} - -module_init(init_ks0127); -module_exit(exit_ks0127); +module_i2c_driver(ks0127_driver); diff --git a/drivers/media/video/m52790.c b/drivers/media/video/m52790.c index 303ffa7df4ac..0991576f4c82 100644 --- a/drivers/media/video/m52790.c +++ b/drivers/media/video/m52790.c @@ -213,15 +213,4 @@ static struct i2c_driver m52790_driver = { .id_table = m52790_id, }; -static __init int init_m52790(void) -{ - return i2c_add_driver(&m52790_driver); -} - -static __exit void exit_m52790(void) -{ - i2c_del_driver(&m52790_driver); -} - -module_init(init_m52790); -module_exit(exit_m52790); +module_i2c_driver(m52790_driver); diff --git a/drivers/media/video/m5mols/m5mols_core.c b/drivers/media/video/m5mols/m5mols_core.c index 93d768db9f33..d718aee01c77 100644 --- a/drivers/media/video/m5mols/m5mols_core.c +++ b/drivers/media/video/m5mols/m5mols_core.c @@ -982,8 +982,8 @@ static int __devinit m5mols_probe(struct i2c_client *client, } sd = &info->sd; - strlcpy(sd->name, MODULE_NAME, sizeof(sd->name)); v4l2_i2c_subdev_init(sd, client, &m5mols_ops); + strlcpy(sd->name, MODULE_NAME, sizeof(sd->name)); sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; sd->internal_ops = &m5mols_subdev_internal_ops; @@ -1057,18 +1057,7 @@ static struct i2c_driver m5mols_i2c_driver = { .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_i2c_driver(m5mols_i2c_driver); MODULE_AUTHOR("HeungJun Kim <riverful.kim@samsung.com>"); MODULE_AUTHOR("Dongsoo Kim <dongsoo45.kim@samsung.com>"); diff --git a/drivers/media/video/marvell-ccic/mcam-core.c b/drivers/media/video/marvell-ccic/mcam-core.c index 37d20e73908a..996ac34d9a89 100644 --- a/drivers/media/video/marvell-ccic/mcam-core.c +++ b/drivers/media/video/marvell-ccic/mcam-core.c @@ -509,11 +509,17 @@ static void mcam_sg_next_buffer(struct mcam_camera *cam) buf = list_first_entry(&cam->buffers, struct mcam_vb_buffer, queue); list_del_init(&buf->queue); + /* + * Very Bad Not Good Things happen if you don't clear + * C1_DESC_ENA before making any descriptor changes. + */ + mcam_reg_clear_bit(cam, REG_CTRL1, C1_DESC_ENA); mcam_reg_write(cam, REG_DMA_DESC_Y, buf->dma_desc_pa); mcam_reg_write(cam, REG_DESC_LEN_Y, buf->dma_desc_nent*sizeof(struct mcam_dma_desc)); mcam_reg_write(cam, REG_DESC_LEN_U, 0); mcam_reg_write(cam, REG_DESC_LEN_V, 0); + mcam_reg_set_bit(cam, REG_CTRL1, C1_DESC_ENA); cam->vb_bufs[0] = buf; } @@ -533,7 +539,6 @@ static void mcam_ctlr_dma_sg(struct mcam_camera *cam) mcam_reg_clear_bit(cam, REG_CTRL1, C1_DESC_3WORD); mcam_sg_next_buffer(cam); - mcam_reg_set_bit(cam, REG_CTRL1, C1_DESC_ENA); cam->nbufs = 3; } @@ -556,17 +561,16 @@ static void mcam_dma_sg_done(struct mcam_camera *cam, int frame) struct mcam_vb_buffer *buf = cam->vb_bufs[0]; /* - * Very Bad Not Good Things happen if you don't clear - * C1_DESC_ENA before making any descriptor changes. + * If we're no longer supposed to be streaming, don't do anything. */ - mcam_reg_clear_bit(cam, REG_CTRL1, C1_DESC_ENA); + if (cam->state != S_STREAMING) + return; /* * If we have another buffer available, put it in and * restart the engine. */ if (!list_empty(&cam->buffers)) { mcam_sg_next_buffer(cam); - mcam_reg_set_bit(cam, REG_CTRL1, C1_DESC_ENA); mcam_ctlr_start(cam); /* * Otherwise set CF_SG_RESTART and the controller will @@ -737,7 +741,14 @@ static void mcam_ctlr_stop_dma(struct mcam_camera *cam) mcam_ctlr_stop(cam); cam->state = S_IDLE; spin_unlock_irqrestore(&cam->dev_lock, flags); - msleep(40); + /* + * This is a brutally long sleep, but experience shows that + * it can take the controller a while to get the message that + * it needs to stop grabbing frames. In particular, we can + * sometimes (on mmp) get a frame at the end WITHOUT the + * start-of-frame indication. + */ + msleep(150); if (test_bit(CF_DMA_ACTIVE, &cam->flags)) cam_err(cam, "Timeout waiting for DMA to end\n"); /* This would be bad news - what now? */ @@ -880,6 +891,7 @@ static int mcam_read_setup(struct mcam_camera *cam) * Turn it loose. */ spin_lock_irqsave(&cam->dev_lock, flags); + clear_bit(CF_DMA_ACTIVE, &cam->flags); mcam_reset_buffers(cam); mcam_ctlr_irq_enable(cam); cam->state = S_STREAMING; @@ -922,7 +934,7 @@ static void mcam_vb_buf_queue(struct vb2_buffer *vb) spin_lock_irqsave(&cam->dev_lock, flags); start = (cam->state == S_BUFWAIT) && !list_empty(&cam->buffers); list_add(&mvb->queue, &cam->buffers); - if (test_bit(CF_SG_RESTART, &cam->flags)) + if (cam->state == S_STREAMING && test_bit(CF_SG_RESTART, &cam->flags)) mcam_sg_restart(cam); spin_unlock_irqrestore(&cam->dev_lock, flags); if (start) @@ -1555,15 +1567,12 @@ static int mcam_v4l_release(struct file *filp) { struct mcam_camera *cam = filp->private_data; - cam_err(cam, "Release, %d frames, %d singles, %d delivered\n", frames, + cam_dbg(cam, "Release, %d frames, %d singles, %d delivered\n", frames, singles, delivered); mutex_lock(&cam->s_mutex); (cam->users)--; - if (filp == cam->owner) { - mcam_ctlr_stop_dma(cam); - cam->owner = NULL; - } if (cam->users == 0) { + mcam_ctlr_stop_dma(cam); mcam_cleanup_vb2(cam); mcam_ctlr_power_down(cam); if (cam->buffer_mode == B_vmalloc && alloc_bufs_at_read) @@ -1688,6 +1697,8 @@ int mccic_irq(struct mcam_camera *cam, unsigned int irqs) if (irqs & (IRQ_EOF0 << frame)) { mcam_frame_complete(cam, frame); handled = 1; + if (cam->buffer_mode == B_DMA_sg) + break; } /* * If a frame starts, note that we have DMA active. This diff --git a/drivers/media/video/marvell-ccic/mcam-core.h b/drivers/media/video/marvell-ccic/mcam-core.h index 917200e63255..bd6acba9fb37 100644 --- a/drivers/media/video/marvell-ccic/mcam-core.h +++ b/drivers/media/video/marvell-ccic/mcam-core.h @@ -107,7 +107,6 @@ struct mcam_camera { enum mcam_state state; unsigned long flags; /* Buffer status, mainly (dev_lock) */ int users; /* How many open FDs */ - struct file *owner; /* Who has data access (v4l2) */ /* * Subsystem structures. diff --git a/drivers/media/video/marvell-ccic/mmp-driver.c b/drivers/media/video/marvell-ccic/mmp-driver.c index 0d64e2d7474a..d23552323f45 100644 --- a/drivers/media/video/marvell-ccic/mmp-driver.c +++ b/drivers/media/video/marvell-ccic/mmp-driver.c @@ -106,6 +106,13 @@ static struct mmp_camera *mmpcam_find_device(struct platform_device *pdev) /* * Power control. */ +static void mmpcam_power_up_ctlr(struct mmp_camera *cam) +{ + iowrite32(0x3f, cam->power_regs + REG_CCIC_DCGCR); + iowrite32(0x3805b, cam->power_regs + REG_CCIC_CRCR); + mdelay(1); +} + static void mmpcam_power_up(struct mcam_camera *mcam) { struct mmp_camera *cam = mcam_to_cam(mcam); @@ -113,9 +120,7 @@ static void mmpcam_power_up(struct mcam_camera *mcam) /* * Turn on power and clocks to the controller. */ - iowrite32(0x3f, cam->power_regs + REG_CCIC_DCGCR); - iowrite32(0x3805b, cam->power_regs + REG_CCIC_CRCR); - mdelay(1); + mmpcam_power_up_ctlr(cam); /* * Provide power to the sensor. */ @@ -335,7 +340,7 @@ static int mmpcam_resume(struct platform_device *pdev) * touch a register even if nothing was active before; trust * me, it's better this way. */ - mmpcam_power_up(&cam->mcam); + mmpcam_power_up_ctlr(cam); return mccic_resume(&cam->mcam); } diff --git a/drivers/media/video/msp3400-driver.c b/drivers/media/video/msp3400-driver.c index d7cd0f633f63..82ce50721de3 100644 --- a/drivers/media/video/msp3400-driver.c +++ b/drivers/media/video/msp3400-driver.c @@ -881,18 +881,7 @@ static struct i2c_driver msp_driver = { .id_table = msp_id, }; -static __init int init_msp(void) -{ - return i2c_add_driver(&msp_driver); -} - -static __exit void exit_msp(void) -{ - i2c_del_driver(&msp_driver); -} - -module_init(init_msp); -module_exit(exit_msp); +module_i2c_driver(msp_driver); /* * Overrides for Emacs so that we follow Linus's tabbing style. diff --git a/drivers/media/video/mt9m001.c b/drivers/media/video/mt9m001.c index 097c9d3d04a8..7e648183f157 100644 --- a/drivers/media/video/mt9m001.c +++ b/drivers/media/video/mt9m001.c @@ -730,18 +730,7 @@ static struct i2c_driver mt9m001_i2c_driver = { .id_table = mt9m001_id, }; -static int __init mt9m001_mod_init(void) -{ - return i2c_add_driver(&mt9m001_i2c_driver); -} - -static void __exit mt9m001_mod_exit(void) -{ - i2c_del_driver(&mt9m001_i2c_driver); -} - -module_init(mt9m001_mod_init); -module_exit(mt9m001_mod_exit); +module_i2c_driver(mt9m001_i2c_driver); MODULE_DESCRIPTION("Micron MT9M001 Camera driver"); MODULE_AUTHOR("Guennadi Liakhovetski <kernel@pengutronix.de>"); diff --git a/drivers/media/video/mt9m032.c b/drivers/media/video/mt9m032.c new file mode 100644 index 000000000000..7636672c3548 --- /dev/null +++ b/drivers/media/video/mt9m032.c @@ -0,0 +1,868 @@ +/* + * Driver for MT9M032 CMOS Image Sensor from Micron + * + * Copyright (C) 2010-2011 Lund Engineering + * Contact: Gil Lund <gwlund@lundeng.com> + * Author: Martin Hostettler <martin@neutronstar.dyndns.org> + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/v4l2-mediabus.h> + +#include <media/media-entity.h> +#include <media/mt9m032.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "aptina-pll.h" + +/* + * width and height include active boundary and black parts + * + * column 0- 15 active boundary + * column 16-1455 image + * column 1456-1471 active boundary + * column 1472-1599 black + * + * row 0- 51 black + * row 53- 59 active boundary + * row 60-1139 image + * row 1140-1147 active boundary + * row 1148-1151 black + */ + +#define MT9M032_PIXEL_ARRAY_WIDTH 1600 +#define MT9M032_PIXEL_ARRAY_HEIGHT 1152 + +#define MT9M032_CHIP_VERSION 0x00 +#define MT9M032_CHIP_VERSION_VALUE 0x1402 +#define MT9M032_ROW_START 0x01 +#define MT9M032_ROW_START_MIN 0 +#define MT9M032_ROW_START_MAX 1152 +#define MT9M032_ROW_START_DEF 60 +#define MT9M032_COLUMN_START 0x02 +#define MT9M032_COLUMN_START_MIN 0 +#define MT9M032_COLUMN_START_MAX 1600 +#define MT9M032_COLUMN_START_DEF 16 +#define MT9M032_ROW_SIZE 0x03 +#define MT9M032_ROW_SIZE_MIN 32 +#define MT9M032_ROW_SIZE_MAX 1152 +#define MT9M032_ROW_SIZE_DEF 1080 +#define MT9M032_COLUMN_SIZE 0x04 +#define MT9M032_COLUMN_SIZE_MIN 32 +#define MT9M032_COLUMN_SIZE_MAX 1600 +#define MT9M032_COLUMN_SIZE_DEF 1440 +#define MT9M032_HBLANK 0x05 +#define MT9M032_VBLANK 0x06 +#define MT9M032_VBLANK_MAX 0x7ff +#define MT9M032_SHUTTER_WIDTH_HIGH 0x08 +#define MT9M032_SHUTTER_WIDTH_LOW 0x09 +#define MT9M032_SHUTTER_WIDTH_MIN 1 +#define MT9M032_SHUTTER_WIDTH_MAX 1048575 +#define MT9M032_SHUTTER_WIDTH_DEF 1943 +#define MT9M032_PIX_CLK_CTRL 0x0a +#define MT9M032_PIX_CLK_CTRL_INV_PIXCLK 0x8000 +#define MT9M032_RESTART 0x0b +#define MT9M032_RESET 0x0d +#define MT9M032_PLL_CONFIG1 0x11 +#define MT9M032_PLL_CONFIG1_OUTDIV_MASK 0x3f +#define MT9M032_PLL_CONFIG1_MUL_SHIFT 8 +#define MT9M032_READ_MODE1 0x1e +#define MT9M032_READ_MODE2 0x20 +#define MT9M032_READ_MODE2_VFLIP_SHIFT 15 +#define MT9M032_READ_MODE2_HFLIP_SHIFT 14 +#define MT9M032_READ_MODE2_ROW_BLC 0x40 +#define MT9M032_GAIN_GREEN1 0x2b +#define MT9M032_GAIN_BLUE 0x2c +#define MT9M032_GAIN_RED 0x2d +#define MT9M032_GAIN_GREEN2 0x2e + +/* write only */ +#define MT9M032_GAIN_ALL 0x35 +#define MT9M032_GAIN_DIGITAL_MASK 0x7f +#define MT9M032_GAIN_DIGITAL_SHIFT 8 +#define MT9M032_GAIN_AMUL_SHIFT 6 +#define MT9M032_GAIN_ANALOG_MASK 0x3f +#define MT9M032_FORMATTER1 0x9e +#define MT9M032_FORMATTER2 0x9f +#define MT9M032_FORMATTER2_DOUT_EN 0x1000 +#define MT9M032_FORMATTER2_PIXCLK_EN 0x2000 + +/* + * The available MT9M032 datasheet is missing documentation for register 0x10 + * MT9P031 seems to be close enough, so use constants from that datasheet for + * now. + * But keep the name MT9P031 to remind us, that this isn't really confirmed + * for this sensor. + */ +#define MT9P031_PLL_CONTROL 0x10 +#define MT9P031_PLL_CONTROL_PWROFF 0x0050 +#define MT9P031_PLL_CONTROL_PWRON 0x0051 +#define MT9P031_PLL_CONTROL_USEPLL 0x0052 +#define MT9P031_PLL_CONFIG2 0x11 +#define MT9P031_PLL_CONFIG2_P1_DIV_MASK 0x1f + +struct mt9m032 { + struct v4l2_subdev subdev; + struct media_pad pad; + struct mt9m032_platform_data *pdata; + + unsigned int pix_clock; + + struct v4l2_ctrl_handler ctrls; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + + struct mutex lock; /* Protects streaming, format, interval and crop */ + + bool streaming; + + struct v4l2_mbus_framefmt format; + struct v4l2_rect crop; + struct v4l2_fract frame_interval; +}; + +#define to_mt9m032(sd) container_of(sd, struct mt9m032, subdev) +#define to_dev(sensor) \ + (&((struct i2c_client *)v4l2_get_subdevdata(&(sensor)->subdev))->dev) + +static int mt9m032_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_word_swapped(client, reg); +} + +static int mt9m032_write(struct i2c_client *client, u8 reg, const u16 data) +{ + return i2c_smbus_write_word_swapped(client, reg, data); +} + +static u32 mt9m032_row_time(struct mt9m032 *sensor, unsigned int width) +{ + unsigned int effective_width; + u32 ns; + + effective_width = width + 716; /* empirical value */ + ns = div_u64(1000000000ULL * effective_width, sensor->pix_clock); + dev_dbg(to_dev(sensor), "MT9M032 line time: %u ns\n", ns); + return ns; +} + +static int mt9m032_update_timing(struct mt9m032 *sensor, + struct v4l2_fract *interval) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct v4l2_rect *crop = &sensor->crop; + unsigned int min_vblank; + unsigned int vblank; + u32 row_time; + + if (!interval) + interval = &sensor->frame_interval; + + row_time = mt9m032_row_time(sensor, crop->width); + + vblank = div_u64(1000000000ULL * interval->numerator, + (u64)row_time * interval->denominator) + - crop->height; + + if (vblank > MT9M032_VBLANK_MAX) { + /* hardware limits to 11 bit values */ + interval->denominator = 1000; + interval->numerator = + div_u64((crop->height + MT9M032_VBLANK_MAX) * + (u64)row_time * interval->denominator, + 1000000000ULL); + vblank = div_u64(1000000000ULL * interval->numerator, + (u64)row_time * interval->denominator) + - crop->height; + } + /* enforce minimal 1.6ms blanking time. */ + min_vblank = 1600000 / row_time; + vblank = clamp_t(unsigned int, vblank, min_vblank, MT9M032_VBLANK_MAX); + + return mt9m032_write(client, MT9M032_VBLANK, vblank); +} + +static int mt9m032_update_geom_timing(struct mt9m032 *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int ret; + + ret = mt9m032_write(client, MT9M032_COLUMN_SIZE, + sensor->crop.width - 1); + if (!ret) + ret = mt9m032_write(client, MT9M032_ROW_SIZE, + sensor->crop.height - 1); + if (!ret) + ret = mt9m032_write(client, MT9M032_COLUMN_START, + sensor->crop.left); + if (!ret) + ret = mt9m032_write(client, MT9M032_ROW_START, + sensor->crop.top); + if (!ret) + ret = mt9m032_update_timing(sensor, NULL); + return ret; +} + +static int update_formatter2(struct mt9m032 *sensor, bool streaming) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + u16 reg_val = MT9M032_FORMATTER2_DOUT_EN + | 0x0070; /* parts reserved! */ + /* possibly for changing to 14-bit mode */ + + if (streaming) + reg_val |= MT9M032_FORMATTER2_PIXCLK_EN; /* pixclock enable */ + + return mt9m032_write(client, MT9M032_FORMATTER2, reg_val); +} + +static int mt9m032_setup_pll(struct mt9m032 *sensor) +{ + static const struct aptina_pll_limits limits = { + .ext_clock_min = 8000000, + .ext_clock_max = 16500000, + .int_clock_min = 2000000, + .int_clock_max = 24000000, + .out_clock_min = 322000000, + .out_clock_max = 693000000, + .pix_clock_max = 99000000, + .n_min = 1, + .n_max = 64, + .m_min = 16, + .m_max = 255, + .p1_min = 1, + .p1_max = 128, + }; + + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + struct mt9m032_platform_data *pdata = sensor->pdata; + struct aptina_pll pll; + int ret; + + pll.ext_clock = pdata->ext_clock; + pll.pix_clock = pdata->pix_clock; + + ret = aptina_pll_calculate(&client->dev, &limits, &pll); + if (ret < 0) + return ret; + + sensor->pix_clock = pdata->pix_clock; + + ret = mt9m032_write(client, MT9M032_PLL_CONFIG1, + (pll.m << MT9M032_PLL_CONFIG1_MUL_SHIFT) + | (pll.p1 - 1)); + if (!ret) + ret = mt9m032_write(client, MT9P031_PLL_CONFIG2, pll.n - 1); + if (!ret) + ret = mt9m032_write(client, MT9P031_PLL_CONTROL, + MT9P031_PLL_CONTROL_PWRON | + MT9P031_PLL_CONTROL_USEPLL); + if (!ret) /* more reserved, Continuous, Master Mode */ + ret = mt9m032_write(client, MT9M032_READ_MODE1, 0x8006); + if (!ret) /* Set 14-bit mode, select 7 divider */ + ret = mt9m032_write(client, MT9M032_FORMATTER1, 0x111e); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Subdev pad operations + */ + +static int mt9m032_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + + code->code = V4L2_MBUS_FMT_Y8_1X8; + return 0; +} + +static int mt9m032_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index != 0 || fse->code != V4L2_MBUS_FMT_Y8_1X8) + return -EINVAL; + + fse->min_width = MT9M032_COLUMN_SIZE_DEF; + fse->max_width = MT9M032_COLUMN_SIZE_DEF; + fse->min_height = MT9M032_ROW_SIZE_DEF; + fse->max_height = MT9M032_ROW_SIZE_DEF; + + return 0; +} + +/** + * __mt9m032_get_pad_crop() - get crop rect + * @sensor: pointer to the sensor struct + * @fh: file handle for getting the try crop rect from + * @which: select try or active crop rect + * + * Returns a pointer the current active or fh relative try crop rect + */ +static struct v4l2_rect * +__mt9m032_get_pad_crop(struct mt9m032 *sensor, struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(fh, 0); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->crop; + default: + return NULL; + } +} + +/** + * __mt9m032_get_pad_format() - get format + * @sensor: pointer to the sensor struct + * @fh: file handle for getting the try format from + * @which: select try or active format + * + * Returns a pointer the current active or fh relative try format + */ +static struct v4l2_mbus_framefmt * +__mt9m032_get_pad_format(struct mt9m032 *sensor, struct v4l2_subdev_fh *fh, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(fh, 0); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &sensor->format; + default: + return NULL; + } +} + +static int mt9m032_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + fmt->format = *__mt9m032_get_pad_format(sensor, fh, fmt->which); + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming && fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = -EBUSY; + goto done; + } + + /* Scaling is not supported, the format is thus fixed. */ + ret = mt9m032_get_pad_format(subdev, fh, fmt); + +done: + mutex_lock(&sensor->lock); + return ret; +} + +static int mt9m032_get_pad_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + crop->rect = *__mt9m032_get_pad_crop(sensor, fh, crop->which); + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_pad_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *__crop; + struct v4l2_rect rect; + int ret = 0; + + mutex_lock(&sensor->lock); + + if (sensor->streaming && crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = -EBUSY; + goto done; + } + + /* Clamp the crop rectangle boundaries and align them to a multiple of 2 + * pixels to ensure a GRBG Bayer pattern. + */ + rect.left = clamp(ALIGN(crop->rect.left, 2), MT9M032_COLUMN_START_MIN, + MT9M032_COLUMN_START_MAX); + rect.top = clamp(ALIGN(crop->rect.top, 2), MT9M032_ROW_START_MIN, + MT9M032_ROW_START_MAX); + rect.width = clamp(ALIGN(crop->rect.width, 2), MT9M032_COLUMN_SIZE_MIN, + MT9M032_COLUMN_SIZE_MAX); + rect.height = clamp(ALIGN(crop->rect.height, 2), MT9M032_ROW_SIZE_MIN, + MT9M032_ROW_SIZE_MAX); + + rect.width = min(rect.width, MT9M032_PIXEL_ARRAY_WIDTH - rect.left); + rect.height = min(rect.height, MT9M032_PIXEL_ARRAY_HEIGHT - rect.top); + + __crop = __mt9m032_get_pad_crop(sensor, fh, crop->which); + + if (rect.width != __crop->width || rect.height != __crop->height) { + /* Reset the output image size if the crop rectangle size has + * been modified. + */ + format = __mt9m032_get_pad_format(sensor, fh, crop->which); + format->width = rect.width; + format->height = rect.height; + } + + *__crop = rect; + crop->rect = rect; + + if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ret = mt9m032_update_geom_timing(sensor); + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_get_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + + mutex_lock(&sensor->lock); + memset(fi, 0, sizeof(*fi)); + fi->interval = sensor->frame_interval; + mutex_unlock(&sensor->lock); + + return 0; +} + +static int mt9m032_set_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fi) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto done; + } + + /* Avoid divisions by 0. */ + if (fi->interval.denominator == 0) + fi->interval.denominator = 1; + + ret = mt9m032_update_timing(sensor, &fi->interval); + if (!ret) + sensor->frame_interval = fi->interval; + +done: + mutex_unlock(&sensor->lock); + return ret; +} + +static int mt9m032_s_stream(struct v4l2_subdev *subdev, int streaming) +{ + struct mt9m032 *sensor = to_mt9m032(subdev); + int ret; + + mutex_lock(&sensor->lock); + ret = update_formatter2(sensor, streaming); + if (!ret) + sensor->streaming = streaming; + mutex_unlock(&sensor->lock); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev core operations + */ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9m032_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct mt9m032 *sensor = to_mt9m032(sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int val; + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + if (reg->match.addr != client->addr) + return -ENODEV; + + val = mt9m032_read(client, reg->reg); + if (val < 0) + return -EIO; + + reg->size = 2; + reg->val = val; + + return 0; +} + +static int mt9m032_s_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct mt9m032 *sensor = to_mt9m032(sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + + if (reg->match.type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match.addr != client->addr) + return -ENODEV; + + return mt9m032_write(client, reg->reg, reg->val); +} +#endif + +/* ----------------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +static int update_read_mode2(struct mt9m032 *sensor, bool vflip, bool hflip) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int reg_val = (vflip << MT9M032_READ_MODE2_VFLIP_SHIFT) + | (hflip << MT9M032_READ_MODE2_HFLIP_SHIFT) + | MT9M032_READ_MODE2_ROW_BLC + | 0x0007; + + return mt9m032_write(client, MT9M032_READ_MODE2, reg_val); +} + +static int mt9m032_set_gain(struct mt9m032 *sensor, s32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int digital_gain_val; /* in 1/8th (0..127) */ + int analog_mul; /* 0 or 1 */ + int analog_gain_val; /* in 1/16th. (0..63) */ + u16 reg_val; + + digital_gain_val = 51; /* from setup example */ + + if (val < 63) { + analog_mul = 0; + analog_gain_val = val; + } else { + analog_mul = 1; + analog_gain_val = val / 2; + } + + /* a_gain = (1 + analog_mul) + (analog_gain_val + 1) / 16 */ + /* overall_gain = a_gain * (1 + digital_gain_val / 8) */ + + reg_val = ((digital_gain_val & MT9M032_GAIN_DIGITAL_MASK) + << MT9M032_GAIN_DIGITAL_SHIFT) + | ((analog_mul & 1) << MT9M032_GAIN_AMUL_SHIFT) + | (analog_gain_val & MT9M032_GAIN_ANALOG_MASK); + + return mt9m032_write(client, MT9M032_GAIN_ALL, reg_val); +} + +static int mt9m032_try_ctrl(struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_GAIN && ctrl->val >= 63) { + /* round because of multiplier used for values >= 63 */ + ctrl->val &= ~1; + } + + return 0; +} + +static int mt9m032_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mt9m032 *sensor = + container_of(ctrl->handler, struct mt9m032, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + int ret; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + return mt9m032_set_gain(sensor, ctrl->val); + + case V4L2_CID_HFLIP: + /* case V4L2_CID_VFLIP: -- In the same cluster */ + return update_read_mode2(sensor, sensor->vflip->val, + sensor->hflip->val); + + case V4L2_CID_EXPOSURE: + ret = mt9m032_write(client, MT9M032_SHUTTER_WIDTH_HIGH, + (ctrl->val >> 16) & 0xffff); + if (ret < 0) + return ret; + + return mt9m032_write(client, MT9M032_SHUTTER_WIDTH_LOW, + ctrl->val & 0xffff); + } + + return 0; +} + +static struct v4l2_ctrl_ops mt9m032_ctrl_ops = { + .s_ctrl = mt9m032_set_ctrl, + .try_ctrl = mt9m032_try_ctrl, +}; + +/* -------------------------------------------------------------------------- */ + +static const struct v4l2_subdev_core_ops mt9m032_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mt9m032_g_register, + .s_register = mt9m032_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops mt9m032_video_ops = { + .s_stream = mt9m032_s_stream, + .g_frame_interval = mt9m032_get_frame_interval, + .s_frame_interval = mt9m032_set_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops mt9m032_pad_ops = { + .enum_mbus_code = mt9m032_enum_mbus_code, + .enum_frame_size = mt9m032_enum_frame_size, + .get_fmt = mt9m032_get_pad_format, + .set_fmt = mt9m032_set_pad_format, + .set_crop = mt9m032_set_pad_crop, + .get_crop = mt9m032_get_pad_crop, +}; + +static const struct v4l2_subdev_ops mt9m032_ops = { + .core = &mt9m032_core_ops, + .video = &mt9m032_video_ops, + .pad = &mt9m032_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Driver initialization and probing + */ + +static int mt9m032_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct i2c_adapter *adapter = client->adapter; + struct mt9m032 *sensor; + int chip_version; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&client->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + if (!client->dev.platform_data) + return -ENODEV; + + sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); + if (sensor == NULL) + return -ENOMEM; + + mutex_init(&sensor->lock); + + sensor->pdata = client->dev.platform_data; + + v4l2_i2c_subdev_init(&sensor->subdev, client, &mt9m032_ops); + sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + chip_version = mt9m032_read(client, MT9M032_CHIP_VERSION); + if (chip_version != MT9M032_CHIP_VERSION_VALUE) { + dev_err(&client->dev, "MT9M032 not detected, wrong version " + "0x%04x\n", chip_version); + ret = -ENODEV; + goto error_sensor; + } + + dev_info(&client->dev, "MT9M032 detected at address 0x%02x\n", + client->addr); + + sensor->frame_interval.numerator = 1; + sensor->frame_interval.denominator = 30; + + sensor->crop.left = MT9M032_COLUMN_START_DEF; + sensor->crop.top = MT9M032_ROW_START_DEF; + sensor->crop.width = MT9M032_COLUMN_SIZE_DEF; + sensor->crop.height = MT9M032_ROW_SIZE_DEF; + + sensor->format.width = sensor->crop.width; + sensor->format.height = sensor->crop.height; + sensor->format.code = V4L2_MBUS_FMT_Y8_1X8; + sensor->format.field = V4L2_FIELD_NONE; + sensor->format.colorspace = V4L2_COLORSPACE_SRGB; + + v4l2_ctrl_handler_init(&sensor->ctrls, 4); + + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_GAIN, 0, 127, 1, 64); + + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, + &mt9m032_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, + &mt9m032_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&sensor->ctrls, &mt9m032_ctrl_ops, + V4L2_CID_EXPOSURE, MT9M032_SHUTTER_WIDTH_MIN, + MT9M032_SHUTTER_WIDTH_MAX, 1, + MT9M032_SHUTTER_WIDTH_DEF); + + if (sensor->ctrls.error) { + ret = sensor->ctrls.error; + dev_err(&client->dev, "control initialization error %d\n", ret); + goto error_ctrl; + } + + v4l2_ctrl_cluster(2, &sensor->hflip); + + sensor->subdev.ctrl_handler = &sensor->ctrls; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_init(&sensor->subdev.entity, 1, &sensor->pad, 0); + if (ret < 0) + goto error_ctrl; + + ret = mt9m032_write(client, MT9M032_RESET, 1); /* reset on */ + if (ret < 0) + goto error_entity; + mt9m032_write(client, MT9M032_RESET, 0); /* reset off */ + if (ret < 0) + goto error_entity; + + ret = mt9m032_setup_pll(sensor); + if (ret < 0) + goto error_entity; + usleep_range(10000, 11000); + + ret = v4l2_ctrl_handler_setup(&sensor->ctrls); + if (ret < 0) + goto error_entity; + + /* SIZE */ + ret = mt9m032_update_geom_timing(sensor); + if (ret < 0) + goto error_entity; + + ret = mt9m032_write(client, 0x41, 0x0000); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x42, 0x0003); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x43, 0x0003); /* reserved !!! */ + if (ret < 0) + goto error_entity; + ret = mt9m032_write(client, 0x7f, 0x0000); /* reserved !!! */ + if (ret < 0) + goto error_entity; + if (sensor->pdata->invert_pixclock) { + ret = mt9m032_write(client, MT9M032_PIX_CLK_CTRL, + MT9M032_PIX_CLK_CTRL_INV_PIXCLK); + if (ret < 0) + goto error_entity; + } + + ret = mt9m032_write(client, MT9M032_RESTART, 1); /* Restart on */ + if (ret < 0) + goto error_entity; + msleep(100); + ret = mt9m032_write(client, MT9M032_RESTART, 0); /* Restart off */ + if (ret < 0) + goto error_entity; + msleep(100); + ret = update_formatter2(sensor, false); + if (ret < 0) + goto error_entity; + + return ret; + +error_entity: + media_entity_cleanup(&sensor->subdev.entity); +error_ctrl: + v4l2_ctrl_handler_free(&sensor->ctrls); +error_sensor: + mutex_destroy(&sensor->lock); + kfree(sensor); + return ret; +} + +static int mt9m032_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct mt9m032 *sensor = to_mt9m032(subdev); + + v4l2_device_unregister_subdev(&sensor->subdev); + v4l2_ctrl_handler_free(&sensor->ctrls); + media_entity_cleanup(&sensor->subdev.entity); + mutex_destroy(&sensor->lock); + kfree(sensor); + return 0; +} + +static const struct i2c_device_id mt9m032_id_table[] = { + { MT9M032_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, mt9m032_id_table); + +static struct i2c_driver mt9m032_i2c_driver = { + .driver = { + .name = MT9M032_NAME, + }, + .probe = mt9m032_probe, + .remove = mt9m032_remove, + .id_table = mt9m032_id_table, +}; + +module_i2c_driver(mt9m032_i2c_driver); + +MODULE_AUTHOR("Martin Hostettler <martin@neutronstar.dyndns.org>"); +MODULE_DESCRIPTION("MT9M032 camera sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/mt9m111.c b/drivers/media/video/mt9m111.c index bee65bff46e8..b0c529964329 100644 --- a/drivers/media/video/mt9m111.c +++ b/drivers/media/video/mt9m111.c @@ -1008,18 +1008,7 @@ static struct i2c_driver mt9m111_i2c_driver = { .id_table = mt9m111_id, }; -static int __init mt9m111_mod_init(void) -{ - return i2c_add_driver(&mt9m111_i2c_driver); -} - -static void __exit mt9m111_mod_exit(void) -{ - i2c_del_driver(&mt9m111_i2c_driver); -} - -module_init(mt9m111_mod_init); -module_exit(mt9m111_mod_exit); +module_i2c_driver(mt9m111_i2c_driver); MODULE_DESCRIPTION("Micron/Aptina MT9M111/MT9M112/MT9M131 Camera driver"); MODULE_AUTHOR("Robert Jarzmik"); diff --git a/drivers/media/video/mt9p031.c b/drivers/media/video/mt9p031.c index 93c3ec7426e8..c81eaf4fbe01 100644 --- a/drivers/media/video/mt9p031.c +++ b/drivers/media/video/mt9p031.c @@ -19,7 +19,6 @@ #include <linux/log2.h> #include <linux/pm.h> #include <linux/slab.h> -#include <media/v4l2-subdev.h> #include <linux/videodev2.h> #include <media/mt9p031.h> @@ -28,6 +27,8 @@ #include <media/v4l2-device.h> #include <media/v4l2-subdev.h> +#include "aptina-pll.h" + #define MT9P031_PIXEL_ARRAY_WIDTH 2752 #define MT9P031_PIXEL_ARRAY_HEIGHT 2004 @@ -98,14 +99,6 @@ #define MT9P031_TEST_PATTERN_RED 0xa2 #define MT9P031_TEST_PATTERN_BLUE 0xa3 -struct mt9p031_pll_divs { - u32 ext_freq; - u32 target_freq; - u8 m; - u8 n; - u8 p1; -}; - struct mt9p031 { struct v4l2_subdev subdev; struct media_pad pad; @@ -115,10 +108,8 @@ struct mt9p031 { struct mt9p031_platform_data *pdata; struct mutex power_lock; /* lock to protect power_count */ int power_count; - u16 xskip; - u16 yskip; - const struct mt9p031_pll_divs *pll; + struct aptina_pll pll; /* Registers cache */ u16 output_control; @@ -186,33 +177,31 @@ static int mt9p031_reset(struct mt9p031 *mt9p031) 0); } -/* - * This static table uses ext_freq and vdd_io values to select suitable - * PLL dividers m, n and p1 which have been calculated as specifiec in p36 - * of Aptina's mt9p031 datasheet. New values should be added here. - */ -static const struct mt9p031_pll_divs mt9p031_divs[] = { - /* ext_freq target_freq m n p1 */ - {21000000, 48000000, 26, 2, 6} -}; - -static int mt9p031_pll_get_divs(struct mt9p031 *mt9p031) +static int mt9p031_pll_setup(struct mt9p031 *mt9p031) { + static const struct aptina_pll_limits limits = { + .ext_clock_min = 6000000, + .ext_clock_max = 27000000, + .int_clock_min = 2000000, + .int_clock_max = 13500000, + .out_clock_min = 180000000, + .out_clock_max = 360000000, + .pix_clock_max = 96000000, + .n_min = 1, + .n_max = 64, + .m_min = 16, + .m_max = 255, + .p1_min = 1, + .p1_max = 128, + }; + struct i2c_client *client = v4l2_get_subdevdata(&mt9p031->subdev); - int i; + struct mt9p031_platform_data *pdata = mt9p031->pdata; - for (i = 0; i < ARRAY_SIZE(mt9p031_divs); i++) { - if (mt9p031_divs[i].ext_freq == mt9p031->pdata->ext_freq && - mt9p031_divs[i].target_freq == mt9p031->pdata->target_freq) { - mt9p031->pll = &mt9p031_divs[i]; - return 0; - } - } + mt9p031->pll.ext_clock = pdata->ext_freq; + mt9p031->pll.pix_clock = pdata->target_freq; - dev_err(&client->dev, "Couldn't find PLL dividers for ext_freq = %d, " - "target_freq = %d\n", mt9p031->pdata->ext_freq, - mt9p031->pdata->target_freq); - return -EINVAL; + return aptina_pll_calculate(&client->dev, &limits, &mt9p031->pll); } static int mt9p031_pll_enable(struct mt9p031 *mt9p031) @@ -226,11 +215,11 @@ static int mt9p031_pll_enable(struct mt9p031 *mt9p031) return ret; ret = mt9p031_write(client, MT9P031_PLL_CONFIG_1, - (mt9p031->pll->m << 8) | (mt9p031->pll->n - 1)); + (mt9p031->pll.m << 8) | (mt9p031->pll.n - 1)); if (ret < 0) return ret; - ret = mt9p031_write(client, MT9P031_PLL_CONFIG_2, mt9p031->pll->p1 - 1); + ret = mt9p031_write(client, MT9P031_PLL_CONFIG_2, mt9p031->pll.p1 - 1); if (ret < 0) return ret; @@ -785,8 +774,6 @@ static int mt9p031_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) format->field = V4L2_FIELD_NONE; format->colorspace = V4L2_COLORSPACE_SRGB; - mt9p031->xskip = 1; - mt9p031->yskip = 1; return mt9p031_set_power(subdev, 1); } @@ -905,7 +892,7 @@ static int mt9p031_probe(struct i2c_client *client, mt9p031->format.field = V4L2_FIELD_NONE; mt9p031->format.colorspace = V4L2_COLORSPACE_SRGB; - ret = mt9p031_pll_get_divs(mt9p031); + ret = mt9p031_pll_setup(mt9p031); done: if (ret < 0) { @@ -945,18 +932,7 @@ static struct i2c_driver mt9p031_i2c_driver = { .id_table = mt9p031_id, }; -static int __init mt9p031_mod_init(void) -{ - return i2c_add_driver(&mt9p031_i2c_driver); -} - -static void __exit mt9p031_mod_exit(void) -{ - i2c_del_driver(&mt9p031_i2c_driver); -} - -module_init(mt9p031_mod_init); -module_exit(mt9p031_mod_exit); +module_i2c_driver(mt9p031_i2c_driver); MODULE_DESCRIPTION("Aptina MT9P031 Camera driver"); MODULE_AUTHOR("Bastian Hecht <hechtb@gmail.com>"); diff --git a/drivers/media/video/mt9t001.c b/drivers/media/video/mt9t001.c index cd81d04a529e..49ca3cbfc6f1 100644 --- a/drivers/media/video/mt9t001.c +++ b/drivers/media/video/mt9t001.c @@ -817,18 +817,7 @@ static struct i2c_driver mt9t001_driver = { .id_table = mt9t001_id, }; -static int __init mt9t001_init(void) -{ - return i2c_add_driver(&mt9t001_driver); -} - -static void __exit mt9t001_exit(void) -{ - i2c_del_driver(&mt9t001_driver); -} - -module_init(mt9t001_init); -module_exit(mt9t001_exit); +module_i2c_driver(mt9t001_driver); MODULE_DESCRIPTION("Aptina (Micron) MT9T001 Camera driver"); MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); diff --git a/drivers/media/video/mt9t031.c b/drivers/media/video/mt9t031.c index 84add1aef139..1415074138a5 100644 --- a/drivers/media/video/mt9t031.c +++ b/drivers/media/video/mt9t031.c @@ -850,18 +850,7 @@ static struct i2c_driver mt9t031_i2c_driver = { .id_table = mt9t031_id, }; -static int __init mt9t031_mod_init(void) -{ - return i2c_add_driver(&mt9t031_i2c_driver); -} - -static void __exit mt9t031_mod_exit(void) -{ - i2c_del_driver(&mt9t031_i2c_driver); -} - -module_init(mt9t031_mod_init); -module_exit(mt9t031_mod_exit); +module_i2c_driver(mt9t031_i2c_driver); MODULE_DESCRIPTION("Micron MT9T031 Camera driver"); MODULE_AUTHOR("Guennadi Liakhovetski <lg@denx.de>"); diff --git a/drivers/media/video/mt9t112.c b/drivers/media/video/mt9t112.c index 7b34b11daf24..8d1445f12708 100644 --- a/drivers/media/video/mt9t112.c +++ b/drivers/media/video/mt9t112.c @@ -1117,21 +1117,7 @@ static struct i2c_driver mt9t112_i2c_driver = { .id_table = mt9t112_id, }; -/************************************************************************ - module function -************************************************************************/ -static int __init mt9t112_module_init(void) -{ - return i2c_add_driver(&mt9t112_i2c_driver); -} - -static void __exit mt9t112_module_exit(void) -{ - i2c_del_driver(&mt9t112_i2c_driver); -} - -module_init(mt9t112_module_init); -module_exit(mt9t112_module_exit); +module_i2c_driver(mt9t112_i2c_driver); MODULE_DESCRIPTION("SoC Camera driver for mt9t112"); MODULE_AUTHOR("Kuninori Morimoto"); diff --git a/drivers/media/video/mt9v011.c b/drivers/media/video/mt9v011.c index db74dd27c722..6bf01ad62765 100644 --- a/drivers/media/video/mt9v011.c +++ b/drivers/media/video/mt9v011.c @@ -709,15 +709,4 @@ static struct i2c_driver mt9v011_driver = { .id_table = mt9v011_id, }; -static __init int init_mt9v011(void) -{ - return i2c_add_driver(&mt9v011_driver); -} - -static __exit void exit_mt9v011(void) -{ - i2c_del_driver(&mt9v011_driver); -} - -module_init(init_mt9v011); -module_exit(exit_mt9v011); +module_i2c_driver(mt9v011_driver); diff --git a/drivers/media/video/mt9v022.c b/drivers/media/video/mt9v022.c index 944940758fa3..bf63417adb8f 100644 --- a/drivers/media/video/mt9v022.c +++ b/drivers/media/video/mt9v022.c @@ -872,18 +872,7 @@ static struct i2c_driver mt9v022_i2c_driver = { .id_table = mt9v022_id, }; -static int __init mt9v022_mod_init(void) -{ - return i2c_add_driver(&mt9v022_i2c_driver); -} - -static void __exit mt9v022_mod_exit(void) -{ - i2c_del_driver(&mt9v022_i2c_driver); -} - -module_init(mt9v022_mod_init); -module_exit(mt9v022_mod_exit); +module_i2c_driver(mt9v022_i2c_driver); MODULE_DESCRIPTION("Micron MT9V022 Camera driver"); MODULE_AUTHOR("Guennadi Liakhovetski <kernel@pengutronix.de>"); diff --git a/drivers/media/video/mt9v032.c b/drivers/media/video/mt9v032.c index d90b982cc218..75e253a343c5 100644 --- a/drivers/media/video/mt9v032.c +++ b/drivers/media/video/mt9v032.c @@ -756,18 +756,7 @@ static struct i2c_driver mt9v032_driver = { .id_table = mt9v032_id, }; -static int __init mt9v032_init(void) -{ - return i2c_add_driver(&mt9v032_driver); -} - -static void __exit mt9v032_exit(void) -{ - i2c_del_driver(&mt9v032_driver); -} - -module_init(mt9v032_init); -module_exit(mt9v032_exit); +module_i2c_driver(mt9v032_driver); MODULE_DESCRIPTION("Aptina MT9V032 Camera driver"); MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); diff --git a/drivers/media/video/mx2_camera.c b/drivers/media/video/mx2_camera.c index 04aab0c538aa..18afaeeadb7b 100644 --- a/drivers/media/video/mx2_camera.c +++ b/drivers/media/video/mx2_camera.c @@ -3,6 +3,7 @@ * * Copyright (C) 2008, Sascha Hauer, Pengutronix * Copyright (C) 2010, Baruch Siach, Orex Computed Radiography + * Copyright (C) 2012, Javier Martin, Vista Silicon S.L. * * 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 @@ -18,6 +19,7 @@ #include <linux/dma-mapping.h> #include <linux/errno.h> #include <linux/fs.h> +#include <linux/gcd.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/mm.h> @@ -30,17 +32,14 @@ #include <media/v4l2-common.h> #include <media/v4l2-dev.h> -#include <media/videobuf-core.h> -#include <media/videobuf-dma-contig.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> #include <media/soc_camera.h> #include <media/soc_mediabus.h> #include <linux/videodev2.h> #include <mach/mx2_cam.h> -#ifdef CONFIG_MACH_MX27 -#include <mach/dma-mx1-mx2.h> -#endif #include <mach/hardware.h> #include <asm/dma.h> @@ -206,10 +205,23 @@ #define PRP_INTR_LBOVF (1 << 7) #define PRP_INTR_CH2OVF (1 << 8) -#define mx27_camera_emma(pcdev) (cpu_is_mx27() && pcdev->use_emma) +/* Resizing registers */ +#define PRP_RZ_VALID_TBL_LEN(x) ((x) << 24) +#define PRP_RZ_VALID_BILINEAR (1 << 31) #define MAX_VIDEO_MEM 16 +#define RESIZE_NUM_MIN 1 +#define RESIZE_NUM_MAX 20 +#define BC_COEF 3 +#define SZ_COEF (1 << BC_COEF) + +#define RESIZE_DIR_H 0 +#define RESIZE_DIR_V 1 + +#define RESIZE_ALGO_BILINEAR 0 +#define RESIZE_ALGO_AVERAGING 1 + struct mx2_prp_cfg { int channel; u32 in_fmt; @@ -219,6 +231,13 @@ struct mx2_prp_cfg { u32 irq_flags; }; +/* prp resizing parameters */ +struct emma_prp_resize { + int algo; /* type of algorithm used */ + int len; /* number of coefficients */ + unsigned char s[RESIZE_NUM_MAX]; /* table of coefficients */ +}; + /* prp configuration for a client-host fmt pair */ struct mx2_fmt_cfg { enum v4l2_mbus_pixelcode in_fmt; @@ -226,6 +245,26 @@ struct mx2_fmt_cfg { struct mx2_prp_cfg cfg; }; +enum mx2_buffer_state { + MX2_STATE_QUEUED, + MX2_STATE_ACTIVE, + MX2_STATE_DONE, +}; + +struct mx2_buf_internal { + struct list_head queue; + int bufnum; + bool discard; +}; + +/* buffer for one video frame */ +struct mx2_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_buffer vb; + enum mx2_buffer_state state; + struct mx2_buf_internal internal; +}; + struct mx2_camera_dev { struct device *dev; struct soc_camera_host soc_host; @@ -242,6 +281,7 @@ struct mx2_camera_dev { struct list_head capture; struct list_head active_bufs; + struct list_head discard; spinlock_t lock; @@ -250,26 +290,23 @@ struct mx2_camera_dev { struct mx2_buffer *fb1_active; struct mx2_buffer *fb2_active; - int use_emma; - u32 csicr1; + struct mx2_buf_internal buf_discard[2]; void *discard_buffer; dma_addr_t discard_buffer_dma; size_t discard_size; struct mx2_fmt_cfg *emma_prp; + struct emma_prp_resize resizing[2]; + unsigned int s_width, s_height; u32 frame_count; + struct vb2_alloc_ctx *alloc_ctx; }; -/* buffer for one video frame */ -struct mx2_buffer { - /* common v4l buffer stuff -- must be first */ - struct videobuf_buffer vb; - - enum v4l2_mbus_pixelcode code; - - int bufnum; -}; +static struct mx2_buffer *mx2_ibuf_to_buf(struct mx2_buf_internal *int_buf) +{ + return container_of(int_buf, struct mx2_buffer, internal); +} static struct mx2_fmt_cfg mx27_emma_prp_table[] = { /* @@ -324,13 +361,36 @@ static struct mx2_fmt_cfg *mx27_emma_prp_get_format( return &mx27_emma_prp_table[0]; }; +static void mx27_update_emma_buf(struct mx2_camera_dev *pcdev, + unsigned long phys, int bufnum) +{ + struct mx2_fmt_cfg *prp = pcdev->emma_prp; + + if (prp->cfg.channel == 1) { + writel(phys, pcdev->base_emma + + PRP_DEST_RGB1_PTR + 4 * bufnum); + } else { + writel(phys, pcdev->base_emma + + PRP_DEST_Y_PTR - 0x14 * bufnum); + if (prp->out_fmt == V4L2_PIX_FMT_YUV420) { + u32 imgsize = pcdev->icd->user_height * + pcdev->icd->user_width; + + writel(phys + imgsize, pcdev->base_emma + + PRP_DEST_CB_PTR - 0x14 * bufnum); + writel(phys + ((5 * imgsize) / 4), pcdev->base_emma + + PRP_DEST_CR_PTR - 0x14 * bufnum); + } + } +} + static void mx2_camera_deactivate(struct mx2_camera_dev *pcdev) { unsigned long flags; clk_disable(pcdev->clk_csi); writel(0, pcdev->base_csi + CSICR1); - if (mx27_camera_emma(pcdev)) { + if (cpu_is_mx27()) { writel(0, pcdev->base_emma + PRP_CNTL); } else if (cpu_is_mx25()) { spin_lock_irqsave(&pcdev->lock, flags); @@ -362,7 +422,7 @@ static int mx2_camera_add_device(struct soc_camera_device *icd) csicr1 = CSICR1_MCLKEN; - if (mx27_camera_emma(pcdev)) { + if (cpu_is_mx27()) { csicr1 |= CSICR1_PRP_IF_EN | CSICR1_FCC | CSICR1_RXFF_LEVEL(0); } else if (cpu_is_mx27()) @@ -392,56 +452,13 @@ static void mx2_camera_remove_device(struct soc_camera_device *icd) mx2_camera_deactivate(pcdev); - if (pcdev->discard_buffer) { - dma_free_coherent(ici->v4l2_dev.dev, pcdev->discard_size, - pcdev->discard_buffer, - pcdev->discard_buffer_dma); - pcdev->discard_buffer = NULL; - } - pcdev->icd = NULL; } -#ifdef CONFIG_MACH_MX27 -static void mx27_camera_dma_enable(struct mx2_camera_dev *pcdev) -{ - u32 tmp; - - imx_dma_enable(pcdev->dma); - - tmp = readl(pcdev->base_csi + CSICR1); - tmp |= CSICR1_RF_OR_INTEN; - writel(tmp, pcdev->base_csi + CSICR1); -} - -static irqreturn_t mx27_camera_irq(int irq_csi, void *data) -{ - struct mx2_camera_dev *pcdev = data; - u32 status = readl(pcdev->base_csi + CSISR); - - if (status & CSISR_SOF_INT && pcdev->active) { - u32 tmp; - - tmp = readl(pcdev->base_csi + CSICR1); - writel(tmp | CSICR1_CLR_RXFIFO, pcdev->base_csi + CSICR1); - mx27_camera_dma_enable(pcdev); - } - - writel(CSISR_SOF_INT | CSISR_RFF_OR_INT, pcdev->base_csi + CSISR); - - return IRQ_HANDLED; -} -#else -static irqreturn_t mx27_camera_irq(int irq_csi, void *data) -{ - return IRQ_NONE; -} -#endif /* CONFIG_MACH_MX27 */ - static void mx25_camera_frame_done(struct mx2_camera_dev *pcdev, int fb, int state) { - struct videobuf_buffer *vb; + struct vb2_buffer *vb; struct mx2_buffer *buf; struct mx2_buffer **fb_active = fb == 1 ? &pcdev->fb1_active : &pcdev->fb2_active; @@ -454,25 +471,24 @@ static void mx25_camera_frame_done(struct mx2_camera_dev *pcdev, int fb, goto out; vb = &(*fb_active)->vb; - dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, - vb, vb->baddr, vb->bsize); + dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); - vb->state = state; - do_gettimeofday(&vb->ts); - vb->field_count++; - - wake_up(&vb->done); + do_gettimeofday(&vb->v4l2_buf.timestamp); + vb->v4l2_buf.sequence++; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); if (list_empty(&pcdev->capture)) { buf = NULL; writel(0, pcdev->base_csi + fb_reg); } else { - buf = list_entry(pcdev->capture.next, struct mx2_buffer, - vb.queue); + buf = list_first_entry(&pcdev->capture, struct mx2_buffer, + internal.queue); vb = &buf->vb; - list_del(&vb->queue); - vb->state = VIDEOBUF_ACTIVE; - writel(videobuf_to_dma_contig(vb), pcdev->base_csi + fb_reg); + list_del(&buf->internal.queue); + buf->state = MX2_STATE_ACTIVE; + writel(vb2_dma_contig_plane_dma_addr(vb, 0), + pcdev->base_csi + fb_reg); } *fb_active = buf; @@ -487,9 +503,9 @@ static irqreturn_t mx25_camera_irq(int irq_csi, void *data) u32 status = readl(pcdev->base_csi + CSISR); if (status & CSISR_DMA_TSF_FB1_INT) - mx25_camera_frame_done(pcdev, 1, VIDEOBUF_DONE); + mx25_camera_frame_done(pcdev, 1, MX2_STATE_DONE); else if (status & CSISR_DMA_TSF_FB2_INT) - mx25_camera_frame_done(pcdev, 2, VIDEOBUF_DONE); + mx25_camera_frame_done(pcdev, 2, MX2_STATE_DONE); /* FIXME: handle CSISR_RFF_OR_INT */ @@ -501,59 +517,50 @@ static irqreturn_t mx25_camera_irq(int irq_csi, void *data) /* * Videobuf operations */ -static int mx2_videobuf_setup(struct videobuf_queue *vq, unsigned int *count, - unsigned int *size) +static int mx2_videobuf_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *count, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) { - struct soc_camera_device *icd = vq->priv_data; + struct soc_camera_device *icd = soc_camera_from_vb2q(vq); + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, icd->current_fmt->host_fmt); - dev_dbg(icd->parent, "count=%d, size=%d\n", *count, *size); + dev_dbg(icd->parent, "count=%d, size=%d\n", *count, sizes[0]); + + /* TODO: support for VIDIOC_CREATE_BUFS not ready */ + if (fmt != NULL) + return -ENOTTY; if (bytes_per_line < 0) return bytes_per_line; - *size = bytes_per_line * icd->user_height; + alloc_ctxs[0] = pcdev->alloc_ctx; + + sizes[0] = bytes_per_line * icd->user_height; if (0 == *count) *count = 32; - if (*size * *count > MAX_VIDEO_MEM * 1024 * 1024) - *count = (MAX_VIDEO_MEM * 1024 * 1024) / *size; + if (!*num_planes && + sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024) + *count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0]; - return 0; -} + *num_planes = 1; -static void free_buffer(struct videobuf_queue *vq, struct mx2_buffer *buf) -{ - struct soc_camera_device *icd = vq->priv_data; - struct videobuf_buffer *vb = &buf->vb; - - dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__, - vb, vb->baddr, vb->bsize); - - /* - * This waits until this buffer is out of danger, i.e., until it is no - * longer in state VIDEOBUF_QUEUED or VIDEOBUF_ACTIVE - */ - videobuf_waiton(vq, vb, 0, 0); - - videobuf_dma_contig_free(vq, vb); - dev_dbg(icd->parent, "%s freed\n", __func__); - - vb->state = VIDEOBUF_NEEDS_INIT; + return 0; } -static int mx2_videobuf_prepare(struct videobuf_queue *vq, - struct videobuf_buffer *vb, enum v4l2_field field) +static int mx2_videobuf_prepare(struct vb2_buffer *vb) { - struct soc_camera_device *icd = vq->priv_data; - struct mx2_buffer *buf = container_of(vb, struct mx2_buffer, vb); + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width, icd->current_fmt->host_fmt); int ret = 0; - dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__, - vb, vb->baddr, vb->bsize); + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); if (bytes_per_line < 0) return bytes_per_line; @@ -563,99 +570,58 @@ static int mx2_videobuf_prepare(struct videobuf_queue *vq, * This can be useful if you want to see if we actually fill * the buffer with something */ - memset((void *)vb->baddr, 0xaa, vb->bsize); + memset((void *)vb2_plane_vaddr(vb, 0), + 0xaa, vb2_get_plane_payload(vb, 0)); #endif - if (buf->code != icd->current_fmt->code || - vb->width != icd->user_width || - vb->height != icd->user_height || - vb->field != field) { - buf->code = icd->current_fmt->code; - vb->width = icd->user_width; - vb->height = icd->user_height; - vb->field = field; - vb->state = VIDEOBUF_NEEDS_INIT; - } - - vb->size = bytes_per_line * vb->height; - if (vb->baddr && vb->bsize < vb->size) { + vb2_set_plane_payload(vb, 0, bytes_per_line * icd->user_height); + if (vb2_plane_vaddr(vb, 0) && + vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) { ret = -EINVAL; goto out; } - if (vb->state == VIDEOBUF_NEEDS_INIT) { - ret = videobuf_iolock(vq, vb, NULL); - if (ret) - goto fail; - - vb->state = VIDEOBUF_PREPARED; - } - return 0; -fail: - free_buffer(vq, buf); out: return ret; } -static void mx2_videobuf_queue(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void mx2_videobuf_queue(struct vb2_buffer *vb) { - struct soc_camera_device *icd = vq->priv_data; + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct mx2_camera_dev *pcdev = ici->priv; struct mx2_buffer *buf = container_of(vb, struct mx2_buffer, vb); unsigned long flags; - dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__, - vb, vb->baddr, vb->bsize); + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); spin_lock_irqsave(&pcdev->lock, flags); - vb->state = VIDEOBUF_QUEUED; - list_add_tail(&vb->queue, &pcdev->capture); + buf->state = MX2_STATE_QUEUED; + list_add_tail(&buf->internal.queue, &pcdev->capture); - if (mx27_camera_emma(pcdev)) { - goto out; -#ifdef CONFIG_MACH_MX27 - } else if (cpu_is_mx27()) { - int ret; - - if (pcdev->active == NULL) { - ret = imx_dma_setup_single(pcdev->dma, - videobuf_to_dma_contig(vb), vb->size, - (u32)pcdev->base_dma + 0x10, - DMA_MODE_READ); - if (ret) { - vb->state = VIDEOBUF_ERROR; - wake_up(&vb->done); - goto out; - } - - vb->state = VIDEOBUF_ACTIVE; - pcdev->active = buf; - } -#endif - } else { /* cpu_is_mx25() */ + if (cpu_is_mx25()) { u32 csicr3, dma_inten = 0; if (pcdev->fb1_active == NULL) { - writel(videobuf_to_dma_contig(vb), + writel(vb2_dma_contig_plane_dma_addr(vb, 0), pcdev->base_csi + CSIDMASA_FB1); pcdev->fb1_active = buf; dma_inten = CSICR1_FB1_DMA_INTEN; } else if (pcdev->fb2_active == NULL) { - writel(videobuf_to_dma_contig(vb), + writel(vb2_dma_contig_plane_dma_addr(vb, 0), pcdev->base_csi + CSIDMASA_FB2); pcdev->fb2_active = buf; dma_inten = CSICR1_FB2_DMA_INTEN; } if (dma_inten) { - list_del(&vb->queue); - vb->state = VIDEOBUF_ACTIVE; + list_del(&buf->internal.queue); + buf->state = MX2_STATE_ACTIVE; csicr3 = readl(pcdev->base_csi + CSICR3); @@ -674,36 +640,31 @@ static void mx2_videobuf_queue(struct videobuf_queue *vq, } } -out: spin_unlock_irqrestore(&pcdev->lock, flags); } -static void mx2_videobuf_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void mx2_videobuf_release(struct vb2_buffer *vb) { - struct soc_camera_device *icd = vq->priv_data; + struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue); struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct mx2_camera_dev *pcdev = ici->priv; struct mx2_buffer *buf = container_of(vb, struct mx2_buffer, vb); unsigned long flags; #ifdef DEBUG - dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__, - vb, vb->baddr, vb->bsize); + dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__, + vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0)); - switch (vb->state) { - case VIDEOBUF_ACTIVE: + switch (buf->state) { + case MX2_STATE_ACTIVE: dev_info(icd->parent, "%s (active)\n", __func__); break; - case VIDEOBUF_QUEUED: + case MX2_STATE_QUEUED: dev_info(icd->parent, "%s (queued)\n", __func__); break; - case VIDEOBUF_PREPARED: - dev_info(icd->parent, "%s (prepared)\n", __func__); - break; default: dev_info(icd->parent, "%s (unknown) %d\n", __func__, - vb->state); + buf->state); break; } #endif @@ -717,11 +678,9 @@ static void mx2_videobuf_release(struct videobuf_queue *vq, * state. This requires a specific handling for each of the these DMA * types. */ + spin_lock_irqsave(&pcdev->lock, flags); - if (vb->state == VIDEOBUF_QUEUED) { - list_del(&vb->queue); - vb->state = VIDEOBUF_ERROR; - } else if (cpu_is_mx25() && vb->state == VIDEOBUF_ACTIVE) { + if (cpu_is_mx25() && buf->state == MX2_STATE_ACTIVE) { if (pcdev->fb1_active == buf) { pcdev->csicr1 &= ~CSICR1_FB1_DMA_INTEN; writel(0, pcdev->base_csi + CSIDMASA_FB1); @@ -732,75 +691,178 @@ static void mx2_videobuf_release(struct videobuf_queue *vq, pcdev->fb2_active = NULL; } writel(pcdev->csicr1, pcdev->base_csi + CSICR1); - vb->state = VIDEOBUF_ERROR; } spin_unlock_irqrestore(&pcdev->lock, flags); - - free_buffer(vq, buf); } -static struct videobuf_queue_ops mx2_videobuf_ops = { - .buf_setup = mx2_videobuf_setup, - .buf_prepare = mx2_videobuf_prepare, - .buf_queue = mx2_videobuf_queue, - .buf_release = mx2_videobuf_release, -}; - -static void mx2_camera_init_videobuf(struct videobuf_queue *q, - struct soc_camera_device *icd) +static void mx27_camera_emma_buf_init(struct soc_camera_device *icd, + int bytesperline) { - struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct soc_camera_host *ici = + to_soc_camera_host(icd->parent); struct mx2_camera_dev *pcdev = ici->priv; + struct mx2_fmt_cfg *prp = pcdev->emma_prp; - videobuf_queue_dma_contig_init(q, &mx2_videobuf_ops, pcdev->dev, - &pcdev->lock, V4L2_BUF_TYPE_VIDEO_CAPTURE, - V4L2_FIELD_NONE, sizeof(struct mx2_buffer), - icd, &icd->video_lock); -} + writel((pcdev->s_width << 16) | pcdev->s_height, + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + writel(prp->cfg.src_pixel, + pcdev->base_emma + PRP_SRC_PIXEL_FORMAT_CNTL); + if (prp->cfg.channel == 1) { + writel((icd->user_width << 16) | icd->user_height, + pcdev->base_emma + PRP_CH1_OUT_IMAGE_SIZE); + writel(bytesperline, + pcdev->base_emma + PRP_DEST_CH1_LINE_STRIDE); + writel(prp->cfg.ch1_pixel, + pcdev->base_emma + PRP_CH1_PIXEL_FORMAT_CNTL); + } else { /* channel 2 */ + writel((icd->user_width << 16) | icd->user_height, + pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + } -#define MX2_BUS_FLAGS (V4L2_MBUS_MASTER | \ - V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ - V4L2_MBUS_VSYNC_ACTIVE_LOW | \ - V4L2_MBUS_HSYNC_ACTIVE_HIGH | \ - V4L2_MBUS_HSYNC_ACTIVE_LOW | \ - V4L2_MBUS_PCLK_SAMPLE_RISING | \ - V4L2_MBUS_PCLK_SAMPLE_FALLING | \ - V4L2_MBUS_DATA_ACTIVE_HIGH | \ - V4L2_MBUS_DATA_ACTIVE_LOW) + /* Enable interrupts */ + writel(prp->cfg.irq_flags, pcdev->base_emma + PRP_INTR_CNTL); +} -static int mx27_camera_emma_prp_reset(struct mx2_camera_dev *pcdev) +static void mx2_prp_resize_commit(struct mx2_camera_dev *pcdev) { - u32 cntl; - int count = 0; + int dir; - cntl = readl(pcdev->base_emma + PRP_CNTL); - writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); - while (count++ < 100) { - if (!(readl(pcdev->base_emma + PRP_CNTL) & PRP_CNTL_SWRST)) - return 0; - barrier(); - udelay(1); - } + for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) { + unsigned char *s = pcdev->resizing[dir].s; + int len = pcdev->resizing[dir].len; + unsigned int coeff[2] = {0, 0}; + unsigned int valid = 0; + int i; - return -ETIMEDOUT; + if (len == 0) + continue; + + for (i = RESIZE_NUM_MAX - 1; i >= 0; i--) { + int j; + + j = i > 9 ? 1 : 0; + coeff[j] = (coeff[j] << BC_COEF) | + (s[i] & (SZ_COEF - 1)); + + if (i == 5 || i == 15) + coeff[j] <<= 1; + + valid = (valid << 1) | (s[i] >> BC_COEF); + } + + valid |= PRP_RZ_VALID_TBL_LEN(len); + + if (pcdev->resizing[dir].algo == RESIZE_ALGO_BILINEAR) + valid |= PRP_RZ_VALID_BILINEAR; + + if (pcdev->emma_prp->cfg.channel == 1) { + if (dir == RESIZE_DIR_H) { + writel(coeff[0], pcdev->base_emma + + PRP_CH1_RZ_HORI_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH1_RZ_HORI_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH1_RZ_HORI_VALID); + } else { + writel(coeff[0], pcdev->base_emma + + PRP_CH1_RZ_VERT_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH1_RZ_VERT_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH1_RZ_VERT_VALID); + } + } else { + if (dir == RESIZE_DIR_H) { + writel(coeff[0], pcdev->base_emma + + PRP_CH2_RZ_HORI_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH2_RZ_HORI_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH2_RZ_HORI_VALID); + } else { + writel(coeff[0], pcdev->base_emma + + PRP_CH2_RZ_VERT_COEF1); + writel(coeff[1], pcdev->base_emma + + PRP_CH2_RZ_VERT_COEF2); + writel(valid, pcdev->base_emma + + PRP_CH2_RZ_VERT_VALID); + } + } + } } -static void mx27_camera_emma_buf_init(struct soc_camera_device *icd, - int bytesperline) +static int mx2_start_streaming(struct vb2_queue *q, unsigned int count) { + struct soc_camera_device *icd = soc_camera_from_vb2q(q); struct soc_camera_host *ici = to_soc_camera_host(icd->parent); struct mx2_camera_dev *pcdev = ici->priv; struct mx2_fmt_cfg *prp = pcdev->emma_prp; - u32 imgsize = pcdev->icd->user_height * pcdev->icd->user_width; + struct vb2_buffer *vb; + struct mx2_buffer *buf; + unsigned long phys; + int bytesperline; - if (prp->cfg.channel == 1) { - writel(pcdev->discard_buffer_dma, - pcdev->base_emma + PRP_DEST_RGB1_PTR); - writel(pcdev->discard_buffer_dma, - pcdev->base_emma + PRP_DEST_RGB2_PTR); + if (cpu_is_mx27()) { + unsigned long flags; + if (count < 2) + return -EINVAL; + + spin_lock_irqsave(&pcdev->lock, flags); + + buf = list_first_entry(&pcdev->capture, struct mx2_buffer, + internal.queue); + buf->internal.bufnum = 0; + vb = &buf->vb; + buf->state = MX2_STATE_ACTIVE; + + phys = vb2_dma_contig_plane_dma_addr(vb, 0); + mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum); + list_move_tail(pcdev->capture.next, &pcdev->active_bufs); + + buf = list_first_entry(&pcdev->capture, struct mx2_buffer, + internal.queue); + buf->internal.bufnum = 1; + vb = &buf->vb; + buf->state = MX2_STATE_ACTIVE; - writel(PRP_CNTL_CH1EN | + phys = vb2_dma_contig_plane_dma_addr(vb, 0); + mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum); + list_move_tail(pcdev->capture.next, &pcdev->active_bufs); + + bytesperline = soc_mbus_bytes_per_line(icd->user_width, + icd->current_fmt->host_fmt); + if (bytesperline < 0) + return bytesperline; + + /* + * I didn't manage to properly enable/disable the prp + * on a per frame basis during running transfers, + * thus we allocate a buffer here and use it to + * discard frames when no buffer is available. + * Feel free to work on this ;) + */ + pcdev->discard_size = icd->user_height * bytesperline; + pcdev->discard_buffer = dma_alloc_coherent(ici->v4l2_dev.dev, + pcdev->discard_size, &pcdev->discard_buffer_dma, + GFP_KERNEL); + if (!pcdev->discard_buffer) + return -ENOMEM; + + pcdev->buf_discard[0].discard = true; + list_add_tail(&pcdev->buf_discard[0].queue, + &pcdev->discard); + + pcdev->buf_discard[1].discard = true; + list_add_tail(&pcdev->buf_discard[1].queue, + &pcdev->discard); + + mx2_prp_resize_commit(pcdev); + + mx27_camera_emma_buf_init(icd, bytesperline); + + if (prp->cfg.channel == 1) { + writel(PRP_CNTL_CH1EN | PRP_CNTL_CSIEN | prp->cfg.in_fmt | prp->cfg.out_fmt | @@ -809,56 +871,107 @@ static void mx27_camera_emma_buf_init(struct soc_camera_device *icd, PRP_CNTL_CH1_TSKIP(0) | PRP_CNTL_IN_TSKIP(0), pcdev->base_emma + PRP_CNTL); + } else { + writel(PRP_CNTL_CH2EN | + PRP_CNTL_CSIEN | + prp->cfg.in_fmt | + prp->cfg.out_fmt | + PRP_CNTL_CH2_LEN | + PRP_CNTL_CH2_TSKIP(0) | + PRP_CNTL_IN_TSKIP(0), + pcdev->base_emma + PRP_CNTL); + } + spin_unlock_irqrestore(&pcdev->lock, flags); + } - writel((icd->user_width << 16) | icd->user_height, - pcdev->base_emma + PRP_SRC_FRAME_SIZE); - writel((icd->user_width << 16) | icd->user_height, - pcdev->base_emma + PRP_CH1_OUT_IMAGE_SIZE); - writel(bytesperline, - pcdev->base_emma + PRP_DEST_CH1_LINE_STRIDE); - writel(prp->cfg.src_pixel, - pcdev->base_emma + PRP_SRC_PIXEL_FORMAT_CNTL); - writel(prp->cfg.ch1_pixel, - pcdev->base_emma + PRP_CH1_PIXEL_FORMAT_CNTL); - } else { /* channel 2 */ - writel(pcdev->discard_buffer_dma, - pcdev->base_emma + PRP_DEST_Y_PTR); - writel(pcdev->discard_buffer_dma, - pcdev->base_emma + PRP_SOURCE_Y_PTR); - - if (prp->cfg.out_fmt == PRP_CNTL_CH2_OUT_YUV420) { - writel(pcdev->discard_buffer_dma + imgsize, - pcdev->base_emma + PRP_DEST_CB_PTR); - writel(pcdev->discard_buffer_dma + ((5 * imgsize) / 4), - pcdev->base_emma + PRP_DEST_CR_PTR); - writel(pcdev->discard_buffer_dma + imgsize, - pcdev->base_emma + PRP_SOURCE_CB_PTR); - writel(pcdev->discard_buffer_dma + ((5 * imgsize) / 4), - pcdev->base_emma + PRP_SOURCE_CR_PTR); + return 0; +} + +static int mx2_stop_streaming(struct vb2_queue *q) +{ + struct soc_camera_device *icd = soc_camera_from_vb2q(q); + struct soc_camera_host *ici = + to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; + struct mx2_fmt_cfg *prp = pcdev->emma_prp; + unsigned long flags; + void *b; + u32 cntl; + + if (cpu_is_mx27()) { + spin_lock_irqsave(&pcdev->lock, flags); + + cntl = readl(pcdev->base_emma + PRP_CNTL); + if (prp->cfg.channel == 1) { + writel(cntl & ~PRP_CNTL_CH1EN, + pcdev->base_emma + PRP_CNTL); + } else { + writel(cntl & ~PRP_CNTL_CH2EN, + pcdev->base_emma + PRP_CNTL); } + INIT_LIST_HEAD(&pcdev->capture); + INIT_LIST_HEAD(&pcdev->active_bufs); + INIT_LIST_HEAD(&pcdev->discard); - writel(PRP_CNTL_CH2EN | - PRP_CNTL_CSIEN | - prp->cfg.in_fmt | - prp->cfg.out_fmt | - PRP_CNTL_CH2_LEN | - PRP_CNTL_CH2_TSKIP(0) | - PRP_CNTL_IN_TSKIP(0), - pcdev->base_emma + PRP_CNTL); + b = pcdev->discard_buffer; + pcdev->discard_buffer = NULL; - writel((icd->user_width << 16) | icd->user_height, - pcdev->base_emma + PRP_SRC_FRAME_SIZE); + spin_unlock_irqrestore(&pcdev->lock, flags); - writel((icd->user_width << 16) | icd->user_height, - pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + dma_free_coherent(ici->v4l2_dev.dev, + pcdev->discard_size, b, pcdev->discard_buffer_dma); + } - writel(prp->cfg.src_pixel, - pcdev->base_emma + PRP_SRC_PIXEL_FORMAT_CNTL); + return 0; +} + +static struct vb2_ops mx2_videobuf_ops = { + .queue_setup = mx2_videobuf_setup, + .buf_prepare = mx2_videobuf_prepare, + .buf_queue = mx2_videobuf_queue, + .buf_cleanup = mx2_videobuf_release, + .start_streaming = mx2_start_streaming, + .stop_streaming = mx2_stop_streaming, +}; + +static int mx2_camera_init_videobuf(struct vb2_queue *q, + struct soc_camera_device *icd) +{ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->drv_priv = icd; + q->ops = &mx2_videobuf_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct mx2_buffer); + + return vb2_queue_init(q); +} +#define MX2_BUS_FLAGS (V4L2_MBUS_MASTER | \ + V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_VSYNC_ACTIVE_LOW | \ + V4L2_MBUS_HSYNC_ACTIVE_HIGH | \ + V4L2_MBUS_HSYNC_ACTIVE_LOW | \ + V4L2_MBUS_PCLK_SAMPLE_RISING | \ + V4L2_MBUS_PCLK_SAMPLE_FALLING | \ + V4L2_MBUS_DATA_ACTIVE_HIGH | \ + V4L2_MBUS_DATA_ACTIVE_LOW) + +static int mx27_camera_emma_prp_reset(struct mx2_camera_dev *pcdev) +{ + u32 cntl; + int count = 0; + + cntl = readl(pcdev->base_emma + PRP_CNTL); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + while (count++ < 100) { + if (!(readl(pcdev->base_emma + PRP_CNTL) & PRP_CNTL_SWRST)) + return 0; + barrier(); + udelay(1); } - /* Enable interrupts */ - writel(prp->cfg.irq_flags, pcdev->base_emma + PRP_INTR_CNTL); + return -ETIMEDOUT; } static int mx2_camera_set_bus_param(struct soc_camera_device *icd) @@ -939,31 +1052,10 @@ static int mx2_camera_set_bus_param(struct soc_camera_device *icd) if (bytesperline < 0) return bytesperline; - if (mx27_camera_emma(pcdev)) { + if (cpu_is_mx27()) { ret = mx27_camera_emma_prp_reset(pcdev); if (ret) return ret; - - if (pcdev->discard_buffer) - dma_free_coherent(ici->v4l2_dev.dev, - pcdev->discard_size, pcdev->discard_buffer, - pcdev->discard_buffer_dma); - - /* - * I didn't manage to properly enable/disable the prp - * on a per frame basis during running transfers, - * thus we allocate a buffer here and use it to - * discard frames when no buffer is available. - * Feel free to work on this ;) - */ - pcdev->discard_size = icd->user_height * bytesperline; - pcdev->discard_buffer = dma_alloc_coherent(ici->v4l2_dev.dev, - pcdev->discard_size, &pcdev->discard_buffer_dma, - GFP_KERNEL); - if (!pcdev->discard_buffer) - return -ENOMEM; - - mx27_camera_emma_buf_init(icd, bytesperline); } else if (cpu_is_mx25()) { writel((bytesperline * icd->user_height) >> 2, pcdev->base_csi + CSIRXCNT); @@ -1052,6 +1144,123 @@ static int mx2_camera_get_formats(struct soc_camera_device *icd, return formats; } +static int mx2_emmaprp_resize(struct mx2_camera_dev *pcdev, + struct v4l2_mbus_framefmt *mf_in, + struct v4l2_pix_format *pix_out, bool apply) +{ + int num, den; + unsigned long m; + int i, dir; + + for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) { + struct emma_prp_resize tmprsz; + unsigned char *s = tmprsz.s; + int len = 0; + int in, out; + + if (dir == RESIZE_DIR_H) { + in = mf_in->width; + out = pix_out->width; + } else { + in = mf_in->height; + out = pix_out->height; + } + + if (in < out) + return -EINVAL; + else if (in == out) + continue; + + /* Calculate ratio */ + m = gcd(in, out); + num = in / m; + den = out / m; + if (num > RESIZE_NUM_MAX) + return -EINVAL; + + if ((num >= 2 * den) && (den == 1) && + (num < 9) && (!(num & 0x01))) { + int sum = 0; + int j; + + /* Average scaling for >= 2:1 ratios */ + /* Support can be added for num >=9 and odd values */ + + tmprsz.algo = RESIZE_ALGO_AVERAGING; + len = num; + + for (i = 0; i < (len / 2); i++) + s[i] = 8; + + do { + for (i = 0; i < (len / 2); i++) { + s[i] = s[i] >> 1; + sum = 0; + for (j = 0; j < (len / 2); j++) + sum += s[j]; + if (sum == 4) + break; + } + } while (sum != 4); + + for (i = (len / 2); i < len; i++) + s[i] = s[len - i - 1]; + + s[len - 1] |= SZ_COEF; + } else { + /* bilinear scaling for < 2:1 ratios */ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + int in_pos_inc = 2 * den; + int out_pos = num; + int out_pos_inc = 2 * num; + int init_carry = num - den; + int carry = init_carry; + + tmprsz.algo = RESIZE_ALGO_BILINEAR; + v = den + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + + if (len > RESIZE_NUM_MAX) + return -EINVAL; + + coeff = ((coeff << BC_COEF) + + (in_pos_inc >> 1)) / in_pos_inc; + + if (coeff >= (SZ_COEF - 1)) + coeff--; + + coeff |= SZ_COEF; + s[len] = (unsigned char)coeff; + len++; + + for (i = 1; i < nxt; i++) { + if (len >= RESIZE_NUM_MAX) + return -EINVAL; + s[len] = 0; + len++; + } + } while (carry != init_carry); + } + tmprsz.len = len; + if (dir == RESIZE_DIR_H) + mf_in->width = pix_out->width; + else + mf_in->height = pix_out->height; + + if (apply) + memcpy(&pcdev->resizing[dir], &tmprsz, sizeof(tmprsz)); + } + return 0; +} + static int mx2_camera_set_fmt(struct soc_camera_device *icd, struct v4l2_format *f) { @@ -1063,6 +1272,9 @@ static int mx2_camera_set_fmt(struct soc_camera_device *icd, struct v4l2_mbus_framefmt mf; int ret; + dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n", + __func__, pix->width, pix->height); + xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat); if (!xlate) { dev_warn(icd->parent, "Format %x not found\n", @@ -1080,6 +1292,22 @@ static int mx2_camera_set_fmt(struct soc_camera_device *icd, if (ret < 0 && ret != -ENOIOCTLCMD) return ret; + /* Store width and height returned by the sensor for resizing */ + pcdev->s_width = mf.width; + pcdev->s_height = mf.height; + dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n", + __func__, pcdev->s_width, pcdev->s_height); + + pcdev->emma_prp = mx27_emma_prp_get_format(xlate->code, + xlate->host_fmt->fourcc); + + memset(pcdev->resizing, 0, sizeof(pcdev->resizing)); + if ((mf.width != pix->width || mf.height != pix->height) && + pcdev->emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) { + if (mx2_emmaprp_resize(pcdev, &mf, pix, true) < 0) + dev_dbg(icd->parent, "%s: can't resize\n", __func__); + } + if (mf.code != xlate->code) return -EINVAL; @@ -1089,9 +1317,8 @@ static int mx2_camera_set_fmt(struct soc_camera_device *icd, pix->colorspace = mf.colorspace; icd->current_fmt = xlate; - if (mx27_camera_emma(pcdev)) - pcdev->emma_prp = mx27_emma_prp_get_format(xlate->code, - xlate->host_fmt->fourcc); + dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n", + __func__, pix->width, pix->height); return 0; } @@ -1104,9 +1331,14 @@ static int mx2_camera_try_fmt(struct soc_camera_device *icd, struct v4l2_pix_format *pix = &f->fmt.pix; struct v4l2_mbus_framefmt mf; __u32 pixfmt = pix->pixelformat; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct mx2_camera_dev *pcdev = ici->priv; unsigned int width_limit; int ret; + dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n", + __func__, pix->width, pix->height); + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); if (pixfmt && !xlate) { dev_warn(icd->parent, "Format %x not found\n", pixfmt); @@ -1156,6 +1388,20 @@ static int mx2_camera_try_fmt(struct soc_camera_device *icd, if (ret < 0) return ret; + dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n", + __func__, pcdev->s_width, pcdev->s_height); + + /* If the sensor does not support image size try PrP resizing */ + pcdev->emma_prp = mx27_emma_prp_get_format(xlate->code, + xlate->host_fmt->fourcc); + + memset(pcdev->resizing, 0, sizeof(pcdev->resizing)); + if ((mf.width != pix->width || mf.height != pix->height) && + pcdev->emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) { + if (mx2_emmaprp_resize(pcdev, &mf, pix, false) < 0) + dev_dbg(icd->parent, "%s: can't resize\n", __func__); + } + if (mf.field == V4L2_FIELD_ANY) mf.field = V4L2_FIELD_NONE; /* @@ -1174,6 +1420,9 @@ static int mx2_camera_try_fmt(struct soc_camera_device *icd, pix->field = mf.field; pix->colorspace = mf.colorspace; + dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n", + __func__, pix->width, pix->height); + return 0; } @@ -1187,136 +1436,11 @@ static int mx2_camera_querycap(struct soc_camera_host *ici, return 0; } -static int mx2_camera_reqbufs(struct soc_camera_device *icd, - struct v4l2_requestbuffers *p) -{ - int i; - - for (i = 0; i < p->count; i++) { - struct mx2_buffer *buf = container_of(icd->vb_vidq.bufs[i], - struct mx2_buffer, vb); - INIT_LIST_HEAD(&buf->vb.queue); - } - - return 0; -} - -#ifdef CONFIG_MACH_MX27 -static void mx27_camera_frame_done(struct mx2_camera_dev *pcdev, int state) -{ - struct videobuf_buffer *vb; - struct mx2_buffer *buf; - unsigned long flags; - int ret; - - spin_lock_irqsave(&pcdev->lock, flags); - - if (!pcdev->active) { - dev_err(pcdev->dev, "%s called with no active buffer!\n", - __func__); - goto out; - } - - vb = &pcdev->active->vb; - buf = container_of(vb, struct mx2_buffer, vb); - WARN_ON(list_empty(&vb->queue)); - dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, - vb, vb->baddr, vb->bsize); - - /* _init is used to debug races, see comment in pxa_camera_reqbufs() */ - list_del_init(&vb->queue); - vb->state = state; - do_gettimeofday(&vb->ts); - vb->field_count++; - - wake_up(&vb->done); - - if (list_empty(&pcdev->capture)) { - pcdev->active = NULL; - goto out; - } - - pcdev->active = list_entry(pcdev->capture.next, - struct mx2_buffer, vb.queue); - - vb = &pcdev->active->vb; - vb->state = VIDEOBUF_ACTIVE; - - ret = imx_dma_setup_single(pcdev->dma, videobuf_to_dma_contig(vb), - vb->size, (u32)pcdev->base_dma + 0x10, DMA_MODE_READ); - - if (ret) { - vb->state = VIDEOBUF_ERROR; - pcdev->active = NULL; - wake_up(&vb->done); - } - -out: - spin_unlock_irqrestore(&pcdev->lock, flags); -} - -static void mx27_camera_dma_err_callback(int channel, void *data, int err) -{ - struct mx2_camera_dev *pcdev = data; - - mx27_camera_frame_done(pcdev, VIDEOBUF_ERROR); -} - -static void mx27_camera_dma_callback(int channel, void *data) -{ - struct mx2_camera_dev *pcdev = data; - - mx27_camera_frame_done(pcdev, VIDEOBUF_DONE); -} - -#define DMA_REQ_CSI_RX 31 /* FIXME: Add this to a resource */ - -static int __devinit mx27_camera_dma_init(struct platform_device *pdev, - struct mx2_camera_dev *pcdev) -{ - int err; - - pcdev->dma = imx_dma_request_by_prio("CSI RX DMA", DMA_PRIO_HIGH); - if (pcdev->dma < 0) { - dev_err(&pdev->dev, "%s failed to request DMA channel\n", - __func__); - return pcdev->dma; - } - - err = imx_dma_setup_handlers(pcdev->dma, mx27_camera_dma_callback, - mx27_camera_dma_err_callback, pcdev); - if (err) { - dev_err(&pdev->dev, "%s failed to set DMA callback\n", - __func__); - goto err_out; - } - - err = imx_dma_config_channel(pcdev->dma, - IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_FIFO, - IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, - DMA_REQ_CSI_RX, 1); - if (err) { - dev_err(&pdev->dev, "%s failed to config DMA channel\n", - __func__); - goto err_out; - } - - imx_dma_config_burstlen(pcdev->dma, 64); - - return 0; - -err_out: - imx_dma_free(pcdev->dma); - - return err; -} -#endif /* CONFIG_MACH_MX27 */ - static unsigned int mx2_camera_poll(struct file *file, poll_table *pt) { struct soc_camera_device *icd = file->private_data; - return videobuf_poll_stream(file, &icd->vb_vidq, pt); + return vb2_poll(&icd->vb2_vidq, file, pt); } static struct soc_camera_host_ops mx2_soc_camera_host_ops = { @@ -1327,144 +1451,148 @@ static struct soc_camera_host_ops mx2_soc_camera_host_ops = { .set_crop = mx2_camera_set_crop, .get_formats = mx2_camera_get_formats, .try_fmt = mx2_camera_try_fmt, - .init_videobuf = mx2_camera_init_videobuf, - .reqbufs = mx2_camera_reqbufs, + .init_videobuf2 = mx2_camera_init_videobuf, .poll = mx2_camera_poll, .querycap = mx2_camera_querycap, .set_bus_param = mx2_camera_set_bus_param, }; static void mx27_camera_frame_done_emma(struct mx2_camera_dev *pcdev, - int bufnum, int state) + int bufnum, bool err) { - u32 imgsize = pcdev->icd->user_height * pcdev->icd->user_width; +#ifdef DEBUG struct mx2_fmt_cfg *prp = pcdev->emma_prp; +#endif + struct mx2_buf_internal *ibuf; struct mx2_buffer *buf; - struct videobuf_buffer *vb; + struct vb2_buffer *vb; unsigned long phys; - if (!list_empty(&pcdev->active_bufs)) { - buf = list_entry(pcdev->active_bufs.next, - struct mx2_buffer, vb.queue); + ibuf = list_first_entry(&pcdev->active_bufs, struct mx2_buf_internal, + queue); + + BUG_ON(ibuf->bufnum != bufnum); - BUG_ON(buf->bufnum != bufnum); + if (ibuf->discard) { + /* + * Discard buffer must not be returned to user space. + * Just return it to the discard queue. + */ + list_move_tail(pcdev->active_bufs.next, &pcdev->discard); + } else { + buf = mx2_ibuf_to_buf(ibuf); vb = &buf->vb; #ifdef DEBUG - phys = videobuf_to_dma_contig(vb); + phys = vb2_dma_contig_plane_dma_addr(vb, 0); if (prp->cfg.channel == 1) { if (readl(pcdev->base_emma + PRP_DEST_RGB1_PTR + 4 * bufnum) != phys) { - dev_err(pcdev->dev, "%p != %p\n", phys, - readl(pcdev->base_emma + - PRP_DEST_RGB1_PTR + - 4 * bufnum)); + dev_err(pcdev->dev, "%lx != %x\n", phys, + readl(pcdev->base_emma + + PRP_DEST_RGB1_PTR + 4 * bufnum)); } } else { if (readl(pcdev->base_emma + PRP_DEST_Y_PTR - 0x14 * bufnum) != phys) { - dev_err(pcdev->dev, "%p != %p\n", phys, - readl(pcdev->base_emma + - PRP_DEST_Y_PTR - - 0x14 * bufnum)); + dev_err(pcdev->dev, "%lx != %x\n", phys, + readl(pcdev->base_emma + + PRP_DEST_Y_PTR - 0x14 * bufnum)); } } #endif - dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, vb, - vb->baddr, vb->bsize); + dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__, vb, + vb2_plane_vaddr(vb, 0), + vb2_get_plane_payload(vb, 0)); - list_del(&vb->queue); - vb->state = state; - do_gettimeofday(&vb->ts); - vb->field_count = pcdev->frame_count * 2; - pcdev->frame_count++; - - wake_up(&vb->done); + list_del_init(&buf->internal.queue); + do_gettimeofday(&vb->v4l2_buf.timestamp); + vb->v4l2_buf.sequence = pcdev->frame_count; + if (err) + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + else + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); } + pcdev->frame_count++; + if (list_empty(&pcdev->capture)) { - if (prp->cfg.channel == 1) { - writel(pcdev->discard_buffer_dma, pcdev->base_emma + - PRP_DEST_RGB1_PTR + 4 * bufnum); - } else { - writel(pcdev->discard_buffer_dma, pcdev->base_emma + - PRP_DEST_Y_PTR - - 0x14 * bufnum); - if (prp->out_fmt == V4L2_PIX_FMT_YUV420) { - writel(pcdev->discard_buffer_dma + imgsize, - pcdev->base_emma + PRP_DEST_CB_PTR - - 0x14 * bufnum); - writel(pcdev->discard_buffer_dma + - ((5 * imgsize) / 4), pcdev->base_emma + - PRP_DEST_CR_PTR - 0x14 * bufnum); - } + if (list_empty(&pcdev->discard)) { + dev_warn(pcdev->dev, "%s: trying to access empty discard list\n", + __func__); + return; } + + ibuf = list_first_entry(&pcdev->discard, + struct mx2_buf_internal, queue); + ibuf->bufnum = bufnum; + + list_move_tail(pcdev->discard.next, &pcdev->active_bufs); + mx27_update_emma_buf(pcdev, pcdev->discard_buffer_dma, bufnum); return; } - buf = list_entry(pcdev->capture.next, - struct mx2_buffer, vb.queue); + buf = list_first_entry(&pcdev->capture, struct mx2_buffer, + internal.queue); - buf->bufnum = !bufnum; + buf->internal.bufnum = bufnum; list_move_tail(pcdev->capture.next, &pcdev->active_bufs); vb = &buf->vb; - vb->state = VIDEOBUF_ACTIVE; + buf->state = MX2_STATE_ACTIVE; - phys = videobuf_to_dma_contig(vb); - if (prp->cfg.channel == 1) { - writel(phys, pcdev->base_emma + PRP_DEST_RGB1_PTR + 4 * bufnum); - } else { - writel(phys, pcdev->base_emma + - PRP_DEST_Y_PTR - 0x14 * bufnum); - if (prp->cfg.out_fmt == PRP_CNTL_CH2_OUT_YUV420) { - writel(phys + imgsize, pcdev->base_emma + - PRP_DEST_CB_PTR - 0x14 * bufnum); - writel(phys + ((5 * imgsize) / 4), pcdev->base_emma + - PRP_DEST_CR_PTR - 0x14 * bufnum); - } - } + phys = vb2_dma_contig_plane_dma_addr(vb, 0); + mx27_update_emma_buf(pcdev, phys, bufnum); } static irqreturn_t mx27_camera_emma_irq(int irq_emma, void *data) { struct mx2_camera_dev *pcdev = data; unsigned int status = readl(pcdev->base_emma + PRP_INTRSTATUS); - struct mx2_buffer *buf; + struct mx2_buf_internal *ibuf; + + spin_lock(&pcdev->lock); + + if (list_empty(&pcdev->active_bufs)) { + dev_warn(pcdev->dev, "%s: called while active list is empty\n", + __func__); + + if (!status) { + spin_unlock(&pcdev->lock); + return IRQ_NONE; + } + } if (status & (1 << 7)) { /* overflow */ - u32 cntl; - /* - * We only disable channel 1 here since this is the only - * enabled channel - * - * FIXME: the correct DMA overflow handling should be resetting - * the buffer, returning an error frame, and continuing with - * the next one. - */ - cntl = readl(pcdev->base_emma + PRP_CNTL); + u32 cntl = readl(pcdev->base_emma + PRP_CNTL); writel(cntl & ~(PRP_CNTL_CH1EN | PRP_CNTL_CH2EN), pcdev->base_emma + PRP_CNTL); writel(cntl, pcdev->base_emma + PRP_CNTL); - } - if ((((status & (3 << 5)) == (3 << 5)) || - ((status & (3 << 3)) == (3 << 3))) - && !list_empty(&pcdev->active_bufs)) { + + ibuf = list_first_entry(&pcdev->active_bufs, + struct mx2_buf_internal, queue); + mx27_camera_frame_done_emma(pcdev, + ibuf->bufnum, true); + + status &= ~(1 << 7); + } else if (((status & (3 << 5)) == (3 << 5)) || + ((status & (3 << 3)) == (3 << 3))) { /* * Both buffers have triggered, process the one we're expecting * to first */ - buf = list_entry(pcdev->active_bufs.next, - struct mx2_buffer, vb.queue); - mx27_camera_frame_done_emma(pcdev, buf->bufnum, VIDEOBUF_DONE); - status &= ~(1 << (6 - buf->bufnum)); /* mark processed */ + ibuf = list_first_entry(&pcdev->active_bufs, + struct mx2_buf_internal, queue); + mx27_camera_frame_done_emma(pcdev, ibuf->bufnum, false); + status &= ~(1 << (6 - ibuf->bufnum)); /* mark processed */ + } else if ((status & (1 << 6)) || (status & (1 << 4))) { + mx27_camera_frame_done_emma(pcdev, 0, false); + } else if ((status & (1 << 5)) || (status & (1 << 3))) { + mx27_camera_frame_done_emma(pcdev, 1, false); } - if ((status & (1 << 6)) || (status & (1 << 4))) - mx27_camera_frame_done_emma(pcdev, 0, VIDEOBUF_DONE); - if ((status & (1 << 5)) || (status & (1 << 3))) - mx27_camera_frame_done_emma(pcdev, 1, VIDEOBUF_DONE); + spin_unlock(&pcdev->lock); writel(status, pcdev->base_emma + PRP_INTRSTATUS); return IRQ_HANDLED; @@ -1527,8 +1655,6 @@ static int __devinit mx2_camera_probe(struct platform_device *pdev) struct resource *res_csi, *res_emma; void __iomem *base_csi; int irq_csi, irq_emma; - irq_handler_t mx2_cam_irq_handler = cpu_is_mx25() ? mx25_camera_irq - : mx27_camera_irq; int err = 0; dev_dbg(&pdev->dev, "initialising\n"); @@ -1550,22 +1676,11 @@ static int __devinit mx2_camera_probe(struct platform_device *pdev) pcdev->clk_csi = clk_get(&pdev->dev, NULL); if (IS_ERR(pcdev->clk_csi)) { + dev_err(&pdev->dev, "Could not get csi clock\n"); err = PTR_ERR(pcdev->clk_csi); goto exit_kfree; } - dev_dbg(&pdev->dev, "Camera clock frequency: %ld\n", - clk_get_rate(pcdev->clk_csi)); - - /* Initialize DMA */ -#ifdef CONFIG_MACH_MX27 - if (cpu_is_mx27()) { - err = mx27_camera_dma_init(pdev, pcdev); - if (err) - goto exit_clk_put; - } -#endif /* CONFIG_MACH_MX27 */ - pcdev->res_csi = res_csi; pcdev->pdata = pdev->dev.platform_data; if (pcdev->pdata) { @@ -1585,6 +1700,7 @@ static int __devinit mx2_camera_probe(struct platform_device *pdev) INIT_LIST_HEAD(&pcdev->capture); INIT_LIST_HEAD(&pcdev->active_bufs); + INIT_LIST_HEAD(&pcdev->discard); spin_lock_init(&pcdev->lock); /* @@ -1606,11 +1722,13 @@ static int __devinit mx2_camera_probe(struct platform_device *pdev) pcdev->base_dma = res_csi->start; pcdev->dev = &pdev->dev; - err = request_irq(pcdev->irq_csi, mx2_cam_irq_handler, 0, - MX2_CAM_DRV_NAME, pcdev); - if (err) { - dev_err(pcdev->dev, "Camera interrupt register failed \n"); - goto exit_iounmap; + if (cpu_is_mx25()) { + err = request_irq(pcdev->irq_csi, mx25_camera_irq, 0, + MX2_CAM_DRV_NAME, pcdev); + if (err) { + dev_err(pcdev->dev, "Camera interrupt register failed \n"); + goto exit_iounmap; + } } if (cpu_is_mx27()) { @@ -1618,14 +1736,15 @@ static int __devinit mx2_camera_probe(struct platform_device *pdev) res_emma = platform_get_resource(pdev, IORESOURCE_MEM, 1); irq_emma = platform_get_irq(pdev, 1); - if (res_emma && irq_emma >= 0) { - dev_info(&pdev->dev, "Using EMMA\n"); - pcdev->use_emma = 1; - pcdev->res_emma = res_emma; - pcdev->irq_emma = irq_emma; - if (mx27_camera_emma_init(pcdev)) - goto exit_free_irq; + if (!res_emma || !irq_emma) { + dev_err(&pdev->dev, "no EMMA resources\n"); + goto exit_free_irq; } + + pcdev->res_emma = res_emma; + pcdev->irq_emma = irq_emma; + if (mx27_camera_emma_init(pcdev)) + goto exit_free_irq; } pcdev->soc_host.drv_name = MX2_CAM_DRV_NAME, @@ -1633,6 +1752,12 @@ static int __devinit mx2_camera_probe(struct platform_device *pdev) pcdev->soc_host.priv = pcdev; pcdev->soc_host.v4l2_dev.dev = &pdev->dev; pcdev->soc_host.nr = pdev->id; + + pcdev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(pcdev->alloc_ctx)) { + err = PTR_ERR(pcdev->alloc_ctx); + goto eallocctx; + } err = soc_camera_host_register(&pcdev->soc_host); if (err) goto exit_free_emma; @@ -1643,26 +1768,24 @@ static int __devinit mx2_camera_probe(struct platform_device *pdev) return 0; exit_free_emma: - if (mx27_camera_emma(pcdev)) { + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); +eallocctx: + if (cpu_is_mx27()) { free_irq(pcdev->irq_emma, pcdev); clk_disable(pcdev->clk_emma); clk_put(pcdev->clk_emma); iounmap(pcdev->base_emma); - release_mem_region(res_emma->start, resource_size(res_emma)); + release_mem_region(pcdev->res_emma->start, resource_size(pcdev->res_emma)); } exit_free_irq: - free_irq(pcdev->irq_csi, pcdev); + if (cpu_is_mx25()) + free_irq(pcdev->irq_csi, pcdev); exit_iounmap: iounmap(base_csi); exit_release: release_mem_region(res_csi->start, resource_size(res_csi)); exit_dma_free: -#ifdef CONFIG_MACH_MX27 - if (cpu_is_mx27()) - imx_dma_free(pcdev->dma); -exit_clk_put: clk_put(pcdev->clk_csi); -#endif /* CONFIG_MACH_MX27 */ exit_kfree: kfree(pcdev); exit: @@ -1677,19 +1800,18 @@ static int __devexit mx2_camera_remove(struct platform_device *pdev) struct resource *res; clk_put(pcdev->clk_csi); -#ifdef CONFIG_MACH_MX27 + if (cpu_is_mx25()) + free_irq(pcdev->irq_csi, pcdev); if (cpu_is_mx27()) - imx_dma_free(pcdev->dma); -#endif /* CONFIG_MACH_MX27 */ - free_irq(pcdev->irq_csi, pcdev); - if (mx27_camera_emma(pcdev)) free_irq(pcdev->irq_emma, pcdev); soc_camera_host_unregister(&pcdev->soc_host); + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); + iounmap(pcdev->base_csi); - if (mx27_camera_emma(pcdev)) { + if (cpu_is_mx27()) { clk_disable(pcdev->clk_emma); clk_put(pcdev->clk_emma); iounmap(pcdev->base_emma); diff --git a/drivers/media/video/mx2_emmaprp.c b/drivers/media/video/mx2_emmaprp.c new file mode 100644 index 000000000000..ba89a7401c8c --- /dev/null +++ b/drivers/media/video/mx2_emmaprp.c @@ -0,0 +1,1008 @@ +/* + * Support eMMa-PrP through mem2mem framework. + * + * eMMa-PrP is a piece of HW that allows fetching buffers + * from one memory location and do several operations on + * them such as scaling or format conversion giving, as a result + * a new processed buffer in another memory location. + * + * Based on mem2mem_testdev.c by Pawel Osciak. + * + * Copyright (c) 2011 Vista Silicon S.L. + * Javier Martin <javier.martin@vista-silicon.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/module.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <linux/platform_device.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <asm/sizes.h> + +#define EMMAPRP_MODULE_NAME "mem2mem-emmaprp" + +MODULE_DESCRIPTION("Mem-to-mem device which supports eMMa-PrP present in mx2 SoCs"); +MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.0.1"); + +static bool debug; +module_param(debug, bool, 0644); + +#define MIN_W 32 +#define MIN_H 32 +#define MAX_W 2040 +#define MAX_H 2046 + +#define S_ALIGN 1 /* multiple of 2 */ +#define W_ALIGN_YUV420 3 /* multiple of 8 */ +#define W_ALIGN_OTHERS 2 /* multiple of 4 */ +#define H_ALIGN 1 /* multiple of 2 */ + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE (1 << 0) +#define MEM2MEM_OUTPUT (1 << 1) + +#define MEM2MEM_NAME "m2m-emmaprp" + +/* In bytes, per queue */ +#define MEM2MEM_VID_MEM_LIMIT SZ_16M + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* EMMA PrP */ +#define PRP_CNTL 0x00 +#define PRP_INTR_CNTL 0x04 +#define PRP_INTRSTATUS 0x08 +#define PRP_SOURCE_Y_PTR 0x0c +#define PRP_SOURCE_CB_PTR 0x10 +#define PRP_SOURCE_CR_PTR 0x14 +#define PRP_DEST_RGB1_PTR 0x18 +#define PRP_DEST_RGB2_PTR 0x1c +#define PRP_DEST_Y_PTR 0x20 +#define PRP_DEST_CB_PTR 0x24 +#define PRP_DEST_CR_PTR 0x28 +#define PRP_SRC_FRAME_SIZE 0x2c +#define PRP_DEST_CH1_LINE_STRIDE 0x30 +#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 +#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 +#define PRP_CH1_OUT_IMAGE_SIZE 0x3c +#define PRP_CH2_OUT_IMAGE_SIZE 0x40 +#define PRP_SRC_LINE_STRIDE 0x44 +#define PRP_CSC_COEF_012 0x48 +#define PRP_CSC_COEF_345 0x4c +#define PRP_CSC_COEF_678 0x50 +#define PRP_CH1_RZ_HORI_COEF1 0x54 +#define PRP_CH1_RZ_HORI_COEF2 0x58 +#define PRP_CH1_RZ_HORI_VALID 0x5c +#define PRP_CH1_RZ_VERT_COEF1 0x60 +#define PRP_CH1_RZ_VERT_COEF2 0x64 +#define PRP_CH1_RZ_VERT_VALID 0x68 +#define PRP_CH2_RZ_HORI_COEF1 0x6c +#define PRP_CH2_RZ_HORI_COEF2 0x70 +#define PRP_CH2_RZ_HORI_VALID 0x74 +#define PRP_CH2_RZ_VERT_COEF1 0x78 +#define PRP_CH2_RZ_VERT_COEF2 0x7c +#define PRP_CH2_RZ_VERT_VALID 0x80 + +#define PRP_CNTL_CH1EN (1 << 0) +#define PRP_CNTL_CH2EN (1 << 1) +#define PRP_CNTL_CSIEN (1 << 2) +#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) +#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) +#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) +#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) +#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) +#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) +#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) +#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) +#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) +#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) +#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) +#define PRP_CNTL_CH1_LEN (1 << 9) +#define PRP_CNTL_CH2_LEN (1 << 10) +#define PRP_CNTL_SKIP_FRAME (1 << 11) +#define PRP_CNTL_SWRST (1 << 12) +#define PRP_CNTL_CLKEN (1 << 13) +#define PRP_CNTL_WEN (1 << 14) +#define PRP_CNTL_CH1BYP (1 << 15) +#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) +#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) +#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) +#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) +#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) +#define PRP_CNTL_CH2B1EN (1 << 29) +#define PRP_CNTL_CH2B2EN (1 << 30) +#define PRP_CNTL_CH2FEN (1 << 31) + +#define PRP_SIZE_HEIGHT(x) (x) +#define PRP_SIZE_WIDTH(x) ((x) << 16) + +/* IRQ Enable and status register */ +#define PRP_INTR_RDERR (1 << 0) +#define PRP_INTR_CH1WERR (1 << 1) +#define PRP_INTR_CH2WERR (1 << 2) +#define PRP_INTR_CH1FC (1 << 3) +#define PRP_INTR_CH2FC (1 << 5) +#define PRP_INTR_LBOVF (1 << 7) +#define PRP_INTR_CH2OVF (1 << 8) + +#define PRP_INTR_ST_RDERR (1 << 0) +#define PRP_INTR_ST_CH1WERR (1 << 1) +#define PRP_INTR_ST_CH2WERR (1 << 2) +#define PRP_INTR_ST_CH2B2CI (1 << 3) +#define PRP_INTR_ST_CH2B1CI (1 << 4) +#define PRP_INTR_ST_CH1B2CI (1 << 5) +#define PRP_INTR_ST_CH1B1CI (1 << 6) +#define PRP_INTR_ST_LBOVF (1 << 7) +#define PRP_INTR_ST_CH2OVF (1 << 8) + +struct emmaprp_fmt { + char *name; + u32 fourcc; + /* Types the format can be used for */ + u32 types; +}; + +static struct emmaprp_fmt formats[] = { + { + .name = "YUV 4:2:0 Planar", + .fourcc = V4L2_PIX_FMT_YUV420, + .types = MEM2MEM_CAPTURE, + }, + { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .types = MEM2MEM_OUTPUT, + }, +}; + +/* Per-queue, driver-specific private data */ +struct emmaprp_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct emmaprp_fmt *fmt; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static struct emmaprp_fmt *find_format(struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct emmaprp_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + + struct mutex dev_mutex; + spinlock_t irqlock; + + int irq_emma; + void __iomem *base_emma; + struct clk *clk_emma; + struct resource *res_emma; + + struct v4l2_m2m_dev *m2m_dev; + struct vb2_alloc_ctx *alloc_ctx; +}; + +struct emmaprp_ctx { + struct emmaprp_dev *dev; + /* Abort requested by m2m */ + int aborting; + struct emmaprp_q_data q_data[2]; + struct v4l2_m2m_ctx *m2m_ctx; +}; + +static struct emmaprp_q_data *get_q_data(struct emmaprp_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &(ctx->q_data[V4L2_M2M_SRC]); + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &(ctx->q_data[V4L2_M2M_DST]); + default: + BUG(); + } + return NULL; +} + +/* + * mem2mem callbacks + */ +static void emmaprp_job_abort(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + + ctx->aborting = 1; + + dprintk(pcdev, "Aborting task\n"); + + v4l2_m2m_job_finish(pcdev->m2m_dev, ctx->m2m_ctx); +} + +static void emmaprp_lock(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + mutex_lock(&pcdev->dev_mutex); +} + +static void emmaprp_unlock(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + mutex_unlock(&pcdev->dev_mutex); +} + +static inline void emmaprp_dump_regs(struct emmaprp_dev *pcdev) +{ + dprintk(pcdev, + "eMMa-PrP Registers:\n" + " SOURCE_Y_PTR = 0x%08X\n" + " SRC_FRAME_SIZE = 0x%08X\n" + " DEST_Y_PTR = 0x%08X\n" + " DEST_CR_PTR = 0x%08X\n" + " DEST_CB_PTR = 0x%08X\n" + " CH2_OUT_IMAGE_SIZE = 0x%08X\n" + " CNTL = 0x%08X\n", + readl(pcdev->base_emma + PRP_SOURCE_Y_PTR), + readl(pcdev->base_emma + PRP_SRC_FRAME_SIZE), + readl(pcdev->base_emma + PRP_DEST_Y_PTR), + readl(pcdev->base_emma + PRP_DEST_CR_PTR), + readl(pcdev->base_emma + PRP_DEST_CB_PTR), + readl(pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE), + readl(pcdev->base_emma + PRP_CNTL)); +} + +static void emmaprp_device_run(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_q_data *s_q_data, *d_q_data; + struct vb2_buffer *src_buf, *dst_buf; + struct emmaprp_dev *pcdev = ctx->dev; + unsigned int s_width, s_height; + unsigned int d_width, d_height; + unsigned int d_size; + dma_addr_t p_in, p_out; + u32 tmp; + + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + + s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + s_width = s_q_data->width; + s_height = s_q_data->height; + + d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + d_width = d_q_data->width; + d_height = d_q_data->height; + d_size = d_width * d_height; + + p_in = vb2_dma_contig_plane_dma_addr(src_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + if (!p_in || !p_out) { + v4l2_err(&pcdev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return; + } + + /* Input frame parameters */ + writel(p_in, pcdev->base_emma + PRP_SOURCE_Y_PTR); + writel(PRP_SIZE_WIDTH(s_width) | PRP_SIZE_HEIGHT(s_height), + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + + /* Output frame parameters */ + writel(p_out, pcdev->base_emma + PRP_DEST_Y_PTR); + writel(p_out + d_size, pcdev->base_emma + PRP_DEST_CB_PTR); + writel(p_out + d_size + (d_size >> 2), + pcdev->base_emma + PRP_DEST_CR_PTR); + writel(PRP_SIZE_WIDTH(d_width) | PRP_SIZE_HEIGHT(d_height), + pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + + /* IRQ configuration */ + tmp = readl(pcdev->base_emma + PRP_INTR_CNTL); + writel(tmp | PRP_INTR_RDERR | + PRP_INTR_CH2WERR | + PRP_INTR_CH2FC, + pcdev->base_emma + PRP_INTR_CNTL); + + emmaprp_dump_regs(pcdev); + + /* Enable transfer */ + tmp = readl(pcdev->base_emma + PRP_CNTL); + writel(tmp | PRP_CNTL_CH2_OUT_YUV420 | + PRP_CNTL_DATA_IN_YUV422 | + PRP_CNTL_CH2EN, + pcdev->base_emma + PRP_CNTL); +} + +static irqreturn_t emmaprp_irq(int irq_emma, void *data) +{ + struct emmaprp_dev *pcdev = data; + struct emmaprp_ctx *curr_ctx; + struct vb2_buffer *src_vb, *dst_vb; + unsigned long flags; + u32 irqst; + + /* Check irq flags and clear irq */ + irqst = readl(pcdev->base_emma + PRP_INTRSTATUS); + writel(irqst, pcdev->base_emma + PRP_INTRSTATUS); + dprintk(pcdev, "irqst = 0x%08x\n", irqst); + + curr_ctx = v4l2_m2m_get_curr_priv(pcdev->m2m_dev); + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + if (!curr_ctx->aborting) { + if ((irqst & PRP_INTR_ST_RDERR) || + (irqst & PRP_INTR_ST_CH2WERR)) { + pr_err("PrP bus error ocurred, this transfer is probably corrupted\n"); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + } else if (irqst & PRP_INTR_ST_CH2B1CI) { /* buffer ready */ + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + + spin_lock_irqsave(&pcdev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&pcdev->irqlock, flags); + } + } + + v4l2_m2m_job_finish(pcdev->m2m_dev, curr_ctx->m2m_ctx); + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1); + strncpy(cap->card, MEM2MEM_NAME, sizeof(cap->card) - 1); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT + | V4L2_CAP_STREAMING; + + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct emmaprp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + strlcpy(f->description, fmt->name, sizeof(f->description) - 1); + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct emmaprp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + f->fmt.pix.bytesperline = q_data->width * 3 / 2; + else /* YUYV */ + f->fmt.pix.bytesperline = q_data->width * 2; + f->fmt.pix.sizeimage = q_data->sizeimage; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_try_fmt(struct v4l2_format *f) +{ + enum v4l2_field field; + + + if (!find_format(f)) + return -EINVAL; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_YUV420, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + } else { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_OTHERS, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + } + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_s_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct emmaprp_q_data *q_data; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = vidioc_try_fmt(f); + if (ret) + return ret; + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + q_data->sizeimage = q_data->width * q_data->height * 3 / 2; + else /* YUYV */ + q_data->sizeimage = q_data->width * q_data->height * 2; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct emmaprp_ctx *ctx = priv; + + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops emmaprp_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +}; + + +/* + * Queue operations + */ +static int emmaprp_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vq); + struct emmaprp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + size = q_data->width * q_data->height * 3 / 2; + else + size = q_data->width * q_data->height * 2; + + while (size * count > MEM2MEM_VID_MEM_LIMIT) + (count)--; + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + alloc_ctxs[0] = ctx->dev->alloc_ctx; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int emmaprp_buf_prepare(struct vb2_buffer *vb) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct emmaprp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, "%s data will not fit into plane" + "(%lu < %lu)\n", __func__, + vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void emmaprp_buf_queue(struct vb2_buffer *vb) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); +} + +static struct vb2_ops emmaprp_qops = { + .queue_setup = emmaprp_queue_setup, + .buf_prepare = emmaprp_buf_prepare, + .buf_queue = emmaprp_buf_queue, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct emmaprp_ctx *ctx = priv; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &emmaprp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &emmaprp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int emmaprp_open(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx; + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + file->private_data = ctx; + ctx->dev = pcdev; + + ctx->m2m_ctx = v4l2_m2m_ctx_init(pcdev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->m2m_ctx)) { + int ret = PTR_ERR(ctx->m2m_ctx); + + kfree(ctx); + return ret; + } + + clk_enable(pcdev->clk_emma); + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[1]; + ctx->q_data[V4L2_M2M_DST].fmt = &formats[0]; + + dprintk(pcdev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->m2m_ctx); + + return 0; +} + +static int emmaprp_release(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx = file->private_data; + + dprintk(pcdev, "Releasing instance %p\n", ctx); + + clk_disable(pcdev->clk_emma); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + kfree(ctx); + + return 0; +} + +static unsigned int emmaprp_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct emmaprp_ctx *ctx = file->private_data; + + return v4l2_m2m_poll(file, ctx->m2m_ctx, wait); +} + +static int emmaprp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct emmaprp_ctx *ctx = file->private_data; + + return v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); +} + +static const struct v4l2_file_operations emmaprp_fops = { + .owner = THIS_MODULE, + .open = emmaprp_open, + .release = emmaprp_release, + .poll = emmaprp_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = emmaprp_mmap, +}; + +static struct video_device emmaprp_videodev = { + .name = MEM2MEM_NAME, + .fops = &emmaprp_fops, + .ioctl_ops = &emmaprp_ioctl_ops, + .minor = -1, + .release = video_device_release, +}; + +static struct v4l2_m2m_ops m2m_ops = { + .device_run = emmaprp_device_run, + .job_abort = emmaprp_job_abort, + .lock = emmaprp_lock, + .unlock = emmaprp_unlock, +}; + +static int emmaprp_probe(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev; + struct video_device *vfd; + struct resource *res_emma; + int irq_emma; + int ret; + + pcdev = kzalloc(sizeof *pcdev, GFP_KERNEL); + if (!pcdev) + return -ENOMEM; + + spin_lock_init(&pcdev->irqlock); + + pcdev->clk_emma = clk_get(&pdev->dev, NULL); + if (IS_ERR(pcdev->clk_emma)) { + ret = PTR_ERR(pcdev->clk_emma); + goto free_dev; + } + + irq_emma = platform_get_irq(pdev, 0); + res_emma = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (irq_emma < 0 || res_emma == NULL) { + dev_err(&pdev->dev, "Missing platform resources data\n"); + ret = -ENODEV; + goto free_clk; + } + + ret = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (ret) + goto free_clk; + + mutex_init(&pcdev->dev_mutex); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&pcdev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = emmaprp_videodev; + vfd->lock = &pcdev->dev_mutex; + + video_set_drvdata(vfd, pcdev); + snprintf(vfd->name, sizeof(vfd->name), "%s", emmaprp_videodev.name); + pcdev->vfd = vfd; + v4l2_info(&pcdev->v4l2_dev, EMMAPRP_MODULE_NAME + " Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, pcdev); + + if (devm_request_mem_region(&pdev->dev, res_emma->start, + resource_size(res_emma), MEM2MEM_NAME) == NULL) + goto rel_vdev; + + pcdev->base_emma = devm_ioremap(&pdev->dev, res_emma->start, + resource_size(res_emma)); + if (!pcdev->base_emma) + goto rel_vdev; + + pcdev->irq_emma = irq_emma; + pcdev->res_emma = res_emma; + + if (devm_request_irq(&pdev->dev, pcdev->irq_emma, emmaprp_irq, + 0, MEM2MEM_NAME, pcdev) < 0) + goto rel_vdev; + + pcdev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(pcdev->alloc_ctx)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to alloc vb2 context\n"); + ret = PTR_ERR(pcdev->alloc_ctx); + goto rel_vdev; + } + + pcdev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(pcdev->m2m_dev)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(pcdev->m2m_dev); + goto rel_ctx; + } + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&pcdev->v4l2_dev, "Failed to register video device\n"); + goto rel_m2m; + } + + return 0; + + +rel_m2m: + v4l2_m2m_release(pcdev->m2m_dev); +rel_ctx: + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); +rel_vdev: + video_device_release(vfd); +unreg_dev: + v4l2_device_unregister(&pcdev->v4l2_dev); +free_clk: + clk_put(pcdev->clk_emma); +free_dev: + kfree(pcdev); + + return ret; +} + +static int emmaprp_remove(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev = platform_get_drvdata(pdev); + + v4l2_info(&pcdev->v4l2_dev, "Removing " EMMAPRP_MODULE_NAME); + + video_unregister_device(pcdev->vfd); + v4l2_m2m_release(pcdev->m2m_dev); + vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx); + v4l2_device_unregister(&pcdev->v4l2_dev); + clk_put(pcdev->clk_emma); + kfree(pcdev); + + return 0; +} + +static struct platform_driver emmaprp_pdrv = { + .probe = emmaprp_probe, + .remove = emmaprp_remove, + .driver = { + .name = MEM2MEM_NAME, + .owner = THIS_MODULE, + }, +}; + +static void __exit emmaprp_exit(void) +{ + platform_driver_unregister(&emmaprp_pdrv); +} + +static int __init emmaprp_init(void) +{ + return platform_driver_register(&emmaprp_pdrv); +} + +module_init(emmaprp_init); +module_exit(emmaprp_exit); diff --git a/drivers/media/video/noon010pc30.c b/drivers/media/video/noon010pc30.c index 50838bf84204..440c12962bae 100644 --- a/drivers/media/video/noon010pc30.c +++ b/drivers/media/video/noon010pc30.c @@ -725,8 +725,8 @@ static int noon010_probe(struct i2c_client *client, mutex_init(&info->lock); sd = &info->sd; - strlcpy(sd->name, MODULE_NAME, sizeof(sd->name)); v4l2_i2c_subdev_init(sd, client, &noon010_ops); + strlcpy(sd->name, MODULE_NAME, sizeof(sd->name)); sd->internal_ops = &noon010_subdev_internal_ops; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; @@ -844,18 +844,7 @@ static struct i2c_driver noon010_i2c_driver = { .id_table = noon010_id, }; -static int __init noon010_init(void) -{ - return i2c_add_driver(&noon010_i2c_driver); -} - -static void __exit noon010_exit(void) -{ - i2c_del_driver(&noon010_i2c_driver); -} - -module_init(noon010_init); -module_exit(noon010_exit); +module_i2c_driver(noon010_i2c_driver); MODULE_DESCRIPTION("Siliconfile NOON010PC30 camera driver"); MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); diff --git a/drivers/media/video/omap/omap_vout.c b/drivers/media/video/omap/omap_vout.c index 1fb7d5bd5ec2..88cf9d952631 100644 --- a/drivers/media/video/omap/omap_vout.c +++ b/drivers/media/video/omap/omap_vout.c @@ -2268,13 +2268,12 @@ static struct platform_driver omap_vout_driver = { .driver = { .name = VOUT_NAME, }, - .probe = omap_vout_probe, .remove = omap_vout_remove, }; static int __init omap_vout_init(void) { - if (platform_driver_register(&omap_vout_driver) != 0) { + if (platform_driver_probe(&omap_vout_driver, omap_vout_probe) != 0) { printk(KERN_ERR VOUT_NAME ":Could not register Video driver\n"); return -EINVAL; } diff --git a/drivers/media/video/ov2640.c b/drivers/media/video/ov2640.c index b5247cb64fde..3c2c5d3bcc6b 100644 --- a/drivers/media/video/ov2640.c +++ b/drivers/media/video/ov2640.c @@ -1103,21 +1103,7 @@ static struct i2c_driver ov2640_i2c_driver = { .id_table = ov2640_id, }; -/* - * Module functions - */ -static int __init ov2640_module_init(void) -{ - return i2c_add_driver(&ov2640_i2c_driver); -} - -static void __exit ov2640_module_exit(void) -{ - i2c_del_driver(&ov2640_i2c_driver); -} - -module_init(ov2640_module_init); -module_exit(ov2640_module_exit); +module_i2c_driver(ov2640_i2c_driver); MODULE_DESCRIPTION("SoC Camera driver for Omni Vision 2640 sensor"); MODULE_AUTHOR("Alberto Panizzo"); diff --git a/drivers/media/video/ov5642.c b/drivers/media/video/ov5642.c index bb37ec80f274..80e07794ac8e 100644 --- a/drivers/media/video/ov5642.c +++ b/drivers/media/video/ov5642.c @@ -1068,18 +1068,7 @@ static struct i2c_driver ov5642_i2c_driver = { .id_table = ov5642_id, }; -static int __init ov5642_mod_init(void) -{ - return i2c_add_driver(&ov5642_i2c_driver); -} - -static void __exit ov5642_mod_exit(void) -{ - i2c_del_driver(&ov5642_i2c_driver); -} - -module_init(ov5642_mod_init); -module_exit(ov5642_mod_exit); +module_i2c_driver(ov5642_i2c_driver); MODULE_DESCRIPTION("Omnivision OV5642 Camera driver"); MODULE_AUTHOR("Bastian Hecht <hechtb@gmail.com>"); diff --git a/drivers/media/video/ov6650.c b/drivers/media/video/ov6650.c index 3627f3225bbb..3e028b1970dd 100644 --- a/drivers/media/video/ov6650.c +++ b/drivers/media/video/ov6650.c @@ -1046,18 +1046,7 @@ static struct i2c_driver ov6650_i2c_driver = { .id_table = ov6650_id, }; -static int __init ov6650_module_init(void) -{ - return i2c_add_driver(&ov6650_i2c_driver); -} - -static void __exit ov6650_module_exit(void) -{ - i2c_del_driver(&ov6650_i2c_driver); -} - -module_init(ov6650_module_init); -module_exit(ov6650_module_exit); +module_i2c_driver(ov6650_i2c_driver); MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV6650"); MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>"); diff --git a/drivers/media/video/ov7670.c b/drivers/media/video/ov7670.c index 6a564964853a..e7c82b297514 100644 --- a/drivers/media/video/ov7670.c +++ b/drivers/media/video/ov7670.c @@ -1583,15 +1583,4 @@ static struct i2c_driver ov7670_driver = { .id_table = ov7670_id, }; -static __init int init_ov7670(void) -{ - return i2c_add_driver(&ov7670_driver); -} - -static __exit void exit_ov7670(void) -{ - i2c_del_driver(&ov7670_driver); -} - -module_init(init_ov7670); -module_exit(exit_ov7670); +module_i2c_driver(ov7670_driver); diff --git a/drivers/media/video/ov772x.c b/drivers/media/video/ov772x.c index 9f6ce3d8a29e..74e77d327ed8 100644 --- a/drivers/media/video/ov772x.c +++ b/drivers/media/video/ov772x.c @@ -1123,22 +1123,7 @@ static struct i2c_driver ov772x_i2c_driver = { .id_table = ov772x_id, }; -/* - * module function - */ - -static int __init ov772x_module_init(void) -{ - return i2c_add_driver(&ov772x_i2c_driver); -} - -static void __exit ov772x_module_exit(void) -{ - i2c_del_driver(&ov772x_i2c_driver); -} - -module_init(ov772x_module_init); -module_exit(ov772x_module_exit); +module_i2c_driver(ov772x_i2c_driver); MODULE_DESCRIPTION("SoC Camera driver for ov772x"); MODULE_AUTHOR("Kuninori Morimoto"); diff --git a/drivers/media/video/ov9640.c b/drivers/media/video/ov9640.c index a4f99797eb56..23412debb36b 100644 --- a/drivers/media/video/ov9640.c +++ b/drivers/media/video/ov9640.c @@ -738,18 +738,7 @@ static struct i2c_driver ov9640_i2c_driver = { .id_table = ov9640_id, }; -static int __init ov9640_module_init(void) -{ - return i2c_add_driver(&ov9640_i2c_driver); -} - -static void __exit ov9640_module_exit(void) -{ - i2c_del_driver(&ov9640_i2c_driver); -} - -module_init(ov9640_module_init); -module_exit(ov9640_module_exit); +module_i2c_driver(ov9640_i2c_driver); MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV96xx"); MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); diff --git a/drivers/media/video/ov9740.c b/drivers/media/video/ov9740.c index d9a9f7174f7a..3eb07c22516e 100644 --- a/drivers/media/video/ov9740.c +++ b/drivers/media/video/ov9740.c @@ -998,18 +998,7 @@ static struct i2c_driver ov9740_i2c_driver = { .id_table = ov9740_id, }; -static int __init ov9740_module_init(void) -{ - return i2c_add_driver(&ov9740_i2c_driver); -} - -static void __exit ov9740_module_exit(void) -{ - i2c_del_driver(&ov9740_i2c_driver); -} - -module_init(ov9740_module_init); -module_exit(ov9740_module_exit); +module_i2c_driver(ov9740_i2c_driver); MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV9740"); MODULE_AUTHOR("Andrew Chew <achew@nvidia.com>"); diff --git a/drivers/media/video/pvrusb2/pvrusb2-devattr.c b/drivers/media/video/pvrusb2/pvrusb2-devattr.c index c6da8f77e1a2..d8c898278e8c 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-devattr.c +++ b/drivers/media/video/pvrusb2/pvrusb2-devattr.c @@ -320,7 +320,17 @@ static struct tda829x_config tda829x_no_probe = { .probe_tuner = TDA829X_DONT_PROBE, }; +static struct tda18271_std_map hauppauge_tda18271_dvbt_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_7 = { .if_freq = 3800, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_8 = { .if_freq = 4300, .agc_mode = 3, .std = 6, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; + static struct tda18271_config hauppauge_tda18271_dvb_config = { + .std_map = &hauppauge_tda18271_dvbt_std_map, .gate = TDA18271_GATE_ANALOG, .output_opt = TDA18271_OUTPUT_LT_OFF, }; diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c index 6d666174dbb4..e1111d968a3d 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c +++ b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c @@ -96,7 +96,6 @@ static struct v4l2_capability pvr_capability ={ .capabilities = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | V4L2_CAP_AUDIO | V4L2_CAP_RADIO | V4L2_CAP_READWRITE), - .reserved = {0,0,0,0} }; static struct v4l2_fmtdesc pvr_fmtdesc [] = { diff --git a/drivers/media/video/pwc/pwc-v4l.c b/drivers/media/video/pwc/pwc-v4l.c index f495eeb5403a..2834e3e65b39 100644 --- a/drivers/media/video/pwc/pwc-v4l.c +++ b/drivers/media/video/pwc/pwc-v4l.c @@ -1146,14 +1146,6 @@ leave: return ret; } -static int pwc_log_status(struct file *file, void *priv) -{ - struct pwc_device *pdev = video_drvdata(file); - - v4l2_ctrl_handler_log_status(&pdev->ctrl_handler, PWC_NAME); - return 0; -} - const struct v4l2_ioctl_ops pwc_ioctl_ops = { .vidioc_querycap = pwc_querycap, .vidioc_enum_input = pwc_enum_input, @@ -1169,7 +1161,7 @@ const struct v4l2_ioctl_ops pwc_ioctl_ops = { .vidioc_dqbuf = pwc_dqbuf, .vidioc_streamon = pwc_streamon, .vidioc_streamoff = pwc_streamoff, - .vidioc_log_status = pwc_log_status, + .vidioc_log_status = v4l2_ctrl_log_status, .vidioc_enum_framesizes = pwc_enum_framesizes, .vidioc_enum_frameintervals = pwc_enum_frameintervals, .vidioc_g_parm = pwc_g_parm, diff --git a/drivers/media/video/pxa_camera.c b/drivers/media/video/pxa_camera.c index 0bd7da26d018..5a413f4427e0 100644 --- a/drivers/media/video/pxa_camera.c +++ b/drivers/media/video/pxa_camera.c @@ -921,12 +921,12 @@ static void pxa_camera_activate(struct pxa_camera_dev *pcdev) /* "Safe default" - 13MHz */ recalculate_fifo_timeout(pcdev, 13000000); - clk_enable(pcdev->clk); + clk_prepare_enable(pcdev->clk); } static void pxa_camera_deactivate(struct pxa_camera_dev *pcdev) { - clk_disable(pcdev->clk); + clk_disable_unprepare(pcdev->clk); } static irqreturn_t pxa_camera_irq(int irq, void *data) diff --git a/drivers/media/video/rj54n1cb0c.c b/drivers/media/video/rj54n1cb0c.c index 9937386a3bae..f6419b22c258 100644 --- a/drivers/media/video/rj54n1cb0c.c +++ b/drivers/media/video/rj54n1cb0c.c @@ -1407,18 +1407,7 @@ static struct i2c_driver rj54n1_i2c_driver = { .id_table = rj54n1_id, }; -static int __init rj54n1_mod_init(void) -{ - return i2c_add_driver(&rj54n1_i2c_driver); -} - -static void __exit rj54n1_mod_exit(void) -{ - i2c_del_driver(&rj54n1_i2c_driver); -} - -module_init(rj54n1_mod_init); -module_exit(rj54n1_mod_exit); +module_i2c_driver(rj54n1_i2c_driver); MODULE_DESCRIPTION("Sharp RJ54N1CB0C Camera driver"); MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); diff --git a/drivers/media/video/s2255drv.c b/drivers/media/video/s2255drv.c index c1bef6187661..4894cbb1c547 100644 --- a/drivers/media/video/s2255drv.c +++ b/drivers/media/video/s2255drv.c @@ -134,7 +134,7 @@ /* usb config commands */ #define IN_DATA_TOKEN cpu_to_le32(0x2255c0de) -#define CMD_2255 cpu_to_le32(0xc2255000) +#define CMD_2255 0xc2255000 #define CMD_SET_MODE cpu_to_le32((CMD_2255 | 0x10)) #define CMD_START cpu_to_le32((CMD_2255 | 0x20)) #define CMD_STOP cpu_to_le32((CMD_2255 | 0x30)) @@ -852,15 +852,13 @@ static int vidioc_querycap(struct file *file, void *priv, static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - int index = 0; - if (f) - index = f->index; + int index = f->index; if (index >= ARRAY_SIZE(formats)) return -EINVAL; - if (!jpeg_enable && ((formats[index].fourcc == V4L2_PIX_FMT_JPEG) || - (formats[index].fourcc == V4L2_PIX_FMT_MJPEG))) - return -EINVAL; + if (!jpeg_enable && ((formats[index].fourcc == V4L2_PIX_FMT_JPEG) || + (formats[index].fourcc == V4L2_PIX_FMT_MJPEG))) + return -EINVAL; dprintk(4, "name %s\n", formats[index].name); strlcpy(f->description, formats[index].name, sizeof(f->description)); f->pixelformat = formats[index].fourcc; @@ -2027,7 +2025,7 @@ static int save_frame(struct s2255_dev *dev, struct s2255_pipeinfo *pipe_info) pdata[1]); offset = jj + PREFIX_SIZE; bframe = 1; - cc = pdword[1]; + cc = le32_to_cpu(pdword[1]); if (cc >= MAX_CHANNELS) { printk(KERN_ERR "bad channel\n"); @@ -2036,22 +2034,22 @@ static int save_frame(struct s2255_dev *dev, struct s2255_pipeinfo *pipe_info) /* reverse it */ dev->cc = G_chnmap[cc]; channel = &dev->channel[dev->cc]; - payload = pdword[3]; + payload = le32_to_cpu(pdword[3]); if (payload > channel->req_image_size) { channel->bad_payload++; /* discard the bad frame */ return -EINVAL; } channel->pkt_size = payload; - channel->jpg_size = pdword[4]; + channel->jpg_size = le32_to_cpu(pdword[4]); break; case S2255_MARKER_RESPONSE: pdata += DEF_USB_BLOCK; jj += DEF_USB_BLOCK; - if (pdword[1] >= MAX_CHANNELS) + if (le32_to_cpu(pdword[1]) >= MAX_CHANNELS) break; - cc = G_chnmap[pdword[1]]; + cc = G_chnmap[le32_to_cpu(pdword[1])]; if (cc >= MAX_CHANNELS) break; channel = &dev->channel[cc]; @@ -2074,11 +2072,11 @@ static int save_frame(struct s2255_dev *dev, struct s2255_pipeinfo *pipe_info) wake_up(&dev->fw_data->wait_fw); break; case S2255_RESPONSE_STATUS: - channel->vidstatus = pdword[3]; + channel->vidstatus = le32_to_cpu(pdword[3]); channel->vidstatus_ready = 1; wake_up(&channel->wait_vidstatus); dprintk(5, "got vidstatus %x chan %d\n", - pdword[3], cc); + le32_to_cpu(pdword[3]), cc); break; default: printk(KERN_INFO "s2255 unknown resp\n"); @@ -2605,10 +2603,11 @@ static int s2255_probe(struct usb_interface *interface, __le32 *pRel; pRel = (__le32 *) &dev->fw_data->fw->data[fw_size - 4]; printk(KERN_INFO "s2255 dsp fw version %x\n", *pRel); - dev->dsp_fw_ver = *pRel; - if (*pRel < S2255_CUR_DSP_FWVER) + dev->dsp_fw_ver = le32_to_cpu(*pRel); + if (dev->dsp_fw_ver < S2255_CUR_DSP_FWVER) printk(KERN_INFO "s2255: f2255usb.bin out of date.\n"); - if (dev->pid == 0x2257 && *pRel < S2255_MIN_DSP_COLORFILTER) + if (dev->pid == 0x2257 && + dev->dsp_fw_ver < S2255_MIN_DSP_COLORFILTER) printk(KERN_WARNING "s2255: 2257 requires firmware %d" " or above.\n", S2255_MIN_DSP_COLORFILTER); } diff --git a/drivers/media/video/s5k6aa.c b/drivers/media/video/s5k6aa.c index 0df7f2a41814..6625e46a4638 100644 --- a/drivers/media/video/s5k6aa.c +++ b/drivers/media/video/s5k6aa.c @@ -1582,8 +1582,8 @@ static int s5k6aa_probe(struct i2c_client *client, s5k6aa->inv_vflip = pdata->vert_flip; sd = &s5k6aa->sd; - strlcpy(sd->name, DRIVER_NAME, sizeof(sd->name)); v4l2_i2c_subdev_init(sd, client, &s5k6aa_subdev_ops); + strlcpy(sd->name, DRIVER_NAME, sizeof(sd->name)); sd->internal_ops = &s5k6aa_subdev_internal_ops; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; @@ -1663,18 +1663,7 @@ static struct i2c_driver s5k6aa_i2c_driver = { .id_table = s5k6aa_id, }; -static int __init s5k6aa_init(void) -{ - return i2c_add_driver(&s5k6aa_i2c_driver); -} - -static void __exit s5k6aa_exit(void) -{ - i2c_del_driver(&s5k6aa_i2c_driver); -} - -module_init(s5k6aa_init); -module_exit(s5k6aa_exit); +module_i2c_driver(s5k6aa_i2c_driver); MODULE_DESCRIPTION("Samsung S5K6AA(FX) SXGA camera driver"); MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); diff --git a/drivers/media/video/s5p-fimc/fimc-capture.c b/drivers/media/video/s5p-fimc/fimc-capture.c index a9e9653beeb4..b06efd208328 100644 --- a/drivers/media/video/s5p-fimc/fimc-capture.c +++ b/drivers/media/video/s5p-fimc/fimc-capture.c @@ -1019,52 +1019,117 @@ static int fimc_cap_dqbuf(struct file *file, void *priv, return vb2_dqbuf(&fimc->vid_cap.vbq, buf, file->f_flags & O_NONBLOCK); } -static int fimc_cap_cropcap(struct file *file, void *fh, - struct v4l2_cropcap *cr) +static int fimc_cap_create_bufs(struct file *file, void *priv, + struct v4l2_create_buffers *create) { struct fimc_dev *fimc = video_drvdata(file); - struct fimc_frame *f = &fimc->vid_cap.ctx->s_frame; - if (cr->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) - return -EINVAL; + return vb2_create_bufs(&fimc->vid_cap.vbq, create); +} - cr->bounds.left = 0; - cr->bounds.top = 0; - cr->bounds.width = f->o_width; - cr->bounds.height = f->o_height; - cr->defrect = cr->bounds; +static int fimc_cap_prepare_buf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct fimc_dev *fimc = video_drvdata(file); - return 0; + return vb2_prepare_buf(&fimc->vid_cap.vbq, b); } -static int fimc_cap_g_crop(struct file *file, void *fh, struct v4l2_crop *cr) +static int fimc_cap_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) { struct fimc_dev *fimc = video_drvdata(file); - struct fimc_frame *f = &fimc->vid_cap.ctx->s_frame; + struct fimc_ctx *ctx = fimc->vid_cap.ctx; + struct fimc_frame *f = &ctx->s_frame; - cr->c.left = f->offs_h; - cr->c.top = f->offs_v; - cr->c.width = f->width; - cr->c.height = f->height; + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; - return 0; + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + f = &ctx->d_frame; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = f->o_width; + s->r.height = f->o_height; + return 0; + + case V4L2_SEL_TGT_COMPOSE_ACTIVE: + f = &ctx->d_frame; + case V4L2_SEL_TGT_CROP_ACTIVE: + s->r.left = f->offs_h; + s->r.top = f->offs_v; + s->r.width = f->width; + s->r.height = f->height; + return 0; + } + + return -EINVAL; } -static int fimc_cap_s_crop(struct file *file, void *fh, struct v4l2_crop *cr) +/* Return 1 if rectangle a is enclosed in rectangle b, or 0 otherwise. */ +int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b) +{ + if (a->left < b->left || a->top < b->top) + return 0; + if (a->left + a->width > b->left + b->width) + return 0; + if (a->top + a->height > b->top + b->height) + return 0; + + return 1; +} + +static int fimc_cap_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) { struct fimc_dev *fimc = video_drvdata(file); struct fimc_ctx *ctx = fimc->vid_cap.ctx; - struct fimc_frame *ff; + struct v4l2_rect rect = s->r; + struct fimc_frame *f; unsigned long flags; + unsigned int pad; - fimc_capture_try_crop(ctx, &cr->c, FIMC_SD_PAD_SINK); - ff = &ctx->s_frame; + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_ACTIVE: + f = &ctx->d_frame; + pad = FIMC_SD_PAD_SOURCE; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_ACTIVE: + f = &ctx->s_frame; + pad = FIMC_SD_PAD_SINK; + break; + default: + return -EINVAL; + } + + fimc_capture_try_crop(ctx, &rect, pad); + + if (s->flags & V4L2_SEL_FLAG_LE && + !enclosed_rectangle(&rect, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && + !enclosed_rectangle(&s->r, &rect)) + return -ERANGE; + + s->r = rect; spin_lock_irqsave(&fimc->slock, flags); - set_frame_crop(ff, cr->c.left, cr->c.top, cr->c.width, cr->c.height); - set_bit(ST_CAPT_APPLY_CFG, &fimc->state); + set_frame_crop(f, s->r.left, s->r.top, s->r.width, + s->r.height); spin_unlock_irqrestore(&fimc->slock, flags); + set_bit(ST_CAPT_APPLY_CFG, &fimc->state); return 0; } @@ -1082,12 +1147,14 @@ static const struct v4l2_ioctl_ops fimc_capture_ioctl_ops = { .vidioc_qbuf = fimc_cap_qbuf, .vidioc_dqbuf = fimc_cap_dqbuf, + .vidioc_prepare_buf = fimc_cap_prepare_buf, + .vidioc_create_bufs = fimc_cap_create_bufs, + .vidioc_streamon = fimc_cap_streamon, .vidioc_streamoff = fimc_cap_streamoff, - .vidioc_g_crop = fimc_cap_g_crop, - .vidioc_s_crop = fimc_cap_s_crop, - .vidioc_cropcap = fimc_cap_cropcap, + .vidioc_g_selection = fimc_cap_g_selection, + .vidioc_s_selection = fimc_cap_s_selection, .vidioc_enum_input = fimc_cap_enum_input, .vidioc_s_input = fimc_cap_s_input, diff --git a/drivers/media/video/s5p-fimc/fimc-core.c b/drivers/media/video/s5p-fimc/fimc-core.c index 81bcbb9492ea..e184e650022a 100644 --- a/drivers/media/video/s5p-fimc/fimc-core.c +++ b/drivers/media/video/s5p-fimc/fimc-core.c @@ -1602,24 +1602,35 @@ static void fimc_clk_put(struct fimc_dev *fimc) { int i; for (i = 0; i < fimc->num_clocks; i++) { - if (fimc->clock[i]) - clk_put(fimc->clock[i]); + if (IS_ERR_OR_NULL(fimc->clock[i])) + continue; + clk_unprepare(fimc->clock[i]); + clk_put(fimc->clock[i]); + fimc->clock[i] = NULL; } } static int fimc_clk_get(struct fimc_dev *fimc) { - int i; + int i, ret; + for (i = 0; i < fimc->num_clocks; i++) { fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clocks[i]); - if (!IS_ERR_OR_NULL(fimc->clock[i])) - continue; - dev_err(&fimc->pdev->dev, "failed to get fimc clock: %s\n", - fimc_clocks[i]); - return -ENXIO; + if (IS_ERR(fimc->clock[i])) + goto err; + ret = clk_prepare(fimc->clock[i]); + if (ret < 0) { + clk_put(fimc->clock[i]); + fimc->clock[i] = NULL; + goto err; + } } - return 0; +err: + fimc_clk_put(fimc); + dev_err(&fimc->pdev->dev, "failed to get clock: %s\n", + fimc_clocks[i]); + return -ENXIO; } static int fimc_m2m_suspend(struct fimc_dev *fimc) @@ -1667,8 +1678,6 @@ static int fimc_probe(struct platform_device *pdev) struct s5p_platform_fimc *pdata; int ret = 0; - dev_dbg(&pdev->dev, "%s():\n", __func__); - drv_data = (struct samsung_fimc_driverdata *) platform_get_device_id(pdev)->driver_data; @@ -1678,7 +1687,7 @@ static int fimc_probe(struct platform_device *pdev) return -EINVAL; } - fimc = kzalloc(sizeof(struct fimc_dev), GFP_KERNEL); + fimc = devm_kzalloc(&pdev->dev, sizeof(*fimc), GFP_KERNEL); if (!fimc) return -ENOMEM; @@ -1689,51 +1698,35 @@ static int fimc_probe(struct platform_device *pdev) pdata = pdev->dev.platform_data; fimc->pdata = pdata; - init_waitqueue_head(&fimc->irq_queue); spin_lock_init(&fimc->slock); mutex_init(&fimc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "failed to find the registers\n"); - ret = -ENOENT; - goto err_info; - } - - fimc->regs_res = request_mem_region(res->start, resource_size(res), - dev_name(&pdev->dev)); - if (!fimc->regs_res) { - dev_err(&pdev->dev, "failed to obtain register region\n"); - ret = -ENOENT; - goto err_info; - } - - fimc->regs = ioremap(res->start, resource_size(res)); - if (!fimc->regs) { - dev_err(&pdev->dev, "failed to map registers\n"); - ret = -ENXIO; - goto err_req_region; + fimc->regs = devm_request_and_ioremap(&pdev->dev, res); + if (fimc->regs == NULL) { + dev_err(&pdev->dev, "Failed to obtain io memory\n"); + return -ENOENT; } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!res) { - dev_err(&pdev->dev, "failed to get IRQ resource\n"); - ret = -ENXIO; - goto err_regs_unmap; + if (res == NULL) { + dev_err(&pdev->dev, "Failed to get IRQ resource\n"); + return -ENXIO; } fimc->irq = res->start; fimc->num_clocks = MAX_FIMC_CLOCKS; ret = fimc_clk_get(fimc); if (ret) - goto err_regs_unmap; + return ret; clk_set_rate(fimc->clock[CLK_BUS], drv_data->lclk_frequency); clk_enable(fimc->clock[CLK_BUS]); platform_set_drvdata(pdev, fimc); - ret = request_irq(fimc->irq, fimc_irq_handler, 0, pdev->name, fimc); + ret = devm_request_irq(&pdev->dev, fimc->irq, fimc_irq_handler, + 0, pdev->name, fimc); if (ret) { dev_err(&pdev->dev, "failed to install irq (%d)\n", ret); goto err_clk; @@ -1742,7 +1735,7 @@ static int fimc_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); ret = pm_runtime_get_sync(&pdev->dev); if (ret < 0) - goto err_irq; + goto err_clk; /* Initialize contiguous memory allocator */ fimc->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); if (IS_ERR(fimc->alloc_ctx)) { @@ -1757,17 +1750,8 @@ static int fimc_probe(struct platform_device *pdev) err_pm: pm_runtime_put(&pdev->dev); -err_irq: - free_irq(fimc->irq, fimc); err_clk: fimc_clk_put(fimc); -err_regs_unmap: - iounmap(fimc->regs); -err_req_region: - release_resource(fimc->regs_res); - kfree(fimc->regs_res); -err_info: - kfree(fimc); return ret; } @@ -1854,11 +1838,6 @@ static int __devexit fimc_remove(struct platform_device *pdev) clk_disable(fimc->clock[CLK_BUS]); fimc_clk_put(fimc); - free_irq(fimc->irq, fimc); - iounmap(fimc->regs); - release_resource(fimc->regs_res); - kfree(fimc->regs_res); - kfree(fimc); dev_info(&pdev->dev, "driver unloaded\n"); return 0; diff --git a/drivers/media/video/s5p-fimc/fimc-core.h b/drivers/media/video/s5p-fimc/fimc-core.h index 4e20560c73d4..a18291e648e2 100644 --- a/drivers/media/video/s5p-fimc/fimc-core.h +++ b/drivers/media/video/s5p-fimc/fimc-core.h @@ -434,7 +434,6 @@ struct fimc_ctx; * @num_clocks: the number of clocks managed by this device instance * @clock: clocks required for FIMC operation * @regs: the mapped hardware registers - * @regs_res: the resource claimed for IO registers * @irq: FIMC interrupt number * @irq_queue: interrupt handler waitqueue * @v4l2_dev: root v4l2_device @@ -454,7 +453,6 @@ struct fimc_dev { u16 num_clocks; struct clk *clock[MAX_FIMC_CLOCKS]; void __iomem *regs; - struct resource *regs_res; int irq; wait_queue_head_t irq_queue; struct v4l2_device *v4l2_dev; diff --git a/drivers/media/video/s5p-fimc/fimc-mdevice.c b/drivers/media/video/s5p-fimc/fimc-mdevice.c index 63eccb55728f..62ed37e40149 100644 --- a/drivers/media/video/s5p-fimc/fimc-mdevice.c +++ b/drivers/media/video/s5p-fimc/fimc-mdevice.c @@ -750,7 +750,7 @@ static int __devinit fimc_md_probe(struct platform_device *pdev) struct fimc_md *fmd; int ret; - fmd = kzalloc(sizeof(struct fimc_md), GFP_KERNEL); + fmd = devm_kzalloc(&pdev->dev, sizeof(*fmd), GFP_KERNEL); if (!fmd) return -ENOMEM; @@ -771,7 +771,7 @@ static int __devinit fimc_md_probe(struct platform_device *pdev) ret = v4l2_device_register(&pdev->dev, &fmd->v4l2_dev); if (ret < 0) { v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret); - goto err1; + return ret; } ret = media_device_register(&fmd->media_dev); if (ret < 0) { @@ -813,8 +813,6 @@ err3: fimc_md_unregister_entities(fmd); err2: v4l2_device_unregister(&fmd->v4l2_dev); -err1: - kfree(fmd); return ret; } @@ -828,7 +826,6 @@ static int __devexit fimc_md_remove(struct platform_device *pdev) fimc_md_unregister_entities(fmd); media_device_unregister(&fmd->media_dev); fimc_md_put_clocks(fmd); - kfree(fmd); return 0; } diff --git a/drivers/media/video/s5p-fimc/mipi-csis.c b/drivers/media/video/s5p-fimc/mipi-csis.c index 130335cf62fd..f44f690397f7 100644 --- a/drivers/media/video/s5p-fimc/mipi-csis.c +++ b/drivers/media/video/s5p-fimc/mipi-csis.c @@ -1,8 +1,8 @@ /* * Samsung S5P/EXYNOS4 SoC series MIPI-CSI receiver driver * - * Copyright (C) 2011 Samsung Electronics Co., Ltd. - * Contact: Sylwester Nawrocki, <s.nawrocki@samsung.com> + * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki, <s.nawrocki@samsung.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 @@ -100,7 +100,6 @@ enum { * @pads: CSIS pads array * @sd: v4l2_subdev associated with CSIS device instance * @pdev: CSIS platform device - * @regs_res: requested I/O register memory resource * @regs: mmaped I/O registers memory * @clock: CSIS clocks * @irq: requested s5p-mipi-csis irq number @@ -113,7 +112,6 @@ struct csis_state { struct media_pad pads[CSIS_PADS_NUM]; struct v4l2_subdev sd; struct platform_device *pdev; - struct resource *regs_res; void __iomem *regs; struct regulator_bulk_data supplies[CSIS_NUM_SUPPLIES]; struct clk *clock[NUM_CSIS_CLOCKS]; @@ -258,26 +256,36 @@ static void s5pcsis_clk_put(struct csis_state *state) { int i; - for (i = 0; i < NUM_CSIS_CLOCKS; i++) - if (!IS_ERR_OR_NULL(state->clock[i])) - clk_put(state->clock[i]); + for (i = 0; i < NUM_CSIS_CLOCKS; i++) { + if (IS_ERR_OR_NULL(state->clock[i])) + continue; + clk_unprepare(state->clock[i]); + clk_put(state->clock[i]); + state->clock[i] = NULL; + } } static int s5pcsis_clk_get(struct csis_state *state) { struct device *dev = &state->pdev->dev; - int i; + int i, ret; for (i = 0; i < NUM_CSIS_CLOCKS; i++) { state->clock[i] = clk_get(dev, csi_clock_name[i]); - if (IS_ERR(state->clock[i])) { - s5pcsis_clk_put(state); - dev_err(dev, "failed to get clock: %s\n", - csi_clock_name[i]); - return -ENXIO; + if (IS_ERR(state->clock[i])) + goto err; + ret = clk_prepare(state->clock[i]); + if (ret < 0) { + clk_put(state->clock[i]); + state->clock[i] = NULL; + goto err; } } return 0; +err: + s5pcsis_clk_put(state); + dev_err(dev, "failed to get clock: %s\n", csi_clock_name[i]); + return -ENXIO; } static int s5pcsis_s_power(struct v4l2_subdev *sd, int on) @@ -480,12 +488,11 @@ static int __devinit s5pcsis_probe(struct platform_device *pdev) { struct s5p_platform_mipi_csis *pdata; struct resource *mem_res; - struct resource *regs_res; struct csis_state *state; int ret = -ENOMEM; int i; - state = kzalloc(sizeof(*state), GFP_KERNEL); + state = devm_kzalloc(&pdev->dev, sizeof(*state), GFP_KERNEL); if (!state) return -ENOMEM; @@ -495,52 +502,27 @@ static int __devinit s5pcsis_probe(struct platform_device *pdev) pdata = pdev->dev.platform_data; if (pdata == NULL || pdata->phy_enable == NULL) { dev_err(&pdev->dev, "Platform data not fully specified\n"); - goto e_free; + return -EINVAL; } if ((pdev->id == 1 && pdata->lanes > CSIS1_MAX_LANES) || pdata->lanes > CSIS0_MAX_LANES) { - ret = -EINVAL; dev_err(&pdev->dev, "Unsupported number of data lanes: %d\n", pdata->lanes); - goto e_free; + return -EINVAL; } mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem_res) { - dev_err(&pdev->dev, "Failed to get IO memory region\n"); - goto e_free; + state->regs = devm_request_and_ioremap(&pdev->dev, mem_res); + if (state->regs == NULL) { + dev_err(&pdev->dev, "Failed to request and remap io memory\n"); + return -ENXIO; } - regs_res = request_mem_region(mem_res->start, resource_size(mem_res), - pdev->name); - if (!regs_res) { - dev_err(&pdev->dev, "Failed to request IO memory region\n"); - goto e_free; - } - state->regs_res = regs_res; - - state->regs = ioremap(mem_res->start, resource_size(mem_res)); - if (!state->regs) { - dev_err(&pdev->dev, "Failed to remap IO region\n"); - goto e_reqmem; - } - - ret = s5pcsis_clk_get(state); - if (ret) - goto e_unmap; - - clk_enable(state->clock[CSIS_CLK_MUX]); - if (pdata->clk_rate) - clk_set_rate(state->clock[CSIS_CLK_MUX], pdata->clk_rate); - else - dev_WARN(&pdev->dev, "No clock frequency specified!\n"); - state->irq = platform_get_irq(pdev, 0); if (state->irq < 0) { - ret = state->irq; dev_err(&pdev->dev, "Failed to get irq\n"); - goto e_clkput; + return state->irq; } for (i = 0; i < CSIS_NUM_SUPPLIES; i++) @@ -549,12 +531,22 @@ static int __devinit s5pcsis_probe(struct platform_device *pdev) ret = regulator_bulk_get(&pdev->dev, CSIS_NUM_SUPPLIES, state->supplies); if (ret) + return ret; + + ret = s5pcsis_clk_get(state); + if (ret) goto e_clkput; - ret = request_irq(state->irq, s5pcsis_irq_handler, 0, - dev_name(&pdev->dev), state); + clk_enable(state->clock[CSIS_CLK_MUX]); + if (pdata->clk_rate) + clk_set_rate(state->clock[CSIS_CLK_MUX], pdata->clk_rate); + else + dev_WARN(&pdev->dev, "No clock frequency specified!\n"); + + ret = devm_request_irq(&pdev->dev, state->irq, s5pcsis_irq_handler, + 0, dev_name(&pdev->dev), state); if (ret) { - dev_err(&pdev->dev, "request_irq failed\n"); + dev_err(&pdev->dev, "Interrupt request failed\n"); goto e_regput; } @@ -573,7 +565,7 @@ static int __devinit s5pcsis_probe(struct platform_device *pdev) ret = media_entity_init(&state->sd.entity, CSIS_PADS_NUM, state->pads, 0); if (ret < 0) - goto e_irqfree; + goto e_clkput; /* This allows to retrieve the platform device id by the host driver */ v4l2_set_subdevdata(&state->sd, pdev); @@ -582,22 +574,13 @@ static int __devinit s5pcsis_probe(struct platform_device *pdev) platform_set_drvdata(pdev, &state->sd); pm_runtime_enable(&pdev->dev); - return 0; -e_irqfree: - free_irq(state->irq, state); e_regput: regulator_bulk_free(CSIS_NUM_SUPPLIES, state->supplies); e_clkput: clk_disable(state->clock[CSIS_CLK_MUX]); s5pcsis_clk_put(state); -e_unmap: - iounmap(state->regs); -e_reqmem: - release_mem_region(regs_res->start, resource_size(regs_res)); -e_free: - kfree(state); return ret; } @@ -699,21 +682,15 @@ static int __devexit s5pcsis_remove(struct platform_device *pdev) { struct v4l2_subdev *sd = platform_get_drvdata(pdev); struct csis_state *state = sd_to_csis_state(sd); - struct resource *res = state->regs_res; pm_runtime_disable(&pdev->dev); - s5pcsis_suspend(&pdev->dev); + s5pcsis_pm_suspend(&pdev->dev, false); clk_disable(state->clock[CSIS_CLK_MUX]); pm_runtime_set_suspended(&pdev->dev); - s5pcsis_clk_put(state); regulator_bulk_free(CSIS_NUM_SUPPLIES, state->supplies); media_entity_cleanup(&state->sd.entity); - free_irq(state->irq, state); - iounmap(state->regs); - release_mem_region(res->start, resource_size(res)); - kfree(state); return 0; } diff --git a/drivers/media/video/s5p-g2d/g2d-hw.c b/drivers/media/video/s5p-g2d/g2d-hw.c index 39937cf03c88..5b86cbe408e2 100644 --- a/drivers/media/video/s5p-g2d/g2d-hw.c +++ b/drivers/media/video/s5p-g2d/g2d-hw.c @@ -77,6 +77,11 @@ void g2d_set_rop4(struct g2d_dev *d, u32 r) w(r, ROP4_REG); } +void g2d_set_flip(struct g2d_dev *d, u32 r) +{ + w(r, SRC_MSK_DIRECT_REG); +} + u32 g2d_cmd_stretch(u32 e) { e &= 1; diff --git a/drivers/media/video/s5p-g2d/g2d.c b/drivers/media/video/s5p-g2d/g2d.c index febaa673d363..789de74014e5 100644 --- a/drivers/media/video/s5p-g2d/g2d.c +++ b/drivers/media/video/s5p-g2d/g2d.c @@ -178,6 +178,9 @@ static int g2d_s_ctrl(struct v4l2_ctrl *ctrl) { struct g2d_ctx *ctx = container_of(ctrl->handler, struct g2d_ctx, ctrl_handler); + unsigned long flags; + + spin_lock_irqsave(&ctx->dev->ctrl_lock, flags); switch (ctrl->id) { case V4L2_CID_COLORFX: if (ctrl->val == V4L2_COLORFX_NEGATIVE) @@ -185,10 +188,13 @@ static int g2d_s_ctrl(struct v4l2_ctrl *ctrl) else ctx->rop = ROP4_COPY; break; - default: - v4l2_err(&ctx->dev->v4l2_dev, "unknown control\n"); - return -EINVAL; + + case V4L2_CID_HFLIP: + ctx->flip = ctx->ctrl_hflip->val | (ctx->ctrl_vflip->val << 1); + break; + } + spin_unlock_irqrestore(&ctx->dev->ctrl_lock, flags); return 0; } @@ -200,11 +206,13 @@ int g2d_setup_ctrls(struct g2d_ctx *ctx) { struct g2d_dev *dev = ctx->dev; - v4l2_ctrl_handler_init(&ctx->ctrl_handler, 1); - if (ctx->ctrl_handler.error) { - v4l2_err(&dev->v4l2_dev, "v4l2_ctrl_handler_init failed\n"); - return ctx->ctrl_handler.error; - } + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); + + ctx->ctrl_hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, &g2d_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + ctx->ctrl_vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, &g2d_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); v4l2_ctrl_new_std_menu( &ctx->ctrl_handler, @@ -215,10 +223,14 @@ int g2d_setup_ctrls(struct g2d_ctx *ctx) V4L2_COLORFX_NONE); if (ctx->ctrl_handler.error) { - v4l2_err(&dev->v4l2_dev, "v4l2_ctrl_handler_init failed\n"); - return ctx->ctrl_handler.error; + int err = ctx->ctrl_handler.error; + v4l2_err(&dev->v4l2_dev, "g2d_setup_ctrls failed\n"); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; } + v4l2_ctrl_cluster(2, &ctx->ctrl_hflip); + return 0; } @@ -547,6 +559,7 @@ static void device_run(void *prv) struct g2d_ctx *ctx = prv; struct g2d_dev *dev = ctx->dev; struct vb2_buffer *src, *dst; + unsigned long flags; u32 cmd = 0; dev->curr = ctx; @@ -557,6 +570,8 @@ static void device_run(void *prv) clk_enable(dev->gate); g2d_reset(dev); + spin_lock_irqsave(&dev->ctrl_lock, flags); + g2d_set_src_size(dev, &ctx->in); g2d_set_src_addr(dev, vb2_dma_contig_plane_dma_addr(src, 0)); @@ -564,11 +579,15 @@ static void device_run(void *prv) g2d_set_dst_addr(dev, vb2_dma_contig_plane_dma_addr(dst, 0)); g2d_set_rop4(dev, ctx->rop); + g2d_set_flip(dev, ctx->flip); + if (ctx->in.c_width != ctx->out.c_width || ctx->in.c_height != ctx->out.c_height) cmd |= g2d_cmd_stretch(1); g2d_set_cmd(dev, cmd); g2d_start(dev); + + spin_unlock_irqrestore(&dev->ctrl_lock, flags); } static irqreturn_t g2d_isr(int irq, void *prv) @@ -658,7 +677,7 @@ static int g2d_probe(struct platform_device *pdev) dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; - spin_lock_init(&dev->irqlock); + spin_lock_init(&dev->ctrl_lock); mutex_init(&dev->mutex); atomic_set(&dev->num_inst, 0); init_waitqueue_head(&dev->irq_queue); @@ -693,18 +712,30 @@ static int g2d_probe(struct platform_device *pdev) goto unmap_regs; } + ret = clk_prepare(dev->clk); + if (ret) { + dev_err(&pdev->dev, "failed to prepare g2d clock\n"); + goto put_clk; + } + dev->gate = clk_get(&pdev->dev, "fimg2d"); if (IS_ERR_OR_NULL(dev->gate)) { dev_err(&pdev->dev, "failed to get g2d clock gate\n"); ret = -ENXIO; - goto put_clk; + goto unprep_clk; + } + + ret = clk_prepare(dev->gate); + if (ret) { + dev_err(&pdev->dev, "failed to prepare g2d clock gate\n"); + goto put_clk_gate; } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(&pdev->dev, "failed to find IRQ\n"); ret = -ENXIO; - goto put_clk_gate; + goto unprep_clk_gate; } dev->irq = res->start; @@ -764,8 +795,12 @@ alloc_ctx_cleanup: vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); rel_irq: free_irq(dev->irq, dev); +unprep_clk_gate: + clk_unprepare(dev->gate); put_clk_gate: clk_put(dev->gate); +unprep_clk: + clk_unprepare(dev->clk); put_clk: clk_put(dev->clk); unmap_regs: @@ -787,7 +822,9 @@ static int g2d_remove(struct platform_device *pdev) v4l2_device_unregister(&dev->v4l2_dev); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); free_irq(dev->irq, dev); + clk_unprepare(dev->gate); clk_put(dev->gate); + clk_unprepare(dev->clk); clk_put(dev->clk); iounmap(dev->regs); release_resource(dev->res_regs); diff --git a/drivers/media/video/s5p-g2d/g2d.h b/drivers/media/video/s5p-g2d/g2d.h index 5eae90107bf8..1b82065aeaef 100644 --- a/drivers/media/video/s5p-g2d/g2d.h +++ b/drivers/media/video/s5p-g2d/g2d.h @@ -20,7 +20,7 @@ struct g2d_dev { struct v4l2_m2m_dev *m2m_dev; struct video_device *vfd; struct mutex mutex; - spinlock_t irqlock; + spinlock_t ctrl_lock; atomic_t num_inst; struct vb2_alloc_ctx *alloc_ctx; struct resource *res_regs; @@ -57,8 +57,11 @@ struct g2d_ctx { struct v4l2_m2m_ctx *m2m_ctx; struct g2d_frame in; struct g2d_frame out; + struct v4l2_ctrl *ctrl_hflip; + struct v4l2_ctrl *ctrl_vflip; struct v4l2_ctrl_handler ctrl_handler; u32 rop; + u32 flip; }; struct g2d_fmt { @@ -77,6 +80,7 @@ void g2d_set_dst_addr(struct g2d_dev *d, dma_addr_t a); void g2d_start(struct g2d_dev *d); void g2d_clear_int(struct g2d_dev *d); void g2d_set_rop4(struct g2d_dev *d, u32 r); +void g2d_set_flip(struct g2d_dev *d, u32 r); u32 g2d_cmd_stretch(u32 e); void g2d_set_cmd(struct g2d_dev *d, u32 c); diff --git a/drivers/media/video/s5p-jpeg/jpeg-core.c b/drivers/media/video/s5p-jpeg/jpeg-core.c index 1105a8749c8b..5a49c307f9c1 100644 --- a/drivers/media/video/s5p-jpeg/jpeg-core.c +++ b/drivers/media/video/s5p-jpeg/jpeg-core.c @@ -32,10 +32,9 @@ static struct s5p_jpeg_fmt formats_enc[] = { { - .name = "YUV 4:2:0 planar, YCbCr", - .fourcc = V4L2_PIX_FMT_YUV420, - .depth = 12, - .colplanes = 3, + .name = "JPEG JFIF", + .fourcc = V4L2_PIX_FMT_JPEG, + .colplanes = 1, .types = MEM2MEM_CAPTURE, }, { @@ -43,7 +42,7 @@ static struct s5p_jpeg_fmt formats_enc[] = { .fourcc = V4L2_PIX_FMT_YUYV, .depth = 16, .colplanes = 1, - .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + .types = MEM2MEM_OUTPUT, }, { .name = "RGB565", @@ -203,6 +202,16 @@ static const unsigned char hactblg0[162] = { 0xf9, 0xfa }; +static inline struct s5p_jpeg_ctx *ctrl_to_ctx(struct v4l2_ctrl *c) +{ + return container_of(c->handler, struct s5p_jpeg_ctx, ctrl_handler); +} + +static inline struct s5p_jpeg_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct s5p_jpeg_ctx, fh); +} + static inline void jpeg_set_qtbl(void __iomem *regs, const unsigned char *qtbl, unsigned long tab, int len) { @@ -269,6 +278,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq); static struct s5p_jpeg_fmt *s5p_jpeg_find_format(unsigned int mode, __u32 pixelformat); +static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx); static int s5p_jpeg_open(struct file *file) { @@ -276,12 +286,18 @@ static int s5p_jpeg_open(struct file *file) struct video_device *vfd = video_devdata(file); struct s5p_jpeg_ctx *ctx; struct s5p_jpeg_fmt *out_fmt; + int ret = 0; ctx = kzalloc(sizeof *ctx, GFP_KERNEL); if (!ctx) return -ENOMEM; - file->private_data = ctx; + v4l2_fh_init(&ctx->fh, vfd); + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + ctx->jpeg = jpeg; if (vfd == jpeg->vfd_encoder) { ctx->mode = S5P_JPEG_ENCODE; @@ -291,24 +307,35 @@ static int s5p_jpeg_open(struct file *file) out_fmt = s5p_jpeg_find_format(ctx->mode, V4L2_PIX_FMT_JPEG); } + ret = s5p_jpeg_controls_create(ctx); + if (ret < 0) + goto error; + ctx->m2m_ctx = v4l2_m2m_ctx_init(jpeg->m2m_dev, ctx, queue_init); if (IS_ERR(ctx->m2m_ctx)) { - int err = PTR_ERR(ctx->m2m_ctx); - kfree(ctx); - return err; + ret = PTR_ERR(ctx->m2m_ctx); + goto error; } ctx->out_q.fmt = out_fmt; ctx->cap_q.fmt = s5p_jpeg_find_format(ctx->mode, V4L2_PIX_FMT_YUYV); - return 0; + +error: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + return ret; } static int s5p_jpeg_release(struct file *file) { - struct s5p_jpeg_ctx *ctx = file->private_data; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); kfree(ctx); return 0; @@ -317,14 +344,14 @@ static int s5p_jpeg_release(struct file *file) static unsigned int s5p_jpeg_poll(struct file *file, struct poll_table_struct *wait) { - struct s5p_jpeg_ctx *ctx = file->private_data; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); return v4l2_m2m_poll(file, ctx->m2m_ctx, wait); } static int s5p_jpeg_mmap(struct file *file, struct vm_area_struct *vma) { - struct s5p_jpeg_ctx *ctx = file->private_data; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); return v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); } @@ -448,7 +475,7 @@ static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result, static int s5p_jpeg_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); if (ctx->mode == S5P_JPEG_ENCODE) { strlcpy(cap->driver, S5P_JPEG_M2M_NAME " encoder", @@ -497,9 +524,7 @@ static int enum_fmt(struct s5p_jpeg_fmt *formats, int n, static int s5p_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - struct s5p_jpeg_ctx *ctx; - - ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); if (ctx->mode == S5P_JPEG_ENCODE) return enum_fmt(formats_enc, NUM_FORMATS_ENC, f, @@ -511,9 +536,7 @@ static int s5p_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, static int s5p_jpeg_enum_fmt_vid_out(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - struct s5p_jpeg_ctx *ctx; - - ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); if (ctx->mode == S5P_JPEG_ENCODE) return enum_fmt(formats_enc, NUM_FORMATS_ENC, f, @@ -538,7 +561,7 @@ static int s5p_jpeg_g_fmt(struct file *file, void *priv, struct v4l2_format *f) struct vb2_queue *vq; struct s5p_jpeg_q_data *q_data = NULL; struct v4l2_pix_format *pix = &f->fmt.pix; - struct s5p_jpeg_ctx *ct = priv; + struct s5p_jpeg_ctx *ct = fh_to_ctx(priv); vq = v4l2_m2m_get_vq(ct->m2m_ctx, f->type); if (!vq) @@ -659,8 +682,8 @@ static int vidioc_try_fmt(struct v4l2_format *f, struct s5p_jpeg_fmt *fmt, static int s5p_jpeg_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); struct s5p_jpeg_fmt *fmt; - struct s5p_jpeg_ctx *ctx = priv; fmt = s5p_jpeg_find_format(ctx->mode, f->fmt.pix.pixelformat); if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { @@ -676,8 +699,8 @@ static int s5p_jpeg_try_fmt_vid_cap(struct file *file, void *priv, static int s5p_jpeg_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); struct s5p_jpeg_fmt *fmt; - struct s5p_jpeg_ctx *ctx = priv; fmt = s5p_jpeg_find_format(ctx->mode, f->fmt.pix.pixelformat); if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { @@ -728,7 +751,7 @@ static int s5p_jpeg_s_fmt_vid_cap(struct file *file, void *priv, if (ret) return ret; - return s5p_jpeg_s_fmt(priv, f); + return s5p_jpeg_s_fmt(fh_to_ctx(priv), f); } static int s5p_jpeg_s_fmt_vid_out(struct file *file, void *priv, @@ -740,13 +763,13 @@ static int s5p_jpeg_s_fmt_vid_out(struct file *file, void *priv, if (ret) return ret; - return s5p_jpeg_s_fmt(priv, f); + return s5p_jpeg_s_fmt(fh_to_ctx(priv), f); } static int s5p_jpeg_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *reqbufs) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); } @@ -754,14 +777,14 @@ static int s5p_jpeg_reqbufs(struct file *file, void *priv, static int s5p_jpeg_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); } static int s5p_jpeg_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); } @@ -769,7 +792,7 @@ static int s5p_jpeg_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) static int s5p_jpeg_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); } @@ -777,7 +800,7 @@ static int s5p_jpeg_dqbuf(struct file *file, void *priv, static int s5p_jpeg_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); } @@ -785,7 +808,7 @@ static int s5p_jpeg_streamon(struct file *file, void *priv, static int s5p_jpeg_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); } @@ -793,7 +816,7 @@ static int s5p_jpeg_streamoff(struct file *file, void *priv, int s5p_jpeg_g_selection(struct file *file, void *priv, struct v4l2_selection *s) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) @@ -822,33 +845,89 @@ int s5p_jpeg_g_selection(struct file *file, void *priv, return 0; } -static int s5p_jpeg_g_jpegcomp(struct file *file, void *priv, - struct v4l2_jpegcompression *compr) +/* + * V4L2 controls + */ + +static int s5p_jpeg_g_volatile_ctrl(struct v4l2_ctrl *ctrl) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); + struct s5p_jpeg *jpeg = ctx->jpeg; + unsigned long flags; - if (ctx->mode == S5P_JPEG_DECODE) - return -ENOTTY; + switch (ctrl->id) { + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + spin_lock_irqsave(&jpeg->slock, flags); - memset(compr, 0, sizeof(*compr)); - compr->quality = ctx->compr_quality; + WARN_ON(ctx->subsampling > S5P_SUBSAMPLING_MODE_GRAY); + if (ctx->subsampling > 2) + ctrl->val = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY; + else + ctrl->val = ctx->subsampling; + spin_unlock_irqrestore(&jpeg->slock, flags); + break; + } return 0; } -static int s5p_jpeg_s_jpegcomp(struct file *file, void *priv, - struct v4l2_jpegcompression *compr) +static int s5p_jpeg_s_ctrl(struct v4l2_ctrl *ctrl) { - struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; - if (ctx->mode == S5P_JPEG_DECODE) - return -ENOTTY; + spin_lock_irqsave(&ctx->jpeg->slock, flags); - compr->quality = clamp(compr->quality, S5P_JPEG_COMPR_QUAL_BEST, - S5P_JPEG_COMPR_QUAL_WORST); + switch (ctrl->id) { + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ctx->compr_quality = S5P_JPEG_COMPR_QUAL_WORST - ctrl->val; + break; + case V4L2_CID_JPEG_RESTART_INTERVAL: + ctx->restart_interval = ctrl->val; + break; + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + ctx->subsampling = ctrl->val; + break; + } - ctx->compr_quality = S5P_JPEG_COMPR_QUAL_WORST - compr->quality; + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); + return 0; +} + +static const struct v4l2_ctrl_ops s5p_jpeg_ctrl_ops = { + .g_volatile_ctrl = s5p_jpeg_g_volatile_ctrl, + .s_ctrl = s5p_jpeg_s_ctrl, +}; +static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx) +{ + unsigned int mask = ~0x27; /* 444, 422, 420, GRAY */ + struct v4l2_ctrl *ctrl; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); + + if (ctx->mode == S5P_JPEG_ENCODE) { + v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, + 0, 3, 1, 3); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, + V4L2_CID_JPEG_RESTART_INTERVAL, + 0, 3, 0xffff, 0); + mask = ~0x06; /* 422, 420 */ + } + + ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, + V4L2_CID_JPEG_CHROMA_SUBSAMPLING, + V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, mask, + V4L2_JPEG_CHROMA_SUBSAMPLING_422); + + if (ctx->ctrl_handler.error) + return ctx->ctrl_handler.error; + + if (ctx->mode == S5P_JPEG_DECODE) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY; return 0; } @@ -877,9 +956,6 @@ static const struct v4l2_ioctl_ops s5p_jpeg_ioctl_ops = { .vidioc_streamoff = s5p_jpeg_streamoff, .vidioc_g_selection = s5p_jpeg_g_selection, - - .vidioc_g_jpegcomp = s5p_jpeg_g_jpegcomp, - .vidioc_s_jpegcomp = s5p_jpeg_s_jpegcomp, }; /* @@ -908,13 +984,8 @@ static void s5p_jpeg_device_run(void *priv) jpeg_input_raw_mode(jpeg->regs, S5P_JPEG_RAW_IN_565); else jpeg_input_raw_mode(jpeg->regs, S5P_JPEG_RAW_IN_422); - if (ctx->cap_q.fmt->fourcc == V4L2_PIX_FMT_YUYV) - jpeg_subsampling_mode(jpeg->regs, - S5P_JPEG_SUBSAMPLING_422); - else - jpeg_subsampling_mode(jpeg->regs, - S5P_JPEG_SUBSAMPLING_420); - jpeg_dri(jpeg->regs, 0); + jpeg_subsampling_mode(jpeg->regs, ctx->subsampling); + jpeg_dri(jpeg->regs, ctx->restart_interval); jpeg_x(jpeg->regs, ctx->out_q.w); jpeg_y(jpeg->regs, ctx->out_q.h); jpeg_imgadr(jpeg->regs, src_addr); @@ -953,14 +1024,18 @@ static void s5p_jpeg_device_run(void *priv) jpeg_htbl_dc(jpeg->regs, 2); jpeg_htbl_ac(jpeg->regs, 3); jpeg_htbl_dc(jpeg->regs, 3); - } else { + } else { /* S5P_JPEG_DECODE */ jpeg_rst_int_enable(jpeg->regs, true); jpeg_data_num_int_enable(jpeg->regs, true); jpeg_final_mcu_num_int_enable(jpeg->regs, true); - jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_422); + if (ctx->cap_q.fmt->fourcc == V4L2_PIX_FMT_YUYV) + jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_422); + else + jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_420); jpeg_jpgadr(jpeg->regs, src_addr); jpeg_imgadr(jpeg->regs, dst_addr); } + jpeg_start(jpeg->regs); } @@ -1162,6 +1237,8 @@ static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id) bool timer_elapsed = false; bool op_completed = false; + spin_lock(&jpeg->slock); + curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); src_buf = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); @@ -1192,6 +1269,9 @@ static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id) v4l2_m2m_buf_done(dst_buf, state); v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->m2m_ctx); + curr_ctx->subsampling = jpeg_get_subsampling_mode(jpeg->regs); + spin_unlock(&jpeg->slock); + jpeg_clear_int(jpeg->regs); return IRQ_HANDLED; @@ -1215,6 +1295,7 @@ static int s5p_jpeg_probe(struct platform_device *pdev) return -ENOMEM; mutex_init(&jpeg->lock); + spin_lock_init(&jpeg->slock); jpeg->dev = &pdev->dev; /* memory-mapped registers */ diff --git a/drivers/media/video/s5p-jpeg/jpeg-core.h b/drivers/media/video/s5p-jpeg/jpeg-core.h index facad6114f5e..38d7367f7a6d 100644 --- a/drivers/media/video/s5p-jpeg/jpeg-core.h +++ b/drivers/media/video/s5p-jpeg/jpeg-core.h @@ -14,6 +14,8 @@ #define JPEG_CORE_H_ #include <media/v4l2-device.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ctrls.h> #define S5P_JPEG_M2M_NAME "s5p-jpeg" @@ -47,6 +49,7 @@ /** * struct s5p_jpeg - JPEG IP abstraction * @lock: the mutex protecting this structure + * @slock: spinlock protecting the device contexts * @v4l2_dev: v4l2 device for mem2mem mode * @vfd_encoder: video device node for encoder mem2mem mode * @vfd_decoder: video device node for decoder mem2mem mode @@ -60,6 +63,7 @@ */ struct s5p_jpeg { struct mutex lock; + struct spinlock slock; struct v4l2_device v4l2_dev; struct video_device *vfd_encoder; @@ -117,15 +121,20 @@ struct s5p_jpeg_q_data { * @out_q: source (output) queue information * @cap_fmt: destination (capture) queue queue information * @hdr_parsed: set if header has been parsed during decompression + * @ctrl_handler: controls handler */ struct s5p_jpeg_ctx { struct s5p_jpeg *jpeg; unsigned int mode; - unsigned int compr_quality; + unsigned short compr_quality; + unsigned short restart_interval; + unsigned short subsampling; struct v4l2_m2m_ctx *m2m_ctx; struct s5p_jpeg_q_data out_q; struct s5p_jpeg_q_data cap_q; + struct v4l2_fh fh; bool hdr_parsed; + struct v4l2_ctrl_handler ctrl_handler; }; /** diff --git a/drivers/media/video/s5p-jpeg/jpeg-hw.h b/drivers/media/video/s5p-jpeg/jpeg-hw.h index e10c744e9f23..f12f0fdbde7c 100644 --- a/drivers/media/video/s5p-jpeg/jpeg-hw.h +++ b/drivers/media/video/s5p-jpeg/jpeg-hw.h @@ -13,6 +13,7 @@ #define JPEG_HW_H_ #include <linux/io.h> +#include <linux/videodev2.h> #include "jpeg-hw.h" #include "jpeg-regs.h" @@ -25,8 +26,6 @@ #define S5P_JPEG_DECODE 1 #define S5P_JPEG_RAW_IN_565 0 #define S5P_JPEG_RAW_IN_422 1 -#define S5P_JPEG_SUBSAMPLING_422 0 -#define S5P_JPEG_SUBSAMPLING_420 1 #define S5P_JPEG_RAW_OUT_422 0 #define S5P_JPEG_RAW_OUT_420 1 @@ -91,21 +90,26 @@ static inline void jpeg_proc_mode(void __iomem *regs, unsigned long mode) writel(reg, regs + S5P_JPGMOD); } -static inline void jpeg_subsampling_mode(void __iomem *regs, unsigned long mode) +static inline void jpeg_subsampling_mode(void __iomem *regs, unsigned int mode) { unsigned long reg, m; - m = S5P_SUBSAMPLING_MODE_422; - if (mode == S5P_JPEG_SUBSAMPLING_422) - m = S5P_SUBSAMPLING_MODE_422; - else if (mode == S5P_JPEG_SUBSAMPLING_420) + if (mode == V4L2_JPEG_CHROMA_SUBSAMPLING_420) m = S5P_SUBSAMPLING_MODE_420; + else + m = S5P_SUBSAMPLING_MODE_422; + reg = readl(regs + S5P_JPGMOD); reg &= ~S5P_SUBSAMPLING_MODE_MASK; reg |= m; writel(reg, regs + S5P_JPGMOD); } +static inline unsigned int jpeg_get_subsampling_mode(void __iomem *regs) +{ + return readl(regs + S5P_JPGMOD) & S5P_SUBSAMPLING_MODE_MASK; +} + static inline void jpeg_dri(void __iomem *regs, unsigned int dri) { unsigned long reg; diff --git a/drivers/media/video/s5p-mfc/s5p_mfc_pm.c b/drivers/media/video/s5p-mfc/s5p_mfc_pm.c index f6a3035c4fb7..738a607be43c 100644 --- a/drivers/media/video/s5p-mfc/s5p_mfc_pm.c +++ b/drivers/media/video/s5p-mfc/s5p_mfc_pm.c @@ -41,15 +41,29 @@ int s5p_mfc_init_pm(struct s5p_mfc_dev *dev) pm->clock_gate = clk_get(&dev->plat_dev->dev, MFC_GATE_CLK_NAME); if (IS_ERR(pm->clock_gate)) { mfc_err("Failed to get clock-gating control\n"); - ret = -ENOENT; + ret = PTR_ERR(pm->clock_gate); goto err_g_ip_clk; } + + ret = clk_prepare(pm->clock_gate); + if (ret) { + mfc_err("Failed to preapre clock-gating control\n"); + goto err_p_ip_clk; + } + pm->clock = clk_get(&dev->plat_dev->dev, MFC_CLKNAME); if (IS_ERR(pm->clock)) { mfc_err("Failed to get MFC clock\n"); - ret = -ENOENT; + ret = PTR_ERR(pm->clock); goto err_g_ip_clk_2; } + + ret = clk_prepare(pm->clock); + if (ret) { + mfc_err("Failed to prepare MFC clock\n"); + goto err_p_ip_clk_2; + } + atomic_set(&pm->power, 0); #ifdef CONFIG_PM_RUNTIME pm->device = &dev->plat_dev->dev; @@ -59,7 +73,11 @@ int s5p_mfc_init_pm(struct s5p_mfc_dev *dev) atomic_set(&clk_ref, 0); #endif return 0; +err_p_ip_clk_2: + clk_put(pm->clock); err_g_ip_clk_2: + clk_unprepare(pm->clock_gate); +err_p_ip_clk: clk_put(pm->clock_gate); err_g_ip_clk: return ret; @@ -67,7 +85,9 @@ err_g_ip_clk: void s5p_mfc_final_pm(struct s5p_mfc_dev *dev) { + clk_unprepare(pm->clock_gate); clk_put(pm->clock_gate); + clk_unprepare(pm->clock); clk_put(pm->clock); #ifdef CONFIG_PM_RUNTIME pm_runtime_disable(pm->device); diff --git a/drivers/media/video/s5p-tv/Kconfig b/drivers/media/video/s5p-tv/Kconfig index f2a09779ec8f..f248b2856720 100644 --- a/drivers/media/video/s5p-tv/Kconfig +++ b/drivers/media/video/s5p-tv/Kconfig @@ -46,6 +46,16 @@ config VIDEO_SAMSUNG_S5P_HDMIPHY as module. It is an I2C driver, that exposes a V4L2 subdev for use by other drivers. +config VIDEO_SAMSUNG_S5P_SII9234 + tristate "Samsung SII9234 Driver" + depends on VIDEO_DEV && VIDEO_V4L2 && I2C + depends on VIDEO_SAMSUNG_S5P_TV + help + Say Y here if you want support for the MHL interface + in S5P Samsung SoC. The driver can be compiled + as module. It is an I2C driver, that exposes a V4L2 + subdev for use by other drivers. + config VIDEO_SAMSUNG_S5P_SDO tristate "Samsung Analog TV Driver" depends on VIDEO_DEV && VIDEO_V4L2 diff --git a/drivers/media/video/s5p-tv/Makefile b/drivers/media/video/s5p-tv/Makefile index 37e4c17663b4..f49e756a2fde 100644 --- a/drivers/media/video/s5p-tv/Makefile +++ b/drivers/media/video/s5p-tv/Makefile @@ -8,6 +8,8 @@ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMIPHY) += s5p-hdmiphy.o s5p-hdmiphy-y += hdmiphy_drv.o +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_SII9234) += s5p-sii9234.o +s5p-sii9234-y += sii9234_drv.o obj-$(CONFIG_VIDEO_SAMSUNG_S5P_HDMI) += s5p-hdmi.o s5p-hdmi-y += hdmi_drv.o obj-$(CONFIG_VIDEO_SAMSUNG_S5P_SDO) += s5p-sdo.o diff --git a/drivers/media/video/s5p-tv/hdmi_drv.c b/drivers/media/video/s5p-tv/hdmi_drv.c index 8b41a0410ab1..4865d25a0e57 100644 --- a/drivers/media/video/s5p-tv/hdmi_drv.c +++ b/drivers/media/video/s5p-tv/hdmi_drv.c @@ -30,6 +30,7 @@ #include <linux/clk.h> #include <linux/regulator/consumer.h> +#include <media/s5p_hdmi.h> #include <media/v4l2-common.h> #include <media/v4l2-dev.h> #include <media/v4l2-device.h> @@ -66,6 +67,8 @@ struct hdmi_device { struct v4l2_device v4l2_dev; /** subdev of HDMIPHY interface */ struct v4l2_subdev *phy_sd; + /** subdev of MHL interface */ + struct v4l2_subdev *mhl_sd; /** configuration of current graphic mode */ const struct hdmi_preset_conf *cur_conf; /** current preset */ @@ -74,10 +77,6 @@ struct hdmi_device { struct hdmi_resources res; }; -struct hdmi_driver_data { - int hdmiphy_bus; -}; - struct hdmi_tg_regs { u8 cmd; u8 h_fsz_l; @@ -129,23 +128,11 @@ struct hdmi_preset_conf { struct v4l2_mbus_framefmt mbus_fmt; }; -/* I2C module and id for HDMIPHY */ -static struct i2c_board_info hdmiphy_info = { - I2C_BOARD_INFO("hdmiphy", 0x38), -}; - -static struct hdmi_driver_data hdmi_driver_data[] = { - { .hdmiphy_bus = 3 }, - { .hdmiphy_bus = 8 }, -}; - static struct platform_device_id hdmi_driver_types[] = { { .name = "s5pv210-hdmi", - .driver_data = (unsigned long)&hdmi_driver_data[0], }, { .name = "exynos4-hdmi", - .driver_data = (unsigned long)&hdmi_driver_data[1], }, { /* end node */ } @@ -587,7 +574,15 @@ static int hdmi_streamon(struct hdmi_device *hdev) if (tries == 0) { dev_err(dev, "hdmiphy's pll could not reach steady state.\n"); v4l2_subdev_call(hdev->phy_sd, video, s_stream, 0); - hdmi_dumpregs(hdev, "s_stream"); + hdmi_dumpregs(hdev, "hdmiphy - s_stream"); + return -EIO; + } + + /* starting MHL */ + ret = v4l2_subdev_call(hdev->mhl_sd, video, s_stream, 1); + if (hdev->mhl_sd && ret) { + v4l2_subdev_call(hdev->phy_sd, video, s_stream, 0); + hdmi_dumpregs(hdev, "mhl - s_stream"); return -EIO; } @@ -618,6 +613,7 @@ static int hdmi_streamoff(struct hdmi_device *hdev) clk_set_parent(res->sclk_hdmi, res->sclk_pixel); clk_enable(res->sclk_hdmi); + v4l2_subdev_call(hdev->mhl_sd, video, s_stream, 0); v4l2_subdev_call(hdev->phy_sd, video, s_stream, 0); hdmi_dumpregs(hdev, "streamoff"); @@ -739,6 +735,7 @@ static int hdmi_runtime_suspend(struct device *dev) struct hdmi_device *hdev = sd_to_hdmi_dev(sd); dev_dbg(dev, "%s\n", __func__); + v4l2_subdev_call(hdev->mhl_sd, core, s_power, 0); hdmi_resource_poweroff(&hdev->res); return 0; } @@ -757,6 +754,11 @@ static int hdmi_runtime_resume(struct device *dev) if (ret) goto fail; + /* starting MHL */ + ret = v4l2_subdev_call(hdev->mhl_sd, core, s_power, 1); + if (hdev->mhl_sd && ret) + goto fail; + dev_dbg(dev, "poweron succeed\n"); return 0; @@ -867,15 +869,21 @@ static int __devinit hdmi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res; - struct i2c_adapter *phy_adapter; + struct i2c_adapter *adapter; struct v4l2_subdev *sd; struct hdmi_device *hdmi_dev = NULL; - struct hdmi_driver_data *drv_data; + struct s5p_hdmi_platform_data *pdata = dev->platform_data; int ret; dev_dbg(dev, "probe start\n"); - hdmi_dev = kzalloc(sizeof(*hdmi_dev), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "platform data is missing\n"); + ret = -ENODEV; + goto fail; + } + + hdmi_dev = devm_kzalloc(&pdev->dev, sizeof(*hdmi_dev), GFP_KERNEL); if (!hdmi_dev) { dev_err(dev, "out of memory\n"); ret = -ENOMEM; @@ -886,7 +894,7 @@ static int __devinit hdmi_probe(struct platform_device *pdev) ret = hdmi_resources_init(hdmi_dev); if (ret) - goto fail_hdev; + goto fail; /* mapping HDMI registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -896,24 +904,26 @@ static int __devinit hdmi_probe(struct platform_device *pdev) goto fail_init; } - hdmi_dev->regs = ioremap(res->start, resource_size(res)); + hdmi_dev->regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); if (hdmi_dev->regs == NULL) { dev_err(dev, "register mapping failed.\n"); ret = -ENXIO; - goto fail_hdev; + goto fail_init; } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res == NULL) { dev_err(dev, "get interrupt resource failed.\n"); ret = -ENXIO; - goto fail_regs; + goto fail_init; } - ret = request_irq(res->start, hdmi_irq_handler, 0, "hdmi", hdmi_dev); + ret = devm_request_irq(&pdev->dev, res->start, hdmi_irq_handler, 0, + "hdmi", hdmi_dev); if (ret) { dev_err(dev, "request interrupt failed.\n"); - goto fail_regs; + goto fail_init; } hdmi_dev->irq = res->start; @@ -924,28 +934,54 @@ static int __devinit hdmi_probe(struct platform_device *pdev) ret = v4l2_device_register(NULL, &hdmi_dev->v4l2_dev); if (ret) { dev_err(dev, "could not register v4l2 device.\n"); - goto fail_irq; + goto fail_init; } - drv_data = (struct hdmi_driver_data *) - platform_get_device_id(pdev)->driver_data; - phy_adapter = i2c_get_adapter(drv_data->hdmiphy_bus); - if (phy_adapter == NULL) { - dev_err(dev, "adapter request failed\n"); + /* testing if hdmiphy info is present */ + if (!pdata->hdmiphy_info) { + dev_err(dev, "hdmiphy info is missing in platform data\n"); + ret = -ENXIO; + goto fail_vdev; + } + + adapter = i2c_get_adapter(pdata->hdmiphy_bus); + if (adapter == NULL) { + dev_err(dev, "hdmiphy adapter request failed\n"); ret = -ENXIO; goto fail_vdev; } hdmi_dev->phy_sd = v4l2_i2c_new_subdev_board(&hdmi_dev->v4l2_dev, - phy_adapter, &hdmiphy_info, NULL); + adapter, pdata->hdmiphy_info, NULL); /* on failure or not adapter is no longer useful */ - i2c_put_adapter(phy_adapter); + i2c_put_adapter(adapter); if (hdmi_dev->phy_sd == NULL) { dev_err(dev, "missing subdev for hdmiphy\n"); ret = -ENODEV; goto fail_vdev; } + /* initialization of MHL interface if present */ + if (pdata->mhl_info) { + adapter = i2c_get_adapter(pdata->mhl_bus); + if (adapter == NULL) { + dev_err(dev, "MHL adapter request failed\n"); + ret = -ENXIO; + goto fail_vdev; + } + + hdmi_dev->mhl_sd = v4l2_i2c_new_subdev_board( + &hdmi_dev->v4l2_dev, adapter, + pdata->mhl_info, NULL); + /* on failure or not adapter is no longer useful */ + i2c_put_adapter(adapter); + if (hdmi_dev->mhl_sd == NULL) { + dev_err(dev, "missing subdev for MHL\n"); + ret = -ENODEV; + goto fail_vdev; + } + } + clk_enable(hdmi_dev->res.hdmi); pm_runtime_enable(dev); @@ -962,25 +998,16 @@ static int __devinit hdmi_probe(struct platform_device *pdev) /* storing subdev for call that have only access to struct device */ dev_set_drvdata(dev, sd); - dev_info(dev, "probe sucessful\n"); + dev_info(dev, "probe successful\n"); return 0; fail_vdev: v4l2_device_unregister(&hdmi_dev->v4l2_dev); -fail_irq: - free_irq(hdmi_dev->irq, hdmi_dev); - -fail_regs: - iounmap(hdmi_dev->regs); - fail_init: hdmi_resources_cleanup(hdmi_dev); -fail_hdev: - kfree(hdmi_dev); - fail: dev_err(dev, "probe failed\n"); return ret; @@ -996,11 +1023,8 @@ static int __devexit hdmi_remove(struct platform_device *pdev) clk_disable(hdmi_dev->res.hdmi); v4l2_device_unregister(&hdmi_dev->v4l2_dev); disable_irq(hdmi_dev->irq); - free_irq(hdmi_dev->irq, hdmi_dev); - iounmap(hdmi_dev->regs); hdmi_resources_cleanup(hdmi_dev); - kfree(hdmi_dev); - dev_info(dev, "remove sucessful\n"); + dev_info(dev, "remove successful\n"); return 0; } diff --git a/drivers/media/video/s5p-tv/hdmiphy_drv.c b/drivers/media/video/s5p-tv/hdmiphy_drv.c index 6693f4aff108..0afef77747e5 100644 --- a/drivers/media/video/s5p-tv/hdmiphy_drv.c +++ b/drivers/media/video/s5p-tv/hdmiphy_drv.c @@ -175,14 +175,4 @@ static struct i2c_driver hdmiphy_driver = { .id_table = hdmiphy_id, }; -static int __init hdmiphy_init(void) -{ - return i2c_add_driver(&hdmiphy_driver); -} -module_init(hdmiphy_init); - -static void __exit hdmiphy_exit(void) -{ - i2c_del_driver(&hdmiphy_driver); -} -module_exit(hdmiphy_exit); +module_i2c_driver(hdmiphy_driver); diff --git a/drivers/media/video/s5p-tv/mixer_drv.c b/drivers/media/video/s5p-tv/mixer_drv.c index 00643094b221..a2c0c25ad130 100644 --- a/drivers/media/video/s5p-tv/mixer_drv.c +++ b/drivers/media/video/s5p-tv/mixer_drv.c @@ -444,7 +444,7 @@ static int __devexit mxr_remove(struct platform_device *pdev) kfree(mdev); - dev_info(dev, "remove sucessful\n"); + dev_info(dev, "remove successful\n"); return 0; } diff --git a/drivers/media/video/s5p-tv/sdo_drv.c b/drivers/media/video/s5p-tv/sdo_drv.c index 059e7749ce95..f6bca2c20e89 100644 --- a/drivers/media/video/s5p-tv/sdo_drv.c +++ b/drivers/media/video/s5p-tv/sdo_drv.c @@ -301,7 +301,7 @@ static int __devinit sdo_probe(struct platform_device *pdev) struct clk *sclk_vpll; dev_info(dev, "probe start\n"); - sdev = kzalloc(sizeof *sdev, GFP_KERNEL); + sdev = devm_kzalloc(&pdev->dev, sizeof *sdev, GFP_KERNEL); if (!sdev) { dev_err(dev, "not enough memory.\n"); ret = -ENOMEM; @@ -314,14 +314,14 @@ static int __devinit sdo_probe(struct platform_device *pdev) if (res == NULL) { dev_err(dev, "get memory resource failed.\n"); ret = -ENXIO; - goto fail_sdev; + goto fail; } - sdev->regs = ioremap(res->start, resource_size(res)); + sdev->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (sdev->regs == NULL) { dev_err(dev, "register mapping failed.\n"); ret = -ENXIO; - goto fail_sdev; + goto fail; } /* acquiring interrupt */ @@ -329,12 +329,13 @@ static int __devinit sdo_probe(struct platform_device *pdev) if (res == NULL) { dev_err(dev, "get interrupt resource failed.\n"); ret = -ENXIO; - goto fail_regs; + goto fail; } - ret = request_irq(res->start, sdo_irq_handler, 0, "s5p-sdo", sdev); + ret = devm_request_irq(&pdev->dev, res->start, sdo_irq_handler, 0, + "s5p-sdo", sdev); if (ret) { dev_err(dev, "request interrupt failed.\n"); - goto fail_regs; + goto fail; } sdev->irq = res->start; @@ -343,7 +344,7 @@ static int __devinit sdo_probe(struct platform_device *pdev) if (IS_ERR_OR_NULL(sdev->sclk_dac)) { dev_err(dev, "failed to get clock 'sclk_dac'\n"); ret = -ENXIO; - goto fail_irq; + goto fail; } sdev->dac = clk_get(dev, "dac"); if (IS_ERR_OR_NULL(sdev->dac)) { @@ -415,12 +416,6 @@ fail_dac: clk_put(sdev->dac); fail_sclk_dac: clk_put(sdev->sclk_dac); -fail_irq: - free_irq(sdev->irq, sdev); -fail_regs: - iounmap(sdev->regs); -fail_sdev: - kfree(sdev); fail: dev_info(dev, "probe failed\n"); return ret; @@ -439,9 +434,6 @@ static int __devexit sdo_remove(struct platform_device *pdev) clk_put(sdev->dacphy); clk_put(sdev->dac); clk_put(sdev->sclk_dac); - free_irq(sdev->irq, sdev); - iounmap(sdev->regs); - kfree(sdev); dev_info(&pdev->dev, "remove successful\n"); return 0; diff --git a/drivers/media/video/s5p-tv/sii9234_drv.c b/drivers/media/video/s5p-tv/sii9234_drv.c new file mode 100644 index 000000000000..0f31eccd7b80 --- /dev/null +++ b/drivers/media/video/s5p-tv/sii9234_drv.c @@ -0,0 +1,432 @@ +/* + * Samsung MHL interface driver + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Author: Tomasz Stanislawski <t.stanislaws@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/delay.h> +#include <linux/err.h> +#include <linux/freezer.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/machine.h> +#include <linux/slab.h> + +#include <mach/gpio.h> +#include <plat/gpio-cfg.h> + +#include <media/sii9234.h> +#include <media/v4l2-subdev.h> + +MODULE_AUTHOR("Tomasz Stanislawski <t.stanislaws@samsung.com>"); +MODULE_DESCRIPTION("Samsung MHL interface driver"); +MODULE_LICENSE("GPL"); + +struct sii9234_context { + struct i2c_client *client; + struct regulator *power; + int gpio_n_reset; + struct v4l2_subdev sd; +}; + +static inline struct sii9234_context *sd_to_context(struct v4l2_subdev *sd) +{ + return container_of(sd, struct sii9234_context, sd); +} + +static inline int sii9234_readb(struct i2c_client *client, int addr) +{ + return i2c_smbus_read_byte_data(client, addr); +} + +static inline int sii9234_writeb(struct i2c_client *client, int addr, int value) +{ + return i2c_smbus_write_byte_data(client, addr, value); +} + +static inline int sii9234_writeb_mask(struct i2c_client *client, int addr, + int value, int mask) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, addr); + if (ret < 0) + return ret; + ret = (ret & ~mask) | (value & mask); + return i2c_smbus_write_byte_data(client, addr, ret); +} + +static inline int sii9234_readb_idx(struct i2c_client *client, int addr) +{ + int ret; + ret = i2c_smbus_write_byte_data(client, 0xbc, addr >> 8); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, 0xbd, addr & 0xff); + if (ret < 0) + return ret; + return i2c_smbus_read_byte_data(client, 0xbe); +} + +static inline int sii9234_writeb_idx(struct i2c_client *client, int addr, + int value) +{ + int ret; + ret = i2c_smbus_write_byte_data(client, 0xbc, addr >> 8); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, 0xbd, addr & 0xff); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, 0xbe, value); + return ret; +} + +static inline int sii9234_writeb_idx_mask(struct i2c_client *client, int addr, + int value, int mask) +{ + int ret; + + ret = sii9234_readb_idx(client, addr); + if (ret < 0) + return ret; + ret = (ret & ~mask) | (value & mask); + return sii9234_writeb_idx(client, addr, ret); +} + +static int sii9234_reset(struct sii9234_context *ctx) +{ + struct i2c_client *client = ctx->client; + struct device *dev = &client->dev; + int ret, tries; + + gpio_direction_output(ctx->gpio_n_reset, 1); + mdelay(1); + gpio_direction_output(ctx->gpio_n_reset, 0); + mdelay(1); + gpio_direction_output(ctx->gpio_n_reset, 1); + mdelay(1); + + /* going to TTPI mode */ + ret = sii9234_writeb(client, 0xc7, 0); + if (ret < 0) { + dev_err(dev, "failed to set TTPI mode\n"); + return ret; + } + for (tries = 0; tries < 100 ; ++tries) { + ret = sii9234_readb(client, 0x1b); + if (ret > 0) + break; + if (ret < 0) { + dev_err(dev, "failed to reset device\n"); + return -EIO; + } + mdelay(1); + } + if (tries == 100) { + dev_err(dev, "maximal number of tries reached\n"); + return -EIO; + } + + return 0; +} + +static int sii9234_verify_version(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int family, rev, tpi_rev, dev_id, sub_id, hdcp, id; + + family = sii9234_readb(client, 0x1b); + rev = sii9234_readb(client, 0x1c) & 0x0f; + tpi_rev = sii9234_readb(client, 0x1d) & 0x7f; + dev_id = sii9234_readb_idx(client, 0x0103); + sub_id = sii9234_readb_idx(client, 0x0102); + hdcp = sii9234_readb(client, 0x30); + + if (family < 0 || rev < 0 || tpi_rev < 0 || dev_id < 0 || + sub_id < 0 || hdcp < 0) { + dev_err(dev, "failed to read chip's version\n"); + return -EIO; + } + + id = (dev_id << 8) | sub_id; + + dev_info(dev, "chip: SiL%02x family: %02x, rev: %02x\n", + id, family, rev); + dev_info(dev, "tpi_rev:%02x, hdcp: %02x\n", tpi_rev, hdcp); + if (id != 0x9234) { + dev_err(dev, "not supported chip\n"); + return -ENODEV; + } + + return 0; +} + +static u8 data[][3] = { +/* setup from driver created by doonsoo45.kim */ + { 0x01, 0x05, 0x04 }, /* Enable Auto soft reset on SCDT = 0 */ + { 0x01, 0x08, 0x35 }, /* Power Up TMDS Tx Core */ + { 0x01, 0x0d, 0x1c }, /* HDMI Transcode mode enable */ + { 0x01, 0x2b, 0x01 }, /* Enable HDCP Compliance workaround */ + { 0x01, 0x79, 0x40 }, /* daniel test...MHL_INT */ + { 0x01, 0x80, 0x34 }, /* Enable Rx PLL Clock Value */ + { 0x01, 0x90, 0x27 }, /* Enable CBUS discovery */ + { 0x01, 0x91, 0xe5 }, /* Skip RGND detection */ + { 0x01, 0x92, 0x46 }, /* Force MHD mode */ + { 0x01, 0x93, 0xdc }, /* Disable CBUS pull-up during RGND measurement */ + { 0x01, 0x94, 0x66 }, /* 1.8V CBUS VTH & GND threshold */ + { 0x01, 0x95, 0x31 }, /* RGND block & single discovery attempt */ + { 0x01, 0x96, 0x22 }, /* use 1K and 2K setting */ + { 0x01, 0xa0, 0x10 }, /* SIMG: Term mode */ + { 0x01, 0xa1, 0xfc }, /* Disable internal Mobile HD driver */ + { 0x01, 0xa3, 0xfa }, /* SIMG: Output Swing default EB, 3x Clk Mult */ + { 0x01, 0xa5, 0x80 }, /* SIMG: RGND Hysterisis, 3x mode for Beast */ + { 0x01, 0xa6, 0x0c }, /* SIMG: Swing Offset */ + { 0x02, 0x3d, 0x3f }, /* Power up CVCC 1.2V core */ + { 0x03, 0x00, 0x00 }, /* SIMG: correcting HW default */ + { 0x03, 0x11, 0x01 }, /* Enable TxPLL Clock */ + { 0x03, 0x12, 0x15 }, /* Enable Tx Clock Path & Equalizer */ + { 0x03, 0x13, 0x60 }, /* SIMG: Set termination value */ + { 0x03, 0x14, 0xf0 }, /* SIMG: Change CKDT level */ + { 0x03, 0x17, 0x07 }, /* SIMG: PLL Calrefsel */ + { 0x03, 0x1a, 0x20 }, /* VCO Cal */ + { 0x03, 0x22, 0xe0 }, /* SIMG: Auto EQ */ + { 0x03, 0x23, 0xc0 }, /* SIMG: Auto EQ */ + { 0x03, 0x24, 0xa0 }, /* SIMG: Auto EQ */ + { 0x03, 0x25, 0x80 }, /* SIMG: Auto EQ */ + { 0x03, 0x26, 0x60 }, /* SIMG: Auto EQ */ + { 0x03, 0x27, 0x40 }, /* SIMG: Auto EQ */ + { 0x03, 0x28, 0x20 }, /* SIMG: Auto EQ */ + { 0x03, 0x29, 0x00 }, /* SIMG: Auto EQ */ + { 0x03, 0x31, 0x0b }, /* SIMG: Rx PLL BW value from I2C BW ~ 4MHz */ + { 0x03, 0x45, 0x06 }, /* SIMG: DPLL Mode */ + { 0x03, 0x4b, 0x06 }, /* SIMG: Correcting HW default */ + { 0x03, 0x4c, 0xa0 }, /* Manual zone control */ + { 0x03, 0x4d, 0x02 }, /* SIMG: PLL Mode Value (order is important) */ +}; + +static int sii9234_set_internal(struct sii9234_context *ctx) +{ + struct i2c_client *client = ctx->client; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(data); ++i) { + int addr = (data[i][0] << 8) | data[i][1]; + ret = sii9234_writeb_idx(client, addr, data[i][2]); + if (ret < 0) + return ret; + } + return 0; +} + +static int sii9234_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct sii9234_context *ctx = sd_to_context(sd); + struct i2c_client *client = ctx->client; + + dev_info(dev, "suspend start\n"); + + sii9234_writeb_mask(client, 0x1e, 3, 3); + regulator_disable(ctx->power); + + return 0; +} + +static int sii9234_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct sii9234_context *ctx = sd_to_context(sd); + struct i2c_client *client = ctx->client; + int ret; + + dev_info(dev, "resume start\n"); + regulator_enable(ctx->power); + + ret = sii9234_reset(ctx); + if (ret) + goto fail; + + /* enable tpi */ + ret = sii9234_writeb_mask(client, 0x1e, 1, 0); + if (ret < 0) + goto fail; + ret = sii9234_set_internal(ctx); + if (ret < 0) + goto fail; + + return 0; + +fail: + dev_err(dev, "failed to resume\n"); + regulator_disable(ctx->power); + + return ret; +} + +static const struct dev_pm_ops sii9234_pm_ops = { + .runtime_suspend = sii9234_runtime_suspend, + .runtime_resume = sii9234_runtime_resume, +}; + +static int sii9234_s_power(struct v4l2_subdev *sd, int on) +{ + struct sii9234_context *ctx = sd_to_context(sd); + int ret; + + if (on) + ret = pm_runtime_get_sync(&ctx->client->dev); + else + ret = pm_runtime_put(&ctx->client->dev); + /* only values < 0 indicate errors */ + return IS_ERR_VALUE(ret) ? ret : 0; +} + +static int sii9234_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct sii9234_context *ctx = sd_to_context(sd); + + /* (dis/en)able TDMS output */ + sii9234_writeb_mask(ctx->client, 0x1a, enable ? 0 : ~0 , 1 << 4); + return 0; +} + +static const struct v4l2_subdev_core_ops sii9234_core_ops = { + .s_power = sii9234_s_power, +}; + +static const struct v4l2_subdev_video_ops sii9234_video_ops = { + .s_stream = sii9234_s_stream, +}; + +static const struct v4l2_subdev_ops sii9234_ops = { + .core = &sii9234_core_ops, + .video = &sii9234_video_ops, +}; + +static int __devinit sii9234_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct sii9234_platform_data *pdata = dev->platform_data; + struct sii9234_context *ctx; + int ret; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + dev_err(dev, "out of memory\n"); + ret = -ENOMEM; + goto fail; + } + ctx->client = client; + + ctx->power = regulator_get(dev, "hdmi-en"); + if (IS_ERR(ctx->power)) { + dev_err(dev, "failed to acquire regulator hdmi-en\n"); + ret = PTR_ERR(ctx->power); + goto fail_ctx; + } + + ctx->gpio_n_reset = pdata->gpio_n_reset; + ret = gpio_request(ctx->gpio_n_reset, "MHL_RST"); + if (ret) { + dev_err(dev, "failed to acquire MHL_RST gpio\n"); + goto fail_power; + } + + v4l2_i2c_subdev_init(&ctx->sd, client, &sii9234_ops); + + pm_runtime_enable(dev); + + /* enable device */ + ret = pm_runtime_get_sync(dev); + if (ret) + goto fail_pm; + + /* verify chip version */ + ret = sii9234_verify_version(client); + if (ret) + goto fail_pm_get; + + /* stop processing */ + pm_runtime_put(dev); + + dev_info(dev, "probe successful\n"); + + return 0; + +fail_pm_get: + pm_runtime_put_sync(dev); + +fail_pm: + pm_runtime_disable(dev); + gpio_free(ctx->gpio_n_reset); + +fail_power: + regulator_put(ctx->power); + +fail_ctx: + kfree(ctx); + +fail: + dev_err(dev, "probe failed\n"); + + return ret; +} + +static int __devexit sii9234_remove(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct sii9234_context *ctx = sd_to_context(sd); + + pm_runtime_disable(dev); + gpio_free(ctx->gpio_n_reset); + regulator_put(ctx->power); + kfree(ctx); + + dev_info(dev, "remove successful\n"); + + return 0; +} + + +static const struct i2c_device_id sii9234_id[] = { + { "SII9234", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, sii9234_id); +static struct i2c_driver sii9234_driver = { + .driver = { + .name = "sii9234", + .owner = THIS_MODULE, + .pm = &sii9234_pm_ops, + }, + .probe = sii9234_probe, + .remove = __devexit_p(sii9234_remove), + .id_table = sii9234_id, +}; + +static int __init sii9234_init(void) +{ + return i2c_add_driver(&sii9234_driver); +} +module_init(sii9234_init); + +static void __exit sii9234_exit(void) +{ + i2c_del_driver(&sii9234_driver); +} +module_exit(sii9234_exit); diff --git a/drivers/media/video/saa6588.c b/drivers/media/video/saa6588.c index 99a2ac16f9e5..0caac50d7cf4 100644 --- a/drivers/media/video/saa6588.c +++ b/drivers/media/video/saa6588.c @@ -539,15 +539,4 @@ static struct i2c_driver saa6588_driver = { .id_table = saa6588_id, }; -static __init int init_saa6588(void) -{ - return i2c_add_driver(&saa6588_driver); -} - -static __exit void exit_saa6588(void) -{ - i2c_del_driver(&saa6588_driver); -} - -module_init(init_saa6588); -module_exit(exit_saa6588); +module_i2c_driver(saa6588_driver); diff --git a/drivers/media/video/saa7110.c b/drivers/media/video/saa7110.c index 99664205ef4e..51cd4c8f0520 100644 --- a/drivers/media/video/saa7110.c +++ b/drivers/media/video/saa7110.c @@ -491,15 +491,4 @@ static struct i2c_driver saa7110_driver = { .id_table = saa7110_id, }; -static __init int init_saa7110(void) -{ - return i2c_add_driver(&saa7110_driver); -} - -static __exit void exit_saa7110(void) -{ - i2c_del_driver(&saa7110_driver); -} - -module_init(init_saa7110); -module_exit(exit_saa7110); +module_i2c_driver(saa7110_driver); diff --git a/drivers/media/video/saa7115.c b/drivers/media/video/saa7115.c index 0ef5484696b6..2107336cd836 100644 --- a/drivers/media/video/saa7115.c +++ b/drivers/media/video/saa7115.c @@ -1724,15 +1724,4 @@ static struct i2c_driver saa711x_driver = { .id_table = saa711x_id, }; -static __init int init_saa711x(void) -{ - return i2c_add_driver(&saa711x_driver); -} - -static __exit void exit_saa711x(void) -{ - i2c_del_driver(&saa711x_driver); -} - -module_init(init_saa711x); -module_exit(exit_saa711x); +module_i2c_driver(saa711x_driver); diff --git a/drivers/media/video/saa7127.c b/drivers/media/video/saa7127.c index ad964616c9d2..39c90b08eea8 100644 --- a/drivers/media/video/saa7127.c +++ b/drivers/media/video/saa7127.c @@ -852,15 +852,4 @@ static struct i2c_driver saa7127_driver = { .id_table = saa7127_id, }; -static __init int init_saa7127(void) -{ - return i2c_add_driver(&saa7127_driver); -} - -static __exit void exit_saa7127(void) -{ - i2c_del_driver(&saa7127_driver); -} - -module_init(init_saa7127); -module_exit(exit_saa7127); +module_i2c_driver(saa7127_driver); diff --git a/drivers/media/video/saa7134/Makefile b/drivers/media/video/saa7134/Makefile index a646ccf51696..da3899329f52 100644 --- a/drivers/media/video/saa7134/Makefile +++ b/drivers/media/video/saa7134/Makefile @@ -10,7 +10,7 @@ obj-$(CONFIG_VIDEO_SAA7134_ALSA) += saa7134-alsa.o obj-$(CONFIG_VIDEO_SAA7134_DVB) += saa7134-dvb.o -ccflags-y += -Idrivers/media/video -ccflags-y += -Idrivers/media/common/tuners -ccflags-y += -Idrivers/media/dvb/dvb-core -ccflags-y += -Idrivers/media/dvb/frontends +ccflags-y += -I$(srctree)/drivers/media/video +ccflags-y += -I$(srctree)/drivers/media/common/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb/frontends diff --git a/drivers/media/video/saa7134/saa6752hs.c b/drivers/media/video/saa7134/saa6752hs.c index f9f29cc93a8a..f147b05bd860 100644 --- a/drivers/media/video/saa7134/saa6752hs.c +++ b/drivers/media/video/saa7134/saa6752hs.c @@ -1001,18 +1001,7 @@ static struct i2c_driver saa6752hs_driver = { .id_table = saa6752hs_id, }; -static __init int init_saa6752hs(void) -{ - return i2c_add_driver(&saa6752hs_driver); -} - -static __exit void exit_saa6752hs(void) -{ - i2c_del_driver(&saa6752hs_driver); -} - -module_init(init_saa6752hs); -module_exit(exit_saa6752hs); +module_i2c_driver(saa6752hs_driver); /* * Overrides for Emacs so that we follow Linus's tabbing style. diff --git a/drivers/media/video/saa7134/saa7134-cards.c b/drivers/media/video/saa7134/saa7134-cards.c index 065d0f6be4a0..53aae5968ffb 100644 --- a/drivers/media/video/saa7134/saa7134-cards.c +++ b/drivers/media/video/saa7134/saa7134-cards.c @@ -33,6 +33,7 @@ #include "tea5767.h" #include "tda18271.h" #include "xc5000.h" +#include "s5h1411.h" /* commly used strings */ static char name_mute[] = "mute"; @@ -5712,6 +5713,36 @@ struct saa7134_board saa7134_boards[] = { .amux = LINE1, } }, }, + [SAA7134_BOARD_KWORLD_PC150U] = { + .name = "Kworld PC150-U", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 1 << 21, + .ts_type = SAA7134_MPEG_TS_PARALLEL, + .inputs = { { + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + }, { + .name = name_comp, + .vmux = 3, + .amux = LINE1, + }, { + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + } }, + .radio = { + .name = name_radio, + .amux = TV, + .gpio = 0x0000000, + }, + }, }; @@ -6306,6 +6337,12 @@ struct pci_device_id saa7134_pci_tbl[] = { .driver_data = SAA7134_BOARD_KWORLD_ATSC110, /* ATSC 115 */ },{ .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, /* SAA7135HL */ + .subvendor = 0x17de, + .subdevice = 0xa134, + .driver_data = SAA7134_BOARD_KWORLD_PC150U, + }, { + .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7134, .subvendor = 0x1461, .subdevice = 0x7360, @@ -7134,6 +7171,23 @@ static inline int saa7134_kworld_sbtvd_toggle_agc(struct saa7134_dev *dev, return 0; } +static int saa7134_kworld_pc150u_toggle_agc(struct saa7134_dev *dev, + enum tda18271_mode mode) +{ + switch (mode) { + case TDA18271_ANALOG: + saa7134_set_gpio(dev, 18, 0); + break; + case TDA18271_DIGITAL: + saa7134_set_gpio(dev, 18, 1); + msleep(30); + break; + default: + return -EINVAL; + } + return 0; +} + static int saa7134_tda8290_18271_callback(struct saa7134_dev *dev, int command, int arg) { @@ -7150,6 +7204,9 @@ static int saa7134_tda8290_18271_callback(struct saa7134_dev *dev, case SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG: ret = saa7134_kworld_sbtvd_toggle_agc(dev, arg); break; + case SAA7134_BOARD_KWORLD_PC150U: + ret = saa7134_kworld_pc150u_toggle_agc(dev, arg); + break; default: break; } @@ -7171,6 +7228,7 @@ static int saa7134_tda8290_callback(struct saa7134_dev *dev, case SAA7134_BOARD_HAUPPAUGE_HVR1120: case SAA7134_BOARD_AVERMEDIA_M733A: case SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG: + case SAA7134_BOARD_KWORLD_PC150U: case SAA7134_BOARD_MAGICPRO_PROHDTV_PRO2: /* tda8290 + tda18271 */ ret = saa7134_tda8290_18271_callback(dev, command, arg); @@ -7452,6 +7510,7 @@ int saa7134_board_init1(struct saa7134_dev *dev) case SAA7134_BOARD_BEHOLD_X7: case SAA7134_BOARD_BEHOLD_H7: case SAA7134_BOARD_BEHOLD_A7: + case SAA7134_BOARD_KWORLD_PC150U: dev->has_remote = SAA7134_REMOTE_I2C; break; case SAA7134_BOARD_AVERMEDIA_A169_B: diff --git a/drivers/media/video/saa7134/saa7134-dvb.c b/drivers/media/video/saa7134/saa7134-dvb.c index 089fa0fb5c94..aaa5c97a7216 100644 --- a/drivers/media/video/saa7134/saa7134-dvb.c +++ b/drivers/media/video/saa7134/saa7134-dvb.c @@ -61,6 +61,7 @@ #include "zl10036.h" #include "zl10039.h" #include "mt312.h" +#include "s5h1411.h" MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); MODULE_LICENSE("GPL"); @@ -1158,6 +1159,33 @@ static struct tda18271_config prohdtv_pro2_tda18271_config = { .output_opt = TDA18271_OUTPUT_LT_OFF, }; +static struct tda18271_std_map kworld_tda18271_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37 }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37 }, +}; + +static struct tda18271_config kworld_pc150u_tda18271_config = { + .std_map = &kworld_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, + .config = 3, /* Use tuner callback for AGC */ + .rf_cal_on_startup = 1 +}; + +static struct s5h1411_config kworld_s5h1411_config = { + .output_mode = S5H1411_PARALLEL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .qam_if = S5H1411_IF_4000, + .vsb_if = S5H1411_IF_3250, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, + .mpeg_timing = + S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, +}; + + /* ================================================================== * Core code */ @@ -1438,6 +1466,22 @@ static int dvb_init(struct saa7134_dev *dev) &dev->i2c_adap, 0x61, TUNER_PHILIPS_TUV1236D); break; + case SAA7134_BOARD_KWORLD_PC150U: + saa7134_set_gpio(dev, 18, 1); /* Switch to digital mode */ + saa7134_tuner_callback(dev, 0, + TDA18271_CALLBACK_CMD_AGC_ENABLE, 1); + fe0->dvb.frontend = dvb_attach(s5h1411_attach, + &kworld_s5h1411_config, + &dev->i2c_adap); + if (fe0->dvb.frontend != NULL) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &kworld_pc150u_tda18271_config); + } + break; case SAA7134_BOARD_FLYDVBS_LR300: fe0->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, &dev->i2c_adap); diff --git a/drivers/media/video/saa7134/saa7134-i2c.c b/drivers/media/video/saa7134/saa7134-i2c.c index 2d3f6d265bbf..a176ec3285e0 100644 --- a/drivers/media/video/saa7134/saa7134-i2c.c +++ b/drivers/media/video/saa7134/saa7134-i2c.c @@ -254,7 +254,9 @@ static int saa7134_i2c_xfer(struct i2c_adapter *i2c_adap, addr = msgs[i].addr << 1; if (msgs[i].flags & I2C_M_RD) addr |= 1; - if (i > 0 && msgs[i].flags & I2C_M_RD && msgs[i].addr != 0x40) { + if (i > 0 && msgs[i].flags & + I2C_M_RD && msgs[i].addr != 0x40 && + msgs[i].addr != 0x19) { /* workaround for a saa7134 i2c bug * needed to talk to the mt352 demux * thanks to pinnacle for the hint */ @@ -279,6 +281,16 @@ static int saa7134_i2c_xfer(struct i2c_adapter *i2c_adap, d1printk("%02x", rc); msgs[i].buf[byte] = rc; } + /* discard mysterious extra byte when reading + from Samsung S5H1411. i2c bus gets error + if we do not. */ + if (0x19 == msgs[i].addr) { + d1printk(" ?"); + rc = i2c_recv_byte(dev); + if (rc < 0) + goto err; + d1printk("%02x", rc); + } } else { /* write bytes */ d2printk("write bytes\n"); diff --git a/drivers/media/video/saa7134/saa7134-input.c b/drivers/media/video/saa7134/saa7134-input.c index 22ecd7297d2d..48d2878699b7 100644 --- a/drivers/media/video/saa7134/saa7134-input.c +++ b/drivers/media/video/saa7134/saa7134-input.c @@ -210,6 +210,54 @@ static int get_key_msi_tvanywhere_plus(struct IR_i2c *ir, u32 *ir_key, return 1; } +/* copied and modified from get_key_msi_tvanywhere_plus() */ +static int get_key_kworld_pc150u(struct IR_i2c *ir, u32 *ir_key, + u32 *ir_raw) +{ + unsigned char b; + unsigned int gpio; + + /* <dev> is needed to access GPIO. Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c->adapter->algo_data; + if (dev == NULL) { + i2cdprintk("get_key_kworld_pc150u: " + "ir->c->adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + /* GPIO&0x100 is pulsed low when a button is pressed. Don't do + I2C receive if gpio&0x100 is not low. */ + + if (gpio & 0x100) + return 0; /* No button press */ + + /* GPIO says there is a button press. Get it. */ + + if (1 != i2c_master_recv(ir->c, &b, 1)) { + i2cdprintk("read error\n"); + return -EIO; + } + + /* No button press */ + + if (b == 0xff) + return 0; + + /* Button pressed */ + + dprintk("get_key_kworld_pc150u: Key = 0x%02X\n", b); + *ir_key = b; + *ir_raw = b; + return 1; +} + static int get_key_purpletv(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) { unsigned char b; @@ -901,6 +949,21 @@ void saa7134_probe_i2c_ir(struct saa7134_dev *dev) msg_msi.addr, dev->i2c_adap.name, (1 == rc) ? "yes" : "no"); break; + case SAA7134_BOARD_KWORLD_PC150U: + /* copied and modified from MSI TV@nywhere Plus */ + dev->init_data.name = "Kworld PC150-U"; + dev->init_data.get_key = get_key_kworld_pc150u; + dev->init_data.ir_codes = RC_MAP_KWORLD_PC150U; + info.addr = 0x30; + /* MSI TV@nywhere Plus controller doesn't seem to + respond to probes unless we read something from + an existing device. Weird... + REVISIT: might no longer be needed */ + rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1); + dprintk("probe 0x%02x @ %s: %s\n", + msg_msi.addr, dev->i2c_adap.name, + (1 == rc) ? "yes" : "no"); + break; case SAA7134_BOARD_HAUPPAUGE_HVR1110: dev->init_data.name = "HVR 1110"; dev->init_data.get_key = get_key_hvr1110; diff --git a/drivers/media/video/saa7134/saa7134.h b/drivers/media/video/saa7134/saa7134.h index 42fba4f93c72..f625060e6a0f 100644 --- a/drivers/media/video/saa7134/saa7134.h +++ b/drivers/media/video/saa7134/saa7134.h @@ -126,8 +126,8 @@ struct saa7134_card_ir { unsigned users; u32 polling; - u32 last_gpio; - u32 mask_keycode, mask_keydown, mask_keyup; + u32 last_gpio; + u32 mask_keycode, mask_keydown, mask_keyup; bool running; bool active; @@ -331,6 +331,7 @@ struct saa7134_card_ir { #define SAA7134_BOARD_BEHOLD_501 186 #define SAA7134_BOARD_BEHOLD_503FM 187 #define SAA7134_BOARD_SENSORAY811_911 188 +#define SAA7134_BOARD_KWORLD_PC150U 189 #define SAA7134_MAXBOARDS 32 #define SAA7134_INPUT_MAX 8 diff --git a/drivers/media/video/saa7164/Makefile b/drivers/media/video/saa7164/Makefile index ecd5811dc486..068443af30c8 100644 --- a/drivers/media/video/saa7164/Makefile +++ b/drivers/media/video/saa7164/Makefile @@ -4,9 +4,9 @@ saa7164-objs := saa7164-cards.o saa7164-core.o saa7164-i2c.o saa7164-dvb.o \ obj-$(CONFIG_VIDEO_SAA7164) += saa7164.o -ccflags-y += -Idrivers/media/video -ccflags-y += -Idrivers/media/common/tuners -ccflags-y += -Idrivers/media/dvb/dvb-core -ccflags-y += -Idrivers/media/dvb/frontends +ccflags-y += -I$(srctree)/drivers/media/video +ccflags-y += -I$(srctree)/drivers/media/common/tuners +ccflags-y += -I$(srctree)/drivers/media/dvb/dvb-core +ccflags-y += -I$(srctree)/drivers/media/dvb/frontends ccflags-y += $(extra-cflags-y) $(extra-cflags-m) diff --git a/drivers/media/video/saa7164/saa7164-encoder.c b/drivers/media/video/saa7164/saa7164-encoder.c index 2fd38a01887f..a9ed686ad08a 100644 --- a/drivers/media/video/saa7164/saa7164-encoder.c +++ b/drivers/media/video/saa7164/saa7164-encoder.c @@ -791,11 +791,6 @@ static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int vidioc_log_status(struct file *file, void *priv) -{ - return 0; -} - static int fill_queryctrl(struct saa7164_encoder_params *params, struct v4l2_queryctrl *c) { @@ -1347,7 +1342,6 @@ static const struct v4l2_ioctl_ops mpeg_ioctl_ops = { .vidioc_g_ext_ctrls = vidioc_g_ext_ctrls, .vidioc_s_ext_ctrls = vidioc_s_ext_ctrls, .vidioc_try_ext_ctrls = vidioc_try_ext_ctrls, - .vidioc_log_status = vidioc_log_status, .vidioc_queryctrl = vidioc_queryctrl, .vidioc_g_chip_ident = saa7164_g_chip_ident, #ifdef CONFIG_VIDEO_ADV_DEBUG diff --git a/drivers/media/video/saa7164/saa7164-vbi.c b/drivers/media/video/saa7164/saa7164-vbi.c index e2e034158718..273cf807401c 100644 --- a/drivers/media/video/saa7164/saa7164-vbi.c +++ b/drivers/media/video/saa7164/saa7164-vbi.c @@ -730,11 +730,6 @@ static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int vidioc_log_status(struct file *file, void *priv) -{ - return 0; -} - static int fill_queryctrl(struct saa7164_vbi_params *params, struct v4l2_queryctrl *c) { @@ -1256,7 +1251,6 @@ static const struct v4l2_ioctl_ops vbi_ioctl_ops = { .vidioc_g_ext_ctrls = vidioc_g_ext_ctrls, .vidioc_s_ext_ctrls = vidioc_s_ext_ctrls, .vidioc_try_ext_ctrls = vidioc_try_ext_ctrls, - .vidioc_log_status = vidioc_log_status, .vidioc_queryctrl = vidioc_queryctrl, #if 0 .vidioc_g_chip_ident = saa7164_g_chip_ident, diff --git a/drivers/media/video/saa717x.c b/drivers/media/video/saa717x.c index b6172c2c517e..1e84466515aa 100644 --- a/drivers/media/video/saa717x.c +++ b/drivers/media/video/saa717x.c @@ -1375,15 +1375,4 @@ static struct i2c_driver saa717x_driver = { .id_table = saa717x_id, }; -static __init int init_saa717x(void) -{ - return i2c_add_driver(&saa717x_driver); -} - -static __exit void exit_saa717x(void) -{ - i2c_del_driver(&saa717x_driver); -} - -module_init(init_saa717x); -module_exit(exit_saa717x); +module_i2c_driver(saa717x_driver); diff --git a/drivers/media/video/saa7185.c b/drivers/media/video/saa7185.c index 96f56c2f11f3..2c6b65c76e2b 100644 --- a/drivers/media/video/saa7185.c +++ b/drivers/media/video/saa7185.c @@ -374,15 +374,4 @@ static struct i2c_driver saa7185_driver = { .id_table = saa7185_id, }; -static __init int init_saa7185(void) -{ - return i2c_add_driver(&saa7185_driver); -} - -static __exit void exit_saa7185(void) -{ - i2c_del_driver(&saa7185_driver); -} - -module_init(init_saa7185); -module_exit(exit_saa7185); +module_i2c_driver(saa7185_driver); diff --git a/drivers/media/video/saa7191.c b/drivers/media/video/saa7191.c index 211fa25a1239..d7d1670e0ca3 100644 --- a/drivers/media/video/saa7191.c +++ b/drivers/media/video/saa7191.c @@ -656,15 +656,4 @@ static struct i2c_driver saa7191_driver = { .id_table = saa7191_id, }; -static __init int init_saa7191(void) -{ - return i2c_add_driver(&saa7191_driver); -} - -static __exit void exit_saa7191(void) -{ - i2c_del_driver(&saa7191_driver); -} - -module_init(init_saa7191); -module_exit(exit_saa7191); +module_i2c_driver(saa7191_driver); diff --git a/drivers/media/video/sh_mobile_ceu_camera.c b/drivers/media/video/sh_mobile_ceu_camera.c index f854d85a387c..424dfacd263a 100644 --- a/drivers/media/video/sh_mobile_ceu_camera.c +++ b/drivers/media/video/sh_mobile_ceu_camera.c @@ -112,6 +112,10 @@ struct sh_mobile_ceu_dev { u32 cflcr; + /* static max sizes either from platform data or default */ + int max_width; + int max_height; + enum v4l2_field field; int sequence; @@ -1081,7 +1085,15 @@ static int sh_mobile_ceu_get_formats(struct soc_camera_device *icd, unsigned int if (ret < 0) return ret; - while ((mf.width > 2560 || mf.height > 1920) && shift < 4) { + /* + * All currently existing CEU implementations support 2560x1920 + * or larger frames. If the sensor is proposing too big a frame, + * don't bother with possibly supportred by the CEU larger + * sizes, just try VGA multiples. If needed, this can be + * adjusted in the future. + */ + while ((mf.width > pcdev->max_width || + mf.height > pcdev->max_height) && shift < 4) { /* Try 2560x1920, 1280x960, 640x480, 320x240 */ mf.width = 2560 >> shift; mf.height = 1920 >> shift; @@ -1377,6 +1389,8 @@ static int client_s_crop(struct soc_camera_device *icd, struct v4l2_crop *crop, static int client_s_fmt(struct soc_camera_device *icd, struct v4l2_mbus_framefmt *mf, bool ceu_can_scale) { + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct sh_mobile_ceu_dev *pcdev = ici->priv; struct sh_mobile_ceu_cam *cam = icd->host_priv; struct v4l2_subdev *sd = soc_camera_to_subdev(icd); struct device *dev = icd->parent; @@ -1410,8 +1424,8 @@ static int client_s_fmt(struct soc_camera_device *icd, if (ret < 0) return ret; - max_width = min(cap.bounds.width, 2560); - max_height = min(cap.bounds.height, 1920); + max_width = min(cap.bounds.width, pcdev->max_width); + max_height = min(cap.bounds.height, pcdev->max_height); /* Camera set a format, but geometry is not precise, try to improve */ tmp_w = mf->width; @@ -1551,7 +1565,7 @@ static int sh_mobile_ceu_set_crop(struct soc_camera_device *icd, if (ret < 0) return ret; - if (mf.width > 2560 || mf.height > 1920) + if (mf.width > pcdev->max_width || mf.height > pcdev->max_height) return -EINVAL; /* 4. Calculate camera scales */ @@ -1834,6 +1848,8 @@ static int sh_mobile_ceu_set_fmt(struct soc_camera_device *icd, static int sh_mobile_ceu_try_fmt(struct soc_camera_device *icd, struct v4l2_format *f) { + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + struct sh_mobile_ceu_dev *pcdev = ici->priv; const struct soc_camera_format_xlate *xlate; struct v4l2_pix_format *pix = &f->fmt.pix; struct v4l2_subdev *sd = soc_camera_to_subdev(icd); @@ -1854,8 +1870,8 @@ static int sh_mobile_ceu_try_fmt(struct soc_camera_device *icd, /* FIXME: calculate using depth and bus width */ /* CFSZR requires height and width to be 4-pixel aligned */ - v4l_bound_align_image(&pix->width, 2, 2560, 2, - &pix->height, 4, 1920, 2, 0); + v4l_bound_align_image(&pix->width, 2, pcdev->max_width, 2, + &pix->height, 4, pcdev->max_height, 2, 0); width = pix->width; height = pix->height; @@ -1890,8 +1906,8 @@ static int sh_mobile_ceu_try_fmt(struct soc_camera_device *icd, * requested a bigger rectangle, it will not return a * smaller one. */ - mf.width = 2560; - mf.height = 1920; + mf.width = pcdev->max_width; + mf.height = pcdev->max_height; ret = v4l2_device_call_until_err(sd->v4l2_dev, soc_camera_grp_id(icd), video, try_mbus_fmt, &mf); @@ -2082,6 +2098,9 @@ static int __devinit sh_mobile_ceu_probe(struct platform_device *pdev) goto exit_kfree; } + pcdev->max_width = pcdev->pdata->max_width ? : 2560; + pcdev->max_height = pcdev->pdata->max_height ? : 1920; + base = ioremap_nocache(res->start, resource_size(res)); if (!base) { err = -ENXIO; diff --git a/drivers/media/video/soc_camera.c b/drivers/media/video/soc_camera.c index b82710745ba8..eb25756a07af 100644 --- a/drivers/media/video/soc_camera.c +++ b/drivers/media/video/soc_camera.c @@ -526,10 +526,6 @@ static int soc_camera_open(struct file *file) }, }; - ret = soc_camera_power_on(icd, icl); - if (ret < 0) - goto epower; - /* The camera could have been already on, try to reset */ if (icl->reset) icl->reset(icd->pdev); @@ -540,6 +536,10 @@ static int soc_camera_open(struct file *file) goto eiciadd; } + ret = soc_camera_power_on(icd, icl); + if (ret < 0) + goto epower; + pm_runtime_enable(&icd->vdev->dev); ret = pm_runtime_resume(&icd->vdev->dev); if (ret < 0 && ret != -ENOSYS) @@ -578,10 +578,10 @@ einitvb: esfmt: pm_runtime_disable(&icd->vdev->dev); eresume: - ici->ops->remove(icd); -eiciadd: soc_camera_power_off(icd, icl); epower: + ici->ops->remove(icd); +eiciadd: icd->use_count--; module_put(ici->ops->owner); @@ -1050,6 +1050,14 @@ static int soc_camera_probe(struct soc_camera_device *icd) if (ret < 0) goto ereg; + /* The camera could have been already on, try to reset */ + if (icl->reset) + icl->reset(icd->pdev); + + ret = ici->ops->add(icd); + if (ret < 0) + goto eadd; + /* * This will not yet call v4l2_subdev_core_ops::s_power(1), because the * subdevice has not been initialised yet. We'll have to call it once @@ -1060,14 +1068,6 @@ static int soc_camera_probe(struct soc_camera_device *icd) if (ret < 0) goto epower; - /* The camera could have been already on, try to reset */ - if (icl->reset) - icl->reset(icd->pdev); - - ret = ici->ops->add(icd); - if (ret < 0) - goto eadd; - /* Must have icd->vdev before registering the device */ ret = video_dev_create(icd); if (ret < 0) @@ -1165,10 +1165,10 @@ eadddev: video_device_release(icd->vdev); icd->vdev = NULL; evdc: - ici->ops->remove(icd); -eadd: soc_camera_power_off(icd, icl); epower: + ici->ops->remove(icd); +eadd: regulator_bulk_free(icl->num_regulators, icl->regulators); ereg: v4l2_ctrl_handler_free(&icd->ctrl_handler); diff --git a/drivers/media/video/sr030pc30.c b/drivers/media/video/sr030pc30.c index d1b07aceaf94..e9d95bda2ab1 100644 --- a/drivers/media/video/sr030pc30.c +++ b/drivers/media/video/sr030pc30.c @@ -864,18 +864,7 @@ static struct i2c_driver sr030pc30_i2c_driver = { .id_table = sr030pc30_id, }; -static int __init sr030pc30_init(void) -{ - return i2c_add_driver(&sr030pc30_i2c_driver); -} - -static void __exit sr030pc30_exit(void) -{ - i2c_del_driver(&sr030pc30_i2c_driver); -} - -module_init(sr030pc30_init); -module_exit(sr030pc30_exit); +module_i2c_driver(sr030pc30_i2c_driver); MODULE_DESCRIPTION("Siliconfile SR030PC30 camera driver"); MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); diff --git a/drivers/media/video/tda7432.c b/drivers/media/video/tda7432.c index bd218545da9c..f7707e65761e 100644 --- a/drivers/media/video/tda7432.c +++ b/drivers/media/video/tda7432.c @@ -482,15 +482,4 @@ static struct i2c_driver tda7432_driver = { .id_table = tda7432_id, }; -static __init int init_tda7432(void) -{ - return i2c_add_driver(&tda7432_driver); -} - -static __exit void exit_tda7432(void) -{ - i2c_del_driver(&tda7432_driver); -} - -module_init(init_tda7432); -module_exit(exit_tda7432); +module_i2c_driver(tda7432_driver); diff --git a/drivers/media/video/tda9840.c b/drivers/media/video/tda9840.c index 22fa8202d5ca..465d7086babf 100644 --- a/drivers/media/video/tda9840.c +++ b/drivers/media/video/tda9840.c @@ -208,15 +208,4 @@ static struct i2c_driver tda9840_driver = { .id_table = tda9840_id, }; -static __init int init_tda9840(void) -{ - return i2c_add_driver(&tda9840_driver); -} - -static __exit void exit_tda9840(void) -{ - i2c_del_driver(&tda9840_driver); -} - -module_init(init_tda9840); -module_exit(exit_tda9840); +module_i2c_driver(tda9840_driver); diff --git a/drivers/media/video/tea6415c.c b/drivers/media/video/tea6415c.c index 827425c5b866..d1d6ea1dd273 100644 --- a/drivers/media/video/tea6415c.c +++ b/drivers/media/video/tea6415c.c @@ -184,15 +184,4 @@ static struct i2c_driver tea6415c_driver = { .id_table = tea6415c_id, }; -static __init int init_tea6415c(void) -{ - return i2c_add_driver(&tea6415c_driver); -} - -static __exit void exit_tea6415c(void) -{ - i2c_del_driver(&tea6415c_driver); -} - -module_init(init_tea6415c); -module_exit(exit_tea6415c); +module_i2c_driver(tea6415c_driver); diff --git a/drivers/media/video/tea6420.c b/drivers/media/video/tea6420.c index f350b6c24500..38757217a074 100644 --- a/drivers/media/video/tea6420.c +++ b/drivers/media/video/tea6420.c @@ -166,15 +166,4 @@ static struct i2c_driver tea6420_driver = { .id_table = tea6420_id, }; -static __init int init_tea6420(void) -{ - return i2c_add_driver(&tea6420_driver); -} - -static __exit void exit_tea6420(void) -{ - i2c_del_driver(&tea6420_driver); -} - -module_init(init_tea6420); -module_exit(exit_tea6420); +module_i2c_driver(tea6420_driver); diff --git a/drivers/media/video/ths7303.c b/drivers/media/video/ths7303.c index 61b1dd118364..e5c0eedebc58 100644 --- a/drivers/media/video/ths7303.c +++ b/drivers/media/video/ths7303.c @@ -137,16 +137,4 @@ static struct i2c_driver ths7303_driver = { .id_table = ths7303_id, }; -static int __init ths7303_init(void) -{ - return i2c_add_driver(&ths7303_driver); -} - -static void __exit ths7303_exit(void) -{ - i2c_del_driver(&ths7303_driver); -} - -module_init(ths7303_init); -module_exit(ths7303_exit); - +module_i2c_driver(ths7303_driver); diff --git a/drivers/media/video/tlv320aic23b.c b/drivers/media/video/tlv320aic23b.c index 286ec7e7062a..809a75a558ee 100644 --- a/drivers/media/video/tlv320aic23b.c +++ b/drivers/media/video/tlv320aic23b.c @@ -227,15 +227,4 @@ static struct i2c_driver tlv320aic23b_driver = { .id_table = tlv320aic23b_id, }; -static __init int init_tlv320aic23b(void) -{ - return i2c_add_driver(&tlv320aic23b_driver); -} - -static __exit void exit_tlv320aic23b(void) -{ - i2c_del_driver(&tlv320aic23b_driver); -} - -module_init(init_tlv320aic23b); -module_exit(exit_tlv320aic23b); +module_i2c_driver(tlv320aic23b_driver); diff --git a/drivers/media/video/tm6000/tm6000-input.c b/drivers/media/video/tm6000/tm6000-input.c index 7844607dd45a..859eb90e4d56 100644 --- a/drivers/media/video/tm6000/tm6000-input.c +++ b/drivers/media/video/tm6000/tm6000-input.c @@ -481,8 +481,6 @@ int tm6000_ir_fini(struct tm6000_core *dev) dprintk(2, "%s\n",__func__); - rc_unregister_device(ir->rc); - if (!ir->polling) __tm6000_ir_int_stop(ir->rc); @@ -492,6 +490,7 @@ int tm6000_ir_fini(struct tm6000_core *dev) tm6000_flash_led(dev, 0); ir->pwled = 0; + rc_unregister_device(ir->rc); kfree(ir); dev->ir = NULL; diff --git a/drivers/media/video/tuner-core.c b/drivers/media/video/tuner-core.c index 4059ea178c2d..a5c6397ad591 100644 --- a/drivers/media/video/tuner-core.c +++ b/drivers/media/video/tuner-core.c @@ -380,6 +380,21 @@ static void set_type(struct i2c_client *c, unsigned int type, tune_now = 0; break; } + case TUNER_XC5000C: + { + struct xc5000_config xc5000c_cfg = { + .i2c_address = t->i2c->addr, + /* if_khz will be set at dvb_attach() */ + .if_khz = 0, + .chip_id = XC5000C, + }; + + if (!dvb_attach(xc5000_attach, + &t->fe, t->i2c->adapter, &xc5000c_cfg)) + goto attach_failed; + tune_now = 0; + break; + } case TUNER_NXP_TDA18271: { struct tda18271_config cfg = { @@ -1314,18 +1329,7 @@ static struct i2c_driver tuner_driver = { .id_table = tuner_id, }; -static __init int init_tuner(void) -{ - return i2c_add_driver(&tuner_driver); -} - -static __exit void exit_tuner(void) -{ - i2c_del_driver(&tuner_driver); -} - -module_init(init_tuner); -module_exit(exit_tuner); +module_i2c_driver(tuner_driver); MODULE_DESCRIPTION("device driver for various TV and TV+FM radio tuners"); MODULE_AUTHOR("Ralph Metzler, Gerd Knorr, Gunther Mayer"); diff --git a/drivers/media/video/tvaudio.c b/drivers/media/video/tvaudio.c index f22dbef9b95b..c5b1a7365e4f 100644 --- a/drivers/media/video/tvaudio.c +++ b/drivers/media/video/tvaudio.c @@ -2078,15 +2078,4 @@ static struct i2c_driver tvaudio_driver = { .id_table = tvaudio_id, }; -static __init int init_tvaudio(void) -{ - return i2c_add_driver(&tvaudio_driver); -} - -static __exit void exit_tvaudio(void) -{ - i2c_del_driver(&tvaudio_driver); -} - -module_init(init_tvaudio); -module_exit(exit_tvaudio); +module_i2c_driver(tvaudio_driver); diff --git a/drivers/media/video/tveeprom.c b/drivers/media/video/tveeprom.c index 6103d1b1081e..3b6cf034976a 100644 --- a/drivers/media/video/tveeprom.c +++ b/drivers/media/video/tveeprom.c @@ -286,8 +286,16 @@ hauppauge_tuner[] = { TUNER_ABSENT, "MaxLinear 301"}, { TUNER_ABSENT, "Mirics MSi001"}, { TUNER_ABSENT, "MaxLinear MxL241SF"}, - { TUNER_ABSENT, "Xceive XC5000C"}, + { TUNER_XC5000C, "Xceive XC5000C"}, { TUNER_ABSENT, "Montage M68TS2020"}, + { TUNER_ABSENT, "Siano SMS1530"}, + { TUNER_ABSENT, "Dibcom 7090"}, + { TUNER_ABSENT, "Xceive XC5200C"}, + { TUNER_ABSENT, "NXP 18273"}, + { TUNER_ABSENT, "Montage M88TS2022"}, + /* 180-189 */ + { TUNER_ABSENT, "NXP 18272M"}, + { TUNER_ABSENT, "NXP 18272S"}, }; /* Use V4L2_IDENT_AMBIGUOUS for those audio 'chips' that are diff --git a/drivers/media/video/tvp514x.c b/drivers/media/video/tvp514x.c index dd26cacd0556..cd615c1d6011 100644 --- a/drivers/media/video/tvp514x.c +++ b/drivers/media/video/tvp514x.c @@ -1163,15 +1163,4 @@ static struct i2c_driver tvp514x_driver = { .id_table = tvp514x_id, }; -static int __init tvp514x_init(void) -{ - return i2c_add_driver(&tvp514x_driver); -} - -static void __exit tvp514x_exit(void) -{ - i2c_del_driver(&tvp514x_driver); -} - -module_init(tvp514x_init); -module_exit(tvp514x_exit); +module_i2c_driver(tvp514x_driver); diff --git a/drivers/media/video/tvp5150.c b/drivers/media/video/tvp5150.c index 6be9910a6e24..1326e11cf4a9 100644 --- a/drivers/media/video/tvp5150.c +++ b/drivers/media/video/tvp5150.c @@ -17,6 +17,13 @@ #include "tvp5150_reg.h" +#define TVP5150_H_MAX 720 +#define TVP5150_V_MAX_525_60 480 +#define TVP5150_V_MAX_OTHERS 576 +#define TVP5150_MAX_CROP_LEFT 511 +#define TVP5150_MAX_CROP_TOP 127 +#define TVP5150_CROP_SHIFT 2 + MODULE_DESCRIPTION("Texas Instruments TVP5150A video decoder driver"); MODULE_AUTHOR("Mauro Carvalho Chehab"); MODULE_LICENSE("GPL"); @@ -29,6 +36,7 @@ MODULE_PARM_DESC(debug, "Debug level (0-2)"); struct tvp5150 { struct v4l2_subdev sd; struct v4l2_ctrl_handler hdl; + struct v4l2_rect rect; v4l2_std_id norm; /* Current set standard */ u32 input; @@ -732,6 +740,13 @@ static int tvp5150_s_std(struct v4l2_subdev *sd, v4l2_std_id std) if (decoder->norm == std) return 0; + /* Change cropping height limits */ + if (std & V4L2_STD_525_60) + decoder->rect.height = TVP5150_V_MAX_525_60; + else + decoder->rect.height = TVP5150_V_MAX_OTHERS; + + return tvp5150_set_std(sd, std); } @@ -828,11 +843,8 @@ static int tvp5150_mbus_fmt(struct v4l2_subdev *sd, else std = decoder->norm; - f->width = 720; - if (std & V4L2_STD_525_60) - f->height = 480; - else - f->height = 576; + f->width = decoder->rect.width; + f->height = decoder->rect.height; f->code = V4L2_MBUS_FMT_YUYV8_2X8; f->field = V4L2_FIELD_SEQ_TB; @@ -843,6 +855,99 @@ static int tvp5150_mbus_fmt(struct v4l2_subdev *sd, return 0; } +static int tvp5150_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct v4l2_rect rect = a->c; + struct tvp5150 *decoder = to_tvp5150(sd); + v4l2_std_id std; + int hmax; + + v4l2_dbg(1, debug, sd, "%s left=%d, top=%d, width=%d, height=%d\n", + __func__, rect.left, rect.top, rect.width, rect.height); + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* tvp5150 has some special limits */ + rect.left = clamp(rect.left, 0, TVP5150_MAX_CROP_LEFT); + rect.width = clamp(rect.width, + TVP5150_H_MAX - TVP5150_MAX_CROP_LEFT - rect.left, + TVP5150_H_MAX - rect.left); + rect.top = clamp(rect.top, 0, TVP5150_MAX_CROP_TOP); + + /* Calculate height based on current standard */ + if (decoder->norm == V4L2_STD_ALL) + std = tvp5150_read_std(sd); + else + std = decoder->norm; + + if (std & V4L2_STD_525_60) + hmax = TVP5150_V_MAX_525_60; + else + hmax = TVP5150_V_MAX_OTHERS; + + rect.height = clamp(rect.height, + hmax - TVP5150_MAX_CROP_TOP - rect.top, + hmax - rect.top); + + tvp5150_write(sd, TVP5150_VERT_BLANKING_START, rect.top); + tvp5150_write(sd, TVP5150_VERT_BLANKING_STOP, + rect.top + rect.height - hmax); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_ST_MSB, + rect.left >> TVP5150_CROP_SHIFT); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_ST_LSB, + rect.left | (1 << TVP5150_CROP_SHIFT)); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_STP_MSB, + (rect.left + rect.width - TVP5150_MAX_CROP_LEFT) >> + TVP5150_CROP_SHIFT); + tvp5150_write(sd, TVP5150_ACT_VD_CROP_STP_LSB, + rect.left + rect.width - TVP5150_MAX_CROP_LEFT); + + decoder->rect = rect; + + return 0; +} + +static int tvp5150_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + struct tvp5150 *decoder = container_of(sd, struct tvp5150, sd); + + a->c = decoder->rect; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +static int tvp5150_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + struct tvp5150 *decoder = container_of(sd, struct tvp5150, sd); + v4l2_std_id std; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = TVP5150_H_MAX; + + /* Calculate height based on current standard */ + if (decoder->norm == V4L2_STD_ALL) + std = tvp5150_read_std(sd); + else + std = decoder->norm; + + if (std & V4L2_STD_525_60) + a->bounds.height = TVP5150_V_MAX_525_60; + else + a->bounds.height = TVP5150_V_MAX_OTHERS; + + a->defrect = a->bounds; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + /**************************************************************************** I2C Command ****************************************************************************/ @@ -998,6 +1103,10 @@ static const struct v4l2_subdev_video_ops tvp5150_video_ops = { .enum_mbus_fmt = tvp5150_enum_mbus_fmt, .s_mbus_fmt = tvp5150_mbus_fmt, .try_mbus_fmt = tvp5150_mbus_fmt, + .g_mbus_fmt = tvp5150_mbus_fmt, + .s_crop = tvp5150_s_crop, + .g_crop = tvp5150_g_crop, + .cropcap = tvp5150_cropcap, }; static const struct v4l2_subdev_vbi_ops tvp5150_vbi_ops = { @@ -1083,6 +1192,15 @@ static int tvp5150_probe(struct i2c_client *c, } v4l2_ctrl_handler_setup(&core->hdl); + /* Default is no cropping */ + core->rect.top = 0; + if (tvp5150_read_std(sd) & V4L2_STD_525_60) + core->rect.height = TVP5150_V_MAX_525_60; + else + core->rect.height = TVP5150_V_MAX_OTHERS; + core->rect.left = 0; + core->rect.width = TVP5150_H_MAX; + if (debug > 1) tvp5150_log_status(sd); return 0; @@ -1121,15 +1239,4 @@ static struct i2c_driver tvp5150_driver = { .id_table = tvp5150_id, }; -static __init int init_tvp5150(void) -{ - return i2c_add_driver(&tvp5150_driver); -} - -static __exit void exit_tvp5150(void) -{ - i2c_del_driver(&tvp5150_driver); -} - -module_init(init_tvp5150); -module_exit(exit_tvp5150); +module_i2c_driver(tvp5150_driver); diff --git a/drivers/media/video/tvp7002.c b/drivers/media/video/tvp7002.c index 236c559d5f51..d7676d85c4df 100644 --- a/drivers/media/video/tvp7002.c +++ b/drivers/media/video/tvp7002.c @@ -1069,27 +1069,4 @@ static struct i2c_driver tvp7002_driver = { .id_table = tvp7002_id, }; -/* - * tvp7002_init - Initialize driver via I2C interface - * - * Register the TVP7002 driver. - * Return 0 on success or error code on failure. - */ -static int __init tvp7002_init(void) -{ - return i2c_add_driver(&tvp7002_driver); -} - -/* - * tvp7002_exit - Remove driver via I2C interface - * - * Unregister the TVP7002 driver. - * Returns nothing. - */ -static void __exit tvp7002_exit(void) -{ - i2c_del_driver(&tvp7002_driver); -} - -module_init(tvp7002_init); -module_exit(tvp7002_exit); +module_i2c_driver(tvp7002_driver); diff --git a/drivers/media/video/tw9910.c b/drivers/media/video/tw9910.c index a514fa61116c..8768efb8508a 100644 --- a/drivers/media/video/tw9910.c +++ b/drivers/media/video/tw9910.c @@ -951,21 +951,7 @@ static struct i2c_driver tw9910_i2c_driver = { .id_table = tw9910_id, }; -/* - * module function - */ -static int __init tw9910_module_init(void) -{ - return i2c_add_driver(&tw9910_i2c_driver); -} - -static void __exit tw9910_module_exit(void) -{ - i2c_del_driver(&tw9910_i2c_driver); -} - -module_init(tw9910_module_init); -module_exit(tw9910_module_exit); +module_i2c_driver(tw9910_i2c_driver); MODULE_DESCRIPTION("SoC Camera driver for tw9910"); MODULE_AUTHOR("Kuninori Morimoto"); diff --git a/drivers/media/video/upd64031a.c b/drivers/media/video/upd64031a.c index 1aab96a88203..1e7446542091 100644 --- a/drivers/media/video/upd64031a.c +++ b/drivers/media/video/upd64031a.c @@ -271,15 +271,4 @@ static struct i2c_driver upd64031a_driver = { .id_table = upd64031a_id, }; -static __init int init_upd64031a(void) -{ - return i2c_add_driver(&upd64031a_driver); -} - -static __exit void exit_upd64031a(void) -{ - i2c_del_driver(&upd64031a_driver); -} - -module_init(init_upd64031a); -module_exit(exit_upd64031a); +module_i2c_driver(upd64031a_driver); diff --git a/drivers/media/video/upd64083.c b/drivers/media/video/upd64083.c index 65d065aa6091..75d6acc62018 100644 --- a/drivers/media/video/upd64083.c +++ b/drivers/media/video/upd64083.c @@ -243,15 +243,4 @@ static struct i2c_driver upd64083_driver = { .id_table = upd64083_id, }; -static __init int init_upd64083(void) -{ - return i2c_add_driver(&upd64083_driver); -} - -static __exit void exit_upd64083(void) -{ - i2c_del_driver(&upd64083_driver); -} - -module_init(init_upd64083); -module_exit(exit_upd64083); +module_i2c_driver(upd64083_driver); diff --git a/drivers/media/video/uvc/uvc_driver.c b/drivers/media/video/uvc/uvc_driver.c index a240d43d15d1..1d131720b6d7 100644 --- a/drivers/media/video/uvc/uvc_driver.c +++ b/drivers/media/video/uvc/uvc_driver.c @@ -23,6 +23,7 @@ * codec can't handle MJPEG data. */ +#include <linux/atomic.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/module.h> @@ -32,7 +33,6 @@ #include <linux/vmalloc.h> #include <linux/wait.h> #include <linux/version.h> -#include <asm/atomic.h> #include <asm/unaligned.h> #include <media/v4l2-common.h> @@ -2139,6 +2139,15 @@ static struct usb_device_id uvc_ids[] = { .bInterfaceSubClass = 1, .bInterfaceProtocol = 0, .driver_info = UVC_QUIRK_PROBE_MINMAX }, + /* Dell XPS m1530 */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x05a9, + .idProduct = 0x2640, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = UVC_QUIRK_PROBE_DEF }, /* Apple Built-In iSight */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, diff --git a/drivers/media/video/uvc/uvc_queue.c b/drivers/media/video/uvc/uvc_queue.c index 518f77d3a4d8..8f54e24e3f35 100644 --- a/drivers/media/video/uvc/uvc_queue.c +++ b/drivers/media/video/uvc/uvc_queue.c @@ -126,7 +126,7 @@ void uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type, int drop_corrupted) { queue->queue.type = type; - queue->queue.io_modes = VB2_MMAP; + queue->queue.io_modes = VB2_MMAP | VB2_USERPTR; queue->queue.drv_priv = queue; queue->queue.buf_struct_size = sizeof(struct uvc_buffer); queue->queue.ops = &uvc_queue_qops; diff --git a/drivers/media/video/uvc/uvc_v4l2.c b/drivers/media/video/uvc/uvc_v4l2.c index 2ae4f880ea05..ff2cdddf9bc6 100644 --- a/drivers/media/video/uvc/uvc_v4l2.c +++ b/drivers/media/video/uvc/uvc_v4l2.c @@ -11,6 +11,7 @@ * */ +#include <linux/compat.h> #include <linux/kernel.h> #include <linux/version.h> #include <linux/list.h> @@ -1012,7 +1013,7 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg) default: uvc_trace(UVC_TRACE_IOCTL, "Unknown ioctl 0x%08x\n", cmd); - return -EINVAL; + return -ENOTTY; } return ret; @@ -1030,6 +1031,207 @@ static long uvc_v4l2_ioctl(struct file *file, return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl); } +#ifdef CONFIG_COMPAT +struct uvc_xu_control_mapping32 { + __u32 id; + __u8 name[32]; + __u8 entity[16]; + __u8 selector; + + __u8 size; + __u8 offset; + __u32 v4l2_type; + __u32 data_type; + + compat_caddr_t menu_info; + __u32 menu_count; + + __u32 reserved[4]; +}; + +static int uvc_v4l2_get_xu_mapping(struct uvc_xu_control_mapping *kp, + const struct uvc_xu_control_mapping32 __user *up) +{ + struct uvc_menu_info __user *umenus; + struct uvc_menu_info __user *kmenus; + compat_caddr_t p; + + if (!access_ok(VERIFY_READ, up, sizeof(*up)) || + __copy_from_user(kp, up, offsetof(typeof(*up), menu_info)) || + __get_user(kp->menu_count, &up->menu_count)) + return -EFAULT; + + memset(kp->reserved, 0, sizeof(kp->reserved)); + + if (kp->menu_count == 0) { + kp->menu_info = NULL; + return 0; + } + + if (__get_user(p, &up->menu_info)) + return -EFAULT; + umenus = compat_ptr(p); + if (!access_ok(VERIFY_READ, umenus, kp->menu_count * sizeof(*umenus))) + return -EFAULT; + + kmenus = compat_alloc_user_space(kp->menu_count * sizeof(*kmenus)); + if (kmenus == NULL) + return -EFAULT; + kp->menu_info = kmenus; + + if (copy_in_user(kmenus, umenus, kp->menu_count * sizeof(*umenus))) + return -EFAULT; + + return 0; +} + +static int uvc_v4l2_put_xu_mapping(const struct uvc_xu_control_mapping *kp, + struct uvc_xu_control_mapping32 __user *up) +{ + struct uvc_menu_info __user *umenus; + struct uvc_menu_info __user *kmenus = kp->menu_info; + compat_caddr_t p; + + if (!access_ok(VERIFY_WRITE, up, sizeof(*up)) || + __copy_to_user(up, kp, offsetof(typeof(*up), menu_info)) || + __put_user(kp->menu_count, &up->menu_count)) + return -EFAULT; + + __clear_user(up->reserved, sizeof(up->reserved)); + + if (kp->menu_count == 0) + return 0; + + if (get_user(p, &up->menu_info)) + return -EFAULT; + umenus = compat_ptr(p); + if (!access_ok(VERIFY_WRITE, umenus, kp->menu_count * sizeof(*umenus))) + return -EFAULT; + + if (copy_in_user(umenus, kmenus, kp->menu_count * sizeof(*umenus))) + return -EFAULT; + + return 0; +} + +struct uvc_xu_control_query32 { + __u8 unit; + __u8 selector; + __u8 query; + __u16 size; + compat_caddr_t data; +}; + +static int uvc_v4l2_get_xu_query(struct uvc_xu_control_query *kp, + const struct uvc_xu_control_query32 __user *up) +{ + u8 __user *udata; + u8 __user *kdata; + compat_caddr_t p; + + if (!access_ok(VERIFY_READ, up, sizeof(*up)) || + __copy_from_user(kp, up, offsetof(typeof(*up), data))) + return -EFAULT; + + if (kp->size == 0) { + kp->data = NULL; + return 0; + } + + if (__get_user(p, &up->data)) + return -EFAULT; + udata = compat_ptr(p); + if (!access_ok(VERIFY_READ, udata, kp->size)) + return -EFAULT; + + kdata = compat_alloc_user_space(kp->size); + if (kdata == NULL) + return -EFAULT; + kp->data = kdata; + + if (copy_in_user(kdata, udata, kp->size)) + return -EFAULT; + + return 0; +} + +static int uvc_v4l2_put_xu_query(const struct uvc_xu_control_query *kp, + struct uvc_xu_control_query32 __user *up) +{ + u8 __user *udata; + u8 __user *kdata = kp->data; + compat_caddr_t p; + + if (!access_ok(VERIFY_WRITE, up, sizeof(*up)) || + __copy_to_user(up, kp, offsetof(typeof(*up), data))) + return -EFAULT; + + if (kp->size == 0) + return 0; + + if (get_user(p, &up->data)) + return -EFAULT; + udata = compat_ptr(p); + if (!access_ok(VERIFY_READ, udata, kp->size)) + return -EFAULT; + + if (copy_in_user(udata, kdata, kp->size)) + return -EFAULT; + + return 0; +} + +#define UVCIOC_CTRL_MAP32 _IOWR('u', 0x20, struct uvc_xu_control_mapping32) +#define UVCIOC_CTRL_QUERY32 _IOWR('u', 0x21, struct uvc_xu_control_query32) + +static long uvc_v4l2_compat_ioctl32(struct file *file, + unsigned int cmd, unsigned long arg) +{ + union { + struct uvc_xu_control_mapping xmap; + struct uvc_xu_control_query xqry; + } karg; + void __user *up = compat_ptr(arg); + mm_segment_t old_fs; + long ret; + + switch (cmd) { + case UVCIOC_CTRL_MAP32: + cmd = UVCIOC_CTRL_MAP; + ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up); + break; + + case UVCIOC_CTRL_QUERY32: + cmd = UVCIOC_CTRL_QUERY; + ret = uvc_v4l2_get_xu_query(&karg.xqry, up); + break; + + default: + return -ENOIOCTLCMD; + } + + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = uvc_v4l2_ioctl(file, cmd, (unsigned long)&karg); + set_fs(old_fs); + + if (ret < 0) + return ret; + + switch (cmd) { + case UVCIOC_CTRL_MAP: + ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up); + break; + + case UVCIOC_CTRL_QUERY: + ret = uvc_v4l2_put_xu_query(&karg.xqry, up); + break; + } + + return ret; +} +#endif + static ssize_t uvc_v4l2_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { @@ -1076,6 +1278,9 @@ const struct v4l2_file_operations uvc_fops = { .open = uvc_v4l2_open, .release = uvc_v4l2_release, .unlocked_ioctl = uvc_v4l2_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = uvc_v4l2_compat_ioctl32, +#endif .read = uvc_v4l2_read, .mmap = uvc_v4l2_mmap, .poll = uvc_v4l2_poll, diff --git a/drivers/media/video/v4l2-compat-ioctl32.c b/drivers/media/video/v4l2-compat-ioctl32.c index af4419e6c658..2829d256e4b7 100644 --- a/drivers/media/video/v4l2-compat-ioctl32.c +++ b/drivers/media/video/v4l2-compat-ioctl32.c @@ -14,12 +14,11 @@ */ #include <linux/compat.h> -#include <linux/videodev2.h> #include <linux/module.h> +#include <linux/videodev2.h> +#include <media/v4l2-dev.h> #include <media/v4l2-ioctl.h> -#ifdef CONFIG_COMPAT - static long native_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long ret = -ENOIOCTLCMD; @@ -937,6 +936,7 @@ static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg) { + struct video_device *vdev = video_devdata(file); long ret = -ENOIOCTLCMD; if (!file->f_op->unlocked_ioctl) @@ -1005,6 +1005,8 @@ long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg) case VIDIOC_G_ENC_INDEX: case VIDIOC_ENCODER_CMD: case VIDIOC_TRY_ENCODER_CMD: + case VIDIOC_DECODER_CMD: + case VIDIOC_TRY_DECODER_CMD: case VIDIOC_DBG_S_REGISTER: case VIDIOC_DBG_G_REGISTER: case VIDIOC_DBG_G_CHIP_IDENT: @@ -1025,14 +1027,16 @@ long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg) break; default: - printk(KERN_WARNING "compat_ioctl32: " - "unknown ioctl '%c', dir=%d, #%d (0x%08x)\n", - _IOC_TYPE(cmd), _IOC_DIR(cmd), _IOC_NR(cmd), cmd); + if (vdev->fops->compat_ioctl32) + ret = vdev->fops->compat_ioctl32(file, cmd, arg); + + if (ret == -ENOIOCTLCMD) + printk(KERN_WARNING "compat_ioctl32: " + "unknown ioctl '%c', dir=%d, #%d (0x%08x)\n", + _IOC_TYPE(cmd), _IOC_DIR(cmd), _IOC_NR(cmd), + cmd); break; } return ret; } EXPORT_SYMBOL_GPL(v4l2_compat_ioctl32); -#endif - -MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/v4l2-ctrls.c b/drivers/media/video/v4l2-ctrls.c index cccd42be718a..18015c0a8d31 100644 --- a/drivers/media/video/v4l2-ctrls.c +++ b/drivers/media/video/v4l2-ctrls.c @@ -175,6 +175,15 @@ const char * const *v4l2_ctrl_get_menu(u32 id) "16-bit CRC", NULL }; + static const char * const mpeg_audio_dec_playback[] = { + "Auto", + "Stereo", + "Left", + "Right", + "Mono", + "Swapped Stereo", + NULL + }; static const char * const mpeg_video_encoding[] = { "MPEG-1", "MPEG-2", @@ -236,8 +245,8 @@ const char * const *v4l2_ctrl_get_menu(u32 id) }; static const char * const tune_preemphasis[] = { "No Preemphasis", - "50 useconds", - "75 useconds", + "50 Microseconds", + "75 Microseconds", NULL, }; static const char * const header_mode[] = { @@ -334,7 +343,7 @@ const char * const *v4l2_ctrl_get_menu(u32 id) }; static const char * const mpeg4_profile[] = { "Simple", - "Adcanved Simple", + "Advanced Simple", "Core", "Simple Scalable", "Advanced Coding Efficency", @@ -353,6 +362,16 @@ const char * const *v4l2_ctrl_get_menu(u32 id) NULL, }; + static const char * const jpeg_chroma_subsampling[] = { + "4:4:4", + "4:2:2", + "4:2:0", + "4:1:1", + "4:1:0", + "Gray", + NULL, + }; + switch (id) { case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ: return mpeg_audio_sampling_freq; @@ -374,6 +393,9 @@ const char * const *v4l2_ctrl_get_menu(u32 id) return mpeg_audio_emphasis; case V4L2_CID_MPEG_AUDIO_CRC: return mpeg_audio_crc; + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: + case V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK: + return mpeg_audio_dec_playback; case V4L2_CID_MPEG_VIDEO_ENCODING: return mpeg_video_encoding; case V4L2_CID_MPEG_VIDEO_ASPECT: @@ -414,6 +436,9 @@ const char * const *v4l2_ctrl_get_menu(u32 id) return mpeg_mpeg4_level; case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: return mpeg4_profile; + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + return jpeg_chroma_subsampling; + default: return NULL; } @@ -492,6 +517,8 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_MPEG_AUDIO_MUTE: return "Audio Mute"; case V4L2_CID_MPEG_AUDIO_AAC_BITRATE: return "Audio AAC Bitrate"; case V4L2_CID_MPEG_AUDIO_AC3_BITRATE: return "Audio AC-3 Bitrate"; + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: return "Audio Playback"; + case V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK: return "Audio Multilingual Playback"; case V4L2_CID_MPEG_VIDEO_ENCODING: return "Video Encoding"; case V4L2_CID_MPEG_VIDEO_ASPECT: return "Video Aspect"; case V4L2_CID_MPEG_VIDEO_B_FRAMES: return "Video B Frames"; @@ -546,6 +573,8 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: return "Number of MBs in a Slice"; case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: return "Slice Partitioning Method"; case V4L2_CID_MPEG_VIDEO_VBV_SIZE: return "VBV Buffer Size"; + case V4L2_CID_MPEG_VIDEO_DEC_PTS: return "Video Decoder PTS"; + case V4L2_CID_MPEG_VIDEO_DEC_FRAME: return "Video Decoder Frame Count"; /* CAMERA controls */ /* Keep the order of the 'case's the same as in videodev2.h! */ @@ -607,6 +636,14 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_FLASH_CHARGE: return "Charge"; case V4L2_CID_FLASH_READY: return "Ready to Strobe"; + /* JPEG encoder controls */ + /* Keep the order of the 'case's the same as in videodev2.h! */ + case V4L2_CID_JPEG_CLASS: return "JPEG Compression Controls"; + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: return "Chroma Subsampling"; + case V4L2_CID_JPEG_RESTART_INTERVAL: return "Restart Interval"; + case V4L2_CID_JPEG_COMPRESSION_QUALITY: return "Compression Quality"; + case V4L2_CID_JPEG_ACTIVE_MARKER: return "Active Markers"; + default: return NULL; } @@ -674,6 +711,8 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: case V4L2_CID_MPEG_AUDIO_EMPHASIS: case V4L2_CID_MPEG_AUDIO_CRC: + case V4L2_CID_MPEG_AUDIO_DEC_PLAYBACK: + case V4L2_CID_MPEG_AUDIO_DEC_MULTILINGUAL_PLAYBACK: case V4L2_CID_MPEG_VIDEO_ENCODING: case V4L2_CID_MPEG_VIDEO_ASPECT: case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: @@ -693,6 +732,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC: case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: *type = V4L2_CTRL_TYPE_MENU; break; case V4L2_CID_RDS_TX_PS_NAME: @@ -704,6 +744,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_MPEG_CLASS: case V4L2_CID_FM_TX_CLASS: case V4L2_CID_FLASH_CLASS: + case V4L2_CID_JPEG_CLASS: *type = V4L2_CTRL_TYPE_CTRL_CLASS; /* You can neither read not write these */ *flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY; @@ -717,6 +758,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, *max = 0xFFFFFF; break; case V4L2_CID_FLASH_FAULT: + case V4L2_CID_JPEG_ACTIVE_MARKER: *type = V4L2_CTRL_TYPE_BITMASK; break; case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: @@ -724,6 +766,11 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, *type = V4L2_CTRL_TYPE_INTEGER; *flags |= V4L2_CTRL_FLAG_READ_ONLY; break; + case V4L2_CID_MPEG_VIDEO_DEC_FRAME: + case V4L2_CID_MPEG_VIDEO_DEC_PTS: + *type = V4L2_CTRL_TYPE_INTEGER64; + *flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE; + break; default: *type = V4L2_CTRL_TYPE_INTEGER; break; @@ -1470,7 +1517,7 @@ EXPORT_SYMBOL(v4l2_ctrl_add_ctrl); int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, struct v4l2_ctrl_handler *add) { - struct v4l2_ctrl *ctrl; + struct v4l2_ctrl_ref *ref; int ret = 0; /* Do nothing if either handler is NULL or if they are the same */ @@ -1479,7 +1526,9 @@ int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, if (hdl->error) return hdl->error; mutex_lock(&add->lock); - list_for_each_entry(ctrl, &add->ctrls, node) { + list_for_each_entry(ref, &add->ctrl_refs, node) { + struct v4l2_ctrl *ctrl = ref->ctrl; + /* Skip handler-private controls. */ if (ctrl->is_private) continue; @@ -2359,3 +2408,35 @@ void v4l2_ctrl_del_event(struct v4l2_ctrl *ctrl, v4l2_ctrl_unlock(ctrl); } EXPORT_SYMBOL(v4l2_ctrl_del_event); + +int v4l2_ctrl_log_status(struct file *file, void *fh) +{ + struct video_device *vfd = video_devdata(file); + struct v4l2_fh *vfh = file->private_data; + + if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags) && vfd->v4l2_dev) + v4l2_ctrl_handler_log_status(vfh->ctrl_handler, + vfd->v4l2_dev->name); + return 0; +} +EXPORT_SYMBOL(v4l2_ctrl_log_status); + +int v4l2_ctrl_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type == V4L2_EVENT_CTRL) + return v4l2_event_subscribe(fh, sub, 0); + return -EINVAL; +} +EXPORT_SYMBOL(v4l2_ctrl_subscribe_event); + +unsigned int v4l2_ctrl_poll(struct file *file, struct poll_table_struct *wait) +{ + struct v4l2_fh *fh = file->private_data; + + if (v4l2_event_pending(fh)) + return POLLPRI; + poll_wait(file, &fh->wait, wait); + return 0; +} +EXPORT_SYMBOL(v4l2_ctrl_poll); diff --git a/drivers/media/video/v4l2-dev.c b/drivers/media/video/v4l2-dev.c index 96e9615663e9..041804b73ebd 100644 --- a/drivers/media/video/v4l2-dev.c +++ b/drivers/media/video/v4l2-dev.c @@ -788,7 +788,7 @@ static void __exit videodev_exit(void) unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); } -module_init(videodev_init) +subsys_initcall(videodev_init); module_exit(videodev_exit) MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab <mchehab@infradead.org>"); diff --git a/drivers/media/video/v4l2-ioctl.c b/drivers/media/video/v4l2-ioctl.c index 3f623859a337..5b2ec1fd2d0a 100644 --- a/drivers/media/video/v4l2-ioctl.c +++ b/drivers/media/video/v4l2-ioctl.c @@ -260,6 +260,8 @@ static const char *v4l2_ioctls[] = { [_IOC_NR(VIDIOC_ENCODER_CMD)] = "VIDIOC_ENCODER_CMD", [_IOC_NR(VIDIOC_TRY_ENCODER_CMD)] = "VIDIOC_TRY_ENCODER_CMD", + [_IOC_NR(VIDIOC_DECODER_CMD)] = "VIDIOC_DECODER_CMD", + [_IOC_NR(VIDIOC_TRY_DECODER_CMD)] = "VIDIOC_TRY_DECODER_CMD", [_IOC_NR(VIDIOC_DBG_S_REGISTER)] = "VIDIOC_DBG_S_REGISTER", [_IOC_NR(VIDIOC_DBG_G_REGISTER)] = "VIDIOC_DBG_G_REGISTER", @@ -540,10 +542,12 @@ static long __video_do_ioctl(struct file *file, if (!ret) dbgarg(cmd, "driver=%s, card=%s, bus=%s, " "version=0x%08x, " - "capabilities=0x%08x\n", + "capabilities=0x%08x, " + "device_caps=0x%08x\n", cap->driver, cap->card, cap->bus_info, cap->version, - cap->capabilities); + cap->capabilities, + cap->device_caps); break; } @@ -1762,6 +1766,32 @@ static long __video_do_ioctl(struct file *file, dbgarg(cmd, "cmd=%d, flags=%x\n", p->cmd, p->flags); break; } + case VIDIOC_DECODER_CMD: + { + struct v4l2_decoder_cmd *p = arg; + + if (!ops->vidioc_decoder_cmd) + break; + if (ret_prio) { + ret = ret_prio; + break; + } + ret = ops->vidioc_decoder_cmd(file, fh, p); + if (!ret) + dbgarg(cmd, "cmd=%d, flags=%x\n", p->cmd, p->flags); + break; + } + case VIDIOC_TRY_DECODER_CMD: + { + struct v4l2_decoder_cmd *p = arg; + + if (!ops->vidioc_try_decoder_cmd) + break; + ret = ops->vidioc_try_decoder_cmd(file, fh, p); + if (!ret) + dbgarg(cmd, "cmd=%d, flags=%x\n", p->cmd, p->flags); + break; + } case VIDIOC_G_PARM: { struct v4l2_streamparm *p = arg; @@ -1909,7 +1939,13 @@ static long __video_do_ioctl(struct file *file, { if (!ops->vidioc_log_status) break; + if (vfd->v4l2_dev) + pr_info("%s: ================= START STATUS =================\n", + vfd->v4l2_dev->name); ret = ops->vidioc_log_status(file, fh); + if (vfd->v4l2_dev) + pr_info("%s: ================== END STATUS ==================\n", + vfd->v4l2_dev->name); break; } #ifdef CONFIG_VIDEO_ADV_DEBUG @@ -2419,7 +2455,7 @@ video_usercopy(struct file *file, unsigned int cmd, unsigned long arg, /* Handles IOCTL */ err = func(file, cmd, parg); if (err == -ENOIOCTLCMD) - err = -EINVAL; + err = -ENOTTY; if (has_array_args) { *kernel_ptr = user_ptr; diff --git a/drivers/media/video/v4l2-subdev.c b/drivers/media/video/v4l2-subdev.c index 41d118ee2de6..6fe88e965a8c 100644 --- a/drivers/media/video/v4l2-subdev.c +++ b/drivers/media/video/v4l2-subdev.c @@ -194,8 +194,16 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg) } #endif - case VIDIOC_LOG_STATUS: - return v4l2_subdev_call(sd, core, log_status); + case VIDIOC_LOG_STATUS: { + int ret; + + pr_info("%s: ================= START STATUS =================\n", + sd->name); + ret = v4l2_subdev_call(sd, core, log_status); + pr_info("%s: ================== END STATUS ==================\n", + sd->name); + return ret; + } #if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) case VIDIOC_SUBDEV_G_FMT: { diff --git a/drivers/media/video/videobuf2-vmalloc.c b/drivers/media/video/videobuf2-vmalloc.c index 4e789a178f8a..6b5ca6c70a46 100644 --- a/drivers/media/video/videobuf2-vmalloc.c +++ b/drivers/media/video/videobuf2-vmalloc.c @@ -10,6 +10,7 @@ * the Free Software Foundation. */ +#include <linux/io.h> #include <linux/module.h> #include <linux/mm.h> #include <linux/sched.h> @@ -22,6 +23,7 @@ struct vb2_vmalloc_buf { void *vaddr; struct page **pages; + struct vm_area_struct *vma; int write; unsigned long size; unsigned int n_pages; @@ -71,6 +73,8 @@ static void *vb2_vmalloc_get_userptr(void *alloc_ctx, unsigned long vaddr, struct vb2_vmalloc_buf *buf; unsigned long first, last; int n_pages, offset; + struct vm_area_struct *vma; + dma_addr_t physp; buf = kzalloc(sizeof(*buf), GFP_KERNEL); if (!buf) @@ -80,23 +84,37 @@ static void *vb2_vmalloc_get_userptr(void *alloc_ctx, unsigned long vaddr, offset = vaddr & ~PAGE_MASK; buf->size = size; - first = vaddr >> PAGE_SHIFT; - last = (vaddr + size - 1) >> PAGE_SHIFT; - buf->n_pages = last - first + 1; - buf->pages = kzalloc(buf->n_pages * sizeof(struct page *), GFP_KERNEL); - if (!buf->pages) - goto fail_pages_array_alloc; - /* current->mm->mmap_sem is taken by videobuf2 core */ - n_pages = get_user_pages(current, current->mm, vaddr & PAGE_MASK, - buf->n_pages, write, 1, /* force */ - buf->pages, NULL); - if (n_pages != buf->n_pages) - goto fail_get_user_pages; - - buf->vaddr = vm_map_ram(buf->pages, buf->n_pages, -1, PAGE_KERNEL); - if (!buf->vaddr) - goto fail_get_user_pages; + vma = find_vma(current->mm, vaddr); + if (vma && (vma->vm_flags & VM_PFNMAP) && (vma->vm_pgoff)) { + if (vb2_get_contig_userptr(vaddr, size, &vma, &physp)) + goto fail_pages_array_alloc; + buf->vma = vma; + buf->vaddr = ioremap_nocache(physp, size); + if (!buf->vaddr) + goto fail_pages_array_alloc; + } else { + first = vaddr >> PAGE_SHIFT; + last = (vaddr + size - 1) >> PAGE_SHIFT; + buf->n_pages = last - first + 1; + buf->pages = kzalloc(buf->n_pages * sizeof(struct page *), + GFP_KERNEL); + if (!buf->pages) + goto fail_pages_array_alloc; + + /* current->mm->mmap_sem is taken by videobuf2 core */ + n_pages = get_user_pages(current, current->mm, + vaddr & PAGE_MASK, buf->n_pages, + write, 1, /* force */ + buf->pages, NULL); + if (n_pages != buf->n_pages) + goto fail_get_user_pages; + + buf->vaddr = vm_map_ram(buf->pages, buf->n_pages, -1, + PAGE_KERNEL); + if (!buf->vaddr) + goto fail_get_user_pages; + } buf->vaddr += offset; return buf; @@ -120,14 +138,20 @@ static void vb2_vmalloc_put_userptr(void *buf_priv) unsigned long vaddr = (unsigned long)buf->vaddr & PAGE_MASK; unsigned int i; - if (vaddr) - vm_unmap_ram((void *)vaddr, buf->n_pages); - for (i = 0; i < buf->n_pages; ++i) { - if (buf->write) - set_page_dirty_lock(buf->pages[i]); - put_page(buf->pages[i]); + if (buf->pages) { + if (vaddr) + vm_unmap_ram((void *)vaddr, buf->n_pages); + for (i = 0; i < buf->n_pages; ++i) { + if (buf->write) + set_page_dirty_lock(buf->pages[i]); + put_page(buf->pages[i]); + } + kfree(buf->pages); + } else { + if (buf->vma) + vb2_put_vma(buf->vma); + iounmap(buf->vaddr); } - kfree(buf->pages); kfree(buf); } diff --git a/drivers/media/video/vivi.c b/drivers/media/video/vivi.c index 7d754fbcccbf..5e8b0710105b 100644 --- a/drivers/media/video/vivi.c +++ b/drivers/media/video/vivi.c @@ -819,8 +819,9 @@ static int vidioc_querycap(struct file *file, void *priv, strcpy(cap->driver, "vivi"); strcpy(cap->card, "vivi"); strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info)); - cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | \ + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -958,14 +959,6 @@ static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) return vb2_streamoff(&dev->vb_vidq, i); } -static int vidioc_log_status(struct file *file, void *priv) -{ - struct vivi_dev *dev = video_drvdata(file); - - v4l2_ctrl_handler_log_status(&dev->ctrl_handler, dev->v4l2_dev.name); - return 0; -} - static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *i) { return 0; @@ -1008,17 +1001,6 @@ static int vidioc_s_input(struct file *file, void *priv, unsigned int i) return 0; } -static int vidioc_subscribe_event(struct v4l2_fh *fh, - struct v4l2_event_subscription *sub) -{ - switch (sub->type) { - case V4L2_EVENT_CTRL: - return v4l2_event_subscribe(fh, sub, 0); - default: - return -EINVAL; - } -} - /* --- controls ---------------------------------------------- */ static int vivi_g_volatile_ctrl(struct v4l2_ctrl *ctrl) @@ -1209,8 +1191,8 @@ static const struct v4l2_ioctl_ops vivi_ioctl_ops = { .vidioc_s_input = vidioc_s_input, .vidioc_streamon = vidioc_streamon, .vidioc_streamoff = vidioc_streamoff, - .vidioc_log_status = vidioc_log_status, - .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; diff --git a/drivers/media/video/vp27smpx.c b/drivers/media/video/vp27smpx.c index c15efb6e7771..7cfbc9d94a48 100644 --- a/drivers/media/video/vp27smpx.c +++ b/drivers/media/video/vp27smpx.c @@ -208,15 +208,4 @@ static struct i2c_driver vp27smpx_driver = { .id_table = vp27smpx_id, }; -static __init int init_vp27smpx(void) -{ - return i2c_add_driver(&vp27smpx_driver); -} - -static __exit void exit_vp27smpx(void) -{ - i2c_del_driver(&vp27smpx_driver); -} - -module_init(init_vp27smpx); -module_exit(exit_vp27smpx); +module_i2c_driver(vp27smpx_driver); diff --git a/drivers/media/video/vpx3220.c b/drivers/media/video/vpx3220.c index e5cad6ff64a1..2f67b4c5c823 100644 --- a/drivers/media/video/vpx3220.c +++ b/drivers/media/video/vpx3220.c @@ -588,15 +588,4 @@ static struct i2c_driver vpx3220_driver = { .id_table = vpx3220_id, }; -static __init int init_vpx3220(void) -{ - return i2c_add_driver(&vpx3220_driver); -} - -static __exit void exit_vpx3220(void) -{ - i2c_del_driver(&vpx3220_driver); -} - -module_init(init_vpx3220); -module_exit(exit_vpx3220); +module_i2c_driver(vpx3220_driver); diff --git a/drivers/media/video/vs6624.c b/drivers/media/video/vs6624.c new file mode 100644 index 000000000000..42ae9dc9c574 --- /dev/null +++ b/drivers/media/video/vs6624.c @@ -0,0 +1,928 @@ +/* + * vs6624.c ST VS6624 CMOS image sensor driver + * + * Copyright (c) 2011 Analog Devices Inc. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/videodev2.h> + +#include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mediabus.h> + +#include "vs6624_regs.h" + +#define VGA_WIDTH 640 +#define VGA_HEIGHT 480 +#define QVGA_WIDTH 320 +#define QVGA_HEIGHT 240 +#define QQVGA_WIDTH 160 +#define QQVGA_HEIGHT 120 +#define CIF_WIDTH 352 +#define CIF_HEIGHT 288 +#define QCIF_WIDTH 176 +#define QCIF_HEIGHT 144 +#define QQCIF_WIDTH 88 +#define QQCIF_HEIGHT 72 + +#define MAX_FRAME_RATE 30 + +struct vs6624 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct v4l2_fract frame_rate; + struct v4l2_mbus_framefmt fmt; + unsigned ce_pin; +}; + +static const struct vs6624_format { + enum v4l2_mbus_pixelcode mbus_code; + enum v4l2_colorspace colorspace; +} vs6624_formats[] = { + { + .mbus_code = V4L2_MBUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .mbus_code = V4L2_MBUS_FMT_RGB565_2X8_LE, + .colorspace = V4L2_COLORSPACE_SRGB, + }, +}; + +static struct v4l2_mbus_framefmt vs6624_default_fmt = { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .code = V4L2_MBUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, +}; + +static const u16 vs6624_p1[] = { + 0x8104, 0x03, + 0x8105, 0x01, + 0xc900, 0x03, + 0xc904, 0x47, + 0xc905, 0x10, + 0xc906, 0x80, + 0xc907, 0x3a, + 0x903a, 0x02, + 0x903b, 0x47, + 0x903c, 0x15, + 0xc908, 0x31, + 0xc909, 0xdc, + 0xc90a, 0x80, + 0xc90b, 0x44, + 0x9044, 0x02, + 0x9045, 0x31, + 0x9046, 0xe2, + 0xc90c, 0x07, + 0xc90d, 0xe0, + 0xc90e, 0x80, + 0xc90f, 0x47, + 0x9047, 0x90, + 0x9048, 0x83, + 0x9049, 0x81, + 0x904a, 0xe0, + 0x904b, 0x60, + 0x904c, 0x08, + 0x904d, 0x90, + 0x904e, 0xc0, + 0x904f, 0x43, + 0x9050, 0x74, + 0x9051, 0x01, + 0x9052, 0xf0, + 0x9053, 0x80, + 0x9054, 0x05, + 0x9055, 0xE4, + 0x9056, 0x90, + 0x9057, 0xc0, + 0x9058, 0x43, + 0x9059, 0xf0, + 0x905a, 0x02, + 0x905b, 0x07, + 0x905c, 0xec, + 0xc910, 0x5d, + 0xc911, 0xca, + 0xc912, 0x80, + 0xc913, 0x5d, + 0x905d, 0xa3, + 0x905e, 0x04, + 0x905f, 0xf0, + 0x9060, 0xa3, + 0x9061, 0x04, + 0x9062, 0xf0, + 0x9063, 0x22, + 0xc914, 0x72, + 0xc915, 0x92, + 0xc916, 0x80, + 0xc917, 0x64, + 0x9064, 0x74, + 0x9065, 0x01, + 0x9066, 0x02, + 0x9067, 0x72, + 0x9068, 0x95, + 0xc918, 0x47, + 0xc919, 0xf2, + 0xc91a, 0x81, + 0xc91b, 0x69, + 0x9169, 0x74, + 0x916a, 0x02, + 0x916b, 0xf0, + 0x916c, 0xec, + 0x916d, 0xb4, + 0x916e, 0x10, + 0x916f, 0x0a, + 0x9170, 0x90, + 0x9171, 0x80, + 0x9172, 0x16, + 0x9173, 0xe0, + 0x9174, 0x70, + 0x9175, 0x04, + 0x9176, 0x90, + 0x9177, 0xd3, + 0x9178, 0xc4, + 0x9179, 0xf0, + 0x917a, 0x22, + 0xc91c, 0x0a, + 0xc91d, 0xbe, + 0xc91e, 0x80, + 0xc91f, 0x73, + 0x9073, 0xfc, + 0x9074, 0xa3, + 0x9075, 0xe0, + 0x9076, 0xf5, + 0x9077, 0x82, + 0x9078, 0x8c, + 0x9079, 0x83, + 0x907a, 0xa3, + 0x907b, 0xa3, + 0x907c, 0xe0, + 0x907d, 0xfc, + 0x907e, 0xa3, + 0x907f, 0xe0, + 0x9080, 0xc3, + 0x9081, 0x9f, + 0x9082, 0xff, + 0x9083, 0xec, + 0x9084, 0x9e, + 0x9085, 0xfe, + 0x9086, 0x02, + 0x9087, 0x0a, + 0x9088, 0xea, + 0xc920, 0x47, + 0xc921, 0x38, + 0xc922, 0x80, + 0xc923, 0x89, + 0x9089, 0xec, + 0x908a, 0xd3, + 0x908b, 0x94, + 0x908c, 0x20, + 0x908d, 0x40, + 0x908e, 0x01, + 0x908f, 0x1c, + 0x9090, 0x90, + 0x9091, 0xd3, + 0x9092, 0xd4, + 0x9093, 0xec, + 0x9094, 0xf0, + 0x9095, 0x02, + 0x9096, 0x47, + 0x9097, 0x3d, + 0xc924, 0x45, + 0xc925, 0xca, + 0xc926, 0x80, + 0xc927, 0x98, + 0x9098, 0x12, + 0x9099, 0x77, + 0x909a, 0xd6, + 0x909b, 0x02, + 0x909c, 0x45, + 0x909d, 0xcd, + 0xc928, 0x20, + 0xc929, 0xd5, + 0xc92a, 0x80, + 0xc92b, 0x9e, + 0x909e, 0x90, + 0x909f, 0x82, + 0x90a0, 0x18, + 0x90a1, 0xe0, + 0x90a2, 0xb4, + 0x90a3, 0x03, + 0x90a4, 0x0e, + 0x90a5, 0x90, + 0x90a6, 0x83, + 0x90a7, 0xbf, + 0x90a8, 0xe0, + 0x90a9, 0x60, + 0x90aa, 0x08, + 0x90ab, 0x90, + 0x90ac, 0x81, + 0x90ad, 0xfc, + 0x90ae, 0xe0, + 0x90af, 0xff, + 0x90b0, 0xc3, + 0x90b1, 0x13, + 0x90b2, 0xf0, + 0x90b3, 0x90, + 0x90b4, 0x81, + 0x90b5, 0xfc, + 0x90b6, 0xe0, + 0x90b7, 0xff, + 0x90b8, 0x02, + 0x90b9, 0x20, + 0x90ba, 0xda, + 0xc92c, 0x70, + 0xc92d, 0xbc, + 0xc92e, 0x80, + 0xc92f, 0xbb, + 0x90bb, 0x90, + 0x90bc, 0x82, + 0x90bd, 0x18, + 0x90be, 0xe0, + 0x90bf, 0xb4, + 0x90c0, 0x03, + 0x90c1, 0x06, + 0x90c2, 0x90, + 0x90c3, 0xc1, + 0x90c4, 0x06, + 0x90c5, 0x74, + 0x90c6, 0x05, + 0x90c7, 0xf0, + 0x90c8, 0x90, + 0x90c9, 0xd3, + 0x90ca, 0xa0, + 0x90cb, 0x02, + 0x90cc, 0x70, + 0x90cd, 0xbf, + 0xc930, 0x72, + 0xc931, 0x21, + 0xc932, 0x81, + 0xc933, 0x3b, + 0x913b, 0x7d, + 0x913c, 0x02, + 0x913d, 0x7f, + 0x913e, 0x7b, + 0x913f, 0x02, + 0x9140, 0x72, + 0x9141, 0x25, + 0xc934, 0x28, + 0xc935, 0xae, + 0xc936, 0x80, + 0xc937, 0xd2, + 0x90d2, 0xf0, + 0x90d3, 0x90, + 0x90d4, 0xd2, + 0x90d5, 0x0a, + 0x90d6, 0x02, + 0x90d7, 0x28, + 0x90d8, 0xb4, + 0xc938, 0x28, + 0xc939, 0xb1, + 0xc93a, 0x80, + 0xc93b, 0xd9, + 0x90d9, 0x90, + 0x90da, 0x83, + 0x90db, 0xba, + 0x90dc, 0xe0, + 0x90dd, 0xff, + 0x90de, 0x90, + 0x90df, 0xd2, + 0x90e0, 0x08, + 0x90e1, 0xe0, + 0x90e2, 0xe4, + 0x90e3, 0xef, + 0x90e4, 0xf0, + 0x90e5, 0xa3, + 0x90e6, 0xe0, + 0x90e7, 0x74, + 0x90e8, 0xff, + 0x90e9, 0xf0, + 0x90ea, 0x90, + 0x90eb, 0xd2, + 0x90ec, 0x0a, + 0x90ed, 0x02, + 0x90ee, 0x28, + 0x90ef, 0xb4, + 0xc93c, 0x29, + 0xc93d, 0x79, + 0xc93e, 0x80, + 0xc93f, 0xf0, + 0x90f0, 0xf0, + 0x90f1, 0x90, + 0x90f2, 0xd2, + 0x90f3, 0x0e, + 0x90f4, 0x02, + 0x90f5, 0x29, + 0x90f6, 0x7f, + 0xc940, 0x29, + 0xc941, 0x7c, + 0xc942, 0x80, + 0xc943, 0xf7, + 0x90f7, 0x90, + 0x90f8, 0x83, + 0x90f9, 0xba, + 0x90fa, 0xe0, + 0x90fb, 0xff, + 0x90fc, 0x90, + 0x90fd, 0xd2, + 0x90fe, 0x0c, + 0x90ff, 0xe0, + 0x9100, 0xe4, + 0x9101, 0xef, + 0x9102, 0xf0, + 0x9103, 0xa3, + 0x9104, 0xe0, + 0x9105, 0x74, + 0x9106, 0xff, + 0x9107, 0xf0, + 0x9108, 0x90, + 0x9109, 0xd2, + 0x910a, 0x0e, + 0x910b, 0x02, + 0x910c, 0x29, + 0x910d, 0x7f, + 0xc944, 0x2a, + 0xc945, 0x42, + 0xc946, 0x81, + 0xc947, 0x0e, + 0x910e, 0xf0, + 0x910f, 0x90, + 0x9110, 0xd2, + 0x9111, 0x12, + 0x9112, 0x02, + 0x9113, 0x2a, + 0x9114, 0x48, + 0xc948, 0x2a, + 0xc949, 0x45, + 0xc94a, 0x81, + 0xc94b, 0x15, + 0x9115, 0x90, + 0x9116, 0x83, + 0x9117, 0xba, + 0x9118, 0xe0, + 0x9119, 0xff, + 0x911a, 0x90, + 0x911b, 0xd2, + 0x911c, 0x10, + 0x911d, 0xe0, + 0x911e, 0xe4, + 0x911f, 0xef, + 0x9120, 0xf0, + 0x9121, 0xa3, + 0x9122, 0xe0, + 0x9123, 0x74, + 0x9124, 0xff, + 0x9125, 0xf0, + 0x9126, 0x90, + 0x9127, 0xd2, + 0x9128, 0x12, + 0x9129, 0x02, + 0x912a, 0x2a, + 0x912b, 0x48, + 0xc900, 0x01, + 0x0000, 0x00, +}; + +static const u16 vs6624_p2[] = { + 0x806f, 0x01, + 0x058c, 0x01, + 0x0000, 0x00, +}; + +static const u16 vs6624_run_setup[] = { + 0x1d18, 0x00, /* Enableconstrainedwhitebalance */ + VS6624_PEAK_MIN_OUT_G_MSB, 0x3c, /* Damper PeakGain Output MSB */ + VS6624_PEAK_MIN_OUT_G_LSB, 0x66, /* Damper PeakGain Output LSB */ + VS6624_CM_LOW_THR_MSB, 0x65, /* Damper Low MSB */ + VS6624_CM_LOW_THR_LSB, 0xd1, /* Damper Low LSB */ + VS6624_CM_HIGH_THR_MSB, 0x66, /* Damper High MSB */ + VS6624_CM_HIGH_THR_LSB, 0x62, /* Damper High LSB */ + VS6624_CM_MIN_OUT_MSB, 0x00, /* Damper Min output MSB */ + VS6624_CM_MIN_OUT_LSB, 0x00, /* Damper Min output LSB */ + VS6624_NORA_DISABLE, 0x00, /* Nora fDisable */ + VS6624_NORA_USAGE, 0x04, /* Nora usage */ + VS6624_NORA_LOW_THR_MSB, 0x63, /* Damper Low MSB Changed 0x63 to 0x65 */ + VS6624_NORA_LOW_THR_LSB, 0xd1, /* Damper Low LSB */ + VS6624_NORA_HIGH_THR_MSB, 0x68, /* Damper High MSB */ + VS6624_NORA_HIGH_THR_LSB, 0xdd, /* Damper High LSB */ + VS6624_NORA_MIN_OUT_MSB, 0x3a, /* Damper Min output MSB */ + VS6624_NORA_MIN_OUT_LSB, 0x00, /* Damper Min output LSB */ + VS6624_F2B_DISABLE, 0x00, /* Disable */ + 0x1d8a, 0x30, /* MAXWeightHigh */ + 0x1d91, 0x62, /* fpDamperLowThresholdHigh MSB */ + 0x1d92, 0x4a, /* fpDamperLowThresholdHigh LSB */ + 0x1d95, 0x65, /* fpDamperHighThresholdHigh MSB */ + 0x1d96, 0x0e, /* fpDamperHighThresholdHigh LSB */ + 0x1da1, 0x3a, /* fpMinimumDamperOutputLow MSB */ + 0x1da2, 0xb8, /* fpMinimumDamperOutputLow LSB */ + 0x1e08, 0x06, /* MAXWeightLow */ + 0x1e0a, 0x0a, /* MAXWeightHigh */ + 0x1601, 0x3a, /* Red A MSB */ + 0x1602, 0x14, /* Red A LSB */ + 0x1605, 0x3b, /* Blue A MSB */ + 0x1606, 0x85, /* BLue A LSB */ + 0x1609, 0x3b, /* RED B MSB */ + 0x160a, 0x85, /* RED B LSB */ + 0x160d, 0x3a, /* Blue B MSB */ + 0x160e, 0x14, /* Blue B LSB */ + 0x1611, 0x30, /* Max Distance from Locus MSB */ + 0x1612, 0x8f, /* Max Distance from Locus MSB */ + 0x1614, 0x01, /* Enable constrainer */ + 0x0000, 0x00, +}; + +static const u16 vs6624_default[] = { + VS6624_CONTRAST0, 0x84, + VS6624_SATURATION0, 0x75, + VS6624_GAMMA0, 0x11, + VS6624_CONTRAST1, 0x84, + VS6624_SATURATION1, 0x75, + VS6624_GAMMA1, 0x11, + VS6624_MAN_RG, 0x80, + VS6624_MAN_GG, 0x80, + VS6624_MAN_BG, 0x80, + VS6624_WB_MODE, 0x1, + VS6624_EXPO_COMPENSATION, 0xfe, + VS6624_EXPO_METER, 0x0, + VS6624_LIGHT_FREQ, 0x64, + VS6624_PEAK_GAIN, 0xe, + VS6624_PEAK_LOW_THR, 0x28, + VS6624_HMIRROR0, 0x0, + VS6624_VFLIP0, 0x0, + VS6624_ZOOM_HSTEP0_MSB, 0x0, + VS6624_ZOOM_HSTEP0_LSB, 0x1, + VS6624_ZOOM_VSTEP0_MSB, 0x0, + VS6624_ZOOM_VSTEP0_LSB, 0x1, + VS6624_PAN_HSTEP0_MSB, 0x0, + VS6624_PAN_HSTEP0_LSB, 0xf, + VS6624_PAN_VSTEP0_MSB, 0x0, + VS6624_PAN_VSTEP0_LSB, 0xf, + VS6624_SENSOR_MODE, 0x1, + VS6624_SYNC_CODE_SETUP, 0x21, + VS6624_DISABLE_FR_DAMPER, 0x0, + VS6624_FR_DEN, 0x1, + VS6624_FR_NUM_LSB, 0xf, + VS6624_INIT_PIPE_SETUP, 0x0, + VS6624_IMG_FMT0, 0x0, + VS6624_YUV_SETUP, 0x1, + VS6624_IMAGE_SIZE0, 0x2, + 0x0000, 0x00, +}; + +static inline struct vs6624 *to_vs6624(struct v4l2_subdev *sd) +{ + return container_of(sd, struct vs6624, sd); +} +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct vs6624, hdl)->sd; +} + +static int vs6624_read(struct v4l2_subdev *sd, u16 index) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buf[2]; + + buf[0] = index >> 8; + buf[1] = index; + i2c_master_send(client, buf, 2); + i2c_master_recv(client, buf, 1); + + return buf[0]; +} + +static int vs6624_write(struct v4l2_subdev *sd, u16 index, + u8 value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + u8 buf[3]; + + buf[0] = index >> 8; + buf[1] = index; + buf[2] = value; + + return i2c_master_send(client, buf, 3); +} + +static int vs6624_writeregs(struct v4l2_subdev *sd, const u16 *regs) +{ + u16 reg; + u8 data; + + while (*regs != 0x00) { + reg = *regs++; + data = *regs++; + + vs6624_write(sd, reg, data); + } + return 0; +} + +static int vs6624_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_CONTRAST: + vs6624_write(sd, VS6624_CONTRAST0, ctrl->val); + break; + case V4L2_CID_SATURATION: + vs6624_write(sd, VS6624_SATURATION0, ctrl->val); + break; + case V4L2_CID_HFLIP: + vs6624_write(sd, VS6624_HMIRROR0, ctrl->val); + break; + case V4L2_CID_VFLIP: + vs6624_write(sd, VS6624_VFLIP0, ctrl->val); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vs6624_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(vs6624_formats)) + return -EINVAL; + + *code = vs6624_formats[index].mbus_code; + return 0; +} + +static int vs6624_try_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + int index; + + for (index = 0; index < ARRAY_SIZE(vs6624_formats); index++) + if (vs6624_formats[index].mbus_code == fmt->code) + break; + if (index >= ARRAY_SIZE(vs6624_formats)) { + /* default to first format */ + index = 0; + fmt->code = vs6624_formats[0].mbus_code; + } + + /* sensor mode is VGA */ + if (fmt->width > VGA_WIDTH) + fmt->width = VGA_WIDTH; + if (fmt->height > VGA_HEIGHT) + fmt->height = VGA_HEIGHT; + fmt->width = fmt->width & (~3); + fmt->height = fmt->height & (~3); + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = vs6624_formats[index].colorspace; + return 0; +} + +static int vs6624_s_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct vs6624 *sensor = to_vs6624(sd); + int ret; + + ret = vs6624_try_mbus_fmt(sd, fmt); + if (ret) + return ret; + + /* set image format */ + switch (fmt->code) { + case V4L2_MBUS_FMT_UYVY8_2X8: + vs6624_write(sd, VS6624_IMG_FMT0, 0x0); + vs6624_write(sd, VS6624_YUV_SETUP, 0x1); + break; + case V4L2_MBUS_FMT_YUYV8_2X8: + vs6624_write(sd, VS6624_IMG_FMT0, 0x0); + vs6624_write(sd, VS6624_YUV_SETUP, 0x3); + break; + case V4L2_MBUS_FMT_RGB565_2X8_LE: + vs6624_write(sd, VS6624_IMG_FMT0, 0x4); + vs6624_write(sd, VS6624_RGB_SETUP, 0x0); + break; + default: + return -EINVAL; + } + + /* set image size */ + if ((fmt->width == VGA_WIDTH) && (fmt->height == VGA_HEIGHT)) + vs6624_write(sd, VS6624_IMAGE_SIZE0, 0x2); + else if ((fmt->width == QVGA_WIDTH) && (fmt->height == QVGA_HEIGHT)) + vs6624_write(sd, VS6624_IMAGE_SIZE0, 0x4); + else if ((fmt->width == QQVGA_WIDTH) && (fmt->height == QQVGA_HEIGHT)) + vs6624_write(sd, VS6624_IMAGE_SIZE0, 0x6); + else if ((fmt->width == CIF_WIDTH) && (fmt->height == CIF_HEIGHT)) + vs6624_write(sd, VS6624_IMAGE_SIZE0, 0x3); + else if ((fmt->width == QCIF_WIDTH) && (fmt->height == QCIF_HEIGHT)) + vs6624_write(sd, VS6624_IMAGE_SIZE0, 0x5); + else if ((fmt->width == QQCIF_WIDTH) && (fmt->height == QQCIF_HEIGHT)) + vs6624_write(sd, VS6624_IMAGE_SIZE0, 0x7); + else { + vs6624_write(sd, VS6624_IMAGE_SIZE0, 0x8); + vs6624_write(sd, VS6624_MAN_HSIZE0_MSB, fmt->width >> 8); + vs6624_write(sd, VS6624_MAN_HSIZE0_LSB, fmt->width & 0xFF); + vs6624_write(sd, VS6624_MAN_VSIZE0_MSB, fmt->height >> 8); + vs6624_write(sd, VS6624_MAN_VSIZE0_LSB, fmt->height & 0xFF); + vs6624_write(sd, VS6624_CROP_CTRL0, 0x1); + } + + sensor->fmt = *fmt; + + return 0; +} + +static int vs6624_g_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct vs6624 *sensor = to_vs6624(sd); + + *fmt = sensor->fmt; + return 0; +} + +static int vs6624_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct vs6624 *sensor = to_vs6624(sd); + struct v4l2_captureparm *cp = &parms->parm.capture; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memset(cp, 0, sizeof(*cp)); + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = sensor->frame_rate.denominator; + cp->timeperframe.denominator = sensor->frame_rate.numerator; + return 0; +} + +static int vs6624_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct vs6624 *sensor = to_vs6624(sd); + struct v4l2_captureparm *cp = &parms->parm.capture; + struct v4l2_fract *tpf = &cp->timeperframe; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (cp->extendedmode != 0) + return -EINVAL; + + if (tpf->numerator == 0 || tpf->denominator == 0 + || (tpf->denominator > tpf->numerator * MAX_FRAME_RATE)) { + /* reset to max frame rate */ + tpf->numerator = 1; + tpf->denominator = MAX_FRAME_RATE; + } + sensor->frame_rate.numerator = tpf->denominator; + sensor->frame_rate.denominator = tpf->numerator; + vs6624_write(sd, VS6624_DISABLE_FR_DAMPER, 0x0); + vs6624_write(sd, VS6624_FR_NUM_MSB, + sensor->frame_rate.numerator >> 8); + vs6624_write(sd, VS6624_FR_NUM_LSB, + sensor->frame_rate.numerator & 0xFF); + vs6624_write(sd, VS6624_FR_DEN, + sensor->frame_rate.denominator & 0xFF); + return 0; +} + +static int vs6624_s_stream(struct v4l2_subdev *sd, int enable) +{ + if (enable) + vs6624_write(sd, VS6624_USER_CMD, 0x2); + else + vs6624_write(sd, VS6624_USER_CMD, 0x4); + udelay(100); + return 0; +} + +static int vs6624_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + int rev; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + rev = (vs6624_read(sd, VS6624_FW_VSN_MAJOR) << 8) + | vs6624_read(sd, VS6624_FW_VSN_MINOR); + + return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_VS6624, rev); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int vs6624_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + reg->val = vs6624_read(sd, reg->reg & 0xffff); + reg->size = 1; + return 0; +} + +static int vs6624_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (!v4l2_chip_match_i2c_client(client, ®->match)) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + vs6624_write(sd, reg->reg & 0xffff, reg->val & 0xff); + return 0; +} +#endif + +static const struct v4l2_ctrl_ops vs6624_ctrl_ops = { + .s_ctrl = vs6624_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops vs6624_core_ops = { + .g_chip_ident = vs6624_g_chip_ident, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = vs6624_g_register, + .s_register = vs6624_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops vs6624_video_ops = { + .enum_mbus_fmt = vs6624_enum_mbus_fmt, + .try_mbus_fmt = vs6624_try_mbus_fmt, + .s_mbus_fmt = vs6624_s_mbus_fmt, + .g_mbus_fmt = vs6624_g_mbus_fmt, + .s_parm = vs6624_s_parm, + .g_parm = vs6624_g_parm, + .s_stream = vs6624_s_stream, +}; + +static const struct v4l2_subdev_ops vs6624_ops = { + .core = &vs6624_core_ops, + .video = &vs6624_video_ops, +}; + +static int __devinit vs6624_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vs6624 *sensor; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + const unsigned *ce; + int ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EIO; + + ce = client->dev.platform_data; + if (ce == NULL) + return -EINVAL; + + ret = gpio_request(*ce, "VS6624 Chip Enable"); + if (ret) { + v4l_err(client, "failed to request GPIO %d\n", *ce); + return ret; + } + gpio_direction_output(*ce, 1); + /* wait 100ms before any further i2c writes are performed */ + mdelay(100); + + sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); + if (sensor == NULL) { + gpio_free(*ce); + return -ENOMEM; + } + + sd = &sensor->sd; + v4l2_i2c_subdev_init(sd, client, &vs6624_ops); + + vs6624_writeregs(sd, vs6624_p1); + vs6624_write(sd, VS6624_MICRO_EN, 0x2); + vs6624_write(sd, VS6624_DIO_EN, 0x1); + mdelay(10); + vs6624_writeregs(sd, vs6624_p2); + + vs6624_writeregs(sd, vs6624_default); + vs6624_write(sd, VS6624_HSYNC_SETUP, 0xF); + vs6624_writeregs(sd, vs6624_run_setup); + + /* set frame rate */ + sensor->frame_rate.numerator = MAX_FRAME_RATE; + sensor->frame_rate.denominator = 1; + vs6624_write(sd, VS6624_DISABLE_FR_DAMPER, 0x0); + vs6624_write(sd, VS6624_FR_NUM_MSB, + sensor->frame_rate.numerator >> 8); + vs6624_write(sd, VS6624_FR_NUM_LSB, + sensor->frame_rate.numerator & 0xFF); + vs6624_write(sd, VS6624_FR_DEN, + sensor->frame_rate.denominator & 0xFF); + + sensor->fmt = vs6624_default_fmt; + sensor->ce_pin = *ce; + + v4l_info(client, "chip found @ 0x%02x (%s)\n", + client->addr << 1, client->adapter->name); + + hdl = &sensor->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &vs6624_ctrl_ops, + V4L2_CID_CONTRAST, 0, 0xFF, 1, 0x87); + v4l2_ctrl_new_std(hdl, &vs6624_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xFF, 1, 0x78); + v4l2_ctrl_new_std(hdl, &vs6624_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &vs6624_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + /* hook the control handler into the driver */ + sd->ctrl_handler = hdl; + if (hdl->error) { + int err = hdl->error; + + v4l2_ctrl_handler_free(hdl); + kfree(sensor); + gpio_free(*ce); + return err; + } + + /* initialize the hardware to the default control values */ + ret = v4l2_ctrl_handler_setup(hdl); + if (ret) { + v4l2_ctrl_handler_free(hdl); + kfree(sensor); + gpio_free(*ce); + } + return ret; +} + +static int __devexit vs6624_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vs6624 *sensor = to_vs6624(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + gpio_free(sensor->ce_pin); + kfree(sensor); + return 0; +} + +static const struct i2c_device_id vs6624_id[] = { + {"vs6624", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, vs6624_id); + +static struct i2c_driver vs6624_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "vs6624", + }, + .probe = vs6624_probe, + .remove = __devexit_p(vs6624_remove), + .id_table = vs6624_id, +}; + +static __init int vs6624_init(void) +{ + return i2c_add_driver(&vs6624_driver); +} + +static __exit void vs6624_exit(void) +{ + i2c_del_driver(&vs6624_driver); +} + +module_init(vs6624_init); +module_exit(vs6624_exit); + +MODULE_DESCRIPTION("VS6624 sensor driver"); +MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/vs6624_regs.h b/drivers/media/video/vs6624_regs.h new file mode 100644 index 000000000000..6ba2ee25827e --- /dev/null +++ b/drivers/media/video/vs6624_regs.h @@ -0,0 +1,337 @@ +/* + * vs6624 - ST VS6624 CMOS image sensor registers + * + * Copyright (c) 2011 Analog Devices Inc. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _VS6624_REGS_H_ +#define _VS6624_REGS_H_ + +/* low level control registers */ +#define VS6624_MICRO_EN 0xC003 /* power enable for all MCU clock */ +#define VS6624_DIO_EN 0xC044 /* enable digital I/O */ +/* device parameters */ +#define VS6624_DEV_ID_MSB 0x0001 /* device id MSB */ +#define VS6624_DEV_ID_LSB 0x0002 /* device id LSB */ +#define VS6624_FW_VSN_MAJOR 0x0004 /* firmware version major */ +#define VS6624_FW_VSN_MINOR 0x0006 /* firmware version minor */ +#define VS6624_PATCH_VSN_MAJOR 0x0008 /* patch version major */ +#define VS6624_PATCH_VSN_MINOR 0x000A /* patch version minor */ +/* host interface manager control */ +#define VS6624_USER_CMD 0x0180 /* user level control of operating states */ +/* host interface manager status */ +#define VS6624_STATE 0x0202 /* current state of the mode manager */ +/* run mode control */ +#define VS6624_METER_ON 0x0280 /* if false AE and AWB are disabled */ +/* mode setup */ +#define VS6624_ACTIVE_PIPE_SETUP 0x0302 /* select the active bank for non view live mode */ +#define VS6624_SENSOR_MODE 0x0308 /* select the different sensor mode */ +/* pipe setup bank0 */ +#define VS6624_IMAGE_SIZE0 0x0380 /* required output dimension */ +#define VS6624_MAN_HSIZE0_MSB 0x0383 /* input required manual H size MSB */ +#define VS6624_MAN_HSIZE0_LSB 0x0384 /* input required manual H size LSB */ +#define VS6624_MAN_VSIZE0_MSB 0x0387 /* input required manual V size MSB */ +#define VS6624_MAN_VSIZE0_LSB 0x0388 /* input required manual V size LSB */ +#define VS6624_ZOOM_HSTEP0_MSB 0x038B /* set the zoom H step MSB */ +#define VS6624_ZOOM_HSTEP0_LSB 0x038C /* set the zoom H step LSB */ +#define VS6624_ZOOM_VSTEP0_MSB 0x038F /* set the zoom V step MSB */ +#define VS6624_ZOOM_VSTEP0_LSB 0x0390 /* set the zoom V step LSB */ +#define VS6624_ZOOM_CTRL0 0x0392 /* control zoon in, out and stop */ +#define VS6624_PAN_HSTEP0_MSB 0x0395 /* set the pan H step MSB */ +#define VS6624_PAN_HSTEP0_LSB 0x0396 /* set the pan H step LSB */ +#define VS6624_PAN_VSTEP0_MSB 0x0399 /* set the pan V step MSB */ +#define VS6624_PAN_VSTEP0_LSB 0x039A /* set the pan V step LSB */ +#define VS6624_PAN_CTRL0 0x039C /* control pan operation */ +#define VS6624_CROP_CTRL0 0x039E /* select cropping mode */ +#define VS6624_CROP_HSTART0_MSB 0x03A1 /* set the cropping H start address MSB */ +#define VS6624_CROP_HSTART0_LSB 0x03A2 /* set the cropping H start address LSB */ +#define VS6624_CROP_HSIZE0_MSB 0x03A5 /* set the cropping H size MSB */ +#define VS6624_CROP_HSIZE0_LSB 0x03A6 /* set the cropping H size LSB */ +#define VS6624_CROP_VSTART0_MSB 0x03A9 /* set the cropping V start address MSB */ +#define VS6624_CROP_VSTART0_LSB 0x03AA /* set the cropping V start address LSB */ +#define VS6624_CROP_VSIZE0_MSB 0x03AD /* set the cropping V size MSB */ +#define VS6624_CROP_VSIZE0_LSB 0x03AE /* set the cropping V size LSB */ +#define VS6624_IMG_FMT0 0x03B0 /* select required output image format */ +#define VS6624_BAYER_OUT_ALIGN0 0x03B2 /* set bayer output alignment */ +#define VS6624_CONTRAST0 0x03B4 /* contrast control for output */ +#define VS6624_SATURATION0 0x03B6 /* saturation control for output */ +#define VS6624_GAMMA0 0x03B8 /* gamma settings */ +#define VS6624_HMIRROR0 0x03BA /* horizontal image orientation flip */ +#define VS6624_VFLIP0 0x03BC /* vertical image orientation flip */ +#define VS6624_CHANNEL_ID0 0x03BE /* logical DMA channel number */ +/* pipe setup bank1 */ +#define VS6624_IMAGE_SIZE1 0x0400 /* required output dimension */ +#define VS6624_MAN_HSIZE1_MSB 0x0403 /* input required manual H size MSB */ +#define VS6624_MAN_HSIZE1_LSB 0x0404 /* input required manual H size LSB */ +#define VS6624_MAN_VSIZE1_MSB 0x0407 /* input required manual V size MSB */ +#define VS6624_MAN_VSIZE1_LSB 0x0408 /* input required manual V size LSB */ +#define VS6624_ZOOM_HSTEP1_MSB 0x040B /* set the zoom H step MSB */ +#define VS6624_ZOOM_HSTEP1_LSB 0x040C /* set the zoom H step LSB */ +#define VS6624_ZOOM_VSTEP1_MSB 0x040F /* set the zoom V step MSB */ +#define VS6624_ZOOM_VSTEP1_LSB 0x0410 /* set the zoom V step LSB */ +#define VS6624_ZOOM_CTRL1 0x0412 /* control zoon in, out and stop */ +#define VS6624_PAN_HSTEP1_MSB 0x0415 /* set the pan H step MSB */ +#define VS6624_PAN_HSTEP1_LSB 0x0416 /* set the pan H step LSB */ +#define VS6624_PAN_VSTEP1_MSB 0x0419 /* set the pan V step MSB */ +#define VS6624_PAN_VSTEP1_LSB 0x041A /* set the pan V step LSB */ +#define VS6624_PAN_CTRL1 0x041C /* control pan operation */ +#define VS6624_CROP_CTRL1 0x041E /* select cropping mode */ +#define VS6624_CROP_HSTART1_MSB 0x0421 /* set the cropping H start address MSB */ +#define VS6624_CROP_HSTART1_LSB 0x0422 /* set the cropping H start address LSB */ +#define VS6624_CROP_HSIZE1_MSB 0x0425 /* set the cropping H size MSB */ +#define VS6624_CROP_HSIZE1_LSB 0x0426 /* set the cropping H size LSB */ +#define VS6624_CROP_VSTART1_MSB 0x0429 /* set the cropping V start address MSB */ +#define VS6624_CROP_VSTART1_LSB 0x042A /* set the cropping V start address LSB */ +#define VS6624_CROP_VSIZE1_MSB 0x042D /* set the cropping V size MSB */ +#define VS6624_CROP_VSIZE1_LSB 0x042E /* set the cropping V size LSB */ +#define VS6624_IMG_FMT1 0x0430 /* select required output image format */ +#define VS6624_BAYER_OUT_ALIGN1 0x0432 /* set bayer output alignment */ +#define VS6624_CONTRAST1 0x0434 /* contrast control for output */ +#define VS6624_SATURATION1 0x0436 /* saturation control for output */ +#define VS6624_GAMMA1 0x0438 /* gamma settings */ +#define VS6624_HMIRROR1 0x043A /* horizontal image orientation flip */ +#define VS6624_VFLIP1 0x043C /* vertical image orientation flip */ +#define VS6624_CHANNEL_ID1 0x043E /* logical DMA channel number */ +/* view live control */ +#define VS6624_VIEW_LIVE_EN 0x0480 /* enable view live mode */ +#define VS6624_INIT_PIPE_SETUP 0x0482 /* select initial pipe setup bank */ +/* view live status */ +#define VS6624_CUR_PIPE_SETUP 0x0500 /* indicates most recently applied setup bank */ +/* power management */ +#define VS6624_TIME_TO_POWER_DOWN 0x0580 /* automatically transition time to stop mode */ +/* video timing parameter host inputs */ +#define VS6624_EXT_CLK_FREQ_NUM_MSB 0x0605 /* external clock frequency numerator MSB */ +#define VS6624_EXT_CLK_FREQ_NUM_LSB 0x0606 /* external clock frequency numerator LSB */ +#define VS6624_EXT_CLK_FREQ_DEN 0x0608 /* external clock frequency denominator */ +/* video timing control */ +#define VS6624_SYS_CLK_MODE 0x0880 /* decides system clock frequency */ +/* frame dimension parameter host inputs */ +#define VS6624_LIGHT_FREQ 0x0C80 /* AC frequency used for flicker free time */ +#define VS6624_FLICKER_COMPAT 0x0C82 /* flicker compatible frame length */ +/* static frame rate control */ +#define VS6624_FR_NUM_MSB 0x0D81 /* desired frame rate numerator MSB */ +#define VS6624_FR_NUM_LSB 0x0D82 /* desired frame rate numerator LSB */ +#define VS6624_FR_DEN 0x0D84 /* desired frame rate denominator */ +/* automatic frame rate control */ +#define VS6624_DISABLE_FR_DAMPER 0x0E80 /* defines frame rate mode */ +#define VS6624_MIN_DAMPER_OUT_MSB 0x0E8C /* minimum frame rate MSB */ +#define VS6624_MIN_DAMPER_OUT_LSB 0x0E8A /* minimum frame rate LSB */ +/* exposure controls */ +#define VS6624_EXPO_MODE 0x1180 /* exposure mode */ +#define VS6624_EXPO_METER 0x1182 /* weights to be associated with the zones */ +#define VS6624_EXPO_TIME_NUM 0x1184 /* exposure time numerator */ +#define VS6624_EXPO_TIME_DEN 0x1186 /* exposure time denominator */ +#define VS6624_EXPO_TIME_MSB 0x1189 /* exposure time for the Manual Mode MSB */ +#define VS6624_EXPO_TIME_LSB 0x118A /* exposure time for the Manual Mode LSB */ +#define VS6624_EXPO_COMPENSATION 0x1190 /* exposure compensation */ +#define VS6624_DIRECT_COARSE_MSB 0x1195 /* coarse integration lines for Direct Mode MSB */ +#define VS6624_DIRECT_COARSE_LSB 0x1196 /* coarse integration lines for Direct Mode LSB */ +#define VS6624_DIRECT_FINE_MSB 0x1199 /* fine integration pixels for Direct Mode MSB */ +#define VS6624_DIRECT_FINE_LSB 0x119A /* fine integration pixels for Direct Mode LSB */ +#define VS6624_DIRECT_ANAL_GAIN_MSB 0x119D /* analog gain for Direct Mode MSB */ +#define VS6624_DIRECT_ANAL_GAIN_LSB 0x119E /* analog gain for Direct Mode LSB */ +#define VS6624_DIRECT_DIGI_GAIN_MSB 0x11A1 /* digital gain for Direct Mode MSB */ +#define VS6624_DIRECT_DIGI_GAIN_LSB 0x11A2 /* digital gain for Direct Mode LSB */ +#define VS6624_FLASH_COARSE_MSB 0x11A5 /* coarse integration lines for Flash Gun Mode MSB */ +#define VS6624_FLASH_COARSE_LSB 0x11A6 /* coarse integration lines for Flash Gun Mode LSB */ +#define VS6624_FLASH_FINE_MSB 0x11A9 /* fine integration pixels for Flash Gun Mode MSB */ +#define VS6624_FLASH_FINE_LSB 0x11AA /* fine integration pixels for Flash Gun Mode LSB */ +#define VS6624_FLASH_ANAL_GAIN_MSB 0x11AD /* analog gain for Flash Gun Mode MSB */ +#define VS6624_FLASH_ANAL_GAIN_LSB 0x11AE /* analog gain for Flash Gun Mode LSB */ +#define VS6624_FLASH_DIGI_GAIN_MSB 0x11B1 /* digital gain for Flash Gun Mode MSB */ +#define VS6624_FLASH_DIGI_GAIN_LSB 0x11B2 /* digital gain for Flash Gun Mode LSB */ +#define VS6624_FREEZE_AE 0x11B4 /* freeze auto exposure */ +#define VS6624_MAX_INT_TIME_MSB 0x11B7 /* user maximum integration time MSB */ +#define VS6624_MAX_INT_TIME_LSB 0x11B8 /* user maximum integration time LSB */ +#define VS6624_FLASH_AG_THR_MSB 0x11BB /* recommend flash gun analog gain threshold MSB */ +#define VS6624_FLASH_AG_THR_LSB 0x11BC /* recommend flash gun analog gain threshold LSB */ +#define VS6624_ANTI_FLICKER_MODE 0x11C0 /* anti flicker mode */ +/* white balance control */ +#define VS6624_WB_MODE 0x1480 /* set white balance mode */ +#define VS6624_MAN_RG 0x1482 /* user setting for red channel gain */ +#define VS6624_MAN_GG 0x1484 /* user setting for green channel gain */ +#define VS6624_MAN_BG 0x1486 /* user setting for blue channel gain */ +#define VS6624_FLASH_RG_MSB 0x148B /* red gain for Flash Gun MSB */ +#define VS6624_FLASH_RG_LSB 0x148C /* red gain for Flash Gun LSB */ +#define VS6624_FLASH_GG_MSB 0x148F /* green gain for Flash Gun MSB */ +#define VS6624_FLASH_GG_LSB 0x1490 /* green gain for Flash Gun LSB */ +#define VS6624_FLASH_BG_MSB 0x1493 /* blue gain for Flash Gun MSB */ +#define VS6624_FLASH_BG_LSB 0x1494 /* blue gain for Flash Gun LSB */ +/* sensor setup */ +#define VS6624_BC_OFFSET 0x1990 /* Black Correction Offset */ +/* image stability */ +#define VS6624_STABLE_WB 0x1900 /* white balance stable */ +#define VS6624_STABLE_EXPO 0x1902 /* exposure stable */ +#define VS6624_STABLE 0x1906 /* system stable */ +/* flash control */ +#define VS6624_FLASH_MODE 0x1A80 /* flash mode */ +#define VS6624_FLASH_OFF_LINE_MSB 0x1A83 /* off line at flash pulse mode MSB */ +#define VS6624_FLASH_OFF_LINE_LSB 0x1A84 /* off line at flash pulse mode LSB */ +/* flash status */ +#define VS6624_FLASH_RECOM 0x1B00 /* flash gun is recommended */ +#define VS6624_FLASH_GRAB_COMPLETE 0x1B02 /* flash gun image has been grabbed */ +/* scythe filter controls */ +#define VS6624_SCYTHE_FILTER 0x1D80 /* disable scythe defect correction */ +/* jack filter controls */ +#define VS6624_JACK_FILTER 0x1E00 /* disable jack defect correction */ +/* demosaic control */ +#define VS6624_ANTI_ALIAS_FILTER 0x1E80 /* anti alias filter suppress */ +/* color matrix dampers */ +#define VS6624_CM_DISABLE 0x1F00 /* disable color matrix damper */ +#define VS6624_CM_LOW_THR_MSB 0x1F03 /* low threshold for exposure MSB */ +#define VS6624_CM_LOW_THR_LSB 0x1F04 /* low threshold for exposure LSB */ +#define VS6624_CM_HIGH_THR_MSB 0x1F07 /* high threshold for exposure MSB */ +#define VS6624_CM_HIGH_THR_LSB 0x1F08 /* high threshold for exposure LSB */ +#define VS6624_CM_MIN_OUT_MSB 0x1F0B /* minimum possible damper output MSB */ +#define VS6624_CM_MIN_OUT_LSB 0x1F0C /* minimum possible damper output LSB */ +/* peaking control */ +#define VS6624_PEAK_GAIN 0x2000 /* controls peaking gain */ +#define VS6624_PEAK_G_DISABLE 0x2002 /* disable peak gain damping */ +#define VS6624_PEAK_LOW_THR_G_MSB 0x2005 /* low threshold for exposure for gain MSB */ +#define VS6624_PEAK_LOW_THR_G_LSB 0x2006 /* low threshold for exposure for gain LSB */ +#define VS6624_PEAK_HIGH_THR_G_MSB 0x2009 /* high threshold for exposure for gain MSB */ +#define VS6624_PEAK_HIGH_THR_G_LSB 0x200A /* high threshold for exposure for gain LSB */ +#define VS6624_PEAK_MIN_OUT_G_MSB 0x200D /* minimum damper output for gain MSB */ +#define VS6624_PEAK_MIN_OUT_G_LSB 0x200E /* minimum damper output for gain LSB */ +#define VS6624_PEAK_LOW_THR 0x2010 /* adjust degree of coring */ +#define VS6624_PEAK_C_DISABLE 0x2012 /* disable coring damping */ +#define VS6624_PEAK_HIGH_THR 0x2014 /* adjust maximum gain */ +#define VS6624_PEAK_LOW_THR_C_MSB 0x2017 /* low threshold for exposure for coring MSB */ +#define VS6624_PEAK_LOW_THR_C_LSB 0x2018 /* low threshold for exposure for coring LSB */ +#define VS6624_PEAK_HIGH_THR_C_MSB 0x201B /* high threshold for exposure for coring MSB */ +#define VS6624_PEAK_HIGH_THR_C_LSB 0x201C /* high threshold for exposure for coring LSB */ +#define VS6624_PEAK_MIN_OUT_C_MSB 0x201F /* minimum damper output for coring MSB */ +#define VS6624_PEAK_MIN_OUT_C_LSB 0x2020 /* minimum damper output for coring LSB */ +/* pipe 0 RGB to YUV matrix manual control */ +#define VS6624_RYM0_MAN_CTRL 0x2180 /* enable manual RGB to YUV matrix */ +#define VS6624_RYM0_W00_MSB 0x2183 /* row 0 column 0 of YUV matrix MSB */ +#define VS6624_RYM0_W00_LSB 0x2184 /* row 0 column 0 of YUV matrix LSB */ +#define VS6624_RYM0_W01_MSB 0x2187 /* row 0 column 1 of YUV matrix MSB */ +#define VS6624_RYM0_W01_LSB 0x2188 /* row 0 column 1 of YUV matrix LSB */ +#define VS6624_RYM0_W02_MSB 0x218C /* row 0 column 2 of YUV matrix MSB */ +#define VS6624_RYM0_W02_LSB 0x218D /* row 0 column 2 of YUV matrix LSB */ +#define VS6624_RYM0_W10_MSB 0x2190 /* row 1 column 0 of YUV matrix MSB */ +#define VS6624_RYM0_W10_LSB 0x218F /* row 1 column 0 of YUV matrix LSB */ +#define VS6624_RYM0_W11_MSB 0x2193 /* row 1 column 1 of YUV matrix MSB */ +#define VS6624_RYM0_W11_LSB 0x2194 /* row 1 column 1 of YUV matrix LSB */ +#define VS6624_RYM0_W12_MSB 0x2197 /* row 1 column 2 of YUV matrix MSB */ +#define VS6624_RYM0_W12_LSB 0x2198 /* row 1 column 2 of YUV matrix LSB */ +#define VS6624_RYM0_W20_MSB 0x219B /* row 2 column 0 of YUV matrix MSB */ +#define VS6624_RYM0_W20_LSB 0x219C /* row 2 column 0 of YUV matrix LSB */ +#define VS6624_RYM0_W21_MSB 0x21A0 /* row 2 column 1 of YUV matrix MSB */ +#define VS6624_RYM0_W21_LSB 0x219F /* row 2 column 1 of YUV matrix LSB */ +#define VS6624_RYM0_W22_MSB 0x21A3 /* row 2 column 2 of YUV matrix MSB */ +#define VS6624_RYM0_W22_LSB 0x21A4 /* row 2 column 2 of YUV matrix LSB */ +#define VS6624_RYM0_YINY_MSB 0x21A7 /* Y in Y MSB */ +#define VS6624_RYM0_YINY_LSB 0x21A8 /* Y in Y LSB */ +#define VS6624_RYM0_YINCB_MSB 0x21AB /* Y in Cb MSB */ +#define VS6624_RYM0_YINCB_LSB 0x21AC /* Y in Cb LSB */ +#define VS6624_RYM0_YINCR_MSB 0x21B0 /* Y in Cr MSB */ +#define VS6624_RYM0_YINCR_LSB 0x21AF /* Y in Cr LSB */ +/* pipe 1 RGB to YUV matrix manual control */ +#define VS6624_RYM1_MAN_CTRL 0x2200 /* enable manual RGB to YUV matrix */ +#define VS6624_RYM1_W00_MSB 0x2203 /* row 0 column 0 of YUV matrix MSB */ +#define VS6624_RYM1_W00_LSB 0x2204 /* row 0 column 0 of YUV matrix LSB */ +#define VS6624_RYM1_W01_MSB 0x2207 /* row 0 column 1 of YUV matrix MSB */ +#define VS6624_RYM1_W01_LSB 0x2208 /* row 0 column 1 of YUV matrix LSB */ +#define VS6624_RYM1_W02_MSB 0x220C /* row 0 column 2 of YUV matrix MSB */ +#define VS6624_RYM1_W02_LSB 0x220D /* row 0 column 2 of YUV matrix LSB */ +#define VS6624_RYM1_W10_MSB 0x2210 /* row 1 column 0 of YUV matrix MSB */ +#define VS6624_RYM1_W10_LSB 0x220F /* row 1 column 0 of YUV matrix LSB */ +#define VS6624_RYM1_W11_MSB 0x2213 /* row 1 column 1 of YUV matrix MSB */ +#define VS6624_RYM1_W11_LSB 0x2214 /* row 1 column 1 of YUV matrix LSB */ +#define VS6624_RYM1_W12_MSB 0x2217 /* row 1 column 2 of YUV matrix MSB */ +#define VS6624_RYM1_W12_LSB 0x2218 /* row 1 column 2 of YUV matrix LSB */ +#define VS6624_RYM1_W20_MSB 0x221B /* row 2 column 0 of YUV matrix MSB */ +#define VS6624_RYM1_W20_LSB 0x221C /* row 2 column 0 of YUV matrix LSB */ +#define VS6624_RYM1_W21_MSB 0x2220 /* row 2 column 1 of YUV matrix MSB */ +#define VS6624_RYM1_W21_LSB 0x221F /* row 2 column 1 of YUV matrix LSB */ +#define VS6624_RYM1_W22_MSB 0x2223 /* row 2 column 2 of YUV matrix MSB */ +#define VS6624_RYM1_W22_LSB 0x2224 /* row 2 column 2 of YUV matrix LSB */ +#define VS6624_RYM1_YINY_MSB 0x2227 /* Y in Y MSB */ +#define VS6624_RYM1_YINY_LSB 0x2228 /* Y in Y LSB */ +#define VS6624_RYM1_YINCB_MSB 0x222B /* Y in Cb MSB */ +#define VS6624_RYM1_YINCB_LSB 0x222C /* Y in Cb LSB */ +#define VS6624_RYM1_YINCR_MSB 0x2220 /* Y in Cr MSB */ +#define VS6624_RYM1_YINCR_LSB 0x222F /* Y in Cr LSB */ +/* pipe 0 gamma manual control */ +#define VS6624_GAMMA_MAN_CTRL0 0x2280 /* enable manual gamma setup */ +#define VS6624_GAMMA_PEAK_R0 0x2282 /* peaked red channel gamma value */ +#define VS6624_GAMMA_PEAK_G0 0x2284 /* peaked green channel gamma value */ +#define VS6624_GAMMA_PEAK_B0 0x2286 /* peaked blue channel gamma value */ +#define VS6624_GAMMA_UNPEAK_R0 0x2288 /* unpeaked red channel gamma value */ +#define VS6624_GAMMA_UNPEAK_G0 0x228A /* unpeaked green channel gamma value */ +#define VS6624_GAMMA_UNPEAK_B0 0x228C /* unpeaked blue channel gamma value */ +/* pipe 1 gamma manual control */ +#define VS6624_GAMMA_MAN_CTRL1 0x2300 /* enable manual gamma setup */ +#define VS6624_GAMMA_PEAK_R1 0x2302 /* peaked red channel gamma value */ +#define VS6624_GAMMA_PEAK_G1 0x2304 /* peaked green channel gamma value */ +#define VS6624_GAMMA_PEAK_B1 0x2306 /* peaked blue channel gamma value */ +#define VS6624_GAMMA_UNPEAK_R1 0x2308 /* unpeaked red channel gamma value */ +#define VS6624_GAMMA_UNPEAK_G1 0x230A /* unpeaked green channel gamma value */ +#define VS6624_GAMMA_UNPEAK_B1 0x230C /* unpeaked blue channel gamma value */ +/* fade to black */ +#define VS6624_F2B_DISABLE 0x2480 /* disable fade to black */ +#define VS6624_F2B_BLACK_VAL_MSB 0x2483 /* black value MSB */ +#define VS6624_F2B_BLACK_VAL_LSB 0x2484 /* black value LSB */ +#define VS6624_F2B_LOW_THR_MSB 0x2487 /* low threshold for exposure MSB */ +#define VS6624_F2B_LOW_THR_LSB 0x2488 /* low threshold for exposure LSB */ +#define VS6624_F2B_HIGH_THR_MSB 0x248B /* high threshold for exposure MSB */ +#define VS6624_F2B_HIGH_THR_LSB 0x248C /* high threshold for exposure LSB */ +#define VS6624_F2B_MIN_OUT_MSB 0x248F /* minimum damper output MSB */ +#define VS6624_F2B_MIN_OUT_LSB 0x2490 /* minimum damper output LSB */ +/* output formatter control */ +#define VS6624_CODE_CK_EN 0x2580 /* code check enable */ +#define VS6624_BLANK_FMT 0x2582 /* blank format */ +#define VS6624_SYNC_CODE_SETUP 0x2584 /* sync code setup */ +#define VS6624_HSYNC_SETUP 0x2586 /* H sync setup */ +#define VS6624_VSYNC_SETUP 0x2588 /* V sync setup */ +#define VS6624_PCLK_SETUP 0x258A /* PCLK setup */ +#define VS6624_PCLK_EN 0x258C /* PCLK enable */ +#define VS6624_OPF_SP_SETUP 0x258E /* output formatter sp setup */ +#define VS6624_BLANK_DATA_MSB 0x2590 /* blank data MSB */ +#define VS6624_BLANK_DATA_LSB 0x2592 /* blank data LSB */ +#define VS6624_RGB_SETUP 0x2594 /* RGB setup */ +#define VS6624_YUV_SETUP 0x2596 /* YUV setup */ +#define VS6624_VSYNC_RIS_COARSE_H 0x2598 /* V sync rising coarse high */ +#define VS6624_VSYNC_RIS_COARSE_L 0x259A /* V sync rising coarse low */ +#define VS6624_VSYNC_RIS_FINE_H 0x259C /* V sync rising fine high */ +#define VS6624_VSYNC_RIS_FINE_L 0x259E /* V sync rising fine low */ +#define VS6624_VSYNC_FALL_COARSE_H 0x25A0 /* V sync falling coarse high */ +#define VS6624_VSYNC_FALL_COARSE_L 0x25A2 /* V sync falling coarse low */ +#define VS6624_VSYNC_FALL_FINE_H 0x25A4 /* V sync falling fine high */ +#define VS6624_VSYNC_FALL_FINE_L 0x25A6 /* V sync falling fine low */ +#define VS6624_HSYNC_RIS_H 0x25A8 /* H sync rising high */ +#define VS6624_HSYNC_RIS_L 0x25AA /* H sync rising low */ +#define VS6624_HSYNC_FALL_H 0x25AC /* H sync falling high */ +#define VS6624_HSYNC_FALL_L 0x25AE /* H sync falling low */ +#define VS6624_OUT_IF 0x25B0 /* output interface */ +#define VS6624_CCP_EXT_DATA 0x25B2 /* CCP extra data */ +/* NoRA controls */ +#define VS6624_NORA_DISABLE 0x2600 /* NoRA control mode */ +#define VS6624_NORA_USAGE 0x2602 /* usage */ +#define VS6624_NORA_SPLIT_KN 0x2604 /* split kn */ +#define VS6624_NORA_SPLIT_NI 0x2606 /* split ni */ +#define VS6624_NORA_TIGHT_G 0x2608 /* tight green */ +#define VS6624_NORA_DISABLE_NP 0x260A /* disable noro promoting */ +#define VS6624_NORA_LOW_THR_MSB 0x260D /* low threshold for exposure MSB */ +#define VS6624_NORA_LOW_THR_LSB 0x260E /* low threshold for exposure LSB */ +#define VS6624_NORA_HIGH_THR_MSB 0x2611 /* high threshold for exposure MSB */ +#define VS6624_NORA_HIGH_THR_LSB 0x2612 /* high threshold for exposure LSB */ +#define VS6624_NORA_MIN_OUT_MSB 0x2615 /* minimum damper output MSB */ +#define VS6624_NORA_MIN_OUT_LSB 0x2616 /* minimum damper output LSB */ + +#endif diff --git a/drivers/media/video/w9966.c b/drivers/media/video/w9966.c index 453dbbd1e6e8..7fd7ac567e1a 100644 --- a/drivers/media/video/w9966.c +++ b/drivers/media/video/w9966.c @@ -129,9 +129,9 @@ MODULE_LICENSE("GPL"); MODULE_VERSION("0.33.1"); #ifdef MODULE -static const char *pardev[] = {[0 ... W9966_MAXCAMS] = ""}; +static char *pardev[] = {[0 ... W9966_MAXCAMS] = ""}; #else -static const char *pardev[] = {[0 ... W9966_MAXCAMS] = "aggressive"}; +static char *pardev[] = {[0 ... W9966_MAXCAMS] = "aggressive"}; #endif module_param_array(pardev, charp, NULL, 0); MODULE_PARM_DESC(pardev, "pardev: where to search for\n" diff --git a/drivers/media/video/wm8739.c b/drivers/media/video/wm8739.c index a22f765e968a..3bb99e93febe 100644 --- a/drivers/media/video/wm8739.c +++ b/drivers/media/video/wm8739.c @@ -291,15 +291,4 @@ static struct i2c_driver wm8739_driver = { .id_table = wm8739_id, }; -static __init int init_wm8739(void) -{ - return i2c_add_driver(&wm8739_driver); -} - -static __exit void exit_wm8739(void) -{ - i2c_del_driver(&wm8739_driver); -} - -module_init(init_wm8739); -module_exit(exit_wm8739); +module_i2c_driver(wm8739_driver); diff --git a/drivers/media/video/wm8775.c b/drivers/media/video/wm8775.c index 9cedb1e69b58..bee77ea9f49e 100644 --- a/drivers/media/video/wm8775.c +++ b/drivers/media/video/wm8775.c @@ -339,15 +339,4 @@ static struct i2c_driver wm8775_driver = { .id_table = wm8775_id, }; -static __init int init_wm8775(void) -{ - return i2c_add_driver(&wm8775_driver); -} - -static __exit void exit_wm8775(void) -{ - i2c_del_driver(&wm8775_driver); -} - -module_init(init_wm8775); -module_exit(exit_wm8775); +module_i2c_driver(wm8775_driver); diff --git a/drivers/message/fusion/mptbase.c b/drivers/message/fusion/mptbase.c index a7dc4672d996..a5c591ffe395 100644 --- a/drivers/message/fusion/mptbase.c +++ b/drivers/message/fusion/mptbase.c @@ -346,7 +346,7 @@ static int mpt_remove_dead_ioc_func(void *arg) if ((pdev == NULL)) return -1; - pci_remove_bus_device(pdev); + pci_stop_and_remove_bus_device(pdev); return 0; } diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 1489c3540f96..243e0c663c37 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -848,8 +848,9 @@ config MCP_SA11X0 # Chip drivers config MCP_UCB1200 - tristate "Support for UCB1200 / UCB1300" - depends on MCP + bool "Support for UCB1200 / UCB1300" + depends on MCP_SA11X0 + select MCP config MCP_UCB1200_TS tristate "Touchscreen interface support" diff --git a/drivers/mfd/mcp-core.c b/drivers/mfd/mcp-core.c index 86cc3f7841cd..6acf2e03f2ba 100644 --- a/drivers/mfd/mcp-core.c +++ b/drivers/mfd/mcp-core.c @@ -19,7 +19,6 @@ #include <linux/string.h> #include <linux/mfd/mcp.h> -#include <mach/dma.h> #include <asm/system.h> @@ -48,39 +47,11 @@ static int mcp_bus_remove(struct device *dev) return 0; } -static int mcp_bus_suspend(struct device *dev, pm_message_t state) -{ - struct mcp *mcp = to_mcp(dev); - int ret = 0; - - if (dev->driver) { - struct mcp_driver *drv = to_mcp_driver(dev->driver); - - ret = drv->suspend(mcp, state); - } - return ret; -} - -static int mcp_bus_resume(struct device *dev) -{ - struct mcp *mcp = to_mcp(dev); - int ret = 0; - - if (dev->driver) { - struct mcp_driver *drv = to_mcp_driver(dev->driver); - - ret = drv->resume(mcp); - } - return ret; -} - static struct bus_type mcp_bus_type = { .name = "mcp", .match = mcp_bus_match, .probe = mcp_bus_probe, .remove = mcp_bus_remove, - .suspend = mcp_bus_suspend, - .resume = mcp_bus_resume, }; /** @@ -208,6 +179,7 @@ struct mcp *mcp_host_alloc(struct device *parent, size_t size) mcp = kzalloc(sizeof(struct mcp) + size, GFP_KERNEL); if (mcp) { spin_lock_init(&mcp->lock); + device_initialize(&mcp->attached_device); mcp->attached_device.parent = parent; mcp->attached_device.bus = &mcp_bus_type; mcp->attached_device.dma_mask = parent->dma_mask; @@ -217,18 +189,25 @@ struct mcp *mcp_host_alloc(struct device *parent, size_t size) } EXPORT_SYMBOL(mcp_host_alloc); -int mcp_host_register(struct mcp *mcp) +int mcp_host_add(struct mcp *mcp, void *pdata) { + mcp->attached_device.platform_data = pdata; dev_set_name(&mcp->attached_device, "mcp0"); - return device_register(&mcp->attached_device); + return device_add(&mcp->attached_device); +} +EXPORT_SYMBOL(mcp_host_add); + +void mcp_host_del(struct mcp *mcp) +{ + device_del(&mcp->attached_device); } -EXPORT_SYMBOL(mcp_host_register); +EXPORT_SYMBOL(mcp_host_del); -void mcp_host_unregister(struct mcp *mcp) +void mcp_host_free(struct mcp *mcp) { - device_unregister(&mcp->attached_device); + put_device(&mcp->attached_device); } -EXPORT_SYMBOL(mcp_host_unregister); +EXPORT_SYMBOL(mcp_host_free); int mcp_driver_register(struct mcp_driver *mcpdrv) { diff --git a/drivers/mfd/mcp-sa11x0.c b/drivers/mfd/mcp-sa11x0.c index 02c53a0766c4..1c0ceacaa1f6 100644 --- a/drivers/mfd/mcp-sa11x0.c +++ b/drivers/mfd/mcp-sa11x0.c @@ -13,51 +13,61 @@ */ #include <linux/module.h> #include <linux/init.h> +#include <linux/io.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/spinlock.h> #include <linux/platform_device.h> +#include <linux/pm.h> #include <linux/mfd/mcp.h> -#include <mach/dma.h> #include <mach/hardware.h> #include <asm/mach-types.h> #include <asm/system.h> #include <mach/mcp.h> -#include <mach/assabet.h> - +#define DRIVER_NAME "sa11x0-mcp" struct mcp_sa11x0 { - u32 mccr0; - u32 mccr1; + void __iomem *base0; + void __iomem *base1; + u32 mccr0; + u32 mccr1; }; +/* Register offsets */ +#define MCCR0(m) ((m)->base0 + 0x00) +#define MCDR0(m) ((m)->base0 + 0x08) +#define MCDR1(m) ((m)->base0 + 0x0c) +#define MCDR2(m) ((m)->base0 + 0x10) +#define MCSR(m) ((m)->base0 + 0x18) +#define MCCR1(m) ((m)->base1 + 0x00) + #define priv(mcp) ((struct mcp_sa11x0 *)mcp_priv(mcp)) static void mcp_sa11x0_set_telecom_divisor(struct mcp *mcp, unsigned int divisor) { - unsigned int mccr0; + struct mcp_sa11x0 *m = priv(mcp); divisor /= 32; - mccr0 = Ser4MCCR0 & ~0x00007f00; - mccr0 |= divisor << 8; - Ser4MCCR0 = mccr0; + m->mccr0 &= ~0x00007f00; + m->mccr0 |= divisor << 8; + writel_relaxed(m->mccr0, MCCR0(m)); } static void mcp_sa11x0_set_audio_divisor(struct mcp *mcp, unsigned int divisor) { - unsigned int mccr0; + struct mcp_sa11x0 *m = priv(mcp); divisor /= 32; - mccr0 = Ser4MCCR0 & ~0x0000007f; - mccr0 |= divisor; - Ser4MCCR0 = mccr0; + m->mccr0 &= ~0x0000007f; + m->mccr0 |= divisor; + writel_relaxed(m->mccr0, MCCR0(m)); } /* @@ -69,14 +79,15 @@ mcp_sa11x0_set_audio_divisor(struct mcp *mcp, unsigned int divisor) static void mcp_sa11x0_write(struct mcp *mcp, unsigned int reg, unsigned int val) { + struct mcp_sa11x0 *m = priv(mcp); int ret = -ETIME; int i; - Ser4MCDR2 = reg << 17 | MCDR2_Wr | (val & 0xffff); + writel_relaxed(reg << 17 | MCDR2_Wr | (val & 0xffff), MCDR2(m)); for (i = 0; i < 2; i++) { udelay(mcp->rw_timeout); - if (Ser4MCSR & MCSR_CWC) { + if (readl_relaxed(MCSR(m)) & MCSR_CWC) { ret = 0; break; } @@ -95,15 +106,16 @@ mcp_sa11x0_write(struct mcp *mcp, unsigned int reg, unsigned int val) static unsigned int mcp_sa11x0_read(struct mcp *mcp, unsigned int reg) { + struct mcp_sa11x0 *m = priv(mcp); int ret = -ETIME; int i; - Ser4MCDR2 = reg << 17 | MCDR2_Rd; + writel_relaxed(reg << 17 | MCDR2_Rd, MCDR2(m)); for (i = 0; i < 2; i++) { udelay(mcp->rw_timeout); - if (Ser4MCSR & MCSR_CRC) { - ret = Ser4MCDR2 & 0xffff; + if (readl_relaxed(MCSR(m)) & MCSR_CRC) { + ret = readl_relaxed(MCDR2(m)) & 0xffff; break; } } @@ -116,13 +128,19 @@ mcp_sa11x0_read(struct mcp *mcp, unsigned int reg) static void mcp_sa11x0_enable(struct mcp *mcp) { - Ser4MCSR = -1; - Ser4MCCR0 |= MCCR0_MCE; + struct mcp_sa11x0 *m = priv(mcp); + + writel(-1, MCSR(m)); + m->mccr0 |= MCCR0_MCE; + writel_relaxed(m->mccr0, MCCR0(m)); } static void mcp_sa11x0_disable(struct mcp *mcp) { - Ser4MCCR0 &= ~MCCR0_MCE; + struct mcp_sa11x0 *m = priv(mcp); + + m->mccr0 &= ~MCCR0_MCE; + writel_relaxed(m->mccr0, MCCR0(m)); } /* @@ -137,55 +155,64 @@ static struct mcp_ops mcp_sa11x0 = { .disable = mcp_sa11x0_disable, }; -static int mcp_sa11x0_probe(struct platform_device *pdev) +static int mcp_sa11x0_probe(struct platform_device *dev) { - struct mcp_plat_data *data = pdev->dev.platform_data; + struct mcp_plat_data *data = dev->dev.platform_data; + struct resource *mem0, *mem1; + struct mcp_sa11x0 *m; struct mcp *mcp; int ret; if (!data) return -ENODEV; - if (!request_mem_region(0x80060000, 0x60, "sa11x0-mcp")) - return -EBUSY; + mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0); + mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1); + if (!mem0 || !mem1) + return -ENXIO; + + if (!request_mem_region(mem0->start, resource_size(mem0), + DRIVER_NAME)) { + ret = -EBUSY; + goto err_mem0; + } - mcp = mcp_host_alloc(&pdev->dev, sizeof(struct mcp_sa11x0)); + if (!request_mem_region(mem1->start, resource_size(mem1), + DRIVER_NAME)) { + ret = -EBUSY; + goto err_mem1; + } + + mcp = mcp_host_alloc(&dev->dev, sizeof(struct mcp_sa11x0)); if (!mcp) { ret = -ENOMEM; - goto release; + goto err_alloc; } mcp->owner = THIS_MODULE; mcp->ops = &mcp_sa11x0; mcp->sclk_rate = data->sclk_rate; - mcp->dma_audio_rd = DMA_Ser4MCP0Rd; - mcp->dma_audio_wr = DMA_Ser4MCP0Wr; - mcp->dma_telco_rd = DMA_Ser4MCP1Rd; - mcp->dma_telco_wr = DMA_Ser4MCP1Wr; - mcp->gpio_base = data->gpio_base; - platform_set_drvdata(pdev, mcp); + m = priv(mcp); + m->mccr0 = data->mccr0 | 0x7f7f; + m->mccr1 = data->mccr1; - if (machine_is_assabet()) { - ASSABET_BCR_set(ASSABET_BCR_CODEC_RST); + m->base0 = ioremap(mem0->start, resource_size(mem0)); + m->base1 = ioremap(mem1->start, resource_size(mem1)); + if (!m->base0 || !m->base1) { + ret = -ENOMEM; + goto err_ioremap; } - /* - * Setup the PPC unit correctly. - */ - PPDR &= ~PPC_RXD4; - PPDR |= PPC_TXD4 | PPC_SCLK | PPC_SFRM; - PSDR |= PPC_RXD4; - PSDR &= ~(PPC_TXD4 | PPC_SCLK | PPC_SFRM); - PPSR &= ~(PPC_TXD4 | PPC_SCLK | PPC_SFRM); + platform_set_drvdata(dev, mcp); /* * Initialise device. Note that we initially * set the sampling rate to minimum. */ - Ser4MCSR = -1; - Ser4MCCR1 = data->mccr1; - Ser4MCCR0 = data->mccr0 | 0x7f7f; + writel_relaxed(-1, MCSR(m)); + writel_relaxed(m->mccr1, MCCR1(m)); + writel_relaxed(m->mccr0, MCCR0(m)); /* * Calculate the read/write timeout (us) from the bit clock @@ -195,62 +222,90 @@ static int mcp_sa11x0_probe(struct platform_device *pdev) mcp->rw_timeout = (64 * 3 * 1000000 + mcp->sclk_rate - 1) / mcp->sclk_rate; - ret = mcp_host_register(mcp); + ret = mcp_host_add(mcp, data->codec_pdata); if (ret == 0) - goto out; + return 0; - release: - release_mem_region(0x80060000, 0x60); - platform_set_drvdata(pdev, NULL); + platform_set_drvdata(dev, NULL); - out: + err_ioremap: + iounmap(m->base1); + iounmap(m->base0); + mcp_host_free(mcp); + err_alloc: + release_mem_region(mem1->start, resource_size(mem1)); + err_mem1: + release_mem_region(mem0->start, resource_size(mem0)); + err_mem0: return ret; } static int mcp_sa11x0_remove(struct platform_device *dev) { struct mcp *mcp = platform_get_drvdata(dev); + struct mcp_sa11x0 *m = priv(mcp); + struct resource *mem0, *mem1; + + if (m->mccr0 & MCCR0_MCE) + dev_warn(&dev->dev, + "device left active (missing disable call?)\n"); + + mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0); + mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1); platform_set_drvdata(dev, NULL); - mcp_host_unregister(mcp); - release_mem_region(0x80060000, 0x60); + mcp_host_del(mcp); + iounmap(m->base1); + iounmap(m->base0); + mcp_host_free(mcp); + release_mem_region(mem1->start, resource_size(mem1)); + release_mem_region(mem0->start, resource_size(mem0)); return 0; } -static int mcp_sa11x0_suspend(struct platform_device *dev, pm_message_t state) +#ifdef CONFIG_PM_SLEEP +static int mcp_sa11x0_suspend(struct device *dev) { - struct mcp *mcp = platform_get_drvdata(dev); + struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev)); + + if (m->mccr0 & MCCR0_MCE) + dev_warn(dev, "device left active (missing disable call?)\n"); - priv(mcp)->mccr0 = Ser4MCCR0; - priv(mcp)->mccr1 = Ser4MCCR1; - Ser4MCCR0 &= ~MCCR0_MCE; + writel(m->mccr0 & ~MCCR0_MCE, MCCR0(m)); return 0; } -static int mcp_sa11x0_resume(struct platform_device *dev) +static int mcp_sa11x0_resume(struct device *dev) { - struct mcp *mcp = platform_get_drvdata(dev); + struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev)); - Ser4MCCR1 = priv(mcp)->mccr1; - Ser4MCCR0 = priv(mcp)->mccr0; + writel_relaxed(m->mccr1, MCCR1(m)); + writel_relaxed(m->mccr0, MCCR0(m)); return 0; } - -/* - * The driver for the SA11x0 MCP port. - */ -MODULE_ALIAS("platform:sa11x0-mcp"); +#endif + +static const struct dev_pm_ops mcp_sa11x0_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = mcp_sa11x0_suspend, + .freeze = mcp_sa11x0_suspend, + .poweroff = mcp_sa11x0_suspend, + .resume_noirq = mcp_sa11x0_resume, + .thaw_noirq = mcp_sa11x0_resume, + .restore_noirq = mcp_sa11x0_resume, +#endif +}; static struct platform_driver mcp_sa11x0_driver = { .probe = mcp_sa11x0_probe, .remove = mcp_sa11x0_remove, - .suspend = mcp_sa11x0_suspend, - .resume = mcp_sa11x0_resume, .driver = { - .name = "sa11x0-mcp", + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &mcp_sa11x0_pm_ops, }, }; @@ -259,6 +314,7 @@ static struct platform_driver mcp_sa11x0_driver = { */ module_platform_driver(mcp_sa11x0_driver); +MODULE_ALIAS("platform:" DRIVER_NAME); MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); MODULE_DESCRIPTION("SA11x0 multimedia communications port driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ucb1x00-assabet.c b/drivers/mfd/ucb1x00-assabet.c index cea9da60850d..b63c0756a669 100644 --- a/drivers/mfd/ucb1x00-assabet.c +++ b/drivers/mfd/ucb1x00-assabet.c @@ -11,14 +11,15 @@ */ #include <linux/module.h> #include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> #include <linux/fs.h> +#include <linux/gpio_keys.h> +#include <linux/input.h> +#include <linux/platform_device.h> #include <linux/proc_fs.h> -#include <linux/device.h> #include <linux/mfd/ucb1x00.h> -#include <mach/dma.h> - - #define UCB1X00_ATTR(name,input)\ static ssize_t name##_show(struct device *dev, struct device_attribute *attr, \ char *buf) \ @@ -38,14 +39,45 @@ UCB1X00_ATTR(batt_temp, UCB_ADC_INP_AD2); static int ucb1x00_assabet_add(struct ucb1x00_dev *dev) { - device_create_file(&dev->ucb->dev, &dev_attr_vbatt); - device_create_file(&dev->ucb->dev, &dev_attr_vcharger); - device_create_file(&dev->ucb->dev, &dev_attr_batt_temp); + struct ucb1x00 *ucb = dev->ucb; + struct platform_device *pdev; + struct gpio_keys_platform_data keys; + static struct gpio_keys_button buttons[6]; + unsigned i; + + memset(buttons, 0, sizeof(buttons)); + memset(&keys, 0, sizeof(keys)); + + for (i = 0; i < ARRAY_SIZE(buttons); i++) { + buttons[i].code = BTN_0 + i; + buttons[i].gpio = ucb->gpio.base + i; + buttons[i].type = EV_KEY; + buttons[i].can_disable = true; + } + + keys.buttons = buttons; + keys.nbuttons = ARRAY_SIZE(buttons); + keys.poll_interval = 50; + keys.name = "ucb1x00"; + + pdev = platform_device_register_data(&ucb->dev, "gpio-keys", -1, + &keys, sizeof(keys)); + + device_create_file(&ucb->dev, &dev_attr_vbatt); + device_create_file(&ucb->dev, &dev_attr_vcharger); + device_create_file(&ucb->dev, &dev_attr_batt_temp); + + dev->priv = pdev; return 0; } static void ucb1x00_assabet_remove(struct ucb1x00_dev *dev) { + struct platform_device *pdev = dev->priv; + + if (!IS_ERR(pdev)) + platform_device_unregister(pdev); + device_remove_file(&dev->ucb->dev, &dev_attr_batt_temp); device_remove_file(&dev->ucb->dev, &dev_attr_vcharger); device_remove_file(&dev->ucb->dev, &dev_attr_vbatt); diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c index febc90cdef7e..70f02daeb22a 100644 --- a/drivers/mfd/ucb1x00-core.c +++ b/drivers/mfd/ucb1x00-core.c @@ -23,14 +23,12 @@ #include <linux/init.h> #include <linux/errno.h> #include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/device.h> #include <linux/mutex.h> #include <linux/mfd/ucb1x00.h> +#include <linux/pm.h> #include <linux/gpio.h> -#include <linux/semaphore.h> - -#include <mach/dma.h> -#include <mach/hardware.h> static DEFINE_MUTEX(ucb1x00_mutex); static LIST_HEAD(ucb1x00_drivers); @@ -102,7 +100,7 @@ void ucb1x00_io_write(struct ucb1x00 *ucb, unsigned int set, unsigned int clear) * ucb1x00_enable must have been called to enable the comms * before using this function. * - * This function does not take any semaphores or spinlocks. + * This function does not take any mutexes or spinlocks. */ unsigned int ucb1x00_io_read(struct ucb1x00 *ucb) { @@ -120,14 +118,22 @@ static void ucb1x00_gpio_set(struct gpio_chip *chip, unsigned offset, int value) else ucb->io_out &= ~(1 << offset); + ucb1x00_enable(ucb); ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); + ucb1x00_disable(ucb); spin_unlock_irqrestore(&ucb->io_lock, flags); } static int ucb1x00_gpio_get(struct gpio_chip *chip, unsigned offset) { struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); - return ucb1x00_reg_read(ucb, UCB_IO_DATA) & (1 << offset); + unsigned val; + + ucb1x00_enable(ucb); + val = ucb1x00_reg_read(ucb, UCB_IO_DATA); + ucb1x00_disable(ucb); + + return val & (1 << offset); } static int ucb1x00_gpio_direction_input(struct gpio_chip *chip, unsigned offset) @@ -137,7 +143,9 @@ static int ucb1x00_gpio_direction_input(struct gpio_chip *chip, unsigned offset) spin_lock_irqsave(&ucb->io_lock, flags); ucb->io_dir &= ~(1 << offset); + ucb1x00_enable(ucb); ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + ucb1x00_disable(ucb); spin_unlock_irqrestore(&ucb->io_lock, flags); return 0; @@ -157,6 +165,7 @@ static int ucb1x00_gpio_direction_output(struct gpio_chip *chip, unsigned offset else ucb->io_out &= ~mask; + ucb1x00_enable(ucb); if (old != ucb->io_out) ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); @@ -164,11 +173,19 @@ static int ucb1x00_gpio_direction_output(struct gpio_chip *chip, unsigned offset ucb->io_dir |= mask; ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); } + ucb1x00_disable(ucb); spin_unlock_irqrestore(&ucb->io_lock, flags); return 0; } +static int ucb1x00_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct ucb1x00 *ucb = container_of(chip, struct ucb1x00, gpio); + + return ucb->irq_base > 0 ? ucb->irq_base + offset : -ENXIO; +} + /* * UCB1300 data sheet says we must: * 1. enable ADC => 5us (including reference startup time) @@ -186,7 +203,7 @@ static int ucb1x00_gpio_direction_output(struct gpio_chip *chip, unsigned offset * Any code wishing to use the ADC converter must call this * function prior to using it. * - * This function takes the ADC semaphore to prevent two or more + * This function takes the ADC mutex to prevent two or more * concurrent uses, and therefore may sleep. As a result, it * can only be called from process context, not interrupt * context. @@ -196,7 +213,7 @@ static int ucb1x00_gpio_direction_output(struct gpio_chip *chip, unsigned offset */ void ucb1x00_adc_enable(struct ucb1x00 *ucb) { - down(&ucb->adc_sem); + mutex_lock(&ucb->adc_mutex); ucb->adc_cr |= UCB_ADC_ENA; @@ -218,7 +235,7 @@ void ucb1x00_adc_enable(struct ucb1x00 *ucb) * complete (2 frames max without sync). * * If called for a synchronised ADC conversion, it may sleep - * with the ADC semaphore held. + * with the ADC mutex held. */ unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync) { @@ -246,7 +263,7 @@ unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync) * ucb1x00_adc_disable - disable the ADC converter * @ucb: UCB1x00 structure describing chip * - * Disable the ADC converter and release the ADC semaphore. + * Disable the ADC converter and release the ADC mutex. */ void ucb1x00_adc_disable(struct ucb1x00 *ucb) { @@ -254,7 +271,7 @@ void ucb1x00_adc_disable(struct ucb1x00 *ucb) ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr); ucb1x00_disable(ucb); - up(&ucb->adc_sem); + mutex_unlock(&ucb->adc_mutex); } /* @@ -265,10 +282,9 @@ void ucb1x00_adc_disable(struct ucb1x00 *ucb) * SIBCLK to talk to the chip. We leave the clock running until * we have finished processing all interrupts from the chip. */ -static irqreturn_t ucb1x00_irq(int irqnr, void *devid) +static void ucb1x00_irq(unsigned int irq, struct irq_desc *desc) { - struct ucb1x00 *ucb = devid; - struct ucb1x00_irq *irq; + struct ucb1x00 *ucb = irq_desc_get_handler_data(desc); unsigned int isr, i; ucb1x00_enable(ucb); @@ -276,157 +292,104 @@ static irqreturn_t ucb1x00_irq(int irqnr, void *devid) ucb1x00_reg_write(ucb, UCB_IE_CLEAR, isr); ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); - for (i = 0, irq = ucb->irq_handler; i < 16 && isr; i++, isr >>= 1, irq++) - if (isr & 1 && irq->fn) - irq->fn(i, irq->devid); + for (i = 0; i < 16 && isr; i++, isr >>= 1, irq++) + if (isr & 1) + generic_handle_irq(ucb->irq_base + i); ucb1x00_disable(ucb); - - return IRQ_HANDLED; } -/** - * ucb1x00_hook_irq - hook a UCB1x00 interrupt - * @ucb: UCB1x00 structure describing chip - * @idx: interrupt index - * @fn: function to call when interrupt is triggered - * @devid: device id to pass to interrupt handler - * - * Hook the specified interrupt. You can only register one handler - * for each interrupt source. The interrupt source is not enabled - * by this function; use ucb1x00_enable_irq instead. - * - * Interrupt handlers will be called with other interrupts enabled. - * - * Returns zero on success, or one of the following errors: - * -EINVAL if the interrupt index is invalid - * -EBUSY if the interrupt has already been hooked - */ -int ucb1x00_hook_irq(struct ucb1x00 *ucb, unsigned int idx, void (*fn)(int, void *), void *devid) +static void ucb1x00_irq_update(struct ucb1x00 *ucb, unsigned mask) { - struct ucb1x00_irq *irq; - int ret = -EINVAL; - - if (idx < 16) { - irq = ucb->irq_handler + idx; - ret = -EBUSY; - - spin_lock_irq(&ucb->lock); - if (irq->fn == NULL) { - irq->devid = devid; - irq->fn = fn; - ret = 0; - } - spin_unlock_irq(&ucb->lock); - } - return ret; + ucb1x00_enable(ucb); + if (ucb->irq_ris_enbl & mask) + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_mask); + if (ucb->irq_fal_enbl & mask) + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_mask); + ucb1x00_disable(ucb); } -/** - * ucb1x00_enable_irq - enable an UCB1x00 interrupt source - * @ucb: UCB1x00 structure describing chip - * @idx: interrupt index - * @edges: interrupt edges to enable - * - * Enable the specified interrupt to trigger on %UCB_RISING, - * %UCB_FALLING or both edges. The interrupt should have been - * hooked by ucb1x00_hook_irq. - */ -void ucb1x00_enable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges) +static void ucb1x00_irq_noop(struct irq_data *data) { - unsigned long flags; +} - if (idx < 16) { - spin_lock_irqsave(&ucb->lock, flags); +static void ucb1x00_irq_mask(struct irq_data *data) +{ + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + unsigned mask = 1 << (data->irq - ucb->irq_base); - ucb1x00_enable(ucb); - if (edges & UCB_RISING) { - ucb->irq_ris_enbl |= 1 << idx; - ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); - } - if (edges & UCB_FALLING) { - ucb->irq_fal_enbl |= 1 << idx; - ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); - } - ucb1x00_disable(ucb); - spin_unlock_irqrestore(&ucb->lock, flags); - } + raw_spin_lock(&ucb->irq_lock); + ucb->irq_mask &= ~mask; + ucb1x00_irq_update(ucb, mask); + raw_spin_unlock(&ucb->irq_lock); } -/** - * ucb1x00_disable_irq - disable an UCB1x00 interrupt source - * @ucb: UCB1x00 structure describing chip - * @edges: interrupt edges to disable - * - * Disable the specified interrupt triggering on the specified - * (%UCB_RISING, %UCB_FALLING or both) edges. - */ -void ucb1x00_disable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges) +static void ucb1x00_irq_unmask(struct irq_data *data) { - unsigned long flags; + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + unsigned mask = 1 << (data->irq - ucb->irq_base); - if (idx < 16) { - spin_lock_irqsave(&ucb->lock, flags); - - ucb1x00_enable(ucb); - if (edges & UCB_RISING) { - ucb->irq_ris_enbl &= ~(1 << idx); - ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); - } - if (edges & UCB_FALLING) { - ucb->irq_fal_enbl &= ~(1 << idx); - ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); - } - ucb1x00_disable(ucb); - spin_unlock_irqrestore(&ucb->lock, flags); - } + raw_spin_lock(&ucb->irq_lock); + ucb->irq_mask |= mask; + ucb1x00_irq_update(ucb, mask); + raw_spin_unlock(&ucb->irq_lock); } -/** - * ucb1x00_free_irq - disable and free the specified UCB1x00 interrupt - * @ucb: UCB1x00 structure describing chip - * @idx: interrupt index - * @devid: device id. - * - * Disable the interrupt source and remove the handler. devid must - * match the devid passed when hooking the interrupt. - * - * Returns zero on success, or one of the following errors: - * -EINVAL if the interrupt index is invalid - * -ENOENT if devid does not match - */ -int ucb1x00_free_irq(struct ucb1x00 *ucb, unsigned int idx, void *devid) +static int ucb1x00_irq_set_type(struct irq_data *data, unsigned int type) { - struct ucb1x00_irq *irq; - int ret; + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + unsigned mask = 1 << (data->irq - ucb->irq_base); - if (idx >= 16) - goto bad; + raw_spin_lock(&ucb->irq_lock); + if (type & IRQ_TYPE_EDGE_RISING) + ucb->irq_ris_enbl |= mask; + else + ucb->irq_ris_enbl &= ~mask; - irq = ucb->irq_handler + idx; - ret = -ENOENT; + if (type & IRQ_TYPE_EDGE_FALLING) + ucb->irq_fal_enbl |= mask; + else + ucb->irq_fal_enbl &= ~mask; + if (ucb->irq_mask & mask) { + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_mask); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_mask); + } + raw_spin_unlock(&ucb->irq_lock); - spin_lock_irq(&ucb->lock); - if (irq->devid == devid) { - ucb->irq_ris_enbl &= ~(1 << idx); - ucb->irq_fal_enbl &= ~(1 << idx); + return 0; +} - ucb1x00_enable(ucb); - ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); - ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); - ucb1x00_disable(ucb); +static int ucb1x00_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct ucb1x00 *ucb = irq_data_get_irq_chip_data(data); + struct ucb1x00_plat_data *pdata = ucb->mcp->attached_device.platform_data; + unsigned mask = 1 << (data->irq - ucb->irq_base); - irq->fn = NULL; - irq->devid = NULL; - ret = 0; - } - spin_unlock_irq(&ucb->lock); - return ret; + if (!pdata || !pdata->can_wakeup) + return -EINVAL; -bad: - printk(KERN_ERR "Freeing bad UCB1x00 irq %d\n", idx); - return -EINVAL; + raw_spin_lock(&ucb->irq_lock); + if (on) + ucb->irq_wake |= mask; + else + ucb->irq_wake &= ~mask; + raw_spin_unlock(&ucb->irq_lock); + + return 0; } +static struct irq_chip ucb1x00_irqchip = { + .name = "ucb1x00", + .irq_ack = ucb1x00_irq_noop, + .irq_mask = ucb1x00_irq_mask, + .irq_unmask = ucb1x00_irq_unmask, + .irq_set_type = ucb1x00_irq_set_type, + .irq_set_wake = ucb1x00_irq_set_wake, +}; + static int ucb1x00_add_dev(struct ucb1x00 *ucb, struct ucb1x00_driver *drv) { struct ucb1x00_dev *dev; @@ -440,8 +403,8 @@ static int ucb1x00_add_dev(struct ucb1x00 *ucb, struct ucb1x00_driver *drv) ret = drv->add(dev); if (ret == 0) { - list_add(&dev->dev_node, &ucb->devs); - list_add(&dev->drv_node, &drv->devs); + list_add_tail(&dev->dev_node, &ucb->devs); + list_add_tail(&dev->drv_node, &drv->devs); } else { kfree(dev); } @@ -533,98 +496,126 @@ static struct class ucb1x00_class = { static int ucb1x00_probe(struct mcp *mcp) { - struct ucb1x00 *ucb; + struct ucb1x00_plat_data *pdata = mcp->attached_device.platform_data; struct ucb1x00_driver *drv; - unsigned int id; + struct ucb1x00 *ucb; + unsigned id, i, irq_base; int ret = -ENODEV; - int temp; + + /* Tell the platform to deassert the UCB1x00 reset */ + if (pdata && pdata->reset) + pdata->reset(UCB_RST_PROBE); mcp_enable(mcp); id = mcp_reg_read(mcp, UCB_ID); + mcp_disable(mcp); if (id != UCB_ID_1200 && id != UCB_ID_1300 && id != UCB_ID_TC35143) { printk(KERN_WARNING "UCB1x00 ID not found: %04x\n", id); - goto err_disable; + goto out; } ucb = kzalloc(sizeof(struct ucb1x00), GFP_KERNEL); ret = -ENOMEM; if (!ucb) - goto err_disable; - + goto out; + device_initialize(&ucb->dev); ucb->dev.class = &ucb1x00_class; ucb->dev.parent = &mcp->attached_device; dev_set_name(&ucb->dev, "ucb1x00"); - spin_lock_init(&ucb->lock); + raw_spin_lock_init(&ucb->irq_lock); spin_lock_init(&ucb->io_lock); - sema_init(&ucb->adc_sem, 1); + mutex_init(&ucb->adc_mutex); ucb->id = id; ucb->mcp = mcp; + + ret = device_add(&ucb->dev); + if (ret) + goto err_dev_add; + + ucb1x00_enable(ucb); ucb->irq = ucb1x00_detect_irq(ucb); + ucb1x00_disable(ucb); if (ucb->irq == NO_IRQ) { - printk(KERN_ERR "UCB1x00: IRQ probe failed\n"); + dev_err(&ucb->dev, "IRQ probe failed\n"); ret = -ENODEV; - goto err_free; + goto err_no_irq; } ucb->gpio.base = -1; - if (mcp->gpio_base != 0) { + irq_base = pdata ? pdata->irq_base : 0; + ucb->irq_base = irq_alloc_descs(-1, irq_base, 16, -1); + if (ucb->irq_base < 0) { + dev_err(&ucb->dev, "unable to allocate 16 irqs: %d\n", + ucb->irq_base); + goto err_irq_alloc; + } + + for (i = 0; i < 16; i++) { + unsigned irq = ucb->irq_base + i; + + irq_set_chip_and_handler(irq, &ucb1x00_irqchip, handle_edge_irq); + irq_set_chip_data(irq, ucb); + set_irq_flags(irq, IRQF_VALID | IRQ_NOREQUEST); + } + + irq_set_irq_type(ucb->irq, IRQ_TYPE_EDGE_RISING); + irq_set_handler_data(ucb->irq, ucb); + irq_set_chained_handler(ucb->irq, ucb1x00_irq); + + if (pdata && pdata->gpio_base) { ucb->gpio.label = dev_name(&ucb->dev); - ucb->gpio.base = mcp->gpio_base; + ucb->gpio.dev = &ucb->dev; + ucb->gpio.owner = THIS_MODULE; + ucb->gpio.base = pdata->gpio_base; ucb->gpio.ngpio = 10; ucb->gpio.set = ucb1x00_gpio_set; ucb->gpio.get = ucb1x00_gpio_get; ucb->gpio.direction_input = ucb1x00_gpio_direction_input; ucb->gpio.direction_output = ucb1x00_gpio_direction_output; + ucb->gpio.to_irq = ucb1x00_to_irq; ret = gpiochip_add(&ucb->gpio); if (ret) - goto err_free; + goto err_gpio_add; } else dev_info(&ucb->dev, "gpio_base not set so no gpiolib support"); - ret = request_irq(ucb->irq, ucb1x00_irq, IRQF_TRIGGER_RISING, - "UCB1x00", ucb); - if (ret) { - printk(KERN_ERR "ucb1x00: unable to grab irq%d: %d\n", - ucb->irq, ret); - goto err_gpio; - } - mcp_set_drvdata(mcp, ucb); - ret = device_register(&ucb->dev); - if (ret) - goto err_irq; - + if (pdata) + device_set_wakeup_capable(&ucb->dev, pdata->can_wakeup); INIT_LIST_HEAD(&ucb->devs); mutex_lock(&ucb1x00_mutex); - list_add(&ucb->node, &ucb1x00_devices); + list_add_tail(&ucb->node, &ucb1x00_devices); list_for_each_entry(drv, &ucb1x00_drivers, node) { ucb1x00_add_dev(ucb, drv); } mutex_unlock(&ucb1x00_mutex); - goto out; + return ret; - err_irq: - free_irq(ucb->irq, ucb); - err_gpio: - if (ucb->gpio.base != -1) - temp = gpiochip_remove(&ucb->gpio); - err_free: - kfree(ucb); - err_disable: - mcp_disable(mcp); + err_gpio_add: + irq_set_chained_handler(ucb->irq, NULL); + err_irq_alloc: + if (ucb->irq_base > 0) + irq_free_descs(ucb->irq_base, 16); + err_no_irq: + device_del(&ucb->dev); + err_dev_add: + put_device(&ucb->dev); out: + if (pdata && pdata->reset) + pdata->reset(UCB_RST_PROBE_FAIL); return ret; } static void ucb1x00_remove(struct mcp *mcp) { + struct ucb1x00_plat_data *pdata = mcp->attached_device.platform_data; struct ucb1x00 *ucb = mcp_get_drvdata(mcp); struct list_head *l, *n; int ret; @@ -643,8 +634,12 @@ static void ucb1x00_remove(struct mcp *mcp) dev_err(&ucb->dev, "Can't remove gpio chip: %d\n", ret); } - free_irq(ucb->irq, ucb); + irq_set_chained_handler(ucb->irq, NULL); + irq_free_descs(ucb->irq_base, 16); device_unregister(&ucb->dev); + + if (pdata && pdata->reset) + pdata->reset(UCB_RST_REMOVE); } int ucb1x00_register_driver(struct ucb1x00_driver *drv) @@ -653,7 +648,7 @@ int ucb1x00_register_driver(struct ucb1x00_driver *drv) INIT_LIST_HEAD(&drv->devs); mutex_lock(&ucb1x00_mutex); - list_add(&drv->node, &ucb1x00_drivers); + list_add_tail(&drv->node, &ucb1x00_drivers); list_for_each_entry(ucb, &ucb1x00_devices, node) { ucb1x00_add_dev(ucb, drv); } @@ -674,44 +669,86 @@ void ucb1x00_unregister_driver(struct ucb1x00_driver *drv) mutex_unlock(&ucb1x00_mutex); } -static int ucb1x00_suspend(struct mcp *mcp, pm_message_t state) +static int ucb1x00_suspend(struct device *dev) { - struct ucb1x00 *ucb = mcp_get_drvdata(mcp); - struct ucb1x00_dev *dev; + struct ucb1x00_plat_data *pdata = dev->platform_data; + struct ucb1x00 *ucb = dev_get_drvdata(dev); + struct ucb1x00_dev *udev; mutex_lock(&ucb1x00_mutex); - list_for_each_entry(dev, &ucb->devs, dev_node) { - if (dev->drv->suspend) - dev->drv->suspend(dev, state); + list_for_each_entry(udev, &ucb->devs, dev_node) { + if (udev->drv->suspend) + udev->drv->suspend(udev); } mutex_unlock(&ucb1x00_mutex); + + if (ucb->irq_wake) { + unsigned long flags; + + raw_spin_lock_irqsave(&ucb->irq_lock, flags); + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_wake); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_wake); + ucb1x00_disable(ucb); + raw_spin_unlock_irqrestore(&ucb->irq_lock, flags); + + enable_irq_wake(ucb->irq); + } else if (pdata && pdata->reset) + pdata->reset(UCB_RST_SUSPEND); + return 0; } -static int ucb1x00_resume(struct mcp *mcp) +static int ucb1x00_resume(struct device *dev) { - struct ucb1x00 *ucb = mcp_get_drvdata(mcp); - struct ucb1x00_dev *dev; + struct ucb1x00_plat_data *pdata = dev->platform_data; + struct ucb1x00 *ucb = dev_get_drvdata(dev); + struct ucb1x00_dev *udev; + + if (!ucb->irq_wake && pdata && pdata->reset) + pdata->reset(UCB_RST_RESUME); + ucb1x00_enable(ucb); ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); + + if (ucb->irq_wake) { + unsigned long flags; + + raw_spin_lock_irqsave(&ucb->irq_lock, flags); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl & + ucb->irq_mask); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl & + ucb->irq_mask); + raw_spin_unlock_irqrestore(&ucb->irq_lock, flags); + + disable_irq_wake(ucb->irq); + } + ucb1x00_disable(ucb); + mutex_lock(&ucb1x00_mutex); - list_for_each_entry(dev, &ucb->devs, dev_node) { - if (dev->drv->resume) - dev->drv->resume(dev); + list_for_each_entry(udev, &ucb->devs, dev_node) { + if (udev->drv->resume) + udev->drv->resume(udev); } mutex_unlock(&ucb1x00_mutex); return 0; } +static const struct dev_pm_ops ucb1x00_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ucb1x00_suspend, ucb1x00_resume) +}; + static struct mcp_driver ucb1x00_driver = { .drv = { .name = "ucb1x00", + .owner = THIS_MODULE, + .pm = &ucb1x00_pm_ops, }, .probe = ucb1x00_probe, .remove = ucb1x00_remove, - .suspend = ucb1x00_suspend, - .resume = ucb1x00_resume, }; static int __init ucb1x00_init(void) @@ -742,14 +779,10 @@ EXPORT_SYMBOL(ucb1x00_adc_enable); EXPORT_SYMBOL(ucb1x00_adc_read); EXPORT_SYMBOL(ucb1x00_adc_disable); -EXPORT_SYMBOL(ucb1x00_hook_irq); -EXPORT_SYMBOL(ucb1x00_free_irq); -EXPORT_SYMBOL(ucb1x00_enable_irq); -EXPORT_SYMBOL(ucb1x00_disable_irq); - EXPORT_SYMBOL(ucb1x00_register_driver); EXPORT_SYMBOL(ucb1x00_unregister_driver); +MODULE_ALIAS("mcp:ucb1x00"); MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); MODULE_DESCRIPTION("UCB1x00 core driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ucb1x00-ts.c b/drivers/mfd/ucb1x00-ts.c index 63a3cbdfa3f3..1e0e20c0e082 100644 --- a/drivers/mfd/ucb1x00-ts.c +++ b/drivers/mfd/ucb1x00-ts.c @@ -20,8 +20,9 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> -#include <linux/smp.h> +#include <linux/interrupt.h> #include <linux/sched.h> +#include <linux/spinlock.h> #include <linux/completion.h> #include <linux/delay.h> #include <linux/string.h> @@ -32,7 +33,6 @@ #include <linux/kthread.h> #include <linux/mfd/ucb1x00.h> -#include <mach/dma.h> #include <mach/collie.h> #include <asm/mach-types.h> @@ -42,6 +42,8 @@ struct ucb1x00_ts { struct input_dev *idev; struct ucb1x00 *ucb; + spinlock_t irq_lock; + unsigned irq_disabled; wait_queue_head_t irq_wait; struct task_struct *rtask; u16 x_res; @@ -238,7 +240,12 @@ static int ucb1x00_thread(void *_ts) if (ucb1x00_ts_pen_down(ts)) { set_current_state(TASK_INTERRUPTIBLE); - ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, machine_is_collie() ? UCB_RISING : UCB_FALLING); + spin_lock_irq(&ts->irq_lock); + if (ts->irq_disabled) { + ts->irq_disabled = 0; + enable_irq(ts->ucb->irq_base + UCB_IRQ_TSPX); + } + spin_unlock_irq(&ts->irq_lock); ucb1x00_disable(ts->ucb); /* @@ -281,23 +288,37 @@ static int ucb1x00_thread(void *_ts) * We only detect touch screen _touches_ with this interrupt * handler, and even then we just schedule our task. */ -static void ucb1x00_ts_irq(int idx, void *id) +static irqreturn_t ucb1x00_ts_irq(int irq, void *id) { struct ucb1x00_ts *ts = id; - ucb1x00_disable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_FALLING); + spin_lock(&ts->irq_lock); + ts->irq_disabled = 1; + disable_irq_nosync(ts->ucb->irq_base + UCB_IRQ_TSPX); + spin_unlock(&ts->irq_lock); wake_up(&ts->irq_wait); + + return IRQ_HANDLED; } static int ucb1x00_ts_open(struct input_dev *idev) { struct ucb1x00_ts *ts = input_get_drvdata(idev); + unsigned long flags = 0; int ret = 0; BUG_ON(ts->rtask); + if (machine_is_collie()) + flags = IRQF_TRIGGER_RISING; + else + flags = IRQF_TRIGGER_FALLING; + + ts->irq_disabled = 0; + init_waitqueue_head(&ts->irq_wait); - ret = ucb1x00_hook_irq(ts->ucb, UCB_IRQ_TSPX, ucb1x00_ts_irq, ts); + ret = request_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ucb1x00_ts_irq, + flags, "ucb1x00-ts", ts); if (ret < 0) goto out; @@ -314,7 +335,7 @@ static int ucb1x00_ts_open(struct input_dev *idev) if (!IS_ERR(ts->rtask)) { ret = 0; } else { - ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts); ts->rtask = NULL; ret = -EFAULT; } @@ -334,7 +355,7 @@ static void ucb1x00_ts_close(struct input_dev *idev) kthread_stop(ts->rtask); ucb1x00_enable(ts->ucb); - ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + free_irq(ts->ucb->irq_base + UCB_IRQ_TSPX, ts); ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0); ucb1x00_disable(ts->ucb); } @@ -359,11 +380,13 @@ static int ucb1x00_ts_add(struct ucb1x00_dev *dev) ts->ucb = dev->ucb; ts->idev = idev; ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC; + spin_lock_init(&ts->irq_lock); idev->name = "Touchscreen panel"; idev->id.product = ts->ucb->id; idev->open = ucb1x00_ts_open; idev->close = ucb1x00_ts_close; + idev->dev.parent = &ts->ucb->dev; idev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); diff --git a/drivers/mfd/wm8994-regmap.c b/drivers/mfd/wm8994-regmap.c index bc0c5096539a..7605b6095453 100644 --- a/drivers/mfd/wm8994-regmap.c +++ b/drivers/mfd/wm8994-regmap.c @@ -15,6 +15,7 @@ #include <linux/mfd/wm8994/core.h> #include <linux/mfd/wm8994/registers.h> #include <linux/regmap.h> +#include <linux/device.h> #include "wm8994.h" diff --git a/drivers/misc/atmel_tclib.c b/drivers/misc/atmel_tclib.c index 4bcfc3759734..c8d8e38d0d8a 100644 --- a/drivers/misc/atmel_tclib.c +++ b/drivers/misc/atmel_tclib.c @@ -6,12 +6,10 @@ #include <linux/ioport.h> #include <linux/kernel.h> #include <linux/platform_device.h> +#include <linux/module.h> #include <linux/slab.h> #include <linux/export.h> - -/* Number of bytes to reserve for the iomem resource */ -#define ATMEL_TC_IOMEM_SIZE 256 - +#include <linux/of.h> /* * This is a thin library to solve the problem of how to portably allocate @@ -48,10 +46,17 @@ struct atmel_tc *atmel_tc_alloc(unsigned block, const char *name) struct atmel_tc *tc; struct platform_device *pdev = NULL; struct resource *r; + size_t size; spin_lock(&tc_list_lock); list_for_each_entry(tc, &tc_list, node) { - if (tc->pdev->id == block) { + if (tc->pdev->dev.of_node) { + if (of_alias_get_id(tc->pdev->dev.of_node, "tcb") + == block) { + pdev = tc->pdev; + break; + } + } else if (tc->pdev->id == block) { pdev = tc->pdev; break; } @@ -61,11 +66,15 @@ struct atmel_tc *atmel_tc_alloc(unsigned block, const char *name) goto fail; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - r = request_mem_region(r->start, ATMEL_TC_IOMEM_SIZE, name); if (!r) goto fail; - tc->regs = ioremap(r->start, ATMEL_TC_IOMEM_SIZE); + size = resource_size(r); + r = request_mem_region(r->start, size, name); + if (!r) + goto fail; + + tc->regs = ioremap(r->start, size); if (!tc->regs) goto fail_ioremap; @@ -76,7 +85,7 @@ out: return tc; fail_ioremap: - release_mem_region(r->start, ATMEL_TC_IOMEM_SIZE); + release_mem_region(r->start, size); fail: tc = NULL; goto out; @@ -96,7 +105,7 @@ void atmel_tc_free(struct atmel_tc *tc) spin_lock(&tc_list_lock); if (tc->regs) { iounmap(tc->regs); - release_mem_region(tc->iomem->start, ATMEL_TC_IOMEM_SIZE); + release_mem_region(tc->iomem->start, resource_size(tc->iomem)); tc->regs = NULL; tc->iomem = NULL; } @@ -104,6 +113,30 @@ void atmel_tc_free(struct atmel_tc *tc) } EXPORT_SYMBOL_GPL(atmel_tc_free); +#if defined(CONFIG_OF) +static struct atmel_tcb_config tcb_rm9200_config = { + .counter_width = 16, +}; + +static struct atmel_tcb_config tcb_sam9x5_config = { + .counter_width = 32, +}; + +static const struct of_device_id atmel_tcb_dt_ids[] = { + { + .compatible = "atmel,at91rm9200-tcb", + .data = &tcb_rm9200_config, + }, { + .compatible = "atmel,at91sam9x5-tcb", + .data = &tcb_sam9x5_config, + }, { + /* sentinel */ + } +}; + +MODULE_DEVICE_TABLE(of, atmel_tcb_dt_ids); +#endif + static int __init tc_probe(struct platform_device *pdev) { struct atmel_tc *tc; @@ -129,6 +162,14 @@ static int __init tc_probe(struct platform_device *pdev) return -EINVAL; } + /* Now take SoC information if available */ + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(atmel_tcb_dt_ids, pdev->dev.of_node); + if (match) + tc->tcb_config = match->data; + } + tc->clk[0] = clk; tc->clk[1] = clk_get(&pdev->dev, "t1_clk"); if (IS_ERR(tc->clk[1])) @@ -153,7 +194,10 @@ static int __init tc_probe(struct platform_device *pdev) } static struct platform_driver tc_driver = { - .driver.name = "atmel_tcb", + .driver = { + .name = "atmel_tcb", + .of_match_table = of_match_ptr(atmel_tcb_dt_ids), + }, }; static int __init tc_init(void) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 00fcbed1afd2..ecbee9bf87b2 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -395,7 +395,7 @@ config MMC_SPI config MMC_S3C tristate "Samsung S3C SD/MMC Card Interface support" - depends on ARCH_S3C2410 + depends on ARCH_S3C24XX help This selects a driver for the MCI interface found in Samsung's S3C2410, S3C2412, S3C2440, S3C2442 CPUs. diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index 947faa5d2ce4..efdb81d21c44 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -86,7 +86,6 @@ static inline int at91mci_is_mci1rev2xx(void) { return ( cpu_is_at91sam9260() || cpu_is_at91sam9263() - || cpu_is_at91cap9() || cpu_is_at91sam9rl() || cpu_is_at91sam9g10() || cpu_is_at91sam9g20() diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 11e589cd8233..983e244eca76 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -53,6 +53,8 @@ static unsigned int fmax = 515633; * @sdio: variant supports SDIO * @st_clkdiv: true if using a ST-specific clock divider algorithm * @blksz_datactrl16: true if Block size is at b16..b30 position in datactrl register + * @pwrreg_powerup: power up value for MMCIPOWER register + * @signal_direction: input/out direction of bus signals can be indicated */ struct variant_data { unsigned int clkreg; @@ -63,18 +65,22 @@ struct variant_data { bool sdio; bool st_clkdiv; bool blksz_datactrl16; + u32 pwrreg_powerup; + bool signal_direction; }; static struct variant_data variant_arm = { .fifosize = 16 * 4, .fifohalfsize = 8 * 4, .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, }; static struct variant_data variant_arm_extended_fifo = { .fifosize = 128 * 4, .fifohalfsize = 64 * 4, .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, }; static struct variant_data variant_u300 = { @@ -83,6 +89,8 @@ static struct variant_data variant_u300 = { .clkreg_enable = MCI_ST_U300_HWFCEN, .datalength_bits = 16, .sdio = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; static struct variant_data variant_ux500 = { @@ -93,6 +101,8 @@ static struct variant_data variant_ux500 = { .datalength_bits = 24, .sdio = true, .st_clkdiv = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; static struct variant_data variant_ux500v2 = { @@ -104,11 +114,35 @@ static struct variant_data variant_ux500v2 = { .sdio = true, .st_clkdiv = true, .blksz_datactrl16 = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; /* * This must be called with host->lock held */ +static void mmci_write_clkreg(struct mmci_host *host, u32 clk) +{ + if (host->clk_reg != clk) { + host->clk_reg = clk; + writel(clk, host->base + MMCICLOCK); + } +} + +/* + * This must be called with host->lock held + */ +static void mmci_write_pwrreg(struct mmci_host *host, u32 pwr) +{ + if (host->pwr_reg != pwr) { + host->pwr_reg = pwr; + writel(pwr, host->base + MMCIPOWER); + } +} + +/* + * This must be called with host->lock held + */ static void mmci_set_clkreg(struct mmci_host *host, unsigned int desired) { struct variant_data *variant = host->variant; @@ -153,7 +187,7 @@ static void mmci_set_clkreg(struct mmci_host *host, unsigned int desired) if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_8) clk |= MCI_ST_8BIT_BUS; - writel(clk, host->base + MMCICLOCK); + mmci_write_clkreg(host, clk); } static void @@ -166,14 +200,10 @@ mmci_request_end(struct mmci_host *host, struct mmc_request *mrq) host->mrq = NULL; host->cmd = NULL; - /* - * Need to drop the host lock here; mmc_request_done may call - * back into the driver... - */ - spin_unlock(&host->lock); - pm_runtime_put(mmc_dev(host->mmc)); mmc_request_done(host->mmc, mrq); - spin_lock(&host->lock); + + pm_runtime_mark_last_busy(mmc_dev(host->mmc)); + pm_runtime_put_autosuspend(mmc_dev(host->mmc)); } static void mmci_set_mask1(struct mmci_host *host, unsigned int mask) @@ -607,6 +637,11 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) if (data->flags & MMC_DATA_READ) datactrl |= MCI_DPSM_DIRECTION; + /* The ST Micro variants has a special bit to enable SDIO */ + if (variant->sdio && host->mmc->card) + if (mmc_card_sdio(host->mmc->card)) + datactrl |= MCI_ST_DPSM_SDIOEN; + /* * Attempt to use DMA operation mode, if this * should fail, fall back to PIO mode @@ -635,11 +670,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) irqmask = MCI_TXFIFOHALFEMPTYMASK; } - /* The ST Micro variants has a special bit to enable SDIO */ - if (variant->sdio && host->mmc->card) - if (mmc_card_sdio(host->mmc->card)) - datactrl |= MCI_ST_DPSM_SDIOEN; - writel(datactrl, base + MMCIDATACTRL); writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0); mmci_set_mask1(host, irqmask); @@ -786,7 +816,24 @@ static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int rema if (count <= 0) break; - readsl(base + MMCIFIFO, ptr, count >> 2); + /* + * SDIO especially may want to send something that is + * not divisible by 4 (as opposed to card sectors + * etc). Therefore make sure to always read the last bytes + * while only doing full 32-bit reads towards the FIFO. + */ + if (unlikely(count & 0x3)) { + if (count < 4) { + unsigned char buf[4]; + readsl(base + MMCIFIFO, buf, 1); + memcpy(ptr, buf, count); + } else { + readsl(base + MMCIFIFO, ptr, count >> 2); + count &= ~0x3; + } + } else { + readsl(base + MMCIFIFO, ptr, count >> 2); + } ptr += count; remain -= count; @@ -821,14 +868,13 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int rem */ if (variant->sdio && mmc_card_sdio(host->mmc->card)) { + u32 clk; if (count < 8) - writel(readl(host->base + MMCICLOCK) & - ~variant->clkreg_enable, - host->base + MMCICLOCK); + clk = host->clk_reg & ~variant->clkreg_enable; else - writel(readl(host->base + MMCICLOCK) | - variant->clkreg_enable, - host->base + MMCICLOCK); + clk = host->clk_reg | variant->clkreg_enable; + + mmci_write_clkreg(host, clk); } /* @@ -1015,10 +1061,17 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; u32 pwr = 0; unsigned long flags; int ret; + pm_runtime_get_sync(mmc_dev(mmc)); + + if (host->plat->ios_handler && + host->plat->ios_handler(mmc_dev(mmc), ios)) + dev_err(mmc_dev(mmc), "platform ios_handler failed\n"); + switch (ios->power_mode) { case MMC_POWER_OFF: if (host->vcc) @@ -1035,22 +1088,38 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) * power should be rare so we print an error * and return here. */ - return; + goto out; } } - if (host->plat->vdd_handler) - pwr |= host->plat->vdd_handler(mmc_dev(mmc), ios->vdd, - ios->power_mode); - /* The ST version does not have this, fall through to POWER_ON */ - if (host->hw_designer != AMBA_VENDOR_ST) { - pwr |= MCI_PWR_UP; - break; - } + /* + * The ST Micro variant doesn't have the PL180s MCI_PWR_UP + * and instead uses MCI_PWR_ON so apply whatever value is + * configured in the variant data. + */ + pwr |= variant->pwrreg_powerup; + + break; case MMC_POWER_ON: pwr |= MCI_PWR_ON; break; } + if (variant->signal_direction && ios->power_mode != MMC_POWER_OFF) { + /* + * The ST Micro variant has some additional bits + * indicating signal direction for the signals in + * the SD/MMC bus and feedback-clock usage. + */ + pwr |= host->plat->sigdir; + + if (ios->bus_width == MMC_BUS_WIDTH_4) + pwr &= ~MCI_ST_DATA74DIREN; + else if (ios->bus_width == MMC_BUS_WIDTH_1) + pwr &= (~MCI_ST_DATA74DIREN & + ~MCI_ST_DATA31DIREN & + ~MCI_ST_DATA2DIREN); + } + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) { if (host->hw_designer != AMBA_VENDOR_ST) pwr |= MCI_ROD; @@ -1066,13 +1135,13 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) spin_lock_irqsave(&host->lock, flags); mmci_set_clkreg(host, ios->clock); - - if (host->pwr != pwr) { - host->pwr = pwr; - writel(pwr, host->base + MMCIPOWER); - } + mmci_write_pwrreg(host, pwr); spin_unlock_irqrestore(&host->lock, flags); + + out: + pm_runtime_mark_last_busy(mmc_dev(mmc)); + pm_runtime_put_autosuspend(mmc_dev(mmc)); } static int mmci_get_ro(struct mmc_host *mmc) @@ -1326,7 +1395,7 @@ static int __devinit mmci_probe(struct amba_device *dev, if (ret) goto unmap; - if (dev->irq[1] == NO_IRQ) + if (dev->irq[1] == NO_IRQ || !dev->irq[1]) host->singleirq = true; else { ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED, @@ -1346,6 +1415,8 @@ static int __devinit mmci_probe(struct amba_device *dev, mmci_dma_setup(host); + pm_runtime_set_autosuspend_delay(&dev->dev, 50); + pm_runtime_use_autosuspend(&dev->dev); pm_runtime_put(&dev->dev); mmc_add_host(mmc); @@ -1430,43 +1501,49 @@ static int __devexit mmci_remove(struct amba_device *dev) return 0; } -#ifdef CONFIG_PM -static int mmci_suspend(struct amba_device *dev, pm_message_t state) +#ifdef CONFIG_SUSPEND +static int mmci_suspend(struct device *dev) { - struct mmc_host *mmc = amba_get_drvdata(dev); + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); int ret = 0; if (mmc) { struct mmci_host *host = mmc_priv(mmc); ret = mmc_suspend_host(mmc); - if (ret == 0) + if (ret == 0) { + pm_runtime_get_sync(dev); writel(0, host->base + MMCIMASK0); + } } return ret; } -static int mmci_resume(struct amba_device *dev) +static int mmci_resume(struct device *dev) { - struct mmc_host *mmc = amba_get_drvdata(dev); + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); int ret = 0; if (mmc) { struct mmci_host *host = mmc_priv(mmc); writel(MCI_IRQENABLE, host->base + MMCIMASK0); + pm_runtime_put(dev); ret = mmc_resume_host(mmc); } return ret; } -#else -#define mmci_suspend NULL -#define mmci_resume NULL #endif +static const struct dev_pm_ops mmci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mmci_suspend, mmci_resume) +}; + static struct amba_id mmci_ids[] = { { .id = 0x00041180, @@ -1512,26 +1589,15 @@ MODULE_DEVICE_TABLE(amba, mmci_ids); static struct amba_driver mmci_driver = { .drv = { .name = DRIVER_NAME, + .pm = &mmci_dev_pm_ops, }, .probe = mmci_probe, .remove = __devexit_p(mmci_remove), - .suspend = mmci_suspend, - .resume = mmci_resume, .id_table = mmci_ids, }; -static int __init mmci_init(void) -{ - return amba_driver_register(&mmci_driver); -} - -static void __exit mmci_exit(void) -{ - amba_driver_unregister(&mmci_driver); -} +module_amba_driver(mmci_driver); -module_init(mmci_init); -module_exit(mmci_exit); module_param(fmax, uint, 0444); MODULE_DESCRIPTION("ARM PrimeCell PL180/181 Multimedia Card Interface driver"); diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index 79e4143ab9df..d437ccf62d6b 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -13,16 +13,6 @@ #define MCI_PWR_ON 0x03 #define MCI_OD (1 << 6) #define MCI_ROD (1 << 7) -/* - * The ST Micro version does not have ROD and reuse the voltage registers - * for direction settings - */ -#define MCI_ST_DATA2DIREN (1 << 2) -#define MCI_ST_CMDDIREN (1 << 3) -#define MCI_ST_DATA0DIREN (1 << 4) -#define MCI_ST_DATA31DIREN (1 << 5) -#define MCI_ST_FBCLKEN (1 << 7) -#define MCI_ST_DATA74DIREN (1 << 8) #define MMCICLOCK 0x004 #define MCI_CLK_ENABLE (1 << 8) @@ -160,7 +150,7 @@ (MCI_RXFIFOHALFFULLMASK | MCI_RXDATAAVLBLMASK | \ MCI_TXFIFOHALFEMPTYMASK) -#define NR_SG 16 +#define NR_SG 128 struct clk; struct variant_data; @@ -189,7 +179,8 @@ struct mmci_host { unsigned int mclk; unsigned int cclk; - u32 pwr; + u32 pwr_reg; + u32 clk_reg; struct mmci_platform_data *plat; struct variant_data *variant; diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 0be4e2013632..6193a0d7bde5 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -464,7 +464,7 @@ static int __devinit sdhci_esdhc_imx_probe(struct platform_device *pdev) err = PTR_ERR(clk); goto err_clk_get; } - clk_enable(clk); + clk_prepare_enable(clk); pltfm_host->clk = clk; if (!is_imx25_esdhc(imx_data)) @@ -559,7 +559,7 @@ no_card_detect_irq: gpio_free(boarddata->wp_gpio); no_card_detect_pin: no_board_data: - clk_disable(pltfm_host->clk); + clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); err_clk_get: kfree(imx_data); @@ -586,7 +586,7 @@ static int __devexit sdhci_esdhc_imx_remove(struct platform_device *pdev) gpio_free(boarddata->cd_gpio); } - clk_disable(pltfm_host->clk); + clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); kfree(imx_data); diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 1af756ee0f9a..b19e7d435f8d 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -518,9 +518,6 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT) host->mmc->caps = MMC_CAP_NONREMOVABLE; - if (pdata->host_caps) - host->mmc->caps |= pdata->host_caps; - if (pdata->pm_caps) host->mmc->pm_caps |= pdata->pm_caps; @@ -544,6 +541,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) if (pdata->host_caps) host->mmc->caps |= pdata->host_caps; + if (pdata->host_caps2) + host->mmc->caps2 |= pdata->host_caps2; + ret = sdhci_add_host(host); if (ret) { dev_err(dev, "sdhci_add_host() failed\n"); diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 78a36eba4df0..cb348569454b 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -23,7 +23,6 @@ #include <linux/gpio.h> #include <linux/mmc/card.h> #include <linux/mmc/host.h> -#include <linux/module.h> #include <asm/gpio.h> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 1be621841400..284cf3433720 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -1,6 +1,6 @@ menuconfig MTD tristate "Memory Technology Device (MTD) support" - depends on HAS_IOMEM + depends on GENERIC_IO help Memory Technology Devices are flash, RAM and similar chips, often used for solid state file systems on embedded devices. This option diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index e1e122f2f929..9bcd1f415f43 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -2526,12 +2526,10 @@ static void cfi_intelext_restore_locks(struct mtd_info *mtd) if (!region->lockmap) continue; - for (block = 0; block < region->numblocks; block++) { + for_each_clear_bit(block, region->lockmap, region->numblocks) { len = region->erasesize; adr = region->offset + block * len; - - if (!test_bit(block, region->lockmap)) - cfi_intelext_unlock(mtd, adr, len); + cfi_intelext_unlock(mtd, adr, len); } } } diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 37b05c3f2792..8d3dac40d7e6 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -1,5 +1,6 @@ menu "Self-contained MTD device drivers" depends on MTD!=n + depends on HAS_IOMEM config MTD_PMC551 tristate "Ramix PMC551 PCI Mezzanine RAM card support" diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 6c5c431c64af..8af67cfd671a 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -1,5 +1,6 @@ menu "Mapping drivers for chip access" depends on MTD!=n + depends on HAS_IOMEM config MTD_COMPLEX_MAPPINGS bool "Support non-linear mappings of flash chips" diff --git a/drivers/mtd/maps/sa1100-flash.c b/drivers/mtd/maps/sa1100-flash.c index 502821997707..cbc3b7867910 100644 --- a/drivers/mtd/maps/sa1100-flash.c +++ b/drivers/mtd/maps/sa1100-flash.c @@ -23,106 +23,6 @@ #include <asm/sizes.h> #include <asm/mach/flash.h> -#if 0 -/* - * This is here for documentation purposes only - until these people - * submit their machine types. It will be gone January 2005. - */ -static struct mtd_partition consus_partitions[] = { - { - .name = "Consus boot firmware", - .offset = 0, - .size = 0x00040000, - .mask_flags = MTD_WRITABLE, /* force read-only */ - }, { - .name = "Consus kernel", - .offset = 0x00040000, - .size = 0x00100000, - .mask_flags = 0, - }, { - .name = "Consus disk", - .offset = 0x00140000, - /* The rest (up to 16M) for jffs. We could put 0 and - make it find the size automatically, but right now - i have 32 megs. jffs will use all 32 megs if given - the chance, and this leads to horrible problems - when you try to re-flash the image because blob - won't erase the whole partition. */ - .size = 0x01000000 - 0x00140000, - .mask_flags = 0, - }, { - /* this disk is a secondary disk, which can be used as - needed, for simplicity, make it the size of the other - consus partition, although realistically it could be - the remainder of the disk (depending on the file - system used) */ - .name = "Consus disk2", - .offset = 0x01000000, - .size = 0x01000000 - 0x00140000, - .mask_flags = 0, - } -}; - -/* Frodo has 2 x 16M 28F128J3A flash chips in bank 0: */ -static struct mtd_partition frodo_partitions[] = -{ - { - .name = "bootloader", - .size = 0x00040000, - .offset = 0x00000000, - .mask_flags = MTD_WRITEABLE - }, { - .name = "bootloader params", - .size = 0x00040000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE - }, { - .name = "kernel", - .size = 0x00100000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE - }, { - .name = "ramdisk", - .size = 0x00400000, - .offset = MTDPART_OFS_APPEND, - .mask_flags = MTD_WRITEABLE - }, { - .name = "file system", - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND - } -}; - -static struct mtd_partition jornada56x_partitions[] = { - { - .name = "bootldr", - .size = 0x00040000, - .offset = 0, - .mask_flags = MTD_WRITEABLE, - }, { - .name = "rootfs", - .size = MTDPART_SIZ_FULL, - .offset = MTDPART_OFS_APPEND, - } -}; - -static void jornada56x_set_vpp(int vpp) -{ - if (vpp) - GPSR = GPIO_GPIO26; - else - GPCR = GPIO_GPIO26; - GPDR |= GPIO_GPIO26; -} - -/* - * Machine Phys Size set_vpp - * Consus : SA1100_CS0_PHYS SZ_32M - * Frodo : SA1100_CS0_PHYS SZ_32M - * Jornada56x: SA1100_CS0_PHYS SZ_32M jornada56x_set_vpp - */ -#endif - struct sa_subdev_info { char name[16]; struct map_info map; @@ -373,21 +273,9 @@ static int __exit sa1100_mtd_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM -static void sa1100_mtd_shutdown(struct platform_device *dev) -{ - struct sa_info *info = platform_get_drvdata(dev); - if (info && mtd_suspend(info->mtd) == 0) - mtd_resume(info->mtd); -} -#else -#define sa1100_mtd_shutdown NULL -#endif - static struct platform_driver sa1100_mtd_driver = { .probe = sa1100_mtd_probe, .remove = __exit_p(sa1100_mtd_remove), - .shutdown = sa1100_mtd_shutdown, .driver = { .name = "sa1100-mtd", .owner = THIS_MODULE, diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index 50c6a1e7f675..c57ae92ebda4 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -31,13 +31,13 @@ #include <linux/compat.h> #include <linux/mount.h> #include <linux/blkpg.h> +#include <linux/magic.h> #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> #include <linux/mtd/map.h> #include <asm/uaccess.h> -#define MTD_INODE_FS_MAGIC 0x11307854 static DEFINE_MUTEX(mtd_mutex); static struct vfsmount *mtd_inode_mnt __read_mostly; diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 3b1d6da874e0..a3c4de551ebe 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -187,7 +187,7 @@ config MTD_NAND_PPCHAMELEONEVB config MTD_NAND_S3C2410 tristate "NAND Flash support for Samsung S3C SoCs" - depends on ARCH_S3C2410 || ARCH_S3C64XX + depends on ARCH_S3C24XX || ARCH_S3C64XX help This enables the NAND flash controller on the S3C24xx and S3C64xx SoCs @@ -246,6 +246,7 @@ config MTD_NAND_BCM_UMI_HWCS config MTD_NAND_DISKONCHIP tristate "DiskOnChip 2000, Millennium and Millennium Plus (NAND reimplementation) (EXPERIMENTAL)" depends on EXPERIMENTAL + depends on HAS_IOMEM select REED_SOLOMON select REED_SOLOMON_DEC16 help @@ -431,6 +432,7 @@ config MTD_NAND_GPMI_NAND config MTD_NAND_PLATFORM tristate "Support for generic platform NAND driver" + depends on HAS_IOMEM help This implements a generic NAND driver for on-SOC platform devices. You will need to provide platform-specific functions diff --git a/drivers/mtd/nand/ams-delta.c b/drivers/mtd/nand/ams-delta.c index 3197e9764fcd..73416951f4c1 100644 --- a/drivers/mtd/nand/ams-delta.c +++ b/drivers/mtd/nand/ams-delta.c @@ -26,7 +26,7 @@ #include <asm/io.h> #include <mach/hardware.h> #include <asm/sizes.h> -#include <asm/gpio.h> +#include <linux/gpio.h> #include <plat/board-ams-delta.h> /* @@ -34,8 +34,6 @@ */ static struct mtd_info *ams_delta_mtd = NULL; -#define NAND_MASK (AMS_DELTA_LATCH2_NAND_NRE | AMS_DELTA_LATCH2_NAND_NWE | AMS_DELTA_LATCH2_NAND_CLE | AMS_DELTA_LATCH2_NAND_ALE | AMS_DELTA_LATCH2_NAND_NCE | AMS_DELTA_LATCH2_NAND_NWP) - /* * Define partitions for flash devices */ @@ -68,10 +66,9 @@ static void ams_delta_write_byte(struct mtd_info *mtd, u_char byte) writew(0, io_base + OMAP_MPUIO_IO_CNTL); writew(byte, this->IO_ADDR_W); - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NWE, 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NWE, 0); ndelay(40); - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NWE, - AMS_DELTA_LATCH2_NAND_NWE); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NWE, 1); } static u_char ams_delta_read_byte(struct mtd_info *mtd) @@ -80,12 +77,11 @@ static u_char ams_delta_read_byte(struct mtd_info *mtd) struct nand_chip *this = mtd->priv; void __iomem *io_base = this->priv; - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NRE, 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NRE, 0); ndelay(40); writew(~0, io_base + OMAP_MPUIO_IO_CNTL); res = readw(this->IO_ADDR_R); - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NRE, - AMS_DELTA_LATCH2_NAND_NRE); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NRE, 1); return res; } @@ -132,15 +128,12 @@ static void ams_delta_hwcontrol(struct mtd_info *mtd, int cmd, { if (ctrl & NAND_CTRL_CHANGE) { - unsigned long bits; - - bits = (~ctrl & NAND_NCE) ? AMS_DELTA_LATCH2_NAND_NCE : 0; - bits |= (ctrl & NAND_CLE) ? AMS_DELTA_LATCH2_NAND_CLE : 0; - bits |= (ctrl & NAND_ALE) ? AMS_DELTA_LATCH2_NAND_ALE : 0; - - ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_CLE | - AMS_DELTA_LATCH2_NAND_ALE | - AMS_DELTA_LATCH2_NAND_NCE, bits); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_NCE, + (ctrl & NAND_NCE) == 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_CLE, + (ctrl & NAND_CLE) != 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_NAND_ALE, + (ctrl & NAND_ALE) != 0); } if (cmd != NAND_CMD_NONE) @@ -152,6 +145,39 @@ static int ams_delta_nand_ready(struct mtd_info *mtd) return gpio_get_value(AMS_DELTA_GPIO_PIN_NAND_RB); } +static const struct gpio _mandatory_gpio[] = { + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NCE, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nce", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NRE, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nre", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NWP, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nwp", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_NWE, + .flags = GPIOF_OUT_INIT_HIGH, + .label = "nand_nwe", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_ALE, + .flags = GPIOF_OUT_INIT_LOW, + .label = "nand_ale", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_NAND_CLE, + .flags = GPIOF_OUT_INIT_LOW, + .label = "nand_cle", + }, +}; + /* * Main initialization routine */ @@ -223,10 +249,9 @@ static int __devinit ams_delta_init(struct platform_device *pdev) platform_set_drvdata(pdev, io_base); /* Set chip enabled, but */ - ams_delta_latch2_write(NAND_MASK, AMS_DELTA_LATCH2_NAND_NRE | - AMS_DELTA_LATCH2_NAND_NWE | - AMS_DELTA_LATCH2_NAND_NCE | - AMS_DELTA_LATCH2_NAND_NWP); + err = gpio_request_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio)); + if (err) + goto out_gpio; /* Scan to find existence of the device */ if (nand_scan(ams_delta_mtd, 1)) { @@ -241,7 +266,10 @@ static int __devinit ams_delta_init(struct platform_device *pdev) goto out; out_mtd: + gpio_free_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio)); +out_gpio: platform_set_drvdata(pdev, NULL); + gpio_free(AMS_DELTA_GPIO_PIN_NAND_RB); iounmap(io_base); out_release_io: release_mem_region(res->start, resource_size(res)); @@ -262,6 +290,8 @@ static int __devexit ams_delta_cleanup(struct platform_device *pdev) /* Release resources, unregister device */ nand_release(ams_delta_mtd); + gpio_free_array(_mandatory_gpio, ARRAY_SIZE(_mandatory_gpio)); + gpio_free(AMS_DELTA_GPIO_PIN_NAND_RB); iounmap(io_base); release_mem_region(res->start, resource_size(res)); diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c index 35b4fb55dbd6..ae7e37d9ac17 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel_nand.c @@ -27,6 +27,10 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_mtd.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/partitions.h> @@ -34,22 +38,10 @@ #include <linux/dmaengine.h> #include <linux/gpio.h> #include <linux/io.h> +#include <linux/platform_data/atmel.h> -#include <mach/board.h> #include <mach/cpu.h> -#ifdef CONFIG_MTD_NAND_ATMEL_ECC_HW -#define hard_ecc 1 -#else -#define hard_ecc 0 -#endif - -#ifdef CONFIG_MTD_NAND_ATMEL_ECC_NONE -#define no_ecc 1 -#else -#define no_ecc 0 -#endif - static int use_dma = 1; module_param(use_dma, int, 0); @@ -95,7 +87,7 @@ struct atmel_nand_host { struct mtd_info mtd; void __iomem *io_base; dma_addr_t io_phys; - struct atmel_nand_data *board; + struct atmel_nand_data board; struct device *dev; void __iomem *ecc; @@ -113,8 +105,8 @@ static int cpu_has_dma(void) */ static void atmel_nand_enable(struct atmel_nand_host *host) { - if (gpio_is_valid(host->board->enable_pin)) - gpio_set_value(host->board->enable_pin, 0); + if (gpio_is_valid(host->board.enable_pin)) + gpio_set_value(host->board.enable_pin, 0); } /* @@ -122,8 +114,8 @@ static void atmel_nand_enable(struct atmel_nand_host *host) */ static void atmel_nand_disable(struct atmel_nand_host *host) { - if (gpio_is_valid(host->board->enable_pin)) - gpio_set_value(host->board->enable_pin, 1); + if (gpio_is_valid(host->board.enable_pin)) + gpio_set_value(host->board.enable_pin, 1); } /* @@ -144,9 +136,9 @@ static void atmel_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl return; if (ctrl & NAND_CLE) - writeb(cmd, host->io_base + (1 << host->board->cle)); + writeb(cmd, host->io_base + (1 << host->board.cle)); else - writeb(cmd, host->io_base + (1 << host->board->ale)); + writeb(cmd, host->io_base + (1 << host->board.ale)); } /* @@ -157,8 +149,8 @@ static int atmel_nand_device_ready(struct mtd_info *mtd) struct nand_chip *nand_chip = mtd->priv; struct atmel_nand_host *host = nand_chip->priv; - return gpio_get_value(host->board->rdy_pin) ^ - !!host->board->rdy_pin_active_low; + return gpio_get_value(host->board.rdy_pin) ^ + !!host->board.rdy_pin_active_low; } /* @@ -273,7 +265,7 @@ static void atmel_read_buf(struct mtd_info *mtd, u8 *buf, int len) if (atmel_nand_dma_op(mtd, buf, len, 1) == 0) return; - if (host->board->bus_width_16) + if (host->board.bus_width_16) atmel_read_buf16(mtd, buf, len); else atmel_read_buf8(mtd, buf, len); @@ -289,7 +281,7 @@ static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len) if (atmel_nand_dma_op(mtd, (void *)buf, len, 0) == 0) return; - if (host->board->bus_width_16) + if (host->board.bus_width_16) atmel_write_buf16(mtd, buf, len); else atmel_write_buf8(mtd, buf, len); @@ -481,6 +473,56 @@ static void atmel_nand_hwctl(struct mtd_info *mtd, int mode) } } +#if defined(CONFIG_OF) +static int __devinit atmel_of_init_port(struct atmel_nand_host *host, + struct device_node *np) +{ + u32 val; + int ecc_mode; + struct atmel_nand_data *board = &host->board; + enum of_gpio_flags flags; + + if (of_property_read_u32(np, "atmel,nand-addr-offset", &val) == 0) { + if (val >= 32) { + dev_err(host->dev, "invalid addr-offset %u\n", val); + return -EINVAL; + } + board->ale = val; + } + + if (of_property_read_u32(np, "atmel,nand-cmd-offset", &val) == 0) { + if (val >= 32) { + dev_err(host->dev, "invalid cmd-offset %u\n", val); + return -EINVAL; + } + board->cle = val; + } + + ecc_mode = of_get_nand_ecc_mode(np); + + board->ecc_mode = ecc_mode < 0 ? NAND_ECC_SOFT : ecc_mode; + + board->on_flash_bbt = of_get_nand_on_flash_bbt(np); + + if (of_get_nand_bus_width(np) == 16) + board->bus_width_16 = 1; + + board->rdy_pin = of_get_gpio_flags(np, 0, &flags); + board->rdy_pin_active_low = (flags == OF_GPIO_ACTIVE_LOW); + + board->enable_pin = of_get_gpio(np, 1); + board->det_pin = of_get_gpio(np, 2); + + return 0; +} +#else +static int __devinit atmel_of_init_port(struct atmel_nand_host *host, + struct device_node *np) +{ + return -EINVAL; +} +#endif + /* * Probe for the NAND device. */ @@ -491,6 +533,7 @@ static int __init atmel_nand_probe(struct platform_device *pdev) struct nand_chip *nand_chip; struct resource *regs; struct resource *mem; + struct mtd_part_parser_data ppdata = {}; int res; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -517,8 +560,15 @@ static int __init atmel_nand_probe(struct platform_device *pdev) mtd = &host->mtd; nand_chip = &host->nand_chip; - host->board = pdev->dev.platform_data; host->dev = &pdev->dev; + if (pdev->dev.of_node) { + res = atmel_of_init_port(host, pdev->dev.of_node); + if (res) + goto err_nand_ioremap; + } else { + memcpy(&host->board, pdev->dev.platform_data, + sizeof(struct atmel_nand_data)); + } nand_chip->priv = host; /* link the private data structures */ mtd->priv = nand_chip; @@ -529,26 +579,25 @@ static int __init atmel_nand_probe(struct platform_device *pdev) nand_chip->IO_ADDR_W = host->io_base; nand_chip->cmd_ctrl = atmel_nand_cmd_ctrl; - if (gpio_is_valid(host->board->rdy_pin)) + if (gpio_is_valid(host->board.rdy_pin)) nand_chip->dev_ready = atmel_nand_device_ready; + nand_chip->ecc.mode = host->board.ecc_mode; + regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!regs && hard_ecc) { + if (!regs && nand_chip->ecc.mode == NAND_ECC_HW) { printk(KERN_ERR "atmel_nand: can't get I/O resource " "regs\nFalling back on software ECC\n"); + nand_chip->ecc.mode = NAND_ECC_SOFT; } - nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC */ - if (no_ecc) - nand_chip->ecc.mode = NAND_ECC_NONE; - if (hard_ecc && regs) { + if (nand_chip->ecc.mode == NAND_ECC_HW) { host->ecc = ioremap(regs->start, resource_size(regs)); if (host->ecc == NULL) { printk(KERN_ERR "atmel_nand: ioremap failed\n"); res = -EIO; goto err_ecc_ioremap; } - nand_chip->ecc.mode = NAND_ECC_HW; nand_chip->ecc.calculate = atmel_nand_calculate; nand_chip->ecc.correct = atmel_nand_correct; nand_chip->ecc.hwctl = atmel_nand_hwctl; @@ -558,7 +607,7 @@ static int __init atmel_nand_probe(struct platform_device *pdev) nand_chip->chip_delay = 20; /* 20us command delay time */ - if (host->board->bus_width_16) /* 16-bit bus width */ + if (host->board.bus_width_16) /* 16-bit bus width */ nand_chip->options |= NAND_BUSWIDTH_16; nand_chip->read_buf = atmel_read_buf; @@ -567,15 +616,15 @@ static int __init atmel_nand_probe(struct platform_device *pdev) platform_set_drvdata(pdev, host); atmel_nand_enable(host); - if (gpio_is_valid(host->board->det_pin)) { - if (gpio_get_value(host->board->det_pin)) { + if (gpio_is_valid(host->board.det_pin)) { + if (gpio_get_value(host->board.det_pin)) { printk(KERN_INFO "No SmartMedia card inserted.\n"); res = -ENXIO; goto err_no_card; } } - if (on_flash_bbt) { + if (host->board.on_flash_bbt || on_flash_bbt) { printk(KERN_INFO "atmel_nand: Use On Flash BBT\n"); nand_chip->bbt_options |= NAND_BBT_USE_FLASH; } @@ -650,8 +699,9 @@ static int __init atmel_nand_probe(struct platform_device *pdev) } mtd->name = "atmel_nand"; - res = mtd_device_parse_register(mtd, NULL, 0, - host->board->parts, host->board->num_parts); + ppdata.of_node = pdev->dev.of_node; + res = mtd_device_parse_register(mtd, NULL, &ppdata, + host->board.parts, host->board.num_parts); if (!res) return res; @@ -695,11 +745,21 @@ static int __exit atmel_nand_remove(struct platform_device *pdev) return 0; } +#if defined(CONFIG_OF) +static const struct of_device_id atmel_nand_dt_ids[] = { + { .compatible = "atmel,at91rm9200-nand" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_nand_dt_ids); +#endif + static struct platform_driver atmel_nand_driver = { .remove = __exit_p(atmel_nand_remove), .driver = { .name = "atmel_nand", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(atmel_nand_dt_ids), }, }; diff --git a/drivers/mtd/onenand/Kconfig b/drivers/mtd/onenand/Kconfig index 772ad2966619..91467bb03634 100644 --- a/drivers/mtd/onenand/Kconfig +++ b/drivers/mtd/onenand/Kconfig @@ -1,6 +1,7 @@ menuconfig MTD_ONENAND tristate "OneNAND Device Support" depends on MTD + depends on HAS_IOMEM help This enables support for accessing all type of OneNAND flash devices. For further information see diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 115749f20f9e..0fde9fc7d2e5 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -945,12 +945,8 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset) goto out_free; err = -ENOMEM; - ubi->peb_buf1 = vmalloc(ubi->peb_size); - if (!ubi->peb_buf1) - goto out_free; - - ubi->peb_buf2 = vmalloc(ubi->peb_size); - if (!ubi->peb_buf2) + ubi->peb_buf = vmalloc(ubi->peb_size); + if (!ubi->peb_buf) goto out_free; err = ubi_debugging_init_dev(ubi); @@ -1029,8 +1025,7 @@ out_detach: out_debugging: ubi_debugging_exit_dev(ubi); out_free: - vfree(ubi->peb_buf1); - vfree(ubi->peb_buf2); + vfree(ubi->peb_buf); if (ref) put_device(&ubi->dev); else @@ -1101,8 +1096,7 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway) vfree(ubi->vtbl); put_mtd_device(ubi->mtd); ubi_debugging_exit_dev(ubi); - vfree(ubi->peb_buf1); - vfree(ubi->peb_buf2); + vfree(ubi->peb_buf); ubi_msg("mtd%d is detached from ubi%d", ubi->mtd->index, ubi->ubi_num); put_device(&ubi->dev); return 0; diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c index cd26da8ad225..2455d620d96b 100644 --- a/drivers/mtd/ubi/eba.c +++ b/drivers/mtd/ubi/eba.c @@ -529,18 +529,18 @@ retry: data_size = offset + len; mutex_lock(&ubi->buf_mutex); - memset(ubi->peb_buf1 + offset, 0xFF, len); + memset(ubi->peb_buf + offset, 0xFF, len); /* Read everything before the area where the write failure happened */ if (offset > 0) { - err = ubi_io_read_data(ubi, ubi->peb_buf1, pnum, 0, offset); + err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, offset); if (err && err != UBI_IO_BITFLIPS) goto out_unlock; } - memcpy(ubi->peb_buf1 + offset, buf, len); + memcpy(ubi->peb_buf + offset, buf, len); - err = ubi_io_write_data(ubi, ubi->peb_buf1, new_pnum, 0, data_size); + err = ubi_io_write_data(ubi, ubi->peb_buf, new_pnum, 0, data_size); if (err) { mutex_unlock(&ubi->buf_mutex); goto write_error; @@ -979,7 +979,7 @@ static int is_error_sane(int err) * physical eraseblock @to. The @vid_hdr buffer may be changed by this * function. Returns: * o %0 in case of success; - * o %MOVE_CANCEL_RACE, %MOVE_TARGET_WR_ERR, %MOVE_CANCEL_BITFLIPS, etc; + * o %MOVE_CANCEL_RACE, %MOVE_TARGET_WR_ERR, %MOVE_TARGET_BITFLIPS, etc; * o a negative error code in case of failure. */ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, @@ -1053,13 +1053,13 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, /* * OK, now the LEB is locked and we can safely start moving it. Since - * this function utilizes the @ubi->peb_buf1 buffer which is shared + * this function utilizes the @ubi->peb_buf buffer which is shared * with some other functions - we lock the buffer by taking the * @ubi->buf_mutex. */ mutex_lock(&ubi->buf_mutex); dbg_wl("read %d bytes of data", aldata_size); - err = ubi_io_read_data(ubi, ubi->peb_buf1, from, 0, aldata_size); + err = ubi_io_read_data(ubi, ubi->peb_buf, from, 0, aldata_size); if (err && err != UBI_IO_BITFLIPS) { ubi_warn("error %d while reading data from PEB %d", err, from); @@ -1079,10 +1079,10 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, */ if (vid_hdr->vol_type == UBI_VID_DYNAMIC) aldata_size = data_size = - ubi_calc_data_len(ubi, ubi->peb_buf1, data_size); + ubi_calc_data_len(ubi, ubi->peb_buf, data_size); cond_resched(); - crc = crc32(UBI_CRC32_INIT, ubi->peb_buf1, data_size); + crc = crc32(UBI_CRC32_INIT, ubi->peb_buf, data_size); cond_resched(); /* @@ -1116,12 +1116,12 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, if (is_error_sane(err)) err = MOVE_TARGET_RD_ERR; } else - err = MOVE_CANCEL_BITFLIPS; + err = MOVE_TARGET_BITFLIPS; goto out_unlock_buf; } if (data_size > 0) { - err = ubi_io_write_data(ubi, ubi->peb_buf1, to, 0, aldata_size); + err = ubi_io_write_data(ubi, ubi->peb_buf, to, 0, aldata_size); if (err) { if (err == -EIO) err = MOVE_TARGET_WR_ERR; @@ -1134,8 +1134,8 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, * We've written the data and are going to read it back to make * sure it was written correctly. */ - - err = ubi_io_read_data(ubi, ubi->peb_buf2, to, 0, aldata_size); + memset(ubi->peb_buf, 0xFF, aldata_size); + err = ubi_io_read_data(ubi, ubi->peb_buf, to, 0, aldata_size); if (err) { if (err != UBI_IO_BITFLIPS) { ubi_warn("error %d while reading data back " @@ -1143,13 +1143,13 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to, if (is_error_sane(err)) err = MOVE_TARGET_RD_ERR; } else - err = MOVE_CANCEL_BITFLIPS; + err = MOVE_TARGET_BITFLIPS; goto out_unlock_buf; } cond_resched(); - if (memcmp(ubi->peb_buf1, ubi->peb_buf2, aldata_size)) { + if (crc != crc32(UBI_CRC32_INIT, ubi->peb_buf, data_size)) { ubi_warn("read data back from PEB %d and it is " "different", to); err = -EINVAL; diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 5cde4e5ca3e5..43f1a0011a55 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -431,11 +431,11 @@ static int torture_peb(struct ubi_device *ubi, int pnum) goto out; /* Make sure the PEB contains only 0xFF bytes */ - err = ubi_io_read(ubi, ubi->peb_buf1, pnum, 0, ubi->peb_size); + err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); if (err) goto out; - err = ubi_check_pattern(ubi->peb_buf1, 0xFF, ubi->peb_size); + err = ubi_check_pattern(ubi->peb_buf, 0xFF, ubi->peb_size); if (err == 0) { ubi_err("erased PEB %d, but a non-0xFF byte found", pnum); @@ -444,17 +444,17 @@ static int torture_peb(struct ubi_device *ubi, int pnum) } /* Write a pattern and check it */ - memset(ubi->peb_buf1, patterns[i], ubi->peb_size); - err = ubi_io_write(ubi, ubi->peb_buf1, pnum, 0, ubi->peb_size); + memset(ubi->peb_buf, patterns[i], ubi->peb_size); + err = ubi_io_write(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); if (err) goto out; - memset(ubi->peb_buf1, ~patterns[i], ubi->peb_size); - err = ubi_io_read(ubi, ubi->peb_buf1, pnum, 0, ubi->peb_size); + memset(ubi->peb_buf, ~patterns[i], ubi->peb_size); + err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); if (err) goto out; - err = ubi_check_pattern(ubi->peb_buf1, patterns[i], + err = ubi_check_pattern(ubi->peb_buf, patterns[i], ubi->peb_size); if (err == 0) { ubi_err("pattern %x checking failed for PEB %d", diff --git a/drivers/mtd/ubi/scan.c b/drivers/mtd/ubi/scan.c index 0cb17d936b5a..12c43b44f815 100644 --- a/drivers/mtd/ubi/scan.c +++ b/drivers/mtd/ubi/scan.c @@ -789,9 +789,9 @@ static int check_corruption(struct ubi_device *ubi, struct ubi_vid_hdr *vid_hdr, int err; mutex_lock(&ubi->buf_mutex); - memset(ubi->peb_buf1, 0x00, ubi->leb_size); + memset(ubi->peb_buf, 0x00, ubi->leb_size); - err = ubi_io_read(ubi, ubi->peb_buf1, pnum, ubi->leb_start, + err = ubi_io_read(ubi, ubi->peb_buf, pnum, ubi->leb_start, ubi->leb_size); if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err)) { /* @@ -808,7 +808,7 @@ static int check_corruption(struct ubi_device *ubi, struct ubi_vid_hdr *vid_hdr, if (err) goto out_unlock; - if (ubi_check_pattern(ubi->peb_buf1, 0xFF, ubi->leb_size)) + if (ubi_check_pattern(ubi->peb_buf, 0xFF, ubi->leb_size)) goto out_unlock; ubi_err("PEB %d contains corrupted VID header, and the data does not " @@ -818,7 +818,7 @@ static int check_corruption(struct ubi_device *ubi, struct ubi_vid_hdr *vid_hdr, dbg_msg("hexdump of PEB %d offset %d, length %d", pnum, ubi->leb_start, ubi->leb_size); ubi_dbg_print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, - ubi->peb_buf1, ubi->leb_size, 1); + ubi->peb_buf, ubi->leb_size, 1); err = 1; out_unlock: @@ -1174,7 +1174,7 @@ struct ubi_scan_info *ubi_scan(struct ubi_device *ubi) ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); if (!ech) - goto out_slab; + goto out_si; vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); if (!vidh) @@ -1235,8 +1235,6 @@ out_vidh: ubi_free_vid_hdr(ubi, vidh); out_ech: kfree(ech); -out_slab: - kmem_cache_destroy(si->scan_leb_slab); out_si: ubi_scan_destroy_si(si); return ERR_PTR(err); @@ -1325,7 +1323,9 @@ void ubi_scan_destroy_si(struct ubi_scan_info *si) } } - kmem_cache_destroy(si->scan_leb_slab); + if (si->scan_leb_slab) + kmem_cache_destroy(si->scan_leb_slab); + kfree(si); } diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index d51d75d34446..b162790790a9 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -118,7 +118,7 @@ enum { * PEB * MOVE_TARGET_WR_ERR: canceled because there was a write error to the target * PEB - * MOVE_CANCEL_BITFLIPS: canceled because a bit-flip was detected in the + * MOVE_TARGET_BITFLIPS: canceled because a bit-flip was detected in the * target PEB * MOVE_RETRY: retry scrubbing the PEB */ @@ -127,7 +127,7 @@ enum { MOVE_SOURCE_RD_ERR, MOVE_TARGET_RD_ERR, MOVE_TARGET_WR_ERR, - MOVE_CANCEL_BITFLIPS, + MOVE_TARGET_BITFLIPS, MOVE_RETRY, }; @@ -387,9 +387,8 @@ struct ubi_wl_entry; * time (MTD write buffer size) * @mtd: MTD device descriptor * - * @peb_buf1: a buffer of PEB size used for different purposes - * @peb_buf2: another buffer of PEB size used for different purposes - * @buf_mutex: protects @peb_buf1 and @peb_buf2 + * @peb_buf: a buffer of PEB size used for different purposes + * @buf_mutex: protects @peb_buf * @ckvol_mutex: serializes static volume checking when opening * * @dbg: debugging information for this UBI device @@ -471,8 +470,7 @@ struct ubi_device { int max_write_size; struct mtd_info *mtd; - void *peb_buf1; - void *peb_buf2; + void *peb_buf; struct mutex buf_mutex; struct mutex ckvol_mutex; diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index 0696e36b0539..7c1a9bf8ac86 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -350,18 +350,19 @@ static void prot_queue_add(struct ubi_device *ubi, struct ubi_wl_entry *e) /** * find_wl_entry - find wear-leveling entry closest to certain erase counter. * @root: the RB-tree where to look for - * @max: highest possible erase counter + * @diff: maximum possible difference from the smallest erase counter * * This function looks for a wear leveling entry with erase counter closest to - * @max and less than @max. + * min + @diff, where min is the smallest erase counter. */ -static struct ubi_wl_entry *find_wl_entry(struct rb_root *root, int max) +static struct ubi_wl_entry *find_wl_entry(struct rb_root *root, int diff) { struct rb_node *p; struct ubi_wl_entry *e; + int max; e = rb_entry(rb_first(root), struct ubi_wl_entry, u.rb); - max += e->ec; + max = e->ec + diff; p = root->rb_node; while (p) { @@ -389,7 +390,7 @@ static struct ubi_wl_entry *find_wl_entry(struct rb_root *root, int max) */ int ubi_wl_get_peb(struct ubi_device *ubi, int dtype) { - int err, medium_ec; + int err; struct ubi_wl_entry *e, *first, *last; ubi_assert(dtype == UBI_LONGTERM || dtype == UBI_SHORTTERM || @@ -427,7 +428,7 @@ retry: * For unknown data we pick a physical eraseblock with medium * erase counter. But we by no means can pick a physical * eraseblock with erase counter greater or equivalent than the - * lowest erase counter plus %WL_FREE_MAX_DIFF. + * lowest erase counter plus %WL_FREE_MAX_DIFF/2. */ first = rb_entry(rb_first(&ubi->free), struct ubi_wl_entry, u.rb); @@ -436,10 +437,8 @@ retry: if (last->ec - first->ec < WL_FREE_MAX_DIFF) e = rb_entry(ubi->free.rb_node, struct ubi_wl_entry, u.rb); - else { - medium_ec = (first->ec + WL_FREE_MAX_DIFF)/2; - e = find_wl_entry(&ubi->free, medium_ec); - } + else + e = find_wl_entry(&ubi->free, WL_FREE_MAX_DIFF/2); break; case UBI_SHORTTERM: /* @@ -799,7 +798,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, scrubbing = 1; goto out_not_moved; } - if (err == MOVE_CANCEL_BITFLIPS || err == MOVE_TARGET_WR_ERR || + if (err == MOVE_TARGET_BITFLIPS || err == MOVE_TARGET_WR_ERR || err == MOVE_TARGET_RD_ERR) { /* * Target PEB had bit-flips or write error - torture it. diff --git a/drivers/net/Space.c b/drivers/net/Space.c index 068c3563e00f..88bbd8ffa7fe 100644 --- a/drivers/net/Space.c +++ b/drivers/net/Space.c @@ -190,8 +190,10 @@ static struct devprobe2 isa_probes[] __initdata = { {seeq8005_probe, 0}, #endif #ifdef CONFIG_CS89x0 +#ifndef CONFIG_CS89x0_PLATFORM {cs89x0_probe, 0}, #endif +#endif #ifdef CONFIG_AT1700 {at1700_probe, 0}, #endif diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 0730203a19f2..b920d829692a 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -2573,12 +2573,16 @@ re_arm: static int bond_has_this_ip(struct bonding *bond, __be32 ip) { struct vlan_entry *vlan; + struct net_device *vlan_dev; - if (ip == bond->master_ip) + if (ip == bond_confirm_addr(bond->dev, 0, ip)) return 1; list_for_each_entry(vlan, &bond->vlan_list, vlan_list) { - if (ip == vlan->vlan_ip) + rcu_read_lock(); + vlan_dev = __vlan_find_dev_deep(bond->dev, vlan->vlan_id); + rcu_read_unlock(); + if (vlan_dev && ip == bond_confirm_addr(vlan_dev, 0, ip)) return 1; } @@ -2620,17 +2624,19 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) int i, vlan_id; __be32 *targets = bond->params.arp_targets; struct vlan_entry *vlan; - struct net_device *vlan_dev; + struct net_device *vlan_dev = NULL; struct rtable *rt; for (i = 0; (i < BOND_MAX_ARP_TARGETS); i++) { + __be32 addr; if (!targets[i]) break; pr_debug("basa: target %x\n", targets[i]); if (!bond_vlan_used(bond)) { pr_debug("basa: empty vlan: arp_send\n"); + addr = bond_confirm_addr(bond->dev, targets[i], 0); bond_arp_send(slave->dev, ARPOP_REQUEST, targets[i], - bond->master_ip, 0); + addr, 0); continue; } @@ -2655,8 +2661,9 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) if (rt->dst.dev == bond->dev) { ip_rt_put(rt); pr_debug("basa: rtdev == bond->dev: arp_send\n"); + addr = bond_confirm_addr(bond->dev, targets[i], 0); bond_arp_send(slave->dev, ARPOP_REQUEST, targets[i], - bond->master_ip, 0); + addr, 0); continue; } @@ -2674,10 +2681,11 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) } } - if (vlan_id) { + if (vlan_id && vlan_dev) { ip_rt_put(rt); + addr = bond_confirm_addr(vlan_dev, targets[i], 0); bond_arp_send(slave->dev, ARPOP_REQUEST, targets[i], - vlan->vlan_ip, vlan_id); + addr, vlan_id); continue; } @@ -3299,68 +3307,10 @@ static int bond_netdev_event(struct notifier_block *this, return NOTIFY_DONE; } -/* - * bond_inetaddr_event: handle inetaddr notifier chain events. - * - * We keep track of device IPs primarily to use as source addresses in - * ARP monitor probes (rather than spewing out broadcasts all the time). - * - * We track one IP for the main device (if it has one), plus one per VLAN. - */ -static int bond_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr) -{ - struct in_ifaddr *ifa = ptr; - struct net_device *vlan_dev, *event_dev = ifa->ifa_dev->dev; - struct bond_net *bn = net_generic(dev_net(event_dev), bond_net_id); - struct bonding *bond; - struct vlan_entry *vlan; - - /* we only care about primary address */ - if(ifa->ifa_flags & IFA_F_SECONDARY) - return NOTIFY_DONE; - - list_for_each_entry(bond, &bn->dev_list, bond_list) { - if (bond->dev == event_dev) { - switch (event) { - case NETDEV_UP: - bond->master_ip = ifa->ifa_local; - return NOTIFY_OK; - case NETDEV_DOWN: - bond->master_ip = 0; - return NOTIFY_OK; - default: - return NOTIFY_DONE; - } - } - - list_for_each_entry(vlan, &bond->vlan_list, vlan_list) { - vlan_dev = __vlan_find_dev_deep(bond->dev, - vlan->vlan_id); - if (vlan_dev == event_dev) { - switch (event) { - case NETDEV_UP: - vlan->vlan_ip = ifa->ifa_local; - return NOTIFY_OK; - case NETDEV_DOWN: - vlan->vlan_ip = 0; - return NOTIFY_OK; - default: - return NOTIFY_DONE; - } - } - } - } - return NOTIFY_DONE; -} - static struct notifier_block bond_netdev_notifier = { .notifier_call = bond_netdev_event, }; -static struct notifier_block bond_inetaddr_notifier = { - .notifier_call = bond_inetaddr_event, -}; - /*---------------------------- Hashing Policies -----------------------------*/ /* @@ -4929,7 +4879,6 @@ static int __init bonding_init(void) } register_netdevice_notifier(&bond_netdev_notifier); - register_inetaddr_notifier(&bond_inetaddr_notifier); out: return res; err: @@ -4943,7 +4892,6 @@ err_link: static void __exit bonding_exit(void) { unregister_netdevice_notifier(&bond_netdev_notifier); - unregister_inetaddr_notifier(&bond_inetaddr_notifier); bond_destroy_debugfs(); diff --git a/drivers/net/bonding/bonding.h b/drivers/net/bonding/bonding.h index 1aecc37e5b4d..9f2bae6616d3 100644 --- a/drivers/net/bonding/bonding.h +++ b/drivers/net/bonding/bonding.h @@ -21,6 +21,7 @@ #include <linux/cpumask.h> #include <linux/in6.h> #include <linux/netpoll.h> +#include <linux/inetdevice.h> #include "bond_3ad.h" #include "bond_alb.h" @@ -166,7 +167,6 @@ struct bond_parm_tbl { struct vlan_entry { struct list_head vlan_list; - __be32 vlan_ip; unsigned short vlan_id; }; @@ -232,7 +232,6 @@ struct bonding { struct list_head bond_list; struct netdev_hw_addr_list mc_list; int (*xmit_hash_policy)(struct sk_buff *, int); - __be32 master_ip; u16 rr_tx_counter; struct ad_bond_info ad_info; struct alb_bond_info alb_info; @@ -378,6 +377,21 @@ static inline bool bond_is_slave_inactive(struct slave *slave) return slave->inactive; } +static inline __be32 bond_confirm_addr(struct net_device *dev, __be32 dst, __be32 local) +{ + struct in_device *in_dev; + __be32 addr = 0; + + rcu_read_lock(); + in_dev = __in_dev_get_rcu(dev); + + if (in_dev) + addr = inet_confirm_addr(in_dev, dst, local, RT_SCOPE_HOST); + + rcu_read_unlock(); + return addr; +} + struct bond_net; struct vlan_entry *bond_next_vlan(struct bonding *bond, struct vlan_entry *curr); diff --git a/drivers/net/ethernet/broadcom/cnic.c b/drivers/net/ethernet/broadcom/cnic.c index 7b65716b8734..c95e7b5e2b85 100644 --- a/drivers/net/ethernet/broadcom/cnic.c +++ b/drivers/net/ethernet/broadcom/cnic.c @@ -47,6 +47,7 @@ #include "bnx2x/bnx2x_hsi.h" #include "../../../scsi/bnx2i/57xx_iscsi_constants.h" #include "../../../scsi/bnx2i/57xx_iscsi_hsi.h" +#include "../../../scsi/bnx2fc/bnx2fc_constants.h" #include "cnic.h" #include "cnic_defs.h" @@ -2547,7 +2548,7 @@ static void cnic_bnx2x_kwqe_err(struct cnic_dev *dev, struct kwqe *kwqe) } kcqe.kcqe_op_flag = kcqe_op << KCQE_FLAGS_OPCODE_SHIFT; kcqe.kcqe_op_flag |= KCQE_FLAGS_LAYER_MASK_L5_FCOE; - kcqe.kcqe_info1 = FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR; + kcqe.kcqe_info1 = FCOE_KCQE_COMPLETION_STATUS_PARITY_ERROR; kcqe.kcqe_info2 = cid; kcqe.kcqe_info0 = l5_cid; @@ -2558,7 +2559,7 @@ static void cnic_bnx2x_kwqe_err(struct cnic_dev *dev, struct kwqe *kwqe) kcqe.kcqe_op_flag = (opcode + 0x10) << KCQE_FLAGS_OPCODE_SHIFT; kcqe.kcqe_op_flag |= KCQE_FLAGS_LAYER_MASK_L5_ISCSI; - kcqe.kcqe_info1 = ISCSI_KCQE_COMPLETION_STATUS_NIC_ERROR; + kcqe.kcqe_info1 = ISCSI_KCQE_COMPLETION_STATUS_PARITY_ERR; kcqe.kcqe_info2 = cid; cnic_get_l5_cid(cp, BNX2X_SW_CID(cid), &kcqe.kcqe_info0); @@ -2577,7 +2578,7 @@ static void cnic_bnx2x_kwqe_err(struct cnic_dev *dev, struct kwqe *kwqe) kcqe.kcqe_op_flag = (kcqe_op << KCQE_FLAGS_OPCODE_SHIFT) | KCQE_FLAGS_LAYER_MASK_L4; - l4kcqe->status = L4_KCQE_COMPLETION_STATUS_NIC_ERROR; + l4kcqe->status = L4_KCQE_COMPLETION_STATUS_PARITY_ERROR; l4kcqe->cid = cid; cnic_get_l5_cid(cp, BNX2X_SW_CID(cid), &l4kcqe->conn_id); } else { @@ -3933,7 +3934,8 @@ static void cnic_cm_process_kcqe(struct cnic_dev *dev, struct kcqe *kcqe) case L4_KCQE_OPCODE_VALUE_CONNECT_COMPLETE: if (l4kcqe->status == 0) set_bit(SK_F_OFFLD_COMPLETE, &csk->flags); - else if (l4kcqe->status == L4_KCQE_COMPLETION_STATUS_NIC_ERROR) + else if (l4kcqe->status == + L4_KCQE_COMPLETION_STATUS_PARITY_ERROR) set_bit(SK_F_HW_ERR, &csk->flags); smp_mb__before_clear_bit(); @@ -3946,7 +3948,7 @@ static void cnic_cm_process_kcqe(struct cnic_dev *dev, struct kcqe *kcqe) case L4_KCQE_OPCODE_VALUE_RESET_COMP: case L5CM_RAMROD_CMD_ID_SEARCHER_DELETE: case L5CM_RAMROD_CMD_ID_TERMINATE_OFFLOAD: - if (l4kcqe->status == L4_KCQE_COMPLETION_STATUS_NIC_ERROR) + if (l4kcqe->status == L4_KCQE_COMPLETION_STATUS_PARITY_ERROR) set_bit(SK_F_HW_ERR, &csk->flags); cp->close_conn(csk, opcode); diff --git a/drivers/net/ethernet/broadcom/cnic_defs.h b/drivers/net/ethernet/broadcom/cnic_defs.h index 06ca00266d70..382c98b0cc0c 100644 --- a/drivers/net/ethernet/broadcom/cnic_defs.h +++ b/drivers/net/ethernet/broadcom/cnic_defs.h @@ -35,16 +35,6 @@ #define L5CM_RAMROD_CMD_ID_SEARCHER_DELETE (L5CM_RAMROD_CMD_ID_BASE + 14) #define L5CM_RAMROD_CMD_ID_TERMINATE_OFFLOAD (L5CM_RAMROD_CMD_ID_BASE + 15) -#define FCOE_KCQE_OPCODE_INIT_FUNC (0x10) -#define FCOE_KCQE_OPCODE_DESTROY_FUNC (0x11) -#define FCOE_KCQE_OPCODE_STAT_FUNC (0x12) -#define FCOE_KCQE_OPCODE_OFFLOAD_CONN (0x15) -#define FCOE_KCQE_OPCODE_ENABLE_CONN (0x16) -#define FCOE_KCQE_OPCODE_DISABLE_CONN (0x17) -#define FCOE_KCQE_OPCODE_DESTROY_CONN (0x18) -#define FCOE_KCQE_OPCODE_CQ_EVENT_NOTIFICATION (0x20) -#define FCOE_KCQE_OPCODE_FCOE_ERROR (0x21) - #define FCOE_RAMROD_CMD_ID_INIT_FUNC (FCOE_KCQE_OPCODE_INIT_FUNC) #define FCOE_RAMROD_CMD_ID_DESTROY_FUNC (FCOE_KCQE_OPCODE_DESTROY_FUNC) #define FCOE_RAMROD_CMD_ID_STAT_FUNC (FCOE_KCQE_OPCODE_STAT_FUNC) @@ -54,23 +44,6 @@ #define FCOE_RAMROD_CMD_ID_DESTROY_CONN (FCOE_KCQE_OPCODE_DESTROY_CONN) #define FCOE_RAMROD_CMD_ID_TERMINATE_CONN (0x81) -#define FCOE_KWQE_OPCODE_INIT1 (0) -#define FCOE_KWQE_OPCODE_INIT2 (1) -#define FCOE_KWQE_OPCODE_INIT3 (2) -#define FCOE_KWQE_OPCODE_OFFLOAD_CONN1 (3) -#define FCOE_KWQE_OPCODE_OFFLOAD_CONN2 (4) -#define FCOE_KWQE_OPCODE_OFFLOAD_CONN3 (5) -#define FCOE_KWQE_OPCODE_OFFLOAD_CONN4 (6) -#define FCOE_KWQE_OPCODE_ENABLE_CONN (7) -#define FCOE_KWQE_OPCODE_DISABLE_CONN (8) -#define FCOE_KWQE_OPCODE_DESTROY_CONN (9) -#define FCOE_KWQE_OPCODE_DESTROY (10) -#define FCOE_KWQE_OPCODE_STAT (11) - -#define FCOE_KCQE_COMPLETION_STATUS_ERROR (0x1) -#define FCOE_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAILURE (0x3) -#define FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR (0x5) - /* KCQ (kernel completion queue) response op codes */ #define L4_KCQE_OPCODE_VALUE_CLOSE_COMP (53) #define L4_KCQE_OPCODE_VALUE_RESET_COMP (54) @@ -87,6 +60,7 @@ /* KCQ (kernel completion queue) completion status */ #define L4_KCQE_COMPLETION_STATUS_SUCCESS (0) #define L4_KCQE_COMPLETION_STATUS_NIC_ERROR (4) +#define L4_KCQE_COMPLETION_STATUS_PARITY_ERROR (0x81) #define L4_KCQE_COMPLETION_STATUS_TIMEOUT (0x93) #define L4_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAIL (0x83) diff --git a/drivers/net/ethernet/broadcom/cnic_if.h b/drivers/net/ethernet/broadcom/cnic_if.h index 60deb84d36bd..289274e546be 100644 --- a/drivers/net/ethernet/broadcom/cnic_if.h +++ b/drivers/net/ethernet/broadcom/cnic_if.h @@ -12,8 +12,8 @@ #ifndef CNIC_IF_H #define CNIC_IF_H -#define CNIC_MODULE_VERSION "2.5.9" -#define CNIC_MODULE_RELDATE "Feb 8, 2012" +#define CNIC_MODULE_VERSION "2.5.10" +#define CNIC_MODULE_RELDATE "March 21, 2012" #define CNIC_ULP_RDMA 0 #define CNIC_ULP_ISCSI 1 diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index b0657466041d..7b71387cf93c 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -89,10 +89,10 @@ static inline void _tg3_flag_clear(enum TG3_FLAGS flag, unsigned long *bits) #define DRV_MODULE_NAME "tg3" #define TG3_MAJ_NUM 3 -#define TG3_MIN_NUM 122 +#define TG3_MIN_NUM 123 #define DRV_MODULE_VERSION \ __stringify(TG3_MAJ_NUM) "." __stringify(TG3_MIN_NUM) -#define DRV_MODULE_RELDATE "December 7, 2011" +#define DRV_MODULE_RELDATE "March 21, 2012" #define RESET_KIND_SHUTDOWN 0 #define RESET_KIND_INIT 1 @@ -5953,8 +5953,10 @@ next_pkt_nopost: tpr->rx_std_prod_idx = std_prod_idx & tp->rx_std_ring_mask; tpr->rx_jmb_prod_idx = jmb_prod_idx & tp->rx_jmb_ring_mask; - if (tnapi != &tp->napi[1]) + if (tnapi != &tp->napi[1]) { + tp->rx_refill = true; napi_schedule(&tp->napi[1].napi); + } } return received; @@ -6134,6 +6136,7 @@ static int tg3_poll_work(struct tg3_napi *tnapi, int work_done, int budget) u32 std_prod_idx = dpr->rx_std_prod_idx; u32 jmb_prod_idx = dpr->rx_jmb_prod_idx; + tp->rx_refill = false; for (i = 1; i < tp->irq_cnt; i++) err |= tg3_rx_prodring_xfer(tp, dpr, &tp->napi[i].prodring); @@ -6197,9 +6200,25 @@ static int tg3_poll_msix(struct napi_struct *napi, int budget) /* check for RX/TX work to do */ if (likely(sblk->idx[0].tx_consumer == tnapi->tx_cons && *(tnapi->rx_rcb_prod_idx) == tnapi->rx_rcb_ptr)) { + + /* This test here is not race free, but will reduce + * the number of interrupts by looping again. + */ + if (tnapi == &tp->napi[1] && tp->rx_refill) + continue; + napi_complete(napi); /* Reenable interrupts. */ tw32_mailbox(tnapi->int_mbox, tnapi->last_tag << 24); + + /* This test here is synchronized by napi_schedule() + * and napi_complete() to close the race condition. + */ + if (unlikely(tnapi == &tp->napi[1] && tp->rx_refill)) { + tw32(HOSTCC_MODE, tp->coalesce_mode | + HOSTCC_MODE_ENABLE | + tnapi->coal_now); + } mmiowb(); break; } diff --git a/drivers/net/ethernet/broadcom/tg3.h b/drivers/net/ethernet/broadcom/tg3.h index 66bcfca55261..93865f899a4f 100644 --- a/drivers/net/ethernet/broadcom/tg3.h +++ b/drivers/net/ethernet/broadcom/tg3.h @@ -3007,6 +3007,7 @@ struct tg3 { u32 rx_std_max_post; u32 rx_offset; u32 rx_pkt_map_sz; + bool rx_refill; /* begin "everything else" cacheline(s) section */ diff --git a/drivers/net/ethernet/cirrus/Kconfig b/drivers/net/ethernet/cirrus/Kconfig index 1f8648f099c7..8388e36cf08f 100644 --- a/drivers/net/ethernet/cirrus/Kconfig +++ b/drivers/net/ethernet/cirrus/Kconfig @@ -5,8 +5,7 @@ config NET_VENDOR_CIRRUS bool "Cirrus devices" default y - depends on ISA || EISA || MACH_IXDP2351 || ARCH_IXDP2X01 \ - || MACH_MX31ADS || MACH_QQ2440 || (ARM && ARCH_EP93XX) || MAC + depends on ISA || EISA || ARM || MAC ---help--- If you have a network (Ethernet) card belonging to this class, say Y and read the Ethernet-HOWTO, available from @@ -21,8 +20,7 @@ if NET_VENDOR_CIRRUS config CS89x0 tristate "CS89x0 support" - depends on (ISA || EISA || MACH_IXDP2351 \ - || ARCH_IXDP2X01 || MACH_MX31ADS || MACH_QQ2440) + depends on ISA || EISA || ARM ---help--- Support for CS89x0 chipset based Ethernet cards. If you have a network (Ethernet) card of this type, say Y and read the @@ -33,10 +31,15 @@ config CS89x0 To compile this driver as a module, choose M here. The module will be called cs89x0. -config CS89x0_NONISA_IRQ - def_bool y - depends on CS89x0 != n - depends on MACH_IXDP2351 || ARCH_IXDP2X01 || MACH_MX31ADS || MACH_QQ2440 +config CS89x0_PLATFORM + bool "CS89x0 platform driver support" + depends on CS89x0 + help + Say Y to compile the cs89x0 driver as a platform driver. This + makes this driver suitable for use on certain evaluation boards + such as the iMX21ADS. + + If you are unsure, say N. config EP93XX_ETH tristate "EP93xx Ethernet support" diff --git a/drivers/net/ethernet/cirrus/cs89x0.c b/drivers/net/ethernet/cirrus/cs89x0.c index d5ff93653e4c..30fee428c489 100644 --- a/drivers/net/ethernet/cirrus/cs89x0.c +++ b/drivers/net/ethernet/cirrus/cs89x0.c @@ -100,9 +100,6 @@ */ -/* Always include 'config.h' first in case the user wants to turn on - or override something. */ -#include <linux/module.h> /* * Set this to zero to disable DMA code @@ -131,9 +128,12 @@ */ +#include <linux/module.h> +#include <linux/printk.h> #include <linux/errno.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> +#include <linux/platform_device.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/fcntl.h> @@ -151,6 +151,7 @@ #include <asm/system.h> #include <asm/io.h> #include <asm/irq.h> +#include <linux/atomic.h> #if ALLOW_DMA #include <asm/dma.h> #endif @@ -174,26 +175,20 @@ static char version[] __initdata = them to system IRQ numbers. This mapping is card specific and is set to the configuration of the Cirrus Eval board for this chip. */ #if defined(CONFIG_MACH_IXDP2351) +#define CS89x0_NONISA_IRQ static unsigned int netcard_portlist[] __used __initdata = {IXDP2351_VIRT_CS8900_BASE, 0}; static unsigned int cs8900_irq_map[] = {IRQ_IXDP2351_CS8900, 0, 0, 0}; #elif defined(CONFIG_ARCH_IXDP2X01) +#define CS89x0_NONISA_IRQ static unsigned int netcard_portlist[] __used __initdata = {IXDP2X01_CS8900_VIRT_BASE, 0}; static unsigned int cs8900_irq_map[] = {IRQ_IXDP2X01_CS8900, 0, 0, 0}; -#elif defined(CONFIG_MACH_QQ2440) -#include <mach/qq2440.h> -static unsigned int netcard_portlist[] __used __initdata = { QQ2440_CS8900_VIRT_BASE + 0x300, 0 }; -static unsigned int cs8900_irq_map[] = { QQ2440_CS8900_IRQ, 0, 0, 0 }; -#elif defined(CONFIG_MACH_MX31ADS) -#include <mach/board-mx31ads.h> -static unsigned int netcard_portlist[] __used __initdata = { - PBC_BASE_ADDRESS + PBC_CS8900A_IOBASE + 0x300, 0 -}; -static unsigned cs8900_irq_map[] = {EXPIO_INT_ENET_INT, 0, 0, 0}; #else +#ifndef CONFIG_CS89x0_PLATFORM static unsigned int netcard_portlist[] __used __initdata = { 0x300, 0x320, 0x340, 0x360, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0}; static unsigned int cs8900_irq_map[] = {10,11,12,5}; #endif +#endif #if DEBUGGING static unsigned int net_debug = DEBUGGING; @@ -236,11 +231,16 @@ struct net_local { unsigned char *end_dma_buff; /* points to the end of the buffer */ unsigned char *rx_dma_ptr; /* points to the next packet */ #endif +#ifdef CONFIG_CS89x0_PLATFORM + void __iomem *virt_addr;/* Virtual address for accessing the CS89x0. */ + unsigned long phys_addr;/* Physical address for accessing the CS89x0. */ + unsigned long size; /* Length of CS89x0 memory region. */ +#endif }; /* Index to functions, as function prototypes. */ -static int cs89x0_probe1(struct net_device *dev, int ioaddr, int modular); +static int cs89x0_probe1(struct net_device *dev, unsigned long ioaddr, int modular); static int net_open(struct net_device *dev); static netdev_tx_t net_send_packet(struct sk_buff *skb, struct net_device *dev); static irqreturn_t net_interrupt(int irq, void *dev_id); @@ -294,6 +294,7 @@ static int __init media_fn(char *str) __setup("cs89x0_media=", media_fn); +#ifndef CONFIG_CS89x0_PLATFORM /* Check for a network adaptor of this type, and return '0' iff one exists. If dev->base_addr == 0, probe all likely locations. If dev->base_addr == 1, always return failure. @@ -343,6 +344,7 @@ out: return ERR_PTR(err); } #endif +#endif #if defined(CONFIG_MACH_IXDP2351) static u16 @@ -504,7 +506,7 @@ static const struct net_device_ops net_ops = { */ static int __init -cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) +cs89x0_probe1(struct net_device *dev, unsigned long ioaddr, int modular) { struct net_local *lp = netdev_priv(dev); static unsigned version_printed; @@ -529,15 +531,12 @@ cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) lp->force = g_cs89x0_media__force; #endif -#if defined(CONFIG_MACH_QQ2440) - lp->force |= FORCE_RJ45 | FORCE_FULL; -#endif } /* Grab the region so we can find another board if autoIRQ fails. */ /* WTF is going on here? */ if (!request_region(ioaddr & ~3, NETCARD_IO_EXTENT, DRV_NAME)) { - printk(KERN_ERR "%s: request_region(0x%x, 0x%x) failed\n", + printk(KERN_ERR "%s: request_region(0x%lx, 0x%x) failed\n", DRV_NAME, ioaddr, NETCARD_IO_EXTENT); retval = -EBUSY; goto out1; @@ -549,7 +548,7 @@ cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) will skip the test for the ADD_PORT. */ if (ioaddr & 1) { if (net_debug > 1) - printk(KERN_INFO "%s: odd ioaddr 0x%x\n", dev->name, ioaddr); + printk(KERN_INFO "%s: odd ioaddr 0x%lx\n", dev->name, ioaddr); if ((ioaddr & 2) != 2) if ((readword(ioaddr & ~3, ADD_PORT) & ADD_MASK) != ADD_SIG) { printk(KERN_ERR "%s: bad signature 0x%x\n", @@ -560,13 +559,13 @@ cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) } ioaddr &= ~3; - printk(KERN_DEBUG "PP_addr at %x[%x]: 0x%x\n", + printk(KERN_DEBUG "PP_addr at %lx[%x]: 0x%x\n", ioaddr, ADD_PORT, readword(ioaddr, ADD_PORT)); writeword(ioaddr, ADD_PORT, PP_ChipID); tmp = readword(ioaddr, DATA_PORT); if (tmp != CHIP_EISA_ID_SIG) { - printk(KERN_DEBUG "%s: incorrect signature at %x[%x]: 0x%x!=" + printk(KERN_DEBUG "%s: incorrect signature at %lx[%x]: 0x%x!=" CHIP_EISA_ID_SIG_STR "\n", dev->name, ioaddr, DATA_PORT, tmp); retval = -ENODEV; @@ -736,8 +735,9 @@ cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) dev->irq = i; } else { i = lp->isa_config & INT_NO_MASK; +#ifndef CONFIG_CS89x0_PLATFORM if (lp->chip_type == CS8900) { -#ifdef CONFIG_CS89x0_NONISA_IRQ +#ifdef CS89x0_NONISA_IRQ i = cs8900_irq_map[0]; #else /* Translate the IRQ using the IRQ mapping table. */ @@ -758,6 +758,7 @@ cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) } #endif } +#endif if (!dev->irq) dev->irq = i; } @@ -1168,6 +1169,7 @@ write_irq(struct net_device *dev, int chip_type, int irq) int i; if (chip_type == CS8900) { +#ifndef CONFIG_CS89x0_PLATFORM /* Search the mapping table for the corresponding IRQ pin. */ for (i = 0; i != ARRAY_SIZE(cs8900_irq_map); i++) if (cs8900_irq_map[i] == irq) @@ -1175,6 +1177,10 @@ write_irq(struct net_device *dev, int chip_type, int irq) /* Not found */ if (i == ARRAY_SIZE(cs8900_irq_map)) i = 3; +#else + /* INTRQ0 pin is used for interrupt generation. */ + i = 0; +#endif writereg(dev, PP_CS8900_ISAINT, i); } else { writereg(dev, PP_CS8920_ISAINT, irq); @@ -1228,7 +1234,7 @@ net_open(struct net_device *dev) } else { -#ifndef CONFIG_CS89x0_NONISA_IRQ +#if !defined(CS89x0_NONISA_IRQ) && !defined(CONFIG_CS89x0_PLATFORM) if (((1 << dev->irq) & lp->irq_map) == 0) { printk(KERN_ERR "%s: IRQ %d is not in our map of allowable IRQs, which is %x\n", dev->name, dev->irq, lp->irq_map); @@ -1746,7 +1752,7 @@ static int set_mac_address(struct net_device *dev, void *p) return 0; } -#ifdef MODULE +#if defined(MODULE) && !defined(CONFIG_CS89x0_PLATFORM) static struct net_device *dev_cs89x0; @@ -1900,7 +1906,97 @@ cleanup_module(void) release_region(dev_cs89x0->base_addr, NETCARD_IO_EXTENT); free_netdev(dev_cs89x0); } -#endif /* MODULE */ +#endif /* MODULE && !CONFIG_CS89x0_PLATFORM */ + +#ifdef CONFIG_CS89x0_PLATFORM +static int __init cs89x0_platform_probe(struct platform_device *pdev) +{ + struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); + struct net_local *lp; + struct resource *mem_res; + int err; + + if (!dev) + return -ENOMEM; + + lp = netdev_priv(dev); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->irq = platform_get_irq(pdev, 0); + if (mem_res == NULL || dev->irq <= 0) { + dev_warn(&dev->dev, "memory/interrupt resource missing.\n"); + err = -ENXIO; + goto free; + } + + lp->phys_addr = mem_res->start; + lp->size = resource_size(mem_res); + if (!request_mem_region(lp->phys_addr, lp->size, DRV_NAME)) { + dev_warn(&dev->dev, "request_mem_region() failed.\n"); + err = -EBUSY; + goto free; + } + + lp->virt_addr = ioremap(lp->phys_addr, lp->size); + if (!lp->virt_addr) { + dev_warn(&dev->dev, "ioremap() failed.\n"); + err = -ENOMEM; + goto release; + } + + err = cs89x0_probe1(dev, (unsigned long)lp->virt_addr, 0); + if (err) { + dev_warn(&dev->dev, "no cs8900 or cs8920 detected.\n"); + goto unmap; + } + + platform_set_drvdata(pdev, dev); + return 0; + +unmap: + iounmap(lp->virt_addr); +release: + release_mem_region(lp->phys_addr, lp->size); +free: + free_netdev(dev); + return err; +} + +static int cs89x0_platform_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct net_local *lp = netdev_priv(dev); + + unregister_netdev(dev); + iounmap(lp->virt_addr); + release_mem_region(lp->phys_addr, lp->size); + free_netdev(dev); + return 0; +} + +static struct platform_driver cs89x0_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .remove = cs89x0_platform_remove, +}; + +static int __init cs89x0_init(void) +{ + return platform_driver_probe(&cs89x0_driver, cs89x0_platform_probe); +} + +module_init(cs89x0_init); + +static void __exit cs89x0_cleanup(void) +{ + platform_driver_unregister(&cs89x0_driver); +} + +module_exit(cs89x0_cleanup); + +#endif /* CONFIG_CS89x0_PLATFORM */ /* * Local variables: diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c index d9428f0e738a..e7bed5303997 100644 --- a/drivers/net/ethernet/freescale/gianfar.c +++ b/drivers/net/ethernet/freescale/gianfar.c @@ -968,7 +968,6 @@ static int gfar_probe(struct platform_device *ofdev) struct gfar_private *priv = NULL; struct gfar __iomem *regs = NULL; int err = 0, i, grp_idx = 0; - int len_devname; u32 rstat = 0, tstat = 0, rqueue = 0, tqueue = 0; u32 isrg = 0; u32 __iomem *baddr; @@ -1169,40 +1168,16 @@ static int gfar_probe(struct platform_device *ofdev) priv->device_flags & FSL_GIANFAR_DEV_HAS_MAGIC_PACKET); /* fill out IRQ number and name fields */ - len_devname = strlen(dev->name); for (i = 0; i < priv->num_grps; i++) { - strncpy(&priv->gfargrp[i].int_name_tx[0], dev->name, - len_devname); if (priv->device_flags & FSL_GIANFAR_DEV_HAS_MULTI_INTR) { - strncpy(&priv->gfargrp[i].int_name_tx[len_devname], - "_g", sizeof("_g")); - priv->gfargrp[i].int_name_tx[ - strlen(priv->gfargrp[i].int_name_tx)] = i+48; - strncpy(&priv->gfargrp[i].int_name_tx[strlen( - priv->gfargrp[i].int_name_tx)], - "_tx", sizeof("_tx") + 1); - - strncpy(&priv->gfargrp[i].int_name_rx[0], dev->name, - len_devname); - strncpy(&priv->gfargrp[i].int_name_rx[len_devname], - "_g", sizeof("_g")); - priv->gfargrp[i].int_name_rx[ - strlen(priv->gfargrp[i].int_name_rx)] = i+48; - strncpy(&priv->gfargrp[i].int_name_rx[strlen( - priv->gfargrp[i].int_name_rx)], - "_rx", sizeof("_rx") + 1); - - strncpy(&priv->gfargrp[i].int_name_er[0], dev->name, - len_devname); - strncpy(&priv->gfargrp[i].int_name_er[len_devname], - "_g", sizeof("_g")); - priv->gfargrp[i].int_name_er[strlen( - priv->gfargrp[i].int_name_er)] = i+48; - strncpy(&priv->gfargrp[i].int_name_er[strlen(\ - priv->gfargrp[i].int_name_er)], - "_er", sizeof("_er") + 1); + sprintf(priv->gfargrp[i].int_name_tx, "%s%s%c%s", + dev->name, "_g", '0' + i, "_tx"); + sprintf(priv->gfargrp[i].int_name_rx, "%s%s%c%s", + dev->name, "_g", '0' + i, "_rx"); + sprintf(priv->gfargrp[i].int_name_er, "%s%s%c%s", + dev->name, "_g", '0' + i, "_er"); } else - priv->gfargrp[i].int_name_tx[len_devname] = '\0'; + strcpy(priv->gfargrp[i].int_name_tx, dev->name); } /* Initialize the filer table */ diff --git a/drivers/net/ethernet/freescale/gianfar.h b/drivers/net/ethernet/freescale/gianfar.h index fc2488adca36..4c9f8d487dbb 100644 --- a/drivers/net/ethernet/freescale/gianfar.h +++ b/drivers/net/ethernet/freescale/gianfar.h @@ -517,7 +517,7 @@ extern const char gfar_driver_version[]; #define RXFCB_PERR_MASK 0x000c #define RXFCB_PERR_BADL3 0x0008 -#define GFAR_INT_NAME_MAX IFNAMSIZ + 4 +#define GFAR_INT_NAME_MAX (IFNAMSIZ + 6) /* '_g#_xx' */ struct txbd8 { diff --git a/drivers/net/ethernet/marvell/sky2.c b/drivers/net/ethernet/marvell/sky2.c index 82c2c86a1951..423a1a2a702e 100644 --- a/drivers/net/ethernet/marvell/sky2.c +++ b/drivers/net/ethernet/marvell/sky2.c @@ -95,6 +95,10 @@ static int disable_msi = 0; module_param(disable_msi, int, 0); MODULE_PARM_DESC(disable_msi, "Disable Message Signaled Interrupt (MSI)"); +static int legacy_pme = 0; +module_param(legacy_pme, int, 0); +MODULE_PARM_DESC(legacy_pme, "Legacy power management"); + static DEFINE_PCI_DEVICE_TABLE(sky2_id_table) = { { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9000) }, /* SK-9Sxx */ { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9E00) }, /* SK-9Exx */ @@ -867,6 +871,13 @@ static void sky2_wol_init(struct sky2_port *sky2) /* Disable PiG firmware */ sky2_write16(hw, B0_CTST, Y2_HW_WOL_OFF); + /* Needed by some broken BIOSes, use PCI rather than PCI-e for WOL */ + if (legacy_pme) { + u32 reg1 = sky2_pci_read32(hw, PCI_DEV_REG1); + reg1 |= PCI_Y2_PME_LEGACY; + sky2_pci_write32(hw, PCI_DEV_REG1, reg1); + } + /* block receiver */ sky2_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_SET); sky2_read32(hw, B0_CTST); diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h b/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h index 2b5af22419a5..385a4d5c7c25 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic.h @@ -36,8 +36,8 @@ #define _QLCNIC_LINUX_MAJOR 5 #define _QLCNIC_LINUX_MINOR 0 -#define _QLCNIC_LINUX_SUBVERSION 25 -#define QLCNIC_LINUX_VERSIONID "5.0.26" +#define _QLCNIC_LINUX_SUBVERSION 27 +#define QLCNIC_LINUX_VERSIONID "5.0.27" #define QLCNIC_DRV_IDC_VER 0x01 #define QLCNIC_DRIVER_VERSION ((_QLCNIC_LINUX_MAJOR << 16) |\ (_QLCNIC_LINUX_MINOR << 8) | (_QLCNIC_LINUX_SUBVERSION)) diff --git a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c index 81bb1a69e69f..75c32e875fef 100644 --- a/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c +++ b/drivers/net/ethernet/qlogic/qlcnic/qlcnic_main.c @@ -1458,8 +1458,10 @@ qlcnic_reset_context(struct qlcnic_adapter *adapter) if (netif_running(netdev)) { err = qlcnic_attach(adapter); - if (!err) + if (!err) { __qlcnic_up(adapter, netdev); + qlcnic_restore_indev_addr(netdev, NETDEV_UP); + } } netif_device_attach(netdev); diff --git a/drivers/net/ethernet/smsc/smc91x.c b/drivers/net/ethernet/smsc/smc91x.c index 1dc4fad593e7..fee449355014 100644 --- a/drivers/net/ethernet/smsc/smc91x.c +++ b/drivers/net/ethernet/smsc/smc91x.c @@ -2280,7 +2280,7 @@ static int __devinit smc_drv_probe(struct platform_device *pdev) if (ret) goto out_release_io; #if defined(CONFIG_SA1100_ASSABET) - NCR_0 |= NCR_ENET_OSC_EN; + neponset_ncr_set(NCR_ENET_OSC_EN); #endif platform_set_drvdata(pdev, ndev); ret = smc_enable_device(pdev); diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig index e535137eb2d0..468047866c8c 100644 --- a/drivers/net/irda/Kconfig +++ b/drivers/net/irda/Kconfig @@ -356,7 +356,7 @@ config VLSI_FIR config SA1100_FIR tristate "SA1100 Internal IR" - depends on ARCH_SA1100 && IRDA + depends on ARCH_SA1100 && IRDA && DMA_SA11X0 config VIA_FIR tristate "VIA VT8231/VT1211 SIR/MIR/FIR" diff --git a/drivers/net/irda/sa1100_ir.c b/drivers/net/irda/sa1100_ir.c index da2705061a60..a0d1913a58d3 100644 --- a/drivers/net/irda/sa1100_ir.c +++ b/drivers/net/irda/sa1100_ir.c @@ -15,7 +15,7 @@ * This driver takes one kernel command line parameter, sa1100ir=, with * the following options: * max_rate:baudrate - set the maximum baud rate - * power_leve:level - set the transmitter power level + * power_level:level - set the transmitter power level * tx_lpm:0|1 - set transmit low power mode */ #include <linux/module.h> @@ -30,13 +30,13 @@ #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/sa11x0-dma.h> #include <net/irda/irda.h> #include <net/irda/wrapper.h> #include <net/irda/irda_device.h> -#include <asm/irq.h> -#include <mach/dma.h> #include <mach/hardware.h> #include <asm/mach/irda.h> @@ -44,8 +44,15 @@ static int power_level = 3; static int tx_lpm; static int max_rate = 4000000; +struct sa1100_buf { + struct device *dev; + struct sk_buff *skb; + struct scatterlist sg; + struct dma_chan *chan; + dma_cookie_t cookie; +}; + struct sa1100_irda { - unsigned char hscr0; unsigned char utcr4; unsigned char power; unsigned char open; @@ -53,12 +60,8 @@ struct sa1100_irda { int speed; int newspeed; - struct sk_buff *txskb; - struct sk_buff *rxskb; - dma_addr_t txbuf_dma; - dma_addr_t rxbuf_dma; - dma_regs_t *txdma; - dma_regs_t *rxdma; + struct sa1100_buf dma_rx; + struct sa1100_buf dma_tx; struct device *dev; struct irda_platform_data *pdata; @@ -67,23 +70,103 @@ struct sa1100_irda { iobuff_t tx_buff; iobuff_t rx_buff; + + int (*tx_start)(struct sk_buff *, struct net_device *, struct sa1100_irda *); + irqreturn_t (*irq)(struct net_device *, struct sa1100_irda *); }; +static int sa1100_irda_set_speed(struct sa1100_irda *, int); + #define IS_FIR(si) ((si)->speed >= 4000000) #define HPSIR_MAX_RXLEN 2047 +static struct dma_slave_config sa1100_irda_sir_tx = { + .direction = DMA_TO_DEVICE, + .dst_addr = __PREG(Ser2UTDR), + .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, + .dst_maxburst = 4, +}; + +static struct dma_slave_config sa1100_irda_fir_rx = { + .direction = DMA_FROM_DEVICE, + .src_addr = __PREG(Ser2HSDR), + .src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, + .src_maxburst = 8, +}; + +static struct dma_slave_config sa1100_irda_fir_tx = { + .direction = DMA_TO_DEVICE, + .dst_addr = __PREG(Ser2HSDR), + .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, + .dst_maxburst = 8, +}; + +static unsigned sa1100_irda_dma_xferred(struct sa1100_buf *buf) +{ + struct dma_chan *chan = buf->chan; + struct dma_tx_state state; + enum dma_status status; + + status = chan->device->device_tx_status(chan, buf->cookie, &state); + if (status != DMA_PAUSED) + return 0; + + return sg_dma_len(&buf->sg) - state.residue; +} + +static int sa1100_irda_dma_request(struct device *dev, struct sa1100_buf *buf, + const char *name, struct dma_slave_config *cfg) +{ + dma_cap_mask_t m; + int ret; + + dma_cap_zero(m); + dma_cap_set(DMA_SLAVE, m); + + buf->chan = dma_request_channel(m, sa11x0_dma_filter_fn, (void *)name); + if (!buf->chan) { + dev_err(dev, "unable to request DMA channel for %s\n", + name); + return -ENOENT; + } + + ret = dmaengine_slave_config(buf->chan, cfg); + if (ret) + dev_warn(dev, "DMA slave_config for %s returned %d\n", + name, ret); + + buf->dev = buf->chan->device->dev; + + return 0; +} + +static void sa1100_irda_dma_start(struct sa1100_buf *buf, + enum dma_transfer_direction dir, dma_async_tx_callback cb, void *cb_p) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *chan = buf->chan; + + desc = chan->device->device_prep_slave_sg(chan, &buf->sg, 1, dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (desc) { + desc->callback = cb; + desc->callback_param = cb_p; + buf->cookie = dmaengine_submit(desc); + dma_async_issue_pending(chan); + } +} + /* * Allocate and map the receive buffer, unless it is already allocated. */ static int sa1100_irda_rx_alloc(struct sa1100_irda *si) { - if (si->rxskb) + if (si->dma_rx.skb) return 0; - si->rxskb = alloc_skb(HPSIR_MAX_RXLEN + 1, GFP_ATOMIC); - - if (!si->rxskb) { + si->dma_rx.skb = alloc_skb(HPSIR_MAX_RXLEN + 1, GFP_ATOMIC); + if (!si->dma_rx.skb) { printk(KERN_ERR "sa1100_ir: out of memory for RX SKB\n"); return -ENOMEM; } @@ -92,11 +175,14 @@ static int sa1100_irda_rx_alloc(struct sa1100_irda *si) * Align any IP headers that may be contained * within the frame. */ - skb_reserve(si->rxskb, 1); + skb_reserve(si->dma_rx.skb, 1); + + sg_set_buf(&si->dma_rx.sg, si->dma_rx.skb->data, HPSIR_MAX_RXLEN); + if (dma_map_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, DMA_FROM_DEVICE) == 0) { + dev_kfree_skb_any(si->dma_rx.skb); + return -ENOMEM; + } - si->rxbuf_dma = dma_map_single(si->dev, si->rxskb->data, - HPSIR_MAX_RXLEN, - DMA_FROM_DEVICE); return 0; } @@ -106,7 +192,7 @@ static int sa1100_irda_rx_alloc(struct sa1100_irda *si) */ static void sa1100_irda_rx_dma_start(struct sa1100_irda *si) { - if (!si->rxskb) { + if (!si->dma_rx.skb) { printk(KERN_ERR "sa1100_ir: rx buffer went missing\n"); return; } @@ -114,254 +200,87 @@ static void sa1100_irda_rx_dma_start(struct sa1100_irda *si) /* * First empty receive FIFO */ - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP; + Ser2HSCR0 = HSCR0_HSSP; /* * Enable the DMA, receiver and receive interrupt. */ - sa1100_clear_dma(si->rxdma); - sa1100_start_dma(si->rxdma, si->rxbuf_dma, HPSIR_MAX_RXLEN); - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP | HSCR0_RXE; + dmaengine_terminate_all(si->dma_rx.chan); + sa1100_irda_dma_start(&si->dma_rx, DMA_DEV_TO_MEM, NULL, NULL); + + Ser2HSCR0 = HSCR0_HSSP | HSCR0_RXE; } -/* - * Set the IrDA communications speed. - */ -static int sa1100_irda_set_speed(struct sa1100_irda *si, int speed) +static void sa1100_irda_check_speed(struct sa1100_irda *si) { - unsigned long flags; - int brd, ret = -EINVAL; - - switch (speed) { - case 9600: case 19200: case 38400: - case 57600: case 115200: - brd = 3686400 / (16 * speed) - 1; - - /* - * Stop the receive DMA. - */ - if (IS_FIR(si)) - sa1100_stop_dma(si->rxdma); - - local_irq_save(flags); - - Ser2UTCR3 = 0; - Ser2HSCR0 = HSCR0_UART; - - Ser2UTCR1 = brd >> 8; - Ser2UTCR2 = brd; - - /* - * Clear status register - */ - Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; - Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; - - if (si->pdata->set_speed) - si->pdata->set_speed(si->dev, speed); - - si->speed = speed; - - local_irq_restore(flags); - ret = 0; - break; - - case 4000000: - local_irq_save(flags); - - si->hscr0 = 0; - - Ser2HSSR0 = 0xff; - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP; - Ser2UTCR3 = 0; - - si->speed = speed; - - if (si->pdata->set_speed) - si->pdata->set_speed(si->dev, speed); - - sa1100_irda_rx_alloc(si); - sa1100_irda_rx_dma_start(si); - - local_irq_restore(flags); - - break; - - default: - break; + if (si->newspeed) { + sa1100_irda_set_speed(si, si->newspeed); + si->newspeed = 0; } - - return ret; } /* - * Control the power state of the IrDA transmitter. - * State: - * 0 - off - * 1 - short range, lowest power - * 2 - medium range, medium power - * 3 - maximum range, high power - * - * Currently, only assabet is known to support this. + * HP-SIR format support. */ -static int -__sa1100_irda_set_power(struct sa1100_irda *si, unsigned int state) +static void sa1100_irda_sirtxdma_irq(void *id) { - int ret = 0; - if (si->pdata->set_power) - ret = si->pdata->set_power(si->dev, state); - return ret; -} - -static inline int -sa1100_set_power(struct sa1100_irda *si, unsigned int state) -{ - int ret; - - ret = __sa1100_irda_set_power(si, state); - if (ret == 0) - si->power = state; + struct net_device *dev = id; + struct sa1100_irda *si = netdev_priv(dev); - return ret; -} + dma_unmap_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, DMA_TO_DEVICE); + dev_kfree_skb(si->dma_tx.skb); + si->dma_tx.skb = NULL; -static int sa1100_irda_startup(struct sa1100_irda *si) -{ - int ret; + dev->stats.tx_packets++; + dev->stats.tx_bytes += sg_dma_len(&si->dma_tx.sg); - /* - * Ensure that the ports for this device are setup correctly. - */ - if (si->pdata->startup) { - ret = si->pdata->startup(si->dev); - if (ret) - return ret; - } - - /* - * Configure PPC for IRDA - we want to drive TXD2 low. - * We also want to drive this pin low during sleep. - */ - PPSR &= ~PPC_TXD2; - PSDR &= ~PPC_TXD2; - PPDR |= PPC_TXD2; - - /* - * Enable HP-SIR modulation, and ensure that the port is disabled. - */ - Ser2UTCR3 = 0; - Ser2HSCR0 = HSCR0_UART; - Ser2UTCR4 = si->utcr4; - Ser2UTCR0 = UTCR0_8BitData; - Ser2HSCR2 = HSCR2_TrDataH | HSCR2_RcDataL; + /* We need to ensure that the transmitter has finished. */ + do + rmb(); + while (Ser2UTSR1 & UTSR1_TBY); /* - * Clear status register + * Ok, we've finished transmitting. Now enable the receiver. + * Sometimes we get a receive IRQ immediately after a transmit... */ Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; + Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; - ret = sa1100_irda_set_speed(si, si->speed = 9600); - if (ret) { - Ser2UTCR3 = 0; - Ser2HSCR0 = 0; - - if (si->pdata->shutdown) - si->pdata->shutdown(si->dev); - } - - return ret; -} - -static void sa1100_irda_shutdown(struct sa1100_irda *si) -{ - /* - * Stop all DMA activity. - */ - sa1100_stop_dma(si->rxdma); - sa1100_stop_dma(si->txdma); - - /* Disable the port. */ - Ser2UTCR3 = 0; - Ser2HSCR0 = 0; + sa1100_irda_check_speed(si); - if (si->pdata->shutdown) - si->pdata->shutdown(si->dev); + /* I'm hungry! */ + netif_wake_queue(dev); } -#ifdef CONFIG_PM -/* - * Suspend the IrDA interface. - */ -static int sa1100_irda_suspend(struct platform_device *pdev, pm_message_t state) +static int sa1100_irda_sir_tx_start(struct sk_buff *skb, struct net_device *dev, + struct sa1100_irda *si) { - struct net_device *dev = platform_get_drvdata(pdev); - struct sa1100_irda *si; - - if (!dev) - return 0; - - si = netdev_priv(dev); - if (si->open) { - /* - * Stop the transmit queue - */ - netif_device_detach(dev); - disable_irq(dev->irq); - sa1100_irda_shutdown(si); - __sa1100_irda_set_power(si, 0); + si->tx_buff.data = si->tx_buff.head; + si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data, + si->tx_buff.truesize); + + si->dma_tx.skb = skb; + sg_set_buf(&si->dma_tx.sg, si->tx_buff.data, si->tx_buff.len); + if (dma_map_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, DMA_TO_DEVICE) == 0) { + si->dma_tx.skb = NULL; + netif_wake_queue(dev); + dev->stats.tx_dropped++; + return NETDEV_TX_OK; } - return 0; -} - -/* - * Resume the IrDA interface. - */ -static int sa1100_irda_resume(struct platform_device *pdev) -{ - struct net_device *dev = platform_get_drvdata(pdev); - struct sa1100_irda *si; - - if (!dev) - return 0; + sa1100_irda_dma_start(&si->dma_tx, DMA_MEM_TO_DEV, sa1100_irda_sirtxdma_irq, dev); - si = netdev_priv(dev); - if (si->open) { - /* - * If we missed a speed change, initialise at the new speed - * directly. It is debatable whether this is actually - * required, but in the interests of continuing from where - * we left off it is desirable. The converse argument is - * that we should re-negotiate at 9600 baud again. - */ - if (si->newspeed) { - si->speed = si->newspeed; - si->newspeed = 0; - } - - sa1100_irda_startup(si); - __sa1100_irda_set_power(si, si->power); - enable_irq(dev->irq); - - /* - * This automatically wakes up the queue - */ - netif_device_attach(dev); - } + /* + * The mean turn-around time is enforced by XBOF padding, + * so we don't have to do anything special here. + */ + Ser2UTCR3 = UTCR3_TXE; - return 0; + return NETDEV_TX_OK; } -#else -#define sa1100_irda_suspend NULL -#define sa1100_irda_resume NULL -#endif -/* - * HP-SIR format interrupt service routines. - */ -static void sa1100_irda_hpsir_irq(struct net_device *dev) +static irqreturn_t sa1100_irda_sir_irq(struct net_device *dev, struct sa1100_irda *si) { - struct sa1100_irda *si = netdev_priv(dev); int status; status = Ser2UTSR0; @@ -414,51 +333,96 @@ static void sa1100_irda_hpsir_irq(struct net_device *dev) } - if (status & UTSR0_TFS && si->tx_buff.len) { - /* - * Transmitter FIFO is not full - */ - do { - Ser2UTDR = *si->tx_buff.data++; - si->tx_buff.len -= 1; - } while (Ser2UTSR1 & UTSR1_TNF && si->tx_buff.len); + return IRQ_HANDLED; +} - if (si->tx_buff.len == 0) { - dev->stats.tx_packets++; - dev->stats.tx_bytes += si->tx_buff.data - - si->tx_buff.head; +/* + * FIR format support. + */ +static void sa1100_irda_firtxdma_irq(void *id) +{ + struct net_device *dev = id; + struct sa1100_irda *si = netdev_priv(dev); + struct sk_buff *skb; - /* - * We need to ensure that the transmitter has - * finished. - */ - do - rmb(); - while (Ser2UTSR1 & UTSR1_TBY); + /* + * Wait for the transmission to complete. Unfortunately, + * the hardware doesn't give us an interrupt to indicate + * "end of frame". + */ + do + rmb(); + while (!(Ser2HSSR0 & HSSR0_TUR) || Ser2HSSR1 & HSSR1_TBY); - /* - * Ok, we've finished transmitting. Now enable - * the receiver. Sometimes we get a receive IRQ - * immediately after a transmit... - */ - Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; - Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; + /* + * Clear the transmit underrun bit. + */ + Ser2HSSR0 = HSSR0_TUR; - if (si->newspeed) { - sa1100_irda_set_speed(si, si->newspeed); - si->newspeed = 0; - } + /* + * Do we need to change speed? Note that we're lazy + * here - we don't free the old dma_rx.skb. We don't need + * to allocate a buffer either. + */ + sa1100_irda_check_speed(si); - /* I'm hungry! */ - netif_wake_queue(dev); - } + /* + * Start reception. This disables the transmitter for + * us. This will be using the existing RX buffer. + */ + sa1100_irda_rx_dma_start(si); + + /* Account and free the packet. */ + skb = si->dma_tx.skb; + if (skb) { + dma_unmap_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, + DMA_TO_DEVICE); + dev->stats.tx_packets ++; + dev->stats.tx_bytes += skb->len; + dev_kfree_skb_irq(skb); + si->dma_tx.skb = NULL; } + + /* + * Make sure that the TX queue is available for sending + * (for retries). TX has priority over RX at all times. + */ + netif_wake_queue(dev); +} + +static int sa1100_irda_fir_tx_start(struct sk_buff *skb, struct net_device *dev, + struct sa1100_irda *si) +{ + int mtt = irda_get_mtt(skb); + + si->dma_tx.skb = skb; + sg_set_buf(&si->dma_tx.sg, skb->data, skb->len); + if (dma_map_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, DMA_TO_DEVICE) == 0) { + si->dma_tx.skb = NULL; + netif_wake_queue(dev); + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + + sa1100_irda_dma_start(&si->dma_tx, DMA_MEM_TO_DEV, sa1100_irda_firtxdma_irq, dev); + + /* + * If we have a mean turn-around time, impose the specified + * specified delay. We could shorten this by timing from + * the point we received the packet. + */ + if (mtt) + udelay(mtt); + + Ser2HSCR0 = HSCR0_HSSP | HSCR0_TXE; + + return NETDEV_TX_OK; } static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev) { - struct sk_buff *skb = si->rxskb; - dma_addr_t dma_addr; + struct sk_buff *skb = si->dma_rx.skb; unsigned int len, stat, data; if (!skb) { @@ -469,11 +433,10 @@ static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev /* * Get the current data position. */ - dma_addr = sa1100_get_dma_pos(si->rxdma); - len = dma_addr - si->rxbuf_dma; + len = sa1100_irda_dma_xferred(&si->dma_rx); if (len > HPSIR_MAX_RXLEN) len = HPSIR_MAX_RXLEN; - dma_unmap_single(si->dev, si->rxbuf_dma, len, DMA_FROM_DEVICE); + dma_unmap_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, DMA_FROM_DEVICE); do { /* @@ -501,7 +464,7 @@ static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev } while (Ser2HSSR0 & HSSR0_EIF); if (stat & HSSR1_EOF) { - si->rxskb = NULL; + si->dma_rx.skb = NULL; skb_put(skb, len); skb->dev = dev; @@ -518,28 +481,23 @@ static void sa1100_irda_fir_error(struct sa1100_irda *si, struct net_device *dev netif_rx(skb); } else { /* - * Remap the buffer. + * Remap the buffer - it was previously mapped, and we + * hope that this succeeds. */ - si->rxbuf_dma = dma_map_single(si->dev, si->rxskb->data, - HPSIR_MAX_RXLEN, - DMA_FROM_DEVICE); + dma_map_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, DMA_FROM_DEVICE); } } /* - * FIR format interrupt service routine. We only have to - * handle RX events; transmit events go via the TX DMA handler. - * - * No matter what, we disable RX, process, and the restart RX. + * We only have to handle RX events here; transmit events go via the TX + * DMA handler. We disable RX, process, and the restart RX. */ -static void sa1100_irda_fir_irq(struct net_device *dev) +static irqreturn_t sa1100_irda_fir_irq(struct net_device *dev, struct sa1100_irda *si) { - struct sa1100_irda *si = netdev_priv(dev); - /* * Stop RX DMA */ - sa1100_stop_dma(si->rxdma); + dmaengine_pause(si->dma_rx.chan); /* * Framing error - we throw away the packet completely. @@ -555,7 +513,7 @@ static void sa1100_irda_fir_irq(struct net_device *dev) /* * Clear out the DMA... */ - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP; + Ser2HSCR0 = HSCR0_HSSP; /* * Clear selected status bits now, so we @@ -577,74 +535,124 @@ static void sa1100_irda_fir_irq(struct net_device *dev) * No matter what happens, we must restart reception. */ sa1100_irda_rx_dma_start(si); -} -static irqreturn_t sa1100_irda_irq(int irq, void *dev_id) -{ - struct net_device *dev = dev_id; - if (IS_FIR(((struct sa1100_irda *)netdev_priv(dev)))) - sa1100_irda_fir_irq(dev); - else - sa1100_irda_hpsir_irq(dev); return IRQ_HANDLED; } /* - * TX DMA completion handler. + * Set the IrDA communications speed. */ -static void sa1100_irda_txdma_irq(void *id) +static int sa1100_irda_set_speed(struct sa1100_irda *si, int speed) { - struct net_device *dev = id; - struct sa1100_irda *si = netdev_priv(dev); - struct sk_buff *skb = si->txskb; + unsigned long flags; + int brd, ret = -EINVAL; - si->txskb = NULL; + switch (speed) { + case 9600: case 19200: case 38400: + case 57600: case 115200: + brd = 3686400 / (16 * speed) - 1; - /* - * Wait for the transmission to complete. Unfortunately, - * the hardware doesn't give us an interrupt to indicate - * "end of frame". - */ - do - rmb(); - while (!(Ser2HSSR0 & HSSR0_TUR) || Ser2HSSR1 & HSSR1_TBY); + /* Stop the receive DMA, and configure transmit. */ + if (IS_FIR(si)) { + dmaengine_terminate_all(si->dma_rx.chan); + dmaengine_slave_config(si->dma_tx.chan, + &sa1100_irda_sir_tx); + } - /* - * Clear the transmit underrun bit. - */ - Ser2HSSR0 = HSSR0_TUR; + local_irq_save(flags); - /* - * Do we need to change speed? Note that we're lazy - * here - we don't free the old rxskb. We don't need - * to allocate a buffer either. - */ - if (si->newspeed) { - sa1100_irda_set_speed(si, si->newspeed); - si->newspeed = 0; - } + Ser2UTCR3 = 0; + Ser2HSCR0 = HSCR0_UART; - /* - * Start reception. This disables the transmitter for - * us. This will be using the existing RX buffer. - */ - sa1100_irda_rx_dma_start(si); + Ser2UTCR1 = brd >> 8; + Ser2UTCR2 = brd; - /* - * Account and free the packet. - */ - if (skb) { - dma_unmap_single(si->dev, si->txbuf_dma, skb->len, DMA_TO_DEVICE); - dev->stats.tx_packets ++; - dev->stats.tx_bytes += skb->len; - dev_kfree_skb_irq(skb); + /* + * Clear status register + */ + Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; + Ser2UTCR3 = UTCR3_RIE | UTCR3_RXE | UTCR3_TXE; + + if (si->pdata->set_speed) + si->pdata->set_speed(si->dev, speed); + + si->speed = speed; + si->tx_start = sa1100_irda_sir_tx_start; + si->irq = sa1100_irda_sir_irq; + + local_irq_restore(flags); + ret = 0; + break; + + case 4000000: + if (!IS_FIR(si)) + dmaengine_slave_config(si->dma_tx.chan, + &sa1100_irda_fir_tx); + + local_irq_save(flags); + + Ser2HSSR0 = 0xff; + Ser2HSCR0 = HSCR0_HSSP; + Ser2UTCR3 = 0; + + si->speed = speed; + si->tx_start = sa1100_irda_fir_tx_start; + si->irq = sa1100_irda_fir_irq; + + if (si->pdata->set_speed) + si->pdata->set_speed(si->dev, speed); + + sa1100_irda_rx_alloc(si); + sa1100_irda_rx_dma_start(si); + + local_irq_restore(flags); + + break; + + default: + break; } - /* - * Make sure that the TX queue is available for sending - * (for retries). TX has priority over RX at all times. - */ - netif_wake_queue(dev); + return ret; +} + +/* + * Control the power state of the IrDA transmitter. + * State: + * 0 - off + * 1 - short range, lowest power + * 2 - medium range, medium power + * 3 - maximum range, high power + * + * Currently, only assabet is known to support this. + */ +static int +__sa1100_irda_set_power(struct sa1100_irda *si, unsigned int state) +{ + int ret = 0; + if (si->pdata->set_power) + ret = si->pdata->set_power(si->dev, state); + return ret; +} + +static inline int +sa1100_set_power(struct sa1100_irda *si, unsigned int state) +{ + int ret; + + ret = __sa1100_irda_set_power(si, state); + if (ret == 0) + si->power = state; + + return ret; +} + +static irqreturn_t sa1100_irda_irq(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct sa1100_irda *si = netdev_priv(dev); + + return si->irq(dev, si); } static int sa1100_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) @@ -660,62 +668,19 @@ static int sa1100_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) if (speed != si->speed && speed != -1) si->newspeed = speed; - /* - * If this is an empty frame, we can bypass a lot. - */ + /* If this is an empty frame, we can bypass a lot. */ if (skb->len == 0) { - if (si->newspeed) { - si->newspeed = 0; - sa1100_irda_set_speed(si, speed); - } + sa1100_irda_check_speed(si); dev_kfree_skb(skb); return NETDEV_TX_OK; } - if (!IS_FIR(si)) { - netif_stop_queue(dev); - - si->tx_buff.data = si->tx_buff.head; - si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data, - si->tx_buff.truesize); - - /* - * Set the transmit interrupt enable. This will fire - * off an interrupt immediately. Note that we disable - * the receiver so we won't get spurious characteres - * received. - */ - Ser2UTCR3 = UTCR3_TIE | UTCR3_TXE; - - dev_kfree_skb(skb); - } else { - int mtt = irda_get_mtt(skb); - - /* - * We must not be transmitting... - */ - BUG_ON(si->txskb); - - netif_stop_queue(dev); - - si->txskb = skb; - si->txbuf_dma = dma_map_single(si->dev, skb->data, - skb->len, DMA_TO_DEVICE); - - sa1100_start_dma(si->txdma, si->txbuf_dma, skb->len); - - /* - * If we have a mean turn-around time, impose the specified - * specified delay. We could shorten this by timing from - * the point we received the packet. - */ - if (mtt) - udelay(mtt); + netif_stop_queue(dev); - Ser2HSCR0 = si->hscr0 | HSCR0_HSSP | HSCR0_TXE; - } + /* We must not already have a skb to transmit... */ + BUG_ON(si->dma_tx.skb); - return NETDEV_TX_OK; + return si->tx_start(skb, dev, si); } static int @@ -762,6 +727,69 @@ sa1100_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd) return ret; } +static int sa1100_irda_startup(struct sa1100_irda *si) +{ + int ret; + + /* + * Ensure that the ports for this device are setup correctly. + */ + if (si->pdata->startup) { + ret = si->pdata->startup(si->dev); + if (ret) + return ret; + } + + /* + * Configure PPC for IRDA - we want to drive TXD2 low. + * We also want to drive this pin low during sleep. + */ + PPSR &= ~PPC_TXD2; + PSDR &= ~PPC_TXD2; + PPDR |= PPC_TXD2; + + /* + * Enable HP-SIR modulation, and ensure that the port is disabled. + */ + Ser2UTCR3 = 0; + Ser2HSCR0 = HSCR0_UART; + Ser2UTCR4 = si->utcr4; + Ser2UTCR0 = UTCR0_8BitData; + Ser2HSCR2 = HSCR2_TrDataH | HSCR2_RcDataL; + + /* + * Clear status register + */ + Ser2UTSR0 = UTSR0_REB | UTSR0_RBB | UTSR0_RID; + + ret = sa1100_irda_set_speed(si, si->speed = 9600); + if (ret) { + Ser2UTCR3 = 0; + Ser2HSCR0 = 0; + + if (si->pdata->shutdown) + si->pdata->shutdown(si->dev); + } + + return ret; +} + +static void sa1100_irda_shutdown(struct sa1100_irda *si) +{ + /* + * Stop all DMA activity. + */ + dmaengine_terminate_all(si->dma_rx.chan); + dmaengine_terminate_all(si->dma_tx.chan); + + /* Disable the port. */ + Ser2UTCR3 = 0; + Ser2HSCR0 = 0; + + if (si->pdata->shutdown) + si->pdata->shutdown(si->dev); +} + static int sa1100_irda_start(struct net_device *dev) { struct sa1100_irda *si = netdev_priv(dev); @@ -769,26 +797,17 @@ static int sa1100_irda_start(struct net_device *dev) si->speed = 9600; - err = request_irq(dev->irq, sa1100_irda_irq, 0, dev->name, dev); - if (err) - goto err_irq; - - err = sa1100_request_dma(DMA_Ser2HSSPRd, "IrDA receive", - NULL, NULL, &si->rxdma); + err = sa1100_irda_dma_request(si->dev, &si->dma_rx, "Ser2ICPRc", + &sa1100_irda_fir_rx); if (err) goto err_rx_dma; - err = sa1100_request_dma(DMA_Ser2HSSPWr, "IrDA transmit", - sa1100_irda_txdma_irq, dev, &si->txdma); + err = sa1100_irda_dma_request(si->dev, &si->dma_tx, "Ser2ICPTr", + &sa1100_irda_sir_tx); if (err) goto err_tx_dma; /* - * The interrupt must remain disabled for now. - */ - disable_irq(dev->irq); - - /* * Setup the serial port for the specified speed. */ err = sa1100_irda_startup(si); @@ -803,44 +822,60 @@ static int sa1100_irda_start(struct net_device *dev) if (!si->irlap) goto err_irlap; + err = request_irq(dev->irq, sa1100_irda_irq, 0, dev->name, dev); + if (err) + goto err_irq; + /* * Now enable the interrupt and start the queue */ si->open = 1; sa1100_set_power(si, power_level); /* low power mode */ - enable_irq(dev->irq); + netif_start_queue(dev); return 0; +err_irq: + irlap_close(si->irlap); err_irlap: si->open = 0; sa1100_irda_shutdown(si); err_startup: - sa1100_free_dma(si->txdma); + dma_release_channel(si->dma_tx.chan); err_tx_dma: - sa1100_free_dma(si->rxdma); + dma_release_channel(si->dma_rx.chan); err_rx_dma: - free_irq(dev->irq, dev); -err_irq: return err; } static int sa1100_irda_stop(struct net_device *dev) { struct sa1100_irda *si = netdev_priv(dev); + struct sk_buff *skb; + + netif_stop_queue(dev); - disable_irq(dev->irq); + si->open = 0; sa1100_irda_shutdown(si); /* - * If we have been doing DMA receive, make sure we + * If we have been doing any DMA activity, make sure we * tidy that up cleanly. */ - if (si->rxskb) { - dma_unmap_single(si->dev, si->rxbuf_dma, HPSIR_MAX_RXLEN, - DMA_FROM_DEVICE); - dev_kfree_skb(si->rxskb); - si->rxskb = NULL; + skb = si->dma_rx.skb; + if (skb) { + dma_unmap_sg(si->dma_rx.dev, &si->dma_rx.sg, 1, + DMA_FROM_DEVICE); + dev_kfree_skb(skb); + si->dma_rx.skb = NULL; + } + + skb = si->dma_tx.skb; + if (skb) { + dma_unmap_sg(si->dma_tx.dev, &si->dma_tx.sg, 1, + DMA_TO_DEVICE); + dev_kfree_skb(skb); + si->dma_tx.skb = NULL; } /* Stop IrLAP */ @@ -849,14 +884,11 @@ static int sa1100_irda_stop(struct net_device *dev) si->irlap = NULL; } - netif_stop_queue(dev); - si->open = 0; - /* * Free resources */ - sa1100_free_dma(si->txdma); - sa1100_free_dma(si->rxdma); + dma_release_channel(si->dma_tx.chan); + dma_release_channel(si->dma_rx.chan); free_irq(dev->irq, dev); sa1100_set_power(si, 0); @@ -888,11 +920,15 @@ static int sa1100_irda_probe(struct platform_device *pdev) struct net_device *dev; struct sa1100_irda *si; unsigned int baudrate_mask; - int err; + int err, irq; if (!pdev->dev.platform_data) return -EINVAL; + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq < 0 ? irq : -ENXIO; + err = request_mem_region(__PREG(Ser2UTCR0), 0x24, "IrDA") ? 0 : -EBUSY; if (err) goto err_mem_1; @@ -907,22 +943,27 @@ static int sa1100_irda_probe(struct platform_device *pdev) if (!dev) goto err_mem_4; + SET_NETDEV_DEV(dev, &pdev->dev); + si = netdev_priv(dev); si->dev = &pdev->dev; si->pdata = pdev->dev.platform_data; + sg_init_table(&si->dma_rx.sg, 1); + sg_init_table(&si->dma_tx.sg, 1); + /* * Initialise the HP-SIR buffers */ err = sa1100_irda_init_iobuf(&si->rx_buff, 14384); if (err) goto err_mem_5; - err = sa1100_irda_init_iobuf(&si->tx_buff, 4000); + err = sa1100_irda_init_iobuf(&si->tx_buff, IRDA_SIR_MAX_FRAME); if (err) goto err_mem_5; dev->netdev_ops = &sa1100_irda_netdev_ops; - dev->irq = IRQ_Ser2ICP; + dev->irq = irq; irda_init_max_qos_capabilies(&si->qos); @@ -996,6 +1037,74 @@ static int sa1100_irda_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +/* + * Suspend the IrDA interface. + */ +static int sa1100_irda_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct sa1100_irda *si; + + if (!dev) + return 0; + + si = netdev_priv(dev); + if (si->open) { + /* + * Stop the transmit queue + */ + netif_device_detach(dev); + disable_irq(dev->irq); + sa1100_irda_shutdown(si); + __sa1100_irda_set_power(si, 0); + } + + return 0; +} + +/* + * Resume the IrDA interface. + */ +static int sa1100_irda_resume(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct sa1100_irda *si; + + if (!dev) + return 0; + + si = netdev_priv(dev); + if (si->open) { + /* + * If we missed a speed change, initialise at the new speed + * directly. It is debatable whether this is actually + * required, but in the interests of continuing from where + * we left off it is desirable. The converse argument is + * that we should re-negotiate at 9600 baud again. + */ + if (si->newspeed) { + si->speed = si->newspeed; + si->newspeed = 0; + } + + sa1100_irda_startup(si); + __sa1100_irda_set_power(si, si->power); + enable_irq(dev->irq); + + /* + * This automatically wakes up the queue + */ + netif_device_attach(dev); + } + + return 0; +} +#else +#define sa1100_irda_suspend NULL +#define sa1100_irda_resume NULL +#endif + static struct platform_driver sa1100ir_driver = { .probe = sa1100_irda_probe, .remove = sa1100_irda_remove, diff --git a/drivers/net/usb/cdc-phonet.c b/drivers/net/usb/cdc-phonet.c index 790cbdea7392..3886b30ed373 100644 --- a/drivers/net/usb/cdc-phonet.c +++ b/drivers/net/usb/cdc-phonet.c @@ -164,12 +164,14 @@ static void rx_complete(struct urb *req) /* Can't use pskb_pull() on page in IRQ */ memcpy(skb_put(skb, 1), page_address(page), 1); skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, - page, 1, req->actual_length); + page, 1, req->actual_length, + req->actual_length); page = NULL; } } else { skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, - page, 0, req->actual_length); + page, 0, req->actual_length, + req->actual_length); page = NULL; } if (req->actual_length < PAGE_SIZE) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index aac68f5195c0..552d24bf862e 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -409,6 +409,42 @@ static const struct usb_device_id products[] = { .bInterfaceProtocol = 0xff, .driver_info = (unsigned long)&qmi_wwan_force_int4, }, + { /* ZTE (Vodafone) K3565-Z */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x19d2, + .idProduct = 0x0063, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .driver_info = (unsigned long)&qmi_wwan_force_int4, + }, + { /* ZTE (Vodafone) K3570-Z */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x19d2, + .idProduct = 0x1008, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .driver_info = (unsigned long)&qmi_wwan_force_int4, + }, + { /* ZTE (Vodafone) K3571-Z */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x19d2, + .idProduct = 0x1010, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .driver_info = (unsigned long)&qmi_wwan_force_int4, + }, + { /* ZTE (Vodafone) K4505-Z */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x19d2, + .idProduct = 0x0104, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .driver_info = (unsigned long)&qmi_wwan_force_int4, + }, {QMI_GOBI_DEVICE(0x05c6, 0x9212)}, /* Acer Gobi Modem Device */ {QMI_GOBI_DEVICE(0x03f0, 0x1f1d)}, /* HP un2400 Gobi Modem Device */ {QMI_GOBI_DEVICE(0x03f0, 0x371d)}, /* HP un2430 Mobile Broadband Module */ diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 4b8b52ca09d8..b7b3f5b0d406 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -493,6 +493,7 @@ block: if (netif_running (dev->net) && !test_bit (EVENT_RX_HALT, &dev->flags)) { rx_submit (dev, urb, GFP_ATOMIC); + usb_mark_last_busy(dev->udev); return; } usb_free_urb (urb); @@ -589,6 +590,14 @@ static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q) entry = (struct skb_data *) skb->cb; urb = entry->urb; + /* + * Get reference count of the URB to avoid it to be + * freed during usb_unlink_urb, which may trigger + * use-after-free problem inside usb_unlink_urb since + * usb_unlink_urb is always racing with .complete + * handler(include defer_bh). + */ + usb_get_urb(urb); spin_unlock_irqrestore(&q->lock, flags); // during some PM-driven resume scenarios, // these (async) unlinks complete immediately @@ -597,6 +606,7 @@ static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q) netdev_dbg(dev->net, "unlink urb err, %d\n", retval); else count++; + usb_put_urb(urb); spin_lock_irqsave(&q->lock, flags); } spin_unlock_irqrestore (&q->lock, flags); @@ -1028,7 +1038,6 @@ static void tx_complete (struct urb *urb) } usb_autopm_put_interface_async(dev->intf); - urb->dev = NULL; entry->state = tx_done; defer_bh(dev, skb, &dev->txq); } diff --git a/drivers/net/wireless/iwlegacy/3945.c b/drivers/net/wireless/iwlegacy/3945.c index c5b1d199e0bc..b25c01be0d90 100644 --- a/drivers/net/wireless/iwlegacy/3945.c +++ b/drivers/net/wireless/iwlegacy/3945.c @@ -499,7 +499,8 @@ il3945_pass_packet_to_mac80211(struct il_priv *il, struct il_rx_buf *rxb, le32_to_cpu(rx_end->status), stats); skb_add_rx_frag(skb, 0, rxb->page, - (void *)rx_hdr->payload - (void *)pkt, len); + (void *)rx_hdr->payload - (void *)pkt, len, + len); il_update_stats(il, false, fc, len); memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); diff --git a/drivers/net/wireless/iwlegacy/4965-mac.c b/drivers/net/wireless/iwlegacy/4965-mac.c index 7b54dbb338be..17f1c6853182 100644 --- a/drivers/net/wireless/iwlegacy/4965-mac.c +++ b/drivers/net/wireless/iwlegacy/4965-mac.c @@ -596,7 +596,8 @@ il4965_pass_packet_to_mac80211(struct il_priv *il, struct ieee80211_hdr *hdr, return; } - skb_add_rx_frag(skb, 0, rxb->page, (void *)hdr - rxb_addr(rxb), len); + skb_add_rx_frag(skb, 0, rxb->page, (void *)hdr - rxb_addr(rxb), len, + len); il_update_stats(il, false, fc, len); memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); diff --git a/drivers/net/wireless/iwlwifi/iwl-agn-rx.c b/drivers/net/wireless/iwlwifi/iwl-agn-rx.c index 44c6f712b77d..f4b84d1596e3 100644 --- a/drivers/net/wireless/iwlwifi/iwl-agn-rx.c +++ b/drivers/net/wireless/iwlwifi/iwl-agn-rx.c @@ -796,7 +796,7 @@ static void iwlagn_pass_packet_to_mac80211(struct iwl_priv *priv, offset = (void *)hdr - rxb_addr(rxb); p = rxb_steal_page(rxb); - skb_add_rx_frag(skb, 0, p, offset, len); + skb_add_rx_frag(skb, 0, p, offset, len, len); iwl_update_stats(priv, false, fc, len); diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index b16175032327..663b32c2e931 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -47,6 +47,7 @@ #include <xen/xenbus.h> #include <xen/events.h> #include <xen/page.h> +#include <xen/platform_pci.h> #include <xen/grant_table.h> #include <xen/interface/io/netif.h> @@ -1964,6 +1965,9 @@ static int __init netif_init(void) if (xen_initial_domain()) return 0; + if (!xen_platform_pci_unplug) + return -ENODEV; + printk(KERN_INFO "Initialising Xen virtual ethernet driver.\n"); return xenbus_register_frontend(&netfront_driver); diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 6ea51dcbc728..8e84ce9765a9 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -91,4 +91,8 @@ config OF_PCI_IRQ help OpenFirmware PCI IRQ routing helpers +config OF_MTD + depends on MTD + def_bool y + endmenu # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index a73f5a51ff4c..aa90e602c8a7 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_OF_SELFTEST) += selftest.o obj-$(CONFIG_OF_MDIO) += of_mdio.o obj-$(CONFIG_OF_PCI) += of_pci.o obj-$(CONFIG_OF_PCI_IRQ) += of_pci_irq.o +obj-$(CONFIG_OF_MTD) += of_mtd.o diff --git a/drivers/of/of_mtd.c b/drivers/of/of_mtd.c new file mode 100644 index 000000000000..e7cad627a5d1 --- /dev/null +++ b/drivers/of/of_mtd.c @@ -0,0 +1,85 @@ +/* + * Copyright 2012 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> + * + * OF helpers for mtd. + * + * This file is released under the GPLv2 + * + */ +#include <linux/kernel.h> +#include <linux/of_mtd.h> +#include <linux/mtd/nand.h> +#include <linux/export.h> + +/** + * It maps 'enum nand_ecc_modes_t' found in include/linux/mtd/nand.h + * into the device tree binding of 'nand-ecc', so that MTD + * device driver can get nand ecc from device tree. + */ +static const char *nand_ecc_modes[] = { + [NAND_ECC_NONE] = "none", + [NAND_ECC_SOFT] = "soft", + [NAND_ECC_HW] = "hw", + [NAND_ECC_HW_SYNDROME] = "hw_syndrome", + [NAND_ECC_HW_OOB_FIRST] = "hw_oob_first", + [NAND_ECC_SOFT_BCH] = "soft_bch", +}; + +/** + * of_get_nand_ecc_mode - Get nand ecc mode for given device_node + * @np: Pointer to the given device_node + * + * The function gets ecc mode string from property 'nand-ecc-mode', + * and return its index in nand_ecc_modes table, or errno in error case. + */ +const int of_get_nand_ecc_mode(struct device_node *np) +{ + const char *pm; + int err, i; + + err = of_property_read_string(np, "nand-ecc-mode", &pm); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(nand_ecc_modes); i++) + if (!strcasecmp(pm, nand_ecc_modes[i])) + return i; + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(of_get_nand_ecc_mode); + +/** + * of_get_nand_bus_width - Get nand bus witdh for given device_node + * @np: Pointer to the given device_node + * + * return bus width option, or errno in error case. + */ +int of_get_nand_bus_width(struct device_node *np) +{ + u32 val; + + if (of_property_read_u32(np, "nand-bus-width", &val)) + return 8; + + switch(val) { + case 8: + case 16: + return val; + default: + return -EIO; + } +} +EXPORT_SYMBOL_GPL(of_get_nand_bus_width); + +/** + * of_get_nand_on_flash_bbt - Get nand on flash bbt for given device_node + * @np: Pointer to the given device_node + * + * return true if present false other wise + */ +bool of_get_nand_on_flash_bbt(struct device_node *np) +{ + return of_property_read_bool(np, "nand-on-flash-bbt"); +} +EXPORT_SYMBOL_GPL(of_get_nand_on_flash_bbt); diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 20fbebd49db3..343ad29e211c 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -253,7 +253,7 @@ static struct amba_device *of_amba_device_create(struct device_node *node, if (!of_device_is_available(node)) return NULL; - dev = kzalloc(sizeof(*dev), GFP_KERNEL); + dev = amba_device_alloc(NULL, 0, 0); if (!dev) return NULL; @@ -283,14 +283,14 @@ static struct amba_device *of_amba_device_create(struct device_node *node, if (ret) goto err_free; - ret = amba_device_register(dev, &iomem_resource); + ret = amba_device_add(dev, &iomem_resource); if (ret) goto err_free; return dev; err_free: - kfree(dev); + amba_device_put(dev); return NULL; } #else /* CONFIG_ARM_AMBA */ diff --git a/drivers/parisc/dino.c b/drivers/parisc/dino.c index 7ff10c1e8664..0610e91bceb2 100644 --- a/drivers/parisc/dino.c +++ b/drivers/parisc/dino.c @@ -553,7 +553,6 @@ dino_fixup_bus(struct pci_bus *bus) struct list_head *ln; struct pci_dev *dev; struct dino_device *dino_dev = DINO_DEV(parisc_walk_tree(bus->bridge)); - int port_base = HBA_PORT_BASE(dino_dev->hba.hba_num); DBG(KERN_WARNING "%s(0x%p) bus %d platform_data 0x%p\n", __func__, bus, bus->secondary, @@ -599,8 +598,6 @@ dino_fixup_bus(struct pci_bus *bus) list_for_each(ln, &bus->devices) { - int i; - dev = pci_dev_b(ln); if (is_card_dino(&dino_dev->hba.dev->id)) dino_card_fixup(dev); @@ -612,21 +609,6 @@ dino_fixup_bus(struct pci_bus *bus) if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) continue; - /* Adjust the I/O Port space addresses */ - for (i = 0; i < PCI_NUM_RESOURCES; i++) { - struct resource *res = &dev->resource[i]; - if (res->flags & IORESOURCE_IO) { - res->start |= port_base; - res->end |= port_base; - } -#ifdef __LP64__ - /* Sign Extend MMIO addresses */ - else if (res->flags & IORESOURCE_MEM) { - res->start |= F_EXTEND(0UL); - res->end |= F_EXTEND(0UL); - } -#endif - } /* null out the ROM resource if there is one (we don't * care about an expansion rom on parisc, since it * usually contains (x86) bios code) */ @@ -991,11 +973,14 @@ static int __init dino_probe(struct parisc_device *dev) dev->dev.platform_data = dino_dev; - pci_add_resource(&resources, &dino_dev->hba.io_space); + pci_add_resource_offset(&resources, &dino_dev->hba.io_space, + HBA_PORT_BASE(dino_dev->hba.hba_num)); if (dino_dev->hba.lmmio_space.flags) - pci_add_resource(&resources, &dino_dev->hba.lmmio_space); + pci_add_resource_offset(&resources, &dino_dev->hba.lmmio_space, + dino_dev->hba.lmmio_space_offset); if (dino_dev->hba.elmmio_space.flags) - pci_add_resource(&resources, &dino_dev->hba.elmmio_space); + pci_add_resource_offset(&resources, &dino_dev->hba.elmmio_space, + dino_dev->hba.lmmio_space_offset); if (dino_dev->hba.gmmio_space.flags) pci_add_resource(&resources, &dino_dev->hba.gmmio_space); diff --git a/drivers/parisc/lba_pci.c b/drivers/parisc/lba_pci.c index d5f3d753a108..e8857647e210 100644 --- a/drivers/parisc/lba_pci.c +++ b/drivers/parisc/lba_pci.c @@ -635,7 +635,6 @@ lba_fixup_bus(struct pci_bus *bus) u16 status; #endif struct lba_device *ldev = LBA_DEV(parisc_walk_tree(bus->bridge)); - int lba_portbase = HBA_PORT_BASE(ldev->hba.hba_num); DBG("lba_fixup_bus(0x%p) bus %d platform_data 0x%p\n", bus, bus->secondary, bus->bridge->platform_data); @@ -726,27 +725,6 @@ lba_fixup_bus(struct pci_bus *bus) if (!res->start) continue; - if (res->flags & IORESOURCE_IO) { - DBG("lba_fixup_bus() I/O Ports [%lx/%lx] -> ", - res->start, res->end); - res->start |= lba_portbase; - res->end |= lba_portbase; - DBG("[%lx/%lx]\n", res->start, res->end); - } else if (res->flags & IORESOURCE_MEM) { - /* - ** Convert PCI (IO_VIEW) addresses to - ** processor (PA_VIEW) addresses - */ - DBG("lba_fixup_bus() MMIO [%lx/%lx] -> ", - res->start, res->end); - res->start = PCI_HOST_ADDR(HBA_DATA(ldev), res->start); - res->end = PCI_HOST_ADDR(HBA_DATA(ldev), res->end); - DBG("[%lx/%lx]\n", res->start, res->end); - } else { - DBG("lba_fixup_bus() WTF? 0x%lx [%lx/%lx] XXX", - res->flags, res->start, res->end); - } - /* ** FIXME: this will result in whinging for devices ** that share expansion ROMs (think quad tulip), but @@ -1514,11 +1492,14 @@ lba_driver_probe(struct parisc_device *dev) lba_dev->hba.lmmio_space.flags = 0; } - pci_add_resource(&resources, &lba_dev->hba.io_space); + pci_add_resource_offset(&resources, &lba_dev->hba.io_space, + HBA_PORT_BASE(lba_dev->hba.hba_num)); if (lba_dev->hba.elmmio_space.start) - pci_add_resource(&resources, &lba_dev->hba.elmmio_space); + pci_add_resource_offset(&resources, &lba_dev->hba.elmmio_space, + lba_dev->hba.lmmio_space_offset); if (lba_dev->hba.lmmio_space.flags) - pci_add_resource(&resources, &lba_dev->hba.lmmio_space); + pci_add_resource_offset(&resources, &lba_dev->hba.lmmio_space, + lba_dev->hba.lmmio_space_offset); if (lba_dev->hba.gmmio_space.flags) pci_add_resource(&resources, &lba_dev->hba.gmmio_space); diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 37856f7c7781..848bfb84c04c 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -31,6 +31,19 @@ config PCI_DEBUG When in doubt, say N. +config PCI_REALLOC_ENABLE_AUTO + bool "Enable PCI resource re-allocation detection" + depends on PCI + help + Say Y here if you want the PCI core to detect if PCI resource + re-allocation needs to be enabled. You can always use pci=realloc=on + or pci=realloc=off to override it. Note this feature is a no-op + unless PCI_IOV support is also enabled; in that case it will + automatically re-allocate PCI resources if SR-IOV BARs have not + been allocated by the BIOS. + + When in doubt, say N. + config PCI_STUB tristate "PCI Stub driver" depends on PCI diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index 398f5d859791..4ce5ef2f2826 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -18,28 +18,36 @@ #include "pci.h" -void pci_add_resource(struct list_head *resources, struct resource *res) +void pci_add_resource_offset(struct list_head *resources, struct resource *res, + resource_size_t offset) { - struct pci_bus_resource *bus_res; + struct pci_host_bridge_window *window; - bus_res = kzalloc(sizeof(struct pci_bus_resource), GFP_KERNEL); - if (!bus_res) { - printk(KERN_ERR "PCI: can't add bus resource %pR\n", res); + window = kzalloc(sizeof(struct pci_host_bridge_window), GFP_KERNEL); + if (!window) { + printk(KERN_ERR "PCI: can't add host bridge window %pR\n", res); return; } - bus_res->res = res; - list_add_tail(&bus_res->list, resources); + window->res = res; + window->offset = offset; + list_add_tail(&window->list, resources); +} +EXPORT_SYMBOL(pci_add_resource_offset); + +void pci_add_resource(struct list_head *resources, struct resource *res) +{ + pci_add_resource_offset(resources, res, 0); } EXPORT_SYMBOL(pci_add_resource); void pci_free_resource_list(struct list_head *resources) { - struct pci_bus_resource *bus_res, *tmp; + struct pci_host_bridge_window *window, *tmp; - list_for_each_entry_safe(bus_res, tmp, resources, list) { - list_del(&bus_res->list); - kfree(bus_res); + list_for_each_entry_safe(window, tmp, resources, list) { + list_del(&window->list); + kfree(window); } } EXPORT_SYMBOL(pci_free_resource_list); diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index 9ddf69e3bbef..806c44fa645a 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -800,20 +800,10 @@ static int __ref enable_device(struct acpiphp_slot *slot) if (slot->flags & SLOT_ENABLED) goto err_exit; - /* sanity check: dev should be NULL when hot-plugged in */ - dev = pci_get_slot(bus, PCI_DEVFN(slot->device, 0)); - if (dev) { - /* This case shouldn't happen */ - err("pci_dev structure already exists.\n"); - pci_dev_put(dev); - retval = -1; - goto err_exit; - } - num = pci_scan_slot(bus, PCI_DEVFN(slot->device, 0)); if (num == 0) { - err("No new device found\n"); - retval = -1; + /* Maybe only part of funcs are added. */ + dbg("No new device found\n"); goto err_exit; } @@ -848,11 +838,16 @@ static int __ref enable_device(struct acpiphp_slot *slot) pci_bus_add_devices(bus); + slot->flags |= SLOT_ENABLED; list_for_each_entry(func, &slot->funcs, sibling) { dev = pci_get_slot(bus, PCI_DEVFN(slot->device, func->function)); - if (!dev) + if (!dev) { + /* Do not set SLOT_ENABLED flag if some funcs + are not added. */ + slot->flags &= (~SLOT_ENABLED); continue; + } if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE && dev->hdr_type != PCI_HEADER_TYPE_CARDBUS) { @@ -867,7 +862,6 @@ static int __ref enable_device(struct acpiphp_slot *slot) pci_dev_put(dev); } - slot->flags |= SLOT_ENABLED; err_exit: return retval; @@ -892,9 +886,12 @@ static int disable_device(struct acpiphp_slot *slot) { struct acpiphp_func *func; struct pci_dev *pdev; + struct pci_bus *bus = slot->bridge->pci_bus; - /* is this slot already disabled? */ - if (!(slot->flags & SLOT_ENABLED)) + /* The slot will be enabled when func 0 is added, so check + func 0 before disable the slot. */ + pdev = pci_get_slot(bus, PCI_DEVFN(slot->device, 0)); + if (!pdev) goto err_exit; list_for_each_entry(func, &slot->funcs, sibling) { @@ -913,7 +910,7 @@ static int disable_device(struct acpiphp_slot *slot) disable_bridges(pdev->subordinate); pci_disable_device(pdev); } - pci_remove_bus_device(pdev); + __pci_remove_bus_device(pdev); pci_dev_put(pdev); } } @@ -1070,7 +1067,7 @@ static void acpiphp_sanitize_bus(struct pci_bus *bus) res->end) { /* Could not assign a required resources * for this device, remove it */ - pci_remove_bus_device(dev); + pci_stop_and_remove_bus_device(dev); break; } } diff --git a/drivers/pci/hotplug/cpci_hotplug_pci.c b/drivers/pci/hotplug/cpci_hotplug_pci.c index 829c327cfb5e..ae853ccd0cd5 100644 --- a/drivers/pci/hotplug/cpci_hotplug_pci.c +++ b/drivers/pci/hotplug/cpci_hotplug_pci.c @@ -341,7 +341,7 @@ int cpci_unconfigure_slot(struct slot* slot) dev = pci_get_slot(slot->bus, PCI_DEVFN(PCI_SLOT(slot->devfn), i)); if (dev) { - pci_remove_bus_device(dev); + pci_stop_and_remove_bus_device(dev); pci_dev_put(dev); } } diff --git a/drivers/pci/hotplug/cpcihp_generic.c b/drivers/pci/hotplug/cpcihp_generic.c index fb3f84661bdc..81af764c629b 100644 --- a/drivers/pci/hotplug/cpcihp_generic.c +++ b/drivers/pci/hotplug/cpcihp_generic.c @@ -62,7 +62,7 @@ #define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME , ## arg) /* local variables */ -static int debug; +static bool debug; static char *bridge; static u8 bridge_busnr; static u8 bridge_slot; diff --git a/drivers/pci/hotplug/cpqphp_pci.c b/drivers/pci/hotplug/cpqphp_pci.c index 6173b9a4544e..1c8494021a42 100644 --- a/drivers/pci/hotplug/cpqphp_pci.c +++ b/drivers/pci/hotplug/cpqphp_pci.c @@ -127,7 +127,7 @@ int cpqhp_unconfigure_device(struct pci_func* func) struct pci_dev* temp = pci_get_bus_and_slot(func->bus, PCI_DEVFN(func->device, j)); if (temp) { pci_dev_put(temp); - pci_remove_bus_device(temp); + pci_stop_and_remove_bus_device(temp); } } return 0; diff --git a/drivers/pci/hotplug/fakephp.c b/drivers/pci/hotplug/fakephp.c index 17d10e2e8fb6..a019c9a712be 100644 --- a/drivers/pci/hotplug/fakephp.c +++ b/drivers/pci/hotplug/fakephp.c @@ -40,7 +40,7 @@ static ssize_t legacy_show(struct kobject *kobj, struct attribute *attr, static void remove_callback(void *data) { - pci_remove_bus_device((struct pci_dev *)data); + pci_stop_and_remove_bus_device((struct pci_dev *)data); } static ssize_t legacy_store(struct kobject *kobj, struct attribute *attr, diff --git a/drivers/pci/hotplug/ibmphp_core.c b/drivers/pci/hotplug/ibmphp_core.c index 5506e0e8fbc0..4fda7e6a86a7 100644 --- a/drivers/pci/hotplug/ibmphp_core.c +++ b/drivers/pci/hotplug/ibmphp_core.c @@ -721,7 +721,7 @@ static void ibm_unconfigure_device(struct pci_func *func) for (j = 0; j < 0x08; j++) { temp = pci_get_bus_and_slot(func->busno, (func->device << 3) | j); if (temp) { - pci_remove_bus_device(temp); + pci_stop_and_remove_bus_device(temp); pci_dev_put(temp); } } diff --git a/drivers/pci/hotplug/ibmphp_ebda.c b/drivers/pci/hotplug/ibmphp_ebda.c index 2850e64dedae..714ca5c4ed50 100644 --- a/drivers/pci/hotplug/ibmphp_ebda.c +++ b/drivers/pci/hotplug/ibmphp_ebda.c @@ -368,8 +368,10 @@ int __init ibmphp_access_ebda (void) debug ("rio blk id: %x\n", blk_id); rio_table_ptr = kzalloc(sizeof(struct rio_table_hdr), GFP_KERNEL); - if (!rio_table_ptr) - return -ENOMEM; + if (!rio_table_ptr) { + rc = -ENOMEM; + goto out; + } rio_table_ptr->ver_num = readb (io_mem + offset); rio_table_ptr->scal_count = readb (io_mem + offset + 1); rio_table_ptr->riodev_count = readb (io_mem + offset + 2); diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index bcdbb1643621..a960faec1021 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -241,34 +241,79 @@ static int pcie_write_cmd(struct controller *ctrl, u16 cmd, u16 mask) return retval; } -static inline int check_link_active(struct controller *ctrl) +static bool check_link_active(struct controller *ctrl) { - u16 link_status; + bool ret = false; + u16 lnk_status; - if (pciehp_readw(ctrl, PCI_EXP_LNKSTA, &link_status)) - return 0; - return !!(link_status & PCI_EXP_LNKSTA_DLLLA); + if (pciehp_readw(ctrl, PCI_EXP_LNKSTA, &lnk_status)) + return ret; + + ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA); + + if (ret) + ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); + + return ret; } -static void pcie_wait_link_active(struct controller *ctrl) +static void __pcie_wait_link_active(struct controller *ctrl, bool active) { int timeout = 1000; - if (check_link_active(ctrl)) + if (check_link_active(ctrl) == active) return; while (timeout > 0) { msleep(10); timeout -= 10; - if (check_link_active(ctrl)) + if (check_link_active(ctrl) == active) return; } - ctrl_dbg(ctrl, "Data Link Layer Link Active not set in 1000 msec\n"); + ctrl_dbg(ctrl, "Data Link Layer Link Active not %s in 1000 msec\n", + active ? "set" : "cleared"); +} + +static void pcie_wait_link_active(struct controller *ctrl) +{ + __pcie_wait_link_active(ctrl, true); +} + +static void pcie_wait_link_not_active(struct controller *ctrl) +{ + __pcie_wait_link_active(ctrl, false); +} + +static bool pci_bus_check_dev(struct pci_bus *bus, int devfn) +{ + u32 l; + int count = 0; + int delay = 1000, step = 20; + bool found = false; + + do { + found = pci_bus_read_dev_vendor_id(bus, devfn, &l, 0); + count++; + + if (found) + break; + + msleep(step); + delay -= step; + } while (delay > 0); + + if (count > 1 && pciehp_debug) + printk(KERN_DEBUG "pci %04x:%02x:%02x.%d id reading try %d times with interval %d ms to get %08x\n", + pci_domain_nr(bus), bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn), count, step, l); + + return found; } int pciehp_check_link_status(struct controller *ctrl) { u16 lnk_status; int retval = 0; + bool found = false; /* * Data Link Layer Link Active Reporting must be capable for @@ -280,13 +325,10 @@ int pciehp_check_link_status(struct controller *ctrl) else msleep(1000); - /* - * Need to wait for 1000 ms after Data Link Layer Link Active - * (DLLLA) bit reads 1b before sending configuration request. - * We need it before checking Link Training (LT) bit becuase - * LT is still set even after DLLLA bit is set on some platform. - */ - msleep(1000); + /* wait 100ms before read pci conf, and try in 1s */ + msleep(100); + found = pci_bus_check_dev(ctrl->pcie->port->subordinate, + PCI_DEVFN(0, 0)); retval = pciehp_readw(ctrl, PCI_EXP_LNKSTA, &lnk_status); if (retval) { @@ -302,19 +344,50 @@ int pciehp_check_link_status(struct controller *ctrl) return retval; } - /* - * If the port supports Link speeds greater than 5.0 GT/s, we - * must wait for 100 ms after Link training completes before - * sending configuration request. - */ - if (ctrl->pcie->port->subordinate->max_bus_speed > PCIE_SPEED_5_0GT) - msleep(100); - pcie_update_link_speed(ctrl->pcie->port->subordinate, lnk_status); + if (!found && !retval) + retval = -1; + return retval; } +static int __pciehp_link_set(struct controller *ctrl, bool enable) +{ + u16 lnk_ctrl; + int retval = 0; + + retval = pciehp_readw(ctrl, PCI_EXP_LNKCTL, &lnk_ctrl); + if (retval) { + ctrl_err(ctrl, "Cannot read LNKCTRL register\n"); + return retval; + } + + if (enable) + lnk_ctrl &= ~PCI_EXP_LNKCTL_LD; + else + lnk_ctrl |= PCI_EXP_LNKCTL_LD; + + retval = pciehp_writew(ctrl, PCI_EXP_LNKCTL, lnk_ctrl); + if (retval) { + ctrl_err(ctrl, "Cannot write LNKCTRL register\n"); + return retval; + } + ctrl_dbg(ctrl, "%s: lnk_ctrl = %x\n", __func__, lnk_ctrl); + + return retval; +} + +static int pciehp_link_enable(struct controller *ctrl) +{ + return __pciehp_link_set(ctrl, true); +} + +static int pciehp_link_disable(struct controller *ctrl) +{ + return __pciehp_link_set(ctrl, false); +} + int pciehp_get_attention_status(struct slot *slot, u8 *status) { struct controller *ctrl = slot->ctrl; @@ -533,6 +606,10 @@ int pciehp_power_on_slot(struct slot * slot) ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_cmd); + retval = pciehp_link_enable(ctrl); + if (retval) + ctrl_err(ctrl, "%s: Can not enable the link!\n", __func__); + return retval; } @@ -543,6 +620,14 @@ int pciehp_power_off_slot(struct slot * slot) u16 cmd_mask; int retval; + /* Disable the link at first */ + pciehp_link_disable(ctrl); + /* wait the link is down */ + if (ctrl->link_active_reporting) + pcie_wait_link_not_active(ctrl); + else + msleep(1000); + slot_cmd = POWER_OFF; cmd_mask = PCI_EXP_SLTCTL_PCC; retval = pcie_write_cmd(ctrl, slot_cmd, cmd_mask); diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c index a4031dfe938e..47d9dc06b109 100644 --- a/drivers/pci/hotplug/pciehp_pci.c +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -141,7 +141,7 @@ int pciehp_unconfigure_device(struct slot *p_slot) break; } } - pci_remove_bus_device(temp); + pci_stop_and_remove_bus_device(temp); /* * Ensure that no new Requests will be generated from * the device. diff --git a/drivers/pci/hotplug/rpadlpar_core.c b/drivers/pci/hotplug/rpadlpar_core.c index c56a9413e1af..1e117c2a3cad 100644 --- a/drivers/pci/hotplug/rpadlpar_core.c +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -389,7 +389,7 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn) BUG_ON(!bus->self); pr_debug("PCI: Now removing bridge device %s\n", pci_name(bus->self)); eeh_remove_bus_device(bus->self); - pci_remove_bus_device(bus->self); + pci_stop_and_remove_bus_device(bus->self); return 0; } diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c index 72d507b6a2aa..de573113c102 100644 --- a/drivers/pci/hotplug/sgi_hotplug.c +++ b/drivers/pci/hotplug/sgi_hotplug.c @@ -554,7 +554,7 @@ static int disable_slot(struct hotplug_slot *bss_hotplug_slot) PCI_FUNC(func))); if (dev) { sn_bus_free_data(dev); - pci_remove_bus_device(dev); + pci_stop_and_remove_bus_device(dev); pci_dev_put(dev); } } diff --git a/drivers/pci/hotplug/shpchp_pci.c b/drivers/pci/hotplug/shpchp_pci.c index a2ccfcd3c298..df7e4bfadae3 100644 --- a/drivers/pci/hotplug/shpchp_pci.c +++ b/drivers/pci/hotplug/shpchp_pci.c @@ -124,7 +124,7 @@ int shpchp_unconfigure_device(struct slot *p_slot) break; } } - pci_remove_bus_device(temp); + pci_stop_and_remove_bus_device(temp); pci_dev_put(temp); } return rc; diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 0dab5ecf61bb..6554e1a0f634 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -142,7 +142,7 @@ failed2: failed1: pci_dev_put(dev); mutex_lock(&iov->dev->sriov->lock); - pci_remove_bus_device(virtfn); + pci_stop_and_remove_bus_device(virtfn); virtfn_remove_bus(dev->bus, virtfn_bus(dev, id)); mutex_unlock(&iov->dev->sriov->lock); @@ -173,10 +173,16 @@ static void virtfn_remove(struct pci_dev *dev, int id, int reset) sprintf(buf, "virtfn%u", id); sysfs_remove_link(&dev->dev.kobj, buf); - sysfs_remove_link(&virtfn->dev.kobj, "physfn"); + /* + * pci_stop_dev() could have been called for this virtfn already, + * so the directory for the virtfn may have been removed before. + * Double check to avoid spurious sysfs warnings. + */ + if (virtfn->dev.kobj.sd) + sysfs_remove_link(&virtfn->dev.kobj, "physfn"); mutex_lock(&iov->dev->sriov->lock); - pci_remove_bus_device(virtfn); + pci_stop_and_remove_bus_device(virtfn); virtfn_remove_bus(dev->bus, virtfn_bus(dev, id)); mutex_unlock(&iov->dev->sriov->lock); diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 8d9616b821ca..6b54b23b990b 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -419,6 +419,16 @@ static void pci_device_shutdown(struct device *dev) drv->shutdown(pci_dev); pci_msi_shutdown(pci_dev); pci_msix_shutdown(pci_dev); + + /* + * Devices may be enabled to wake up by runtime PM, but they need not + * be supposed to wake up the system from its "power off" state (e.g. + * ACPI S5). Therefore disable wakeup for all devices that aren't + * supposed to wake up the system at this point. The state argument + * will be ignored by pci_enable_wake(). + */ + if (!device_may_wakeup(dev)) + pci_enable_wake(pci_dev, PCI_UNKNOWN, false); } #ifdef CONFIG_PM diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index a3cd8cad532a..a55e248618cd 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -330,7 +330,7 @@ static void remove_callback(struct device *dev) struct pci_dev *pdev = to_pci_dev(dev); mutex_lock(&pci_remove_rescan_mutex); - pci_remove_bus_device(pdev); + pci_stop_and_remove_bus_device(pdev); mutex_unlock(&pci_remove_rescan_mutex); } @@ -366,7 +366,10 @@ dev_bus_rescan_store(struct device *dev, struct device_attribute *attr, if (val) { mutex_lock(&pci_remove_rescan_mutex); - pci_rescan_bus(bus); + if (!pci_is_root_bus(bus) && list_empty(&bus->devices)) + pci_rescan_bus_bridge_resize(bus->self); + else + pci_rescan_bus(bus); mutex_unlock(&pci_remove_rescan_mutex); } return count; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 053670e09e2b..815674415267 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -94,6 +94,9 @@ u8 pci_cache_line_size; */ unsigned int pcibios_max_latency = 255; +/* If set, the PCIe ARI capability will not be used. */ +static bool pcie_ari_disabled; + /** * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children * @bus: pointer to PCI bus structure to search @@ -825,6 +828,19 @@ EXPORT_SYMBOL(pci_choose_state); #define pcie_cap_has_sltctl2(type, flags) \ ((flags & PCI_EXP_FLAGS_VERS) > 1) +static struct pci_cap_saved_state *pci_find_saved_cap( + struct pci_dev *pci_dev, char cap) +{ + struct pci_cap_saved_state *tmp; + struct hlist_node *pos; + + hlist_for_each_entry(tmp, pos, &pci_dev->saved_cap_space, next) { + if (tmp->cap.cap_nr == cap) + return tmp; + } + return NULL; +} + static int pci_save_pcie_state(struct pci_dev *dev) { int pos, i = 0; @@ -959,6 +975,7 @@ void pci_restore_state(struct pci_dev *dev) { int i; u32 val; + int tries; if (!dev->state_saved) return; @@ -973,12 +990,16 @@ void pci_restore_state(struct pci_dev *dev) */ for (i = 15; i >= 0; i--) { pci_read_config_dword(dev, i * 4, &val); - if (val != dev->saved_config_space[i]) { + tries = 10; + while (tries && val != dev->saved_config_space[i]) { dev_dbg(&dev->dev, "restoring config " "space at offset %#x (was %#x, writing %#x)\n", i, val, (int)dev->saved_config_space[i]); pci_write_config_dword(dev,i * 4, dev->saved_config_space[i]); + pci_read_config_dword(dev, i * 4, &val); + mdelay(10); + tries--; } } pci_restore_pcix_state(dev); @@ -1864,6 +1885,12 @@ void platform_pci_wakeup_init(struct pci_dev *dev) platform_pci_sleep_wake(dev, false); } +static void pci_add_saved_cap(struct pci_dev *pci_dev, + struct pci_cap_saved_state *new_cap) +{ + hlist_add_head(&new_cap->next, &pci_dev->saved_cap_space); +} + /** * pci_add_save_buffer - allocate buffer for saving given capability registers * @dev: the PCI device @@ -1911,6 +1938,15 @@ void pci_allocate_cap_save_buffers(struct pci_dev *dev) "unable to preallocate PCI-X save buffer\n"); } +void pci_free_cap_save_buffers(struct pci_dev *dev) +{ + struct pci_cap_saved_state *tmp; + struct hlist_node *pos, *n; + + hlist_for_each_entry_safe(tmp, pos, n, &dev->saved_cap_space, next) + kfree(tmp); +} + /** * pci_enable_ari - enable ARI forwarding if hardware support it * @dev: the PCI device @@ -1922,7 +1958,7 @@ void pci_enable_ari(struct pci_dev *dev) u16 flags, ctrl; struct pci_dev *bridge; - if (!pci_is_pcie(dev) || dev->devfn) + if (pcie_ari_disabled || !pci_is_pcie(dev) || dev->devfn) return; pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI); @@ -3661,6 +3697,68 @@ int pci_is_reassigndev(struct pci_dev *dev) return (pci_specified_resource_alignment(dev) != 0); } +/* + * This function disables memory decoding and releases memory resources + * of the device specified by kernel's boot parameter 'pci=resource_alignment='. + * It also rounds up size to specified alignment. + * Later on, the kernel will assign page-aligned memory resource back + * to the device. + */ +void pci_reassigndev_resource_alignment(struct pci_dev *dev) +{ + int i; + struct resource *r; + resource_size_t align, size; + u16 command; + + if (!pci_is_reassigndev(dev)) + return; + + if (dev->hdr_type == PCI_HEADER_TYPE_NORMAL && + (dev->class >> 8) == PCI_CLASS_BRIDGE_HOST) { + dev_warn(&dev->dev, + "Can't reassign resources to host bridge.\n"); + return; + } + + dev_info(&dev->dev, + "Disabling memory decoding and releasing memory resources.\n"); + pci_read_config_word(dev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(dev, PCI_COMMAND, command); + + align = pci_specified_resource_alignment(dev); + for (i = 0; i < PCI_BRIDGE_RESOURCES; i++) { + r = &dev->resource[i]; + if (!(r->flags & IORESOURCE_MEM)) + continue; + size = resource_size(r); + if (size < align) { + size = align; + dev_info(&dev->dev, + "Rounding up size of resource #%d to %#llx.\n", + i, (unsigned long long)size); + } + r->end = size - 1; + r->start = 0; + } + /* Need to disable bridge's resource window, + * to enable the kernel to reassign new resource + * window later on. + */ + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE && + (dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { + for (i = PCI_BRIDGE_RESOURCES; i < PCI_NUM_RESOURCES; i++) { + r = &dev->resource[i]; + if (!(r->flags & IORESOURCE_MEM)) + continue; + r->end = resource_size(r) - 1; + r->start = 0; + } + pci_disable_bridge_window(dev); + } +} + ssize_t pci_set_resource_alignment_param(const char *buf, size_t count) { if (count > RESOURCE_ALIGNMENT_PARAM_SIZE - 1) @@ -3739,10 +3837,14 @@ static int __init pci_setup(char *str) pci_no_msi(); } else if (!strcmp(str, "noaer")) { pci_no_aer(); + } else if (!strncmp(str, "realloc=", 8)) { + pci_realloc_get_opt(str + 8); } else if (!strncmp(str, "realloc", 7)) { - pci_realloc(); + pci_realloc_get_opt("on"); } else if (!strcmp(str, "nodomains")) { pci_no_domains(); + } else if (!strncmp(str, "noari", 5)) { + pcie_ari_disabled = true; } else if (!strncmp(str, "cbiosize=", 9)) { pci_cardbus_io_size = memparse(str + 9, &str); } else if (!strncmp(str, "cbmemsize=", 10)) { diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 1009a5e88e53..e4943479b234 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -73,6 +73,7 @@ extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign); extern void pci_pm_init(struct pci_dev *dev); extern void platform_pci_wakeup_init(struct pci_dev *dev); extern void pci_allocate_cap_save_buffers(struct pci_dev *dev); +void pci_free_cap_save_buffers(struct pci_dev *dev); static inline void pci_wakeup_event(struct pci_dev *dev) { @@ -148,7 +149,7 @@ static inline void pci_no_msi(void) { } static inline void pci_msi_init_pci_dev(struct pci_dev *dev) { } #endif -extern void pci_realloc(void); +void pci_realloc_get_opt(char *); static inline int pci_no_d1d2(struct pci_dev *dev) { @@ -207,6 +208,8 @@ enum pci_bar_type { pci_bar_mem64, /* A 64-bit memory BAR */ }; +bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl, + int crs_timeout); extern int pci_setup_device(struct pci_dev *dev); extern int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, struct resource *res, unsigned int reg); @@ -225,11 +228,8 @@ static inline int pci_ari_enabled(struct pci_bus *bus) return bus->self && bus->self->ari_enabled; } -#ifdef CONFIG_PCI_QUIRKS -extern int pci_is_reassigndev(struct pci_dev *dev); -resource_size_t pci_specified_resource_alignment(struct pci_dev *dev); +void pci_reassigndev_resource_alignment(struct pci_dev *dev); extern void pci_disable_bridge_window(struct pci_dev *dev); -#endif /* Single Root I/O Virtualization */ struct pci_sriov { diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index 72962cc92e0a..6c8bc5809787 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -55,6 +55,31 @@ config PCIEASPM_DEBUG This enables PCI Express ASPM debug support. It will add per-device interface to control ASPM. +choice + prompt "Default ASPM policy" + default PCIEASPM_DEFAULT + depends on PCIEASPM + +config PCIEASPM_DEFAULT + bool "BIOS default" + depends on PCIEASPM + help + Use the BIOS defaults for PCI Express ASPM. + +config PCIEASPM_POWERSAVE + bool "Powersave" + depends on PCIEASPM + help + Enable PCI Express ASPM L0s and L1 where possible, even if the + BIOS did not. + +config PCIEASPM_PERFORMANCE + bool "Performance" + depends on PCIEASPM + help + Disable PCI Express ASPM L0s and L1, even if the BIOS enabled them. +endchoice + config PCIE_PME def_bool y depends on PCIEPORTBUS && PM_RUNTIME && EXPERIMENTAL && ACPI diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 24f049e73952..4bdef24cd412 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -76,7 +76,15 @@ static LIST_HEAD(link_list); #define POLICY_DEFAULT 0 /* BIOS default setting */ #define POLICY_PERFORMANCE 1 /* high performance */ #define POLICY_POWERSAVE 2 /* high power saving */ + +#ifdef CONFIG_PCIEASPM_PERFORMANCE +static int aspm_policy = POLICY_PERFORMANCE; +#elif defined CONFIG_PCIEASPM_POWERSAVE +static int aspm_policy = POLICY_POWERSAVE; +#else static int aspm_policy; +#endif + static const char *policy_str[] = { [POLICY_DEFAULT] = "default", [POLICY_PERFORMANCE] = "performance", diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index bd00a01aef14..eea2ca2375e6 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -34,6 +34,18 @@ struct pci_dev; extern void pcie_clear_root_pme_status(struct pci_dev *dev); +#ifdef CONFIG_HOTPLUG_PCI_PCIE +extern bool pciehp_msi_disabled; + +static inline bool pciehp_no_msi(void) +{ + return pciehp_msi_disabled; +} + +#else /* !CONFIG_HOTPLUG_PCI_PCIE */ +static inline bool pciehp_no_msi(void) { return false; } +#endif /* !CONFIG_HOTPLUG_PCI_PCIE */ + #ifdef CONFIG_PCIE_PME extern bool pcie_pme_msi_disabled; diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 595654a1a6a6..2f589a54f9bd 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -19,6 +19,17 @@ #include "../pci.h" #include "portdrv.h" +bool pciehp_msi_disabled; + +static int __init pciehp_setup(char *str) +{ + if (!strncmp(str, "nomsi", 5)) + pciehp_msi_disabled = true; + + return 1; +} +__setup("pcie_hp=", pciehp_setup); + /** * release_pcie_device - free PCI Express port service device structure * @dev: Port service device to release @@ -189,8 +200,9 @@ static int init_service_irqs(struct pci_dev *dev, int *irqs, int mask) { int i, irq = -1; - /* We have to use INTx if MSI cannot be used for PCIe PME. */ - if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi()) { + /* We have to use INTx if MSI cannot be used for PCIe PME or pciehp. */ + if (((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi()) || + ((mask & PCIE_PORT_SERVICE_HP) && pciehp_no_msi())) { if (dev->pin) irq = dev->irq; goto no_msi; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 71eac9cd724d..5e1ca3c58a7d 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -15,6 +15,8 @@ #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ #define CARDBUS_RESERVE_BUSNR 3 +static LIST_HEAD(pci_host_bridges); + /* Ugh. Need to stop exporting this to modules. */ LIST_HEAD(pci_root_buses); EXPORT_SYMBOL(pci_root_buses); @@ -42,6 +44,82 @@ int no_pci_devices(void) } EXPORT_SYMBOL(no_pci_devices); +static struct pci_host_bridge *pci_host_bridge(struct pci_dev *dev) +{ + struct pci_bus *bus; + struct pci_host_bridge *bridge; + + bus = dev->bus; + while (bus->parent) + bus = bus->parent; + + list_for_each_entry(bridge, &pci_host_bridges, list) { + if (bridge->bus == bus) + return bridge; + } + + return NULL; +} + +static bool resource_contains(struct resource *res1, struct resource *res2) +{ + return res1->start <= res2->start && res1->end >= res2->end; +} + +void pcibios_resource_to_bus(struct pci_dev *dev, struct pci_bus_region *region, + struct resource *res) +{ + struct pci_host_bridge *bridge = pci_host_bridge(dev); + struct pci_host_bridge_window *window; + resource_size_t offset = 0; + + list_for_each_entry(window, &bridge->windows, list) { + if (resource_type(res) != resource_type(window->res)) + continue; + + if (resource_contains(window->res, res)) { + offset = window->offset; + break; + } + } + + region->start = res->start - offset; + region->end = res->end - offset; +} +EXPORT_SYMBOL(pcibios_resource_to_bus); + +static bool region_contains(struct pci_bus_region *region1, + struct pci_bus_region *region2) +{ + return region1->start <= region2->start && region1->end >= region2->end; +} + +void pcibios_bus_to_resource(struct pci_dev *dev, struct resource *res, + struct pci_bus_region *region) +{ + struct pci_host_bridge *bridge = pci_host_bridge(dev); + struct pci_host_bridge_window *window; + struct pci_bus_region bus_region; + resource_size_t offset = 0; + + list_for_each_entry(window, &bridge->windows, list) { + if (resource_type(res) != resource_type(window->res)) + continue; + + bus_region.start = window->res->start - window->offset; + bus_region.end = window->res->end - window->offset; + + if (region_contains(&bus_region, region)) { + offset = window->offset; + break; + } + } + + res->start = region->start + offset; + res->end = region->end + offset; +} +EXPORT_SYMBOL(pcibios_bus_to_resource); + /* * PCI Bus Class */ @@ -135,6 +213,7 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, { u32 l, sz, mask; u16 orig_cmd; + struct pci_bus_region region; mask = type ? PCI_ROM_ADDRESS_MASK : ~0; @@ -214,11 +293,13 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, /* Address above 32-bit boundary; disable the BAR */ pci_write_config_dword(dev, pos, 0); pci_write_config_dword(dev, pos + 4, 0); - res->start = 0; - res->end = sz64; + region.start = 0; + region.end = sz64; + pcibios_bus_to_resource(dev, res, ®ion); } else { - res->start = l64; - res->end = l64 + sz64; + region.start = l64; + region.end = l64 + sz64; + pcibios_bus_to_resource(dev, res, ®ion); dev_printk(KERN_DEBUG, &dev->dev, "reg %x: %pR\n", pos, res); } @@ -228,8 +309,9 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, if (!sz) goto fail; - res->start = l; - res->end = l + sz; + region.start = l; + region.end = l + sz; + pcibios_bus_to_resource(dev, res, ®ion); dev_printk(KERN_DEBUG, &dev->dev, "reg %x: %pR\n", pos, res); } @@ -266,7 +348,8 @@ static void __devinit pci_read_bridge_io(struct pci_bus *child) struct pci_dev *dev = child->self; u8 io_base_lo, io_limit_lo; unsigned long base, limit; - struct resource *res; + struct pci_bus_region region; + struct resource *res, res2; res = child->resource[0]; pci_read_config_byte(dev, PCI_IO_BASE, &io_base_lo); @@ -284,10 +367,14 @@ static void __devinit pci_read_bridge_io(struct pci_bus *child) if (base && base <= limit) { res->flags = (io_base_lo & PCI_IO_RANGE_TYPE_MASK) | IORESOURCE_IO; + res2.flags = res->flags; + region.start = base; + region.end = limit + 0xfff; + pcibios_bus_to_resource(dev, &res2, ®ion); if (!res->start) - res->start = base; + res->start = res2.start; if (!res->end) - res->end = limit + 0xfff; + res->end = res2.end; dev_printk(KERN_DEBUG, &dev->dev, " bridge window %pR\n", res); } } @@ -297,6 +384,7 @@ static void __devinit pci_read_bridge_mmio(struct pci_bus *child) struct pci_dev *dev = child->self; u16 mem_base_lo, mem_limit_lo; unsigned long base, limit; + struct pci_bus_region region; struct resource *res; res = child->resource[1]; @@ -306,8 +394,9 @@ static void __devinit pci_read_bridge_mmio(struct pci_bus *child) limit = (mem_limit_lo & PCI_MEMORY_RANGE_MASK) << 16; if (base && base <= limit) { res->flags = (mem_base_lo & PCI_MEMORY_RANGE_TYPE_MASK) | IORESOURCE_MEM; - res->start = base; - res->end = limit + 0xfffff; + region.start = base; + region.end = limit + 0xfffff; + pcibios_bus_to_resource(dev, res, ®ion); dev_printk(KERN_DEBUG, &dev->dev, " bridge window %pR\n", res); } } @@ -317,6 +406,7 @@ static void __devinit pci_read_bridge_mmio_pref(struct pci_bus *child) struct pci_dev *dev = child->self; u16 mem_base_lo, mem_limit_lo; unsigned long base, limit; + struct pci_bus_region region; struct resource *res; res = child->resource[2]; @@ -353,8 +443,9 @@ static void __devinit pci_read_bridge_mmio_pref(struct pci_bus *child) IORESOURCE_MEM | IORESOURCE_PREFETCH; if (res->flags & PCI_PREF_RANGE_TYPE_64) res->flags |= IORESOURCE_MEM_64; - res->start = base; - res->end = limit + 0xfffff; + region.start = base; + region.end = limit + 0xfffff; + pcibios_bus_to_resource(dev, res, ®ion); dev_printk(KERN_DEBUG, &dev->dev, " bridge window %pR\n", res); } } @@ -900,6 +991,8 @@ int pci_setup_device(struct pci_dev *dev) u8 hdr_type; struct pci_slot *slot; int pos = 0; + struct pci_bus_region region; + struct resource *res; if (pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type)) return -EIO; @@ -926,12 +1019,10 @@ int pci_setup_device(struct pci_dev *dev) pci_read_config_dword(dev, PCI_CLASS_REVISION, &class); dev->revision = class & 0xff; - class >>= 8; /* upper 3 bytes */ - dev->class = class; - class >>= 8; + dev->class = class >> 8; /* upper 3 bytes */ - dev_printk(KERN_DEBUG, &dev->dev, "[%04x:%04x] type %d class %#08x\n", - dev->vendor, dev->device, dev->hdr_type, class); + dev_printk(KERN_DEBUG, &dev->dev, "[%04x:%04x] type %02x class %#08x\n", + dev->vendor, dev->device, dev->hdr_type, dev->class); /* need to have dev->class ready */ dev->cfg_size = pci_cfg_space_size(dev); @@ -963,20 +1054,28 @@ int pci_setup_device(struct pci_dev *dev) u8 progif; pci_read_config_byte(dev, PCI_CLASS_PROG, &progif); if ((progif & 1) == 0) { - dev->resource[0].start = 0x1F0; - dev->resource[0].end = 0x1F7; - dev->resource[0].flags = LEGACY_IO_RESOURCE; - dev->resource[1].start = 0x3F6; - dev->resource[1].end = 0x3F6; - dev->resource[1].flags = LEGACY_IO_RESOURCE; + region.start = 0x1F0; + region.end = 0x1F7; + res = &dev->resource[0]; + res->flags = LEGACY_IO_RESOURCE; + pcibios_bus_to_resource(dev, res, ®ion); + region.start = 0x3F6; + region.end = 0x3F6; + res = &dev->resource[1]; + res->flags = LEGACY_IO_RESOURCE; + pcibios_bus_to_resource(dev, res, ®ion); } if ((progif & 4) == 0) { - dev->resource[2].start = 0x170; - dev->resource[2].end = 0x177; - dev->resource[2].flags = LEGACY_IO_RESOURCE; - dev->resource[3].start = 0x376; - dev->resource[3].end = 0x376; - dev->resource[3].flags = LEGACY_IO_RESOURCE; + region.start = 0x170; + region.end = 0x177; + res = &dev->resource[2]; + res->flags = LEGACY_IO_RESOURCE; + pcibios_bus_to_resource(dev, res, ®ion); + region.start = 0x376; + region.end = 0x376; + res = &dev->resource[3]; + res->flags = LEGACY_IO_RESOURCE; + pcibios_bus_to_resource(dev, res, ®ion); } } break; @@ -1013,8 +1112,8 @@ int pci_setup_device(struct pci_dev *dev) return -EIO; bad: - dev_err(&dev->dev, "ignoring class %02x (doesn't match header " - "type %02x)\n", class, dev->hdr_type); + dev_err(&dev->dev, "ignoring class %#08x (doesn't match header " + "type %02x)\n", dev->class, dev->hdr_type); dev->class = PCI_CLASS_NOT_DEFINED; } @@ -1026,6 +1125,7 @@ static void pci_release_capabilities(struct pci_dev *dev) { pci_vpd_release(dev); pci_iov_release(dev); + pci_free_cap_save_buffers(dev); } /** @@ -1118,40 +1218,54 @@ struct pci_dev *alloc_pci_dev(void) } EXPORT_SYMBOL(alloc_pci_dev); -/* - * Read the config data for a PCI device, sanity-check it - * and fill in the dev structure... - */ -static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) +bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, + int crs_timeout) { - struct pci_dev *dev; - u32 l; int delay = 1; - if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l)) - return NULL; + if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l)) + return false; /* some broken boards return 0 or ~0 if a slot is empty: */ - if (l == 0xffffffff || l == 0x00000000 || - l == 0x0000ffff || l == 0xffff0000) - return NULL; + if (*l == 0xffffffff || *l == 0x00000000 || + *l == 0x0000ffff || *l == 0xffff0000) + return false; /* Configuration request Retry Status */ - while (l == 0xffff0001) { + while (*l == 0xffff0001) { + if (!crs_timeout) + return false; + msleep(delay); delay *= 2; - if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l)) - return NULL; + if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l)) + return false; /* Card hasn't responded in 60 seconds? Must be stuck. */ - if (delay > 60 * 1000) { + if (delay > crs_timeout) { printk(KERN_WARNING "pci %04x:%02x:%02x.%d: not " "responding\n", pci_domain_nr(bus), bus->number, PCI_SLOT(devfn), PCI_FUNC(devfn)); - return NULL; + return false; } } + return true; +} +EXPORT_SYMBOL(pci_bus_read_dev_vendor_id); + +/* + * Read the config data for a PCI device, sanity-check it + * and fill in the dev structure... + */ +static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) +{ + struct pci_dev *dev; + u32 l; + + if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000)) + return NULL; + dev = alloc_pci_dev(); if (!dev) return NULL; @@ -1212,6 +1326,9 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus) /* Fix up broken headers */ pci_fixup_device(pci_fixup_header, dev); + /* moved out from quirk header fixup code */ + pci_reassigndev_resource_alignment(dev); + /* Clear the state_saved flag. */ dev->state_saved = false; @@ -1530,21 +1647,27 @@ unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus) struct pci_bus *pci_create_root_bus(struct device *parent, int bus, struct pci_ops *ops, void *sysdata, struct list_head *resources) { - int error, i; + int error; + struct pci_host_bridge *bridge; struct pci_bus *b, *b2; struct device *dev; - struct pci_bus_resource *bus_res, *n; + struct pci_host_bridge_window *window, *n; struct resource *res; + resource_size_t offset; + char bus_addr[64]; + char *fmt; + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return NULL; b = pci_alloc_bus(); if (!b) - return NULL; + goto err_bus; dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) { - kfree(b); - return NULL; - } + if (!dev) + goto err_dev; b->sysdata = sysdata; b->ops = ops; @@ -1556,10 +1679,6 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus, goto err_out; } - down_write(&pci_bus_sem); - list_add_tail(&b->node, &pci_root_buses); - up_write(&pci_bus_sem); - dev->parent = parent; dev->release = pci_release_bus_bridge_dev; dev_set_name(dev, "pci%04x:%02x", pci_domain_nr(b), bus); @@ -1585,31 +1704,53 @@ struct pci_bus *pci_create_root_bus(struct device *parent, int bus, b->number = b->secondary = bus; - /* Add initial resources to the bus */ - list_for_each_entry_safe(bus_res, n, resources, list) - list_move_tail(&bus_res->list, &b->resources); + bridge->bus = b; + INIT_LIST_HEAD(&bridge->windows); if (parent) dev_info(parent, "PCI host bridge to bus %s\n", dev_name(&b->dev)); else printk(KERN_INFO "PCI host bridge to bus %s\n", dev_name(&b->dev)); - pci_bus_for_each_resource(b, res, i) { - if (res) - dev_info(&b->dev, "root bus resource %pR\n", res); + /* Add initial resources to the bus */ + list_for_each_entry_safe(window, n, resources, list) { + list_move_tail(&window->list, &bridge->windows); + res = window->res; + offset = window->offset; + pci_bus_add_resource(b, res, 0); + if (offset) { + if (resource_type(res) == IORESOURCE_IO) + fmt = " (bus address [%#06llx-%#06llx])"; + else + fmt = " (bus address [%#010llx-%#010llx])"; + snprintf(bus_addr, sizeof(bus_addr), fmt, + (unsigned long long) (res->start - offset), + (unsigned long long) (res->end - offset)); + } else + bus_addr[0] = '\0'; + dev_info(&b->dev, "root bus resource %pR%s\n", res, bus_addr); } + down_write(&pci_bus_sem); + list_add_tail(&bridge->list, &pci_host_bridges); + list_add_tail(&b->node, &pci_root_buses); + up_write(&pci_bus_sem); + return b; class_dev_reg_err: device_unregister(dev); dev_reg_err: down_write(&pci_bus_sem); + list_del(&bridge->list); list_del(&b->node); up_write(&pci_bus_sem); err_out: kfree(dev); +err_dev: kfree(b); +err_bus: + kfree(bridge); return NULL; } @@ -1667,36 +1808,29 @@ EXPORT_SYMBOL(pci_scan_bus); #ifdef CONFIG_HOTPLUG /** - * pci_rescan_bus - scan a PCI bus for devices. - * @bus: PCI bus to scan + * pci_rescan_bus_bridge_resize - scan a PCI bus for devices. + * @bridge: PCI bridge for the bus to scan * - * Scan a PCI bus and child buses for new devices, adds them, - * and enables them. + * Scan a PCI bus and child buses for new devices, add them, + * and enable them, resizing bridge mmio/io resource if necessary + * and possible. The caller must ensure the child devices are already + * removed for resizing to occur. * * Returns the max number of subordinate bus discovered. */ -unsigned int __ref pci_rescan_bus(struct pci_bus *bus) +unsigned int __ref pci_rescan_bus_bridge_resize(struct pci_dev *bridge) { unsigned int max; - struct pci_dev *dev; + struct pci_bus *bus = bridge->subordinate; max = pci_scan_child_bus(bus); - down_read(&pci_bus_sem); - list_for_each_entry(dev, &bus->devices, bus_list) - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || - dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) - if (dev->subordinate) - pci_bus_size_bridges(dev->subordinate); - up_read(&pci_bus_sem); + pci_assign_unassigned_bridge_resources(bridge); - pci_bus_assign_resources(bus); - pci_enable_bridges(bus); pci_bus_add_devices(bus); return max; } -EXPORT_SYMBOL_GPL(pci_rescan_bus); EXPORT_SYMBOL(pci_add_new_bus); EXPORT_SYMBOL(pci_scan_slot); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index f722c5f6951a..4bf71028556b 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -26,73 +26,12 @@ #include <linux/dmi.h> #include <linux/pci-aspm.h> #include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/ktime.h> #include <asm/dma.h> /* isa_dma_bridge_buggy */ #include "pci.h" /* - * This quirk function disables memory decoding and releases memory resources - * of the device specified by kernel's boot parameter 'pci=resource_alignment='. - * It also rounds up size to specified alignment. - * Later on, the kernel will assign page-aligned memory resource back - * to the device. - */ -static void __devinit quirk_resource_alignment(struct pci_dev *dev) -{ - int i; - struct resource *r; - resource_size_t align, size; - u16 command; - - if (!pci_is_reassigndev(dev)) - return; - - if (dev->hdr_type == PCI_HEADER_TYPE_NORMAL && - (dev->class >> 8) == PCI_CLASS_BRIDGE_HOST) { - dev_warn(&dev->dev, - "Can't reassign resources to host bridge.\n"); - return; - } - - dev_info(&dev->dev, - "Disabling memory decoding and releasing memory resources.\n"); - pci_read_config_word(dev, PCI_COMMAND, &command); - command &= ~PCI_COMMAND_MEMORY; - pci_write_config_word(dev, PCI_COMMAND, command); - - align = pci_specified_resource_alignment(dev); - for (i=0; i < PCI_BRIDGE_RESOURCES; i++) { - r = &dev->resource[i]; - if (!(r->flags & IORESOURCE_MEM)) - continue; - size = resource_size(r); - if (size < align) { - size = align; - dev_info(&dev->dev, - "Rounding up size of resource #%d to %#llx.\n", - i, (unsigned long long)size); - } - r->end = size - 1; - r->start = 0; - } - /* Need to disable bridge's resource window, - * to enable the kernel to reassign new resource - * window later on. - */ - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE && - (dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { - for (i = PCI_BRIDGE_RESOURCES; i < PCI_NUM_RESOURCES; i++) { - r = &dev->resource[i]; - if (!(r->flags & IORESOURCE_MEM)) - continue; - r->end = resource_size(r) - 1; - r->start = 0; - } - pci_disable_bridge_window(dev); - } -} -DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_resource_alignment); - -/* * Decoding should be disabled for a PCI device during BAR sizing to avoid * conflict. But doing so may cause problems on host bridge and perhaps other * key system devices. For devices that need to have mmio decoding always-on, @@ -100,10 +39,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, quirk_resource_alignment); */ static void __devinit quirk_mmio_always_on(struct pci_dev *dev) { - if ((dev->class >> 8) == PCI_CLASS_BRIDGE_HOST) - dev->mmio_always_on = 1; + dev->mmio_always_on = 1; } -DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, quirk_mmio_always_on); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_BRIDGE_HOST, 8, quirk_mmio_always_on); /* The Mellanox Tavor device gives false positive parity errors * Mark this device with a broken_parity_status, to allow @@ -1002,12 +941,12 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C597_0, quirk_vt */ static void quirk_cardbus_legacy(struct pci_dev *dev) { - if ((PCI_CLASS_BRIDGE_CARDBUS << 8) ^ dev->class) - return; pci_write_config_dword(dev, PCI_CB_LEGACY_MODE_BASE, 0); } -DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, quirk_cardbus_legacy); -DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_ANY_ID, PCI_ANY_ID, quirk_cardbus_legacy); +DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_BRIDGE_CARDBUS, 8, quirk_cardbus_legacy); +DECLARE_PCI_FIXUP_CLASS_RESUME_EARLY(PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_BRIDGE_CARDBUS, 8, quirk_cardbus_legacy); /* * Following the PCI ordering rules is optional on the AMD762. I'm not @@ -1164,17 +1103,20 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_10, qui static void __devinit quirk_no_ata_d3(struct pci_dev *pdev) { - /* Quirk the legacy ATA devices only. The AHCI ones are ok */ - if ((pdev->class >> 8) == PCI_CLASS_STORAGE_IDE) - pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; + pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; } -DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_ANY_ID, quirk_no_ata_d3); -DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_ATI, PCI_ANY_ID, quirk_no_ata_d3); +/* Quirk the legacy ATA devices only. The AHCI ones are ok */ +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_SERVERWORKS, PCI_ANY_ID, + PCI_CLASS_STORAGE_IDE, 8, quirk_no_ata_d3); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_ATI, PCI_ANY_ID, + PCI_CLASS_STORAGE_IDE, 8, quirk_no_ata_d3); /* ALi loses some register settings that we cannot then restore */ -DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AL, PCI_ANY_ID, quirk_no_ata_d3); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_AL, PCI_ANY_ID, + PCI_CLASS_STORAGE_IDE, 8, quirk_no_ata_d3); /* VIA comes back fine but we need to keep it alive or ACPI GTM failures occur when mode detecting */ -DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_VIA, PCI_ANY_ID, quirk_no_ata_d3); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_VIA, PCI_ANY_ID, + PCI_CLASS_STORAGE_IDE, 8, quirk_no_ata_d3); /* This was originally an Alpha specific thing, but it really fits here. * The i82375 PCI/EISA bridge appears as non-classified. Fix that. @@ -1873,8 +1815,7 @@ static void __devinit quirk_netmos(struct pci_dev *dev) case PCI_DEVICE_ID_NETMOS_9745: case PCI_DEVICE_ID_NETMOS_9845: case PCI_DEVICE_ID_NETMOS_9855: - if ((dev->class >> 8) == PCI_CLASS_COMMUNICATION_SERIAL && - num_parallel) { + if (num_parallel) { dev_info(&dev->dev, "Netmos %04x (%u parallel, " "%u serial); changing class SERIAL to OTHER " "(use parport_serial)\n", @@ -1884,7 +1825,8 @@ static void __devinit quirk_netmos(struct pci_dev *dev) } } } -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NETMOS, PCI_ANY_ID, quirk_netmos); +DECLARE_PCI_FIXUP_CLASS_HEADER(PCI_VENDOR_ID_NETMOS, PCI_ANY_ID, + PCI_CLASS_COMMUNICATION_SERIAL, 8, quirk_netmos); static void __devinit quirk_e100_interrupt(struct pci_dev *dev) { @@ -1952,7 +1894,8 @@ static void __devinit quirk_e100_interrupt(struct pci_dev *dev) iounmap(csr); } -DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_e100_interrupt); +DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, + PCI_CLASS_NETWORK_ETHERNET, 8, quirk_e100_interrupt); /* * The 82575 and 82598 may experience data corruption issues when transitioning @@ -2834,12 +2777,11 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x3c28, vtd_mask_spec_errors); static void __devinit fixup_ti816x_class(struct pci_dev* dev) { /* TI 816x devices do not have class code set when in PCIe boot mode */ - if (dev->class == PCI_CLASS_NOT_DEFINED) { - dev_info(&dev->dev, "Setting PCI class for 816x PCIe device\n"); - dev->class = PCI_CLASS_MULTIMEDIA_VIDEO; - } + dev_info(&dev->dev, "Setting PCI class for 816x PCIe device\n"); + dev->class = PCI_CLASS_MULTIMEDIA_VIDEO; } -DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_TI, 0xb800, fixup_ti816x_class); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_TI, 0xb800, + PCI_CLASS_NOT_DEFINED, 0, fixup_ti816x_class); /* Some PCIe devices do not work reliably with the claimed maximum * payload size supported. @@ -2924,17 +2866,73 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65f8, quirk_intel_mc_errata); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65f9, quirk_intel_mc_errata); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65fa, quirk_intel_mc_errata); + +static void do_one_fixup_debug(void (*fn)(struct pci_dev *dev), struct pci_dev *dev) +{ + ktime_t calltime, delta, rettime; + unsigned long long duration; + + printk(KERN_DEBUG "calling %pF @ %i for %s\n", + fn, task_pid_nr(current), dev_name(&dev->dev)); + calltime = ktime_get(); + fn(dev); + rettime = ktime_get(); + delta = ktime_sub(rettime, calltime); + duration = (unsigned long long) ktime_to_ns(delta) >> 10; + printk(KERN_DEBUG "pci fixup %pF returned after %lld usecs for %s\n", + fn, duration, dev_name(&dev->dev)); +} + +/* + * Some BIOS implementations leave the Intel GPU interrupts enabled, + * even though no one is handling them (f.e. i915 driver is never loaded). + * Additionally the interrupt destination is not set up properly + * and the interrupt ends up -somewhere-. + * + * These spurious interrupts are "sticky" and the kernel disables + * the (shared) interrupt line after 100.000+ generated interrupts. + * + * Fix it by disabling the still enabled interrupts. + * This resolves crashes often seen on monitor unplug. + */ +#define I915_DEIER_REG 0x4400c +static void __devinit disable_igfx_irq(struct pci_dev *dev) +{ + void __iomem *regs = pci_iomap(dev, 0, 0); + if (regs == NULL) { + dev_warn(&dev->dev, "igfx quirk: Can't iomap PCI device\n"); + return; + } + + /* Check if any interrupt line is still enabled */ + if (readl(regs + I915_DEIER_REG) != 0) { + dev_warn(&dev->dev, "BIOS left Intel GPU interrupts enabled; " + "disabling\n"); + + writel(0, regs + I915_DEIER_REG); + } + + pci_iounmap(dev, regs); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x0102, disable_igfx_irq); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x010a, disable_igfx_irq); + static void pci_do_fixups(struct pci_dev *dev, struct pci_fixup *f, struct pci_fixup *end) { - while (f < end) { - if ((f->vendor == dev->vendor || f->vendor == (u16) PCI_ANY_ID) && - (f->device == dev->device || f->device == (u16) PCI_ANY_ID)) { + for (; f < end; f++) + if ((f->class == (u32) (dev->class >> f->class_shift) || + f->class == (u32) PCI_ANY_ID) && + (f->vendor == dev->vendor || + f->vendor == (u16) PCI_ANY_ID) && + (f->device == dev->device || + f->device == (u16) PCI_ANY_ID)) { dev_dbg(&dev->dev, "calling %pF\n", f->hook); - f->hook(dev); + if (initcall_debug) + do_one_fixup_debug(f->hook, dev); + else + f->hook(dev); } - f++; - } } extern struct pci_fixup __start_pci_fixups_early[]; diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index ef8b18c48f26..fd77e2bde2e8 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -79,7 +79,7 @@ EXPORT_SYMBOL(pci_remove_bus); static void __pci_remove_behind_bridge(struct pci_dev *dev); /** - * pci_remove_bus_device - remove a PCI device and any children + * pci_stop_and_remove_bus_device - remove a PCI device and any children * @dev: the device to remove * * Remove a PCI device from the device lists, informing the drivers @@ -90,7 +90,7 @@ static void __pci_remove_behind_bridge(struct pci_dev *dev); * device lists, remove the /proc entry, and notify userspace * (/sbin/hotplug). */ -static void __pci_remove_bus_device(struct pci_dev *dev) +void __pci_remove_bus_device(struct pci_dev *dev) { if (dev->subordinate) { struct pci_bus *b = dev->subordinate; @@ -102,7 +102,9 @@ static void __pci_remove_bus_device(struct pci_dev *dev) pci_destroy_dev(dev); } -void pci_remove_bus_device(struct pci_dev *dev) +EXPORT_SYMBOL(__pci_remove_bus_device); + +void pci_stop_and_remove_bus_device(struct pci_dev *dev) { pci_stop_bus_device(dev); __pci_remove_bus_device(dev); @@ -127,14 +129,15 @@ static void pci_stop_behind_bridge(struct pci_dev *dev) } /** - * pci_remove_behind_bridge - remove all devices behind a PCI bridge + * pci_stop_and_remove_behind_bridge - stop and remove all devices behind + * a PCI bridge * @dev: PCI bridge device * * Remove all devices on the bus, except for the parent bridge. * This also removes any child buses, and any devices they may * contain in a depth-first manner. */ -void pci_remove_behind_bridge(struct pci_dev *dev) +void pci_stop_and_remove_behind_bridge(struct pci_dev *dev) { pci_stop_behind_bridge(dev); __pci_remove_behind_bridge(dev); @@ -144,7 +147,15 @@ static void pci_stop_bus_devices(struct pci_bus *bus) { struct list_head *l, *n; - list_for_each_safe(l, n, &bus->devices) { + /* + * VFs could be removed by pci_stop_and_remove_bus_device() in the + * pci_stop_bus_devices() code path for PF. + * aka, bus->devices get updated in the process. + * but VFs are inserted after PFs when SRIOV is enabled for PF, + * We can iterate the list backwards to get prev valid PF instead + * of removed VF. + */ + list_for_each_prev_safe(l, n, &bus->devices) { struct pci_dev *dev = pci_dev_b(l); pci_stop_bus_device(dev); } @@ -166,6 +177,6 @@ void pci_stop_bus_device(struct pci_dev *dev) pci_stop_dev(dev); } -EXPORT_SYMBOL(pci_remove_bus_device); -EXPORT_SYMBOL(pci_remove_behind_bridge); +EXPORT_SYMBOL(pci_stop_and_remove_bus_device); +EXPORT_SYMBOL(pci_stop_and_remove_behind_bridge); EXPORT_SYMBOL_GPL(pci_stop_bus_device); diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 86b69f85f900..8fa2d4be88de 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -25,10 +25,13 @@ #include <linux/ioport.h> #include <linux/cache.h> #include <linux/slab.h> +#include <asm-generic/pci-bridge.h> #include "pci.h" -struct resource_list_x { - struct resource_list_x *next; +unsigned int pci_flags; + +struct pci_dev_resource { + struct list_head list; struct resource *res; struct pci_dev *dev; resource_size_t start; @@ -38,21 +41,14 @@ struct resource_list_x { unsigned long flags; }; -#define free_list(type, head) do { \ - struct type *list, *tmp; \ - for (list = (head)->next; list;) { \ - tmp = list; \ - list = list->next; \ - kfree(tmp); \ - } \ - (head)->next = NULL; \ -} while (0) - -int pci_realloc_enable = 0; -#define pci_realloc_enabled() pci_realloc_enable -void pci_realloc(void) +static void free_list(struct list_head *head) { - pci_realloc_enable = 1; + struct pci_dev_resource *dev_res, *tmp; + + list_for_each_entry_safe(dev_res, tmp, head, list) { + list_del(&dev_res->list); + kfree(dev_res); + } } /** @@ -64,21 +60,18 @@ void pci_realloc(void) * @add_size: additional size to be optionally added * to the resource */ -static void add_to_list(struct resource_list_x *head, +static int add_to_list(struct list_head *head, struct pci_dev *dev, struct resource *res, resource_size_t add_size, resource_size_t min_align) { - struct resource_list_x *list = head; - struct resource_list_x *ln = list->next; - struct resource_list_x *tmp; + struct pci_dev_resource *tmp; - tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); if (!tmp) { pr_warning("add_to_list: kmalloc() failed!\n"); - return; + return -ENOMEM; } - tmp->next = ln; tmp->res = res; tmp->dev = dev; tmp->start = res->start; @@ -86,19 +79,100 @@ static void add_to_list(struct resource_list_x *head, tmp->flags = res->flags; tmp->add_size = add_size; tmp->min_align = min_align; - list->next = tmp; + + list_add(&tmp->list, head); + + return 0; } -static void add_to_failed_list(struct resource_list_x *head, - struct pci_dev *dev, struct resource *res) +static void remove_from_list(struct list_head *head, + struct resource *res) { - add_to_list(head, dev, res, - 0 /* dont care */, - 0 /* dont care */); + struct pci_dev_resource *dev_res, *tmp; + + list_for_each_entry_safe(dev_res, tmp, head, list) { + if (dev_res->res == res) { + list_del(&dev_res->list); + kfree(dev_res); + break; + } + } +} + +static resource_size_t get_res_add_size(struct list_head *head, + struct resource *res) +{ + struct pci_dev_resource *dev_res; + + list_for_each_entry(dev_res, head, list) { + if (dev_res->res == res) { + int idx = res - &dev_res->dev->resource[0]; + + dev_printk(KERN_DEBUG, &dev_res->dev->dev, + "res[%d]=%pR get_res_add_size add_size %llx\n", + idx, dev_res->res, + (unsigned long long)dev_res->add_size); + + return dev_res->add_size; + } + } + + return 0; +} + +/* Sort resources by alignment */ +static void pdev_sort_resources(struct pci_dev *dev, struct list_head *head) +{ + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *r; + struct pci_dev_resource *dev_res, *tmp; + resource_size_t r_align; + struct list_head *n; + + r = &dev->resource[i]; + + if (r->flags & IORESOURCE_PCI_FIXED) + continue; + + if (!(r->flags) || r->parent) + continue; + + r_align = pci_resource_alignment(dev, r); + if (!r_align) { + dev_warn(&dev->dev, "BAR %d: %pR has bogus alignment\n", + i, r); + continue; + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) + panic("pdev_sort_resources(): " + "kmalloc() failed!\n"); + tmp->res = r; + tmp->dev = dev; + + /* fallback is smallest one or list is empty*/ + n = head; + list_for_each_entry(dev_res, head, list) { + resource_size_t align; + + align = pci_resource_alignment(dev_res->dev, + dev_res->res); + + if (r_align > align) { + n = &dev_res->list; + break; + } + } + /* Insert it just before n*/ + list_add_tail(&tmp->list, n); + } } static void __dev_sort_resources(struct pci_dev *dev, - struct resource_list *head) + struct list_head *head) { u16 class = dev->class >> 8; @@ -136,49 +210,54 @@ static inline void reset_resource(struct resource *res) * additional resources for the element, provided the element * is in the head list. */ -static void reassign_resources_sorted(struct resource_list_x *realloc_head, - struct resource_list *head) +static void reassign_resources_sorted(struct list_head *realloc_head, + struct list_head *head) { struct resource *res; - struct resource_list_x *list, *tmp, *prev; - struct resource_list *hlist; + struct pci_dev_resource *add_res, *tmp; + struct pci_dev_resource *dev_res; resource_size_t add_size; int idx; - prev = realloc_head; - for (list = realloc_head->next; list;) { - res = list->res; + list_for_each_entry_safe(add_res, tmp, realloc_head, list) { + bool found_match = false; + + res = add_res->res; /* skip resource that has been reset */ if (!res->flags) goto out; /* skip this resource if not found in head list */ - for (hlist = head->next; hlist && hlist->res != res; - hlist = hlist->next); - if (!hlist) { /* just skip */ - prev = list; - list = list->next; - continue; + list_for_each_entry(dev_res, head, list) { + if (dev_res->res == res) { + found_match = true; + break; + } } + if (!found_match)/* just skip */ + continue; - idx = res - &list->dev->resource[0]; - add_size=list->add_size; + idx = res - &add_res->dev->resource[0]; + add_size = add_res->add_size; if (!resource_size(res)) { - res->start = list->start; + res->start = add_res->start; res->end = res->start + add_size - 1; - if(pci_assign_resource(list->dev, idx)) + if (pci_assign_resource(add_res->dev, idx)) reset_resource(res); } else { - resource_size_t align = list->min_align; - res->flags |= list->flags & (IORESOURCE_STARTALIGN|IORESOURCE_SIZEALIGN); - if (pci_reassign_resource(list->dev, idx, add_size, align)) - dev_printk(KERN_DEBUG, &list->dev->dev, "failed to add optional resources res=%pR\n", - res); + resource_size_t align = add_res->min_align; + res->flags |= add_res->flags & + (IORESOURCE_STARTALIGN|IORESOURCE_SIZEALIGN); + if (pci_reassign_resource(add_res->dev, idx, + add_size, align)) + dev_printk(KERN_DEBUG, &add_res->dev->dev, + "failed to add %llx res[%d]=%pR\n", + (unsigned long long)add_size, + idx, res); } out: - tmp = list; - prev->next = list = list->next; - kfree(tmp); + list_del(&add_res->list); + kfree(add_res); } } @@ -192,35 +271,99 @@ out: * Satisfy resource requests of each element in the list. Add * requests that could not satisfied to the failed_list. */ -static void assign_requested_resources_sorted(struct resource_list *head, - struct resource_list_x *fail_head) +static void assign_requested_resources_sorted(struct list_head *head, + struct list_head *fail_head) { struct resource *res; - struct resource_list *list; + struct pci_dev_resource *dev_res; int idx; - for (list = head->next; list; list = list->next) { - res = list->res; - idx = res - &list->dev->resource[0]; - if (resource_size(res) && pci_assign_resource(list->dev, idx)) { - if (fail_head && !pci_is_root_bus(list->dev->bus)) { + list_for_each_entry(dev_res, head, list) { + res = dev_res->res; + idx = res - &dev_res->dev->resource[0]; + if (resource_size(res) && + pci_assign_resource(dev_res->dev, idx)) { + if (fail_head && !pci_is_root_bus(dev_res->dev->bus)) { /* * if the failed res is for ROM BAR, and it will * be enabled later, don't add it to the list */ if (!((idx == PCI_ROM_RESOURCE) && (!(res->flags & IORESOURCE_ROM_ENABLE)))) - add_to_failed_list(fail_head, list->dev, res); + add_to_list(fail_head, + dev_res->dev, res, + 0 /* dont care */, + 0 /* dont care */); } reset_resource(res); } } } -static void __assign_resources_sorted(struct resource_list *head, - struct resource_list_x *realloc_head, - struct resource_list_x *fail_head) +static void __assign_resources_sorted(struct list_head *head, + struct list_head *realloc_head, + struct list_head *fail_head) { + /* + * Should not assign requested resources at first. + * they could be adjacent, so later reassign can not reallocate + * them one by one in parent resource window. + * Try to assign requested + add_size at begining + * if could do that, could get out early. + * if could not do that, we still try to assign requested at first, + * then try to reassign add_size for some resources. + */ + LIST_HEAD(save_head); + LIST_HEAD(local_fail_head); + struct pci_dev_resource *save_res; + struct pci_dev_resource *dev_res; + + /* Check if optional add_size is there */ + if (!realloc_head || list_empty(realloc_head)) + goto requested_and_reassign; + + /* Save original start, end, flags etc at first */ + list_for_each_entry(dev_res, head, list) { + if (add_to_list(&save_head, dev_res->dev, dev_res->res, 0, 0)) { + free_list(&save_head); + goto requested_and_reassign; + } + } + + /* Update res in head list with add_size in realloc_head list */ + list_for_each_entry(dev_res, head, list) + dev_res->res->end += get_res_add_size(realloc_head, + dev_res->res); + + /* Try updated head list with add_size added */ + assign_requested_resources_sorted(head, &local_fail_head); + + /* all assigned with add_size ? */ + if (list_empty(&local_fail_head)) { + /* Remove head list from realloc_head list */ + list_for_each_entry(dev_res, head, list) + remove_from_list(realloc_head, dev_res->res); + free_list(&save_head); + free_list(head); + return; + } + + free_list(&local_fail_head); + /* Release assigned resource */ + list_for_each_entry(dev_res, head, list) + if (dev_res->res->parent) + release_resource(dev_res->res); + /* Restore start/end/flags from saved list */ + list_for_each_entry(save_res, &save_head, list) { + struct resource *res = save_res->res; + + res->start = save_res->start; + res->end = save_res->end; + res->flags = save_res->flags; + } + free_list(&save_head); + +requested_and_reassign: /* Satisfy the must-have resource requests */ assign_requested_resources_sorted(head, fail_head); @@ -228,28 +371,27 @@ static void __assign_resources_sorted(struct resource_list *head, requests */ if (realloc_head) reassign_resources_sorted(realloc_head, head); - free_list(resource_list, head); + free_list(head); } static void pdev_assign_resources_sorted(struct pci_dev *dev, - struct resource_list_x *fail_head) + struct list_head *add_head, + struct list_head *fail_head) { - struct resource_list head; + LIST_HEAD(head); - head.next = NULL; __dev_sort_resources(dev, &head); - __assign_resources_sorted(&head, NULL, fail_head); + __assign_resources_sorted(&head, add_head, fail_head); } static void pbus_assign_resources_sorted(const struct pci_bus *bus, - struct resource_list_x *realloc_head, - struct resource_list_x *fail_head) + struct list_head *realloc_head, + struct list_head *fail_head) { struct pci_dev *dev; - struct resource_list head; + LIST_HEAD(head); - head.next = NULL; list_for_each_entry(dev, &bus->devices, bus_list) __dev_sort_resources(dev, &head); @@ -548,20 +690,6 @@ static resource_size_t calculate_memsize(resource_size_t size, return size; } -static resource_size_t get_res_add_size(struct resource_list_x *realloc_head, - struct resource *res) -{ - struct resource_list_x *list; - - /* check if it is in realloc_head list */ - for (list = realloc_head->next; list && list->res != res; - list = list->next); - if (list) - return list->add_size; - - return 0; -} - /** * pbus_size_io() - size the io window of a given bus * @@ -576,7 +704,7 @@ static resource_size_t get_res_add_size(struct resource_list_x *realloc_head, * We must be careful with the ISA aliasing though. */ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, - resource_size_t add_size, struct resource_list_x *realloc_head) + resource_size_t add_size, struct list_head *realloc_head) { struct pci_dev *dev; struct resource *b_res = find_free_bus_resource(bus, IORESOURCE_IO); @@ -612,7 +740,7 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, if (children_add_size > add_size) add_size = children_add_size; size1 = (!realloc_head || (realloc_head && !add_size)) ? size0 : - calculate_iosize(size, min_size+add_size, size1, + calculate_iosize(size, min_size, add_size + size1, resource_size(b_res), 4096); if (!size0 && !size1) { if (b_res->start || b_res->end) @@ -626,8 +754,12 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, b_res->start = 4096; b_res->end = b_res->start + size0 - 1; b_res->flags |= IORESOURCE_STARTALIGN; - if (size1 > size0 && realloc_head) + if (size1 > size0 && realloc_head) { add_to_list(realloc_head, bus->self, b_res, size1-size0, 4096); + dev_printk(KERN_DEBUG, &bus->self->dev, "bridge window " + "%pR to [bus %02x-%02x] add_size %lx\n", b_res, + bus->secondary, bus->subordinate, size1-size0); + } } /** @@ -644,7 +776,7 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, unsigned long type, resource_size_t min_size, resource_size_t add_size, - struct resource_list_x *realloc_head) + struct list_head *realloc_head) { struct pci_dev *dev; resource_size_t min_align, align, size, size0, size1; @@ -726,7 +858,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, if (children_add_size > add_size) add_size = children_add_size; size1 = (!realloc_head || (realloc_head && !add_size)) ? size0 : - calculate_memsize(size, min_size+add_size, 0, + calculate_memsize(size, min_size, add_size, resource_size(b_res), min_align); if (!size0 && !size1) { if (b_res->start || b_res->end) @@ -739,8 +871,12 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, b_res->start = min_align; b_res->end = size0 + min_align - 1; b_res->flags |= IORESOURCE_STARTALIGN | mem64_mask; - if (size1 > size0 && realloc_head) + if (size1 > size0 && realloc_head) { add_to_list(realloc_head, bus->self, b_res, size1-size0, min_align); + dev_printk(KERN_DEBUG, &bus->self->dev, "bridge window " + "%pR to [bus %02x-%02x] add_size %llx\n", b_res, + bus->secondary, bus->subordinate, (unsigned long long)size1-size0); + } return 1; } @@ -754,25 +890,48 @@ unsigned long pci_cardbus_resource_alignment(struct resource *res) } static void pci_bus_size_cardbus(struct pci_bus *bus, - struct resource_list_x *realloc_head) + struct list_head *realloc_head) { struct pci_dev *bridge = bus->self; struct resource *b_res = &bridge->resource[PCI_BRIDGE_RESOURCES]; + resource_size_t b_res_3_size = pci_cardbus_mem_size * 2; u16 ctrl; + if (b_res[0].parent) + goto handle_b_res_1; /* * Reserve some resources for CardBus. We reserve * a fixed amount of bus space for CardBus bridges. */ - b_res[0].start = 0; - b_res[0].flags |= IORESOURCE_IO | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res, pci_cardbus_io_size, 0 /* dont care */); + b_res[0].start = pci_cardbus_io_size; + b_res[0].end = b_res[0].start + pci_cardbus_io_size - 1; + b_res[0].flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[0].end -= pci_cardbus_io_size; + add_to_list(realloc_head, bridge, b_res, pci_cardbus_io_size, + pci_cardbus_io_size); + } - b_res[1].start = 0; - b_res[1].flags |= IORESOURCE_IO | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+1, pci_cardbus_io_size, 0 /* dont care */); +handle_b_res_1: + if (b_res[1].parent) + goto handle_b_res_2; + b_res[1].start = pci_cardbus_io_size; + b_res[1].end = b_res[1].start + pci_cardbus_io_size - 1; + b_res[1].flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[1].end -= pci_cardbus_io_size; + add_to_list(realloc_head, bridge, b_res+1, pci_cardbus_io_size, + pci_cardbus_io_size); + } + +handle_b_res_2: + /* MEM1 must not be pref mmio */ + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM1) { + ctrl &= ~PCI_CB_BRIDGE_CTL_PREFETCH_MEM1; + pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl); + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + } /* * Check whether prefetchable memory is supported @@ -785,38 +944,46 @@ static void pci_bus_size_cardbus(struct pci_bus *bus, pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); } + if (b_res[2].parent) + goto handle_b_res_3; /* * If we have prefetchable memory support, allocate * two regions. Otherwise, allocate one region of * twice the size. */ if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0) { - b_res[2].start = 0; - b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+2, pci_cardbus_mem_size, 0 /* dont care */); - - b_res[3].start = 0; - b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+3, pci_cardbus_mem_size, 0 /* dont care */); - } else { - b_res[3].start = 0; - b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_SIZEALIGN; - if (realloc_head) - add_to_list(realloc_head, bridge, b_res+3, pci_cardbus_mem_size * 2, 0 /* dont care */); + b_res[2].start = pci_cardbus_mem_size; + b_res[2].end = b_res[2].start + pci_cardbus_mem_size - 1; + b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH | + IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[2].end -= pci_cardbus_mem_size; + add_to_list(realloc_head, bridge, b_res+2, + pci_cardbus_mem_size, pci_cardbus_mem_size); + } + + /* reduce that to half */ + b_res_3_size = pci_cardbus_mem_size; + } + +handle_b_res_3: + if (b_res[3].parent) + goto handle_done; + b_res[3].start = pci_cardbus_mem_size; + b_res[3].end = b_res[3].start + b_res_3_size - 1; + b_res[3].flags |= IORESOURCE_MEM | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res[3].end -= b_res_3_size; + add_to_list(realloc_head, bridge, b_res+3, b_res_3_size, + pci_cardbus_mem_size); } - /* set the size of the resource to zero, so that the resource does not - * get assigned during required-resource allocation cycle but gets assigned - * during the optional-resource allocation cycle. - */ - b_res[0].start = b_res[1].start = b_res[2].start = b_res[3].start = 1; - b_res[0].end = b_res[1].end = b_res[2].end = b_res[3].end = 0; +handle_done: + ; } void __ref __pci_bus_size_bridges(struct pci_bus *bus, - struct resource_list_x *realloc_head) + struct list_head *realloc_head) { struct pci_dev *dev; unsigned long mask, prefmask; @@ -858,7 +1025,8 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, * Follow thru */ default: - pbus_size_io(bus, 0, additional_io_size, realloc_head); + pbus_size_io(bus, realloc_head ? 0 : additional_io_size, + additional_io_size, realloc_head); /* If the bridge supports prefetchable range, size it separately. If it doesn't, or its prefetchable window has already been allocated by arch code, try @@ -866,11 +1034,15 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, resources. */ mask = IORESOURCE_MEM; prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH; - if (pbus_size_mem(bus, prefmask, prefmask, 0, additional_mem_size, realloc_head)) + if (pbus_size_mem(bus, prefmask, prefmask, + realloc_head ? 0 : additional_mem_size, + additional_mem_size, realloc_head)) mask = prefmask; /* Success, size non-prefetch only. */ else additional_mem_size += additional_mem_size; - pbus_size_mem(bus, mask, IORESOURCE_MEM, 0, additional_mem_size, realloc_head); + pbus_size_mem(bus, mask, IORESOURCE_MEM, + realloc_head ? 0 : additional_mem_size, + additional_mem_size, realloc_head); break; } } @@ -882,8 +1054,8 @@ void __ref pci_bus_size_bridges(struct pci_bus *bus) EXPORT_SYMBOL(pci_bus_size_bridges); static void __ref __pci_bus_assign_resources(const struct pci_bus *bus, - struct resource_list_x *realloc_head, - struct resource_list_x *fail_head) + struct list_head *realloc_head, + struct list_head *fail_head) { struct pci_bus *b; struct pci_dev *dev; @@ -922,17 +1094,19 @@ void __ref pci_bus_assign_resources(const struct pci_bus *bus) EXPORT_SYMBOL(pci_bus_assign_resources); static void __ref __pci_bridge_assign_resources(const struct pci_dev *bridge, - struct resource_list_x *fail_head) + struct list_head *add_head, + struct list_head *fail_head) { struct pci_bus *b; - pdev_assign_resources_sorted((struct pci_dev *)bridge, fail_head); + pdev_assign_resources_sorted((struct pci_dev *)bridge, + add_head, fail_head); b = bridge->subordinate; if (!b) return; - __pci_bus_assign_resources(b, NULL, fail_head); + __pci_bus_assign_resources(b, add_head, fail_head); switch (bridge->class >> 8) { case PCI_CLASS_BRIDGE_PCI: @@ -1095,6 +1269,58 @@ static int __init pci_get_max_depth(void) return depth; } +/* + * -1: undefined, will auto detect later + * 0: disabled by user + * 1: disabled by auto detect + * 2: enabled by user + * 3: enabled by auto detect + */ +enum enable_type { + undefined = -1, + user_disabled, + auto_disabled, + user_enabled, + auto_enabled, +}; + +static enum enable_type pci_realloc_enable __initdata = undefined; +void __init pci_realloc_get_opt(char *str) +{ + if (!strncmp(str, "off", 3)) + pci_realloc_enable = user_disabled; + else if (!strncmp(str, "on", 2)) + pci_realloc_enable = user_enabled; +} +static bool __init pci_realloc_enabled(void) +{ + return pci_realloc_enable >= user_enabled; +} + +static void __init pci_realloc_detect(void) +{ +#if defined(CONFIG_PCI_IOV) && defined(CONFIG_PCI_REALLOC_ENABLE_AUTO) + struct pci_dev *dev = NULL; + + if (pci_realloc_enable != undefined) + return; + + for_each_pci_dev(dev) { + int i; + + for (i = PCI_IOV_RESOURCES; i <= PCI_IOV_RESOURCE_END; i++) { + struct resource *r = &dev->resource[i]; + + /* Not assigned, or rejected by kernel ? */ + if (r->flags && !r->start) { + pci_realloc_enable = auto_enabled; + + return; + } + } + } +#endif +} /* * first try will not touch pci bridge res @@ -1105,59 +1331,57 @@ void __init pci_assign_unassigned_resources(void) { struct pci_bus *bus; - struct resource_list_x realloc_list; /* list of resources that + LIST_HEAD(realloc_head); /* list of resources that want additional resources */ + struct list_head *add_list = NULL; int tried_times = 0; enum release_type rel_type = leaf_only; - struct resource_list_x head, *list; + LIST_HEAD(fail_head); + struct pci_dev_resource *fail_res; unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH; - unsigned long failed_type; - int max_depth = pci_get_max_depth(); - int pci_try_num; - + int pci_try_num = 1; - head.next = NULL; - realloc_list.next = NULL; + /* don't realloc if asked to do so */ + pci_realloc_detect(); + if (pci_realloc_enabled()) { + int max_depth = pci_get_max_depth(); - pci_try_num = max_depth + 1; - printk(KERN_DEBUG "PCI: max bus depth: %d pci_try_num: %d\n", - max_depth, pci_try_num); + pci_try_num = max_depth + 1; + printk(KERN_DEBUG "PCI: max bus depth: %d pci_try_num: %d\n", + max_depth, pci_try_num); + } again: + /* + * last try will use add_list, otherwise will try good to have as + * must have, so can realloc parent bridge resource + */ + if (tried_times + 1 == pci_try_num) + add_list = &realloc_head; /* Depth first, calculate sizes and alignments of all subordinate buses. */ list_for_each_entry(bus, &pci_root_buses, node) - __pci_bus_size_bridges(bus, &realloc_list); + __pci_bus_size_bridges(bus, add_list); /* Depth last, allocate resources and update the hardware. */ list_for_each_entry(bus, &pci_root_buses, node) - __pci_bus_assign_resources(bus, &realloc_list, &head); - BUG_ON(realloc_list.next); + __pci_bus_assign_resources(bus, add_list, &fail_head); + if (add_list) + BUG_ON(!list_empty(add_list)); tried_times++; /* any device complain? */ - if (!head.next) + if (list_empty(&fail_head)) goto enable_and_dump; - /* don't realloc if asked to do so */ - if (!pci_realloc_enabled()) { - free_list(resource_list_x, &head); - goto enable_and_dump; - } + if (tried_times >= pci_try_num) { + if (pci_realloc_enable == undefined) + printk(KERN_INFO "Some PCI device resources are unassigned, try booting with pci=realloc\n"); + else if (pci_realloc_enable == auto_enabled) + printk(KERN_INFO "Automatically enabled pci realloc, if you have problem, try booting with pci=realloc=off\n"); - failed_type = 0; - for (list = head.next; list;) { - failed_type |= list->flags; - list = list->next; - } - /* - * io port are tight, don't try extra - * or if reach the limit, don't want to try more - */ - failed_type &= type_mask; - if ((failed_type == IORESOURCE_IO) || (tried_times >= pci_try_num)) { - free_list(resource_list_x, &head); + free_list(&fail_head); goto enable_and_dump; } @@ -1172,25 +1396,23 @@ again: * Try to release leaf bridge's resources that doesn't fit resource of * child device under that bridge */ - for (list = head.next; list;) { - bus = list->dev->bus; - pci_bus_release_bridge_resources(bus, list->flags & type_mask, - rel_type); - list = list->next; + list_for_each_entry(fail_res, &fail_head, list) { + bus = fail_res->dev->bus; + pci_bus_release_bridge_resources(bus, + fail_res->flags & type_mask, + rel_type); } /* restore size and flags */ - for (list = head.next; list;) { - struct resource *res = list->res; + list_for_each_entry(fail_res, &fail_head, list) { + struct resource *res = fail_res->res; - res->start = list->start; - res->end = list->end; - res->flags = list->flags; - if (list->dev->subordinate) + res->start = fail_res->start; + res->end = fail_res->end; + res->flags = fail_res->flags; + if (fail_res->dev->subordinate) res->flags = 0; - - list = list->next; } - free_list(resource_list_x, &head); + free_list(&fail_head); goto again; @@ -1207,26 +1429,27 @@ enable_and_dump: void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge) { struct pci_bus *parent = bridge->subordinate; + LIST_HEAD(add_list); /* list of resources that + want additional resources */ int tried_times = 0; - struct resource_list_x head, *list; + LIST_HEAD(fail_head); + struct pci_dev_resource *fail_res; int retval; unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH; - head.next = NULL; - again: - pci_bus_size_bridges(parent); - __pci_bridge_assign_resources(bridge, &head); - + __pci_bus_size_bridges(parent, &add_list); + __pci_bridge_assign_resources(bridge, &add_list, &fail_head); + BUG_ON(!list_empty(&add_list)); tried_times++; - if (!head.next) + if (list_empty(&fail_head)) goto enable_all; if (tried_times >= 2) { /* still fail, don't need to try more */ - free_list(resource_list_x, &head); + free_list(&fail_head); goto enable_all; } @@ -1237,27 +1460,24 @@ again: * Try to release leaf bridge's resources that doesn't fit resource of * child device under that bridge */ - for (list = head.next; list;) { - struct pci_bus *bus = list->dev->bus; - unsigned long flags = list->flags; + list_for_each_entry(fail_res, &fail_head, list) { + struct pci_bus *bus = fail_res->dev->bus; + unsigned long flags = fail_res->flags; pci_bus_release_bridge_resources(bus, flags & type_mask, whole_subtree); - list = list->next; } /* restore size and flags */ - for (list = head.next; list;) { - struct resource *res = list->res; + list_for_each_entry(fail_res, &fail_head, list) { + struct resource *res = fail_res->res; - res->start = list->start; - res->end = list->end; - res->flags = list->flags; - if (list->dev->subordinate) + res->start = fail_res->start; + res->end = fail_res->end; + res->flags = fail_res->flags; + if (fail_res->dev->subordinate) res->flags = 0; - - list = list->next; } - free_list(resource_list_x, &head); + free_list(&fail_head); goto again; @@ -1267,3 +1487,41 @@ enable_all: pci_enable_bridges(parent); } EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources); + +#ifdef CONFIG_HOTPLUG +/** + * pci_rescan_bus - scan a PCI bus for devices. + * @bus: PCI bus to scan + * + * Scan a PCI bus and child buses for new devices, adds them, + * and enables them. + * + * Returns the max number of subordinate bus discovered. + */ +unsigned int __ref pci_rescan_bus(struct pci_bus *bus) +{ + unsigned int max; + struct pci_dev *dev; + LIST_HEAD(add_list); /* list of resources that + want additional resources */ + + max = pci_scan_child_bus(bus); + + down_read(&pci_bus_sem); + list_for_each_entry(dev, &bus->devices, bus_list) + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + if (dev->subordinate) + __pci_bus_size_bridges(dev->subordinate, + &add_list); + up_read(&pci_bus_sem); + __pci_bus_assign_resources(bus, &add_list, NULL); + BUG_ON(!list_empty(&add_list)); + + pci_enable_bridges(bus); + pci_bus_add_devices(bus); + + return max; +} +EXPORT_SYMBOL_GPL(pci_rescan_bus); +#endif diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index b66bfdbd21f7..eea85dafc763 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -114,7 +114,6 @@ int pci_claim_resource(struct pci_dev *dev, int resource) } EXPORT_SYMBOL(pci_claim_resource); -#ifdef CONFIG_PCI_QUIRKS void pci_disable_bridge_window(struct pci_dev *dev) { dev_info(&dev->dev, "disabling bridge mem windows\n"); @@ -127,9 +126,6 @@ void pci_disable_bridge_window(struct pci_dev *dev) pci_write_config_dword(dev, PCI_PREF_MEMORY_BASE, 0x0000fff0); pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, 0xffffffff); } -#endif /* CONFIG_PCI_QUIRKS */ - - static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, int resno, resource_size_t size, resource_size_t align) @@ -158,22 +154,44 @@ static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, return ret; } +/* + * Generic function that returns a value indicating that the device's + * original BIOS BAR address was not saved and so is not available for + * reinstatement. + * + * Can be over-ridden by architecture specific code that implements + * reinstatement functionality rather than leaving it disabled when + * normal allocation attempts fail. + */ +resource_size_t __weak pcibios_retrieve_fw_addr(struct pci_dev *dev, int idx) +{ + return 0; +} + static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev, int resno, resource_size_t size) { struct resource *root, *conflict; - resource_size_t start, end; + resource_size_t fw_addr, start, end; int ret = 0; - if (res->flags & IORESOURCE_IO) - root = &ioport_resource; - else - root = &iomem_resource; + fw_addr = pcibios_retrieve_fw_addr(dev, resno); + if (!fw_addr) + return 1; start = res->start; end = res->end; - res->start = dev->fw_addr[resno]; + res->start = fw_addr; res->end = res->start + size - 1; + + root = pci_find_parent_resource(dev, res); + if (!root) { + if (res->flags & IORESOURCE_IO) + root = &ioport_resource; + else + root = &iomem_resource; + } + dev_info(&dev->dev, "BAR %d: trying firmware assignment %pR\n", resno, res); conflict = request_resource_conflict(root, res); @@ -228,16 +246,17 @@ int pci_reassign_resource(struct pci_dev *dev, int resno, resource_size_t addsiz int ret; if (!res->parent) { - dev_info(&dev->dev, "BAR %d: can't reassign an unassigned resouce %pR " + dev_info(&dev->dev, "BAR %d: can't reassign an unassigned resource %pR " "\n", resno, res); return -EINVAL; } - new_size = resource_size(res) + addsize + min_align; + /* already aligned with min_align */ + new_size = resource_size(res) + addsize; ret = _pci_assign_resource(dev, resno, new_size, min_align); if (!ret) { res->flags &= ~IORESOURCE_STARTALIGN; - dev_info(&dev->dev, "BAR %d: assigned %pR\n", resno, res); + dev_info(&dev->dev, "BAR %d: reassigned %pR\n", resno, res); if (resno < PCI_BRIDGE_RESOURCES) pci_update_resource(dev, resno); } @@ -267,7 +286,7 @@ int pci_assign_resource(struct pci_dev *dev, int resno) * where firmware left it. That at least has a chance of * working, which is better than just leaving it disabled. */ - if (ret < 0 && dev->fw_addr[resno]) + if (ret < 0) ret = pci_revert_fw_address(res, dev, resno, size); if (!ret) { @@ -279,53 +298,6 @@ int pci_assign_resource(struct pci_dev *dev, int resno) return ret; } - -/* Sort resources by alignment */ -void pdev_sort_resources(struct pci_dev *dev, struct resource_list *head) -{ - int i; - - for (i = 0; i < PCI_NUM_RESOURCES; i++) { - struct resource *r; - struct resource_list *list, *tmp; - resource_size_t r_align; - - r = &dev->resource[i]; - - if (r->flags & IORESOURCE_PCI_FIXED) - continue; - - if (!(r->flags) || r->parent) - continue; - - r_align = pci_resource_alignment(dev, r); - if (!r_align) { - dev_warn(&dev->dev, "BAR %d: %pR has bogus alignment\n", - i, r); - continue; - } - for (list = head; ; list = list->next) { - resource_size_t align = 0; - struct resource_list *ln = list->next; - - if (ln) - align = pci_resource_alignment(ln->dev, ln->res); - - if (r_align > align) { - tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); - if (!tmp) - panic("pdev_sort_resources(): " - "kmalloc() failed!\n"); - tmp->next = ln; - tmp->res = r; - tmp->dev = dev; - list->next = tmp; - break; - } - } - } -} - int pci_enable_resources(struct pci_dev *dev, int mask) { u16 cmd, old_cmd; diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c index 401090110922..fd00ff02ab4d 100644 --- a/drivers/pci/xen-pcifront.c +++ b/drivers/pci/xen-pcifront.c @@ -544,7 +544,7 @@ static void free_root_bus_devs(struct pci_bus *bus) dev = container_of(bus->devices.next, struct pci_dev, bus_list); dev_dbg(&dev->dev, "removing device\n"); - pci_remove_bus_device(dev); + pci_stop_and_remove_bus_device(dev); } } @@ -1044,7 +1044,7 @@ static int pcifront_detach_devices(struct pcifront_device *pdev) domain, bus, slot, func); continue; } - pci_remove_bus_device(pci_dev); + pci_stop_and_remove_bus_device(pci_dev); pci_dev_put(pci_dev); dev_dbg(&pdev->xdev->dev, diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig index f9e3fb3a285b..bba3ab2066ee 100644 --- a/drivers/pcmcia/Kconfig +++ b/drivers/pcmcia/Kconfig @@ -183,10 +183,14 @@ config PCMCIA_BCM63XX config PCMCIA_SOC_COMMON tristate +config PCMCIA_SA11XX_BASE + tristate + config PCMCIA_SA1100 tristate "SA1100 support" depends on ARM && ARCH_SA1100 && PCMCIA select PCMCIA_SOC_COMMON + select PCMCIA_SA11XX_BASE help Say Y here to include support for SA11x0-based PCMCIA or CF sockets, found on HP iPAQs, Yopy, and other StrongARM(R)/ @@ -196,8 +200,9 @@ config PCMCIA_SA1100 config PCMCIA_SA1111 tristate "SA1111 support" - depends on ARM && ARCH_SA1100 && SA1111 && PCMCIA + depends on ARM && SA1111 && PCMCIA select PCMCIA_SOC_COMMON + select PCMCIA_SA11XX_BASE if ARCH_SA1100 help Say Y here to include support for SA1111-based PCMCIA or CF sockets, found on the Jornada 720, Graphicsmaster and other @@ -213,6 +218,7 @@ config PCMCIA_PXA2XX || ARCOM_PCMCIA || ARCH_PXA_ESERIES || MACH_STARGATE2 \ || MACH_VPAC270 || MACH_BALLOON3 || MACH_COLIBRI \ || MACH_COLIBRI320) + select PCMCIA_SA1111 if ARCH_LUBBOCK && SA1111 select PCMCIA_SOC_COMMON help Say Y here to include support for the PXA2xx PCMCIA controller diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile index ec543a4ff2e4..47525de6a631 100644 --- a/drivers/pcmcia/Makefile +++ b/drivers/pcmcia/Makefile @@ -25,8 +25,9 @@ obj-$(CONFIG_I82092) += i82092.o obj-$(CONFIG_TCIC) += tcic.o obj-$(CONFIG_PCMCIA_M8XX) += m8xx_pcmcia.o obj-$(CONFIG_PCMCIA_SOC_COMMON) += soc_common.o -obj-$(CONFIG_PCMCIA_SA1100) += sa11xx_base.o sa1100_cs.o -obj-$(CONFIG_PCMCIA_SA1111) += sa11xx_base.o sa1111_cs.o +obj-$(CONFIG_PCMCIA_SA11XX_BASE) += sa11xx_base.o +obj-$(CONFIG_PCMCIA_SA1100) += sa1100_cs.o +obj-$(CONFIG_PCMCIA_SA1111) += sa1111_cs.o obj-$(CONFIG_M32R_PCC) += m32r_pcc.o obj-$(CONFIG_M32R_CFC) += m32r_cfc.o obj-$(CONFIG_PCMCIA_BCM63XX) += bcm63xx_pcmcia.o @@ -39,9 +40,10 @@ obj-$(CONFIG_ELECTRA_CF) += electra_cf.o obj-$(CONFIG_PCMCIA_ALCHEMY_DEVBOARD) += db1xxx_ss.o sa1111_cs-y += sa1111_generic.o -sa1111_cs-$(CONFIG_ASSABET_NEPONSET) += sa1100_neponset.o -sa1111_cs-$(CONFIG_SA1100_BADGE4) += sa1100_badge4.o -sa1111_cs-$(CONFIG_SA1100_JORNADA720) += sa1100_jornada720.o +sa1111_cs-$(CONFIG_ASSABET_NEPONSET) += sa1111_neponset.o +sa1111_cs-$(CONFIG_SA1100_BADGE4) += sa1111_badge4.o +sa1111_cs-$(CONFIG_SA1100_JORNADA720) += sa1111_jornada720.o +sa1111_cs-$(CONFIG_ARCH_LUBBOCK) += sa1111_lubbock.o sa1100_cs-y += sa1100_generic.o sa1100_cs-$(CONFIG_SA1100_ASSABET) += sa1100_assabet.o @@ -52,9 +54,7 @@ sa1100_cs-$(CONFIG_SA1100_NANOENGINE) += sa1100_nanoengine.o sa1100_cs-$(CONFIG_SA1100_SHANNON) += sa1100_shannon.o sa1100_cs-$(CONFIG_SA1100_SIMPAD) += sa1100_simpad.o -pxa2xx_lubbock_cs-y += pxa2xx_lubbock.o sa1111_generic.o pxa2xx_cm_x2xx_cs-y += pxa2xx_cm_x2xx.o pxa2xx_cm_x255.o pxa2xx_cm_x270.o -pxa2xx-obj-$(CONFIG_ARCH_LUBBOCK) += pxa2xx_lubbock_cs.o pxa2xx-obj-$(CONFIG_MACH_MAINSTONE) += pxa2xx_mainstone.o pxa2xx-obj-$(CONFIG_PXA_SHARPSL) += pxa2xx_sharpsl.o pxa2xx-obj-$(CONFIG_MACH_ARMCORE) += pxa2xx_cm_x2xx_cs.o diff --git a/drivers/pcmcia/at91_cf.c b/drivers/pcmcia/at91_cf.c index 4902206f53d9..1dd68f502634 100644 --- a/drivers/pcmcia/at91_cf.c +++ b/drivers/pcmcia/at91_cf.c @@ -26,6 +26,7 @@ #include <mach/board.h> #include <mach/at91rm9200_mc.h> +#include <mach/at91_ramc.h> /* @@ -156,7 +157,7 @@ static int at91_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) /* * Use 16 bit accesses unless/until we need 8-bit i/o space. */ - csr = at91_sys_read(AT91_SMC_CSR(cf->board->chipselect)) & ~AT91_SMC_DBW; + csr = at91_ramc_read(0, AT91_SMC_CSR(cf->board->chipselect)) & ~AT91_SMC_DBW; /* * NOTE: this CF controller ignores IOIS16, so we can't really do @@ -175,7 +176,7 @@ static int at91_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) csr |= AT91_SMC_DBW_16; pr_debug("%s: 16bit i/o bus\n", driver_name); } - at91_sys_write(AT91_SMC_CSR(cf->board->chipselect), csr); + at91_ramc_write(0, AT91_SMC_CSR(cf->board->chipselect), csr); io->start = cf->socket.io_offset; io->stop = io->start + SZ_2K - 1; diff --git a/drivers/pcmcia/cardbus.c b/drivers/pcmcia/cardbus.c index 9a58862f1401..6e75153c5b4f 100644 --- a/drivers/pcmcia/cardbus.c +++ b/drivers/pcmcia/cardbus.c @@ -108,5 +108,5 @@ void cb_free(struct pcmcia_socket *s) struct pci_dev *bridge = s->cb_dev; if (bridge) - pci_remove_behind_bridge(bridge); + pci_stop_and_remove_behind_bridge(bridge); } diff --git a/drivers/pcmcia/pxa2xx_balloon3.c b/drivers/pcmcia/pxa2xx_balloon3.c index 22a75e610f12..2ef576c5b69d 100644 --- a/drivers/pcmcia/pxa2xx_balloon3.c +++ b/drivers/pcmcia/pxa2xx_balloon3.c @@ -29,15 +29,6 @@ #include "soc_common.h" -/* - * These are a list of interrupt sources that provokes a polled - * check of status - */ -static struct pcmcia_irqs irqs[] = { - { 0, BALLOON3_S0_CD_IRQ, "PCMCIA0 CD" }, - { 0, BALLOON3_BP_NSTSCHG_IRQ, "PCMCIA0 STSCHG" }, -}; - static int balloon3_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { uint16_t ver; @@ -49,12 +40,12 @@ static int balloon3_pcmcia_hw_init(struct soc_pcmcia_socket *skt) ver); skt->socket.pci_irq = BALLOON3_BP_CF_NRDY_IRQ; - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} + skt->stat[SOC_STAT_CD].gpio = BALLOON3_GPIO_S0_CD; + skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD"; + skt->stat[SOC_STAT_BVD1].irq = BALLOON3_BP_NSTSCHG_IRQ; + skt->stat[SOC_STAT_BVD1].name = "PCMCIA0 STSCHG"; -static void balloon3_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + return 0; } static unsigned long balloon3_pcmcia_status[2] = { @@ -85,13 +76,11 @@ static void balloon3_pcmcia_socket_state(struct soc_pcmcia_socket *skt, disable_irq(BALLOON3_BP_NSTSCHG_IRQ); } - state->detect = !gpio_get_value(BALLOON3_GPIO_S0_CD); state->ready = !!(status & BALLOON3_CF_nIRQ); state->bvd1 = !!(status & BALLOON3_CF_nSTSCHG_BVD1); state->bvd2 = 0; /* not available */ state->vs_3v = 1; /* Always true its a CF card */ state->vs_Xv = 0; /* not available */ - state->wrprot = 0; /* not available */ } static int balloon3_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, @@ -106,7 +95,6 @@ static int balloon3_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, static struct pcmcia_low_level balloon3_pcmcia_ops = { .owner = THIS_MODULE, .hw_init = balloon3_pcmcia_hw_init, - .hw_shutdown = balloon3_pcmcia_hw_shutdown, .socket_state = balloon3_pcmcia_socket_state, .configure_socket = balloon3_pcmcia_configure_socket, .first = 0, diff --git a/drivers/pcmcia/pxa2xx_base.c b/drivers/pcmcia/pxa2xx_base.c index 64d433ec4fc6..66a54222bbf4 100644 --- a/drivers/pcmcia/pxa2xx_base.c +++ b/drivers/pcmcia/pxa2xx_base.c @@ -318,10 +318,7 @@ static int pxa2xx_drv_pcmcia_probe(struct platform_device *dev) skt->nr = ops->first + i; skt->clk = clk; - skt->ops = ops; - skt->socket.owner = ops->owner; - skt->socket.dev.parent = &dev->dev; - skt->socket.pci_irq = NO_IRQ; + soc_pcmcia_init_one(skt, ops, &dev->dev); ret = pxa2xx_drv_pcmcia_add_one(skt); if (ret) diff --git a/drivers/pcmcia/pxa2xx_cm_x255.c b/drivers/pcmcia/pxa2xx_cm_x255.c index 31ab6ddf52c9..da40908b29dd 100644 --- a/drivers/pcmcia/pxa2xx_cm_x255.c +++ b/drivers/pcmcia/pxa2xx_cm_x255.c @@ -25,17 +25,6 @@ #define GPIO_PCMCIA_S1_RDYINT (8) #define GPIO_PCMCIA_RESET (9) -#define PCMCIA_S0_CD_VALID gpio_to_irq(GPIO_PCMCIA_S0_CD_VALID) -#define PCMCIA_S1_CD_VALID gpio_to_irq(GPIO_PCMCIA_S1_CD_VALID) -#define PCMCIA_S0_RDYINT gpio_to_irq(GPIO_PCMCIA_S0_RDYINT) -#define PCMCIA_S1_RDYINT gpio_to_irq(GPIO_PCMCIA_S1_RDYINT) - - -static struct pcmcia_irqs irqs[] = { - { .sock = 0, .str = "PCMCIA0 CD" }, - { .sock = 1, .str = "PCMCIA1 CD" }, -}; - static int cmx255_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { int ret = gpio_request(GPIO_PCMCIA_RESET, "PCCard reset"); @@ -43,19 +32,23 @@ static int cmx255_pcmcia_hw_init(struct soc_pcmcia_socket *skt) return ret; gpio_direction_output(GPIO_PCMCIA_RESET, 0); - skt->socket.pci_irq = skt->nr == 0 ? PCMCIA_S0_RDYINT : PCMCIA_S1_RDYINT; - irqs[0].irq = PCMCIA_S0_CD_VALID; - irqs[1].irq = PCMCIA_S1_CD_VALID; - ret = soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); - if (!ret) - gpio_free(GPIO_PCMCIA_RESET); + if (skt->nr == 0) { + skt->stat[SOC_STAT_CD].gpio = GPIO_PCMCIA_S0_CD_VALID; + skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD"; + skt->stat[SOC_STAT_RDY].gpio = GPIO_PCMCIA_S0_RDYINT; + skt->stat[SOC_STAT_RDY].name = "PCMCIA0 RDY"; + } else { + skt->stat[SOC_STAT_CD].gpio = GPIO_PCMCIA_S1_CD_VALID; + skt->stat[SOC_STAT_CD].name = "PCMCIA1 CD"; + skt->stat[SOC_STAT_RDY].gpio = GPIO_PCMCIA_S1_RDYINT; + skt->stat[SOC_STAT_RDY].name = "PCMCIA1 RDY"; + } - return ret; + return 0; } static void cmx255_pcmcia_shutdown(struct soc_pcmcia_socket *skt) { - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); gpio_free(GPIO_PCMCIA_RESET); } @@ -63,16 +56,8 @@ static void cmx255_pcmcia_shutdown(struct soc_pcmcia_socket *skt) static void cmx255_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - int cd = skt->nr ? GPIO_PCMCIA_S1_CD_VALID : GPIO_PCMCIA_S0_CD_VALID; - int rdy = skt->nr ? GPIO_PCMCIA_S1_RDYINT : GPIO_PCMCIA_S0_RDYINT; - - state->detect = !gpio_get_value(cd); - state->ready = !!gpio_get_value(rdy); - state->bvd1 = 1; - state->bvd2 = 1; state->vs_3v = 0; state->vs_Xv = 0; - state->wrprot = 0; /* not available */ } diff --git a/drivers/pcmcia/pxa2xx_cm_x270.c b/drivers/pcmcia/pxa2xx_cm_x270.c index 3dc7621a0767..f59223f2307d 100644 --- a/drivers/pcmcia/pxa2xx_cm_x270.c +++ b/drivers/pcmcia/pxa2xx_cm_x270.c @@ -22,14 +22,6 @@ #define GPIO_PCMCIA_S0_RDYINT (82) #define GPIO_PCMCIA_RESET (53) -#define PCMCIA_S0_CD_VALID gpio_to_irq(GPIO_PCMCIA_S0_CD_VALID) -#define PCMCIA_S0_RDYINT gpio_to_irq(GPIO_PCMCIA_S0_RDYINT) - - -static struct pcmcia_irqs irqs[] = { - { .sock = 0, .str = "PCMCIA0 CD" }, -}; - static int cmx270_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { int ret = gpio_request(GPIO_PCMCIA_RESET, "PCCard reset"); @@ -37,18 +29,16 @@ static int cmx270_pcmcia_hw_init(struct soc_pcmcia_socket *skt) return ret; gpio_direction_output(GPIO_PCMCIA_RESET, 0); - skt->socket.pci_irq = PCMCIA_S0_RDYINT; - irqs[0].irq = PCMCIA_S0_CD_VALID; - ret = soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); - if (!ret) - gpio_free(GPIO_PCMCIA_RESET); + skt->stat[SOC_STAT_CD].gpio = GPIO_PCMCIA_S0_CD_VALID; + skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD"; + skt->stat[SOC_STAT_RDY].gpio = GPIO_PCMCIA_S0_RDYINT; + skt->stat[SOC_STAT_RDY].name = "PCMCIA0 RDY"; return ret; } static void cmx270_pcmcia_shutdown(struct soc_pcmcia_socket *skt) { - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); gpio_free(GPIO_PCMCIA_RESET); } @@ -56,13 +46,8 @@ static void cmx270_pcmcia_shutdown(struct soc_pcmcia_socket *skt) static void cmx270_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - state->detect = (gpio_get_value(GPIO_PCMCIA_S0_CD_VALID) == 0) ? 1 : 0; - state->ready = (gpio_get_value(GPIO_PCMCIA_S0_RDYINT) == 0) ? 0 : 1; - state->bvd1 = 1; - state->bvd2 = 1; state->vs_3v = 0; state->vs_Xv = 0; - state->wrprot = 0; /* not available */ } diff --git a/drivers/pcmcia/pxa2xx_colibri.c b/drivers/pcmcia/pxa2xx_colibri.c index c6dec572a05d..4dee7b2a8032 100644 --- a/drivers/pcmcia/pxa2xx_colibri.c +++ b/drivers/pcmcia/pxa2xx_colibri.c @@ -53,13 +53,6 @@ static struct gpio colibri_pcmcia_gpios[] = { { 0, GPIOF_INIT_HIGH,"PCMCIA Reset" }, }; -static struct pcmcia_irqs colibri_irqs[] = { - { - .sock = 0, - .str = "PCMCIA CD" - }, -}; - static int colibri_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { int ret; @@ -69,19 +62,10 @@ static int colibri_pcmcia_hw_init(struct soc_pcmcia_socket *skt) if (ret) goto err1; - colibri_irqs[0].irq = gpio_to_irq(colibri_pcmcia_gpios[DETECT].gpio); skt->socket.pci_irq = gpio_to_irq(colibri_pcmcia_gpios[READY].gpio); + skt->stat[SOC_STAT_CD].irq = gpio_to_irq(colibri_pcmcia_gpios[DETECT].gpio); + skt->stat[SOC_STAT_CD].name = "PCMCIA CD"; - ret = soc_pcmcia_request_irqs(skt, colibri_irqs, - ARRAY_SIZE(colibri_irqs)); - if (ret) - goto err2; - - return ret; - -err2: - gpio_free_array(colibri_pcmcia_gpios, - ARRAY_SIZE(colibri_pcmcia_gpios)); err1: return ret; } @@ -100,7 +84,6 @@ static void colibri_pcmcia_socket_state(struct soc_pcmcia_socket *skt, state->ready = !!gpio_get_value(colibri_pcmcia_gpios[READY].gpio); state->bvd1 = !!gpio_get_value(colibri_pcmcia_gpios[BVD1].gpio); state->bvd2 = !!gpio_get_value(colibri_pcmcia_gpios[BVD2].gpio); - state->wrprot = 0; state->vs_3v = 1; state->vs_Xv = 0; } diff --git a/drivers/pcmcia/pxa2xx_e740.c b/drivers/pcmcia/pxa2xx_e740.c index 17cd2ce7428f..8751a323b448 100644 --- a/drivers/pcmcia/pxa2xx_e740.c +++ b/drivers/pcmcia/pxa2xx_e740.c @@ -23,53 +23,27 @@ #include "soc_common.h" -static struct pcmcia_irqs cd_irqs[] = { - { - .sock = 0, - .str = "CF card detect" - }, - { - .sock = 1, - .str = "Wifi switch" - }, -}; - static int e740_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { - if (skt->nr == 0) - skt->socket.pci_irq = gpio_to_irq(GPIO_E740_PCMCIA_RDY0); - else - skt->socket.pci_irq = gpio_to_irq(GPIO_E740_PCMCIA_RDY1); - - cd_irqs[0].irq = gpio_to_irq(GPIO_E740_PCMCIA_CD0); - cd_irqs[1].irq = gpio_to_irq(GPIO_E740_PCMCIA_CD1); - - return soc_pcmcia_request_irqs(skt, &cd_irqs[skt->nr], 1); -} + if (skt->nr == 0) { + skt->stat[SOC_STAT_CD].gpio = GPIO_E740_PCMCIA_CD0; + skt->stat[SOC_STAT_CD].name = "CF card detect"; + skt->stat[SOC_STAT_RDY].gpio = GPIO_E740_PCMCIA_RDY0; + skt->stat[SOC_STAT_RDY].name = "CF ready"; + } else { + skt->stat[SOC_STAT_CD].gpio = GPIO_E740_PCMCIA_CD1; + skt->stat[SOC_STAT_CD].name = "Wifi switch"; + skt->stat[SOC_STAT_RDY].gpio = GPIO_E740_PCMCIA_RDY1; + skt->stat[SOC_STAT_RDY].name = "Wifi ready"; + } -/* - * Release all resources. - */ -static void e740_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_free_irqs(skt, &cd_irqs[skt->nr], 1); + return 0; } static void e740_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - if (skt->nr == 0) { - state->detect = gpio_get_value(GPIO_E740_PCMCIA_CD0) ? 0 : 1; - state->ready = gpio_get_value(GPIO_E740_PCMCIA_RDY0) ? 1 : 0; - } else { - state->detect = gpio_get_value(GPIO_E740_PCMCIA_CD1) ? 0 : 1; - state->ready = gpio_get_value(GPIO_E740_PCMCIA_RDY1) ? 1 : 0; - } - state->vs_3v = 1; - state->bvd1 = 1; - state->bvd2 = 1; - state->wrprot = 0; state->vs_Xv = 0; } @@ -109,32 +83,11 @@ static int e740_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, return 0; } -/* - * Enable card status IRQs on (re-)initialisation. This can - * be called at initialisation, power management event, or - * pcmcia event. - */ -static void e740_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_enable_irqs(skt, cd_irqs, ARRAY_SIZE(cd_irqs)); -} - -/* - * Disable card status IRQs on suspend. - */ -static void e740_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_disable_irqs(skt, cd_irqs, ARRAY_SIZE(cd_irqs)); -} - static struct pcmcia_low_level e740_pcmcia_ops = { .owner = THIS_MODULE, .hw_init = e740_pcmcia_hw_init, - .hw_shutdown = e740_pcmcia_hw_shutdown, .socket_state = e740_pcmcia_socket_state, .configure_socket = e740_pcmcia_configure_socket, - .socket_init = e740_pcmcia_socket_init, - .socket_suspend = e740_pcmcia_socket_suspend, .nr = 2, }; diff --git a/drivers/pcmcia/pxa2xx_mainstone.c b/drivers/pcmcia/pxa2xx_mainstone.c index aded706c0b9f..7e32e25cdcb2 100644 --- a/drivers/pcmcia/pxa2xx_mainstone.c +++ b/drivers/pcmcia/pxa2xx_mainstone.c @@ -30,27 +30,26 @@ #include "soc_common.h" -static struct pcmcia_irqs irqs[] = { - { 0, MAINSTONE_S0_CD_IRQ, "PCMCIA0 CD" }, - { 1, MAINSTONE_S1_CD_IRQ, "PCMCIA1 CD" }, - { 0, MAINSTONE_S0_STSCHG_IRQ, "PCMCIA0 STSCHG" }, - { 1, MAINSTONE_S1_STSCHG_IRQ, "PCMCIA1 STSCHG" }, -}; - static int mst_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { /* * Setup default state of GPIO outputs * before we enable them as outputs. */ - - skt->socket.pci_irq = (skt->nr == 0) ? MAINSTONE_S0_IRQ : MAINSTONE_S1_IRQ; - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -static void mst_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + if (skt->nr == 0) { + skt->socket.pci_irq = MAINSTONE_S0_IRQ; + skt->stat[SOC_STAT_CD].irq = MAINSTONE_S0_CD_IRQ; + skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD"; + skt->stat[SOC_STAT_BVD1].irq = MAINSTONE_S0_STSCHG_IRQ; + skt->stat[SOC_STAT_BVD1].name = "PCMCIA0 STSCHG"; + } else { + skt->socket.pci_irq = MAINSTONE_S1_IRQ; + skt->stat[SOC_STAT_CD].irq = MAINSTONE_S1_CD_IRQ; + skt->stat[SOC_STAT_CD].name = "PCMCIA1 CD"; + skt->stat[SOC_STAT_BVD1].irq = MAINSTONE_S1_STSCHG_IRQ; + skt->stat[SOC_STAT_BVD1].name = "PCMCIA1 STSCHG"; + } + return 0; } static unsigned long mst_pcmcia_status[2]; @@ -84,7 +83,6 @@ static void mst_pcmcia_socket_state(struct soc_pcmcia_socket *skt, state->bvd2 = (status & MST_PCMCIA_nSPKR_BVD2) ? 1 : 0; state->vs_3v = (status & MST_PCMCIA_nVS1) ? 0 : 1; state->vs_Xv = (status & MST_PCMCIA_nVS2) ? 0 : 1; - state->wrprot = 0; /* not available */ } static int mst_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, @@ -131,7 +129,6 @@ static int mst_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, static struct pcmcia_low_level mst_pcmcia_ops __initdata = { .owner = THIS_MODULE, .hw_init = mst_pcmcia_hw_init, - .hw_shutdown = mst_pcmcia_hw_shutdown, .socket_state = mst_pcmcia_socket_state, .configure_socket = mst_pcmcia_configure_socket, .nr = 2, diff --git a/drivers/pcmcia/pxa2xx_palmld.c b/drivers/pcmcia/pxa2xx_palmld.c index 6a8e011a8c13..ed7d4dbc39fa 100644 --- a/drivers/pcmcia/pxa2xx_palmld.c +++ b/drivers/pcmcia/pxa2xx_palmld.c @@ -23,7 +23,6 @@ static struct gpio palmld_pcmcia_gpios[] = { { GPIO_NR_PALMLD_PCMCIA_POWER, GPIOF_INIT_LOW, "PCMCIA Power" }, { GPIO_NR_PALMLD_PCMCIA_RESET, GPIOF_INIT_HIGH,"PCMCIA Reset" }, - { GPIO_NR_PALMLD_PCMCIA_READY, GPIOF_IN, "PCMCIA Ready" }, }; static int palmld_pcmcia_hw_init(struct soc_pcmcia_socket *skt) @@ -33,7 +32,8 @@ static int palmld_pcmcia_hw_init(struct soc_pcmcia_socket *skt) ret = gpio_request_array(palmld_pcmcia_gpios, ARRAY_SIZE(palmld_pcmcia_gpios)); - skt->socket.pci_irq = gpio_to_irq(GPIO_NR_PALMLD_PCMCIA_READY); + skt->stat[SOC_STAT_RDY].gpio = GPIO_NR_PALMLD_PCMCIA_READY; + skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready"; return ret; } @@ -47,10 +47,6 @@ static void palmld_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { state->detect = 1; /* always inserted */ - state->ready = !!gpio_get_value(GPIO_NR_PALMLD_PCMCIA_READY); - state->bvd1 = 1; - state->bvd2 = 1; - state->wrprot = 0; state->vs_3v = 1; state->vs_Xv = 0; } diff --git a/drivers/pcmcia/pxa2xx_palmtc.c b/drivers/pcmcia/pxa2xx_palmtc.c index 9e38de769ba3..81225a7a8cbb 100644 --- a/drivers/pcmcia/pxa2xx_palmtc.c +++ b/drivers/pcmcia/pxa2xx_palmtc.c @@ -26,7 +26,6 @@ static struct gpio palmtc_pcmcia_gpios[] = { { GPIO_NR_PALMTC_PCMCIA_POWER2, GPIOF_INIT_LOW, "PCMCIA Power 2" }, { GPIO_NR_PALMTC_PCMCIA_POWER3, GPIOF_INIT_LOW, "PCMCIA Power 3" }, { GPIO_NR_PALMTC_PCMCIA_RESET, GPIOF_INIT_HIGH,"PCMCIA Reset" }, - { GPIO_NR_PALMTC_PCMCIA_READY, GPIOF_IN, "PCMCIA Ready" }, { GPIO_NR_PALMTC_PCMCIA_PWRREADY, GPIOF_IN, "PCMCIA Power Ready" }, }; @@ -37,7 +36,8 @@ static int palmtc_pcmcia_hw_init(struct soc_pcmcia_socket *skt) ret = gpio_request_array(palmtc_pcmcia_gpios, ARRAY_SIZE(palmtc_pcmcia_gpios)); - skt->socket.pci_irq = gpio_to_irq(GPIO_NR_PALMTC_PCMCIA_READY); + skt->stat[SOC_STAT_RDY].gpio = GPIO_NR_PALMTC_PCMCIA_READY; + skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready"; return ret; } @@ -51,10 +51,6 @@ static void palmtc_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { state->detect = 1; /* always inserted */ - state->ready = !!gpio_get_value(GPIO_NR_PALMTC_PCMCIA_READY); - state->bvd1 = 1; - state->bvd2 = 1; - state->wrprot = 0; state->vs_3v = 1; state->vs_Xv = 0; } diff --git a/drivers/pcmcia/pxa2xx_palmtx.c b/drivers/pcmcia/pxa2xx_palmtx.c index 80645a688ee3..069b6bbcf319 100644 --- a/drivers/pcmcia/pxa2xx_palmtx.c +++ b/drivers/pcmcia/pxa2xx_palmtx.c @@ -23,7 +23,6 @@ static struct gpio palmtx_pcmcia_gpios[] = { { GPIO_NR_PALMTX_PCMCIA_POWER1, GPIOF_INIT_LOW, "PCMCIA Power 1" }, { GPIO_NR_PALMTX_PCMCIA_POWER2, GPIOF_INIT_LOW, "PCMCIA Power 2" }, { GPIO_NR_PALMTX_PCMCIA_RESET, GPIOF_INIT_HIGH,"PCMCIA Reset" }, - { GPIO_NR_PALMTX_PCMCIA_READY, GPIOF_IN, "PCMCIA Ready" }, }; static int palmtx_pcmcia_hw_init(struct soc_pcmcia_socket *skt) @@ -33,7 +32,8 @@ static int palmtx_pcmcia_hw_init(struct soc_pcmcia_socket *skt) ret = gpio_request_array(palmtx_pcmcia_gpios, ARRAY_SIZE(palmtx_pcmcia_gpios)); - skt->socket.pci_irq = gpio_to_irq(GPIO_NR_PALMTX_PCMCIA_READY); + skt->stat[SOC_STAT_RDY].gpio = GPIO_NR_PALMTX_PCMCIA_READY; + skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready"; return ret; } @@ -47,10 +47,6 @@ static void palmtx_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { state->detect = 1; /* always inserted */ - state->ready = !!gpio_get_value(GPIO_NR_PALMTX_PCMCIA_READY); - state->bvd1 = 1; - state->bvd2 = 1; - state->wrprot = 0; state->vs_3v = 1; state->vs_Xv = 0; } diff --git a/drivers/pcmcia/pxa2xx_sharpsl.c b/drivers/pcmcia/pxa2xx_sharpsl.c index 69ae2fd22400..b066273b6b4f 100644 --- a/drivers/pcmcia/pxa2xx_sharpsl.c +++ b/drivers/pcmcia/pxa2xx_sharpsl.c @@ -46,21 +46,9 @@ static void sharpsl_pcmcia_init_reset(struct soc_pcmcia_socket *skt) static int sharpsl_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { - int ret; - - /* Register interrupts */ if (SCOOP_DEV[skt->nr].cd_irq >= 0) { - struct pcmcia_irqs cd_irq; - - cd_irq.sock = skt->nr; - cd_irq.irq = SCOOP_DEV[skt->nr].cd_irq; - cd_irq.str = SCOOP_DEV[skt->nr].cd_irq_str; - ret = soc_pcmcia_request_irqs(skt, &cd_irq, 1); - - if (ret) { - printk(KERN_ERR "Request for Compact Flash IRQ failed\n"); - return ret; - } + skt->stat[SOC_STAT_CD].irq = SCOOP_DEV[skt->nr].cd_irq; + skt->stat[SOC_STAT_CD].name = SCOOP_DEV[skt->nr].cd_irq_str; } skt->socket.pci_irq = SCOOP_DEV[skt->nr].irq; @@ -68,19 +56,6 @@ static int sharpsl_pcmcia_hw_init(struct soc_pcmcia_socket *skt) return 0; } -static void sharpsl_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - if (SCOOP_DEV[skt->nr].cd_irq >= 0) { - struct pcmcia_irqs cd_irq; - - cd_irq.sock = skt->nr; - cd_irq.irq = SCOOP_DEV[skt->nr].cd_irq; - cd_irq.str = SCOOP_DEV[skt->nr].cd_irq_str; - soc_pcmcia_free_irqs(skt, &cd_irq, 1); - } -} - - static void sharpsl_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { @@ -222,7 +197,6 @@ static void sharpsl_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) static struct pcmcia_low_level sharpsl_pcmcia_ops __initdata = { .owner = THIS_MODULE, .hw_init = sharpsl_pcmcia_hw_init, - .hw_shutdown = sharpsl_pcmcia_hw_shutdown, .socket_state = sharpsl_pcmcia_socket_state, .configure_socket = sharpsl_pcmcia_configure_socket, .socket_init = sharpsl_pcmcia_socket_init, diff --git a/drivers/pcmcia/pxa2xx_stargate2.c b/drivers/pcmcia/pxa2xx_stargate2.c index 6c2366b74a35..1d73c4401fdd 100644 --- a/drivers/pcmcia/pxa2xx_stargate2.c +++ b/drivers/pcmcia/pxa2xx_stargate2.c @@ -33,10 +33,6 @@ #define SG2_S0_GPIO_DETECT 53 #define SG2_S0_GPIO_READY 81 -static struct pcmcia_irqs irqs[] = { - {.sock = 0, .str = "PCMCIA0 CD" }, -}; - static struct gpio sg2_pcmcia_gpios[] = { { SG2_S0_GPIO_RESET, GPIOF_OUT_INIT_HIGH, "PCMCIA Reset" }, { SG2_S0_POWER_CTL, GPIOF_OUT_INIT_HIGH, "PCMCIA Power Ctrl" }, @@ -44,27 +40,20 @@ static struct gpio sg2_pcmcia_gpios[] = { static int sg2_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { - skt->socket.pci_irq = gpio_to_irq(SG2_S0_GPIO_READY); - irqs[0].irq = gpio_to_irq(SG2_S0_GPIO_DETECT); - - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -static void sg2_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + skt->stat[SOC_STAT_CD].gpio = SG2_S0_GPIO_DETECT; + skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD"; + skt->stat[SOC_STAT_RDY].gpio = SG2_S0_GPIO_READY; + skt->stat[SOC_STAT_RDY].name = "PCMCIA0 RDY"; + return 0; } static void sg2_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - state->detect = !gpio_get_value(SG2_S0_GPIO_DETECT); - state->ready = !!gpio_get_value(SG2_S0_GPIO_READY); state->bvd1 = 0; /* not available - battery detect on card */ state->bvd2 = 0; /* not available */ state->vs_3v = 1; /* not available - voltage detect for card */ state->vs_Xv = 0; /* not available */ - state->wrprot = 0; /* not available - write protect */ } static int sg2_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, @@ -94,24 +83,11 @@ static int sg2_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, return 0; } -static void sg2_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -static void sg2_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - static struct pcmcia_low_level sg2_pcmcia_ops __initdata = { .owner = THIS_MODULE, .hw_init = sg2_pcmcia_hw_init, - .hw_shutdown = sg2_pcmcia_hw_shutdown, .socket_state = sg2_pcmcia_socket_state, .configure_socket = sg2_pcmcia_configure_socket, - .socket_init = sg2_pcmcia_socket_init, - .socket_suspend = sg2_pcmcia_socket_suspend, .nr = 1, }; diff --git a/drivers/pcmcia/pxa2xx_trizeps4.c b/drivers/pcmcia/pxa2xx_trizeps4.c index 7c33f898135a..d326ba1fa1ce 100644 --- a/drivers/pcmcia/pxa2xx_trizeps4.c +++ b/drivers/pcmcia/pxa2xx_trizeps4.c @@ -29,32 +29,17 @@ extern void board_pcmcia_power(int power); -static struct pcmcia_irqs irqs[] = { - { .sock = 0, .str = "cs0_cd" } - /* on other baseboards we can have more inputs */ -}; - static int trizeps_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { - int ret, i; /* we dont have voltage/card/ready detection * so we dont need interrupts for it */ switch (skt->nr) { case 0: - if (gpio_request(GPIO_PRDY, "cf_irq") < 0) { - pr_err("%s: sock %d unable to request gpio %d\n", __func__, - skt->nr, GPIO_PRDY); - return -EBUSY; - } - if (gpio_direction_input(GPIO_PRDY) < 0) { - pr_err("%s: sock %d unable to set input gpio %d\n", __func__, - skt->nr, GPIO_PRDY); - gpio_free(GPIO_PRDY); - return -EINVAL; - } - skt->socket.pci_irq = gpio_to_irq(GPIO_PRDY); - irqs[0].irq = gpio_to_irq(GPIO_PCD); + skt->stat[SOC_STAT_CD].gpio = GPIO_PCD; + skt->stat[SOC_STAT_CD].name = "cs0_cd"; + skt->stat[SOC_STAT_RDY].gpio = GPIO_PRDY; + skt->stat[SOC_STAT_RDY].name = "cs0_rdy"; break; default: break; @@ -62,39 +47,7 @@ static int trizeps_pcmcia_hw_init(struct soc_pcmcia_socket *skt) /* release the reset of this card */ pr_debug("%s: sock %d irq %d\n", __func__, skt->nr, skt->socket.pci_irq); - /* supplementory irqs for the socket */ - for (i = 0; i < ARRAY_SIZE(irqs); i++) { - if (irqs[i].sock != skt->nr) - continue; - if (gpio_request(irq_to_gpio(irqs[i].irq), irqs[i].str) < 0) { - pr_err("%s: sock %d unable to request gpio %d\n", - __func__, skt->nr, irq_to_gpio(irqs[i].irq)); - ret = -EBUSY; - goto error; - } - if (gpio_direction_input(irq_to_gpio(irqs[i].irq)) < 0) { - pr_err("%s: sock %d unable to set input gpio %d\n", - __func__, skt->nr, irq_to_gpio(irqs[i].irq)); - ret = -EINVAL; - goto error; - } - } - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); - -error: - for (; i >= 0; i--) { - gpio_free(irq_to_gpio(irqs[i].irq)); - } - return (ret); -} - -static void trizeps_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - int i; - /* free allocated gpio's */ - gpio_free(GPIO_PRDY); - for (i = 0; i < ARRAY_SIZE(irqs); i++) - gpio_free(irq_to_gpio(irqs[i].irq)); + return 0; } static unsigned long trizeps_pcmcia_status[2]; @@ -118,13 +71,10 @@ static void trizeps_pcmcia_socket_state(struct soc_pcmcia_socket *skt, switch (skt->nr) { case 0: /* just fill in fix states */ - state->detect = gpio_get_value(GPIO_PCD) ? 0 : 1; - state->ready = gpio_get_value(GPIO_PRDY) ? 1 : 0; state->bvd1 = (status & ConXS_CFSR_BVD1) ? 1 : 0; state->bvd2 = (status & ConXS_CFSR_BVD2) ? 1 : 0; state->vs_3v = (status & ConXS_CFSR_VS1) ? 0 : 1; state->vs_Xv = (status & ConXS_CFSR_VS2) ? 0 : 1; - state->wrprot = 0; /* not available */ break; #ifndef CONFIG_MACH_TRIZEPS_CONXS @@ -136,7 +86,6 @@ static void trizeps_pcmcia_socket_state(struct soc_pcmcia_socket *skt, state->bvd2 = 0; state->vs_3v = 0; state->vs_Xv = 0; - state->wrprot = 0; break; #endif @@ -204,7 +153,6 @@ static void trizeps_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) static struct pcmcia_low_level trizeps_pcmcia_ops = { .owner = THIS_MODULE, .hw_init = trizeps_pcmcia_hw_init, - .hw_shutdown = trizeps_pcmcia_hw_shutdown, .socket_state = trizeps_pcmcia_socket_state, .configure_socket = trizeps_pcmcia_configure_socket, .socket_init = trizeps_pcmcia_socket_init, diff --git a/drivers/pcmcia/pxa2xx_viper.c b/drivers/pcmcia/pxa2xx_viper.c index 1064b1c2869d..adfae4987a42 100644 --- a/drivers/pcmcia/pxa2xx_viper.c +++ b/drivers/pcmcia/pxa2xx_viper.c @@ -32,13 +32,6 @@ static struct platform_device *arcom_pcmcia_dev; -static struct pcmcia_irqs irqs[] = { - { - .sock = 0, - .str = "PCMCIA_CD", - }, -}; - static inline struct arcom_pcmcia_pdata *viper_get_pdata(void) { return arcom_pcmcia_dev->dev.platform_data; @@ -49,38 +42,28 @@ static int viper_pcmcia_hw_init(struct soc_pcmcia_socket *skt) struct arcom_pcmcia_pdata *pdata = viper_get_pdata(); unsigned long flags; - skt->socket.pci_irq = gpio_to_irq(pdata->rdy_gpio); - irqs[0].irq = gpio_to_irq(pdata->cd_gpio); - - if (gpio_request(pdata->cd_gpio, "CF detect")) - goto err_request_cd; - - if (gpio_request(pdata->rdy_gpio, "CF ready")) - goto err_request_rdy; + skt->stat[SOC_STAT_CD].gpio = pdata->cd_gpio; + skt->stat[SOC_STAT_CD].name = "PCMCIA_CD"; + skt->stat[SOC_STAT_RDY].gpio = pdata->rdy_gpio; + skt->stat[SOC_STAT_RDY].name = "CF ready"; if (gpio_request(pdata->pwr_gpio, "CF power")) goto err_request_pwr; local_irq_save(flags); - if (gpio_direction_output(pdata->pwr_gpio, 0) || - gpio_direction_input(pdata->cd_gpio) || - gpio_direction_input(pdata->rdy_gpio)) { + if (gpio_direction_output(pdata->pwr_gpio, 0)) { local_irq_restore(flags); goto err_dir; } local_irq_restore(flags); - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); + return 0; err_dir: gpio_free(pdata->pwr_gpio); err_request_pwr: - gpio_free(pdata->rdy_gpio); -err_request_rdy: - gpio_free(pdata->cd_gpio); -err_request_cd: dev_err(&arcom_pcmcia_dev->dev, "Failed to setup PCMCIA GPIOs\n"); return -1; } @@ -92,22 +75,12 @@ static void viper_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) { struct arcom_pcmcia_pdata *pdata = viper_get_pdata(); - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); gpio_free(pdata->pwr_gpio); - gpio_free(pdata->rdy_gpio); - gpio_free(pdata->cd_gpio); } static void viper_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - struct arcom_pcmcia_pdata *pdata = viper_get_pdata(); - - state->detect = !gpio_get_value(pdata->cd_gpio); - state->ready = !!gpio_get_value(pdata->rdy_gpio); - state->bvd1 = 1; - state->bvd2 = 1; - state->wrprot = 0; state->vs_3v = 1; /* Can only apply 3.3V */ state->vs_Xv = 0; } diff --git a/drivers/pcmcia/pxa2xx_vpac270.c b/drivers/pcmcia/pxa2xx_vpac270.c index 61b17d235dbe..a47dcd24a26a 100644 --- a/drivers/pcmcia/pxa2xx_vpac270.c +++ b/drivers/pcmcia/pxa2xx_vpac270.c @@ -23,29 +23,14 @@ #include "soc_common.h" static struct gpio vpac270_pcmcia_gpios[] = { - { GPIO84_VPAC270_PCMCIA_CD, GPIOF_IN, "PCMCIA Card Detect" }, - { GPIO35_VPAC270_PCMCIA_RDY, GPIOF_IN, "PCMCIA Ready" }, { GPIO107_VPAC270_PCMCIA_PPEN, GPIOF_INIT_LOW, "PCMCIA PPEN" }, { GPIO11_VPAC270_PCMCIA_RESET, GPIOF_INIT_LOW, "PCMCIA Reset" }, }; static struct gpio vpac270_cf_gpios[] = { - { GPIO17_VPAC270_CF_CD, GPIOF_IN, "CF Card Detect" }, - { GPIO12_VPAC270_CF_RDY, GPIOF_IN, "CF Ready" }, { GPIO16_VPAC270_CF_RESET, GPIOF_INIT_LOW, "CF Reset" }, }; -static struct pcmcia_irqs cd_irqs[] = { - { - .sock = 0, - .str = "PCMCIA CD" - }, - { - .sock = 1, - .str = "CF CD" - }, -}; - static int vpac270_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { int ret; @@ -54,20 +39,18 @@ static int vpac270_pcmcia_hw_init(struct soc_pcmcia_socket *skt) ret = gpio_request_array(vpac270_pcmcia_gpios, ARRAY_SIZE(vpac270_pcmcia_gpios)); - skt->socket.pci_irq = gpio_to_irq(GPIO35_VPAC270_PCMCIA_RDY); - cd_irqs[0].irq = gpio_to_irq(GPIO84_VPAC270_PCMCIA_CD); - - if (!ret) - ret = soc_pcmcia_request_irqs(skt, &cd_irqs[0], 1); + skt->stat[SOC_STAT_CD].gpio = GPIO84_VPAC270_PCMCIA_CD; + skt->stat[SOC_STAT_CD].name = "PCMCIA CD"; + skt->stat[SOC_STAT_RDY].gpio = GPIO35_VPAC270_PCMCIA_RDY; + skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready"; } else { ret = gpio_request_array(vpac270_cf_gpios, ARRAY_SIZE(vpac270_cf_gpios)); - skt->socket.pci_irq = gpio_to_irq(GPIO12_VPAC270_CF_RDY); - cd_irqs[1].irq = gpio_to_irq(GPIO17_VPAC270_CF_CD); - - if (!ret) - ret = soc_pcmcia_request_irqs(skt, &cd_irqs[1], 1); + skt->stat[SOC_STAT_CD].gpio = GPIO17_VPAC270_CF_CD; + skt->stat[SOC_STAT_CD].name = "CF CD"; + skt->stat[SOC_STAT_RDY].gpio = GPIO12_VPAC270_CF_RDY; + skt->stat[SOC_STAT_RDY].name = "CF Ready"; } return ret; @@ -86,16 +69,6 @@ static void vpac270_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) static void vpac270_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - if (skt->nr == 0) { - state->detect = !gpio_get_value(GPIO84_VPAC270_PCMCIA_CD); - state->ready = !!gpio_get_value(GPIO35_VPAC270_PCMCIA_RDY); - } else { - state->detect = !gpio_get_value(GPIO17_VPAC270_CF_CD); - state->ready = !!gpio_get_value(GPIO12_VPAC270_CF_RDY); - } - state->bvd1 = 1; - state->bvd2 = 1; - state->wrprot = 0; state->vs_3v = 1; state->vs_Xv = 0; } @@ -117,14 +90,6 @@ vpac270_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, return 0; } -static void vpac270_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ -} - -static void vpac270_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) -{ -} - static struct pcmcia_low_level vpac270_pcmcia_ops = { .owner = THIS_MODULE, @@ -136,9 +101,6 @@ static struct pcmcia_low_level vpac270_pcmcia_ops = { .socket_state = vpac270_pcmcia_socket_state, .configure_socket = vpac270_pcmcia_configure_socket, - - .socket_init = vpac270_pcmcia_socket_init, - .socket_suspend = vpac270_pcmcia_socket_suspend, }; static struct platform_device *vpac270_pcmcia_device; diff --git a/drivers/pcmcia/sa1100_assabet.c b/drivers/pcmcia/sa1100_assabet.c index f1e882272ab0..ba8557eea618 100644 --- a/drivers/pcmcia/sa1100_assabet.c +++ b/drivers/pcmcia/sa1100_assabet.c @@ -10,46 +10,30 @@ #include <linux/interrupt.h> #include <linux/device.h> #include <linux/init.h> +#include <linux/gpio.h> -#include <mach/hardware.h> #include <asm/mach-types.h> -#include <asm/irq.h> -#include <asm/signal.h> #include <mach/assabet.h> #include "sa1100_generic.h" -static struct pcmcia_irqs irqs[] = { - { 1, ASSABET_IRQ_GPIO_CF_CD, "CF CD" }, - { 1, ASSABET_IRQ_GPIO_CF_BVD2, "CF BVD2" }, - { 1, ASSABET_IRQ_GPIO_CF_BVD1, "CF BVD1" }, -}; - static int assabet_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { - skt->socket.pci_irq = ASSABET_IRQ_GPIO_CF_IRQ; - - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} + skt->stat[SOC_STAT_CD].gpio = ASSABET_GPIO_CF_CD; + skt->stat[SOC_STAT_CD].name = "CF CD"; + skt->stat[SOC_STAT_BVD1].gpio = ASSABET_GPIO_CF_BVD1; + skt->stat[SOC_STAT_BVD1].name = "CF BVD1"; + skt->stat[SOC_STAT_BVD2].gpio = ASSABET_GPIO_CF_BVD2; + skt->stat[SOC_STAT_BVD2].name = "CF BVD2"; + skt->stat[SOC_STAT_RDY].gpio = ASSABET_GPIO_CF_IRQ; + skt->stat[SOC_STAT_RDY].name = "CF RDY"; -/* - * Release all resources. - */ -static void assabet_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + return 0; } static void assabet_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - unsigned long levels = GPLR; - - state->detect = (levels & ASSABET_GPIO_CF_CD) ? 0 : 1; - state->ready = (levels & ASSABET_GPIO_CF_IRQ) ? 1 : 0; - state->bvd1 = (levels & ASSABET_GPIO_CF_BVD1) ? 1 : 0; - state->bvd2 = (levels & ASSABET_GPIO_CF_BVD2) ? 1 : 0; - state->wrprot = 0; /* Not available on Assabet. */ state->vs_3v = 1; /* Can only apply 3.3V on Assabet. */ state->vs_Xv = 0; } @@ -78,38 +62,24 @@ assabet_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_stat return -1; } - /* Silently ignore Vpp, output enable, speaker enable. */ + /* Silently ignore Vpp, speaker enable. */ if (state->flags & SS_RESET) mask |= ASSABET_BCR_CF_RST; + if (!(state->flags & SS_OUTPUT_ENA)) + mask |= ASSABET_BCR_CF_BUS_OFF; - ASSABET_BCR_frob(ASSABET_BCR_CF_RST | ASSABET_BCR_CF_PWR, mask); + ASSABET_BCR_frob(ASSABET_BCR_CF_RST | ASSABET_BCR_CF_PWR | + ASSABET_BCR_CF_BUS_OFF, mask); return 0; } /* - * Enable card status IRQs on (re-)initialisation. This can - * be called at initialisation, power management event, or - * pcmcia event. - */ -static void assabet_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - /* - * Enable CF bus - */ - ASSABET_BCR_clear(ASSABET_BCR_CF_BUS_OFF); - - soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -/* * Disable card status IRQs on suspend. */ static void assabet_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) { - soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); - /* * Tristate the CF bus signals. Also assert CF * reset as per user guide page 4-11. @@ -119,14 +89,9 @@ static void assabet_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) static struct pcmcia_low_level assabet_pcmcia_ops = { .owner = THIS_MODULE, - .hw_init = assabet_pcmcia_hw_init, - .hw_shutdown = assabet_pcmcia_hw_shutdown, - .socket_state = assabet_pcmcia_socket_state, .configure_socket = assabet_pcmcia_configure_socket, - - .socket_init = assabet_pcmcia_socket_init, .socket_suspend = assabet_pcmcia_socket_suspend, }; diff --git a/drivers/pcmcia/sa1100_cerf.c b/drivers/pcmcia/sa1100_cerf.c index 30560df8c76b..c59c44921a3a 100644 --- a/drivers/pcmcia/sa1100_cerf.c +++ b/drivers/pcmcia/sa1100_cerf.c @@ -10,6 +10,7 @@ #include <linux/device.h> #include <linux/init.h> #include <linux/delay.h> +#include <linux/gpio.h> #include <mach/hardware.h> #include <asm/mach-types.h> @@ -19,34 +20,34 @@ #define CERF_SOCKET 1 -static struct pcmcia_irqs irqs[] = { - { CERF_SOCKET, CERF_IRQ_GPIO_CF_CD, "CF_CD" }, - { CERF_SOCKET, CERF_IRQ_GPIO_CF_BVD2, "CF_BVD2" }, - { CERF_SOCKET, CERF_IRQ_GPIO_CF_BVD1, "CF_BVD1" } -}; - static int cerf_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { - skt->socket.pci_irq = CERF_IRQ_GPIO_CF_IRQ; + int ret; + + ret = gpio_request_one(CERF_GPIO_CF_RESET, GPIOF_OUT_INIT_LOW, "CF_RESET"); + if (ret) + return ret; + + skt->stat[SOC_STAT_CD].gpio = CERF_GPIO_CF_CD; + skt->stat[SOC_STAT_CD].name = "CF_CD"; + skt->stat[SOC_STAT_BVD1].gpio = CERF_GPIO_CF_BVD1; + skt->stat[SOC_STAT_BVD1].name = "CF_BVD1"; + skt->stat[SOC_STAT_BVD2].gpio = CERF_GPIO_CF_BVD2; + skt->stat[SOC_STAT_BVD2].name = "CF_BVD2"; + skt->stat[SOC_STAT_RDY].gpio = CERF_GPIO_CF_IRQ; + skt->stat[SOC_STAT_RDY].name = "CF_IRQ"; - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); + return 0; } static void cerf_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) { - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + gpio_free(CERF_GPIO_CF_RESET); } static void cerf_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - unsigned long levels = GPLR; - - state->detect = (levels & CERF_GPIO_CF_CD) ?0:1; - state->ready = (levels & CERF_GPIO_CF_IRQ) ?1:0; - state->bvd1 = (levels & CERF_GPIO_CF_BVD1)?1:0; - state->bvd2 = (levels & CERF_GPIO_CF_BVD2)?1:0; - state->wrprot = 0; state->vs_3v = 1; state->vs_Xv = 0; } @@ -67,34 +68,17 @@ cerf_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, return -1; } - if (state->flags & SS_RESET) { - GPSR = CERF_GPIO_CF_RESET; - } else { - GPCR = CERF_GPIO_CF_RESET; - } + gpio_set_value(CERF_GPIO_CF_RESET, !!(state->flags & SS_RESET)); return 0; } -static void cerf_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -static void cerf_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - static struct pcmcia_low_level cerf_pcmcia_ops = { .owner = THIS_MODULE, .hw_init = cerf_pcmcia_hw_init, .hw_shutdown = cerf_pcmcia_hw_shutdown, .socket_state = cerf_pcmcia_socket_state, .configure_socket = cerf_pcmcia_configure_socket, - - .socket_init = cerf_pcmcia_socket_init, - .socket_suspend = cerf_pcmcia_socket_suspend, }; int __devinit pcmcia_cerf_init(struct device *dev) diff --git a/drivers/pcmcia/sa1100_h3600.c b/drivers/pcmcia/sa1100_h3600.c index edf8f0028898..d9c7337b909c 100644 --- a/drivers/pcmcia/sa1100_h3600.c +++ b/drivers/pcmcia/sa1100_h3600.c @@ -19,36 +19,20 @@ #include "sa1100_generic.h" -static struct pcmcia_irqs irqs[] = { - { .sock = 0, .str = "PCMCIA CD0" }, /* .irq will be filled later */ - { .sock = 1, .str = "PCMCIA CD1" } -}; - static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { int err; switch (skt->nr) { case 0: - err = gpio_request(H3XXX_GPIO_PCMCIA_IRQ0, "PCMCIA IRQ0"); - if (err) - goto err00; - err = gpio_direction_input(H3XXX_GPIO_PCMCIA_IRQ0); - if (err) - goto err01; - skt->socket.pci_irq = gpio_to_irq(H3XXX_GPIO_PCMCIA_IRQ0); - - err = gpio_request(H3XXX_GPIO_PCMCIA_CD0, "PCMCIA CD0"); - if (err) - goto err01; - err = gpio_direction_input(H3XXX_GPIO_PCMCIA_CD0); - if (err) - goto err02; - irqs[0].irq = gpio_to_irq(H3XXX_GPIO_PCMCIA_CD0); + skt->stat[SOC_STAT_CD].gpio = H3XXX_GPIO_PCMCIA_CD0; + skt->stat[SOC_STAT_CD].name = "PCMCIA CD0"; + skt->stat[SOC_STAT_RDY].gpio = H3XXX_GPIO_PCMCIA_IRQ0; + skt->stat[SOC_STAT_RDY].name = "PCMCIA IRQ0"; err = gpio_request(H3XXX_EGPIO_OPT_NVRAM_ON, "OPT NVRAM ON"); if (err) - goto err02; + goto err01; err = gpio_direction_output(H3XXX_EGPIO_OPT_NVRAM_ON, 0); if (err) goto err03; @@ -70,30 +54,12 @@ static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt) err = gpio_direction_output(H3XXX_EGPIO_CARD_RESET, 0); if (err) goto err06; - err = soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); - if (err) - goto err06; break; case 1: - err = gpio_request(H3XXX_GPIO_PCMCIA_IRQ1, "PCMCIA IRQ1"); - if (err) - goto err10; - err = gpio_direction_input(H3XXX_GPIO_PCMCIA_IRQ1); - if (err) - goto err11; - skt->socket.pci_irq = gpio_to_irq(H3XXX_GPIO_PCMCIA_IRQ1); - - err = gpio_request(H3XXX_GPIO_PCMCIA_CD1, "PCMCIA CD1"); - if (err) - goto err11; - err = gpio_direction_input(H3XXX_GPIO_PCMCIA_CD1); - if (err) - goto err12; - irqs[1].irq = gpio_to_irq(H3XXX_GPIO_PCMCIA_CD1); - - err = soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); - if (err) - goto err12; + skt->stat[SOC_STAT_CD].gpio = H3XXX_GPIO_PCMCIA_CD1; + skt->stat[SOC_STAT_CD].name = "PCMCIA CD1"; + skt->stat[SOC_STAT_RDY].gpio = H3XXX_GPIO_PCMCIA_IRQ1; + skt->stat[SOC_STAT_RDY].name = "PCMCIA IRQ1"; break; } return 0; @@ -102,19 +68,12 @@ err06: gpio_free(H3XXX_EGPIO_CARD_RESET); err05: gpio_free(H3XXX_EGPIO_OPT_RESET); err04: gpio_free(H3XXX_EGPIO_OPT_ON); err03: gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON); -err02: gpio_free(H3XXX_GPIO_PCMCIA_CD0); err01: gpio_free(H3XXX_GPIO_PCMCIA_IRQ0); -err00: return err; - -err12: gpio_free(H3XXX_GPIO_PCMCIA_CD0); -err11: gpio_free(H3XXX_GPIO_PCMCIA_IRQ0); -err10: return err; + return err; } static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) { - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); - switch (skt->nr) { case 0: /* Disable CF bus: */ @@ -126,12 +85,8 @@ static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) gpio_free(H3XXX_EGPIO_OPT_RESET); gpio_free(H3XXX_EGPIO_OPT_ON); gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON); - gpio_free(H3XXX_GPIO_PCMCIA_CD0); - gpio_free(H3XXX_GPIO_PCMCIA_IRQ0); break; case 1: - gpio_free(H3XXX_GPIO_PCMCIA_CD1); - gpio_free(H3XXX_GPIO_PCMCIA_IRQ1); break; } } @@ -139,27 +94,10 @@ static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) static void h3600_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - switch (skt->nr) { - case 0: - state->detect = !gpio_get_value(H3XXX_GPIO_PCMCIA_CD0); - state->ready = !!gpio_get_value(H3XXX_GPIO_PCMCIA_IRQ0); - state->bvd1 = 0; - state->bvd2 = 0; - state->wrprot = 0; /* Not available on H3600. */ - state->vs_3v = 0; - state->vs_Xv = 0; - break; - - case 1: - state->detect = !gpio_get_value(H3XXX_GPIO_PCMCIA_CD1); - state->ready = !!gpio_get_value(H3XXX_GPIO_PCMCIA_IRQ1); - state->bvd1 = 0; - state->bvd2 = 0; - state->wrprot = 0; /* Not available on H3600. */ - state->vs_3v = 0; - state->vs_Xv = 0; - break; - } + state->bvd1 = 0; + state->bvd2 = 0; + state->vs_3v = 0; + state->vs_Xv = 0; } static int @@ -186,14 +124,10 @@ static void h3600_pcmcia_socket_init(struct soc_pcmcia_socket *skt) gpio_set_value(H3XXX_EGPIO_OPT_RESET, 0); msleep(10); - - soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); } static void h3600_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) { - soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); - /* * FIXME: This doesn't fit well. We don't have the mechanism in * the generic PCMCIA layer to deal with the idea of two sockets diff --git a/drivers/pcmcia/sa1100_nanoengine.c b/drivers/pcmcia/sa1100_nanoengine.c index 93b9c9ba57c3..35c30ff41e81 100644 --- a/drivers/pcmcia/sa1100_nanoengine.c +++ b/drivers/pcmcia/sa1100_nanoengine.c @@ -19,6 +19,7 @@ */ #include <linux/device.h> #include <linux/errno.h> +#include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/init.h> @@ -34,43 +35,23 @@ #include "sa1100_generic.h" -static struct pcmcia_irqs irqs_skt0[] = { - /* socket, IRQ, name */ - { 0, NANOENGINE_IRQ_GPIO_PC_CD0, "PC CD0" }, -}; - -static struct pcmcia_irqs irqs_skt1[] = { - /* socket, IRQ, name */ - { 1, NANOENGINE_IRQ_GPIO_PC_CD1, "PC CD1" }, -}; - struct nanoengine_pins { - unsigned input_pins; unsigned output_pins; unsigned clear_outputs; - unsigned transition_pins; - unsigned pci_irq; - struct pcmcia_irqs *pcmcia_irqs; - unsigned pcmcia_irqs_size; + int gpio_rst; + int gpio_cd; + int gpio_rdy; }; static struct nanoengine_pins nano_skts[] = { { - .input_pins = GPIO_PC_READY0 | GPIO_PC_CD0, - .output_pins = GPIO_PC_RESET0, - .clear_outputs = GPIO_PC_RESET0, - .transition_pins = NANOENGINE_IRQ_GPIO_PC_CD0, - .pci_irq = NANOENGINE_IRQ_GPIO_PC_READY0, - .pcmcia_irqs = irqs_skt0, - .pcmcia_irqs_size = ARRAY_SIZE(irqs_skt0) + .gpio_rst = GPIO_PC_RESET0, + .gpio_cd = GPIO_PC_CD0, + .gpio_rdy = GPIO_PC_READY0, }, { - .input_pins = GPIO_PC_READY1 | GPIO_PC_CD1, - .output_pins = GPIO_PC_RESET1, - .clear_outputs = GPIO_PC_RESET1, - .transition_pins = NANOENGINE_IRQ_GPIO_PC_CD1, - .pci_irq = NANOENGINE_IRQ_GPIO_PC_READY1, - .pcmcia_irqs = irqs_skt1, - .pcmcia_irqs_size = ARRAY_SIZE(irqs_skt1) + .gpio_rst = GPIO_PC_RESET1, + .gpio_cd = GPIO_PC_CD1, + .gpio_rdy = GPIO_PC_READY1, } }; @@ -79,58 +60,38 @@ unsigned num_nano_pcmcia_sockets = ARRAY_SIZE(nano_skts); static int nanoengine_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { unsigned i = skt->nr; + int ret; if (i >= num_nano_pcmcia_sockets) return -ENXIO; - GPDR &= ~nano_skts[i].input_pins; - GPDR |= nano_skts[i].output_pins; - GPCR = nano_skts[i].clear_outputs; - irq_set_irq_type(nano_skts[i].transition_pins, IRQ_TYPE_EDGE_BOTH); - skt->socket.pci_irq = nano_skts[i].pci_irq; + ret = gpio_request_one(nano_skts[i].gpio_rst, GPIOF_OUT_INIT_LOW, + i ? "PC RST1" : "PC RST0"); + if (ret) + return ret; + + skt->stat[SOC_STAT_CD].gpio = nano_skts[i].gpio_cd; + skt->stat[SOC_STAT_CD].name = i ? "PC CD1" : "PC CD0"; + skt->stat[SOC_STAT_RDY].gpio = nano_skts[i].gpio_rdy; + skt->stat[SOC_STAT_RDY].name = i ? "PC RDY1" : "PC RDY0"; - return soc_pcmcia_request_irqs(skt, - nano_skts[i].pcmcia_irqs, nano_skts[i].pcmcia_irqs_size); + return 0; } -/* - * Release all resources. - */ static void nanoengine_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) { - unsigned i = skt->nr; - - if (i >= num_nano_pcmcia_sockets) - return; - - soc_pcmcia_free_irqs(skt, - nano_skts[i].pcmcia_irqs, nano_skts[i].pcmcia_irqs_size); + gpio_free(nano_skts[skt->nr].gpio_rst); } static int nanoengine_pcmcia_configure_socket( struct soc_pcmcia_socket *skt, const socket_state_t *state) { - unsigned reset; unsigned i = skt->nr; if (i >= num_nano_pcmcia_sockets) return -ENXIO; - switch (i) { - case 0: - reset = GPIO_PC_RESET0; - break; - case 1: - reset = GPIO_PC_RESET1; - break; - default: - return -ENXIO; - } - - if (state->flags & SS_RESET) - GPSR = reset; - else - GPCR = reset; + gpio_set_value(nano_skts[skt->nr].gpio_rst, !!(state->flags & SS_RESET)); return 0; } @@ -138,62 +99,17 @@ static int nanoengine_pcmcia_configure_socket( static void nanoengine_pcmcia_socket_state( struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - unsigned long levels = GPLR; unsigned i = skt->nr; if (i >= num_nano_pcmcia_sockets) return; - memset(state, 0, sizeof(struct pcmcia_state)); - switch (i) { - case 0: - state->ready = (levels & GPIO_PC_READY0) ? 1 : 0; - state->detect = !(levels & GPIO_PC_CD0) ? 1 : 0; - break; - case 1: - state->ready = (levels & GPIO_PC_READY1) ? 1 : 0; - state->detect = !(levels & GPIO_PC_CD1) ? 1 : 0; - break; - default: - return; - } state->bvd1 = 1; state->bvd2 = 1; - state->wrprot = 0; /* Not available */ state->vs_3v = 1; /* Can only apply 3.3V */ state->vs_Xv = 0; } -/* - * Enable card status IRQs on (re-)initialisation. This can - * be called at initialisation, power management event, or - * pcmcia event. - */ -static void nanoengine_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - unsigned i = skt->nr; - - if (i >= num_nano_pcmcia_sockets) - return; - - soc_pcmcia_enable_irqs(skt, - nano_skts[i].pcmcia_irqs, nano_skts[i].pcmcia_irqs_size); -} - -/* - * Disable card status IRQs on suspend. - */ -static void nanoengine_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) -{ - unsigned i = skt->nr; - - if (i >= num_nano_pcmcia_sockets) - return; - - soc_pcmcia_disable_irqs(skt, - nano_skts[i].pcmcia_irqs, nano_skts[i].pcmcia_irqs_size); -} - static struct pcmcia_low_level nanoengine_pcmcia_ops = { .owner = THIS_MODULE, @@ -202,8 +118,6 @@ static struct pcmcia_low_level nanoengine_pcmcia_ops = { .configure_socket = nanoengine_pcmcia_configure_socket, .socket_state = nanoengine_pcmcia_socket_state, - .socket_init = nanoengine_pcmcia_socket_init, - .socket_suspend = nanoengine_pcmcia_socket_suspend, }; int pcmcia_nanoengine_init(struct device *dev) diff --git a/drivers/pcmcia/sa1100_shannon.c b/drivers/pcmcia/sa1100_shannon.c index 7ff1b43540b8..decb34730bcf 100644 --- a/drivers/pcmcia/sa1100_shannon.c +++ b/drivers/pcmcia/sa1100_shannon.c @@ -15,40 +15,35 @@ #include <asm/irq.h> #include "sa1100_generic.h" -static struct pcmcia_irqs irqs[] = { - { 0, SHANNON_IRQ_GPIO_EJECT_0, "PCMCIA_CD_0" }, - { 1, SHANNON_IRQ_GPIO_EJECT_1, "PCMCIA_CD_1" }, -}; - static int shannon_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { /* All those are inputs */ - GPDR &= ~(SHANNON_GPIO_EJECT_0 | SHANNON_GPIO_EJECT_1 | - SHANNON_GPIO_RDY_0 | SHANNON_GPIO_RDY_1); - GAFR &= ~(SHANNON_GPIO_EJECT_0 | SHANNON_GPIO_EJECT_1 | - SHANNON_GPIO_RDY_0 | SHANNON_GPIO_RDY_1); - - skt->socket.pci_irq = skt->nr ? SHANNON_IRQ_GPIO_RDY_1 : SHANNON_IRQ_GPIO_RDY_0; - - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} + GAFR &= ~(GPIO_GPIO(SHANNON_GPIO_EJECT_0) | + GPIO_GPIO(SHANNON_GPIO_EJECT_1) | + GPIO_GPIO(SHANNON_GPIO_RDY_0) | + GPIO_GPIO(SHANNON_GPIO_RDY_1)); + + if (skt->nr == 0) { + skt->stat[SOC_STAT_CD].gpio = SHANNON_GPIO_EJECT_0; + skt->stat[SOC_STAT_CD].name = "PCMCIA_CD_0"; + skt->stat[SOC_STAT_RDY].gpio = SHANNON_GPIO_RDY_0; + skt->stat[SOC_STAT_RDY].name = "PCMCIA_RDY_0"; + } else { + skt->stat[SOC_STAT_CD].gpio = SHANNON_GPIO_EJECT_1; + skt->stat[SOC_STAT_CD].name = "PCMCIA_CD_1"; + skt->stat[SOC_STAT_RDY].gpio = SHANNON_GPIO_RDY_1; + skt->stat[SOC_STAT_RDY].name = "PCMCIA_RDY_1"; + } -static void shannon_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + return 0; } static void shannon_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - unsigned long levels = GPLR; - switch (skt->nr) { case 0: - state->detect = (levels & SHANNON_GPIO_EJECT_0) ? 0 : 1; - state->ready = (levels & SHANNON_GPIO_RDY_0) ? 1 : 0; - state->wrprot = 0; /* Not available on Shannon. */ state->bvd1 = 1; state->bvd2 = 1; state->vs_3v = 1; /* FIXME Can only apply 3.3V on Shannon. */ @@ -56,9 +51,6 @@ shannon_pcmcia_socket_state(struct soc_pcmcia_socket *skt, break; case 1: - state->detect = (levels & SHANNON_GPIO_EJECT_1) ? 0 : 1; - state->ready = (levels & SHANNON_GPIO_RDY_1) ? 1 : 0; - state->wrprot = 0; /* Not available on Shannon. */ state->bvd1 = 1; state->bvd2 = 1; state->vs_3v = 1; /* FIXME Can only apply 3.3V on Shannon. */ @@ -92,25 +84,11 @@ shannon_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, return 0; } -static void shannon_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -static void shannon_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - static struct pcmcia_low_level shannon_pcmcia_ops = { .owner = THIS_MODULE, .hw_init = shannon_pcmcia_hw_init, - .hw_shutdown = shannon_pcmcia_hw_shutdown, .socket_state = shannon_pcmcia_socket_state, .configure_socket = shannon_pcmcia_configure_socket, - - .socket_init = shannon_pcmcia_socket_init, - .socket_suspend = shannon_pcmcia_socket_suspend, }; int __devinit pcmcia_shannon_init(struct device *dev) diff --git a/drivers/pcmcia/sa1100_simpad.c b/drivers/pcmcia/sa1100_simpad.c index 0fac9658b020..8647b17c449e 100644 --- a/drivers/pcmcia/sa1100_simpad.c +++ b/drivers/pcmcia/sa1100_simpad.c @@ -15,24 +15,21 @@ #include <mach/simpad.h> #include "sa1100_generic.h" -static struct pcmcia_irqs irqs[] = { - { 1, IRQ_GPIO_CF_CD, "CF_CD" }, -}; - static int simpad_pcmcia_hw_init(struct soc_pcmcia_socket *skt) { simpad_clear_cs3_bit(VCC_3V_EN|VCC_5V_EN|EN0|EN1); - skt->socket.pci_irq = IRQ_GPIO_CF_IRQ; + skt->stat[SOC_STAT_CD].gpio = GPIO_CF_CD; + skt->stat[SOC_STAT_CD].name = "CF_CD"; + skt->stat[SOC_STAT_RDY].gpio = GPIO_CF_IRQ; + skt->stat[SOC_STAT_RDY].name = "CF_RDY"; - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); + return 0; } static void simpad_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) { - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); - /* Disable CF bus: */ /*simpad_set_cs3_bit(PCMCIA_BUFF_DIS);*/ simpad_clear_cs3_bit(PCMCIA_RESET); @@ -42,14 +39,13 @@ static void simpad_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { - unsigned long levels = GPLR; long cs3reg = simpad_get_cs3_ro(); - state->detect=((levels & GPIO_CF_CD)==0)?1:0; - state->ready=(levels & GPIO_CF_IRQ)?1:0; + /* the detect signal is inverted - fix that up here */ + state->detect = !state->detect; + state->bvd1 = 1; /* Might be cs3reg & PCMCIA_BVD1 */ state->bvd2 = 1; /* Might be cs3reg & PCMCIA_BVD2 */ - state->wrprot=0; /* Not available on Simpad. */ if ((cs3reg & (PCMCIA_VS1|PCMCIA_VS2)) == (PCMCIA_VS1|PCMCIA_VS2)) { @@ -99,14 +95,8 @@ simpad_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, return 0; } -static void simpad_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - static void simpad_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) { - soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); simpad_set_cs3_bit(PCMCIA_RESET); } @@ -116,7 +106,6 @@ static struct pcmcia_low_level simpad_pcmcia_ops = { .hw_shutdown = simpad_pcmcia_hw_shutdown, .socket_state = simpad_pcmcia_socket_state, .configure_socket = simpad_pcmcia_configure_socket, - .socket_init = simpad_pcmcia_socket_init, .socket_suspend = simpad_pcmcia_socket_suspend, }; diff --git a/drivers/pcmcia/sa1100_badge4.c b/drivers/pcmcia/sa1111_badge4.c index 1ce53f493bef..4d206f4dd67b 100644 --- a/drivers/pcmcia/sa1100_badge4.c +++ b/drivers/pcmcia/sa1111_badge4.c @@ -122,13 +122,12 @@ badge4_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state local_irq_restore(flags); } - return 0; + return ret; } static struct pcmcia_low_level badge4_pcmcia_ops = { .owner = THIS_MODULE, .configure_socket = badge4_pcmcia_configure_socket, - .socket_init = sa1111_pcmcia_socket_init, .first = 0, .nr = 2, }; diff --git a/drivers/pcmcia/sa1111_generic.c b/drivers/pcmcia/sa1111_generic.c index 27f2fe3b7fb4..70f728ce1856 100644 --- a/drivers/pcmcia/sa1111_generic.c +++ b/drivers/pcmcia/sa1111_generic.c @@ -22,6 +22,40 @@ #include "sa1111_generic.h" +/* + * These are offsets from the above base. + */ +#define PCCR 0x0000 +#define PCSSR 0x0004 +#define PCSR 0x0008 + +#define PCSR_S0_READY (1<<0) +#define PCSR_S1_READY (1<<1) +#define PCSR_S0_DETECT (1<<2) +#define PCSR_S1_DETECT (1<<3) +#define PCSR_S0_VS1 (1<<4) +#define PCSR_S0_VS2 (1<<5) +#define PCSR_S1_VS1 (1<<6) +#define PCSR_S1_VS2 (1<<7) +#define PCSR_S0_WP (1<<8) +#define PCSR_S1_WP (1<<9) +#define PCSR_S0_BVD1 (1<<10) +#define PCSR_S0_BVD2 (1<<11) +#define PCSR_S1_BVD1 (1<<12) +#define PCSR_S1_BVD2 (1<<13) + +#define PCCR_S0_RST (1<<0) +#define PCCR_S1_RST (1<<1) +#define PCCR_S0_FLT (1<<2) +#define PCCR_S1_FLT (1<<3) +#define PCCR_S0_PWAITEN (1<<4) +#define PCCR_S1_PWAITEN (1<<5) +#define PCCR_S0_PSE (1<<6) +#define PCCR_S1_PSE (1<<7) + +#define PCSSR_S0_SLEEP (1<<0) +#define PCSSR_S1_SLEEP (1<<1) + #define IDX_IRQ_S0_READY_NINT (0) #define IDX_IRQ_S0_CD_VALID (1) #define IDX_IRQ_S0_BVD1_STSCHG (2) @@ -29,27 +63,10 @@ #define IDX_IRQ_S1_CD_VALID (4) #define IDX_IRQ_S1_BVD1_STSCHG (5) -static struct pcmcia_irqs irqs[] = { - { 0, NO_IRQ, "SA1111 PCMCIA card detect" }, - { 0, NO_IRQ, "SA1111 PCMCIA BVD1" }, - { 1, NO_IRQ, "SA1111 CF card detect" }, - { 1, NO_IRQ, "SA1111 CF BVD1" }, -}; - -static int sa1111_pcmcia_hw_init(struct soc_pcmcia_socket *skt) -{ - return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -static void sa1111_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - void sa1111_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) { struct sa1111_pcmcia_socket *s = to_skt(skt); - unsigned long status = sa1111_readl(s->dev->mapbase + SA1111_PCSR); + unsigned long status = sa1111_readl(s->dev->mapbase + PCSR); switch (skt->nr) { case 0: @@ -105,35 +122,22 @@ int sa1111_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_s pccr_set_mask |= PCCR_S0_FLT|PCCR_S1_FLT; local_irq_save(flags); - val = sa1111_readl(s->dev->mapbase + SA1111_PCCR); + val = sa1111_readl(s->dev->mapbase + PCCR); val &= ~pccr_skt_mask; val |= pccr_set_mask & pccr_skt_mask; - sa1111_writel(val, s->dev->mapbase + SA1111_PCCR); + sa1111_writel(val, s->dev->mapbase + PCCR); local_irq_restore(flags); return 0; } -void sa1111_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - -static void sa1111_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) -{ - soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); -} - int sa1111_pcmcia_add(struct sa1111_dev *dev, struct pcmcia_low_level *ops, int (*add)(struct soc_pcmcia_socket *)) { struct sa1111_pcmcia_socket *s; int i, ret = 0; - ops->hw_init = sa1111_pcmcia_hw_init; - ops->hw_shutdown = sa1111_pcmcia_hw_shutdown; ops->socket_state = sa1111_pcmcia_socket_state; - ops->socket_suspend = sa1111_pcmcia_socket_suspend; for (i = 0; i < ops->nr; i++) { s = kzalloc(sizeof(*s), GFP_KERNEL); @@ -141,13 +145,21 @@ int sa1111_pcmcia_add(struct sa1111_dev *dev, struct pcmcia_low_level *ops, return -ENOMEM; s->soc.nr = ops->first + i; - s->soc.ops = ops; - s->soc.socket.owner = ops->owner; - s->soc.socket.dev.parent = &dev->dev; - s->soc.socket.pci_irq = s->soc.nr ? - dev->irq[IDX_IRQ_S0_READY_NINT] : - dev->irq[IDX_IRQ_S1_READY_NINT]; + soc_pcmcia_init_one(&s->soc, ops, &dev->dev); s->dev = dev; + if (s->soc.nr) { + s->soc.socket.pci_irq = dev->irq[IDX_IRQ_S1_READY_NINT]; + s->soc.stat[SOC_STAT_CD].irq = dev->irq[IDX_IRQ_S1_CD_VALID]; + s->soc.stat[SOC_STAT_CD].name = "SA1111 CF card detect"; + s->soc.stat[SOC_STAT_BVD1].irq = dev->irq[IDX_IRQ_S1_BVD1_STSCHG]; + s->soc.stat[SOC_STAT_BVD1].name = "SA1111 CF BVD1"; + } else { + s->soc.socket.pci_irq = dev->irq[IDX_IRQ_S0_READY_NINT]; + s->soc.stat[SOC_STAT_CD].irq = dev->irq[IDX_IRQ_S0_CD_VALID]; + s->soc.stat[SOC_STAT_CD].name = "SA1111 PCMCIA card detect"; + s->soc.stat[SOC_STAT_BVD1].irq = dev->irq[IDX_IRQ_S0_BVD1_STSCHG]; + s->soc.stat[SOC_STAT_BVD1].name = "SA1111 PCMCIA BVD1"; + } ret = add(&s->soc); if (ret == 0) { @@ -163,26 +175,26 @@ int sa1111_pcmcia_add(struct sa1111_dev *dev, struct pcmcia_low_level *ops, static int pcmcia_probe(struct sa1111_dev *dev) { void __iomem *base; + int ret; + + ret = sa1111_enable_device(dev); + if (ret) + return ret; dev_set_drvdata(&dev->dev, NULL); - if (!request_mem_region(dev->res.start, 512, - SA1111_DRIVER_NAME(dev))) + if (!request_mem_region(dev->res.start, 512, SA1111_DRIVER_NAME(dev))) { + sa1111_disable_device(dev); return -EBUSY; + } base = dev->mapbase; - /* Initialize PCMCIA IRQs */ - irqs[0].irq = dev->irq[IDX_IRQ_S0_CD_VALID]; - irqs[1].irq = dev->irq[IDX_IRQ_S0_BVD1_STSCHG]; - irqs[2].irq = dev->irq[IDX_IRQ_S1_CD_VALID]; - irqs[3].irq = dev->irq[IDX_IRQ_S1_BVD1_STSCHG]; - /* * Initialise the suspend state. */ - sa1111_writel(PCSSR_S0_SLEEP | PCSSR_S1_SLEEP, base + SA1111_PCSSR); - sa1111_writel(PCCR_S0_FLT | PCCR_S1_FLT, base + SA1111_PCCR); + sa1111_writel(PCSSR_S0_SLEEP | PCSSR_S1_SLEEP, base + PCSSR); + sa1111_writel(PCCR_S0_FLT | PCCR_S1_FLT, base + PCCR); #ifdef CONFIG_SA1100_BADGE4 pcmcia_badge4_init(&dev->dev); @@ -212,6 +224,7 @@ static int __devexit pcmcia_remove(struct sa1111_dev *dev) } release_mem_region(dev->res.start, 512); + sa1111_disable_device(dev); return 0; } diff --git a/drivers/pcmcia/sa1111_generic.h b/drivers/pcmcia/sa1111_generic.h index 02dc8577cdaf..f6376e34a7e4 100644 --- a/drivers/pcmcia/sa1111_generic.h +++ b/drivers/pcmcia/sa1111_generic.h @@ -17,7 +17,6 @@ int sa1111_pcmcia_add(struct sa1111_dev *dev, struct pcmcia_low_level *ops, extern void sa1111_pcmcia_socket_state(struct soc_pcmcia_socket *, struct pcmcia_state *); extern int sa1111_pcmcia_configure_socket(struct soc_pcmcia_socket *, const socket_state_t *); -extern void sa1111_pcmcia_socket_init(struct soc_pcmcia_socket *); extern int pcmcia_badge4_init(struct device *); extern int pcmcia_jornada720_init(struct device *); diff --git a/drivers/pcmcia/sa1100_jornada720.c b/drivers/pcmcia/sa1111_jornada720.c index 6bcabee6bde4..69428d1f5ae1 100644 --- a/drivers/pcmcia/sa1100_jornada720.c +++ b/drivers/pcmcia/sa1111_jornada720.c @@ -78,13 +78,8 @@ jornada720_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_s } ret = sa1111_pcmcia_configure_socket(skt, state); - if (ret == 0) { - unsigned long flags; - - local_irq_save(flags); + if (ret == 0) sa1111_set_io(s->dev, pa_dwr_mask, pa_dwr_set); - local_irq_restore(flags); - } return ret; } @@ -92,7 +87,6 @@ jornada720_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_s static struct pcmcia_low_level jornada720_pcmcia_ops = { .owner = THIS_MODULE, .configure_socket = jornada720_pcmcia_configure_socket, - .socket_init = sa1111_pcmcia_socket_init, .first = 0, .nr = 2, }; diff --git a/drivers/pcmcia/pxa2xx_lubbock.c b/drivers/pcmcia/sa1111_lubbock.c index c21888eebb58..c5caf5790451 100644 --- a/drivers/pcmcia/pxa2xx_lubbock.c +++ b/drivers/pcmcia/sa1111_lubbock.c @@ -202,7 +202,6 @@ lubbock_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, static struct pcmcia_low_level lubbock_pcmcia_ops = { .owner = THIS_MODULE, .configure_socket = lubbock_pcmcia_configure_socket, - .socket_init = sa1111_pcmcia_socket_init, .first = 0, .nr = 2, }; diff --git a/drivers/pcmcia/sa1100_neponset.c b/drivers/pcmcia/sa1111_neponset.c index c95639b5f2a0..1d78739c4c07 100644 --- a/drivers/pcmcia/sa1100_neponset.c +++ b/drivers/pcmcia/sa1111_neponset.c @@ -94,30 +94,16 @@ neponset_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_sta ret = sa1111_pcmcia_configure_socket(skt, state); if (ret == 0) { - unsigned long flags; - - local_irq_save(flags); - NCR_0 = (NCR_0 & ~ncr_mask) | ncr_set; - - local_irq_restore(flags); + neponset_ncr_frob(ncr_mask, ncr_set); sa1111_set_io(s->dev, pa_dwr_mask, pa_dwr_set); } - return 0; -} - -static void neponset_pcmcia_socket_init(struct soc_pcmcia_socket *skt) -{ - if (skt->nr == 0) - NCR_0 &= ~(NCR_A0VPP | NCR_A1VPP); - - sa1111_pcmcia_socket_init(skt); + return ret; } static struct pcmcia_low_level neponset_pcmcia_ops = { .owner = THIS_MODULE, .configure_socket = neponset_pcmcia_configure_socket, - .socket_init = neponset_pcmcia_socket_init, .first = 0, .nr = 2, }; diff --git a/drivers/pcmcia/sa11xx_base.c b/drivers/pcmcia/sa11xx_base.c index 0c62fe31a40e..a3ee89a6dd0e 100644 --- a/drivers/pcmcia/sa11xx_base.c +++ b/drivers/pcmcia/sa11xx_base.c @@ -236,10 +236,7 @@ int sa11xx_drv_pcmcia_probe(struct device *dev, struct pcmcia_low_level *ops, skt = &sinfo->skt[i]; skt->nr = first + i; - skt->ops = ops; - skt->socket.owner = ops->owner; - skt->socket.dev.parent = dev; - skt->socket.pci_irq = NO_IRQ; + soc_pcmcia_init_one(skt, ops, dev); ret = sa11xx_drv_pcmcia_add_one(skt); if (ret) diff --git a/drivers/pcmcia/soc_common.c b/drivers/pcmcia/soc_common.c index a0a9c2aa8d78..e0433f571962 100644 --- a/drivers/pcmcia/soc_common.c +++ b/drivers/pcmcia/soc_common.c @@ -32,6 +32,7 @@ #include <linux/cpufreq.h> +#include <linux/gpio.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/io.h> @@ -49,6 +50,8 @@ #include "soc_common.h" +static irqreturn_t soc_common_pcmcia_interrupt(int irq, void *dev); + #ifdef CONFIG_PCMCIA_DEBUG static int pc_debug; @@ -104,6 +107,93 @@ void soc_common_pcmcia_get_timing(struct soc_pcmcia_socket *skt, } EXPORT_SYMBOL(soc_common_pcmcia_get_timing); +static void __soc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt, + unsigned int nr) +{ + unsigned int i; + + for (i = 0; i < nr; i++) { + if (skt->stat[i].irq) + free_irq(skt->stat[i].irq, skt); + if (gpio_is_valid(skt->stat[i].gpio)) + gpio_free(skt->stat[i].gpio); + } + + if (skt->ops->hw_shutdown) + skt->ops->hw_shutdown(skt); +} + +static void soc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + __soc_pcmcia_hw_shutdown(skt, ARRAY_SIZE(skt->stat)); +} + +static int soc_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + int ret = 0, i; + + if (skt->ops->hw_init) { + ret = skt->ops->hw_init(skt); + if (ret) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) { + if (gpio_is_valid(skt->stat[i].gpio)) { + int irq; + + ret = gpio_request_one(skt->stat[i].gpio, GPIOF_IN, + skt->stat[i].name); + if (ret) { + __soc_pcmcia_hw_shutdown(skt, i); + return ret; + } + + irq = gpio_to_irq(skt->stat[i].gpio); + + if (i == SOC_STAT_RDY) + skt->socket.pci_irq = irq; + else + skt->stat[i].irq = irq; + } + + if (skt->stat[i].irq) { + ret = request_irq(skt->stat[i].irq, + soc_common_pcmcia_interrupt, + IRQF_TRIGGER_NONE, + skt->stat[i].name, skt); + if (ret) { + if (gpio_is_valid(skt->stat[i].gpio)) + gpio_free(skt->stat[i].gpio); + __soc_pcmcia_hw_shutdown(skt, i); + return ret; + } + } + } + + return ret; +} + +static void soc_pcmcia_hw_enable(struct soc_pcmcia_socket *skt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) + if (skt->stat[i].irq) { + irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_EDGE_RISING); + irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_EDGE_BOTH); + } +} + +static void soc_pcmcia_hw_disable(struct soc_pcmcia_socket *skt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) + if (skt->stat[i].irq) + irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_NONE); +} + static unsigned int soc_common_pcmcia_skt_state(struct soc_pcmcia_socket *skt) { struct pcmcia_state state; @@ -111,6 +201,22 @@ static unsigned int soc_common_pcmcia_skt_state(struct soc_pcmcia_socket *skt) memset(&state, 0, sizeof(struct pcmcia_state)); + /* Make battery voltage state report 'good' */ + state.bvd1 = 1; + state.bvd2 = 1; + + /* CD is active low by default */ + if (gpio_is_valid(skt->stat[SOC_STAT_CD].gpio)) + state.detect = !gpio_get_value(skt->stat[SOC_STAT_CD].gpio); + + /* RDY and BVD are active high by default */ + if (gpio_is_valid(skt->stat[SOC_STAT_RDY].gpio)) + state.ready = !!gpio_get_value(skt->stat[SOC_STAT_RDY].gpio); + if (gpio_is_valid(skt->stat[SOC_STAT_BVD1].gpio)) + state.bvd1 = !!gpio_get_value(skt->stat[SOC_STAT_BVD1].gpio); + if (gpio_is_valid(skt->stat[SOC_STAT_BVD2].gpio)) + state.bvd2 = !!gpio_get_value(skt->stat[SOC_STAT_BVD2].gpio); + skt->ops->socket_state(skt, &state); stat = state.detect ? SS_DETECT : 0; @@ -188,6 +294,7 @@ static int soc_common_pcmcia_sock_init(struct pcmcia_socket *sock) debug(skt, 2, "initializing socket\n"); if (skt->ops->socket_init) skt->ops->socket_init(skt); + soc_pcmcia_hw_enable(skt); return 0; } @@ -207,6 +314,7 @@ static int soc_common_pcmcia_suspend(struct pcmcia_socket *sock) debug(skt, 2, "suspending socket\n"); + soc_pcmcia_hw_disable(skt); if (skt->ops->socket_suspend) skt->ops->socket_suspend(skt); @@ -526,69 +634,6 @@ static struct pccard_operations soc_common_pcmcia_operations = { }; -int soc_pcmcia_request_irqs(struct soc_pcmcia_socket *skt, - struct pcmcia_irqs *irqs, int nr) -{ - int i, res = 0; - - for (i = 0; i < nr; i++) { - if (irqs[i].sock != skt->nr) - continue; - res = request_irq(irqs[i].irq, soc_common_pcmcia_interrupt, - IRQF_DISABLED, irqs[i].str, skt); - if (res) - break; - irq_set_irq_type(irqs[i].irq, IRQ_TYPE_NONE); - } - - if (res) { - printk(KERN_ERR "PCMCIA: request for IRQ%d failed (%d)\n", - irqs[i].irq, res); - - while (i--) - if (irqs[i].sock == skt->nr) - free_irq(irqs[i].irq, skt); - } - return res; -} -EXPORT_SYMBOL(soc_pcmcia_request_irqs); - -void soc_pcmcia_free_irqs(struct soc_pcmcia_socket *skt, - struct pcmcia_irqs *irqs, int nr) -{ - int i; - - for (i = 0; i < nr; i++) - if (irqs[i].sock == skt->nr) - free_irq(irqs[i].irq, skt); -} -EXPORT_SYMBOL(soc_pcmcia_free_irqs); - -void soc_pcmcia_disable_irqs(struct soc_pcmcia_socket *skt, - struct pcmcia_irqs *irqs, int nr) -{ - int i; - - for (i = 0; i < nr; i++) - if (irqs[i].sock == skt->nr) - irq_set_irq_type(irqs[i].irq, IRQ_TYPE_NONE); -} -EXPORT_SYMBOL(soc_pcmcia_disable_irqs); - -void soc_pcmcia_enable_irqs(struct soc_pcmcia_socket *skt, - struct pcmcia_irqs *irqs, int nr) -{ - int i; - - for (i = 0; i < nr; i++) - if (irqs[i].sock == skt->nr) { - irq_set_irq_type(irqs[i].irq, IRQ_TYPE_EDGE_RISING); - irq_set_irq_type(irqs[i].irq, IRQ_TYPE_EDGE_BOTH); - } -} -EXPORT_SYMBOL(soc_pcmcia_enable_irqs); - - static LIST_HEAD(soc_pcmcia_sockets); static DEFINE_MUTEX(soc_pcmcia_sockets_lock); @@ -635,6 +680,21 @@ module_exit(soc_pcmcia_cpufreq_unregister); #endif +void soc_pcmcia_init_one(struct soc_pcmcia_socket *skt, + struct pcmcia_low_level *ops, struct device *dev) +{ + int i; + + skt->ops = ops; + skt->socket.owner = ops->owner; + skt->socket.dev.parent = dev; + skt->socket.pci_irq = NO_IRQ; + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) + skt->stat[i].gpio = -EINVAL; +} +EXPORT_SYMBOL(soc_pcmcia_init_one); + void soc_pcmcia_remove_one(struct soc_pcmcia_socket *skt) { mutex_lock(&soc_pcmcia_sockets_lock); @@ -642,8 +702,9 @@ void soc_pcmcia_remove_one(struct soc_pcmcia_socket *skt) pcmcia_unregister_socket(&skt->socket); - skt->ops->hw_shutdown(skt); + soc_pcmcia_hw_shutdown(skt); + /* should not be required; violates some lowlevel drivers */ soc_common_pcmcia_config_skt(skt, &dead_socket); list_del(&skt->node); @@ -700,7 +761,7 @@ int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt) */ skt->ops->set_timing(skt); - ret = skt->ops->hw_init(skt); + ret = soc_pcmcia_hw_init(skt); if (ret) goto out_err_6; @@ -733,7 +794,7 @@ int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt) pcmcia_unregister_socket(&skt->socket); out_err_7: - skt->ops->hw_shutdown(skt); + soc_pcmcia_hw_shutdown(skt); out_err_6: list_del(&skt->node); mutex_unlock(&soc_pcmcia_sockets_lock); diff --git a/drivers/pcmcia/soc_common.h b/drivers/pcmcia/soc_common.h index 9daa73615c8b..e6fcbea5b682 100644 --- a/drivers/pcmcia/soc_common.h +++ b/drivers/pcmcia/soc_common.h @@ -50,6 +50,16 @@ struct soc_pcmcia_socket { struct resource res_attr; void __iomem *virt_io; + struct { + int gpio; + unsigned int irq; + const char *name; + } stat[4]; +#define SOC_STAT_CD 0 /* Card detect */ +#define SOC_STAT_BVD1 1 /* BATDEAD / IOSTSCHG */ +#define SOC_STAT_BVD2 2 /* BATWARN / IOSPKR */ +#define SOC_STAT_RDY 3 /* Ready / Interrupt */ + unsigned int irq_state; struct timer_list poll_timer; @@ -115,25 +125,16 @@ struct pcmcia_low_level { }; -struct pcmcia_irqs { - int sock; - int irq; - const char *str; -}; - struct soc_pcmcia_timing { unsigned short io; unsigned short mem; unsigned short attr; }; -extern int soc_pcmcia_request_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); -extern void soc_pcmcia_free_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); -extern void soc_pcmcia_disable_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); -extern void soc_pcmcia_enable_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); extern void soc_common_pcmcia_get_timing(struct soc_pcmcia_socket *, struct soc_pcmcia_timing *); - +void soc_pcmcia_init_one(struct soc_pcmcia_socket *skt, + struct pcmcia_low_level *ops, struct device *dev); void soc_pcmcia_remove_one(struct soc_pcmcia_socket *skt); int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt); diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 72d731c21d45..9929246895de 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -571,7 +571,7 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus) } else { dev = pci_get_slot(bus, 0); if (dev) { - pci_remove_bus_device(dev); + pci_stop_and_remove_bus_device(dev); pci_dev_put(dev); } } diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index ea44abd8df48..d9a9e2bedb30 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -646,7 +646,7 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle) } else { dev = pci_get_slot(bus, 0); if (dev) { - pci_remove_bus_device(dev); + pci_stop_and_remove_bus_device(dev); pci_dev_put(dev); } } diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c index 8a612dec9139..39763015b360 100644 --- a/drivers/power/apm_power.c +++ b/drivers/power/apm_power.c @@ -10,6 +10,7 @@ */ #include <linux/module.h> +#include <linux/device.h> #include <linux/power_supply.h> #include <linux/apm-emulation.h> diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c index 9b3f2bf56e70..6dc01c255592 100644 --- a/drivers/power/max8998_charger.c +++ b/drivers/power/max8998_charger.c @@ -19,7 +19,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <linux/module.h> #include <linux/err.h> #include <linux/module.h> #include <linux/slab.h> diff --git a/drivers/power/power_supply.h b/drivers/power/power_supply.h index 018de2b26998..cc439fd89d8d 100644 --- a/drivers/power/power_supply.h +++ b/drivers/power/power_supply.h @@ -10,6 +10,10 @@ * You may use this code as per GPL version 2 */ +struct device; +struct device_type; +struct power_supply; + #ifdef CONFIG_SYSFS extern void power_supply_init_attrs(struct device_type *dev_type); diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c index da25eb94e5c6..995f966ed5b7 100644 --- a/drivers/power/power_supply_leds.c +++ b/drivers/power/power_supply_leds.c @@ -11,6 +11,7 @@ */ #include <linux/kernel.h> +#include <linux/device.h> #include <linux/power_supply.h> #include <linux/slab.h> diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index b52b57ca3084..4368e7d61316 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -12,6 +12,7 @@ */ #include <linux/ctype.h> +#include <linux/device.h> #include <linux/power_supply.h> #include <linux/slab.h> #include <linux/stat.h> diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index a229de98ae6f..36db5a441eba 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -258,14 +258,6 @@ config REGULATOR_DB8500_PRCMU This driver supports the voltage domain regulators controlled by the DB8500 PRCMU -config REGULATOR_BQ24022 - tristate "TI bq24022 Dual Input 1-Cell Li-Ion Charger IC" - help - This driver controls a TI bq24022 Charger attached via - GPIOs. The provided current regulator can enable/disable - charging select between 100 mA and 500 mA charging current - limit. - config REGULATOR_TPS6105X tristate "TI TPS6105X Power regulators" depends on TPS6105X diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index b5042c885d89..94b52745e957 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -16,7 +16,6 @@ obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o obj-$(CONFIG_REGULATOR_AD5398) += ad5398.o obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o -obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o obj-$(CONFIG_REGULATOR_DBX500_PRCMU) += dbx500-prcmu.o diff --git a/drivers/regulator/bq24022.c b/drivers/regulator/bq24022.c deleted file mode 100644 index 9fab6d1bbe80..000000000000 --- a/drivers/regulator/bq24022.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Support for TI bq24022 (bqTINY-II) Dual Input (USB/AC Adpater) - * 1-Cell Li-Ion Charger connected via GPIOs. - * - * Copyright (c) 2008 Philipp Zabel - * - * 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/err.h> -#include <linux/module.h> -#include <linux/gpio.h> -#include <linux/regulator/bq24022.h> -#include <linux/regulator/driver.h> - - -static int bq24022_set_current_limit(struct regulator_dev *rdev, - int min_uA, int max_uA) -{ - struct bq24022_mach_info *pdata = rdev_get_drvdata(rdev); - - dev_dbg(rdev_get_dev(rdev), "setting current limit to %s mA\n", - max_uA >= 500000 ? "500" : "100"); - - /* REVISIT: maybe return error if min_uA != 0 ? */ - gpio_set_value(pdata->gpio_iset2, max_uA >= 500000); - return 0; -} - -static int bq24022_get_current_limit(struct regulator_dev *rdev) -{ - struct bq24022_mach_info *pdata = rdev_get_drvdata(rdev); - - return gpio_get_value(pdata->gpio_iset2) ? 500000 : 100000; -} - -static int bq24022_enable(struct regulator_dev *rdev) -{ - struct bq24022_mach_info *pdata = rdev_get_drvdata(rdev); - - dev_dbg(rdev_get_dev(rdev), "enabling charger\n"); - - gpio_set_value(pdata->gpio_nce, 0); - return 0; -} - -static int bq24022_disable(struct regulator_dev *rdev) -{ - struct bq24022_mach_info *pdata = rdev_get_drvdata(rdev); - - dev_dbg(rdev_get_dev(rdev), "disabling charger\n"); - - gpio_set_value(pdata->gpio_nce, 1); - return 0; -} - -static int bq24022_is_enabled(struct regulator_dev *rdev) -{ - struct bq24022_mach_info *pdata = rdev_get_drvdata(rdev); - - return !gpio_get_value(pdata->gpio_nce); -} - -static struct regulator_ops bq24022_ops = { - .set_current_limit = bq24022_set_current_limit, - .get_current_limit = bq24022_get_current_limit, - .enable = bq24022_enable, - .disable = bq24022_disable, - .is_enabled = bq24022_is_enabled, -}; - -static struct regulator_desc bq24022_desc = { - .name = "bq24022", - .ops = &bq24022_ops, - .type = REGULATOR_CURRENT, - .owner = THIS_MODULE, -}; - -static int __init bq24022_probe(struct platform_device *pdev) -{ - struct bq24022_mach_info *pdata = pdev->dev.platform_data; - struct regulator_dev *bq24022; - int ret; - - if (!pdata || !pdata->gpio_nce || !pdata->gpio_iset2) - return -EINVAL; - - ret = gpio_request(pdata->gpio_nce, "ncharge_en"); - if (ret) { - dev_dbg(&pdev->dev, "couldn't request nCE GPIO: %d\n", - pdata->gpio_nce); - goto err_ce; - } - ret = gpio_request(pdata->gpio_iset2, "charge_mode"); - if (ret) { - dev_dbg(&pdev->dev, "couldn't request ISET2 GPIO: %d\n", - pdata->gpio_iset2); - goto err_iset2; - } - ret = gpio_direction_output(pdata->gpio_iset2, 0); - ret = gpio_direction_output(pdata->gpio_nce, 1); - - bq24022 = regulator_register(&bq24022_desc, &pdev->dev, - pdata->init_data, pdata, NULL); - if (IS_ERR(bq24022)) { - dev_dbg(&pdev->dev, "couldn't register regulator\n"); - ret = PTR_ERR(bq24022); - goto err_reg; - } - platform_set_drvdata(pdev, bq24022); - dev_dbg(&pdev->dev, "registered regulator\n"); - - return 0; -err_reg: - gpio_free(pdata->gpio_iset2); -err_iset2: - gpio_free(pdata->gpio_nce); -err_ce: - return ret; -} - -static int __devexit bq24022_remove(struct platform_device *pdev) -{ - struct bq24022_mach_info *pdata = pdev->dev.platform_data; - struct regulator_dev *bq24022 = platform_get_drvdata(pdev); - - regulator_unregister(bq24022); - gpio_free(pdata->gpio_iset2); - gpio_free(pdata->gpio_nce); - - return 0; -} - -static struct platform_driver bq24022_driver = { - .driver = { - .name = "bq24022", - }, - .remove = __devexit_p(bq24022_remove), -}; - -static int __init bq24022_init(void) -{ - return platform_driver_probe(&bq24022_driver, bq24022_probe); -} - -static void __exit bq24022_exit(void) -{ - platform_driver_unregister(&bq24022_driver); -} - -module_init(bq24022_init); -module_exit(bq24022_exit); - -MODULE_AUTHOR("Philipp Zabel"); -MODULE_DESCRIPTION("TI bq24022 Li-Ion Charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig new file mode 100644 index 000000000000..24d880e78ec6 --- /dev/null +++ b/drivers/remoteproc/Kconfig @@ -0,0 +1,28 @@ +menu "Remoteproc drivers (EXPERIMENTAL)" + +# REMOTEPROC gets selected by whoever wants it +config REMOTEPROC + tristate + depends on EXPERIMENTAL + +config OMAP_REMOTEPROC + tristate "OMAP remoteproc support" + depends on ARCH_OMAP4 + depends on OMAP_IOMMU + select REMOTEPROC + select OMAP_MBOX_FWK + select RPMSG + help + Say y here to support OMAP's remote processors (dual M3 + and DSP on OMAP4) via the remote processor framework. + + Currently only supported on OMAP4. + + Usually you want to say y here, in order to enable multimedia + use-cases to run on your platform (multimedia codecs are + offloaded to remote DSP processors using this framework). + + It's safe to say n here if you're not interested in multimedia + offloading or just want a bare minimum kernel. + +endmenu diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile new file mode 100644 index 000000000000..5445d9b23294 --- /dev/null +++ b/drivers/remoteproc/Makefile @@ -0,0 +1,9 @@ +# +# Generic framework for controlling remote processors +# + +obj-$(CONFIG_REMOTEPROC) += remoteproc.o +remoteproc-y := remoteproc_core.o +remoteproc-y += remoteproc_debugfs.o +remoteproc-y += remoteproc_virtio.o +obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c new file mode 100644 index 000000000000..69425c4e86f3 --- /dev/null +++ b/drivers/remoteproc/omap_remoteproc.c @@ -0,0 +1,229 @@ +/* + * OMAP Remote Processor driver + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.com> + * Fernando Guzman Lugo <fernando.lugo@ti.com> + * Mark Grosen <mgrosen@ti.com> + * Suman Anna <s-anna@ti.com> + * Hari Kanigeri <h-kanigeri2@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/remoteproc.h> + +#include <plat/mailbox.h> +#include <plat/remoteproc.h> + +#include "omap_remoteproc.h" +#include "remoteproc_internal.h" + +/** + * struct omap_rproc - omap remote processor state + * @mbox: omap mailbox handle + * @nb: notifier block that will be invoked on inbound mailbox messages + * @rproc: rproc handle + */ +struct omap_rproc { + struct omap_mbox *mbox; + struct notifier_block nb; + struct rproc *rproc; +}; + +/** + * omap_rproc_mbox_callback() - inbound mailbox message handler + * @this: notifier block + * @index: unused + * @data: mailbox payload + * + * This handler is invoked by omap's mailbox driver whenever a mailbox + * message is received. Usually, the mailbox payload simply contains + * the index of the virtqueue that is kicked by the remote processor, + * and we let remoteproc core handle it. + * + * In addition to virtqueue indices, we also have some out-of-band values + * that indicates different events. Those values are deliberately very + * big so they don't coincide with virtqueue indices. + */ +static int omap_rproc_mbox_callback(struct notifier_block *this, + unsigned long index, void *data) +{ + mbox_msg_t msg = (mbox_msg_t) data; + struct omap_rproc *oproc = container_of(this, struct omap_rproc, nb); + struct device *dev = oproc->rproc->dev; + const char *name = oproc->rproc->name; + + dev_dbg(dev, "mbox msg: 0x%x\n", msg); + + switch (msg) { + case RP_MBOX_CRASH: + /* just log this for now. later, we'll also do recovery */ + dev_err(dev, "omap rproc %s crashed\n", name); + break; + case RP_MBOX_ECHO_REPLY: + dev_info(dev, "received echo reply from %s\n", name); + break; + default: + /* msg contains the index of the triggered vring */ + if (rproc_vq_interrupt(oproc->rproc, msg) == IRQ_NONE) + dev_dbg(dev, "no message was found in vqid %d\n", msg); + } + + return NOTIFY_DONE; +} + +/* kick a virtqueue */ +static void omap_rproc_kick(struct rproc *rproc, int vqid) +{ + struct omap_rproc *oproc = rproc->priv; + int ret; + + /* send the index of the triggered virtqueue in the mailbox payload */ + ret = omap_mbox_msg_send(oproc->mbox, vqid); + if (ret) + dev_err(rproc->dev, "omap_mbox_msg_send failed: %d\n", ret); +} + +/* + * Power up the remote processor. + * + * This function will be invoked only after the firmware for this rproc + * was loaded, parsed successfully, and all of its resource requirements + * were met. + */ +static int omap_rproc_start(struct rproc *rproc) +{ + struct omap_rproc *oproc = rproc->priv; + struct platform_device *pdev = to_platform_device(rproc->dev); + struct omap_rproc_pdata *pdata = pdev->dev.platform_data; + int ret; + + oproc->nb.notifier_call = omap_rproc_mbox_callback; + + /* every omap rproc is assigned a mailbox instance for messaging */ + oproc->mbox = omap_mbox_get(pdata->mbox_name, &oproc->nb); + if (IS_ERR(oproc->mbox)) { + ret = PTR_ERR(oproc->mbox); + dev_err(rproc->dev, "omap_mbox_get failed: %d\n", ret); + return ret; + } + + /* + * Ping the remote processor. this is only for sanity-sake; + * there is no functional effect whatsoever. + * + * Note that the reply will _not_ arrive immediately: this message + * will wait in the mailbox fifo until the remote processor is booted. + */ + ret = omap_mbox_msg_send(oproc->mbox, RP_MBOX_ECHO_REQUEST); + if (ret) { + dev_err(rproc->dev, "omap_mbox_get failed: %d\n", ret); + goto put_mbox; + } + + ret = pdata->device_enable(pdev); + if (ret) { + dev_err(rproc->dev, "omap_device_enable failed: %d\n", ret); + goto put_mbox; + } + + return 0; + +put_mbox: + omap_mbox_put(oproc->mbox, &oproc->nb); + return ret; +} + +/* power off the remote processor */ +static int omap_rproc_stop(struct rproc *rproc) +{ + struct platform_device *pdev = to_platform_device(rproc->dev); + struct omap_rproc_pdata *pdata = pdev->dev.platform_data; + struct omap_rproc *oproc = rproc->priv; + int ret; + + ret = pdata->device_shutdown(pdev); + if (ret) + return ret; + + omap_mbox_put(oproc->mbox, &oproc->nb); + + return 0; +} + +static struct rproc_ops omap_rproc_ops = { + .start = omap_rproc_start, + .stop = omap_rproc_stop, + .kick = omap_rproc_kick, +}; + +static int __devinit omap_rproc_probe(struct platform_device *pdev) +{ + struct omap_rproc_pdata *pdata = pdev->dev.platform_data; + struct omap_rproc *oproc; + struct rproc *rproc; + int ret; + + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(pdev->dev.parent, "dma_set_coherent_mask: %d\n", ret); + return ret; + } + + rproc = rproc_alloc(&pdev->dev, pdata->name, &omap_rproc_ops, + pdata->firmware, sizeof(*oproc)); + if (!rproc) + return -ENOMEM; + + oproc = rproc->priv; + oproc->rproc = rproc; + + platform_set_drvdata(pdev, rproc); + + ret = rproc_register(rproc); + if (ret) + goto free_rproc; + + return 0; + +free_rproc: + rproc_free(rproc); + return ret; +} + +static int __devexit omap_rproc_remove(struct platform_device *pdev) +{ + struct rproc *rproc = platform_get_drvdata(pdev); + + return rproc_unregister(rproc); +} + +static struct platform_driver omap_rproc_driver = { + .probe = omap_rproc_probe, + .remove = __devexit_p(omap_rproc_remove), + .driver = { + .name = "omap-rproc", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(omap_rproc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("OMAP Remote Processor control driver"); diff --git a/drivers/remoteproc/omap_remoteproc.h b/drivers/remoteproc/omap_remoteproc.h new file mode 100644 index 000000000000..f6d2036d383d --- /dev/null +++ b/drivers/remoteproc/omap_remoteproc.h @@ -0,0 +1,69 @@ +/* + * Remote processor messaging + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * All rights reserved. + * + * 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. + * * Neither the name Texas Instruments nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OMAP_RPMSG_H +#define _OMAP_RPMSG_H + +/* + * enum - Predefined Mailbox Messages + * + * @RP_MBOX_READY: informs the M3's that we're up and running. this is + * part of the init sequence sent that the M3 expects to see immediately + * after it is booted. + * + * @RP_MBOX_PENDING_MSG: informs the receiver that there is an inbound + * message waiting in its own receive-side vring. please note that currently + * this message is optional: alternatively, one can explicitly send the index + * of the triggered virtqueue itself. the preferred approach will be decided + * as we progress and experiment with those two different approaches. + * + * @RP_MBOX_CRASH: this message is sent if BIOS crashes + * + * @RP_MBOX_ECHO_REQUEST: a mailbox-level "ping" message. + * + * @RP_MBOX_ECHO_REPLY: a mailbox-level reply to a "ping" + * + * @RP_MBOX_ABORT_REQUEST: a "please crash" request, used for testing the + * recovery mechanism (to some extent). + */ +enum omap_rp_mbox_messages { + RP_MBOX_READY = 0xFFFFFF00, + RP_MBOX_PENDING_MSG = 0xFFFFFF01, + RP_MBOX_CRASH = 0xFFFFFF02, + RP_MBOX_ECHO_REQUEST = 0xFFFFFF03, + RP_MBOX_ECHO_REPLY = 0xFFFFFF04, + RP_MBOX_ABORT_REQUEST = 0xFFFFFF05, +}; + +#endif /* _OMAP_RPMSG_H */ diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c new file mode 100644 index 000000000000..ee15c68fb519 --- /dev/null +++ b/drivers/remoteproc/remoteproc_core.c @@ -0,0 +1,1586 @@ +/* + * Remote Processor Framework + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.com> + * Mark Grosen <mgrosen@ti.com> + * Fernando Guzman Lugo <fernando.lugo@ti.com> + * Suman Anna <s-anna@ti.com> + * Robert Tivy <rtivy@ti.com> + * Armando Uribe De Leon <x0095078@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/string.h> +#include <linux/debugfs.h> +#include <linux/remoteproc.h> +#include <linux/iommu.h> +#include <linux/klist.h> +#include <linux/elf.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_ring.h> +#include <asm/byteorder.h> + +#include "remoteproc_internal.h" + +static void klist_rproc_get(struct klist_node *n); +static void klist_rproc_put(struct klist_node *n); + +/* + * klist of the available remote processors. + * + * We need this in order to support name-based lookups (needed by the + * rproc_get_by_name()). + * + * That said, we don't use rproc_get_by_name() at this point. + * The use cases that do require its existence should be + * scrutinized, and hopefully migrated to rproc_boot() using device-based + * binding. + * + * If/when this materializes, we could drop the klist (and the by_name + * API). + */ +static DEFINE_KLIST(rprocs, klist_rproc_get, klist_rproc_put); + +typedef int (*rproc_handle_resources_t)(struct rproc *rproc, + struct resource_table *table, int len); +typedef int (*rproc_handle_resource_t)(struct rproc *rproc, void *, int avail); + +/* + * This is the IOMMU fault handler we register with the IOMMU API + * (when relevant; not all remote processors access memory through + * an IOMMU). + * + * IOMMU core will invoke this handler whenever the remote processor + * will try to access an unmapped device address. + * + * Currently this is mostly a stub, but it will be later used to trigger + * the recovery of the remote processor. + */ +static int rproc_iommu_fault(struct iommu_domain *domain, struct device *dev, + unsigned long iova, int flags) +{ + dev_err(dev, "iommu fault: da 0x%lx flags 0x%x\n", iova, flags); + + /* + * Let the iommu core know we're not really handling this fault; + * we just plan to use this as a recovery trigger. + */ + return -ENOSYS; +} + +static int rproc_enable_iommu(struct rproc *rproc) +{ + struct iommu_domain *domain; + struct device *dev = rproc->dev; + int ret; + + /* + * We currently use iommu_present() to decide if an IOMMU + * setup is needed. + * + * This works for simple cases, but will easily fail with + * platforms that do have an IOMMU, but not for this specific + * rproc. + * + * This will be easily solved by introducing hw capabilities + * that will be set by the remoteproc driver. + */ + if (!iommu_present(dev->bus)) { + dev_dbg(dev, "iommu not found\n"); + return 0; + } + + domain = iommu_domain_alloc(dev->bus); + if (!domain) { + dev_err(dev, "can't alloc iommu domain\n"); + return -ENOMEM; + } + + iommu_set_fault_handler(domain, rproc_iommu_fault); + + ret = iommu_attach_device(domain, dev); + if (ret) { + dev_err(dev, "can't attach iommu device: %d\n", ret); + goto free_domain; + } + + rproc->domain = domain; + + return 0; + +free_domain: + iommu_domain_free(domain); + return ret; +} + +static void rproc_disable_iommu(struct rproc *rproc) +{ + struct iommu_domain *domain = rproc->domain; + struct device *dev = rproc->dev; + + if (!domain) + return; + + iommu_detach_device(domain, dev); + iommu_domain_free(domain); + + return; +} + +/* + * Some remote processors will ask us to allocate them physically contiguous + * memory regions (which we call "carveouts"), and map them to specific + * device addresses (which are hardcoded in the firmware). + * + * They may then ask us to copy objects into specific device addresses (e.g. + * code/data sections) or expose us certain symbols in other device address + * (e.g. their trace buffer). + * + * This function is an internal helper with which we can go over the allocated + * carveouts and translate specific device address to kernel virtual addresses + * so we can access the referenced memory. + * + * Note: phys_to_virt(iommu_iova_to_phys(rproc->domain, da)) will work too, + * but only on kernel direct mapped RAM memory. Instead, we're just using + * here the output of the DMA API, which should be more correct. + */ +static void *rproc_da_to_va(struct rproc *rproc, u64 da, int len) +{ + struct rproc_mem_entry *carveout; + void *ptr = NULL; + + list_for_each_entry(carveout, &rproc->carveouts, node) { + int offset = da - carveout->da; + + /* try next carveout if da is too small */ + if (offset < 0) + continue; + + /* try next carveout if da is too large */ + if (offset + len > carveout->len) + continue; + + ptr = carveout->va + offset; + + break; + } + + return ptr; +} + +/** + * rproc_load_segments() - load firmware segments to memory + * @rproc: remote processor which will be booted using these fw segments + * @elf_data: the content of the ELF firmware image + * @len: firmware size (in bytes) + * + * This function loads the firmware segments to memory, where the remote + * processor expects them. + * + * Some remote processors will expect their code and data to be placed + * in specific device addresses, and can't have them dynamically assigned. + * + * We currently support only those kind of remote processors, and expect + * the program header's paddr member to contain those addresses. We then go + * through the physically contiguous "carveout" memory regions which we + * allocated (and mapped) earlier on behalf of the remote processor, + * and "translate" device address to kernel addresses, so we can copy the + * segments where they are expected. + * + * Currently we only support remote processors that required carveout + * allocations and got them mapped onto their iommus. Some processors + * might be different: they might not have iommus, and would prefer to + * directly allocate memory for every segment/resource. This is not yet + * supported, though. + */ +static int +rproc_load_segments(struct rproc *rproc, const u8 *elf_data, size_t len) +{ + struct device *dev = rproc->dev; + struct elf32_hdr *ehdr; + struct elf32_phdr *phdr; + int i, ret = 0; + + ehdr = (struct elf32_hdr *)elf_data; + phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff); + + /* go through the available ELF segments */ + for (i = 0; i < ehdr->e_phnum; i++, phdr++) { + u32 da = phdr->p_paddr; + u32 memsz = phdr->p_memsz; + u32 filesz = phdr->p_filesz; + u32 offset = phdr->p_offset; + void *ptr; + + if (phdr->p_type != PT_LOAD) + continue; + + dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n", + phdr->p_type, da, memsz, filesz); + + if (filesz > memsz) { + dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n", + filesz, memsz); + ret = -EINVAL; + break; + } + + if (offset + filesz > len) { + dev_err(dev, "truncated fw: need 0x%x avail 0x%x\n", + offset + filesz, len); + ret = -EINVAL; + break; + } + + /* grab the kernel address for this device address */ + ptr = rproc_da_to_va(rproc, da, memsz); + if (!ptr) { + dev_err(dev, "bad phdr da 0x%x mem 0x%x\n", da, memsz); + ret = -EINVAL; + break; + } + + /* put the segment where the remote processor expects it */ + if (phdr->p_filesz) + memcpy(ptr, elf_data + phdr->p_offset, filesz); + + /* + * Zero out remaining memory for this segment. + * + * This isn't strictly required since dma_alloc_coherent already + * did this for us. albeit harmless, we may consider removing + * this. + */ + if (memsz > filesz) + memset(ptr + filesz, 0, memsz - filesz); + } + + return ret; +} + +static int +__rproc_handle_vring(struct rproc_vdev *rvdev, struct fw_rsc_vdev *rsc, int i) +{ + struct rproc *rproc = rvdev->rproc; + struct device *dev = rproc->dev; + struct fw_rsc_vdev_vring *vring = &rsc->vring[i]; + dma_addr_t dma; + void *va; + int ret, size, notifyid; + + dev_dbg(dev, "vdev rsc: vring%d: da %x, qsz %d, align %d\n", + i, vring->da, vring->num, vring->align); + + /* make sure reserved bytes are zeroes */ + if (vring->reserved) { + dev_err(dev, "vring rsc has non zero reserved bytes\n"); + return -EINVAL; + } + + /* verify queue size and vring alignment are sane */ + if (!vring->num || !vring->align) { + dev_err(dev, "invalid qsz (%d) or alignment (%d)\n", + vring->num, vring->align); + return -EINVAL; + } + + /* actual size of vring (in bytes) */ + size = PAGE_ALIGN(vring_size(vring->num, vring->align)); + + if (!idr_pre_get(&rproc->notifyids, GFP_KERNEL)) { + dev_err(dev, "idr_pre_get failed\n"); + return -ENOMEM; + } + + /* + * Allocate non-cacheable memory for the vring. In the future + * this call will also configure the IOMMU for us + */ + va = dma_alloc_coherent(dev, size, &dma, GFP_KERNEL); + if (!va) { + dev_err(dev, "dma_alloc_coherent failed\n"); + return -EINVAL; + } + + /* assign an rproc-wide unique index for this vring */ + /* TODO: assign a notifyid for rvdev updates as well */ + ret = idr_get_new(&rproc->notifyids, &rvdev->vring[i], ¬ifyid); + if (ret) { + dev_err(dev, "idr_get_new failed: %d\n", ret); + dma_free_coherent(dev, size, va, dma); + return ret; + } + + /* let the rproc know the da and notifyid of this vring */ + /* TODO: expose this to remote processor */ + vring->da = dma; + vring->notifyid = notifyid; + + dev_dbg(dev, "vring%d: va %p dma %x size %x idr %d\n", i, va, + dma, size, notifyid); + + rvdev->vring[i].len = vring->num; + rvdev->vring[i].align = vring->align; + rvdev->vring[i].va = va; + rvdev->vring[i].dma = dma; + rvdev->vring[i].notifyid = notifyid; + rvdev->vring[i].rvdev = rvdev; + + return 0; +} + +static void __rproc_free_vrings(struct rproc_vdev *rvdev, int i) +{ + struct rproc *rproc = rvdev->rproc; + + for (i--; i > 0; i--) { + struct rproc_vring *rvring = &rvdev->vring[i]; + int size = PAGE_ALIGN(vring_size(rvring->len, rvring->align)); + + dma_free_coherent(rproc->dev, size, rvring->va, rvring->dma); + idr_remove(&rproc->notifyids, rvring->notifyid); + } +} + +/** + * rproc_handle_vdev() - handle a vdev fw resource + * @rproc: the remote processor + * @rsc: the vring resource descriptor + * @avail: size of available data (for sanity checking the image) + * + * This resource entry requests the host to statically register a virtio + * device (vdev), and setup everything needed to support it. It contains + * everything needed to make it possible: the virtio device id, virtio + * device features, vrings information, virtio config space, etc... + * + * Before registering the vdev, the vrings are allocated from non-cacheable + * physically contiguous memory. Currently we only support two vrings per + * remote processor (temporary limitation). We might also want to consider + * doing the vring allocation only later when ->find_vqs() is invoked, and + * then release them upon ->del_vqs(). + * + * Note: @da is currently not really handled correctly: we dynamically + * allocate it using the DMA API, ignoring requested hard coded addresses, + * and we don't take care of any required IOMMU programming. This is all + * going to be taken care of when the generic iommu-based DMA API will be + * merged. Meanwhile, statically-addressed iommu-based firmware images should + * use RSC_DEVMEM resource entries to map their required @da to the physical + * address of their base CMA region (ouch, hacky!). + * + * Returns 0 on success, or an appropriate error code otherwise + */ +static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, + int avail) +{ + struct device *dev = rproc->dev; + struct rproc_vdev *rvdev; + int i, ret; + + /* make sure resource isn't truncated */ + if (sizeof(*rsc) + rsc->num_of_vrings * sizeof(struct fw_rsc_vdev_vring) + + rsc->config_len > avail) { + dev_err(rproc->dev, "vdev rsc is truncated\n"); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (rsc->reserved[0] || rsc->reserved[1]) { + dev_err(dev, "vdev rsc has non zero reserved bytes\n"); + return -EINVAL; + } + + dev_dbg(dev, "vdev rsc: id %d, dfeatures %x, cfg len %d, %d vrings\n", + rsc->id, rsc->dfeatures, rsc->config_len, rsc->num_of_vrings); + + /* we currently support only two vrings per rvdev */ + if (rsc->num_of_vrings > ARRAY_SIZE(rvdev->vring)) { + dev_err(dev, "too many vrings: %d\n", rsc->num_of_vrings); + return -EINVAL; + } + + rvdev = kzalloc(sizeof(struct rproc_vdev), GFP_KERNEL); + if (!rvdev) + return -ENOMEM; + + rvdev->rproc = rproc; + + /* allocate the vrings */ + for (i = 0; i < rsc->num_of_vrings; i++) { + ret = __rproc_handle_vring(rvdev, rsc, i); + if (ret) + goto free_vrings; + } + + /* remember the device features */ + rvdev->dfeatures = rsc->dfeatures; + + list_add_tail(&rvdev->node, &rproc->rvdevs); + + /* it is now safe to add the virtio device */ + ret = rproc_add_virtio_dev(rvdev, rsc->id); + if (ret) + goto free_vrings; + + return 0; + +free_vrings: + __rproc_free_vrings(rvdev, i); + kfree(rvdev); + return ret; +} + +/** + * rproc_handle_trace() - handle a shared trace buffer resource + * @rproc: the remote processor + * @rsc: the trace resource descriptor + * @avail: size of available data (for sanity checking the image) + * + * In case the remote processor dumps trace logs into memory, + * export it via debugfs. + * + * Currently, the 'da' member of @rsc should contain the device address + * where the remote processor is dumping the traces. Later we could also + * support dynamically allocating this address using the generic + * DMA API (but currently there isn't a use case for that). + * + * Returns 0 on success, or an appropriate error code otherwise + */ +static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc, + int avail) +{ + struct rproc_mem_entry *trace; + struct device *dev = rproc->dev; + void *ptr; + char name[15]; + + if (sizeof(*rsc) > avail) { + dev_err(rproc->dev, "trace rsc is truncated\n"); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (rsc->reserved) { + dev_err(dev, "trace rsc has non zero reserved bytes\n"); + return -EINVAL; + } + + /* what's the kernel address of this resource ? */ + ptr = rproc_da_to_va(rproc, rsc->da, rsc->len); + if (!ptr) { + dev_err(dev, "erroneous trace resource entry\n"); + return -EINVAL; + } + + trace = kzalloc(sizeof(*trace), GFP_KERNEL); + if (!trace) { + dev_err(dev, "kzalloc trace failed\n"); + return -ENOMEM; + } + + /* set the trace buffer dma properties */ + trace->len = rsc->len; + trace->va = ptr; + + /* make sure snprintf always null terminates, even if truncating */ + snprintf(name, sizeof(name), "trace%d", rproc->num_traces); + + /* create the debugfs entry */ + trace->priv = rproc_create_trace_file(name, rproc, trace); + if (!trace->priv) { + trace->va = NULL; + kfree(trace); + return -EINVAL; + } + + list_add_tail(&trace->node, &rproc->traces); + + rproc->num_traces++; + + dev_dbg(dev, "%s added: va %p, da 0x%x, len 0x%x\n", name, ptr, + rsc->da, rsc->len); + + return 0; +} + +/** + * rproc_handle_devmem() - handle devmem resource entry + * @rproc: remote processor handle + * @rsc: the devmem resource entry + * @avail: size of available data (for sanity checking the image) + * + * Remote processors commonly need to access certain on-chip peripherals. + * + * Some of these remote processors access memory via an iommu device, + * and might require us to configure their iommu before they can access + * the on-chip peripherals they need. + * + * This resource entry is a request to map such a peripheral device. + * + * These devmem entries will contain the physical address of the device in + * the 'pa' member. If a specific device address is expected, then 'da' will + * contain it (currently this is the only use case supported). 'len' will + * contain the size of the physical region we need to map. + * + * Currently we just "trust" those devmem entries to contain valid physical + * addresses, but this is going to change: we want the implementations to + * tell us ranges of physical addresses the firmware is allowed to request, + * and not allow firmwares to request access to physical addresses that + * are outside those ranges. + */ +static int rproc_handle_devmem(struct rproc *rproc, struct fw_rsc_devmem *rsc, + int avail) +{ + struct rproc_mem_entry *mapping; + int ret; + + /* no point in handling this resource without a valid iommu domain */ + if (!rproc->domain) + return -EINVAL; + + if (sizeof(*rsc) > avail) { + dev_err(rproc->dev, "devmem rsc is truncated\n"); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (rsc->reserved) { + dev_err(rproc->dev, "devmem rsc has non zero reserved bytes\n"); + return -EINVAL; + } + + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); + if (!mapping) { + dev_err(rproc->dev, "kzalloc mapping failed\n"); + return -ENOMEM; + } + + ret = iommu_map(rproc->domain, rsc->da, rsc->pa, rsc->len, rsc->flags); + if (ret) { + dev_err(rproc->dev, "failed to map devmem: %d\n", ret); + goto out; + } + + /* + * We'll need this info later when we'll want to unmap everything + * (e.g. on shutdown). + * + * We can't trust the remote processor not to change the resource + * table, so we must maintain this info independently. + */ + mapping->da = rsc->da; + mapping->len = rsc->len; + list_add_tail(&mapping->node, &rproc->mappings); + + dev_dbg(rproc->dev, "mapped devmem pa 0x%x, da 0x%x, len 0x%x\n", + rsc->pa, rsc->da, rsc->len); + + return 0; + +out: + kfree(mapping); + return ret; +} + +/** + * rproc_handle_carveout() - handle phys contig memory allocation requests + * @rproc: rproc handle + * @rsc: the resource entry + * @avail: size of available data (for image validation) + * + * This function will handle firmware requests for allocation of physically + * contiguous memory regions. + * + * These request entries should come first in the firmware's resource table, + * as other firmware entries might request placing other data objects inside + * these memory regions (e.g. data/code segments, trace resource entries, ...). + * + * Allocating memory this way helps utilizing the reserved physical memory + * (e.g. CMA) more efficiently, and also minimizes the number of TLB entries + * needed to map it (in case @rproc is using an IOMMU). Reducing the TLB + * pressure is important; it may have a substantial impact on performance. + */ +static int rproc_handle_carveout(struct rproc *rproc, + struct fw_rsc_carveout *rsc, int avail) +{ + struct rproc_mem_entry *carveout, *mapping; + struct device *dev = rproc->dev; + dma_addr_t dma; + void *va; + int ret; + + if (sizeof(*rsc) > avail) { + dev_err(rproc->dev, "carveout rsc is truncated\n"); + return -EINVAL; + } + + /* make sure reserved bytes are zeroes */ + if (rsc->reserved) { + dev_err(dev, "carveout rsc has non zero reserved bytes\n"); + return -EINVAL; + } + + dev_dbg(dev, "carveout rsc: da %x, pa %x, len %x, flags %x\n", + rsc->da, rsc->pa, rsc->len, rsc->flags); + + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); + if (!mapping) { + dev_err(dev, "kzalloc mapping failed\n"); + return -ENOMEM; + } + + carveout = kzalloc(sizeof(*carveout), GFP_KERNEL); + if (!carveout) { + dev_err(dev, "kzalloc carveout failed\n"); + ret = -ENOMEM; + goto free_mapping; + } + + va = dma_alloc_coherent(dev, rsc->len, &dma, GFP_KERNEL); + if (!va) { + dev_err(dev, "failed to dma alloc carveout: %d\n", rsc->len); + ret = -ENOMEM; + goto free_carv; + } + + dev_dbg(dev, "carveout va %p, dma %x, len 0x%x\n", va, dma, rsc->len); + + /* + * Ok, this is non-standard. + * + * Sometimes we can't rely on the generic iommu-based DMA API + * to dynamically allocate the device address and then set the IOMMU + * tables accordingly, because some remote processors might + * _require_ us to use hard coded device addresses that their + * firmware was compiled with. + * + * In this case, we must use the IOMMU API directly and map + * the memory to the device address as expected by the remote + * processor. + * + * Obviously such remote processor devices should not be configured + * to use the iommu-based DMA API: we expect 'dma' to contain the + * physical address in this case. + */ + if (rproc->domain) { + ret = iommu_map(rproc->domain, rsc->da, dma, rsc->len, + rsc->flags); + if (ret) { + dev_err(dev, "iommu_map failed: %d\n", ret); + goto dma_free; + } + + /* + * We'll need this info later when we'll want to unmap + * everything (e.g. on shutdown). + * + * We can't trust the remote processor not to change the + * resource table, so we must maintain this info independently. + */ + mapping->da = rsc->da; + mapping->len = rsc->len; + list_add_tail(&mapping->node, &rproc->mappings); + + dev_dbg(dev, "carveout mapped 0x%x to 0x%x\n", rsc->da, dma); + + /* + * Some remote processors might need to know the pa + * even though they are behind an IOMMU. E.g., OMAP4's + * remote M3 processor needs this so it can control + * on-chip hardware accelerators that are not behind + * the IOMMU, and therefor must know the pa. + * + * Generally we don't want to expose physical addresses + * if we don't have to (remote processors are generally + * _not_ trusted), so we might want to do this only for + * remote processor that _must_ have this (e.g. OMAP4's + * dual M3 subsystem). + */ + rsc->pa = dma; + } + + carveout->va = va; + carveout->len = rsc->len; + carveout->dma = dma; + carveout->da = rsc->da; + + list_add_tail(&carveout->node, &rproc->carveouts); + + return 0; + +dma_free: + dma_free_coherent(dev, rsc->len, va, dma); +free_carv: + kfree(carveout); +free_mapping: + kfree(mapping); + return ret; +} + +/* + * A lookup table for resource handlers. The indices are defined in + * enum fw_resource_type. + */ +static rproc_handle_resource_t rproc_handle_rsc[] = { + [RSC_CARVEOUT] = (rproc_handle_resource_t)rproc_handle_carveout, + [RSC_DEVMEM] = (rproc_handle_resource_t)rproc_handle_devmem, + [RSC_TRACE] = (rproc_handle_resource_t)rproc_handle_trace, + [RSC_VDEV] = NULL, /* VDEVs were handled upon registrarion */ +}; + +/* handle firmware resource entries before booting the remote processor */ +static int +rproc_handle_boot_rsc(struct rproc *rproc, struct resource_table *table, int len) +{ + struct device *dev = rproc->dev; + rproc_handle_resource_t handler; + int ret = 0, i; + + for (i = 0; i < table->num; i++) { + int offset = table->offset[i]; + struct fw_rsc_hdr *hdr = (void *)table + offset; + int avail = len - offset - sizeof(*hdr); + void *rsc = (void *)hdr + sizeof(*hdr); + + /* make sure table isn't truncated */ + if (avail < 0) { + dev_err(dev, "rsc table is truncated\n"); + return -EINVAL; + } + + dev_dbg(dev, "rsc: type %d\n", hdr->type); + + if (hdr->type >= RSC_LAST) { + dev_warn(dev, "unsupported resource %d\n", hdr->type); + continue; + } + + handler = rproc_handle_rsc[hdr->type]; + if (!handler) + continue; + + ret = handler(rproc, rsc, avail); + if (ret) + break; + } + + return ret; +} + +/* handle firmware resource entries while registering the remote processor */ +static int +rproc_handle_virtio_rsc(struct rproc *rproc, struct resource_table *table, int len) +{ + struct device *dev = rproc->dev; + int ret = 0, i; + + for (i = 0; i < table->num; i++) { + int offset = table->offset[i]; + struct fw_rsc_hdr *hdr = (void *)table + offset; + int avail = len - offset - sizeof(*hdr); + struct fw_rsc_vdev *vrsc; + + /* make sure table isn't truncated */ + if (avail < 0) { + dev_err(dev, "rsc table is truncated\n"); + return -EINVAL; + } + + dev_dbg(dev, "%s: rsc type %d\n", __func__, hdr->type); + + if (hdr->type != RSC_VDEV) + continue; + + vrsc = (struct fw_rsc_vdev *)hdr->data; + + ret = rproc_handle_vdev(rproc, vrsc, avail); + if (ret) + break; + } + + return ret; +} + +/** + * rproc_find_rsc_table() - find the resource table + * @rproc: the rproc handle + * @elf_data: the content of the ELF firmware image + * @len: firmware size (in bytes) + * @tablesz: place holder for providing back the table size + * + * This function finds the resource table inside the remote processor's + * firmware. It is used both upon the registration of @rproc (in order + * to look for and register the supported virito devices), and when the + * @rproc is booted. + * + * Returns the pointer to the resource table if it is found, and write its + * size into @tablesz. If a valid table isn't found, NULL is returned + * (and @tablesz isn't set). + */ +static struct resource_table * +rproc_find_rsc_table(struct rproc *rproc, const u8 *elf_data, size_t len, + int *tablesz) +{ + struct elf32_hdr *ehdr; + struct elf32_shdr *shdr; + const char *name_table; + struct device *dev = rproc->dev; + struct resource_table *table = NULL; + int i; + + ehdr = (struct elf32_hdr *)elf_data; + shdr = (struct elf32_shdr *)(elf_data + ehdr->e_shoff); + name_table = elf_data + shdr[ehdr->e_shstrndx].sh_offset; + + /* look for the resource table and handle it */ + for (i = 0; i < ehdr->e_shnum; i++, shdr++) { + int size = shdr->sh_size; + int offset = shdr->sh_offset; + + if (strcmp(name_table + shdr->sh_name, ".resource_table")) + continue; + + table = (struct resource_table *)(elf_data + offset); + + /* make sure we have the entire table */ + if (offset + size > len) { + dev_err(dev, "resource table truncated\n"); + return NULL; + } + + /* make sure table has at least the header */ + if (sizeof(struct resource_table) > size) { + dev_err(dev, "header-less resource table\n"); + return NULL; + } + + /* we don't support any version beyond the first */ + if (table->ver != 1) { + dev_err(dev, "unsupported fw ver: %d\n", table->ver); + return NULL; + } + + /* make sure reserved bytes are zeroes */ + if (table->reserved[0] || table->reserved[1]) { + dev_err(dev, "non zero reserved bytes\n"); + return NULL; + } + + /* make sure the offsets array isn't truncated */ + if (table->num * sizeof(table->offset[0]) + + sizeof(struct resource_table) > size) { + dev_err(dev, "resource table incomplete\n"); + return NULL; + } + + *tablesz = shdr->sh_size; + break; + } + + return table; +} + +/** + * rproc_resource_cleanup() - clean up and free all acquired resources + * @rproc: rproc handle + * + * This function will free all resources acquired for @rproc, and it + * is called whenever @rproc either shuts down or fails to boot. + */ +static void rproc_resource_cleanup(struct rproc *rproc) +{ + struct rproc_mem_entry *entry, *tmp; + struct device *dev = rproc->dev; + + /* clean up debugfs trace entries */ + list_for_each_entry_safe(entry, tmp, &rproc->traces, node) { + rproc_remove_trace_file(entry->priv); + rproc->num_traces--; + list_del(&entry->node); + kfree(entry); + } + + /* clean up carveout allocations */ + list_for_each_entry_safe(entry, tmp, &rproc->carveouts, node) { + dma_free_coherent(dev, entry->len, entry->va, entry->dma); + list_del(&entry->node); + kfree(entry); + } + + /* clean up iommu mapping entries */ + list_for_each_entry_safe(entry, tmp, &rproc->mappings, node) { + size_t unmapped; + + unmapped = iommu_unmap(rproc->domain, entry->da, entry->len); + if (unmapped != entry->len) { + /* nothing much to do besides complaining */ + dev_err(dev, "failed to unmap %u/%u\n", entry->len, + unmapped); + } + + list_del(&entry->node); + kfree(entry); + } +} + +/* make sure this fw image is sane */ +static int rproc_fw_sanity_check(struct rproc *rproc, const struct firmware *fw) +{ + const char *name = rproc->firmware; + struct device *dev = rproc->dev; + struct elf32_hdr *ehdr; + char class; + + if (!fw) { + dev_err(dev, "failed to load %s\n", name); + return -EINVAL; + } + + if (fw->size < sizeof(struct elf32_hdr)) { + dev_err(dev, "Image is too small\n"); + return -EINVAL; + } + + ehdr = (struct elf32_hdr *)fw->data; + + /* We only support ELF32 at this point */ + class = ehdr->e_ident[EI_CLASS]; + if (class != ELFCLASS32) { + dev_err(dev, "Unsupported class: %d\n", class); + return -EINVAL; + } + + /* We assume the firmware has the same endianess as the host */ +# ifdef __LITTLE_ENDIAN + if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) { +# else /* BIG ENDIAN */ + if (ehdr->e_ident[EI_DATA] != ELFDATA2MSB) { +# endif + dev_err(dev, "Unsupported firmware endianess\n"); + return -EINVAL; + } + + if (fw->size < ehdr->e_shoff + sizeof(struct elf32_shdr)) { + dev_err(dev, "Image is too small\n"); + return -EINVAL; + } + + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { + dev_err(dev, "Image is corrupted (bad magic)\n"); + return -EINVAL; + } + + if (ehdr->e_phnum == 0) { + dev_err(dev, "No loadable segments\n"); + return -EINVAL; + } + + if (ehdr->e_phoff > fw->size) { + dev_err(dev, "Firmware size is too small\n"); + return -EINVAL; + } + + return 0; +} + +/* + * take a firmware and boot a remote processor with it. + */ +static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) +{ + struct device *dev = rproc->dev; + const char *name = rproc->firmware; + struct elf32_hdr *ehdr; + struct resource_table *table; + int ret, tablesz; + + ret = rproc_fw_sanity_check(rproc, fw); + if (ret) + return ret; + + ehdr = (struct elf32_hdr *)fw->data; + + dev_info(dev, "Booting fw image %s, size %d\n", name, fw->size); + + /* + * if enabling an IOMMU isn't relevant for this rproc, this is + * just a nop + */ + ret = rproc_enable_iommu(rproc); + if (ret) { + dev_err(dev, "can't enable iommu: %d\n", ret); + return ret; + } + + /* + * The ELF entry point is the rproc's boot addr (though this is not + * a configurable property of all remote processors: some will always + * boot at a specific hardcoded address). + */ + rproc->bootaddr = ehdr->e_entry; + + /* look for the resource table */ + table = rproc_find_rsc_table(rproc, fw->data, fw->size, &tablesz); + if (!table) + goto clean_up; + + /* handle fw resources which are required to boot rproc */ + ret = rproc_handle_boot_rsc(rproc, table, tablesz); + if (ret) { + dev_err(dev, "Failed to process resources: %d\n", ret); + goto clean_up; + } + + /* load the ELF segments to memory */ + ret = rproc_load_segments(rproc, fw->data, fw->size); + if (ret) { + dev_err(dev, "Failed to load program segments: %d\n", ret); + goto clean_up; + } + + /* power up the remote processor */ + ret = rproc->ops->start(rproc); + if (ret) { + dev_err(dev, "can't start rproc %s: %d\n", rproc->name, ret); + goto clean_up; + } + + rproc->state = RPROC_RUNNING; + + dev_info(dev, "remote processor %s is now up\n", rproc->name); + + return 0; + +clean_up: + rproc_resource_cleanup(rproc); + rproc_disable_iommu(rproc); + return ret; +} + +/* + * take a firmware and look for virtio devices to register. + * + * Note: this function is called asynchronously upon registration of the + * remote processor (so we must wait until it completes before we try + * to unregister the device. one other option is just to use kref here, + * that might be cleaner). + */ +static void rproc_fw_config_virtio(const struct firmware *fw, void *context) +{ + struct rproc *rproc = context; + struct resource_table *table; + int ret, tablesz; + + if (rproc_fw_sanity_check(rproc, fw) < 0) + goto out; + + /* look for the resource table */ + table = rproc_find_rsc_table(rproc, fw->data, fw->size, &tablesz); + if (!table) + goto out; + + /* look for virtio devices and register them */ + ret = rproc_handle_virtio_rsc(rproc, table, tablesz); + if (ret) + goto out; + +out: + if (fw) + release_firmware(fw); + /* allow rproc_unregister() contexts, if any, to proceed */ + complete_all(&rproc->firmware_loading_complete); +} + +/** + * rproc_boot() - boot a remote processor + * @rproc: handle of a remote processor + * + * Boot a remote processor (i.e. load its firmware, power it on, ...). + * + * If the remote processor is already powered on, this function immediately + * returns (successfully). + * + * Returns 0 on success, and an appropriate error value otherwise. + */ +int rproc_boot(struct rproc *rproc) +{ + const struct firmware *firmware_p; + struct device *dev; + int ret; + + if (!rproc) { + pr_err("invalid rproc handle\n"); + return -EINVAL; + } + + dev = rproc->dev; + + ret = mutex_lock_interruptible(&rproc->lock); + if (ret) { + dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, ret); + return ret; + } + + /* loading a firmware is required */ + if (!rproc->firmware) { + dev_err(dev, "%s: no firmware to load\n", __func__); + ret = -EINVAL; + goto unlock_mutex; + } + + /* prevent underlying implementation from being removed */ + if (!try_module_get(dev->driver->owner)) { + dev_err(dev, "%s: can't get owner\n", __func__); + ret = -EINVAL; + goto unlock_mutex; + } + + /* skip the boot process if rproc is already powered up */ + if (atomic_inc_return(&rproc->power) > 1) { + ret = 0; + goto unlock_mutex; + } + + dev_info(dev, "powering up %s\n", rproc->name); + + /* load firmware */ + ret = request_firmware(&firmware_p, rproc->firmware, dev); + if (ret < 0) { + dev_err(dev, "request_firmware failed: %d\n", ret); + goto downref_rproc; + } + + ret = rproc_fw_boot(rproc, firmware_p); + + release_firmware(firmware_p); + +downref_rproc: + if (ret) { + module_put(dev->driver->owner); + atomic_dec(&rproc->power); + } +unlock_mutex: + mutex_unlock(&rproc->lock); + return ret; +} +EXPORT_SYMBOL(rproc_boot); + +/** + * rproc_shutdown() - power off the remote processor + * @rproc: the remote processor + * + * Power off a remote processor (previously booted with rproc_boot()). + * + * In case @rproc is still being used by an additional user(s), then + * this function will just decrement the power refcount and exit, + * without really powering off the device. + * + * Every call to rproc_boot() must (eventually) be accompanied by a call + * to rproc_shutdown(). Calling rproc_shutdown() redundantly is a bug. + * + * Notes: + * - we're not decrementing the rproc's refcount, only the power refcount. + * which means that the @rproc handle stays valid even after rproc_shutdown() + * returns, and users can still use it with a subsequent rproc_boot(), if + * needed. + * - don't call rproc_shutdown() to unroll rproc_get_by_name(), exactly + * because rproc_shutdown() _does not_ decrement the refcount of @rproc. + * To decrement the refcount of @rproc, use rproc_put() (but _only_ if + * you acquired @rproc using rproc_get_by_name()). + */ +void rproc_shutdown(struct rproc *rproc) +{ + struct device *dev = rproc->dev; + int ret; + + ret = mutex_lock_interruptible(&rproc->lock); + if (ret) { + dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, ret); + return; + } + + /* if the remote proc is still needed, bail out */ + if (!atomic_dec_and_test(&rproc->power)) + goto out; + + /* power off the remote processor */ + ret = rproc->ops->stop(rproc); + if (ret) { + atomic_inc(&rproc->power); + dev_err(dev, "can't stop rproc: %d\n", ret); + goto out; + } + + /* clean up all acquired resources */ + rproc_resource_cleanup(rproc); + + rproc_disable_iommu(rproc); + + rproc->state = RPROC_OFFLINE; + + dev_info(dev, "stopped remote processor %s\n", rproc->name); + +out: + mutex_unlock(&rproc->lock); + if (!ret) + module_put(dev->driver->owner); +} +EXPORT_SYMBOL(rproc_shutdown); + +/** + * rproc_release() - completely deletes the existence of a remote processor + * @kref: the rproc's kref + * + * This function should _never_ be called directly. + * + * The only reasonable location to use it is as an argument when kref_put'ing + * @rproc's refcount. + * + * This way it will be called when no one holds a valid pointer to this @rproc + * anymore (and obviously after it is removed from the rprocs klist). + * + * Note: this function is not static because rproc_vdev_release() needs it when + * it decrements @rproc's refcount. + */ +void rproc_release(struct kref *kref) +{ + struct rproc *rproc = container_of(kref, struct rproc, refcount); + struct rproc_vdev *rvdev, *rvtmp; + + dev_info(rproc->dev, "removing %s\n", rproc->name); + + rproc_delete_debug_dir(rproc); + + /* clean up remote vdev entries */ + list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node) { + __rproc_free_vrings(rvdev, RVDEV_NUM_VRINGS); + list_del(&rvdev->node); + } + + /* + * At this point no one holds a reference to rproc anymore, + * so we can directly unroll rproc_alloc() + */ + rproc_free(rproc); +} + +/* will be called when an rproc is added to the rprocs klist */ +static void klist_rproc_get(struct klist_node *n) +{ + struct rproc *rproc = container_of(n, struct rproc, node); + + kref_get(&rproc->refcount); +} + +/* will be called when an rproc is removed from the rprocs klist */ +static void klist_rproc_put(struct klist_node *n) +{ + struct rproc *rproc = container_of(n, struct rproc, node); + + kref_put(&rproc->refcount, rproc_release); +} + +static struct rproc *next_rproc(struct klist_iter *i) +{ + struct klist_node *n; + + n = klist_next(i); + if (!n) + return NULL; + + return container_of(n, struct rproc, node); +} + +/** + * rproc_get_by_name() - find a remote processor by name and boot it + * @name: name of the remote processor + * + * Finds an rproc handle using the remote processor's name, and then + * boot it. If it's already powered on, then just immediately return + * (successfully). + * + * Returns the rproc handle on success, and NULL on failure. + * + * This function increments the remote processor's refcount, so always + * use rproc_put() to decrement it back once rproc isn't needed anymore. + * + * Note: currently this function (and its counterpart rproc_put()) are not + * being used. We need to scrutinize the use cases + * that still need them, and see if we can migrate them to use the non + * name-based boot/shutdown interface. + */ +struct rproc *rproc_get_by_name(const char *name) +{ + struct rproc *rproc; + struct klist_iter i; + int ret; + + /* find the remote processor, and upref its refcount */ + klist_iter_init(&rprocs, &i); + while ((rproc = next_rproc(&i)) != NULL) + if (!strcmp(rproc->name, name)) { + kref_get(&rproc->refcount); + break; + } + klist_iter_exit(&i); + + /* can't find this rproc ? */ + if (!rproc) { + pr_err("can't find remote processor %s\n", name); + return NULL; + } + + ret = rproc_boot(rproc); + if (ret < 0) { + kref_put(&rproc->refcount, rproc_release); + return NULL; + } + + return rproc; +} +EXPORT_SYMBOL(rproc_get_by_name); + +/** + * rproc_put() - decrement the refcount of a remote processor, and shut it down + * @rproc: the remote processor + * + * This function tries to shutdown @rproc, and it then decrements its + * refcount. + * + * After this function returns, @rproc may _not_ be used anymore, and its + * handle should be considered invalid. + * + * This function should be called _iff_ the @rproc handle was grabbed by + * calling rproc_get_by_name(). + */ +void rproc_put(struct rproc *rproc) +{ + /* try to power off the remote processor */ + rproc_shutdown(rproc); + + /* downref rproc's refcount */ + kref_put(&rproc->refcount, rproc_release); +} +EXPORT_SYMBOL(rproc_put); + +/** + * rproc_register() - register a remote processor + * @rproc: the remote processor handle to register + * + * Registers @rproc with the remoteproc framework, after it has been + * allocated with rproc_alloc(). + * + * This is called by the platform-specific rproc implementation, whenever + * a new remote processor device is probed. + * + * Returns 0 on success and an appropriate error code otherwise. + * + * Note: this function initiates an asynchronous firmware loading + * context, which will look for virtio devices supported by the rproc's + * firmware. + * + * If found, those virtio devices will be created and added, so as a result + * of registering this remote processor, additional virtio drivers might be + * probed. + */ +int rproc_register(struct rproc *rproc) +{ + struct device *dev = rproc->dev; + int ret = 0; + + /* expose to rproc_get_by_name users */ + klist_add_tail(&rproc->node, &rprocs); + + dev_info(rproc->dev, "%s is available\n", rproc->name); + + dev_info(dev, "Note: remoteproc is still under development and considered experimental.\n"); + dev_info(dev, "THE BINARY FORMAT IS NOT YET FINALIZED, and backward compatibility isn't yet guaranteed.\n"); + + /* create debugfs entries */ + rproc_create_debug_dir(rproc); + + /* rproc_unregister() calls must wait until async loader completes */ + init_completion(&rproc->firmware_loading_complete); + + /* + * We must retrieve early virtio configuration info from + * the firmware (e.g. whether to register a virtio device, + * what virtio features does it support, ...). + * + * We're initiating an asynchronous firmware loading, so we can + * be built-in kernel code, without hanging the boot process. + */ + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + rproc->firmware, dev, GFP_KERNEL, + rproc, rproc_fw_config_virtio); + if (ret < 0) { + dev_err(dev, "request_firmware_nowait failed: %d\n", ret); + complete_all(&rproc->firmware_loading_complete); + klist_remove(&rproc->node); + } + + return ret; +} +EXPORT_SYMBOL(rproc_register); + +/** + * rproc_alloc() - allocate a remote processor handle + * @dev: the underlying device + * @name: name of this remote processor + * @ops: platform-specific handlers (mainly start/stop) + * @firmware: name of firmware file to load + * @len: length of private data needed by the rproc driver (in bytes) + * + * Allocates a new remote processor handle, but does not register + * it yet. + * + * This function should be used by rproc implementations during initialization + * of the remote processor. + * + * After creating an rproc handle using this function, and when ready, + * implementations should then call rproc_register() to complete + * the registration of the remote processor. + * + * On success the new rproc is returned, and on failure, NULL. + * + * Note: _never_ directly deallocate @rproc, even if it was not registered + * yet. Instead, if you just need to unroll rproc_alloc(), use rproc_free(). + */ +struct rproc *rproc_alloc(struct device *dev, const char *name, + const struct rproc_ops *ops, + const char *firmware, int len) +{ + struct rproc *rproc; + + if (!dev || !name || !ops) + return NULL; + + rproc = kzalloc(sizeof(struct rproc) + len, GFP_KERNEL); + if (!rproc) { + dev_err(dev, "%s: kzalloc failed\n", __func__); + return NULL; + } + + rproc->dev = dev; + rproc->name = name; + rproc->ops = ops; + rproc->firmware = firmware; + rproc->priv = &rproc[1]; + + atomic_set(&rproc->power, 0); + + kref_init(&rproc->refcount); + + mutex_init(&rproc->lock); + + idr_init(&rproc->notifyids); + + INIT_LIST_HEAD(&rproc->carveouts); + INIT_LIST_HEAD(&rproc->mappings); + INIT_LIST_HEAD(&rproc->traces); + INIT_LIST_HEAD(&rproc->rvdevs); + + rproc->state = RPROC_OFFLINE; + + return rproc; +} +EXPORT_SYMBOL(rproc_alloc); + +/** + * rproc_free() - free an rproc handle that was allocated by rproc_alloc + * @rproc: the remote processor handle + * + * This function should _only_ be used if @rproc was only allocated, + * but not registered yet. + * + * If @rproc was already successfully registered (by calling rproc_register()), + * then use rproc_unregister() instead. + */ +void rproc_free(struct rproc *rproc) +{ + idr_remove_all(&rproc->notifyids); + idr_destroy(&rproc->notifyids); + + kfree(rproc); +} +EXPORT_SYMBOL(rproc_free); + +/** + * rproc_unregister() - unregister a remote processor + * @rproc: rproc handle to unregister + * + * Unregisters a remote processor, and decrements its refcount. + * If its refcount drops to zero, then @rproc will be freed. If not, + * it will be freed later once the last reference is dropped. + * + * This function should be called when the platform specific rproc + * implementation decides to remove the rproc device. it should + * _only_ be called if a previous invocation of rproc_register() + * has completed successfully. + * + * After rproc_unregister() returns, @rproc is _not_ valid anymore and + * it shouldn't be used. More specifically, don't call rproc_free() + * or try to directly free @rproc after rproc_unregister() returns; + * none of these are needed, and calling them is a bug. + * + * Returns 0 on success and -EINVAL if @rproc isn't valid. + */ +int rproc_unregister(struct rproc *rproc) +{ + struct rproc_vdev *rvdev; + + if (!rproc) + return -EINVAL; + + /* if rproc is just being registered, wait */ + wait_for_completion(&rproc->firmware_loading_complete); + + /* clean up remote vdev entries */ + list_for_each_entry(rvdev, &rproc->rvdevs, node) + rproc_remove_virtio_dev(rvdev); + + /* the rproc is downref'ed as soon as it's removed from the klist */ + klist_del(&rproc->node); + + /* the rproc will only be released after its refcount drops to zero */ + kref_put(&rproc->refcount, rproc_release); + + return 0; +} +EXPORT_SYMBOL(rproc_unregister); + +static int __init remoteproc_init(void) +{ + rproc_init_debugfs(); + return 0; +} +module_init(remoteproc_init); + +static void __exit remoteproc_exit(void) +{ + rproc_exit_debugfs(); +} +module_exit(remoteproc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Generic Remote Processor Framework"); diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c new file mode 100644 index 000000000000..70277a530133 --- /dev/null +++ b/drivers/remoteproc/remoteproc_debugfs.c @@ -0,0 +1,179 @@ +/* + * Remote Processor Framework + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Mark Grosen <mgrosen@ti.com> + * Brian Swetland <swetland@google.com> + * Fernando Guzman Lugo <fernando.lugo@ti.com> + * Suman Anna <s-anna@ti.com> + * Robert Tivy <rtivy@ti.com> + * Armando Uribe De Leon <x0095078@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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/debugfs.h> +#include <linux/remoteproc.h> +#include <linux/device.h> + +/* remoteproc debugfs parent dir */ +static struct dentry *rproc_dbg; + +/* + * Some remote processors may support dumping trace logs into a shared + * memory buffer. We expose this trace buffer using debugfs, so users + * can easily tell what's going on remotely. + * + * We will most probably improve the rproc tracing facilities later on, + * but this kind of lightweight and simple mechanism is always good to have, + * as it provides very early tracing with little to no dependencies at all. + */ +static ssize_t rproc_trace_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct rproc_mem_entry *trace = filp->private_data; + int len = strnlen(trace->va, trace->len); + + return simple_read_from_buffer(userbuf, count, ppos, trace->va, len); +} + +static int rproc_open_generic(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static const struct file_operations trace_rproc_ops = { + .read = rproc_trace_read, + .open = rproc_open_generic, + .llseek = generic_file_llseek, +}; + +/* + * A state-to-string lookup table, for exposing a human readable state + * via debugfs. Always keep in sync with enum rproc_state + */ +static const char * const rproc_state_string[] = { + "offline", + "suspended", + "running", + "crashed", + "invalid", +}; + +/* expose the state of the remote processor via debugfs */ +static ssize_t rproc_state_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct rproc *rproc = filp->private_data; + unsigned int state; + char buf[30]; + int i; + + state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state; + + i = snprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state], + rproc->state); + + return simple_read_from_buffer(userbuf, count, ppos, buf, i); +} + +static const struct file_operations rproc_state_ops = { + .read = rproc_state_read, + .open = rproc_open_generic, + .llseek = generic_file_llseek, +}; + +/* expose the name of the remote processor via debugfs */ +static ssize_t rproc_name_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct rproc *rproc = filp->private_data; + /* need room for the name, a newline and a terminating null */ + char buf[100]; + int i; + + i = snprintf(buf, sizeof(buf), "%.98s\n", rproc->name); + + return simple_read_from_buffer(userbuf, count, ppos, buf, i); +} + +static const struct file_operations rproc_name_ops = { + .read = rproc_name_read, + .open = rproc_open_generic, + .llseek = generic_file_llseek, +}; + +void rproc_remove_trace_file(struct dentry *tfile) +{ + debugfs_remove(tfile); +} + +struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc, + struct rproc_mem_entry *trace) +{ + struct dentry *tfile; + + tfile = debugfs_create_file(name, 0400, rproc->dbg_dir, + trace, &trace_rproc_ops); + if (!tfile) { + dev_err(rproc->dev, "failed to create debugfs trace entry\n"); + return NULL; + } + + return tfile; +} + +void rproc_delete_debug_dir(struct rproc *rproc) +{ + if (!rproc->dbg_dir) + return; + + debugfs_remove_recursive(rproc->dbg_dir); +} + +void rproc_create_debug_dir(struct rproc *rproc) +{ + struct device *dev = rproc->dev; + + if (!rproc_dbg) + return; + + rproc->dbg_dir = debugfs_create_dir(dev_name(dev), rproc_dbg); + if (!rproc->dbg_dir) + return; + + debugfs_create_file("name", 0400, rproc->dbg_dir, + rproc, &rproc_name_ops); + debugfs_create_file("state", 0400, rproc->dbg_dir, + rproc, &rproc_state_ops); +} + +void __init rproc_init_debugfs(void) +{ + if (debugfs_initialized()) { + rproc_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL); + if (!rproc_dbg) + pr_err("can't create debugfs dir\n"); + } +} + +void __exit rproc_exit_debugfs(void) +{ + if (rproc_dbg) + debugfs_remove(rproc_dbg); +} diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h new file mode 100644 index 000000000000..9f336d6bdef3 --- /dev/null +++ b/drivers/remoteproc/remoteproc_internal.h @@ -0,0 +1,44 @@ +/* + * Remote processor framework + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef REMOTEPROC_INTERNAL_H +#define REMOTEPROC_INTERNAL_H + +#include <linux/irqreturn.h> + +struct rproc; + +/* from remoteproc_core.c */ +void rproc_release(struct kref *kref); +irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id); + +/* from remoteproc_virtio.c */ +int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id); +void rproc_remove_virtio_dev(struct rproc_vdev *rvdev); + +/* from remoteproc_debugfs.c */ +void rproc_remove_trace_file(struct dentry *tfile); +struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc, + struct rproc_mem_entry *trace); +void rproc_delete_debug_dir(struct rproc *rproc); +void rproc_create_debug_dir(struct rproc *rproc); +void rproc_init_debugfs(void); +void rproc_exit_debugfs(void); + +#endif /* REMOTEPROC_INTERNAL_H */ diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c new file mode 100644 index 000000000000..ecf612130750 --- /dev/null +++ b/drivers/remoteproc/remoteproc_virtio.c @@ -0,0 +1,289 @@ +/* + * Remote processor messaging transport (OMAP platform-specific bits) + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.com> + * + * 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/export.h> +#include <linux/remoteproc.h> +#include <linux/virtio.h> +#include <linux/virtio_config.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_ring.h> +#include <linux/err.h> +#include <linux/kref.h> +#include <linux/slab.h> + +#include "remoteproc_internal.h" + +/* kick the remote processor, and let it know which virtqueue to poke at */ +static void rproc_virtio_notify(struct virtqueue *vq) +{ + struct rproc_vring *rvring = vq->priv; + struct rproc *rproc = rvring->rvdev->rproc; + int notifyid = rvring->notifyid; + + dev_dbg(rproc->dev, "kicking vq index: %d\n", notifyid); + + rproc->ops->kick(rproc, notifyid); +} + +/** + * rproc_vq_interrupt() - tell remoteproc that a virtqueue is interrupted + * @rproc: handle to the remote processor + * @notifyid: index of the signalled virtqueue (unique per this @rproc) + * + * This function should be called by the platform-specific rproc driver, + * when the remote processor signals that a specific virtqueue has pending + * messages available. + * + * Returns IRQ_NONE if no message was found in the @notifyid virtqueue, + * and otherwise returns IRQ_HANDLED. + */ +irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid) +{ + struct rproc_vring *rvring; + + dev_dbg(rproc->dev, "vq index %d is interrupted\n", notifyid); + + rvring = idr_find(&rproc->notifyids, notifyid); + if (!rvring || !rvring->vq) + return IRQ_NONE; + + return vring_interrupt(0, rvring->vq); +} +EXPORT_SYMBOL(rproc_vq_interrupt); + +static struct virtqueue *rp_find_vq(struct virtio_device *vdev, + unsigned id, + void (*callback)(struct virtqueue *vq), + const char *name) +{ + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + struct rproc *rproc = vdev_to_rproc(vdev); + struct rproc_vring *rvring; + struct virtqueue *vq; + void *addr; + int len, size; + + /* we're temporarily limited to two virtqueues per rvdev */ + if (id >= ARRAY_SIZE(rvdev->vring)) + return ERR_PTR(-EINVAL); + + rvring = &rvdev->vring[id]; + + addr = rvring->va; + len = rvring->len; + + /* zero vring */ + size = vring_size(len, rvring->align); + memset(addr, 0, size); + + dev_dbg(rproc->dev, "vring%d: va %p qsz %d notifyid %d\n", + id, addr, len, rvring->notifyid); + + /* + * Create the new vq, and tell virtio we're not interested in + * the 'weak' smp barriers, since we're talking with a real device. + */ + vq = vring_new_virtqueue(len, rvring->align, vdev, false, addr, + rproc_virtio_notify, callback, name); + if (!vq) { + dev_err(rproc->dev, "vring_new_virtqueue %s failed\n", name); + return ERR_PTR(-ENOMEM); + } + + rvring->vq = vq; + vq->priv = rvring; + + return vq; +} + +static void rproc_virtio_del_vqs(struct virtio_device *vdev) +{ + struct virtqueue *vq, *n; + struct rproc *rproc = vdev_to_rproc(vdev); + struct rproc_vring *rvring; + + /* power down the remote processor before deleting vqs */ + rproc_shutdown(rproc); + + list_for_each_entry_safe(vq, n, &vdev->vqs, list) { + rvring = vq->priv; + rvring->vq = NULL; + vring_del_virtqueue(vq); + } +} + +static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs, + struct virtqueue *vqs[], + vq_callback_t *callbacks[], + const char *names[]) +{ + struct rproc *rproc = vdev_to_rproc(vdev); + int i, ret; + + for (i = 0; i < nvqs; ++i) { + vqs[i] = rp_find_vq(vdev, i, callbacks[i], names[i]); + if (IS_ERR(vqs[i])) { + ret = PTR_ERR(vqs[i]); + goto error; + } + } + + /* now that the vqs are all set, boot the remote processor */ + ret = rproc_boot(rproc); + if (ret) { + dev_err(rproc->dev, "rproc_boot() failed %d\n", ret); + goto error; + } + + return 0; + +error: + rproc_virtio_del_vqs(vdev); + return ret; +} + +/* + * We don't support yet real virtio status semantics. + * + * The plan is to provide this via the VDEV resource entry + * which is part of the firmware: this way the remote processor + * will be able to access the status values as set by us. + */ +static u8 rproc_virtio_get_status(struct virtio_device *vdev) +{ + return 0; +} + +static void rproc_virtio_set_status(struct virtio_device *vdev, u8 status) +{ + dev_dbg(&vdev->dev, "status: %d\n", status); +} + +static void rproc_virtio_reset(struct virtio_device *vdev) +{ + dev_dbg(&vdev->dev, "reset !\n"); +} + +/* provide the vdev features as retrieved from the firmware */ +static u32 rproc_virtio_get_features(struct virtio_device *vdev) +{ + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + + return rvdev->dfeatures; +} + +static void rproc_virtio_finalize_features(struct virtio_device *vdev) +{ + struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); + + /* Give virtio_ring a chance to accept features */ + vring_transport_features(vdev); + + /* + * Remember the finalized features of our vdev, and provide it + * to the remote processor once it is powered on. + * + * Similarly to the status field, we don't expose yet the negotiated + * features to the remote processors at this point. This will be + * fixed as part of a small resource table overhaul and then an + * extension of the virtio resource entries. + */ + rvdev->gfeatures = vdev->features[0]; +} + +static struct virtio_config_ops rproc_virtio_config_ops = { + .get_features = rproc_virtio_get_features, + .finalize_features = rproc_virtio_finalize_features, + .find_vqs = rproc_virtio_find_vqs, + .del_vqs = rproc_virtio_del_vqs, + .reset = rproc_virtio_reset, + .set_status = rproc_virtio_set_status, + .get_status = rproc_virtio_get_status, +}; + +/* + * This function is called whenever vdev is released, and is responsible + * to decrement the remote processor's refcount taken when vdev was + * added. + * + * Never call this function directly; it will be called by the driver + * core when needed. + */ +static void rproc_vdev_release(struct device *dev) +{ + struct virtio_device *vdev = dev_to_virtio(dev); + struct rproc *rproc = vdev_to_rproc(vdev); + + kref_put(&rproc->refcount, rproc_release); +} + +/** + * rproc_add_virtio_dev() - register an rproc-induced virtio device + * @rvdev: the remote vdev + * + * This function registers a virtio device. This vdev's partent is + * the rproc device. + * + * Returns 0 on success or an appropriate error value otherwise. + */ +int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id) +{ + struct rproc *rproc = rvdev->rproc; + struct device *dev = rproc->dev; + struct virtio_device *vdev = &rvdev->vdev; + int ret; + + vdev->id.device = id, + vdev->config = &rproc_virtio_config_ops, + vdev->dev.parent = dev; + vdev->dev.release = rproc_vdev_release; + + /* + * We're indirectly making a non-temporary copy of the rproc pointer + * here, because drivers probed with this vdev will indirectly + * access the wrapping rproc. + * + * Therefore we must increment the rproc refcount here, and decrement + * it _only_ when the vdev is released. + */ + kref_get(&rproc->refcount); + + ret = register_virtio_device(vdev); + if (ret) { + kref_put(&rproc->refcount, rproc_release); + dev_err(dev, "failed to register vdev: %d\n", ret); + goto out; + } + + dev_info(dev, "registered %s (type %d)\n", dev_name(&vdev->dev), id); + +out: + return ret; +} + +/** + * rproc_remove_virtio_dev() - remove an rproc-induced virtio device + * @rvdev: the remote vdev + * + * This function unregisters an existing virtio device. + */ +void rproc_remove_virtio_dev(struct rproc_vdev *rvdev) +{ + unregister_virtio_device(&rvdev->vdev); +} diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig new file mode 100644 index 000000000000..32aead65735a --- /dev/null +++ b/drivers/rpmsg/Kconfig @@ -0,0 +1,10 @@ +menu "Rpmsg drivers (EXPERIMENTAL)" + +# RPMSG always gets selected by whoever wants it +config RPMSG + tristate + select VIRTIO + select VIRTIO_RING + depends on EXPERIMENTAL + +endmenu diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile new file mode 100644 index 000000000000..7617fcb8259f --- /dev/null +++ b/drivers/rpmsg/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_RPMSG) += virtio_rpmsg_bus.o diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c new file mode 100644 index 000000000000..75506ec2840e --- /dev/null +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -0,0 +1,1054 @@ +/* + * Virtio-based remote processor messaging bus + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.com> + * + * 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/virtio.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_config.h> +#include <linux/scatterlist.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/idr.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/rpmsg.h> +#include <linux/mutex.h> + +/** + * struct virtproc_info - virtual remote processor state + * @vdev: the virtio device + * @rvq: rx virtqueue + * @svq: tx virtqueue + * @rbufs: kernel address of rx buffers + * @sbufs: kernel address of tx buffers + * @last_sbuf: index of last tx buffer used + * @bufs_dma: dma base addr of the buffers + * @tx_lock: protects svq, sbufs and sleepers, to allow concurrent senders. + * sending a message might require waking up a dozing remote + * processor, which involves sleeping, hence the mutex. + * @endpoints: idr of local endpoints, allows fast retrieval + * @endpoints_lock: lock of the endpoints set + * @sendq: wait queue of sending contexts waiting for a tx buffers + * @sleepers: number of senders that are waiting for a tx buffer + * @ns_ept: the bus's name service endpoint + * + * This structure stores the rpmsg state of a given virtio remote processor + * device (there might be several virtio proc devices for each physical + * remote processor). + */ +struct virtproc_info { + struct virtio_device *vdev; + struct virtqueue *rvq, *svq; + void *rbufs, *sbufs; + int last_sbuf; + dma_addr_t bufs_dma; + struct mutex tx_lock; + struct idr endpoints; + struct mutex endpoints_lock; + wait_queue_head_t sendq; + atomic_t sleepers; + struct rpmsg_endpoint *ns_ept; +}; + +/** + * struct rpmsg_channel_info - internal channel info representation + * @name: name of service + * @src: local address + * @dst: destination address + */ +struct rpmsg_channel_info { + char name[RPMSG_NAME_SIZE]; + u32 src; + u32 dst; +}; + +#define to_rpmsg_channel(d) container_of(d, struct rpmsg_channel, dev) +#define to_rpmsg_driver(d) container_of(d, struct rpmsg_driver, drv) + +/* + * We're allocating 512 buffers of 512 bytes for communications, and then + * using the first 256 buffers for RX, and the last 256 buffers for TX. + * + * Each buffer will have 16 bytes for the msg header and 496 bytes for + * the payload. + * + * This will require a total space of 256KB for the buffers. + * + * We might also want to add support for user-provided buffers in time. + * This will allow bigger buffer size flexibility, and can also be used + * to achieve zero-copy messaging. + * + * Note that these numbers are purely a decision of this driver - we + * can change this without changing anything in the firmware of the remote + * processor. + */ +#define RPMSG_NUM_BUFS (512) +#define RPMSG_BUF_SIZE (512) +#define RPMSG_TOTAL_BUF_SPACE (RPMSG_NUM_BUFS * RPMSG_BUF_SIZE) + +/* + * Local addresses are dynamically allocated on-demand. + * We do not dynamically assign addresses from the low 1024 range, + * in order to reserve that address range for predefined services. + */ +#define RPMSG_RESERVED_ADDRESSES (1024) + +/* Address 53 is reserved for advertising remote services */ +#define RPMSG_NS_ADDR (53) + +/* sysfs show configuration fields */ +#define rpmsg_show_attr(field, path, format_string) \ +static ssize_t \ +field##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); \ + \ + return sprintf(buf, format_string, rpdev->path); \ +} + +/* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */ +rpmsg_show_attr(name, id.name, "%s\n"); +rpmsg_show_attr(src, src, "0x%x\n"); +rpmsg_show_attr(dst, dst, "0x%x\n"); +rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n"); + +/* + * Unique (and free running) index for rpmsg devices. + * + * Yeah, we're not recycling those numbers (yet?). will be easy + * to change if/when we want to. + */ +static unsigned int rpmsg_dev_index; + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + return sprintf(buf, RPMSG_DEVICE_MODALIAS_FMT "\n", rpdev->id.name); +} + +static struct device_attribute rpmsg_dev_attrs[] = { + __ATTR_RO(name), + __ATTR_RO(modalias), + __ATTR_RO(dst), + __ATTR_RO(src), + __ATTR_RO(announce), + __ATTR_NULL +}; + +/* rpmsg devices and drivers are matched using the service name */ +static inline int rpmsg_id_match(const struct rpmsg_channel *rpdev, + const struct rpmsg_device_id *id) +{ + return strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE) == 0; +} + +/* match rpmsg channel and rpmsg driver */ +static int rpmsg_dev_match(struct device *dev, struct device_driver *drv) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + struct rpmsg_driver *rpdrv = to_rpmsg_driver(drv); + const struct rpmsg_device_id *ids = rpdrv->id_table; + unsigned int i; + + for (i = 0; ids[i].name[0]; i++) + if (rpmsg_id_match(rpdev, &ids[i])) + return 1; + + return 0; +} + +static int rpmsg_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT, + rpdev->id.name); +} + +/* for more info, see below documentation of rpmsg_create_ept() */ +static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp, + struct rpmsg_channel *rpdev, rpmsg_rx_cb_t cb, + void *priv, u32 addr) +{ + int err, tmpaddr, request; + struct rpmsg_endpoint *ept; + struct device *dev = rpdev ? &rpdev->dev : &vrp->vdev->dev; + + if (!idr_pre_get(&vrp->endpoints, GFP_KERNEL)) + return NULL; + + ept = kzalloc(sizeof(*ept), GFP_KERNEL); + if (!ept) { + dev_err(dev, "failed to kzalloc a new ept\n"); + return NULL; + } + + ept->rpdev = rpdev; + ept->cb = cb; + ept->priv = priv; + + /* do we need to allocate a local address ? */ + request = addr == RPMSG_ADDR_ANY ? RPMSG_RESERVED_ADDRESSES : addr; + + mutex_lock(&vrp->endpoints_lock); + + /* bind the endpoint to an rpmsg address (and allocate one if needed) */ + err = idr_get_new_above(&vrp->endpoints, ept, request, &tmpaddr); + if (err) { + dev_err(dev, "idr_get_new_above failed: %d\n", err); + goto free_ept; + } + + /* make sure the user's address request is fulfilled, if relevant */ + if (addr != RPMSG_ADDR_ANY && tmpaddr != addr) { + dev_err(dev, "address 0x%x already in use\n", addr); + goto rem_idr; + } + + ept->addr = tmpaddr; + + mutex_unlock(&vrp->endpoints_lock); + + return ept; + +rem_idr: + idr_remove(&vrp->endpoints, request); +free_ept: + mutex_unlock(&vrp->endpoints_lock); + kfree(ept); + return NULL; +} + +/** + * rpmsg_create_ept() - create a new rpmsg_endpoint + * @rpdev: rpmsg channel device + * @cb: rx callback handler + * @priv: private data for the driver's use + * @addr: local rpmsg address to bind with @cb + * + * Every rpmsg address in the system is bound to an rx callback (so when + * inbound messages arrive, they are dispatched by the rpmsg bus using the + * appropriate callback handler) by means of an rpmsg_endpoint struct. + * + * This function allows drivers to create such an endpoint, and by that, + * bind a callback, and possibly some private data too, to an rpmsg address + * (either one that is known in advance, or one that will be dynamically + * assigned for them). + * + * Simple rpmsg drivers need not call rpmsg_create_ept, because an endpoint + * is already created for them when they are probed by the rpmsg bus + * (using the rx callback provided when they registered to the rpmsg bus). + * + * So things should just work for simple drivers: they already have an + * endpoint, their rx callback is bound to their rpmsg address, and when + * relevant inbound messages arrive (i.e. messages which their dst address + * equals to the src address of their rpmsg channel), the driver's handler + * is invoked to process it. + * + * That said, more complicated drivers might do need to allocate + * additional rpmsg addresses, and bind them to different rx callbacks. + * To accomplish that, those drivers need to call this function. + * + * Drivers should provide their @rpdev channel (so the new endpoint would belong + * to the same remote processor their channel belongs to), an rx callback + * function, an optional private data (which is provided back when the + * rx callback is invoked), and an address they want to bind with the + * callback. If @addr is RPMSG_ADDR_ANY, then rpmsg_create_ept will + * dynamically assign them an available rpmsg address (drivers should have + * a very good reason why not to always use RPMSG_ADDR_ANY here). + * + * Returns a pointer to the endpoint on success, or NULL on error. + */ +struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_channel *rpdev, + rpmsg_rx_cb_t cb, void *priv, u32 addr) +{ + return __rpmsg_create_ept(rpdev->vrp, rpdev, cb, priv, addr); +} +EXPORT_SYMBOL(rpmsg_create_ept); + +/** + * __rpmsg_destroy_ept() - destroy an existing rpmsg endpoint + * @vrp: virtproc which owns this ept + * @ept: endpoing to destroy + * + * An internal function which destroy an ept without assuming it is + * bound to an rpmsg channel. This is needed for handling the internal + * name service endpoint, which isn't bound to an rpmsg channel. + * See also __rpmsg_create_ept(). + */ +static void +__rpmsg_destroy_ept(struct virtproc_info *vrp, struct rpmsg_endpoint *ept) +{ + mutex_lock(&vrp->endpoints_lock); + idr_remove(&vrp->endpoints, ept->addr); + mutex_unlock(&vrp->endpoints_lock); + + kfree(ept); +} + +/** + * rpmsg_destroy_ept() - destroy an existing rpmsg endpoint + * @ept: endpoing to destroy + * + * Should be used by drivers to destroy an rpmsg endpoint previously + * created with rpmsg_create_ept(). + */ +void rpmsg_destroy_ept(struct rpmsg_endpoint *ept) +{ + __rpmsg_destroy_ept(ept->rpdev->vrp, ept); +} +EXPORT_SYMBOL(rpmsg_destroy_ept); + +/* + * when an rpmsg driver is probed with a channel, we seamlessly create + * it an endpoint, binding its rx callback to a unique local rpmsg + * address. + * + * if we need to, we also announce about this channel to the remote + * processor (needed in case the driver is exposing an rpmsg service). + */ +static int rpmsg_dev_probe(struct device *dev) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver); + struct virtproc_info *vrp = rpdev->vrp; + struct rpmsg_endpoint *ept; + int err; + + ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, rpdev->src); + if (!ept) { + dev_err(dev, "failed to create endpoint\n"); + err = -ENOMEM; + goto out; + } + + rpdev->ept = ept; + rpdev->src = ept->addr; + + err = rpdrv->probe(rpdev); + if (err) { + dev_err(dev, "%s: failed: %d\n", __func__, err); + rpmsg_destroy_ept(ept); + goto out; + } + + /* need to tell remote processor's name service about this channel ? */ + if (rpdev->announce && + virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) { + struct rpmsg_ns_msg nsm; + + strncpy(nsm.name, rpdev->id.name, RPMSG_NAME_SIZE); + nsm.addr = rpdev->src; + nsm.flags = RPMSG_NS_CREATE; + + err = rpmsg_sendto(rpdev, &nsm, sizeof(nsm), RPMSG_NS_ADDR); + if (err) + dev_err(dev, "failed to announce service %d\n", err); + } + +out: + return err; +} + +static int rpmsg_dev_remove(struct device *dev) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver); + struct virtproc_info *vrp = rpdev->vrp; + int err = 0; + + /* tell remote processor's name service we're removing this channel */ + if (rpdev->announce && + virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) { + struct rpmsg_ns_msg nsm; + + strncpy(nsm.name, rpdev->id.name, RPMSG_NAME_SIZE); + nsm.addr = rpdev->src; + nsm.flags = RPMSG_NS_DESTROY; + + err = rpmsg_sendto(rpdev, &nsm, sizeof(nsm), RPMSG_NS_ADDR); + if (err) + dev_err(dev, "failed to announce service %d\n", err); + } + + rpdrv->remove(rpdev); + + rpmsg_destroy_ept(rpdev->ept); + + return err; +} + +static struct bus_type rpmsg_bus = { + .name = "rpmsg", + .match = rpmsg_dev_match, + .dev_attrs = rpmsg_dev_attrs, + .uevent = rpmsg_uevent, + .probe = rpmsg_dev_probe, + .remove = rpmsg_dev_remove, +}; + +/** + * register_rpmsg_driver() - register an rpmsg driver with the rpmsg bus + * @rpdrv: pointer to a struct rpmsg_driver + * + * Returns 0 on success, and an appropriate error value on failure. + */ +int register_rpmsg_driver(struct rpmsg_driver *rpdrv) +{ + rpdrv->drv.bus = &rpmsg_bus; + return driver_register(&rpdrv->drv); +} +EXPORT_SYMBOL(register_rpmsg_driver); + +/** + * unregister_rpmsg_driver() - unregister an rpmsg driver from the rpmsg bus + * @rpdrv: pointer to a struct rpmsg_driver + * + * Returns 0 on success, and an appropriate error value on failure. + */ +void unregister_rpmsg_driver(struct rpmsg_driver *rpdrv) +{ + driver_unregister(&rpdrv->drv); +} +EXPORT_SYMBOL(unregister_rpmsg_driver); + +static void rpmsg_release_device(struct device *dev) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + kfree(rpdev); +} + +/* + * match an rpmsg channel with a channel info struct. + * this is used to make sure we're not creating rpmsg devices for channels + * that already exist. + */ +static int rpmsg_channel_match(struct device *dev, void *data) +{ + struct rpmsg_channel_info *chinfo = data; + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src) + return 0; + + if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst) + return 0; + + if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE)) + return 0; + + /* found a match ! */ + return 1; +} + +/* + * create an rpmsg channel using its name and address info. + * this function will be used to create both static and dynamic + * channels. + */ +static struct rpmsg_channel *rpmsg_create_channel(struct virtproc_info *vrp, + struct rpmsg_channel_info *chinfo) +{ + struct rpmsg_channel *rpdev; + struct device *tmp, *dev = &vrp->vdev->dev; + int ret; + + /* make sure a similar channel doesn't already exist */ + tmp = device_find_child(dev, chinfo, rpmsg_channel_match); + if (tmp) { + /* decrement the matched device's refcount back */ + put_device(tmp); + dev_err(dev, "channel %s:%x:%x already exist\n", + chinfo->name, chinfo->src, chinfo->dst); + return NULL; + } + + rpdev = kzalloc(sizeof(struct rpmsg_channel), GFP_KERNEL); + if (!rpdev) { + pr_err("kzalloc failed\n"); + return NULL; + } + + rpdev->vrp = vrp; + rpdev->src = chinfo->src; + rpdev->dst = chinfo->dst; + + /* + * rpmsg server channels has predefined local address (for now), + * and their existence needs to be announced remotely + */ + rpdev->announce = rpdev->src != RPMSG_ADDR_ANY ? true : false; + + strncpy(rpdev->id.name, chinfo->name, RPMSG_NAME_SIZE); + + /* very simple device indexing plumbing which is enough for now */ + dev_set_name(&rpdev->dev, "rpmsg%d", rpmsg_dev_index++); + + rpdev->dev.parent = &vrp->vdev->dev; + rpdev->dev.bus = &rpmsg_bus; + rpdev->dev.release = rpmsg_release_device; + + ret = device_register(&rpdev->dev); + if (ret) { + dev_err(dev, "device_register failed: %d\n", ret); + put_device(&rpdev->dev); + return NULL; + } + + return rpdev; +} + +/* + * find an existing channel using its name + address properties, + * and destroy it + */ +static int rpmsg_destroy_channel(struct virtproc_info *vrp, + struct rpmsg_channel_info *chinfo) +{ + struct virtio_device *vdev = vrp->vdev; + struct device *dev; + + dev = device_find_child(&vdev->dev, chinfo, rpmsg_channel_match); + if (!dev) + return -EINVAL; + + device_unregister(dev); + + put_device(dev); + + return 0; +} + +/* super simple buffer "allocator" that is just enough for now */ +static void *get_a_tx_buf(struct virtproc_info *vrp) +{ + unsigned int len; + void *ret; + + /* support multiple concurrent senders */ + mutex_lock(&vrp->tx_lock); + + /* + * either pick the next unused tx buffer + * (half of our buffers are used for sending messages) + */ + if (vrp->last_sbuf < RPMSG_NUM_BUFS / 2) + ret = vrp->sbufs + RPMSG_BUF_SIZE * vrp->last_sbuf++; + /* or recycle a used one */ + else + ret = virtqueue_get_buf(vrp->svq, &len); + + mutex_unlock(&vrp->tx_lock); + + return ret; +} + +/** + * rpmsg_upref_sleepers() - enable "tx-complete" interrupts, if needed + * @vrp: virtual remote processor state + * + * This function is called before a sender is blocked, waiting for + * a tx buffer to become available. + * + * If we already have blocking senders, this function merely increases + * the "sleepers" reference count, and exits. + * + * Otherwise, if this is the first sender to block, we also enable + * virtio's tx callbacks, so we'd be immediately notified when a tx + * buffer is consumed (we rely on virtio's tx callback in order + * to wake up sleeping senders as soon as a tx buffer is used by the + * remote processor). + */ +static void rpmsg_upref_sleepers(struct virtproc_info *vrp) +{ + /* support multiple concurrent senders */ + mutex_lock(&vrp->tx_lock); + + /* are we the first sleeping context waiting for tx buffers ? */ + if (atomic_inc_return(&vrp->sleepers) == 1) + /* enable "tx-complete" interrupts before dozing off */ + virtqueue_enable_cb(vrp->svq); + + mutex_unlock(&vrp->tx_lock); +} + +/** + * rpmsg_downref_sleepers() - disable "tx-complete" interrupts, if needed + * @vrp: virtual remote processor state + * + * This function is called after a sender, that waited for a tx buffer + * to become available, is unblocked. + * + * If we still have blocking senders, this function merely decreases + * the "sleepers" reference count, and exits. + * + * Otherwise, if there are no more blocking senders, we also disable + * virtio's tx callbacks, to avoid the overhead incurred with handling + * those (now redundant) interrupts. + */ +static void rpmsg_downref_sleepers(struct virtproc_info *vrp) +{ + /* support multiple concurrent senders */ + mutex_lock(&vrp->tx_lock); + + /* are we the last sleeping context waiting for tx buffers ? */ + if (atomic_dec_and_test(&vrp->sleepers)) + /* disable "tx-complete" interrupts */ + virtqueue_disable_cb(vrp->svq); + + mutex_unlock(&vrp->tx_lock); +} + +/** + * rpmsg_send_offchannel_raw() - send a message across to the remote processor + * @rpdev: the rpmsg channel + * @src: source address + * @dst: destination address + * @data: payload of message + * @len: length of payload + * @wait: indicates whether caller should block in case no TX buffers available + * + * This function is the base implementation for all of the rpmsg sending API. + * + * It will send @data of length @len to @dst, and say it's from @src. The + * message will be sent to the remote processor which the @rpdev channel + * belongs to. + * + * The message is sent using one of the TX buffers that are available for + * communication with this remote processor. + * + * If @wait is true, the caller will be blocked until either a TX buffer is + * available, or 15 seconds elapses (we don't want callers to + * sleep indefinitely due to misbehaving remote processors), and in that + * case -ERESTARTSYS is returned. The number '15' itself was picked + * arbitrarily; there's little point in asking drivers to provide a timeout + * value themselves. + * + * Otherwise, if @wait is false, and there are no TX buffers available, + * the function will immediately fail, and -ENOMEM will be returned. + * + * Normally drivers shouldn't use this function directly; instead, drivers + * should use the appropriate rpmsg_{try}send{to, _offchannel} API + * (see include/linux/rpmsg.h). + * + * Returns 0 on success and an appropriate error value on failure. + */ +int rpmsg_send_offchannel_raw(struct rpmsg_channel *rpdev, u32 src, u32 dst, + void *data, int len, bool wait) +{ + struct virtproc_info *vrp = rpdev->vrp; + struct device *dev = &rpdev->dev; + struct scatterlist sg; + struct rpmsg_hdr *msg; + int err; + + /* bcasting isn't allowed */ + if (src == RPMSG_ADDR_ANY || dst == RPMSG_ADDR_ANY) { + dev_err(dev, "invalid addr (src 0x%x, dst 0x%x)\n", src, dst); + return -EINVAL; + } + + /* + * We currently use fixed-sized buffers, and therefore the payload + * length is limited. + * + * One of the possible improvements here is either to support + * user-provided buffers (and then we can also support zero-copy + * messaging), or to improve the buffer allocator, to support + * variable-length buffer sizes. + */ + if (len > RPMSG_BUF_SIZE - sizeof(struct rpmsg_hdr)) { + dev_err(dev, "message is too big (%d)\n", len); + return -EMSGSIZE; + } + + /* grab a buffer */ + msg = get_a_tx_buf(vrp); + if (!msg && !wait) + return -ENOMEM; + + /* no free buffer ? wait for one (but bail after 15 seconds) */ + while (!msg) { + /* enable "tx-complete" interrupts, if not already enabled */ + rpmsg_upref_sleepers(vrp); + + /* + * sleep until a free buffer is available or 15 secs elapse. + * the timeout period is not configurable because there's + * little point in asking drivers to specify that. + * if later this happens to be required, it'd be easy to add. + */ + err = wait_event_interruptible_timeout(vrp->sendq, + (msg = get_a_tx_buf(vrp)), + msecs_to_jiffies(15000)); + + /* disable "tx-complete" interrupts if we're the last sleeper */ + rpmsg_downref_sleepers(vrp); + + /* timeout ? */ + if (!err) { + dev_err(dev, "timeout waiting for a tx buffer\n"); + return -ERESTARTSYS; + } + } + + msg->len = len; + msg->flags = 0; + msg->src = src; + msg->dst = dst; + msg->reserved = 0; + memcpy(msg->data, data, len); + + dev_dbg(dev, "TX From 0x%x, To 0x%x, Len %d, Flags %d, Reserved %d\n", + msg->src, msg->dst, msg->len, + msg->flags, msg->reserved); + print_hex_dump(KERN_DEBUG, "rpmsg_virtio TX: ", DUMP_PREFIX_NONE, 16, 1, + msg, sizeof(*msg) + msg->len, true); + + sg_init_one(&sg, msg, sizeof(*msg) + len); + + mutex_lock(&vrp->tx_lock); + + /* add message to the remote processor's virtqueue */ + err = virtqueue_add_buf(vrp->svq, &sg, 1, 0, msg, GFP_KERNEL); + if (err < 0) { + /* + * need to reclaim the buffer here, otherwise it's lost + * (memory won't leak, but rpmsg won't use it again for TX). + * this will wait for a buffer management overhaul. + */ + dev_err(dev, "virtqueue_add_buf failed: %d\n", err); + goto out; + } + + /* tell the remote processor it has a pending message to read */ + virtqueue_kick(vrp->svq); + + err = 0; +out: + mutex_unlock(&vrp->tx_lock); + return err; +} +EXPORT_SYMBOL(rpmsg_send_offchannel_raw); + +/* called when an rx buffer is used, and it's time to digest a message */ +static void rpmsg_recv_done(struct virtqueue *rvq) +{ + struct rpmsg_hdr *msg; + unsigned int len; + struct rpmsg_endpoint *ept; + struct scatterlist sg; + struct virtproc_info *vrp = rvq->vdev->priv; + struct device *dev = &rvq->vdev->dev; + int err; + + msg = virtqueue_get_buf(rvq, &len); + if (!msg) { + dev_err(dev, "uhm, incoming signal, but no used buffer ?\n"); + return; + } + + dev_dbg(dev, "From: 0x%x, To: 0x%x, Len: %d, Flags: %d, Reserved: %d\n", + msg->src, msg->dst, msg->len, + msg->flags, msg->reserved); + print_hex_dump(KERN_DEBUG, "rpmsg_virtio RX: ", DUMP_PREFIX_NONE, 16, 1, + msg, sizeof(*msg) + msg->len, true); + + /* + * We currently use fixed-sized buffers, so trivially sanitize + * the reported payload length. + */ + if (len > RPMSG_BUF_SIZE || + msg->len > (len - sizeof(struct rpmsg_hdr))) { + dev_warn(dev, "inbound msg too big: (%d, %d)\n", len, msg->len); + return; + } + + /* use the dst addr to fetch the callback of the appropriate user */ + mutex_lock(&vrp->endpoints_lock); + ept = idr_find(&vrp->endpoints, msg->dst); + mutex_unlock(&vrp->endpoints_lock); + + if (ept && ept->cb) + ept->cb(ept->rpdev, msg->data, msg->len, ept->priv, msg->src); + else + dev_warn(dev, "msg received with no recepient\n"); + + /* publish the real size of the buffer */ + sg_init_one(&sg, msg, RPMSG_BUF_SIZE); + + /* add the buffer back to the remote processor's virtqueue */ + err = virtqueue_add_buf(vrp->rvq, &sg, 0, 1, msg, GFP_KERNEL); + if (err < 0) { + dev_err(dev, "failed to add a virtqueue buffer: %d\n", err); + return; + } + + /* tell the remote processor we added another available rx buffer */ + virtqueue_kick(vrp->rvq); +} + +/* + * This is invoked whenever the remote processor completed processing + * a TX msg we just sent it, and the buffer is put back to the used ring. + * + * Normally, though, we suppress this "tx complete" interrupt in order to + * avoid the incurred overhead. + */ +static void rpmsg_xmit_done(struct virtqueue *svq) +{ + struct virtproc_info *vrp = svq->vdev->priv; + + dev_dbg(&svq->vdev->dev, "%s\n", __func__); + + /* wake up potential senders that are waiting for a tx buffer */ + wake_up_interruptible(&vrp->sendq); +} + +/* invoked when a name service announcement arrives */ +static void rpmsg_ns_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct rpmsg_ns_msg *msg = data; + struct rpmsg_channel *newch; + struct rpmsg_channel_info chinfo; + struct virtproc_info *vrp = priv; + struct device *dev = &vrp->vdev->dev; + int ret; + + print_hex_dump(KERN_DEBUG, "NS announcement: ", + DUMP_PREFIX_NONE, 16, 1, + data, len, true); + + if (len != sizeof(*msg)) { + dev_err(dev, "malformed ns msg (%d)\n", len); + return; + } + + /* + * the name service ept does _not_ belong to a real rpmsg channel, + * and is handled by the rpmsg bus itself. + * for sanity reasons, make sure a valid rpdev has _not_ sneaked + * in somehow. + */ + if (rpdev) { + dev_err(dev, "anomaly: ns ept has an rpdev handle\n"); + return; + } + + /* don't trust the remote processor for null terminating the name */ + msg->name[RPMSG_NAME_SIZE - 1] = '\0'; + + dev_info(dev, "%sing channel %s addr 0x%x\n", + msg->flags & RPMSG_NS_DESTROY ? "destroy" : "creat", + msg->name, msg->addr); + + strncpy(chinfo.name, msg->name, sizeof(chinfo.name)); + chinfo.src = RPMSG_ADDR_ANY; + chinfo.dst = msg->addr; + + if (msg->flags & RPMSG_NS_DESTROY) { + ret = rpmsg_destroy_channel(vrp, &chinfo); + if (ret) + dev_err(dev, "rpmsg_destroy_channel failed: %d\n", ret); + } else { + newch = rpmsg_create_channel(vrp, &chinfo); + if (!newch) + dev_err(dev, "rpmsg_create_channel failed\n"); + } +} + +static int rpmsg_probe(struct virtio_device *vdev) +{ + vq_callback_t *vq_cbs[] = { rpmsg_recv_done, rpmsg_xmit_done }; + const char *names[] = { "input", "output" }; + struct virtqueue *vqs[2]; + struct virtproc_info *vrp; + void *bufs_va; + int err = 0, i; + + vrp = kzalloc(sizeof(*vrp), GFP_KERNEL); + if (!vrp) + return -ENOMEM; + + vrp->vdev = vdev; + + idr_init(&vrp->endpoints); + mutex_init(&vrp->endpoints_lock); + mutex_init(&vrp->tx_lock); + init_waitqueue_head(&vrp->sendq); + + /* We expect two virtqueues, rx and tx (and in this order) */ + err = vdev->config->find_vqs(vdev, 2, vqs, vq_cbs, names); + if (err) + goto free_vrp; + + vrp->rvq = vqs[0]; + vrp->svq = vqs[1]; + + /* allocate coherent memory for the buffers */ + bufs_va = dma_alloc_coherent(vdev->dev.parent, RPMSG_TOTAL_BUF_SPACE, + &vrp->bufs_dma, GFP_KERNEL); + if (!bufs_va) + goto vqs_del; + + dev_dbg(&vdev->dev, "buffers: va %p, dma 0x%llx\n", bufs_va, + (unsigned long long)vrp->bufs_dma); + + /* half of the buffers is dedicated for RX */ + vrp->rbufs = bufs_va; + + /* and half is dedicated for TX */ + vrp->sbufs = bufs_va + RPMSG_TOTAL_BUF_SPACE / 2; + + /* set up the receive buffers */ + for (i = 0; i < RPMSG_NUM_BUFS / 2; i++) { + struct scatterlist sg; + void *cpu_addr = vrp->rbufs + i * RPMSG_BUF_SIZE; + + sg_init_one(&sg, cpu_addr, RPMSG_BUF_SIZE); + + err = virtqueue_add_buf(vrp->rvq, &sg, 0, 1, cpu_addr, + GFP_KERNEL); + WARN_ON(err < 0); /* sanity check; this can't really happen */ + } + + /* suppress "tx-complete" interrupts */ + virtqueue_disable_cb(vrp->svq); + + vdev->priv = vrp; + + /* if supported by the remote processor, enable the name service */ + if (virtio_has_feature(vdev, VIRTIO_RPMSG_F_NS)) { + /* a dedicated endpoint handles the name service msgs */ + vrp->ns_ept = __rpmsg_create_ept(vrp, NULL, rpmsg_ns_cb, + vrp, RPMSG_NS_ADDR); + if (!vrp->ns_ept) { + dev_err(&vdev->dev, "failed to create the ns ept\n"); + err = -ENOMEM; + goto free_coherent; + } + } + + /* tell the remote processor it can start sending messages */ + virtqueue_kick(vrp->rvq); + + dev_info(&vdev->dev, "rpmsg host is online\n"); + + return 0; + +free_coherent: + dma_free_coherent(vdev->dev.parent, RPMSG_TOTAL_BUF_SPACE, bufs_va, + vrp->bufs_dma); +vqs_del: + vdev->config->del_vqs(vrp->vdev); +free_vrp: + kfree(vrp); + return err; +} + +static int rpmsg_remove_device(struct device *dev, void *data) +{ + device_unregister(dev); + + return 0; +} + +static void __devexit rpmsg_remove(struct virtio_device *vdev) +{ + struct virtproc_info *vrp = vdev->priv; + int ret; + + vdev->config->reset(vdev); + + ret = device_for_each_child(&vdev->dev, NULL, rpmsg_remove_device); + if (ret) + dev_warn(&vdev->dev, "can't remove rpmsg device: %d\n", ret); + + if (vrp->ns_ept) + __rpmsg_destroy_ept(vrp, vrp->ns_ept); + + idr_remove_all(&vrp->endpoints); + idr_destroy(&vrp->endpoints); + + vdev->config->del_vqs(vrp->vdev); + + dma_free_coherent(vdev->dev.parent, RPMSG_TOTAL_BUF_SPACE, + vrp->rbufs, vrp->bufs_dma); + + kfree(vrp); +} + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static unsigned int features[] = { + VIRTIO_RPMSG_F_NS, +}; + +static struct virtio_driver virtio_ipc_driver = { + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = rpmsg_probe, + .remove = __devexit_p(rpmsg_remove), +}; + +static int __init rpmsg_init(void) +{ + int ret; + + ret = bus_register(&rpmsg_bus); + if (ret) { + pr_err("failed to register rpmsg bus: %d\n", ret); + return ret; + } + + ret = register_virtio_driver(&virtio_ipc_driver); + if (ret) { + pr_err("failed to register virtio driver: %d\n", ret); + bus_unregister(&rpmsg_bus); + } + + return ret; +} +module_init(rpmsg_init); + +static void __exit rpmsg_fini(void) +{ + unregister_virtio_driver(&virtio_ipc_driver); + bus_unregister(&rpmsg_bus); +} +module_exit(rpmsg_fini); + +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("Virtio-based remote processor messaging bus"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 3a125b835546..8c8377d50c4c 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -554,6 +554,13 @@ config RTC_DRV_DS1742 This driver can also be built as a module. If so, the module will be called rtc-ds1742. +config RTC_DRV_DA9052 + tristate "Dialog DA9052/DA9053 RTC" + depends on PMIC_DA9052 + help + Say y here to support the RTC driver for Dialog Semiconductor + DA9052-BC and DA9053-AA/Bx PMICs. + config RTC_DRV_EFI tristate "EFI RTC" depends on IA64 @@ -748,7 +755,7 @@ config HAVE_S3C_RTC config RTC_DRV_S3C tristate "Samsung S3C series SoC RTC" - depends on ARCH_S3C2410 || ARCH_S3C64XX || HAVE_S3C_RTC + depends on ARCH_S3C64XX || HAVE_S3C_RTC help RTC (Realtime Clock) driver for the clock inbuilt into the Samsung S3C24XX series of SoCs. This can provide periodic @@ -773,8 +780,8 @@ config RTC_DRV_EP93XX will be called rtc-ep93xx. config RTC_DRV_SA1100 - tristate "SA11x0/PXA2xx" - depends on ARCH_SA1100 || ARCH_PXA + tristate "SA11x0/PXA2xx/PXA910" + depends on ARCH_SA1100 || ARCH_PXA || ARCH_MMP help If you say Y here you will get access to the real time clock built into your SA11x0 or PXA2xx CPU. @@ -1070,4 +1077,14 @@ config RTC_DRV_PUV3 This drive can also be built as a module. If so, the module will be called rtc-puv3. +config RTC_DRV_LOONGSON1 + tristate "loongson1 RTC support" + depends on MACH_LOONGSON1 + help + This is a driver for the loongson1 on-chip Counter0 (Time-Of-Year + counter) to be used as a RTC. + + This driver can also be built as a module. If so, the module + will be called rtc-ls1x. + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6e6982335c10..727ae7786e6c 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_RTC_DRV_BQ32K) += rtc-bq32k.o obj-$(CONFIG_RTC_DRV_BQ4802) += rtc-bq4802.o obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o +obj-$(CONFIG_RTC_DRV_DA9052) += rtc-da9052.o obj-$(CONFIG_RTC_DRV_DAVINCI) += rtc-davinci.o obj-$(CONFIG_RTC_DRV_DM355EVM) += rtc-dm355evm.o obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o @@ -53,6 +54,7 @@ obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o 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_LOONGSON1) += rtc-ls1x.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 diff --git a/drivers/rtc/rtc-at91sam9.c b/drivers/rtc/rtc-at91sam9.c index ee3c122c0599..831868904e02 100644 --- a/drivers/rtc/rtc-at91sam9.c +++ b/drivers/rtc/rtc-at91sam9.c @@ -57,6 +57,7 @@ struct sam9_rtc { void __iomem *rtt; struct rtc_device *rtcdev; u32 imr; + void __iomem *gpbr; }; #define rtt_readl(rtc, field) \ @@ -65,9 +66,9 @@ struct sam9_rtc { __raw_writel((val), (rtc)->rtt + AT91_RTT_ ## field) #define gpbr_readl(rtc) \ - at91_sys_read(AT91_GPBR + 4 * CONFIG_RTC_DRV_AT91SAM9_GPBR) + __raw_readl((rtc)->gpbr) #define gpbr_writel(rtc, val) \ - at91_sys_write(AT91_GPBR + 4 * CONFIG_RTC_DRV_AT91SAM9_GPBR, (val)) + __raw_writel((val), (rtc)->gpbr) /* * Read current time and date in RTC @@ -287,16 +288,19 @@ static const struct rtc_class_ops at91_rtc_ops = { /* * Initialize and install RTC driver */ -static int __init at91_rtc_probe(struct platform_device *pdev) +static int __devinit at91_rtc_probe(struct platform_device *pdev) { - struct resource *r; + struct resource *r, *r_gpbr; struct sam9_rtc *rtc; int ret; u32 mr; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!r) + r_gpbr = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!r || !r_gpbr) { + dev_err(&pdev->dev, "need 2 ressources\n"); return -ENODEV; + } rtc = kzalloc(sizeof *rtc, GFP_KERNEL); if (!rtc) @@ -314,6 +318,13 @@ static int __init at91_rtc_probe(struct platform_device *pdev) goto fail; } + rtc->gpbr = ioremap(r_gpbr->start, resource_size(r_gpbr)); + if (!rtc->gpbr) { + dev_err(&pdev->dev, "failed to map gpbr registers, aborting.\n"); + ret = -ENOMEM; + goto fail_gpbr; + } + mr = rtt_readl(rtc, MR); /* unless RTT is counting at 1 Hz, re-initialize it */ @@ -335,12 +346,12 @@ static int __init at91_rtc_probe(struct platform_device *pdev) /* register irq handler after we know what name we'll use */ ret = request_irq(AT91_ID_SYS, at91_rtc_interrupt, - IRQF_DISABLED | IRQF_SHARED, + IRQF_SHARED, dev_name(&rtc->rtcdev->dev), rtc); if (ret) { dev_dbg(&pdev->dev, "can't share IRQ %d?\n", AT91_ID_SYS); rtc_device_unregister(rtc->rtcdev); - goto fail; + goto fail_register; } /* NOTE: sam9260 rev A silicon has a ROM bug which resets the @@ -356,6 +367,8 @@ static int __init at91_rtc_probe(struct platform_device *pdev) return 0; fail_register: + iounmap(rtc->gpbr); +fail_gpbr: iounmap(rtc->rtt); fail: platform_set_drvdata(pdev, NULL); @@ -366,7 +379,7 @@ fail: /* * Disable and remove the RTC driver */ -static int __exit at91_rtc_remove(struct platform_device *pdev) +static int __devexit at91_rtc_remove(struct platform_device *pdev) { struct sam9_rtc *rtc = platform_get_drvdata(pdev); u32 mr = rtt_readl(rtc, MR); @@ -377,6 +390,7 @@ static int __exit at91_rtc_remove(struct platform_device *pdev) rtc_device_unregister(rtc->rtcdev); + iounmap(rtc->gpbr); iounmap(rtc->rtt); platform_set_drvdata(pdev, NULL); kfree(rtc); @@ -440,63 +454,20 @@ static int at91_rtc_resume(struct platform_device *pdev) #endif static struct platform_driver at91_rtc_driver = { - .driver.name = "rtc-at91sam9", - .driver.owner = THIS_MODULE, - .remove = __exit_p(at91_rtc_remove), + .probe = at91_rtc_probe, + .remove = __devexit_p(at91_rtc_remove), .shutdown = at91_rtc_shutdown, .suspend = at91_rtc_suspend, .resume = at91_rtc_resume, + .driver = { + .name = "rtc-at91sam9", + .owner = THIS_MODULE, + }, }; -/* Chips can have more than one RTT module, and they can be used for more - * than just RTCs. So we can't just register as "the" RTT driver. - * - * A normal approach in such cases is to create a library to allocate and - * free the modules. Here we just use bus_find_device() as like such a - * library, binding directly ... no runtime "library" footprint is needed. - */ -static int __init at91_rtc_match(struct device *dev, void *v) -{ - struct platform_device *pdev = to_platform_device(dev); - int ret; - - /* continue searching if this isn't the RTT we need */ - if (strcmp("at91_rtt", pdev->name) != 0 - || pdev->id != CONFIG_RTC_DRV_AT91SAM9_RTT) - goto fail; - - /* else we found it ... but fail unless we can bind to the RTC driver */ - if (dev->driver) { - dev_dbg(dev, "busy, can't use as RTC!\n"); - goto fail; - } - dev->driver = &at91_rtc_driver.driver; - if (device_attach(dev) == 0) { - dev_dbg(dev, "can't attach RTC!\n"); - goto fail; - } - ret = at91_rtc_probe(pdev); - if (ret == 0) - return true; - - dev_dbg(dev, "RTC probe err %d!\n", ret); -fail: - return false; -} - static int __init at91_rtc_init(void) { - int status; - struct device *rtc; - - status = platform_driver_register(&at91_rtc_driver); - if (status) - return status; - rtc = bus_find_device(&platform_bus_type, NULL, - NULL, at91_rtc_match); - if (!rtc) - platform_driver_unregister(&at91_rtc_driver); - return rtc ? 0 : -ENODEV; + return platform_driver_register(&at91_rtc_driver); } module_init(at91_rtc_init); diff --git a/drivers/rtc/rtc-bq32k.c b/drivers/rtc/rtc-bq32k.c index 408cc8f735be..f090159dce4a 100644 --- a/drivers/rtc/rtc-bq32k.c +++ b/drivers/rtc/rtc-bq32k.c @@ -187,17 +187,7 @@ static struct i2c_driver bq32k_driver = { .id_table = bq32k_id, }; -static __init int bq32k_init(void) -{ - return i2c_add_driver(&bq32k_driver); -} -module_init(bq32k_init); - -static __exit void bq32k_exit(void) -{ - i2c_del_driver(&bq32k_driver); -} -module_exit(bq32k_exit); +module_i2c_driver(bq32k_driver); MODULE_AUTHOR("Semihalf, Piotr Ziecik <kosmo@semihalf.com>"); MODULE_DESCRIPTION("TI BQ32000 I2C RTC driver"); diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index d7782aa09943..7d5f56edb8ef 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -714,7 +714,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) rtc_cmos_int_handler = cmos_interrupt; retval = request_irq(rtc_irq, rtc_cmos_int_handler, - IRQF_DISABLED, dev_name(&cmos_rtc.rtc->dev), + 0, dev_name(&cmos_rtc.rtc->dev), cmos_rtc.rtc); if (retval < 0) { dev_dbg(dev, "IRQ %d is already in use\n", rtc_irq); diff --git a/drivers/rtc/rtc-coh901331.c b/drivers/rtc/rtc-coh901331.c index 80f9c88214c5..a5b8a0c4ea84 100644 --- a/drivers/rtc/rtc-coh901331.c +++ b/drivers/rtc/rtc-coh901331.c @@ -199,7 +199,7 @@ static int __init coh901331_probe(struct platform_device *pdev) } rtap->irq = platform_get_irq(pdev, 0); - if (request_irq(rtap->irq, coh901331_interrupt, IRQF_DISABLED, + if (request_irq(rtap->irq, coh901331_interrupt, 0, "RTC COH 901 331 Alarm", rtap)) { ret = -EIO; goto out_no_irq; diff --git a/drivers/rtc/rtc-da9052.c b/drivers/rtc/rtc-da9052.c new file mode 100644 index 000000000000..da6ab5291a41 --- /dev/null +++ b/drivers/rtc/rtc-da9052.c @@ -0,0 +1,293 @@ +/* + * Real time clock driver for DA9052 + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: Dajun Dajun Chen <dajun.chen@diasemi.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/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + +#define rtc_err(da9052, fmt, ...) \ + dev_err(da9052->dev, "%s: " fmt, __func__, ##__VA_ARGS__) + +struct da9052_rtc { + struct rtc_device *rtc; + struct da9052 *da9052; + int irq; +}; + +static int da9052_rtc_enable_alarm(struct da9052 *da9052, bool enable) +{ + int ret; + if (enable) { + ret = da9052_reg_update(da9052, DA9052_ALARM_Y_REG, + DA9052_ALARM_Y_ALARM_ON, + DA9052_ALARM_Y_ALARM_ON); + if (ret != 0) + rtc_err(da9052, "Failed to enable ALM: %d\n", ret); + } else { + ret = da9052_reg_update(da9052, DA9052_ALARM_Y_REG, + DA9052_ALARM_Y_ALARM_ON, 0); + if (ret != 0) + rtc_err(da9052, "Write error: %d\n", ret); + } + return ret; +} + +static irqreturn_t da9052_rtc_irq(int irq, void *data) +{ + struct da9052_rtc *rtc = data; + int ret; + + ret = da9052_reg_read(rtc->da9052, DA9052_ALARM_MI_REG); + if (ret < 0) { + rtc_err(rtc->da9052, "Read error: %d\n", ret); + return IRQ_NONE; + } + + if (ret & DA9052_ALARMMI_ALARMTYPE) { + da9052_rtc_enable_alarm(rtc->da9052, 0); + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + } else + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_PF); + + return IRQ_HANDLED; +} + +static int da9052_read_alarm(struct da9052 *da9052, struct rtc_time *rtc_tm) +{ + int ret; + uint8_t v[5]; + + ret = da9052_group_read(da9052, DA9052_ALARM_MI_REG, 5, v); + if (ret != 0) { + rtc_err(da9052, "Failed to group read ALM: %d\n", ret); + return ret; + } + + rtc_tm->tm_year = (v[4] & DA9052_RTC_YEAR) + 100; + rtc_tm->tm_mon = (v[3] & DA9052_RTC_MONTH) - 1; + rtc_tm->tm_mday = v[2] & DA9052_RTC_DAY; + rtc_tm->tm_hour = v[1] & DA9052_RTC_HOUR; + rtc_tm->tm_min = v[0] & DA9052_RTC_MIN; + + ret = rtc_valid_tm(rtc_tm); + if (ret != 0) + return ret; + return ret; +} + +static int da9052_set_alarm(struct da9052 *da9052, struct rtc_time *rtc_tm) +{ + int ret; + uint8_t v[3]; + + rtc_tm->tm_year -= 100; + rtc_tm->tm_mon += 1; + + ret = da9052_reg_update(da9052, DA9052_ALARM_MI_REG, + DA9052_RTC_MIN, rtc_tm->tm_min); + if (ret != 0) { + rtc_err(da9052, "Failed to write ALRM MIN: %d\n", ret); + return ret; + } + + v[0] = rtc_tm->tm_hour; + v[1] = rtc_tm->tm_mday; + v[2] = rtc_tm->tm_mon; + + ret = da9052_group_write(da9052, DA9052_ALARM_H_REG, 3, v); + if (ret < 0) + return ret; + + ret = da9052_reg_update(da9052, DA9052_ALARM_Y_REG, + DA9052_RTC_YEAR, rtc_tm->tm_year); + if (ret != 0) + rtc_err(da9052, "Failed to write ALRM YEAR: %d\n", ret); + + return ret; +} + +static int da9052_rtc_get_alarm_status(struct da9052 *da9052) +{ + int ret; + + ret = da9052_reg_read(da9052, DA9052_ALARM_Y_REG); + if (ret < 0) { + rtc_err(da9052, "Failed to read ALM: %d\n", ret); + return ret; + } + ret &= DA9052_ALARM_Y_ALARM_ON; + return (ret > 0) ? 1 : 0; +} + +static int da9052_rtc_read_time(struct device *dev, struct rtc_time *rtc_tm) +{ + struct da9052_rtc *rtc = dev_get_drvdata(dev); + uint8_t v[6]; + int ret; + + ret = da9052_group_read(rtc->da9052, DA9052_COUNT_S_REG, 6, v); + if (ret < 0) { + rtc_err(rtc->da9052, "Failed to read RTC time : %d\n", ret); + return ret; + } + + rtc_tm->tm_year = (v[5] & DA9052_RTC_YEAR) + 100; + rtc_tm->tm_mon = (v[4] & DA9052_RTC_MONTH) - 1; + rtc_tm->tm_mday = v[3] & DA9052_RTC_DAY; + rtc_tm->tm_hour = v[2] & DA9052_RTC_HOUR; + rtc_tm->tm_min = v[1] & DA9052_RTC_MIN; + rtc_tm->tm_sec = v[0] & DA9052_RTC_SEC; + + ret = rtc_valid_tm(rtc_tm); + if (ret != 0) { + rtc_err(rtc->da9052, "rtc_valid_tm failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int da9052_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct da9052_rtc *rtc; + uint8_t v[6]; + + rtc = dev_get_drvdata(dev); + + v[0] = tm->tm_sec; + v[1] = tm->tm_min; + v[2] = tm->tm_hour; + v[3] = tm->tm_mday; + v[4] = tm->tm_mon + 1; + v[5] = tm->tm_year - 100; + + return da9052_group_write(rtc->da9052, DA9052_COUNT_S_REG, 6, v); +} + +static int da9052_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + int ret; + struct rtc_time *tm = &alrm->time; + struct da9052_rtc *rtc = dev_get_drvdata(dev); + + ret = da9052_read_alarm(rtc->da9052, tm); + + if (ret) + return ret; + + alrm->enabled = da9052_rtc_get_alarm_status(rtc->da9052); + + return 0; +} + +static int da9052_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + int ret; + struct rtc_time *tm = &alrm->time; + struct da9052_rtc *rtc = dev_get_drvdata(dev); + + ret = da9052_rtc_enable_alarm(rtc->da9052, 0); + if (ret < 0) + return ret; + + ret = da9052_set_alarm(rtc->da9052, tm); + if (ret) + return ret; + + ret = da9052_rtc_enable_alarm(rtc->da9052, 1); + + return ret; +} + +static int da9052_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct da9052_rtc *rtc = dev_get_drvdata(dev); + + return da9052_rtc_enable_alarm(rtc->da9052, enabled); +} + +static const struct rtc_class_ops da9052_rtc_ops = { + .read_time = da9052_rtc_read_time, + .set_time = da9052_rtc_set_time, + .read_alarm = da9052_rtc_read_alarm, + .set_alarm = da9052_rtc_set_alarm, + .alarm_irq_enable = da9052_rtc_alarm_irq_enable, +}; + +static int __devinit da9052_rtc_probe(struct platform_device *pdev) +{ + struct da9052_rtc *rtc; + int ret; + + rtc = devm_kzalloc(&pdev->dev, sizeof(struct da9052_rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + rtc->da9052 = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, rtc); + rtc->irq = platform_get_irq_byname(pdev, "ALM"); + ret = request_threaded_irq(rtc->irq, NULL, da9052_rtc_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "ALM", rtc); + if (ret != 0) { + rtc_err(rtc->da9052, "irq registration failed: %d\n", ret); + goto err_mem; + } + + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, + &da9052_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc->rtc)) { + ret = PTR_ERR(rtc->rtc); + goto err_free_irq; + } + + return 0; + +err_free_irq: + free_irq(rtc->irq, rtc); +err_mem: + devm_kfree(&pdev->dev, rtc); + return ret; +} + +static int __devexit da9052_rtc_remove(struct platform_device *pdev) +{ + struct da9052_rtc *rtc = pdev->dev.platform_data; + + rtc_device_unregister(rtc->rtc); + free_irq(rtc->irq, rtc); + platform_set_drvdata(pdev, NULL); + devm_kfree(&pdev->dev, rtc); + + return 0; +} + +static struct platform_driver da9052_rtc_driver = { + .probe = da9052_rtc_probe, + .remove = __devexit_p(da9052_rtc_remove), + .driver = { + .name = "da9052-rtc", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(da9052_rtc_driver); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("RTC driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-rtc"); diff --git a/drivers/rtc/rtc-davinci.c b/drivers/rtc/rtc-davinci.c index 755e1fe914af..14c2109dbaa3 100644 --- a/drivers/rtc/rtc-davinci.c +++ b/drivers/rtc/rtc-davinci.c @@ -542,7 +542,7 @@ static int __init davinci_rtc_probe(struct platform_device *pdev) rtcss_write(davinci_rtc, 0, PRTCSS_RTC_CCTRL); ret = request_irq(davinci_rtc->irq, davinci_rtc_interrupt, - IRQF_DISABLED, "davinci_rtc", davinci_rtc); + 0, "davinci_rtc", davinci_rtc); if (ret < 0) { dev_err(dev, "unable to register davinci RTC interrupt\n"); goto fail4; diff --git a/drivers/rtc/rtc-ds1305.c b/drivers/rtc/rtc-ds1305.c index 3a33b1fdbe0f..686a865913e1 100644 --- a/drivers/rtc/rtc-ds1305.c +++ b/drivers/rtc/rtc-ds1305.c @@ -814,17 +814,7 @@ static struct spi_driver ds1305_driver = { /* REVISIT add suspend/resume */ }; -static int __init ds1305_init(void) -{ - return spi_register_driver(&ds1305_driver); -} -module_init(ds1305_init); - -static void __exit ds1305_exit(void) -{ - spi_unregister_driver(&ds1305_driver); -} -module_exit(ds1305_exit); +module_spi_driver(ds1305_driver); MODULE_DESCRIPTION("RTC driver for DS1305 and DS1306 chips"); MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index 62b0763b7b9a..cd188ab72f79 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -20,7 +20,8 @@ -/* We can't determine type by probing, but if we expect pre-Linux code +/* + * We can't determine type by probing, but if we expect pre-Linux code * to have set the chip up as a clock (turning on the oscillator and * setting the date and time), Linux can ignore the non-clock features. * That's a natural job for a factory or repair bench. @@ -36,7 +37,8 @@ enum ds_type { m41t00, mcp7941x, rx_8025, - // rs5c372 too? different address... + last_ds_type /* always last */ + /* rs5c372 too? different address... */ }; @@ -58,7 +60,8 @@ enum ds_type { # define DS1337_BIT_CENTURY 0x80 /* in REG_MONTH */ #define DS1307_REG_YEAR 0x06 /* 00-99 */ -/* Other registers (control, status, alarms, trickle charge, NVRAM, etc) +/* + * Other registers (control, status, alarms, trickle charge, NVRAM, etc) * start at 7, and they differ a LOT. Only control and status matter for * basic RTC date and time functionality; be careful using them. */ @@ -102,6 +105,8 @@ enum ds_type { struct ds1307 { u8 offset; /* register's offset */ u8 regs[11]; + u16 nvram_offset; + struct bin_attribute *nvram; enum ds_type type; unsigned long flags; #define HAS_NVRAM 0 /* bit 0 == sysfs file active */ @@ -116,34 +121,35 @@ struct ds1307 { }; struct chip_desc { - unsigned nvram56:1; unsigned alarm:1; + u16 nvram_offset; + u16 nvram_size; }; -static const struct chip_desc chips[] = { -[ds_1307] = { - .nvram56 = 1, -}, -[ds_1337] = { - .alarm = 1, -}, -[ds_1338] = { - .nvram56 = 1, -}, -[ds_1339] = { - .alarm = 1, -}, -[ds_1340] = { -}, -[ds_3231] = { - .alarm = 1, -}, -[m41t00] = { -}, -[mcp7941x] = { -}, -[rx_8025] = { -}, }; +static const struct chip_desc chips[last_ds_type] = { + [ds_1307] = { + .nvram_offset = 8, + .nvram_size = 56, + }, + [ds_1337] = { + .alarm = 1, + }, + [ds_1338] = { + .nvram_offset = 8, + .nvram_size = 56, + }, + [ds_1339] = { + .alarm = 1, + }, + [ds_3231] = { + .alarm = 1, + }, + [mcp7941x] = { + /* this is battery backed SRAM */ + .nvram_offset = 0x20, + .nvram_size = 0x40, + }, +}; static const struct i2c_device_id ds1307_id[] = { { "ds1307", ds_1307 }, @@ -372,6 +378,11 @@ static int ds1307_set_time(struct device *dev, struct rtc_time *t) | DS1340_BIT_CENTURY; break; case mcp7941x: + /* + * these bits were cleared when preparing the date/time + * values and need to be set again before writing the + * buffer out to the device. + */ buf[DS1307_REG_SECS] |= MCP7941X_BIT_ST; buf[DS1307_REG_WDAY] |= MCP7941X_BIT_VBATEN; break; @@ -417,7 +428,8 @@ static int ds1337_read_alarm(struct device *dev, struct rtc_wkalrm *t) ds1307->regs[6], ds1307->regs[7], ds1307->regs[8]); - /* report alarm time (ALARM1); assume 24 hour and day-of-month modes, + /* + * report alarm time (ALARM1); assume 24 hour and day-of-month modes, * and that all four fields are checked matches */ t->time.tm_sec = bcd2bin(ds1307->regs[0] & 0x7f); @@ -445,7 +457,7 @@ static int ds1337_read_alarm(struct device *dev, struct rtc_wkalrm *t) static int ds1337_set_alarm(struct device *dev, struct rtc_wkalrm *t) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = to_i2c_client(dev); struct ds1307 *ds1307 = i2c_get_clientdata(client); unsigned char *buf = ds1307->regs; u8 control, status; @@ -541,8 +553,6 @@ static const struct rtc_class_ops ds13xx_rtc_ops = { /*----------------------------------------------------------------------*/ -#define NVRAM_SIZE 56 - static ssize_t ds1307_nvram_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, @@ -555,14 +565,15 @@ ds1307_nvram_read(struct file *filp, struct kobject *kobj, client = kobj_to_i2c_client(kobj); ds1307 = i2c_get_clientdata(client); - if (unlikely(off >= NVRAM_SIZE)) + if (unlikely(off >= ds1307->nvram->size)) return 0; - if ((off + count) > NVRAM_SIZE) - count = NVRAM_SIZE - off; + if ((off + count) > ds1307->nvram->size) + count = ds1307->nvram->size - off; if (unlikely(!count)) return count; - result = ds1307->read_block_data(client, 8 + off, count, buf); + result = ds1307->read_block_data(client, ds1307->nvram_offset + off, + count, buf); if (result < 0) dev_err(&client->dev, "%s error %d\n", "nvram read", result); return result; @@ -580,14 +591,15 @@ ds1307_nvram_write(struct file *filp, struct kobject *kobj, client = kobj_to_i2c_client(kobj); ds1307 = i2c_get_clientdata(client); - if (unlikely(off >= NVRAM_SIZE)) + if (unlikely(off >= ds1307->nvram->size)) return -EFBIG; - if ((off + count) > NVRAM_SIZE) - count = NVRAM_SIZE - off; + if ((off + count) > ds1307->nvram->size) + count = ds1307->nvram->size - off; if (unlikely(!count)) return count; - result = ds1307->write_block_data(client, 8 + off, count, buf); + result = ds1307->write_block_data(client, ds1307->nvram_offset + off, + count, buf); if (result < 0) { dev_err(&client->dev, "%s error %d\n", "nvram write", result); return result; @@ -595,21 +607,8 @@ ds1307_nvram_write(struct file *filp, struct kobject *kobj, return count; } -static struct bin_attribute nvram = { - .attr = { - .name = "nvram", - .mode = S_IRUGO | S_IWUSR, - }, - - .read = ds1307_nvram_read, - .write = ds1307_nvram_write, - .size = NVRAM_SIZE, -}; - /*----------------------------------------------------------------------*/ -static struct i2c_driver ds1307_driver; - static int __devinit ds1307_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -630,7 +629,8 @@ static int __devinit ds1307_probe(struct i2c_client *client, && !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) return -EIO; - if (!(ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL))) + ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL); + if (!ds1307) return -ENOMEM; i2c_set_clientdata(client, ds1307); @@ -652,11 +652,6 @@ static int __devinit ds1307_probe(struct i2c_client *client, case ds_1337: case ds_1339: case ds_3231: - /* has IRQ? */ - if (ds1307->client->irq > 0 && chip->alarm) { - INIT_WORK(&ds1307->work, ds1307_work); - want_irq = true; - } /* get registers that the "rtc" read below won't read... */ tmp = ds1307->read_block_data(ds1307->client, DS1337_REG_CONTROL, 2, buf); @@ -670,14 +665,19 @@ static int __devinit ds1307_probe(struct i2c_client *client, if (ds1307->regs[0] & DS1337_BIT_nEOSC) ds1307->regs[0] &= ~DS1337_BIT_nEOSC; - /* Using IRQ? Disable the square wave and both alarms. + /* + * Using IRQ? Disable the square wave and both alarms. * For some variants, be sure alarms can trigger when we're * running on Vbackup (BBSQI/BBSQW) */ - if (want_irq) { + if (ds1307->client->irq > 0 && chip->alarm) { + INIT_WORK(&ds1307->work, ds1307_work); + ds1307->regs[0] |= DS1337_BIT_INTCN | bbsqi_bitpos[ds1307->type]; ds1307->regs[0] &= ~(DS1337_BIT_A2IE | DS1337_BIT_A1IE); + + want_irq = true; } i2c_smbus_write_byte_data(client, DS1337_REG_CONTROL, @@ -772,7 +772,8 @@ read_rtc: goto exit_free; } - /* minimal sanity checking; some chips (like DS1340) don't + /* + * minimal sanity checking; some chips (like DS1340) don't * specify the extra bits as must-be-zero, but there are * still a few values that are clearly out-of-range. */ @@ -836,11 +837,7 @@ read_rtc: } break; - case rx_8025: - case ds_1337: - case ds_1339: - case ds_1388: - case ds_3231: + default: break; } @@ -848,7 +845,8 @@ read_rtc: switch (ds1307->type) { case ds_1340: case m41t00: - /* NOTE: ignores century bits; fix before deploying + /* + * NOTE: ignores century bits; fix before deploying * systems that will run through year 2100. */ break; @@ -858,7 +856,8 @@ read_rtc: if (!(tmp & DS1307_BIT_12HR)) break; - /* Be sure we're in 24 hour mode. Multi-master systems + /* + * Be sure we're in 24 hour mode. Multi-master systems * take note... */ tmp = bcd2bin(tmp & 0x1f); @@ -894,16 +893,31 @@ read_rtc: dev_dbg(&client->dev, "got IRQ %d\n", client->irq); } - if (chip->nvram56) { - err = sysfs_create_bin_file(&client->dev.kobj, &nvram); - if (err == 0) { - set_bit(HAS_NVRAM, &ds1307->flags); - dev_info(&client->dev, "56 bytes nvram\n"); + if (chip->nvram_size) { + ds1307->nvram = kzalloc(sizeof(struct bin_attribute), + GFP_KERNEL); + if (!ds1307->nvram) { + err = -ENOMEM; + goto exit_nvram; + } + ds1307->nvram->attr.name = "nvram"; + ds1307->nvram->attr.mode = S_IRUGO | S_IWUSR; + ds1307->nvram->read = ds1307_nvram_read, + ds1307->nvram->write = ds1307_nvram_write, + ds1307->nvram->size = chip->nvram_size; + ds1307->nvram_offset = chip->nvram_offset; + err = sysfs_create_bin_file(&client->dev.kobj, ds1307->nvram); + if (err) { + kfree(ds1307->nvram); + goto exit_nvram; } + set_bit(HAS_NVRAM, &ds1307->flags); + dev_info(&client->dev, "%zu bytes nvram\n", ds1307->nvram->size); } return 0; +exit_nvram: exit_irq: rtc_device_unregister(ds1307->rtc); exit_free: @@ -913,15 +927,17 @@ exit_free: static int __devexit ds1307_remove(struct i2c_client *client) { - struct ds1307 *ds1307 = i2c_get_clientdata(client); + struct ds1307 *ds1307 = i2c_get_clientdata(client); if (test_and_clear_bit(HAS_ALARM, &ds1307->flags)) { free_irq(client->irq, client); cancel_work_sync(&ds1307->work); } - if (test_and_clear_bit(HAS_NVRAM, &ds1307->flags)) - sysfs_remove_bin_file(&client->dev.kobj, &nvram); + if (test_and_clear_bit(HAS_NVRAM, &ds1307->flags)) { + sysfs_remove_bin_file(&client->dev.kobj, ds1307->nvram); + kfree(ds1307->nvram); + } rtc_device_unregister(ds1307->rtc); kfree(ds1307); @@ -938,17 +954,7 @@ static struct i2c_driver ds1307_driver = { .id_table = ds1307_id, }; -static int __init ds1307_init(void) -{ - return i2c_add_driver(&ds1307_driver); -} -module_init(ds1307_init); - -static void __exit ds1307_exit(void) -{ - i2c_del_driver(&ds1307_driver); -} -module_exit(ds1307_exit); +module_i2c_driver(ds1307_driver); MODULE_DESCRIPTION("RTC driver for DS1307 and similar chips"); MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-ds1374.c b/drivers/rtc/rtc-ds1374.c index e6e71deb188f..966316088b7f 100644 --- a/drivers/rtc/rtc-ds1374.c +++ b/drivers/rtc/rtc-ds1374.c @@ -446,18 +446,7 @@ static struct i2c_driver ds1374_driver = { .id_table = ds1374_id, }; -static int __init ds1374_init(void) -{ - return i2c_add_driver(&ds1374_driver); -} - -static void __exit ds1374_exit(void) -{ - i2c_del_driver(&ds1374_driver); -} - -module_init(ds1374_init); -module_exit(ds1374_exit); +module_i2c_driver(ds1374_driver); MODULE_AUTHOR("Scott Wood <scottwood@freescale.com>"); MODULE_DESCRIPTION("Maxim/Dallas DS1374 RTC Driver"); diff --git a/drivers/rtc/rtc-ds1390.c b/drivers/rtc/rtc-ds1390.c index b038d2cfef26..b0a99e1b25be 100644 --- a/drivers/rtc/rtc-ds1390.c +++ b/drivers/rtc/rtc-ds1390.c @@ -175,17 +175,7 @@ static struct spi_driver ds1390_driver = { .remove = __devexit_p(ds1390_remove), }; -static __init int ds1390_init(void) -{ - return spi_register_driver(&ds1390_driver); -} -module_init(ds1390_init); - -static __exit void ds1390_exit(void) -{ - spi_unregister_driver(&ds1390_driver); -} -module_exit(ds1390_exit); +module_spi_driver(ds1390_driver); MODULE_DESCRIPTION("Dallas/Maxim DS1390/93/94 SPI RTC driver"); MODULE_AUTHOR("Mark Jackson <mpfj@mimc.co.uk>"); diff --git a/drivers/rtc/rtc-ds1511.c b/drivers/rtc/rtc-ds1511.c index 761f36bc83a9..1f675f5294f5 100644 --- a/drivers/rtc/rtc-ds1511.c +++ b/drivers/rtc/rtc-ds1511.c @@ -532,7 +532,7 @@ ds1511_rtc_probe(struct platform_device *pdev) if (pdata->irq > 0) { rtc_read(RTC_CMD1); if (devm_request_irq(&pdev->dev, pdata->irq, ds1511_interrupt, - IRQF_DISABLED | IRQF_SHARED, pdev->name, pdev) < 0) { + IRQF_SHARED, pdev->name, pdev) < 0) { dev_warn(&pdev->dev, "interrupt not available.\n"); pdata->irq = 0; diff --git a/drivers/rtc/rtc-ds1553.c b/drivers/rtc/rtc-ds1553.c index 6f0a1b530f2e..6ccedbbf923c 100644 --- a/drivers/rtc/rtc-ds1553.c +++ b/drivers/rtc/rtc-ds1553.c @@ -320,7 +320,7 @@ static int __devinit ds1553_rtc_probe(struct platform_device *pdev) writeb(0, ioaddr + RTC_INTERRUPTS); if (devm_request_irq(&pdev->dev, pdata->irq, ds1553_rtc_interrupt, - IRQF_DISABLED, pdev->name, pdev) < 0) { + 0, pdev->name, pdev) < 0) { dev_warn(&pdev->dev, "interrupt not available.\n"); pdata->irq = 0; } diff --git a/drivers/rtc/rtc-ds1672.c b/drivers/rtc/rtc-ds1672.c index a319402a5447..7fa67d0df172 100644 --- a/drivers/rtc/rtc-ds1672.c +++ b/drivers/rtc/rtc-ds1672.c @@ -202,20 +202,9 @@ static struct i2c_driver ds1672_driver = { .id_table = ds1672_id, }; -static int __init ds1672_init(void) -{ - return i2c_add_driver(&ds1672_driver); -} - -static void __exit ds1672_exit(void) -{ - i2c_del_driver(&ds1672_driver); -} +module_i2c_driver(ds1672_driver); MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); MODULE_DESCRIPTION("Dallas/Maxim DS1672 timekeeper driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(ds1672_init); -module_exit(ds1672_exit); diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c index 27b7bf672ac6..e1945095814e 100644 --- a/drivers/rtc/rtc-ds3232.c +++ b/drivers/rtc/rtc-ds3232.c @@ -473,18 +473,7 @@ static struct i2c_driver ds3232_driver = { .id_table = ds3232_id, }; -static int __init ds3232_init(void) -{ - return i2c_add_driver(&ds3232_driver); -} - -static void __exit ds3232_exit(void) -{ - i2c_del_driver(&ds3232_driver); -} - -module_init(ds3232_init); -module_exit(ds3232_exit); +module_i2c_driver(ds3232_driver); MODULE_AUTHOR("Srikanth Srinivasan <srikanth.srinivasan@freescale.com>"); MODULE_DESCRIPTION("Maxim/Dallas DS3232 RTC Driver"); diff --git a/drivers/rtc/rtc-ds3234.c b/drivers/rtc/rtc-ds3234.c index bbd26228f532..fda707926f02 100644 --- a/drivers/rtc/rtc-ds3234.c +++ b/drivers/rtc/rtc-ds3234.c @@ -173,17 +173,7 @@ static struct spi_driver ds3234_driver = { .remove = __devexit_p(ds3234_remove), }; -static __init int ds3234_init(void) -{ - return spi_register_driver(&ds3234_driver); -} -module_init(ds3234_init); - -static __exit void ds3234_exit(void) -{ - spi_unregister_driver(&ds3234_driver); -} -module_exit(ds3234_exit); +module_spi_driver(ds3234_driver); MODULE_DESCRIPTION("DS3234 SPI RTC driver"); MODULE_AUTHOR("Dennis Aberilla <denzzzhome@yahoo.com>"); diff --git a/drivers/rtc/rtc-em3027.c b/drivers/rtc/rtc-em3027.c index 8414dea5fb14..0104ea7ebe50 100644 --- a/drivers/rtc/rtc-em3027.c +++ b/drivers/rtc/rtc-em3027.c @@ -144,19 +144,8 @@ static struct i2c_driver em3027_driver = { .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_i2c_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-fm3130.c b/drivers/rtc/rtc-fm3130.c index 4cf2e70c5078..86b6ecce99f0 100644 --- a/drivers/rtc/rtc-fm3130.c +++ b/drivers/rtc/rtc-fm3130.c @@ -565,17 +565,7 @@ static struct i2c_driver fm3130_driver = { .id_table = fm3130_id, }; -static int __init fm3130_init(void) -{ - return i2c_add_driver(&fm3130_driver); -} -module_init(fm3130_init); - -static void __exit fm3130_exit(void) -{ - i2c_del_driver(&fm3130_driver); -} -module_exit(fm3130_exit); +module_i2c_driver(fm3130_driver); MODULE_DESCRIPTION("RTC driver for FM3130"); MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>"); diff --git a/drivers/rtc/rtc-isl12022.c b/drivers/rtc/rtc-isl12022.c index 6186833973ee..1850104705c0 100644 --- a/drivers/rtc/rtc-isl12022.c +++ b/drivers/rtc/rtc-isl12022.c @@ -309,18 +309,7 @@ static struct i2c_driver isl12022_driver = { .id_table = isl12022_id, }; -static int __init isl12022_init(void) -{ - return i2c_add_driver(&isl12022_driver); -} - -static void __exit isl12022_exit(void) -{ - i2c_del_driver(&isl12022_driver); -} - -module_init(isl12022_init); -module_exit(isl12022_exit); +module_i2c_driver(isl12022_driver); MODULE_AUTHOR("roman.fietze@telemotive.de"); MODULE_DESCRIPTION("ISL 12022 RTC driver"); diff --git a/drivers/rtc/rtc-isl1208.c b/drivers/rtc/rtc-isl1208.c index da8beb8cae51..dd2aeee6c66a 100644 --- a/drivers/rtc/rtc-isl1208.c +++ b/drivers/rtc/rtc-isl1208.c @@ -710,22 +710,9 @@ static struct i2c_driver isl1208_driver = { .id_table = isl1208_id, }; -static int __init -isl1208_init(void) -{ - return i2c_add_driver(&isl1208_driver); -} - -static void __exit -isl1208_exit(void) -{ - i2c_del_driver(&isl1208_driver); -} +module_i2c_driver(isl1208_driver); MODULE_AUTHOR("Herbert Valerio Riedel <hvr@gnu.org>"); MODULE_DESCRIPTION("Intersil ISL1208 RTC driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(isl1208_init); -module_exit(isl1208_exit); diff --git a/drivers/rtc/rtc-lpc32xx.c b/drivers/rtc/rtc-lpc32xx.c index ecc1713b2b4f..63c72189c64b 100644 --- a/drivers/rtc/rtc-lpc32xx.c +++ b/drivers/rtc/rtc-lpc32xx.c @@ -287,7 +287,7 @@ static int __devinit lpc32xx_rtc_probe(struct platform_device *pdev) if (rtc->irq >= 0) { if (devm_request_irq(&pdev->dev, rtc->irq, lpc32xx_rtc_alarm_interrupt, - IRQF_DISABLED, pdev->name, rtc) < 0) { + 0, pdev->name, rtc) < 0) { dev_warn(&pdev->dev, "Can't request interrupt.\n"); rtc->irq = -1; } else { diff --git a/drivers/rtc/rtc-ls1x.c b/drivers/rtc/rtc-ls1x.c new file mode 100644 index 000000000000..07e81c5f8247 --- /dev/null +++ b/drivers/rtc/rtc-ls1x.c @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2011 Zhao Zhang <zhzhl555@gmail.com> + * + * Derived from driver/rtc/rtc-au1xxx.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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/rtc.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/io.h> +#include <asm/mach-loongson1/loongson1.h> + +#define LS1X_RTC_REG_OFFSET (LS1X_RTC_BASE + 0x20) +#define LS1X_RTC_REGS(x) \ + ((void __iomem *)KSEG1ADDR(LS1X_RTC_REG_OFFSET + (x))) + +/*RTC programmable counters 0 and 1*/ +#define SYS_COUNTER_CNTRL (LS1X_RTC_REGS(0x20)) +#define SYS_CNTRL_ERS (1 << 23) +#define SYS_CNTRL_RTS (1 << 20) +#define SYS_CNTRL_RM2 (1 << 19) +#define SYS_CNTRL_RM1 (1 << 18) +#define SYS_CNTRL_RM0 (1 << 17) +#define SYS_CNTRL_RS (1 << 16) +#define SYS_CNTRL_BP (1 << 14) +#define SYS_CNTRL_REN (1 << 13) +#define SYS_CNTRL_BRT (1 << 12) +#define SYS_CNTRL_TEN (1 << 11) +#define SYS_CNTRL_BTT (1 << 10) +#define SYS_CNTRL_E0 (1 << 8) +#define SYS_CNTRL_ETS (1 << 7) +#define SYS_CNTRL_32S (1 << 5) +#define SYS_CNTRL_TTS (1 << 4) +#define SYS_CNTRL_TM2 (1 << 3) +#define SYS_CNTRL_TM1 (1 << 2) +#define SYS_CNTRL_TM0 (1 << 1) +#define SYS_CNTRL_TS (1 << 0) + +/* Programmable Counter 0 Registers */ +#define SYS_TOYTRIM (LS1X_RTC_REGS(0)) +#define SYS_TOYWRITE0 (LS1X_RTC_REGS(4)) +#define SYS_TOYWRITE1 (LS1X_RTC_REGS(8)) +#define SYS_TOYREAD0 (LS1X_RTC_REGS(0xC)) +#define SYS_TOYREAD1 (LS1X_RTC_REGS(0x10)) +#define SYS_TOYMATCH0 (LS1X_RTC_REGS(0x14)) +#define SYS_TOYMATCH1 (LS1X_RTC_REGS(0x18)) +#define SYS_TOYMATCH2 (LS1X_RTC_REGS(0x1C)) + +/* Programmable Counter 1 Registers */ +#define SYS_RTCTRIM (LS1X_RTC_REGS(0x40)) +#define SYS_RTCWRITE0 (LS1X_RTC_REGS(0x44)) +#define SYS_RTCREAD0 (LS1X_RTC_REGS(0x48)) +#define SYS_RTCMATCH0 (LS1X_RTC_REGS(0x4C)) +#define SYS_RTCMATCH1 (LS1X_RTC_REGS(0x50)) +#define SYS_RTCMATCH2 (LS1X_RTC_REGS(0x54)) + +#define LS1X_SEC_OFFSET (4) +#define LS1X_MIN_OFFSET (10) +#define LS1X_HOUR_OFFSET (16) +#define LS1X_DAY_OFFSET (21) +#define LS1X_MONTH_OFFSET (26) + + +#define LS1X_SEC_MASK (0x3f) +#define LS1X_MIN_MASK (0x3f) +#define LS1X_HOUR_MASK (0x1f) +#define LS1X_DAY_MASK (0x1f) +#define LS1X_MONTH_MASK (0x3f) +#define LS1X_YEAR_MASK (0xffffffff) + +#define ls1x_get_sec(t) (((t) >> LS1X_SEC_OFFSET) & LS1X_SEC_MASK) +#define ls1x_get_min(t) (((t) >> LS1X_MIN_OFFSET) & LS1X_MIN_MASK) +#define ls1x_get_hour(t) (((t) >> LS1X_HOUR_OFFSET) & LS1X_HOUR_MASK) +#define ls1x_get_day(t) (((t) >> LS1X_DAY_OFFSET) & LS1X_DAY_MASK) +#define ls1x_get_month(t) (((t) >> LS1X_MONTH_OFFSET) & LS1X_MONTH_MASK) + +#define RTC_CNTR_OK (SYS_CNTRL_E0 | SYS_CNTRL_32S) + +static int ls1x_rtc_read_time(struct device *dev, struct rtc_time *rtm) +{ + unsigned long v, t; + + v = readl(SYS_TOYREAD0); + t = readl(SYS_TOYREAD1); + + memset(rtm, 0, sizeof(struct rtc_time)); + t = mktime((t & LS1X_YEAR_MASK), ls1x_get_month(v), + ls1x_get_day(v), ls1x_get_hour(v), + ls1x_get_min(v), ls1x_get_sec(v)); + rtc_time_to_tm(t, rtm); + + return rtc_valid_tm(rtm); +} + +static int ls1x_rtc_set_time(struct device *dev, struct rtc_time *rtm) +{ + unsigned long v, t, c; + int ret = -ETIMEDOUT; + + v = ((rtm->tm_mon + 1) << LS1X_MONTH_OFFSET) + | (rtm->tm_mday << LS1X_DAY_OFFSET) + | (rtm->tm_hour << LS1X_HOUR_OFFSET) + | (rtm->tm_min << LS1X_MIN_OFFSET) + | (rtm->tm_sec << LS1X_SEC_OFFSET); + + writel(v, SYS_TOYWRITE0); + c = 0x10000; + /* add timeout check counter, for more safe */ + while ((readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TS) && --c) + usleep_range(1000, 3000); + + if (!c) { + dev_err(dev, "set time timeout!\n"); + goto err; + } + + t = rtm->tm_year + 1900; + writel(t, SYS_TOYWRITE1); + c = 0x10000; + while ((readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TS) && --c) + usleep_range(1000, 3000); + + if (!c) { + dev_err(dev, "set time timeout!\n"); + goto err; + } + return 0; +err: + return ret; +} + +static struct rtc_class_ops ls1x_rtc_ops = { + .read_time = ls1x_rtc_read_time, + .set_time = ls1x_rtc_set_time, +}; + +static int __devinit ls1x_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtcdev; + unsigned long v; + int ret; + + v = readl(SYS_COUNTER_CNTRL); + if (!(v & RTC_CNTR_OK)) { + dev_err(&pdev->dev, "rtc counters not working\n"); + ret = -ENODEV; + goto err; + } + ret = -ETIMEDOUT; + /* set to 1 HZ if needed */ + if (readl(SYS_TOYTRIM) != 32767) { + v = 0x100000; + while ((readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TTS) && --v) + usleep_range(1000, 3000); + + if (!v) { + dev_err(&pdev->dev, "time out\n"); + goto err; + } + writel(32767, SYS_TOYTRIM); + } + /* this loop coundn't be endless */ + while (readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TTS) + usleep_range(1000, 3000); + + rtcdev = rtc_device_register("ls1x-rtc", &pdev->dev, + &ls1x_rtc_ops , THIS_MODULE); + if (IS_ERR(rtcdev)) { + ret = PTR_ERR(rtcdev); + goto err; + } + + platform_set_drvdata(pdev, rtcdev); + return 0; +err: + return ret; +} + +static int __devexit ls1x_rtc_remove(struct platform_device *pdev) +{ + struct rtc_device *rtcdev = platform_get_drvdata(pdev); + + rtc_device_unregister(rtcdev); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ls1x_rtc_driver = { + .driver = { + .name = "ls1x-rtc", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(ls1x_rtc_remove), + .probe = ls1x_rtc_probe, +}; + +module_platform_driver(ls1x_rtc_driver); + +MODULE_AUTHOR("zhao zhang <zhzhl555@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c index 64aedd8cc095..4e0f84af99a7 100644 --- a/drivers/rtc/rtc-m41t80.c +++ b/drivers/rtc/rtc-m41t80.c @@ -900,20 +900,9 @@ static struct i2c_driver m41t80_driver = { .id_table = m41t80_id, }; -static int __init m41t80_rtc_init(void) -{ - return i2c_add_driver(&m41t80_driver); -} - -static void __exit m41t80_rtc_exit(void) -{ - i2c_del_driver(&m41t80_driver); -} +module_i2c_driver(m41t80_driver); MODULE_AUTHOR("Alexander Bigga <ab@mycable.de>"); MODULE_DESCRIPTION("ST Microelectronics M41T80 series RTC I2C Client Driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(m41t80_rtc_init); -module_exit(m41t80_rtc_exit); diff --git a/drivers/rtc/rtc-m41t93.c b/drivers/rtc/rtc-m41t93.c index ef71132ff205..10f1c29436ec 100644 --- a/drivers/rtc/rtc-m41t93.c +++ b/drivers/rtc/rtc-m41t93.c @@ -206,17 +206,7 @@ static struct spi_driver m41t93_driver = { .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_spi_driver(m41t93_driver); MODULE_AUTHOR("Nikolaus Voss <n.voss@weinmann.de>"); MODULE_DESCRIPTION("Driver for ST M41T93 SPI RTC"); diff --git a/drivers/rtc/rtc-m41t94.c b/drivers/rtc/rtc-m41t94.c index 2a4721f61797..6e78193e026b 100644 --- a/drivers/rtc/rtc-m41t94.c +++ b/drivers/rtc/rtc-m41t94.c @@ -153,19 +153,7 @@ static struct spi_driver m41t94_driver = { .remove = __devexit_p(m41t94_remove), }; -static __init int m41t94_init(void) -{ - return spi_register_driver(&m41t94_driver); -} - -module_init(m41t94_init); - -static __exit void m41t94_exit(void) -{ - spi_unregister_driver(&m41t94_driver); -} - -module_exit(m41t94_exit); +module_spi_driver(m41t94_driver); MODULE_AUTHOR("Kim B. Heino <Kim.Heino@bluegiga.com>"); MODULE_DESCRIPTION("Driver for ST M41T94 SPI RTC"); diff --git a/drivers/rtc/rtc-max6900.c b/drivers/rtc/rtc-max6900.c index 486142c2637a..a00e33204b91 100644 --- a/drivers/rtc/rtc-max6900.c +++ b/drivers/rtc/rtc-max6900.c @@ -261,20 +261,9 @@ static struct i2c_driver max6900_driver = { .id_table = max6900_id, }; -static int __init max6900_init(void) -{ - return i2c_add_driver(&max6900_driver); -} - -static void __exit max6900_exit(void) -{ - i2c_del_driver(&max6900_driver); -} +module_i2c_driver(max6900_driver); MODULE_DESCRIPTION("Maxim MAX6900 RTC driver"); MODULE_AUTHOR("Dale Farnsworth <dale@farnsworth.org>"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(max6900_init); -module_exit(max6900_exit); diff --git a/drivers/rtc/rtc-max6902.c b/drivers/rtc/rtc-max6902.c index 1f6b3cc58e8a..36c74d22e8b5 100644 --- a/drivers/rtc/rtc-max6902.c +++ b/drivers/rtc/rtc-max6902.c @@ -160,17 +160,7 @@ static struct spi_driver max6902_driver = { .remove = __devexit_p(max6902_remove), }; -static __init int max6902_init(void) -{ - return spi_register_driver(&max6902_driver); -} -module_init(max6902_init); - -static __exit void max6902_exit(void) -{ - spi_unregister_driver(&max6902_driver); -} -module_exit(max6902_exit); +module_spi_driver(max6902_driver); MODULE_DESCRIPTION ("max6902 spi RTC driver"); MODULE_AUTHOR ("Raphael Assenat"); diff --git a/drivers/rtc/rtc-max8925.c b/drivers/rtc/rtc-max8925.c index 2d71943bc436..1459055a83aa 100644 --- a/drivers/rtc/rtc-max8925.c +++ b/drivers/rtc/rtc-max8925.c @@ -193,10 +193,17 @@ static int max8925_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) ret = max8925_reg_read(info->rtc, MAX8925_RTC_IRQ_MASK); if (ret < 0) goto out; - if ((ret & ALARM0_IRQ) == 0) - alrm->enabled = 1; - else + if (ret & ALARM0_IRQ) { alrm->enabled = 0; + } else { + ret = max8925_reg_read(info->rtc, MAX8925_ALARM0_CNTL); + if (ret < 0) + goto out; + if (!ret) + alrm->enabled = 0; + else + alrm->enabled = 1; + } ret = max8925_reg_read(info->rtc, MAX8925_RTC_STATUS); if (ret < 0) goto out; @@ -204,6 +211,7 @@ static int max8925_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) alrm->pending = 1; else alrm->pending = 0; + return 0; out: return ret; } @@ -220,8 +228,11 @@ static int max8925_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) ret = max8925_bulk_write(info->rtc, MAX8925_ALARM0_SEC, TIME_NUM, buf); if (ret < 0) goto out; - /* only enable alarm on year/month/day/hour/min/sec */ - ret = max8925_reg_write(info->rtc, MAX8925_ALARM0_CNTL, 0x77); + if (alrm->enabled) + /* only enable alarm on year/month/day/hour/min/sec */ + ret = max8925_reg_write(info->rtc, MAX8925_ALARM0_CNTL, 0x77); + else + ret = max8925_reg_write(info->rtc, MAX8925_ALARM0_CNTL, 0x0); if (ret < 0) goto out; out: diff --git a/drivers/rtc/rtc-mpc5121.c b/drivers/rtc/rtc-mpc5121.c index 9d3caccfc250..e954a759ba85 100644 --- a/drivers/rtc/rtc-mpc5121.c +++ b/drivers/rtc/rtc-mpc5121.c @@ -327,7 +327,7 @@ static int __devinit mpc5121_rtc_probe(struct platform_device *op) dev_set_drvdata(&op->dev, rtc); rtc->irq = irq_of_parse_and_map(op->dev.of_node, 1); - err = request_irq(rtc->irq, mpc5121_rtc_handler, IRQF_DISABLED, + err = request_irq(rtc->irq, mpc5121_rtc_handler, 0, "mpc5121-rtc", &op->dev); if (err) { dev_err(&op->dev, "%s: could not request irq: %i\n", @@ -337,7 +337,7 @@ static int __devinit mpc5121_rtc_probe(struct platform_device *op) rtc->irq_periodic = irq_of_parse_and_map(op->dev.of_node, 0); err = request_irq(rtc->irq_periodic, mpc5121_rtc_handler_upd, - IRQF_DISABLED, "mpc5121-rtc_upd", &op->dev); + 0, "mpc5121-rtc_upd", &op->dev); if (err) { dev_err(&op->dev, "%s: could not request irq: %i\n", __func__, rtc->irq_periodic); diff --git a/drivers/rtc/rtc-mrst.c b/drivers/rtc/rtc-mrst.c index 6cd6c7235344..f51719bf4a75 100644 --- a/drivers/rtc/rtc-mrst.c +++ b/drivers/rtc/rtc-mrst.c @@ -366,7 +366,7 @@ vrtc_mrst_do_probe(struct device *dev, struct resource *iomem, int rtc_irq) if (rtc_irq) { retval = request_irq(rtc_irq, mrst_rtc_irq, - IRQF_DISABLED, dev_name(&mrst_rtc.rtc->dev), + 0, dev_name(&mrst_rtc.rtc->dev), mrst_rtc.rtc); if (retval < 0) { dev_dbg(dev, "IRQ %d is already in use, err %d\n", diff --git a/drivers/rtc/rtc-mv.c b/drivers/rtc/rtc-mv.c index 768e2edb9678..b2185f4255aa 100644 --- a/drivers/rtc/rtc-mv.c +++ b/drivers/rtc/rtc-mv.c @@ -12,6 +12,7 @@ #include <linux/bcd.h> #include <linux/io.h> #include <linux/platform_device.h> +#include <linux/of.h> #include <linux/delay.h> #include <linux/gfp.h> #include <linux/module.h> @@ -273,7 +274,7 @@ static int __devinit mv_rtc_probe(struct platform_device *pdev) if (pdata->irq >= 0) { writel(0, pdata->ioaddr + RTC_ALARM_INTERRUPT_MASK_REG_OFFS); if (devm_request_irq(&pdev->dev, pdata->irq, mv_rtc_interrupt, - IRQF_DISABLED | IRQF_SHARED, + IRQF_SHARED, pdev->name, pdata) < 0) { dev_warn(&pdev->dev, "interrupt not available.\n"); pdata->irq = -1; @@ -294,11 +295,19 @@ static int __exit mv_rtc_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static struct of_device_id rtc_mv_of_match_table[] = { + { .compatible = "mrvl,orion-rtc", }, + {} +}; +#endif + static struct platform_driver mv_rtc_driver = { .remove = __exit_p(mv_rtc_remove), .driver = { .name = "rtc-mv", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rtc_mv_of_match_table), }, }; diff --git a/drivers/rtc/rtc-nuc900.c b/drivers/rtc/rtc-nuc900.c index 781068d62f23..b79010987d1e 100644 --- a/drivers/rtc/rtc-nuc900.c +++ b/drivers/rtc/rtc-nuc900.c @@ -269,7 +269,7 @@ static int __devinit nuc900_rtc_probe(struct platform_device *pdev) nuc900_rtc->irq_num = platform_get_irq(pdev, 0); if (request_irq(nuc900_rtc->irq_num, nuc900_rtc_interrupt, - IRQF_DISABLED, "nuc900rtc", nuc900_rtc)) { + 0, "nuc900rtc", nuc900_rtc)) { dev_err(&pdev->dev, "NUC900 RTC request irq failed\n"); err = -EBUSY; goto fail4; diff --git a/drivers/rtc/rtc-omap.c b/drivers/rtc/rtc-omap.c index 7789002bdd5c..0b614e32653d 100644 --- a/drivers/rtc/rtc-omap.c +++ b/drivers/rtc/rtc-omap.c @@ -348,14 +348,14 @@ static int __init omap_rtc_probe(struct platform_device *pdev) rtc_write(OMAP_RTC_STATUS_ALARM, OMAP_RTC_STATUS_REG); /* handle periodic and alarm irqs */ - if (request_irq(omap_rtc_timer, rtc_irq, IRQF_DISABLED, + if (request_irq(omap_rtc_timer, rtc_irq, 0, dev_name(&rtc->dev), rtc)) { pr_debug("%s: RTC timer interrupt IRQ%d already claimed\n", pdev->name, omap_rtc_timer); goto fail1; } if ((omap_rtc_timer != omap_rtc_alarm) && - (request_irq(omap_rtc_alarm, rtc_irq, IRQF_DISABLED, + (request_irq(omap_rtc_alarm, rtc_irq, 0, dev_name(&rtc->dev), rtc))) { pr_debug("%s: RTC alarm interrupt IRQ%d already claimed\n", pdev->name, omap_rtc_alarm); diff --git a/drivers/rtc/rtc-pcf2123.c b/drivers/rtc/rtc-pcf2123.c index b46c4004d8fe..836118795c0b 100644 --- a/drivers/rtc/rtc-pcf2123.c +++ b/drivers/rtc/rtc-pcf2123.c @@ -346,20 +346,9 @@ static struct spi_driver pcf2123_driver = { .remove = __devexit_p(pcf2123_remove), }; -static int __init pcf2123_init(void) -{ - return spi_register_driver(&pcf2123_driver); -} - -static void __exit pcf2123_exit(void) -{ - spi_unregister_driver(&pcf2123_driver); -} +module_spi_driver(pcf2123_driver); MODULE_AUTHOR("Chris Verges <chrisv@cyberswitching.com>"); MODULE_DESCRIPTION("NXP PCF2123 RTC driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(pcf2123_init); -module_exit(pcf2123_exit); diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 606fdfab34e2..bc0677de1996 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -252,20 +252,9 @@ static struct i2c_driver pcf8563_driver = { .id_table = pcf8563_id, }; -static int __init pcf8563_init(void) -{ - return i2c_add_driver(&pcf8563_driver); -} - -static void __exit pcf8563_exit(void) -{ - i2c_del_driver(&pcf8563_driver); -} +module_i2c_driver(pcf8563_driver); MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); MODULE_DESCRIPTION("Philips PCF8563/Epson RTC8564 RTC driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(pcf8563_init); -module_exit(pcf8563_exit); diff --git a/drivers/rtc/rtc-pcf8583.c b/drivers/rtc/rtc-pcf8583.c index 2d201afead3b..019ff3571168 100644 --- a/drivers/rtc/rtc-pcf8583.c +++ b/drivers/rtc/rtc-pcf8583.c @@ -320,18 +320,7 @@ static struct i2c_driver pcf8583_driver = { .id_table = pcf8583_id, }; -static __init int pcf8583_init(void) -{ - return i2c_add_driver(&pcf8583_driver); -} - -static __exit void pcf8583_exit(void) -{ - i2c_del_driver(&pcf8583_driver); -} - -module_init(pcf8583_init); -module_exit(pcf8583_exit); +module_i2c_driver(pcf8583_driver); MODULE_AUTHOR("Russell King"); MODULE_DESCRIPTION("PCF8583 I2C RTC driver"); diff --git a/drivers/rtc/rtc-pl030.c b/drivers/rtc/rtc-pl030.c index 02111fee077e..22bacdbf9139 100644 --- a/drivers/rtc/rtc-pl030.c +++ b/drivers/rtc/rtc-pl030.c @@ -123,7 +123,7 @@ static int pl030_probe(struct amba_device *dev, const struct amba_id *id) amba_set_drvdata(dev, rtc); - ret = request_irq(dev->irq[0], pl030_interrupt, IRQF_DISABLED, + ret = request_irq(dev->irq[0], pl030_interrupt, 0, "rtc-pl030", rtc); if (ret) goto err_irq; @@ -185,18 +185,7 @@ static struct amba_driver pl030_driver = { .id_table = pl030_ids, }; -static int __init pl030_init(void) -{ - return amba_driver_register(&pl030_driver); -} - -static void __exit pl030_exit(void) -{ - amba_driver_unregister(&pl030_driver); -} - -module_init(pl030_init); -module_exit(pl030_exit); +module_amba_driver(pl030_driver); MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); MODULE_DESCRIPTION("ARM AMBA PL030 RTC Driver"); diff --git a/drivers/rtc/rtc-pl031.c b/drivers/rtc/rtc-pl031.c index a952c8de1dd7..692de7360e94 100644 --- a/drivers/rtc/rtc-pl031.c +++ b/drivers/rtc/rtc-pl031.c @@ -352,7 +352,7 @@ static int pl031_probe(struct amba_device *adev, const struct amba_id *id) } if (request_irq(adev->irq[0], pl031_interrupt, - IRQF_DISABLED, "rtc-pl031", ldata)) { + 0, "rtc-pl031", ldata)) { ret = -EIO; goto out_no_irq; } @@ -431,18 +431,7 @@ static struct amba_driver pl031_driver = { .remove = pl031_remove, }; -static int __init pl031_init(void) -{ - return amba_driver_register(&pl031_driver); -} - -static void __exit pl031_exit(void) -{ - amba_driver_unregister(&pl031_driver); -} - -module_init(pl031_init); -module_exit(pl031_exit); +module_amba_driver(pl031_driver); MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net"); MODULE_DESCRIPTION("ARM AMBA PL031 RTC Driver"); diff --git a/drivers/rtc/rtc-pm8xxx.c b/drivers/rtc/rtc-pm8xxx.c index 9f1d6bcbdf6c..d00bd24342a3 100644 --- a/drivers/rtc/rtc-pm8xxx.c +++ b/drivers/rtc/rtc-pm8xxx.c @@ -520,7 +520,7 @@ static int pm8xxx_rtc_suspend(struct device *dev) } #endif -SIMPLE_DEV_PM_OPS(pm8xxx_rtc_pm_ops, pm8xxx_rtc_suspend, pm8xxx_rtc_resume); +static SIMPLE_DEV_PM_OPS(pm8xxx_rtc_pm_ops, pm8xxx_rtc_suspend, pm8xxx_rtc_resume); static struct platform_driver pm8xxx_rtc_driver = { .probe = pm8xxx_rtc_probe, diff --git a/drivers/rtc/rtc-pxa.c b/drivers/rtc/rtc-pxa.c index fc9f4991574b..0075c8fd93d8 100644 --- a/drivers/rtc/rtc-pxa.c +++ b/drivers/rtc/rtc-pxa.c @@ -174,14 +174,14 @@ static int pxa_rtc_open(struct device *dev) struct pxa_rtc *pxa_rtc = dev_get_drvdata(dev); int ret; - ret = request_irq(pxa_rtc->irq_1Hz, pxa_rtc_irq, IRQF_DISABLED, + ret = request_irq(pxa_rtc->irq_1Hz, pxa_rtc_irq, 0, "rtc 1Hz", dev); if (ret < 0) { dev_err(dev, "can't get irq %i, err %d\n", pxa_rtc->irq_1Hz, ret); goto err_irq_1Hz; } - ret = request_irq(pxa_rtc->irq_Alrm, pxa_rtc_irq, IRQF_DISABLED, + ret = request_irq(pxa_rtc->irq_Alrm, pxa_rtc_irq, 0, "rtc Alrm", dev); if (ret < 0) { dev_err(dev, "can't get irq %i, err %d\n", pxa_rtc->irq_Alrm, diff --git a/drivers/rtc/rtc-r9701.c b/drivers/rtc/rtc-r9701.c index 2853c2a6f10f..7f8e6c247935 100644 --- a/drivers/rtc/rtc-r9701.c +++ b/drivers/rtc/rtc-r9701.c @@ -159,17 +159,7 @@ static struct spi_driver r9701_driver = { .remove = __devexit_p(r9701_remove), }; -static __init int r9701_init(void) -{ - return spi_register_driver(&r9701_driver); -} -module_init(r9701_init); - -static __exit void r9701_exit(void) -{ - spi_unregister_driver(&r9701_driver); -} -module_exit(r9701_exit); +module_spi_driver(r9701_driver); MODULE_DESCRIPTION("r9701 spi RTC driver"); MODULE_AUTHOR("Magnus Damm <damm@opensource.se>"); diff --git a/drivers/rtc/rtc-rs5c348.c b/drivers/rtc/rtc-rs5c348.c index ce2ca8523ddd..77074ccd2850 100644 --- a/drivers/rtc/rtc-rs5c348.c +++ b/drivers/rtc/rtc-rs5c348.c @@ -235,18 +235,7 @@ static struct spi_driver rs5c348_driver = { .remove = __devexit_p(rs5c348_remove), }; -static __init int rs5c348_init(void) -{ - return spi_register_driver(&rs5c348_driver); -} - -static __exit void rs5c348_exit(void) -{ - spi_unregister_driver(&rs5c348_driver); -} - -module_init(rs5c348_init); -module_exit(rs5c348_exit); +module_spi_driver(rs5c348_driver); MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); MODULE_DESCRIPTION("Ricoh RS5C348 RTC driver"); diff --git a/drivers/rtc/rtc-rs5c372.c b/drivers/rtc/rtc-rs5c372.c index d29f5432c6e8..fb4842c3544e 100644 --- a/drivers/rtc/rtc-rs5c372.c +++ b/drivers/rtc/rtc-rs5c372.c @@ -689,18 +689,7 @@ static struct i2c_driver rs5c372_driver = { .id_table = rs5c372_id, }; -static __init int rs5c372_init(void) -{ - return i2c_add_driver(&rs5c372_driver); -} - -static __exit void rs5c372_exit(void) -{ - i2c_del_driver(&rs5c372_driver); -} - -module_init(rs5c372_init); -module_exit(rs5c372_exit); +module_i2c_driver(rs5c372_driver); MODULE_AUTHOR( "Pavel Mironchik <pmironchik@optifacio.net>, " diff --git a/drivers/rtc/rtc-rv3029c2.c b/drivers/rtc/rtc-rv3029c2.c index ea09ff211dc6..0fbe57b2f6d2 100644 --- a/drivers/rtc/rtc-rv3029c2.c +++ b/drivers/rtc/rtc-rv3029c2.c @@ -436,18 +436,7 @@ static struct i2c_driver rv3029c2_driver = { .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_i2c_driver(rv3029c2_driver); MODULE_AUTHOR("Gregory Hermant <gregory.hermant@calao-systems.com>"); MODULE_DESCRIPTION("Micro Crystal RV3029C2 RTC driver"); diff --git a/drivers/rtc/rtc-rx8025.c b/drivers/rtc/rtc-rx8025.c index fde172fb2abe..0de902dc1cd5 100644 --- a/drivers/rtc/rtc-rx8025.c +++ b/drivers/rtc/rtc-rx8025.c @@ -644,19 +644,8 @@ static struct i2c_driver rx8025_driver = { .id_table = rx8025_id, }; -static int __init rx8025_init(void) -{ - return i2c_add_driver(&rx8025_driver); -} - -static void __exit rx8025_exit(void) -{ - i2c_del_driver(&rx8025_driver); -} +module_i2c_driver(rx8025_driver); MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>"); MODULE_DESCRIPTION("RX-8025 SA/NB RTC driver"); MODULE_LICENSE("GPL"); - -module_init(rx8025_init); -module_exit(rx8025_exit); diff --git a/drivers/rtc/rtc-rx8581.c b/drivers/rtc/rtc-rx8581.c index 600b890a3c15..d84825124a7a 100644 --- a/drivers/rtc/rtc-rx8581.c +++ b/drivers/rtc/rtc-rx8581.c @@ -276,20 +276,9 @@ static struct i2c_driver rx8581_driver = { .id_table = rx8581_id, }; -static int __init rx8581_init(void) -{ - return i2c_add_driver(&rx8581_driver); -} - -static void __exit rx8581_exit(void) -{ - i2c_del_driver(&rx8581_driver); -} +module_i2c_driver(rx8581_driver); MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>"); MODULE_DESCRIPTION("Epson RX-8581 RTC driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(rx8581_init); -module_exit(rx8581_exit); diff --git a/drivers/rtc/rtc-s35390a.c b/drivers/rtc/rtc-s35390a.c index f789e002c9b0..c9562ceedef3 100644 --- a/drivers/rtc/rtc-s35390a.c +++ b/drivers/rtc/rtc-s35390a.c @@ -304,19 +304,8 @@ static struct i2c_driver s35390a_driver = { .id_table = s35390a_id, }; -static int __init s35390a_rtc_init(void) -{ - return i2c_add_driver(&s35390a_driver); -} - -static void __exit s35390a_rtc_exit(void) -{ - i2c_del_driver(&s35390a_driver); -} +module_i2c_driver(s35390a_driver); MODULE_AUTHOR("Byron Bradley <byron.bbradley@gmail.com>"); MODULE_DESCRIPTION("S35390A RTC driver"); MODULE_LICENSE("GPL"); - -module_init(s35390a_rtc_init); -module_exit(s35390a_rtc_exit); diff --git a/drivers/rtc/rtc-s3c.c b/drivers/rtc/rtc-s3c.c index aef40bd2957b..9ccea134a996 100644 --- a/drivers/rtc/rtc-s3c.c +++ b/drivers/rtc/rtc-s3c.c @@ -35,6 +35,8 @@ enum s3c_cpu_type { TYPE_S3C2410, + TYPE_S3C2416, + TYPE_S3C2443, TYPE_S3C64XX, }; @@ -132,6 +134,7 @@ static int s3c_rtc_setfreq(struct device *dev, int freq) struct platform_device *pdev = to_platform_device(dev); struct rtc_device *rtc_dev = platform_get_drvdata(pdev); unsigned int tmp = 0; + int val; if (!is_power_of_2(freq)) return -EINVAL; @@ -139,12 +142,22 @@ static int s3c_rtc_setfreq(struct device *dev, int freq) clk_enable(rtc_clk); spin_lock_irq(&s3c_rtc_pie_lock); - if (s3c_rtc_cpu_type == TYPE_S3C2410) { + if (s3c_rtc_cpu_type != TYPE_S3C64XX) { tmp = readb(s3c_rtc_base + S3C2410_TICNT); tmp &= S3C2410_TICNT_ENABLE; } - tmp |= (rtc_dev->max_user_freq / freq)-1; + val = (rtc_dev->max_user_freq / freq) - 1; + + if (s3c_rtc_cpu_type == TYPE_S3C2416 || s3c_rtc_cpu_type == TYPE_S3C2443) { + tmp |= S3C2443_TICNT_PART(val); + writel(S3C2443_TICNT1_PART(val), s3c_rtc_base + S3C2443_TICNT1); + + if (s3c_rtc_cpu_type == TYPE_S3C2416) + writel(S3C2416_TICNT2_PART(val), s3c_rtc_base + S3C2416_TICNT2); + } else { + tmp |= val; + } writel(tmp, s3c_rtc_base + S3C2410_TICNT); spin_unlock_irq(&s3c_rtc_pie_lock); @@ -371,7 +384,7 @@ static void s3c_rtc_enable(struct platform_device *pdev, int en) tmp &= ~S3C2410_RTCCON_RTCEN; writew(tmp, base + S3C2410_RTCCON); - if (s3c_rtc_cpu_type == TYPE_S3C2410) { + if (s3c_rtc_cpu_type != TYPE_S3C64XX) { tmp = readb(base + S3C2410_TICNT); tmp &= ~S3C2410_TICNT_ENABLE; writeb(tmp, base + S3C2410_TICNT); @@ -428,12 +441,27 @@ static int __devexit s3c_rtc_remove(struct platform_device *dev) return 0; } +static const struct of_device_id s3c_rtc_dt_match[]; + +static inline int s3c_rtc_get_driver_data(struct platform_device *pdev) +{ +#ifdef CONFIG_OF + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(s3c_rtc_dt_match, pdev->dev.of_node); + return match->data; + } +#endif + return platform_get_device_id(pdev)->driver_data; +} + static int __devinit s3c_rtc_probe(struct platform_device *pdev) { struct rtc_device *rtc; struct rtc_time rtc_tm; struct resource *res; int ret; + int tmp; pr_debug("%s: probe=%p\n", __func__, pdev); @@ -508,13 +536,7 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev) goto err_nortc; } -#ifdef CONFIG_OF - if (pdev->dev.of_node) - s3c_rtc_cpu_type = of_device_is_compatible(pdev->dev.of_node, - "samsung,s3c6410-rtc") ? TYPE_S3C64XX : TYPE_S3C2410; - else -#endif - s3c_rtc_cpu_type = platform_get_device_id(pdev)->driver_data; + s3c_rtc_cpu_type = s3c_rtc_get_driver_data(pdev); /* Check RTC Time */ @@ -533,24 +555,30 @@ static int __devinit s3c_rtc_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "warning: invalid RTC value so initializing it\n"); } - if (s3c_rtc_cpu_type == TYPE_S3C64XX) + if (s3c_rtc_cpu_type != TYPE_S3C2410) rtc->max_user_freq = 32768; else rtc->max_user_freq = 128; + if (s3c_rtc_cpu_type == TYPE_S3C2416 || s3c_rtc_cpu_type == TYPE_S3C2443) { + tmp = readw(s3c_rtc_base + S3C2410_RTCCON); + tmp |= S3C2443_RTCCON_TICSEL; + writew(tmp, s3c_rtc_base + S3C2410_RTCCON); + } + platform_set_drvdata(pdev, rtc); s3c_rtc_setfreq(&pdev->dev, 1); ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq, - IRQF_DISABLED, "s3c2410-rtc alarm", rtc); + 0, "s3c2410-rtc alarm", rtc); if (ret) { dev_err(&pdev->dev, "IRQ%d error %d\n", s3c_rtc_alarmno, ret); goto err_alarm_irq; } ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq, - IRQF_DISABLED, "s3c2410-rtc tick", rtc); + 0, "s3c2410-rtc tick", rtc); if (ret) { dev_err(&pdev->dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret); free_irq(s3c_rtc_alarmno, rtc); @@ -638,8 +666,19 @@ static int s3c_rtc_resume(struct platform_device *pdev) #ifdef CONFIG_OF static const struct of_device_id s3c_rtc_dt_match[] = { - { .compatible = "samsung,s3c2410-rtc" }, - { .compatible = "samsung,s3c6410-rtc" }, + { + .compatible = "samsung,s3c2410-rtc" + .data = TYPE_S3C2410, + }, { + .compatible = "samsung,s3c2416-rtc" + .data = TYPE_S3C2416, + }, { + .compatible = "samsung,s3c2443-rtc" + .data = TYPE_S3C2443, + }, { + .compatible = "samsung,s3c6410-rtc" + .data = TYPE_S3C64XX, + }, {}, }; MODULE_DEVICE_TABLE(of, s3c_rtc_dt_match); @@ -652,6 +691,12 @@ static struct platform_device_id s3c_rtc_driver_ids[] = { .name = "s3c2410-rtc", .driver_data = TYPE_S3C2410, }, { + .name = "s3c2416-rtc", + .driver_data = TYPE_S3C2416, + }, { + .name = "s3c2443-rtc", + .driver_data = TYPE_S3C2443, + }, { .name = "s3c64xx-rtc", .driver_data = TYPE_S3C64XX, }, diff --git a/drivers/rtc/rtc-sa1100.c b/drivers/rtc/rtc-sa1100.c index cb9a585312cc..4940fa8c4e10 100644 --- a/drivers/rtc/rtc-sa1100.c +++ b/drivers/rtc/rtc-sa1100.c @@ -23,94 +23,44 @@ #include <linux/platform_device.h> #include <linux/module.h> +#include <linux/clk.h> #include <linux/rtc.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> +#include <linux/slab.h> #include <linux/string.h> +#include <linux/of.h> #include <linux/pm.h> #include <linux/bitops.h> #include <mach/hardware.h> -#include <asm/irq.h> +#include <mach/irqs.h> -#ifdef CONFIG_ARCH_PXA +#if defined(CONFIG_ARCH_PXA) || defined(CONFIG_ARCH_MMP) #include <mach/regs-rtc.h> #endif #define RTC_DEF_DIVIDER (32768 - 1) #define RTC_DEF_TRIM 0 - -static const unsigned long RTC_FREQ = 1024; -static struct rtc_time rtc_alarm; -static DEFINE_SPINLOCK(sa1100_rtc_lock); - -static inline int rtc_periodic_alarm(struct rtc_time *tm) -{ - return (tm->tm_year == -1) || - ((unsigned)tm->tm_mon >= 12) || - ((unsigned)(tm->tm_mday - 1) >= 31) || - ((unsigned)tm->tm_hour > 23) || - ((unsigned)tm->tm_min > 59) || - ((unsigned)tm->tm_sec > 59); -} - -/* - * 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 rtc_update_alarm(struct rtc_time *alrm) -{ - struct rtc_time alarm_tm, now_tm; - unsigned long now, time; - int ret; - - do { - now = RCNR; - rtc_time_to_tm(now, &now_tm); - rtc_next_alarm_time(&alarm_tm, &now_tm, alrm); - ret = rtc_tm_to_time(&alarm_tm, &time); - if (ret != 0) - break; - - RTSR = RTSR & (RTSR_HZE|RTSR_ALE|RTSR_AL); - RTAR = time; - } while (now != RCNR); - - return ret; -} +#define RTC_FREQ 1024 + +struct sa1100_rtc { + spinlock_t lock; + int irq_1hz; + int irq_alarm; + struct rtc_device *rtc; + struct clk *clk; +}; static irqreturn_t sa1100_rtc_interrupt(int irq, void *dev_id) { - struct platform_device *pdev = to_platform_device(dev_id); - struct rtc_device *rtc = platform_get_drvdata(pdev); + struct sa1100_rtc *info = dev_get_drvdata(dev_id); + struct rtc_device *rtc = info->rtc; unsigned int rtsr; unsigned long events = 0; - spin_lock(&sa1100_rtc_lock); + spin_lock(&info->lock); rtsr = RTSR; /* clear interrupt sources */ @@ -146,30 +96,28 @@ static irqreturn_t sa1100_rtc_interrupt(int irq, void *dev_id) rtc_update_irq(rtc, 1, events); - if (rtsr & RTSR_AL && rtc_periodic_alarm(&rtc_alarm)) - rtc_update_alarm(&rtc_alarm); - - spin_unlock(&sa1100_rtc_lock); + spin_unlock(&info->lock); return IRQ_HANDLED; } static int sa1100_rtc_open(struct device *dev) { + struct sa1100_rtc *info = dev_get_drvdata(dev); + struct rtc_device *rtc = info->rtc; int ret; - struct platform_device *plat_dev = to_platform_device(dev); - struct rtc_device *rtc = platform_get_drvdata(plat_dev); - ret = request_irq(IRQ_RTC1Hz, sa1100_rtc_interrupt, IRQF_DISABLED, - "rtc 1Hz", dev); + ret = clk_prepare_enable(info->clk); + if (ret) + goto fail_clk; + ret = request_irq(info->irq_1hz, sa1100_rtc_interrupt, 0, "rtc 1Hz", dev); if (ret) { - dev_err(dev, "IRQ %d already in use.\n", IRQ_RTC1Hz); + dev_err(dev, "IRQ %d already in use.\n", info->irq_1hz); goto fail_ui; } - ret = request_irq(IRQ_RTCAlrm, sa1100_rtc_interrupt, IRQF_DISABLED, - "rtc Alrm", dev); + ret = request_irq(info->irq_alarm, sa1100_rtc_interrupt, 0, "rtc Alrm", dev); if (ret) { - dev_err(dev, "IRQ %d already in use.\n", IRQ_RTCAlrm); + dev_err(dev, "IRQ %d already in use.\n", info->irq_alarm); goto fail_ai; } rtc->max_user_freq = RTC_FREQ; @@ -178,29 +126,36 @@ static int sa1100_rtc_open(struct device *dev) return 0; fail_ai: - free_irq(IRQ_RTC1Hz, dev); + free_irq(info->irq_1hz, dev); fail_ui: + clk_disable_unprepare(info->clk); + fail_clk: return ret; } static void sa1100_rtc_release(struct device *dev) { - spin_lock_irq(&sa1100_rtc_lock); + struct sa1100_rtc *info = dev_get_drvdata(dev); + + spin_lock_irq(&info->lock); RTSR = 0; - spin_unlock_irq(&sa1100_rtc_lock); + spin_unlock_irq(&info->lock); - free_irq(IRQ_RTCAlrm, dev); - free_irq(IRQ_RTC1Hz, dev); + free_irq(info->irq_alarm, dev); + free_irq(info->irq_1hz, dev); + clk_disable_unprepare(info->clk); } static int sa1100_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) { - spin_lock_irq(&sa1100_rtc_lock); + struct sa1100_rtc *info = dev_get_drvdata(dev); + + spin_lock_irq(&info->lock); if (enabled) RTSR |= RTSR_ALE; else RTSR &= ~RTSR_ALE; - spin_unlock_irq(&sa1100_rtc_lock); + spin_unlock_irq(&info->lock); return 0; } @@ -225,7 +180,6 @@ static int sa1100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) { u32 rtsr; - memcpy(&alrm->time, &rtc_alarm, sizeof(struct rtc_time)); rtsr = RTSR; alrm->enabled = (rtsr & RTSR_ALE) ? 1 : 0; alrm->pending = (rtsr & RTSR_AL) ? 1 : 0; @@ -234,17 +188,22 @@ static int sa1100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) static int sa1100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) { + struct sa1100_rtc *info = dev_get_drvdata(dev); + unsigned long time; int ret; - spin_lock_irq(&sa1100_rtc_lock); - ret = rtc_update_alarm(&alrm->time); - if (ret == 0) { - if (alrm->enabled) - RTSR |= RTSR_ALE; - else - RTSR &= ~RTSR_ALE; - } - spin_unlock_irq(&sa1100_rtc_lock); + spin_lock_irq(&info->lock); + ret = rtc_tm_to_time(&alrm->time, &time); + if (ret != 0) + goto out; + RTSR = RTSR & (RTSR_HZE|RTSR_ALE|RTSR_AL); + RTAR = time; + if (alrm->enabled) + RTSR |= RTSR_ALE; + else + RTSR &= ~RTSR_ALE; +out: + spin_unlock_irq(&info->lock); return ret; } @@ -271,6 +230,27 @@ static const struct rtc_class_ops sa1100_rtc_ops = { static int sa1100_rtc_probe(struct platform_device *pdev) { struct rtc_device *rtc; + struct sa1100_rtc *info; + int irq_1hz, irq_alarm, ret = 0; + + irq_1hz = platform_get_irq_byname(pdev, "rtc 1Hz"); + irq_alarm = platform_get_irq_byname(pdev, "rtc alarm"); + if (irq_1hz < 0 || irq_alarm < 0) + return -ENODEV; + + info = kzalloc(sizeof(struct sa1100_rtc), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed to find rtc clock source\n"); + ret = PTR_ERR(info->clk); + goto err_clk; + } + info->irq_1hz = irq_1hz; + info->irq_alarm = irq_alarm; + spin_lock_init(&info->lock); + platform_set_drvdata(pdev, info); /* * According to the manual we should be able to let RTTR be zero @@ -292,10 +272,11 @@ static int sa1100_rtc_probe(struct platform_device *pdev) rtc = rtc_device_register(pdev->name, &pdev->dev, &sa1100_rtc_ops, THIS_MODULE); - if (IS_ERR(rtc)) - return PTR_ERR(rtc); - - platform_set_drvdata(pdev, rtc); + if (IS_ERR(rtc)) { + ret = PTR_ERR(rtc); + goto err_dev; + } + info->rtc = rtc; /* Fix for a nasty initialization problem the in SA11xx RTSR register. * See also the comments in sa1100_rtc_interrupt(). @@ -322,14 +303,24 @@ static int sa1100_rtc_probe(struct platform_device *pdev) RTSR = RTSR_AL | RTSR_HZ; return 0; +err_dev: + platform_set_drvdata(pdev, NULL); + clk_put(info->clk); +err_clk: + kfree(info); + return ret; } static int sa1100_rtc_remove(struct platform_device *pdev) { - struct rtc_device *rtc = platform_get_drvdata(pdev); + struct sa1100_rtc *info = platform_get_drvdata(pdev); - if (rtc) - rtc_device_unregister(rtc); + if (info) { + rtc_device_unregister(info->rtc); + clk_put(info->clk); + platform_set_drvdata(pdev, NULL); + kfree(info); + } return 0; } @@ -337,15 +328,17 @@ static int sa1100_rtc_remove(struct platform_device *pdev) #ifdef CONFIG_PM static int sa1100_rtc_suspend(struct device *dev) { + struct sa1100_rtc *info = dev_get_drvdata(dev); if (device_may_wakeup(dev)) - enable_irq_wake(IRQ_RTCAlrm); + enable_irq_wake(info->irq_alarm); return 0; } static int sa1100_rtc_resume(struct device *dev) { + struct sa1100_rtc *info = dev_get_drvdata(dev); if (device_may_wakeup(dev)) - disable_irq_wake(IRQ_RTCAlrm); + disable_irq_wake(info->irq_alarm); return 0; } @@ -355,6 +348,13 @@ static const struct dev_pm_ops sa1100_rtc_pm_ops = { }; #endif +static struct of_device_id sa1100_rtc_dt_ids[] = { + { .compatible = "mrvl,sa1100-rtc", }, + { .compatible = "mrvl,mmp-rtc", }, + {} +}; +MODULE_DEVICE_TABLE(of, sa1100_rtc_dt_ids); + static struct platform_driver sa1100_rtc_driver = { .probe = sa1100_rtc_probe, .remove = sa1100_rtc_remove, @@ -363,6 +363,7 @@ static struct platform_driver sa1100_rtc_driver = { #ifdef CONFIG_PM .pm = &sa1100_rtc_pm_ops, #endif + .of_match_table = sa1100_rtc_dt_ids, }, }; diff --git a/drivers/rtc/rtc-sh.c b/drivers/rtc/rtc-sh.c index 6ac55fd48413..e55a7635ae5f 100644 --- a/drivers/rtc/rtc-sh.c +++ b/drivers/rtc/rtc-sh.c @@ -666,7 +666,7 @@ static int __init sh_rtc_probe(struct platform_device *pdev) if (rtc->carry_irq <= 0) { /* register shared periodic/carry/alarm irq */ ret = request_irq(rtc->periodic_irq, sh_rtc_shared, - IRQF_DISABLED, "sh-rtc", rtc); + 0, "sh-rtc", rtc); if (unlikely(ret)) { dev_err(&pdev->dev, "request IRQ failed with %d, IRQ %d\n", ret, @@ -676,7 +676,7 @@ static int __init sh_rtc_probe(struct platform_device *pdev) } else { /* register periodic/carry/alarm irqs */ ret = request_irq(rtc->periodic_irq, sh_rtc_periodic, - IRQF_DISABLED, "sh-rtc period", rtc); + 0, "sh-rtc period", rtc); if (unlikely(ret)) { dev_err(&pdev->dev, "request period IRQ failed with %d, IRQ %d\n", @@ -685,7 +685,7 @@ static int __init sh_rtc_probe(struct platform_device *pdev) } ret = request_irq(rtc->carry_irq, sh_rtc_interrupt, - IRQF_DISABLED, "sh-rtc carry", rtc); + 0, "sh-rtc carry", rtc); if (unlikely(ret)) { dev_err(&pdev->dev, "request carry IRQ failed with %d, IRQ %d\n", @@ -695,7 +695,7 @@ static int __init sh_rtc_probe(struct platform_device *pdev) } ret = request_irq(rtc->alarm_irq, sh_rtc_alarm, - IRQF_DISABLED, "sh-rtc alarm", rtc); + 0, "sh-rtc alarm", rtc); if (unlikely(ret)) { dev_err(&pdev->dev, "request alarm IRQ failed with %d, IRQ %d\n", diff --git a/drivers/rtc/rtc-spear.c b/drivers/rtc/rtc-spear.c index 19a28a671a8e..e38da0dc4187 100644 --- a/drivers/rtc/rtc-spear.c +++ b/drivers/rtc/rtc-spear.c @@ -77,9 +77,11 @@ #define STATUS_FAIL (LOST_WR_TIME | LOST_WR_DATE) struct spear_rtc_config { + struct rtc_device *rtc; struct clk *clk; spinlock_t lock; void __iomem *ioaddr; + unsigned int irq_wake; }; static inline void spear_rtc_clear_interrupt(struct spear_rtc_config *config) @@ -149,8 +151,7 @@ static void rtc_wait_not_busy(struct spear_rtc_config *config) 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); + struct spear_rtc_config *config = dev_id; unsigned long flags, events = 0; unsigned int irq_data; @@ -161,7 +162,7 @@ static irqreturn_t spear_rtc_irq(int irq, void *dev_id) if ((irq_data & RTC_INT_MASK)) { spear_rtc_clear_interrupt(config); events = RTC_IRQF | RTC_AF; - rtc_update_irq(rtc, 1, events); + rtc_update_irq(config->rtc, 1, events); return IRQ_HANDLED; } else return IRQ_NONE; @@ -203,9 +204,7 @@ static void bcd2tm(struct rtc_time *tm) */ 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); + struct spear_rtc_config *config = dev_get_drvdata(dev); unsigned int time, date; /* we don't report wday/yday/isdst ... */ @@ -234,9 +233,7 @@ static int spear_rtc_read_time(struct device *dev, struct rtc_time *tm) */ 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); + struct spear_rtc_config *config = dev_get_drvdata(dev); unsigned int time, date, err = 0; if (tm2bcd(tm) < 0) @@ -266,9 +263,7 @@ static int spear_rtc_set_time(struct device *dev, struct rtc_time *tm) */ 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); + struct spear_rtc_config *config = dev_get_drvdata(dev); unsigned int time, date; rtc_wait_not_busy(config); @@ -298,9 +293,7 @@ static int spear_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) */ 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); + struct spear_rtc_config *config = dev_get_drvdata(dev); unsigned int time, date, err = 0; if (tm2bcd(&alm->time) < 0) @@ -326,17 +319,42 @@ static int spear_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) return 0; } + +static int spear_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct spear_rtc_config *config = dev_get_drvdata(dev); + int ret = 0; + + spear_rtc_clear_interrupt(config); + + switch (enabled) { + case 0: + /* alarm off */ + spear_rtc_disable_interrupt(config); + break; + case 1: + /* alarm on */ + spear_rtc_enable_interrupt(config); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + 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, + .alarm_irq_enable = spear_alarm_irq_enable, }; 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; @@ -376,19 +394,17 @@ static int __devinit spear_rtc_probe(struct platform_device *pdev) } spin_lock_init(&config->lock); + platform_set_drvdata(pdev, config); - rtc = rtc_device_register(pdev->name, &pdev->dev, &spear_rtc_ops, - THIS_MODULE); - if (IS_ERR(rtc)) { + config->rtc = rtc_device_register(pdev->name, &pdev->dev, + &spear_rtc_ops, THIS_MODULE); + if (IS_ERR(config->rtc)) { dev_err(&pdev->dev, "can't register RTC device, err %ld\n", - PTR_ERR(rtc)); - status = PTR_ERR(rtc); + PTR_ERR(config->rtc)); + status = PTR_ERR(config->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) { @@ -397,7 +413,7 @@ static int __devinit spear_rtc_probe(struct platform_device *pdev) goto err_clear_platdata; } - status = request_irq(irq, spear_rtc_irq, 0, pdev->name, rtc); + status = request_irq(irq, spear_rtc_irq, 0, pdev->name, config); if (status) { dev_err(&pdev->dev, "Alarm interrupt IRQ%d already \ claimed\n", irq); @@ -411,8 +427,7 @@ static int __devinit spear_rtc_probe(struct platform_device *pdev) err_clear_platdata: platform_set_drvdata(pdev, NULL); - dev_set_drvdata(&rtc->dev, NULL); - rtc_device_unregister(rtc); + rtc_device_unregister(config->rtc); err_iounmap: iounmap(config->ioaddr); err_disable_clock: @@ -429,8 +444,7 @@ err_release_region: 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); + struct spear_rtc_config *config = platform_get_drvdata(pdev); int irq; struct resource *res; @@ -448,8 +462,7 @@ static int __devexit spear_rtc_remove(struct platform_device *pdev) 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); + rtc_device_unregister(config->rtc); return 0; } @@ -458,14 +471,14 @@ static int __devexit spear_rtc_remove(struct platform_device *pdev) 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); + struct spear_rtc_config *config = platform_get_drvdata(pdev); int irq; irq = platform_get_irq(pdev, 0); - if (device_may_wakeup(&pdev->dev)) - enable_irq_wake(irq); - else { + if (device_may_wakeup(&pdev->dev)) { + if (!enable_irq_wake(irq)) + config->irq_wake = 1; + } else { spear_rtc_disable_interrupt(config); clk_disable(config->clk); } @@ -475,15 +488,17 @@ static int spear_rtc_suspend(struct platform_device *pdev, pm_message_t state) 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); + struct spear_rtc_config *config = platform_get_drvdata(pdev); int irq; irq = platform_get_irq(pdev, 0); - if (device_may_wakeup(&pdev->dev)) - disable_irq_wake(irq); - else { + if (device_may_wakeup(&pdev->dev)) { + if (config->irq_wake) { + disable_irq_wake(irq); + config->irq_wake = 0; + } + } else { clk_enable(config->clk); spear_rtc_enable_interrupt(config); } @@ -498,8 +513,7 @@ static int spear_rtc_resume(struct platform_device *pdev) 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); + struct spear_rtc_config *config = platform_get_drvdata(pdev); spear_rtc_disable_interrupt(config); clk_disable(config->clk); diff --git a/drivers/rtc/rtc-stk17ta8.c b/drivers/rtc/rtc-stk17ta8.c index 7621116bd20d..279f5cfa691a 100644 --- a/drivers/rtc/rtc-stk17ta8.c +++ b/drivers/rtc/rtc-stk17ta8.c @@ -329,7 +329,7 @@ static int __devinit stk17ta8_rtc_probe(struct platform_device *pdev) writeb(0, ioaddr + RTC_INTERRUPTS); if (devm_request_irq(&pdev->dev, pdata->irq, stk17ta8_rtc_interrupt, - IRQF_DISABLED | IRQF_SHARED, + IRQF_SHARED, pdev->name, pdev) < 0) { dev_warn(&pdev->dev, "interrupt not available.\n"); pdata->irq = 0; diff --git a/drivers/rtc/rtc-twl.c b/drivers/rtc/rtc-twl.c index d43b4f6eb4e4..4c2c6df2a9ef 100644 --- a/drivers/rtc/rtc-twl.c +++ b/drivers/rtc/rtc-twl.c @@ -176,6 +176,10 @@ static int set_rtc_irq_bit(unsigned char bit) unsigned char val; int ret; + /* if the bit is set, return from here */ + if (rtc_irq_bits & bit) + return 0; + val = rtc_irq_bits | bit; val &= ~BIT_RTC_INTERRUPTS_REG_EVERY_M; ret = twl_rtc_write_u8(val, REG_RTC_INTERRUPTS_REG); @@ -193,6 +197,10 @@ static int mask_rtc_irq_bit(unsigned char bit) unsigned char val; int ret; + /* if the bit is clear, return from here */ + if (!(rtc_irq_bits & bit)) + return 0; + val = rtc_irq_bits & ~bit; ret = twl_rtc_write_u8(val, REG_RTC_INTERRUPTS_REG); if (ret == 0) @@ -357,7 +365,7 @@ out: static irqreturn_t twl_rtc_interrupt(int irq, void *rtc) { - unsigned long events = 0; + unsigned long events; int ret = IRQ_NONE; int res; u8 rd_reg; @@ -372,11 +380,11 @@ static irqreturn_t twl_rtc_interrupt(int irq, void *rtc) * by reading RTS_INTERRUPTS_REGISTER[IT_TIMER,IT_ALARM] */ if (rd_reg & BIT_RTC_STATUS_REG_ALARM_M) - events |= RTC_IRQF | RTC_AF; + events = RTC_IRQF | RTC_AF; else - events |= RTC_IRQF | RTC_UF; + events = RTC_IRQF | RTC_PF; - res = twl_rtc_write_u8(rd_reg | BIT_RTC_STATUS_REG_ALARM_M, + res = twl_rtc_write_u8(BIT_RTC_STATUS_REG_ALARM_M, REG_RTC_STATUS_REG); if (res) goto out; @@ -449,19 +457,11 @@ static int __devinit twl_rtc_probe(struct platform_device *pdev) REG_INT_MSK_STS_A); } - /* Check RTC module status, Enable if it is off */ - ret = twl_rtc_read_u8(&rd_reg, REG_RTC_CTRL_REG); + dev_info(&pdev->dev, "Enabling TWL-RTC\n"); + ret = twl_rtc_write_u8(BIT_RTC_CTRL_REG_STOP_RTC_M, REG_RTC_CTRL_REG); if (ret < 0) goto out1; - if (!(rd_reg & BIT_RTC_CTRL_REG_STOP_RTC_M)) { - dev_info(&pdev->dev, "Enabling TWL-RTC.\n"); - rd_reg = BIT_RTC_CTRL_REG_STOP_RTC_M; - ret = twl_rtc_write_u8(rd_reg, REG_RTC_CTRL_REG); - if (ret < 0) - goto out1; - } - /* init cached IRQ enable bits */ ret = twl_rtc_read_u8(&rtc_irq_bits, REG_RTC_INTERRUPTS_REG); if (ret < 0) diff --git a/drivers/rtc/rtc-tx4939.c b/drivers/rtc/rtc-tx4939.c index aac0ffed4345..a12bfac49d36 100644 --- a/drivers/rtc/rtc-tx4939.c +++ b/drivers/rtc/rtc-tx4939.c @@ -266,7 +266,7 @@ static int __init tx4939_rtc_probe(struct platform_device *pdev) spin_lock_init(&pdata->lock); tx4939_rtc_cmd(pdata->rtcreg, TX4939_RTCCTL_COMMAND_NOP); if (devm_request_irq(&pdev->dev, irq, tx4939_rtc_interrupt, - IRQF_DISABLED, pdev->name, &pdev->dev) < 0) + 0, pdev->name, &pdev->dev) < 0) return -EBUSY; rtc = rtc_device_register(pdev->name, &pdev->dev, &tx4939_rtc_ops, THIS_MODULE); diff --git a/drivers/rtc/rtc-vr41xx.c b/drivers/rtc/rtc-vr41xx.c index fcbfdda2993b..5f60a7c6a155 100644 --- a/drivers/rtc/rtc-vr41xx.c +++ b/drivers/rtc/rtc-vr41xx.c @@ -333,7 +333,7 @@ static int __devinit rtc_probe(struct platform_device *pdev) goto err_device_unregister; } - retval = request_irq(aie_irq, elapsedtime_interrupt, IRQF_DISABLED, + retval = request_irq(aie_irq, elapsedtime_interrupt, 0, "elapsed_time", pdev); if (retval < 0) goto err_device_unregister; @@ -342,7 +342,7 @@ static int __devinit rtc_probe(struct platform_device *pdev) if (pie_irq <= 0) goto err_free_irq; - retval = request_irq(pie_irq, rtclong1_interrupt, IRQF_DISABLED, + retval = request_irq(pie_irq, rtclong1_interrupt, 0, "rtclong1", pdev); if (retval < 0) goto err_free_irq; diff --git a/drivers/rtc/rtc-x1205.c b/drivers/rtc/rtc-x1205.c index 8c051d3179db..403b3d41d101 100644 --- a/drivers/rtc/rtc-x1205.c +++ b/drivers/rtc/rtc-x1205.c @@ -623,15 +623,7 @@ static struct i2c_driver x1205_driver = { .id_table = x1205_id, }; -static int __init x1205_init(void) -{ - return i2c_add_driver(&x1205_driver); -} - -static void __exit x1205_exit(void) -{ - i2c_del_driver(&x1205_driver); -} +module_i2c_driver(x1205_driver); MODULE_AUTHOR( "Karen Spearel <kas111 at gmail dot com>, " @@ -639,6 +631,3 @@ MODULE_AUTHOR( MODULE_DESCRIPTION("Xicor/Intersil X1205 RTC driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(x1205_init); -module_exit(x1205_exit); diff --git a/drivers/s390/char/sclp_cmd.c b/drivers/s390/char/sclp_cmd.c index 0b54a91f8dcd..168525a9c292 100644 --- a/drivers/s390/char/sclp_cmd.c +++ b/drivers/s390/char/sclp_cmd.c @@ -441,9 +441,8 @@ static int sclp_mem_notifier(struct notifier_block *nb, start = arg->start_pfn << PAGE_SHIFT; size = arg->nr_pages << PAGE_SHIFT; mutex_lock(&sclp_mem_mutex); - for (id = 0; id <= sclp_max_storage_id; id++) - if (!test_bit(id, sclp_storage_ids)) - sclp_attach_storage(id); + for_each_clear_bit(id, sclp_storage_ids, sclp_max_storage_id + 1) + sclp_attach_storage(id); switch (action) { case MEM_ONLINE: case MEM_GOING_OFFLINE: diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index 2a0dfcb0bc42..35c685c374e9 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -1458,7 +1458,6 @@ int qdio_establish(struct qdio_initialize *init_data) } qdio_setup_ssqd_info(irq_ptr); - DBF_EVENT("qib ac:%4x", irq_ptr->qib.ac); qdio_detect_hsicq(irq_ptr); diff --git a/drivers/s390/cio/qdio_setup.c b/drivers/s390/cio/qdio_setup.c index 452989a7ec13..ecf12f0aca7b 100644 --- a/drivers/s390/cio/qdio_setup.c +++ b/drivers/s390/cio/qdio_setup.c @@ -311,7 +311,8 @@ void qdio_setup_ssqd_info(struct qdio_irq *irq_ptr) check_and_setup_qebsm(irq_ptr, qdioac, irq_ptr->ssqd_desc.sch_token); process_ac_flags(irq_ptr, qdioac); - DBF_EVENT("qdioac:%4x", qdioac); + DBF_EVENT("ac 1:%2x 2:%4x", qdioac, irq_ptr->ssqd_desc.qdioac2); + DBF_EVENT("3:%4x qib:%4x", irq_ptr->ssqd_desc.qdioac3, irq_ptr->qib.ac); } void qdio_release_memory(struct qdio_irq *irq_ptr) diff --git a/drivers/scsi/arm/arxescsi.c b/drivers/scsi/arm/arxescsi.c index a750aa72b8ef..2a28b4ad1975 100644 --- a/drivers/scsi/arm/arxescsi.c +++ b/drivers/scsi/arm/arxescsi.c @@ -305,7 +305,7 @@ arxescsi_probe(struct expansion_card *ec, const struct ecard_id *id) info->base = base; info->info.scsi.io_base = base + 0x2000; - info->info.scsi.irq = NO_IRQ; + info->info.scsi.irq = 0; info->info.scsi.dma = NO_DMA; info->info.scsi.io_shift = 5; info->info.ifcfg.clockrate = 24; /* MHz */ diff --git a/drivers/scsi/arm/fas216.c b/drivers/scsi/arm/fas216.c index e85c40b6e19b..6206a666a8ec 100644 --- a/drivers/scsi/arm/fas216.c +++ b/drivers/scsi/arm/fas216.c @@ -2176,7 +2176,7 @@ static void fas216_done(FAS216_Info *info, unsigned int result) fn = (void (*)(FAS216_Info *, struct scsi_cmnd *, unsigned int))SCpnt->host_scribble; fn(info, SCpnt, result); - if (info->scsi.irq != NO_IRQ) { + if (info->scsi.irq) { spin_lock_irqsave(&info->host_lock, flags); if (info->scsi.phase == PHASE_IDLE) fas216_kick(info); @@ -2276,7 +2276,7 @@ static int fas216_noqueue_command_lck(struct scsi_cmnd *SCpnt, * We should only be using this if we don't have an interrupt. * Provide some "incentive" to use the queueing code. */ - BUG_ON(info->scsi.irq != NO_IRQ); + BUG_ON(info->scsi.irq); info->internal_done = 0; fas216_queue_command_lck(SCpnt, fas216_internal_done); diff --git a/drivers/scsi/arm/fas216.h b/drivers/scsi/arm/fas216.h index 84b7127c0121..df2e1b3ddfe2 100644 --- a/drivers/scsi/arm/fas216.h +++ b/drivers/scsi/arm/fas216.h @@ -12,10 +12,6 @@ #ifndef FAS216_H #define FAS216_H -#ifndef NO_IRQ -#define NO_IRQ 255 -#endif - #include <scsi/scsi_eh.h> #include "queue.h" diff --git a/drivers/scsi/bnx2fc/bnx2fc_constants.h b/drivers/scsi/bnx2fc/bnx2fc_constants.h index c12702bb16d6..dad9924abbbb 100644 --- a/drivers/scsi/bnx2fc/bnx2fc_constants.h +++ b/drivers/scsi/bnx2fc/bnx2fc_constants.h @@ -47,6 +47,7 @@ #define FCOE_KCQE_COMPLETION_STATUS_CTX_FREE_FAILURE (0x4) #define FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR (0x5) #define FCOE_KCQE_COMPLETION_STATUS_WRONG_HSI_VERSION (0x6) +#define FCOE_KCQE_COMPLETION_STATUS_PARITY_ERROR (0x81) /* CQE type */ #define FCOE_PENDING_CQE_TYPE 0 diff --git a/drivers/scsi/bnx2i/57xx_iscsi_constants.h b/drivers/scsi/bnx2i/57xx_iscsi_constants.h index 57515f1f1690..495a841645f9 100644 --- a/drivers/scsi/bnx2i/57xx_iscsi_constants.h +++ b/drivers/scsi/bnx2i/57xx_iscsi_constants.h @@ -122,6 +122,7 @@ #define ISCSI_KCQE_COMPLETION_STATUS_LOM_ISCSI_NOT_ENABLED (0x51) #define ISCSI_KCQE_COMPLETION_STATUS_CID_BUSY (0x80) +#define ISCSI_KCQE_COMPLETION_STATUS_PARITY_ERR (0x81) /* SQ/RQ/CQ DB structure sizes */ #define ISCSI_SQ_DB_SIZE (16) diff --git a/drivers/scsi/mpt2sas/mpt2sas_base.c b/drivers/scsi/mpt2sas/mpt2sas_base.c index 82fa6ce481f0..5e69f468535f 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_base.c +++ b/drivers/scsi/mpt2sas/mpt2sas_base.c @@ -132,7 +132,7 @@ static int mpt2sas_remove_dead_ioc_func(void *arg) pdev = ioc->pdev; if ((pdev == NULL)) return -1; - pci_remove_bus_device(pdev); + pci_stop_and_remove_bus_device(pdev); return 0; } diff --git a/drivers/sh/clk/cpg.c b/drivers/sh/clk/cpg.c index 92d314a73f69..91b6d52f74eb 100644 --- a/drivers/sh/clk/cpg.c +++ b/drivers/sh/clk/cpg.c @@ -26,7 +26,7 @@ static void sh_clk_mstp32_disable(struct clk *clk) clk->mapped_reg); } -static struct clk_ops sh_clk_mstp32_clk_ops = { +static struct sh_clk_ops sh_clk_mstp32_clk_ops = { .enable = sh_clk_mstp32_enable, .disable = sh_clk_mstp32_disable, .recalc = followparent_recalc, @@ -150,7 +150,7 @@ static void sh_clk_div6_disable(struct clk *clk) iowrite32(value, clk->mapped_reg); } -static struct clk_ops sh_clk_div6_clk_ops = { +static struct sh_clk_ops sh_clk_div6_clk_ops = { .recalc = sh_clk_div6_recalc, .round_rate = sh_clk_div_round_rate, .set_rate = sh_clk_div6_set_rate, @@ -158,7 +158,7 @@ static struct clk_ops sh_clk_div6_clk_ops = { .disable = sh_clk_div6_disable, }; -static struct clk_ops sh_clk_div6_reparent_clk_ops = { +static struct sh_clk_ops sh_clk_div6_reparent_clk_ops = { .recalc = sh_clk_div6_recalc, .round_rate = sh_clk_div_round_rate, .set_rate = sh_clk_div6_set_rate, @@ -200,7 +200,7 @@ static int __init sh_clk_init_parent(struct clk *clk) } static int __init sh_clk_div6_register_ops(struct clk *clks, int nr, - struct clk_ops *ops) + struct sh_clk_ops *ops) { struct clk *clkp; void *freq_table; @@ -317,13 +317,13 @@ static void sh_clk_div4_disable(struct clk *clk) iowrite32(ioread32(clk->mapped_reg) | (1 << 8), clk->mapped_reg); } -static struct clk_ops sh_clk_div4_clk_ops = { +static struct sh_clk_ops sh_clk_div4_clk_ops = { .recalc = sh_clk_div4_recalc, .set_rate = sh_clk_div4_set_rate, .round_rate = sh_clk_div_round_rate, }; -static struct clk_ops sh_clk_div4_enable_clk_ops = { +static struct sh_clk_ops sh_clk_div4_enable_clk_ops = { .recalc = sh_clk_div4_recalc, .set_rate = sh_clk_div4_set_rate, .round_rate = sh_clk_div_round_rate, @@ -331,7 +331,7 @@ static struct clk_ops sh_clk_div4_enable_clk_ops = { .disable = sh_clk_div4_disable, }; -static struct clk_ops sh_clk_div4_reparent_clk_ops = { +static struct sh_clk_ops sh_clk_div4_reparent_clk_ops = { .recalc = sh_clk_div4_recalc, .set_rate = sh_clk_div4_set_rate, .round_rate = sh_clk_div_round_rate, @@ -341,7 +341,7 @@ static struct clk_ops sh_clk_div4_reparent_clk_ops = { }; static int __init sh_clk_div4_register_ops(struct clk *clks, int nr, - struct clk_div4_table *table, struct clk_ops *ops) + struct clk_div4_table *table, struct sh_clk_ops *ops) { struct clk *clkp; void *freq_table; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 0b06e360628a..3ed748355b98 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -293,7 +293,7 @@ config SPI_RSPI config SPI_S3C24XX tristate "Samsung S3C24XX series SPI" - depends on ARCH_S3C2410 && EXPERIMENTAL + depends on ARCH_S3C24XX && EXPERIMENTAL select SPI_BITBANG help SPI driver for Samsung S3C24XX series ARM SoCs diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index 13448c832c44..e496f799b7a9 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -359,11 +359,6 @@ static int orion_spi_setup(struct spi_device *spi) orion_spi = spi_master_get_devdata(spi->master); - /* Fix ac timing if required. */ - if (orion_spi->spi_info->enable_clock_fix) - orion_spi_setbits(orion_spi, ORION_SPI_IF_CONFIG_REG, - (1 << 14)); - if ((spi->max_speed_hz == 0) || (spi->max_speed_hz > orion_spi->max_speed)) spi->max_speed_hz = orion_spi->max_speed; diff --git a/drivers/spi/spi-s3c24xx.c b/drivers/spi/spi-s3c24xx.c index fc064535f4fc..8ee7d790ce49 100644 --- a/drivers/spi/spi-s3c24xx.c +++ b/drivers/spi/spi-s3c24xx.c @@ -24,10 +24,10 @@ #include <linux/spi/spi.h> #include <linux/spi/spi_bitbang.h> +#include <linux/spi/s3c24xx.h> #include <linux/module.h> #include <plat/regs-spi.h> -#include <mach/spi.h> #include <plat/fiq.h> #include <asm/fiq.h> diff --git a/drivers/staging/iio/gyro/adis16060_core.c b/drivers/staging/iio/gyro/adis16060_core.c index c0ca7093e0ed..02cc23420b90 100644 --- a/drivers/staging/iio/gyro/adis16060_core.c +++ b/drivers/staging/iio/gyro/adis16060_core.c @@ -14,7 +14,6 @@ #include <linux/spi/spi.h> #include <linux/slab.h> #include <linux/sysfs.h> -#include <linux/module.h> #include "../iio.h" #include "../sysfs.h" diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 7e5caa39ed3f..4f4b7d6281a7 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -6,7 +6,7 @@ menuconfig STAGING_MEDIA don't have the "normal" Linux kernel quality level. Most of them don't follow properly the V4L, DVB and/or RC API's, so, they won't likely work fine with the existing applications. - That also means that, one fixed, their API's will change to match + That also means that, once fixed, their API's will change to match the existing ones. If you wish to work on these drivers, to help improve them, or diff --git a/drivers/staging/media/as102/as102_drv.c b/drivers/staging/media/as102/as102_drv.c index aae0505a36c4..ea4f992de235 100644 --- a/drivers/staging/media/as102/as102_drv.c +++ b/drivers/staging/media/as102/as102_drv.c @@ -27,7 +27,7 @@ #include <linux/uaccess.h> #include <linux/usb.h> -/* header file for Usb device driver*/ +/* header file for usb device driver*/ #include "as102_drv.h" #include "as102_fw.h" #include "dvbdev.h" diff --git a/drivers/staging/media/as102/as102_drv.h b/drivers/staging/media/as102/as102_drv.h index 957f0ed0d81a..b0e5a23bd532 100644 --- a/drivers/staging/media/as102/as102_drv.h +++ b/drivers/staging/media/as102/as102_drv.h @@ -76,7 +76,7 @@ struct as102_dev_t { struct as10x_bus_adapter_t bus_adap; struct list_head device_entry; struct kref kref; - unsigned long minor; + uint8_t elna_cfg; struct dvb_adapter dvb_adap; struct dvb_frontend dvb_fe; diff --git a/drivers/staging/media/as102/as102_fe.c b/drivers/staging/media/as102/as102_fe.c index bdc5a38cddf7..5917657b9d0f 100644 --- a/drivers/staging/media/as102/as102_fe.c +++ b/drivers/staging/media/as102/as102_fe.c @@ -265,7 +265,7 @@ static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) if (acquire) { if (elna_enable) - as10x_cmd_set_context(&dev->bus_adap, 1010, 0xC0); + as10x_cmd_set_context(&dev->bus_adap, CONTEXT_LNA, dev->elna_cfg); ret = as10x_cmd_turn_on(&dev->bus_adap); } else { @@ -337,7 +337,7 @@ int as102_dvb_register_fe(struct as102_dev_t *as102_dev, strncpy(dvb_fe->ops.info.name, as102_dev->name, sizeof(dvb_fe->ops.info.name)); - /* register dbvb frontend */ + /* register dvb frontend */ errno = dvb_register_frontend(dvb_adap, dvb_fe); if (errno == 0) dvb_fe->tuner_priv = as102_dev; @@ -349,7 +349,7 @@ static void as10x_fe_copy_tps_parameters(struct dtv_frontend_properties *fe_tps, struct as10x_tps *as10x_tps) { - /* extract consteallation */ + /* extract constellation */ switch (as10x_tps->modulation) { case CONST_QPSK: fe_tps->modulation = QPSK; diff --git a/drivers/staging/media/as102/as102_fw.h b/drivers/staging/media/as102/as102_fw.h index bd21f0554392..4bfc6849d95a 100644 --- a/drivers/staging/media/as102/as102_fw.h +++ b/drivers/staging/media/as102/as102_fw.h @@ -29,7 +29,7 @@ struct as10x_fw_pkt_t { union { unsigned char request[2]; unsigned char length[2]; - } u; + } __packed u; struct as10x_raw_fw_pkt raw; } __packed; diff --git a/drivers/staging/media/as102/as102_usb_drv.c b/drivers/staging/media/as102/as102_usb_drv.c index d775be0173ea..0f6bfe7eccba 100644 --- a/drivers/staging/media/as102/as102_usb_drv.c +++ b/drivers/staging/media/as102/as102_usb_drv.c @@ -57,6 +57,17 @@ static const char * const as102_device_names[] = { NULL /* Terminating entry */ }; +/* eLNA configuration: devices built on the reference design work best + with 0xA0, while custom designs seem to require 0xC0 */ +static uint8_t const as102_elna_cfg[] = { + 0xA0, + 0xC0, + 0xC0, + 0xA0, + 0xA0, + 0x00 /* Terminating entry */ +}; + struct usb_driver as102_usb_driver = { .name = DRIVER_FULL_NAME, .probe = as102_usb_probe, @@ -270,6 +281,8 @@ static int as102_alloc_usb_stream_buffer(struct as102_dev_t *dev) } urb->transfer_buffer = dev->stream + (i * AS102_USB_BUF_SIZE); + urb->transfer_dma = dev->dma_addr + (i * AS102_USB_BUF_SIZE); + urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; urb->transfer_buffer_length = AS102_USB_BUF_SIZE; dev->stream_urb[i] = urb; @@ -369,8 +382,10 @@ static int as102_usb_probe(struct usb_interface *intf, /* Assign the user-friendly device name */ for (i = 0; i < (sizeof(as102_usb_id_table) / sizeof(struct usb_device_id)); i++) { - if (id == &as102_usb_id_table[i]) + if (id == &as102_usb_id_table[i]) { as102_dev->name = as102_device_names[i]; + as102_dev->elna_cfg = as102_elna_cfg[i]; + } } if (as102_dev->name == NULL) diff --git a/drivers/staging/media/as102/as10x_cmd.h b/drivers/staging/media/as102/as10x_cmd.h index 4ea249e7adab..e21ec6c702a9 100644 --- a/drivers/staging/media/as102/as10x_cmd.h +++ b/drivers/staging/media/as102/as10x_cmd.h @@ -99,14 +99,14 @@ union as10x_turn_on { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_turn_off { @@ -114,14 +114,14 @@ union as10x_turn_off { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* error */ uint8_t err; - } rsp; + } __packed rsp; } __packed; union as10x_set_tune { @@ -131,14 +131,14 @@ union as10x_set_tune { uint16_t proc_id; /* tune params */ struct as10x_tune_args args; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* response error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_get_tune_status { @@ -146,7 +146,7 @@ union as10x_get_tune_status { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -155,7 +155,7 @@ union as10x_get_tune_status { uint8_t error; /* tune status */ struct as10x_tune_status sts; - } rsp; + } __packed rsp; } __packed; union as10x_get_tps { @@ -163,7 +163,7 @@ union as10x_get_tps { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -172,7 +172,7 @@ union as10x_get_tps { uint8_t error; /* tps details */ struct as10x_tps tps; - } rsp; + } __packed rsp; } __packed; union as10x_common { @@ -180,14 +180,14 @@ union as10x_common { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* response error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_add_pid_filter { @@ -201,7 +201,7 @@ union as10x_add_pid_filter { uint8_t stream_type; /* PID index in filter table */ uint8_t idx; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -210,7 +210,7 @@ union as10x_add_pid_filter { uint8_t error; /* Filter id */ uint8_t filter_id; - } rsp; + } __packed rsp; } __packed; union as10x_del_pid_filter { @@ -220,14 +220,14 @@ union as10x_del_pid_filter { uint16_t proc_id; /* PID to remove */ uint16_t pid; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* response error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_start_streaming { @@ -235,14 +235,14 @@ union as10x_start_streaming { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_stop_streaming { @@ -250,14 +250,14 @@ union as10x_stop_streaming { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_get_demod_stats { @@ -265,7 +265,7 @@ union as10x_get_demod_stats { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -274,7 +274,7 @@ union as10x_get_demod_stats { uint8_t error; /* demod stats */ struct as10x_demod_stats stats; - } rsp; + } __packed rsp; } __packed; union as10x_get_impulse_resp { @@ -282,7 +282,7 @@ union as10x_get_impulse_resp { struct { /* request identifier */ uint16_t proc_id; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -291,7 +291,7 @@ union as10x_get_impulse_resp { uint8_t error; /* impulse response ready */ uint8_t is_ready; - } rsp; + } __packed rsp; } __packed; union as10x_fw_context { @@ -305,7 +305,7 @@ union as10x_fw_context { uint16_t tag; /* context request type */ uint16_t type; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -316,7 +316,7 @@ union as10x_fw_context { uint16_t type; /* error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_set_register { @@ -328,14 +328,14 @@ union as10x_set_register { struct as10x_register_addr reg_addr; /* register content */ struct as10x_register_value reg_val; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; union as10x_get_register { @@ -345,7 +345,7 @@ union as10x_get_register { uint16_t proc_id; /* register description */ struct as10x_register_addr reg_addr; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -354,7 +354,7 @@ union as10x_get_register { uint8_t error; /* register content */ struct as10x_register_value reg_val; - } rsp; + } __packed rsp; } __packed; union as10x_cfg_change_mode { @@ -364,14 +364,14 @@ union as10x_cfg_change_mode { uint16_t proc_id; /* mode */ uint8_t mode; - } req; + } __packed req; /* response */ struct { /* response identifier */ uint16_t proc_id; /* error */ uint8_t error; - } rsp; + } __packed rsp; } __packed; struct as10x_cmd_header_t { @@ -394,7 +394,7 @@ union as10x_dump_memory { struct as10x_register_addr reg_addr; /* nb blocks to read */ uint16_t num_blocks; - } req; + } __packed req; /* response */ struct { /* response identifier */ @@ -408,8 +408,8 @@ union as10x_dump_memory { uint8_t data8[DUMP_BLOCK_SIZE]; uint16_t data16[DUMP_BLOCK_SIZE / sizeof(uint16_t)]; uint32_t data32[DUMP_BLOCK_SIZE / sizeof(uint32_t)]; - } u; - } rsp; + } __packed u; + } __packed rsp; } __packed; union as10x_dumplog_memory { @@ -418,7 +418,7 @@ union as10x_dumplog_memory { uint16_t proc_id; /* dump memory type request */ uint8_t dump_req; - } req; + } __packed req; struct { /* request identifier */ uint16_t proc_id; @@ -428,7 +428,7 @@ union as10x_dumplog_memory { uint8_t dump_rsp; /* dump data */ uint8_t data[DUMP_BLOCK_SIZE]; - } rsp; + } __packed rsp; } __packed; union as10x_raw_data { @@ -437,14 +437,14 @@ union as10x_raw_data { uint16_t proc_id; uint8_t data[64 - sizeof(struct as10x_cmd_header_t) - 2 /* proc_id */]; - } req; + } __packed req; /* response */ struct { uint16_t proc_id; uint8_t error; uint8_t data[64 - sizeof(struct as10x_cmd_header_t) - 2 /* proc_id */ - 1 /* rc */]; - } rsp; + } __packed rsp; } __packed; struct as10x_cmd_t { @@ -469,7 +469,7 @@ struct as10x_cmd_t { union as10x_dump_memory dump_memory; union as10x_dumplog_memory dumplog_memory; union as10x_raw_data raw_data; - } body; + } __packed body; } __packed; struct as10x_token_cmd_t { diff --git a/drivers/staging/media/as102/as10x_types.h b/drivers/staging/media/as102/as10x_types.h index fde8140ae88b..af26e057d9a2 100644 --- a/drivers/staging/media/as102/as10x_types.h +++ b/drivers/staging/media/as102/as10x_types.h @@ -181,7 +181,7 @@ struct as10x_register_value { uint8_t value8; /* 8 bit value */ uint16_t value16; /* 16 bit value */ uint32_t value32; /* 32 bit value */ - } u; + } __packed u; } __packed; struct as10x_register_addr { diff --git a/drivers/staging/media/easycap/easycap_main.c b/drivers/staging/media/easycap/easycap_main.c index 3d439b790cc6..d0fe34afc2e5 100644 --- a/drivers/staging/media/easycap/easycap_main.c +++ b/drivers/staging/media/easycap/easycap_main.c @@ -2849,13 +2849,11 @@ static const struct v4l2_file_operations v4l2_fops = { .poll = easycap_poll, .mmap = easycap_mmap, }; -/*****************************************************************************/ -/*---------------------------------------------------------------------------*/ + /* - * WHEN THE EasyCAP IS PHYSICALLY PLUGGED IN, THIS FUNCTION IS CALLED THREE - * TIMES, ONCE FOR EACH OF THE THREE INTERFACES. BEWARE. + * When the device is plugged, this function is called three times, + * one for each interface. */ -/*---------------------------------------------------------------------------*/ static int easycap_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) { @@ -2884,7 +2882,6 @@ static int easycap_usb_probe(struct usb_interface *intf, usbdev = interface_to_usbdev(intf); -/*---------------------------------------------------------------------------*/ alt = usb_altnum_to_altsetting(intf, 0); if (!alt) { SAY("ERROR: usb_host_interface not found\n"); @@ -2896,11 +2893,8 @@ static int easycap_usb_probe(struct usb_interface *intf, SAY("ERROR: intf_descriptor is NULL\n"); return -EFAULT; } -/*---------------------------------------------------------------------------*/ -/* - * GET PROPERTIES OF PROBED INTERFACE - */ -/*---------------------------------------------------------------------------*/ + + /* Get properties of probed interface */ bInterfaceNumber = interface->bInterfaceNumber; bInterfaceClass = interface->bInterfaceClass; bInterfaceSubClass = interface->bInterfaceSubClass; @@ -2912,28 +2906,23 @@ static int easycap_usb_probe(struct usb_interface *intf, (long int)(intf->cur_altsetting - intf->altsetting)); JOT(4, "intf[%i]: bInterfaceClass=0x%02X bInterfaceSubClass=0x%02X\n", bInterfaceNumber, bInterfaceClass, bInterfaceSubClass); -/*---------------------------------------------------------------------------*/ -/* - * A NEW struct easycap IS ALWAYS ALLOCATED WHEN INTERFACE 0 IS PROBED. - * IT IS NOT POSSIBLE HERE TO FREE ANY EXISTING struct easycap. THIS - * SHOULD HAVE BEEN DONE BY easycap_delete() WHEN THE EasyCAP WAS - * PHYSICALLY UNPLUGGED. - * - * THE POINTER peasycap TO THE struct easycap IS REMEMBERED WHEN - * INTERFACES 1 AND 2 ARE PROBED. -*/ -/*---------------------------------------------------------------------------*/ + + /* + * A new struct easycap is always allocated when interface 0 is probed. + * It is not possible here to free any existing struct easycap. + * This should have been done by easycap_delete() when the device was + * physically unplugged. + * The allocated struct easycap is saved for later usage when + * interfaces 1 and 2 are probed. + */ if (0 == bInterfaceNumber) { peasycap = kzalloc(sizeof(struct easycap), GFP_KERNEL); if (!peasycap) { SAY("ERROR: Could not allocate peasycap\n"); return -ENOMEM; } -/*---------------------------------------------------------------------------*/ -/* - * PERFORM URGENT INTIALIZATIONS ... -*/ -/*---------------------------------------------------------------------------*/ + + /* Perform urgent initializations */ peasycap->minor = -1; kref_init(&peasycap->kref); JOM(8, "intf[%i]: after kref_init(..._video) " @@ -2976,11 +2965,7 @@ static int easycap_usb_probe(struct usb_interface *intf, peasycap->allocation_video_struct = sizeof(struct easycap); -/*---------------------------------------------------------------------------*/ -/* - * ... AND FURTHER INITIALIZE THE STRUCTURE -*/ -/*---------------------------------------------------------------------------*/ + /* and further initialize the structure */ peasycap->pusb_device = usbdev; peasycap->pusb_interface = intf; @@ -3002,11 +2987,7 @@ static int easycap_usb_probe(struct usb_interface *intf, peasycap->frame_buffer_many = FRAME_BUFFER_MANY; -/*---------------------------------------------------------------------------*/ -/* - * DYNAMICALLY FILL IN THE AVAILABLE FORMATS ... - */ -/*---------------------------------------------------------------------------*/ + /* Dynamically fill in the available formats */ rc = easycap_video_fillin_formats(); if (0 > rc) { SAM("ERROR: fillin_formats() rc = %i\n", rc); @@ -3014,10 +2995,8 @@ static int easycap_usb_probe(struct usb_interface *intf, } JOM(4, "%i formats available\n", rc); - /* ... AND POPULATE easycap.inputset[] */ - + /* Populate easycap.inputset[] */ inputset = peasycap->inputset; - fmtidx = peasycap->ntsc ? NTSC_M : PAL_BGHIN; m = 0; mask = 0; @@ -3030,7 +3009,6 @@ static int easycap_usb_probe(struct usb_interface *intf, mask = easycap_standard[i].mask; } } - if (1 != m) { SAM("ERROR: " "inputset->standard_offset unpopulated, %i=m\n", m); @@ -3089,14 +3067,13 @@ static int easycap_usb_probe(struct usb_interface *intf, JOM(4, "populated inputset[]\n"); JOM(4, "finished initialization\n"); } else { -/*---------------------------------------------------------------------------*/ -/* - * FIXME - * - * IDENTIFY THE APPROPRIATE POINTER peasycap FOR INTERFACES 1 AND 2. - * THE ADDRESS OF peasycap->pusb_device IS RELUCTANTLY USED FOR THIS PURPOSE. - */ -/*---------------------------------------------------------------------------*/ + + /* + * FIXME: Identify the appropriate pointer + * peasycap for interfaces 1 and 2. + * The address of peasycap->pusb_device + * is reluctantly used for this purpose. + */ for (ndong = 0; ndong < DONGLE_MANY; ndong++) { if (usbdev == easycapdc60_dongle[ndong].peasycap-> pusb_device) { @@ -3117,7 +3094,7 @@ static int easycap_usb_probe(struct usb_interface *intf, return -ENODEV; } } -/*---------------------------------------------------------------------------*/ + if ((USB_CLASS_VIDEO == bInterfaceClass) || (USB_CLASS_VENDOR_SPEC == bInterfaceClass)) { if (-1 == peasycap->video_interface) { @@ -3149,14 +3126,12 @@ static int easycap_usb_probe(struct usb_interface *intf, } } } -/*---------------------------------------------------------------------------*/ -/* - * INVESTIGATE ALL ALTSETTINGS. - * DONE IN DETAIL BECAUSE USB DEVICE 05e1:0408 HAS DISPARATE INCARNATIONS. - */ -/*---------------------------------------------------------------------------*/ - isokalt = 0; + /* + * Investigate all altsettings. This is done in detail + * because USB device 05e1:0408 has disparate incarnations. + */ + isokalt = 0; for (i = 0; i < intf->num_altsetting; i++) { alt = usb_altnum_to_altsetting(intf, i); if (!alt) { @@ -3172,7 +3147,6 @@ static int easycap_usb_probe(struct usb_interface *intf, if (0 == interface->bNumEndpoints) JOM(4, "intf[%i]alt[%i] has no endpoints\n", bInterfaceNumber, i); -/*---------------------------------------------------------------------------*/ for (j = 0; j < interface->bNumEndpoints; j++) { ep = &alt->endpoint[j].desc; if (!ep) { @@ -3312,19 +3286,12 @@ static int easycap_usb_probe(struct usb_interface *intf, } } } -/*---------------------------------------------------------------------------*/ -/* - * PERFORM INITIALIZATION OF THE PROBED INTERFACE - */ -/*---------------------------------------------------------------------------*/ + + /* Perform initialization of the probed interface */ JOM(4, "initialization begins for interface %i\n", interface->bInterfaceNumber); switch (bInterfaceNumber) { -/*---------------------------------------------------------------------------*/ -/* - * INTERFACE 0 IS THE VIDEO INTERFACE - */ -/*---------------------------------------------------------------------------*/ + /* 0: Video interface */ case 0: { if (!peasycap) { SAM("MISTAKE: peasycap is NULL\n"); @@ -3337,11 +3304,8 @@ static int easycap_usb_probe(struct usb_interface *intf, peasycap->video_altsetting_on = okalt[isokalt - 1]; JOM(4, "%i=video_altsetting_on <====\n", peasycap->video_altsetting_on); -/*---------------------------------------------------------------------------*/ -/* - * DECIDE THE VIDEO STREAMING PARAMETERS - */ -/*---------------------------------------------------------------------------*/ + + /* Decide video streaming parameters */ peasycap->video_endpointnumber = okepn[isokalt - 1]; JOM(4, "%i=video_endpointnumber\n", peasycap->video_endpointnumber); maxpacketsize = okmps[isokalt - 1]; @@ -3373,7 +3337,6 @@ static int easycap_usb_probe(struct usb_interface *intf, SAM("MISTAKE: peasycap->video_isoc_buffer_size too big\n"); return -EFAULT; } -/*---------------------------------------------------------------------------*/ if (-1 == peasycap->video_interface) { SAM("MISTAKE: video_interface is unset\n"); return -EFAULT; @@ -3398,14 +3361,13 @@ static int easycap_usb_probe(struct usb_interface *intf, SAM("MISTAKE: video_isoc_buffer_size is unset\n"); return -EFAULT; } -/*---------------------------------------------------------------------------*/ -/* - * ALLOCATE MEMORY FOR VIDEO BUFFERS. LISTS MUST BE INITIALIZED FIRST. - */ -/*---------------------------------------------------------------------------*/ + + /* + * Allocate memory for video buffers. + * Lists must be initialized first. + */ INIT_LIST_HEAD(&(peasycap->urb_video_head)); peasycap->purb_video_head = &(peasycap->urb_video_head); -/*---------------------------------------------------------------------------*/ JOM(4, "allocating %i frame buffers of size %li\n", FRAME_BUFFER_MANY, (long int)FRAME_BUFFER_SIZE); JOM(4, ".... each scattered over %li pages\n", @@ -3436,7 +3398,6 @@ static int easycap_usb_probe(struct usb_interface *intf, peasycap->frame_read = 0; JOM(4, "allocation of frame buffers done: %i pages\n", k * m); -/*---------------------------------------------------------------------------*/ JOM(4, "allocating %i field buffers of size %li\n", FIELD_BUFFER_MANY, (long int)FIELD_BUFFER_SIZE); JOM(4, ".... each scattered over %li pages\n", @@ -3468,7 +3429,6 @@ static int easycap_usb_probe(struct usb_interface *intf, peasycap->field_read = 0; JOM(4, "allocation of field buffers done: %i pages\n", k * m); -/*---------------------------------------------------------------------------*/ JOM(4, "allocating %i isoc video buffers of size %i\n", VIDEO_ISOC_BUFFER_MANY, peasycap->video_isoc_buffer_size); @@ -3492,11 +3452,8 @@ static int easycap_usb_probe(struct usb_interface *intf, } JOM(4, "allocation of isoc video buffers done: %i pages\n", k * (0x01 << VIDEO_ISOC_ORDER)); -/*---------------------------------------------------------------------------*/ -/* - * ALLOCATE AND INITIALIZE MULTIPLE struct urb ... - */ -/*---------------------------------------------------------------------------*/ + + /* Allocate and initialize multiple struct usb */ JOM(4, "allocating %i struct urb.\n", VIDEO_ISOC_BUFFER_MANY); JOM(4, "using %i=peasycap->video_isoc_framesperdesc\n", peasycap->video_isoc_framesperdesc); @@ -3515,7 +3472,6 @@ static int easycap_usb_probe(struct usb_interface *intf, } peasycap->allocation_video_urb += 1; -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL); if (!pdata_urb) { SAM("ERROR: Could not allocate struct data_urb.\n"); @@ -3530,11 +3486,8 @@ static int easycap_usb_probe(struct usb_interface *intf, pdata_urb->length = 0; list_add_tail(&(pdata_urb->list_head), peasycap->purb_video_head); -/*---------------------------------------------------------------------------*/ -/* - * ... AND INITIALIZE THEM - */ -/*---------------------------------------------------------------------------*/ + + /* Initialize allocated urbs */ if (!k) { JOM(4, "initializing video urbs thus:\n"); JOM(4, " purb->interval = 1;\n"); @@ -3582,20 +3535,16 @@ static int easycap_usb_probe(struct usb_interface *intf, } } JOM(4, "allocation of %i struct urb done.\n", k); -/*--------------------------------------------------------------------------*/ -/* - * SAVE POINTER peasycap IN THIS INTERFACE. - */ -/*--------------------------------------------------------------------------*/ + + /* Save pointer peasycap in this interface */ usb_set_intfdata(intf, peasycap); -/*---------------------------------------------------------------------------*/ -/* - * IT IS ESSENTIAL TO INITIALIZE THE HARDWARE BEFORE, RATHER THAN AFTER, - * THE DEVICE IS REGISTERED, BECAUSE SOME VERSIONS OF THE videodev MODULE - * CALL easycap_open() IMMEDIATELY AFTER REGISTRATION, CAUSING A CLASH. - * BEWARE. -*/ -/*---------------------------------------------------------------------------*/ + + /* + * It is essential to initialize the hardware before, + * rather than after, the device is registered, + * because some udev rules triggers easycap_open() + * immediately after registration, causing a clash. + */ peasycap->ntsc = easycap_ntsc; JOM(8, "defaulting initially to %s\n", easycap_ntsc ? "NTSC" : "PAL"); @@ -3604,27 +3553,20 @@ static int easycap_usb_probe(struct usb_interface *intf, SAM("ERROR: reset() rc = %i\n", rc); return -EFAULT; } -/*--------------------------------------------------------------------------*/ -/* - * THE VIDEO DEVICE CAN BE REGISTERED NOW, AS IT IS READY. - */ -/*--------------------------------------------------------------------------*/ + + /* The video device can now be registered */ if (v4l2_device_register(&intf->dev, &peasycap->v4l2_device)) { SAM("v4l2_device_register() failed\n"); return -ENODEV; } JOM(4, "registered device instance: %s\n", peasycap->v4l2_device.name); -/*---------------------------------------------------------------------------*/ -/* - * FIXME - * - * - * THIS IS BELIEVED TO BE HARMLESS, BUT MAY WELL BE UNNECESSARY OR WRONG: -*/ -/*---------------------------------------------------------------------------*/ + + /* + * FIXME: This is believed to be harmless, + * but may well be unnecessary or wrong. + */ peasycap->video_device.v4l2_dev = NULL; -/*---------------------------------------------------------------------------*/ strcpy(&peasycap->video_device.name[0], "easycapdc60"); @@ -3648,28 +3590,19 @@ static int easycap_usb_probe(struct usb_interface *intf, break; } -/*--------------------------------------------------------------------------*/ -/* - * INTERFACE 1 IS THE AUDIO CONTROL INTERFACE - * INTERFACE 2 IS THE AUDIO STREAMING INTERFACE - */ -/*--------------------------------------------------------------------------*/ + /* 1: Audio control */ case 1: { if (!peasycap) { SAM("MISTAKE: peasycap is NULL\n"); return -EFAULT; } -/*--------------------------------------------------------------------------*/ -/* - * SAVE POINTER peasycap IN INTERFACE 1 - */ -/*--------------------------------------------------------------------------*/ + /* Save pointer peasycap in this interface */ usb_set_intfdata(intf, peasycap); JOM(4, "no initialization required for interface %i\n", interface->bInterfaceNumber); break; } -/*--------------------------------------------------------------------------*/ + /* 2: Audio streaming */ case 2: { if (!peasycap) { SAM("MISTAKE: peasycap is NULL\n"); @@ -3769,15 +3702,14 @@ static int easycap_usb_probe(struct usb_interface *intf, SAM("MISTAKE: audio_isoc_buffer_size is unset\n"); return -EFAULT; } -/*---------------------------------------------------------------------------*/ -/* - * ALLOCATE MEMORY FOR AUDIO BUFFERS. LISTS MUST BE INITIALIZED FIRST. - */ -/*---------------------------------------------------------------------------*/ + + /* + * Allocate memory for audio buffers. + * Lists must be initialized first. + */ INIT_LIST_HEAD(&(peasycap->urb_audio_head)); peasycap->purb_audio_head = &(peasycap->urb_audio_head); -/*---------------------------------------------------------------------------*/ JOM(4, "allocating %i isoc audio buffers of size %i\n", AUDIO_ISOC_BUFFER_MANY, peasycap->audio_isoc_buffer_size); @@ -3800,11 +3732,8 @@ static int easycap_usb_probe(struct usb_interface *intf, peasycap->audio_isoc_buffer[k].kount = k; } JOM(4, "allocation of isoc audio buffers done.\n"); -/*---------------------------------------------------------------------------*/ -/* - * ALLOCATE AND INITIALIZE MULTIPLE struct urb ... - */ -/*---------------------------------------------------------------------------*/ + + /* Allocate and initialize urbs */ JOM(4, "allocating %i struct urb.\n", AUDIO_ISOC_BUFFER_MANY); JOM(4, "using %i=peasycap->audio_isoc_framesperdesc\n", peasycap->audio_isoc_framesperdesc); @@ -3822,7 +3751,6 @@ static int easycap_usb_probe(struct usb_interface *intf, return -ENOMEM; } peasycap->allocation_audio_urb += 1 ; -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL); if (!pdata_urb) { usb_free_urb(purb); @@ -3837,11 +3765,7 @@ static int easycap_usb_probe(struct usb_interface *intf, pdata_urb->length = 0; list_add_tail(&(pdata_urb->list_head), peasycap->purb_audio_head); -/*---------------------------------------------------------------------------*/ -/* - * ... AND INITIALIZE THEM - */ -/*---------------------------------------------------------------------------*/ + if (!k) { JOM(4, "initializing audio urbs thus:\n"); JOM(4, " purb->interval = 1;\n"); @@ -3889,17 +3813,11 @@ static int easycap_usb_probe(struct usb_interface *intf, } } JOM(4, "allocation of %i struct urb done.\n", k); -/*---------------------------------------------------------------------------*/ -/* - * SAVE POINTER peasycap IN THIS INTERFACE. - */ -/*---------------------------------------------------------------------------*/ + + /* Save pointer peasycap in this interface */ usb_set_intfdata(intf, peasycap); -/*---------------------------------------------------------------------------*/ -/* - * THE AUDIO DEVICE CAN BE REGISTERED NOW, AS IT IS READY. - */ -/*---------------------------------------------------------------------------*/ + + /* The audio device can now be registered */ JOM(4, "initializing ALSA card\n"); rc = easycap_alsa_probe(peasycap); @@ -3915,11 +3833,7 @@ static int easycap_usb_probe(struct usb_interface *intf, peasycap->registered_audio++; break; } -/*---------------------------------------------------------------------------*/ -/* - * INTERFACES OTHER THAN 0, 1 AND 2 ARE UNEXPECTED - */ -/*---------------------------------------------------------------------------*/ + /* Interfaces other than 0,1,2 are unexpected */ default: JOM(4, "ERROR: unexpected interface %i\n", bInterfaceNumber); return -EINVAL; diff --git a/drivers/staging/media/go7007/go7007-v4l2.c b/drivers/staging/media/go7007/go7007-v4l2.c index 2b27d8da70a2..f91658670e34 100644 --- a/drivers/staging/media/go7007/go7007-v4l2.c +++ b/drivers/staging/media/go7007/go7007-v4l2.c @@ -1050,15 +1050,15 @@ static int vidioc_s_parm(struct file *filp, void *priv, return 0; } -/* VIDIOC_ENUMSTD on go7007 were used for enumberating the supported fps and +/* VIDIOC_ENUMSTD on go7007 were used for enumerating the supported fps and its resolution, when the device is not connected to TV. - This were an API abuse, probably used by the lack of specific IOCTL's to - enumberate it, by the time the driver were written. + This is were an API abuse, probably used by the lack of specific IOCTL's to + enumerate it, by the time the driver was written. However, since kernel 2.6.19, two new ioctls (VIDIOC_ENUM_FRAMEINTERVALS and VIDIOC_ENUM_FRAMESIZES) were added for this purpose. - The two functions bellow implements the newer ioctls + The two functions below implement the newer ioctls */ static int vidioc_enum_framesizes(struct file *filp, void *priv, struct v4l2_frmsizeenum *fsize) diff --git a/drivers/staging/media/go7007/s2250-board.c b/drivers/staging/media/go7007/s2250-board.c index e7736a915530..014d38410c99 100644 --- a/drivers/staging/media/go7007/s2250-board.c +++ b/drivers/staging/media/go7007/s2250-board.c @@ -192,6 +192,7 @@ static int write_reg_fp(struct i2c_client *client, u16 addr, u16 val) { struct go7007 *go = i2c_get_adapdata(client->adapter); struct go7007_usb *usb; + int rc; u8 *buf; struct s2250 *dec = i2c_get_clientdata(client); @@ -216,12 +217,13 @@ static int write_reg_fp(struct i2c_client *client, u16 addr, u16 val) kfree(buf); return -EINTR; } - if (go7007_usb_vendor_request(go, 0x57, addr, val, buf, 16, 1) < 0) { + rc = go7007_usb_vendor_request(go, 0x57, addr, val, buf, 16, 1); + mutex_unlock(&usb->i2c_lock); + if (rc < 0) { kfree(buf); - return -EFAULT; + return rc; } - mutex_unlock(&usb->i2c_lock); if (buf[0] == 0) { unsigned int subaddr, val_read; @@ -254,6 +256,7 @@ static int read_reg_fp(struct i2c_client *client, u16 addr, u16 *val) { struct go7007 *go = i2c_get_adapdata(client->adapter); struct go7007_usb *usb; + int rc; u8 *buf; if (go == NULL) @@ -276,11 +279,12 @@ static int read_reg_fp(struct i2c_client *client, u16 addr, u16 *val) kfree(buf); return -EINTR; } - if (go7007_usb_vendor_request(go, 0x58, addr, 0, buf, 16, 1) < 0) { + rc = go7007_usb_vendor_request(go, 0x58, addr, 0, buf, 16, 1); + mutex_unlock(&usb->i2c_lock); + if (rc < 0) { kfree(buf); - return -EFAULT; + return rc; } - mutex_unlock(&usb->i2c_lock); *val = (buf[0] << 8) | buf[1]; kfree(buf); diff --git a/drivers/staging/media/lirc/lirc_serial.c b/drivers/staging/media/lirc/lirc_serial.c index 8dd8897ad860..97352cf6bd98 100644 --- a/drivers/staging/media/lirc/lirc_serial.c +++ b/drivers/staging/media/lirc/lirc_serial.c @@ -1282,7 +1282,7 @@ MODULE_PARM_DESC(iommap, "physical base for memory mapped I/O" /* * some architectures (e.g. intel xscale) align the 8bit serial registers * on 32bit word boundaries. - * See linux-kernel/serial/8250.c serial_in()/out() + * See linux-kernel/drivers/tty/serial/8250/8250.c serial_in()/out() */ module_param(ioshift, int, S_IRUGO); MODULE_PARM_DESC(ioshift, "shift I/O register offset (0 = no shift)"); diff --git a/drivers/staging/media/solo6x10/Kconfig b/drivers/staging/media/solo6x10/Kconfig index 03dcac4ea4d0..63352de5eabf 100644 --- a/drivers/staging/media/solo6x10/Kconfig +++ b/drivers/staging/media/solo6x10/Kconfig @@ -5,4 +5,4 @@ config SOLO6X10 select SND_PCM ---help--- This driver supports the Softlogic based MPEG-4 and h.264 codec - codec cards. + cards. diff --git a/drivers/staging/media/solo6x10/core.c b/drivers/staging/media/solo6x10/core.c index f974f6412ad7..d2fd842e37cf 100644 --- a/drivers/staging/media/solo6x10/core.c +++ b/drivers/staging/media/solo6x10/core.c @@ -195,28 +195,28 @@ static int __devinit solo_pci_probe(struct pci_dev *pdev, SOLO6010_SYS_CFG_OUTDIV(3); solo_reg_write(solo_dev, SOLO_SYS_CFG, reg); - if (solo_dev->flags & FLAGS_6110) { - u32 sys_clock_MHz = SOLO_CLOCK_MHZ; - u32 pll_DIVQ; - u32 pll_DIVF; - - if (sys_clock_MHz < 125) { - pll_DIVQ = 3; - pll_DIVF = (sys_clock_MHz * 4) / 3; - } else { - pll_DIVQ = 2; - pll_DIVF = (sys_clock_MHz * 2) / 3; - } - - solo_reg_write(solo_dev, SOLO6110_PLL_CONFIG, + if (solo_dev->flags & FLAGS_6110) { + u32 sys_clock_MHz = SOLO_CLOCK_MHZ; + u32 pll_DIVQ; + u32 pll_DIVF; + + if (sys_clock_MHz < 125) { + pll_DIVQ = 3; + pll_DIVF = (sys_clock_MHz * 4) / 3; + } else { + pll_DIVQ = 2; + pll_DIVF = (sys_clock_MHz * 2) / 3; + } + + solo_reg_write(solo_dev, SOLO6110_PLL_CONFIG, SOLO6110_PLL_RANGE_5_10MHZ | SOLO6110_PLL_DIVR(9) | SOLO6110_PLL_DIVQ_EXP(pll_DIVQ) | SOLO6110_PLL_DIVF(pll_DIVF) | SOLO6110_PLL_FSEN); - mdelay(1); // PLL Locking time (1ms) + mdelay(1); /* PLL Locking time (1ms) */ solo_reg_write(solo_dev, SOLO_DMA_CTRL1, 3 << 8); /* ? */ - } else + } else solo_reg_write(solo_dev, SOLO_DMA_CTRL1, 1 << 8); /* ? */ solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM, SOLO_CLOCK_MHZ - 1); diff --git a/drivers/staging/rtl8187se/r8180_core.c b/drivers/staging/rtl8187se/r8180_core.c index e4ade550cfe5..4fe52f6b0034 100644 --- a/drivers/staging/rtl8187se/r8180_core.c +++ b/drivers/staging/rtl8187se/r8180_core.c @@ -4159,7 +4159,7 @@ void GPIOChangeRFWorkItemCallBack(struct work_struct *work) argv[0] = RadioPowerPath; argv[2] = NULL; - call_usermodehelper(RadioPowerPath, argv, envp, 1); + call_usermodehelper(RadioPowerPath, argv, envp, UMH_WAIT_PROC); } } diff --git a/drivers/staging/rtl8192e/rtl8192e/rtl_dm.c b/drivers/staging/rtl8192e/rtl8192e/rtl_dm.c index a7fa9aad6f2d..f026b7171f62 100644 --- a/drivers/staging/rtl8192e/rtl8192e/rtl_dm.c +++ b/drivers/staging/rtl8192e/rtl8192e/rtl_dm.c @@ -208,7 +208,7 @@ static void dm_check_ac_dc_power(struct net_device *dev) if (priv->rtllib->state != RTLLIB_LINKED) return; - call_usermodehelper(ac_dc_check_script_path, argv, envp, 1); + call_usermodehelper(ac_dc_check_script_path, argv, envp, UMH_WAIT_PROC); return; }; @@ -2296,7 +2296,7 @@ void dm_CheckRfCtrlGPIO(void *data) argv[0] = RadioPowerPath; argv[2] = NULL; - call_usermodehelper(RadioPowerPath, argv, envp, 1); + call_usermodehelper(RadioPowerPath, argv, envp, UMH_WAIT_PROC); } } diff --git a/drivers/staging/ste_rmi4/Makefile b/drivers/staging/ste_rmi4/Makefile index 176f46900571..e4c03351420f 100644 --- a/drivers/staging/ste_rmi4/Makefile +++ b/drivers/staging/ste_rmi4/Makefile @@ -2,4 +2,4 @@ # Makefile for the RMI4 touchscreen driver. # obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += synaptics_i2c_rmi4.o -obj-$(CONFIG_MACH_U8500) += board-mop500-u8500uib-rmi4.o +obj-$(CONFIG_MACH_MOP500) += board-mop500-u8500uib-rmi4.o diff --git a/drivers/staging/telephony/ixj.c b/drivers/staging/telephony/ixj.c index d5f923bcdffe..f96027921f60 100644 --- a/drivers/staging/telephony/ixj.c +++ b/drivers/staging/telephony/ixj.c @@ -5927,7 +5927,8 @@ static void add_caps(IXJ *j) j->caplist[j->caps].cap = PHONE_VENDOR_QUICKNET; strcpy(j->caplist[j->caps].desc, "Quicknet Technologies, Inc. (www.quicknet.net)"); j->caplist[j->caps].captype = vendor; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; j->caplist[j->caps].captype = device; switch (j->cardtype) { case QTI_PHONEJACK: @@ -5947,11 +5948,13 @@ static void add_caps(IXJ *j) break; } j->caplist[j->caps].cap = j->cardtype; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; strcpy(j->caplist[j->caps].desc, "POTS"); j->caplist[j->caps].captype = port; j->caplist[j->caps].cap = pots; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; /* add devices that can do speaker/mic */ switch (j->cardtype) { @@ -5962,7 +5965,8 @@ static void add_caps(IXJ *j) strcpy(j->caplist[j->caps].desc, "SPEAKER"); j->caplist[j->caps].captype = port; j->caplist[j->caps].cap = speaker; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; default: break; } @@ -5973,7 +5977,8 @@ static void add_caps(IXJ *j) strcpy(j->caplist[j->caps].desc, "HANDSET"); j->caplist[j->caps].captype = port; j->caplist[j->caps].cap = handset; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; break; default: break; @@ -5985,7 +5990,8 @@ static void add_caps(IXJ *j) strcpy(j->caplist[j->caps].desc, "PSTN"); j->caplist[j->caps].captype = port; j->caplist[j->caps].cap = pstn; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; break; default: break; @@ -5995,50 +6001,59 @@ static void add_caps(IXJ *j) strcpy(j->caplist[j->caps].desc, "ULAW"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = ULAW; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; strcpy(j->caplist[j->caps].desc, "LINEAR 16 bit"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = LINEAR16; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; strcpy(j->caplist[j->caps].desc, "LINEAR 8 bit"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = LINEAR8; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; strcpy(j->caplist[j->caps].desc, "Windows Sound System"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = WSS; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; /* software ALAW codec, made from ULAW */ strcpy(j->caplist[j->caps].desc, "ALAW"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = ALAW; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; /* version 12 of the 8020 does the following codecs in a broken way */ if (j->dsp.low != 0x20 || j->ver.low != 0x12) { strcpy(j->caplist[j->caps].desc, "G.723.1 6.3kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = G723_63; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; strcpy(j->caplist[j->caps].desc, "G.723.1 5.3kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = G723_53; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; strcpy(j->caplist[j->caps].desc, "TrueSpeech 4.8kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = TS48; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; strcpy(j->caplist[j->caps].desc, "TrueSpeech 4.1kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = TS41; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; } /* 8020 chips can do TS8.5 native, and 8021/8022 can load it */ @@ -6046,7 +6061,8 @@ static void add_caps(IXJ *j) strcpy(j->caplist[j->caps].desc, "TrueSpeech 8.5kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = TS85; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; } /* 8021 chips can do G728 */ @@ -6054,7 +6070,8 @@ static void add_caps(IXJ *j) strcpy(j->caplist[j->caps].desc, "G.728 16kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = G728; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; } /* 8021/8022 chips can do G729 if loaded */ @@ -6062,13 +6079,15 @@ static void add_caps(IXJ *j) strcpy(j->caplist[j->caps].desc, "G.729A 8kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = G729; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; } if (j->dsp.low != 0x20 && j->flags.g729_loaded) { strcpy(j->caplist[j->caps].desc, "G.729B 8kbps"); j->caplist[j->caps].captype = codec; j->caplist[j->caps].cap = G729B; - j->caplist[j->caps].handle = j->caps++; + j->caplist[j->caps].handle = j->caps; + j->caps++; } } diff --git a/drivers/staging/wlags49_h2/hcf.c b/drivers/staging/wlags49_h2/hcf.c index b008773323b3..5957c3a439ac 100644 --- a/drivers/staging/wlags49_h2/hcf.c +++ b/drivers/staging/wlags49_h2/hcf.c @@ -91,6 +91,7 @@ #include "hcf.h" // HCF and MSF common include file #include "hcfdef.h" // HCF specific include file #include "mmd.h" // MoreModularDriver common include file +#include <linux/bug.h> #include <linux/kernel.h> #if ! defined offsetof diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 10605ecc99ab..f9a6be7a9bed 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -1526,6 +1526,8 @@ void __init atmel_register_uart_fns(struct atmel_port_fns *fns) atmel_pops.set_wake = fns->set_wake; } +struct platform_device *atmel_default_console_device; /* the serial console device */ + #ifdef CONFIG_SERIAL_ATMEL_CONSOLE static void atmel_console_putchar(struct uart_port *port, int ch) { diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 0b7fed746b27..e7feceeebc2f 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -1508,7 +1508,7 @@ static int serial_imx_probe(struct platform_device *pdev) ret = PTR_ERR(sport->clk); goto unmap; } - clk_enable(sport->clk); + clk_prepare_enable(sport->clk); sport->port.uartclk = clk_get_rate(sport->clk); @@ -1531,8 +1531,8 @@ deinit: if (pdata && pdata->exit) pdata->exit(pdev); clkput: + clk_disable_unprepare(sport->clk); clk_put(sport->clk); - clk_disable(sport->clk); unmap: iounmap(sport->port.membase); free: @@ -1552,11 +1552,10 @@ static int serial_imx_remove(struct platform_device *pdev) if (sport) { uart_remove_one_port(&imx_reg, &sport->port); + clk_disable_unprepare(sport->clk); clk_put(sport->clk); } - clk_disable(sport->clk); - if (pdata && pdata->exit) pdata->exit(pdev); diff --git a/drivers/tty/serial/pxa.c b/drivers/tty/serial/pxa.c index e2fd3d8e0ab4..5847a4b855f7 100644 --- a/drivers/tty/serial/pxa.c +++ b/drivers/tty/serial/pxa.c @@ -36,6 +36,7 @@ #include <linux/circ_buf.h> #include <linux/delay.h> #include <linux/interrupt.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/tty.h> #include <linux/tty_flip.h> @@ -44,6 +45,8 @@ #include <linux/io.h> #include <linux/slab.h> +#define PXA_NAME_LEN 8 + struct uart_pxa_port { struct uart_port port; unsigned char ier; @@ -51,7 +54,7 @@ struct uart_pxa_port { unsigned char mcr; unsigned int lsr_break_flag; struct clk *clk; - char *name; + char name[PXA_NAME_LEN]; }; static inline unsigned int serial_in(struct uart_pxa_port *up, int offset) @@ -781,6 +784,31 @@ static const struct dev_pm_ops serial_pxa_pm_ops = { }; #endif +static struct of_device_id serial_pxa_dt_ids[] = { + { .compatible = "mrvl,pxa-uart", }, + { .compatible = "mrvl,mmp-uart", }, + {} +}; +MODULE_DEVICE_TABLE(of, serial_pxa_dt_ids); + +static int serial_pxa_probe_dt(struct platform_device *pdev, + struct uart_pxa_port *sport) +{ + struct device_node *np = pdev->dev.of_node; + int ret; + + if (!np) + return 1; + + ret = of_alias_get_id(np, "serial"); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); + return ret; + } + sport->port.line = ret; + return 0; +} + static int serial_pxa_probe(struct platform_device *dev) { struct uart_pxa_port *sport; @@ -808,20 +836,16 @@ static int serial_pxa_probe(struct platform_device *dev) sport->port.irq = irqres->start; sport->port.fifosize = 64; sport->port.ops = &serial_pxa_pops; - sport->port.line = dev->id; sport->port.dev = &dev->dev; sport->port.flags = UPF_IOREMAP | UPF_BOOT_AUTOCONF; sport->port.uartclk = clk_get_rate(sport->clk); - switch (dev->id) { - case 0: sport->name = "FFUART"; break; - case 1: sport->name = "BTUART"; break; - case 2: sport->name = "STUART"; break; - case 3: sport->name = "HWUART"; break; - default: - sport->name = "???"; - break; - } + ret = serial_pxa_probe_dt(dev, sport); + if (ret > 0) + sport->port.line = dev->id; + else if (ret < 0) + goto err_clk; + snprintf(sport->name, PXA_NAME_LEN - 1, "UART%d", sport->port.line + 1); sport->port.membase = ioremap(mmres->start, resource_size(mmres)); if (!sport->port.membase) { @@ -829,7 +853,7 @@ static int serial_pxa_probe(struct platform_device *dev) goto err_clk; } - serial_pxa_ports[dev->id] = sport; + serial_pxa_ports[sport->port.line] = sport; uart_add_one_port(&serial_pxa_reg, &sport->port); platform_set_drvdata(dev, sport); @@ -866,6 +890,7 @@ static struct platform_driver serial_pxa_driver = { #ifdef CONFIG_PM .pm = &serial_pxa_pm_ops, #endif + .of_match_table = serial_pxa_dt_ids, }, }; diff --git a/drivers/tty/serial/sa1100.c b/drivers/tty/serial/sa1100.c index ef7a21a6a01b..2ca5959ec3fa 100644 --- a/drivers/tty/serial/sa1100.c +++ b/drivers/tty/serial/sa1100.c @@ -38,6 +38,7 @@ #include <asm/irq.h> #include <mach/hardware.h> +#include <mach/irqs.h> #include <asm/mach/serial_sa1100.h> /* We've been assigned a range on the "Low-density serial ports" major */ diff --git a/drivers/tty/serial/sn_console.c b/drivers/tty/serial/sn_console.c index 4e1b5515f881..1c6de9f58699 100644 --- a/drivers/tty/serial/sn_console.c +++ b/drivers/tty/serial/sn_console.c @@ -743,6 +743,7 @@ static void __init sn_sal_switch_to_interrupts(struct sn_cons_port *port) spin_lock_irqsave(&port->sc_port.lock, flags); port->sc_port.irq = SGI_UART_VECTOR; port->sc_ops = &intr_ops; + irq_set_handler(port->sc_port.irq, handle_level_irq); /* turn on receive interrupts */ ia64_sn_console_intr_enable(SAL_CONSOLE_INTR_RECV); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index e4405e088589..cbd8f5f80596 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -16,7 +16,7 @@ config USB_ARCH_HAS_OHCI # ARM: default y if SA1111 default y if ARCH_OMAP - default y if ARCH_S3C2410 + default y if ARCH_S3C24XX default y if PXA27x default y if PXA3xx default y if ARCH_EP93XX @@ -44,7 +44,7 @@ config USB_ARCH_HAS_EHCI default y if PPC_MPC512x default y if ARCH_IXP4XX default y if ARCH_W90X900 - default y if ARCH_AT91SAM9G45 + default y if ARCH_AT91 default y if ARCH_MXC default y if ARCH_OMAP3 default y if ARCH_CNS3XXX diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index c14a3972953a..2633f7595116 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -137,7 +137,7 @@ choice config USB_AT91 tristate "Atmel AT91 USB Device Port" - depends on ARCH_AT91 && !ARCH_AT91SAM9RL && !ARCH_AT91CAP9 && !ARCH_AT91SAM9G45 + depends on ARCH_AT91 help Many Atmel AT91 processors (such as the AT91RM2000) have a full speed USB Device Port with support for five configurable @@ -150,7 +150,7 @@ config USB_AT91 config USB_ATMEL_USBA tristate "Atmel USBA" select USB_GADGET_DUALSPEED - depends on AVR32 || ARCH_AT91CAP9 || ARCH_AT91SAM9RL || ARCH_AT91SAM9G45 + depends on AVR32 || ARCH_AT91SAM9RL || ARCH_AT91SAM9G45 help USBA is the integrated high-speed USB Device controller on the AT32AP700x, some AT91SAM9 and AT91CAP9 processors from Atmel. @@ -284,7 +284,7 @@ config USB_IMX config USB_S3C2410 tristate "S3C2410 USB Device Controller" - depends on ARCH_S3C2410 + depends on ARCH_S3C24XX help Samsung's S3C2410 is an ARM-4 processor with an integrated full speed USB 1.1 device controller. It has 4 configurable @@ -299,7 +299,7 @@ config USB_S3C2410_DEBUG config USB_S3C_HSUDC tristate "S3C2416, S3C2443 and S3C2450 USB Device Controller" - depends on ARCH_S3C2410 + depends on ARCH_S3C24XX select USB_GADGET_DUALSPEED help Samsung's S3C2416, S3C2443 and S3C2450 is an ARM9 based SoC diff --git a/drivers/usb/gadget/at91_udc.c b/drivers/usb/gadget/at91_udc.c index 15a8cdb2ded5..36fd2b4b49a2 100644 --- a/drivers/usb/gadget/at91_udc.c +++ b/drivers/usb/gadget/at91_udc.c @@ -29,6 +29,8 @@ #include <linux/clk.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> +#include <linux/of.h> +#include <linux/of_gpio.h> #include <asm/byteorder.h> #include <mach/hardware.h> @@ -40,6 +42,7 @@ #include <mach/board.h> #include <mach/cpu.h> #include <mach/at91sam9261_matrix.h> +#include <mach/at91_matrix.h> #include "at91_udc.h" @@ -910,9 +913,9 @@ static void pullup(struct at91_udc *udc, int is_on) } else if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) { u32 usbpucr; - usbpucr = at91_sys_read(AT91_MATRIX_USBPUCR); + usbpucr = at91_matrix_read(AT91_MATRIX_USBPUCR); usbpucr |= AT91_MATRIX_USBPUCR_PUON; - at91_sys_write(AT91_MATRIX_USBPUCR, usbpucr); + at91_matrix_write(AT91_MATRIX_USBPUCR, usbpucr); } } else { stop_activity(udc); @@ -928,9 +931,9 @@ static void pullup(struct at91_udc *udc, int is_on) } else if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) { u32 usbpucr; - usbpucr = at91_sys_read(AT91_MATRIX_USBPUCR); + usbpucr = at91_matrix_read(AT91_MATRIX_USBPUCR); usbpucr &= ~AT91_MATRIX_USBPUCR_PUON; - at91_sys_write(AT91_MATRIX_USBPUCR, usbpucr); + at91_matrix_write(AT91_MATRIX_USBPUCR, usbpucr); } clk_off(udc); } @@ -1706,7 +1709,27 @@ static void at91udc_shutdown(struct platform_device *dev) spin_unlock_irqrestore(&udc->lock, flags); } -static int __init at91udc_probe(struct platform_device *pdev) +static void __devinit at91udc_of_init(struct at91_udc *udc, + struct device_node *np) +{ + struct at91_udc_data *board = &udc->board; + u32 val; + enum of_gpio_flags flags; + + if (of_property_read_u32(np, "atmel,vbus-polled", &val) == 0) + board->vbus_polled = 1; + + board->vbus_pin = of_get_named_gpio_flags(np, "atmel,vbus-gpio", 0, + &flags); + board->vbus_active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; + + board->pullup_pin = of_get_named_gpio_flags(np, "atmel,pullup-gpio", 0, + &flags); + + board->pullup_active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; +} + +static int __devinit at91udc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct at91_udc *udc; @@ -1741,7 +1764,11 @@ static int __init at91udc_probe(struct platform_device *pdev) /* init software state */ udc = &controller; udc->gadget.dev.parent = dev; - udc->board = *(struct at91_udc_data *) dev->platform_data; + if (pdev->dev.of_node) + at91udc_of_init(udc, pdev->dev.of_node); + else + memcpy(&udc->board, dev->platform_data, + sizeof(struct at91_udc_data)); udc->pdev = pdev; udc->enabled = 0; spin_lock_init(&udc->lock); @@ -1970,6 +1997,15 @@ static int at91udc_resume(struct platform_device *pdev) #define at91udc_resume NULL #endif +#if defined(CONFIG_OF) +static const struct of_device_id at91_udc_dt_ids[] = { + { .compatible = "atmel,at91rm9200-udc" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, at91_udc_dt_ids); +#endif + static struct platform_driver at91_udc_driver = { .remove = __exit_p(at91udc_remove), .shutdown = at91udc_shutdown, @@ -1978,6 +2014,7 @@ static struct platform_driver at91_udc_driver = { .driver = { .name = (char *) driver_name, .owner = THIS_MODULE, + .of_match_table = of_match_ptr(at91_udc_dt_ids), }, }; diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 5e10f651ad63..9f98508966d1 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -332,12 +332,12 @@ static int vbus_is_present(struct usba_udc *udc) static void toggle_bias(int is_on) { - unsigned int uckr = at91_sys_read(AT91_CKGR_UCKR); + unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); if (is_on) - at91_sys_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); + at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); else - at91_sys_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); + at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); } #else diff --git a/drivers/usb/gadget/f_phonet.c b/drivers/usb/gadget/f_phonet.c index 7cdcb63b21ff..85a5cebe96b3 100644 --- a/drivers/usb/gadget/f_phonet.c +++ b/drivers/usb/gadget/f_phonet.c @@ -345,7 +345,7 @@ static void pn_rx_complete(struct usb_ep *ep, struct usb_request *req) } skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, - skb->len <= 1, req->actual); + skb->len <= 1, req->actual, req->actual); page = NULL; if (req->actual < req->length) { /* Last fragment */ diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c index a5a3ef1f0096..19f318ababa2 100644 --- a/drivers/usb/host/ehci-atmel.c +++ b/drivers/usb/host/ehci-atmel.c @@ -13,6 +13,7 @@ #include <linux/clk.h> #include <linux/platform_device.h> +#include <linux/of_platform.h> /* interface and function clocks */ static struct clk *iclk, *fclk; @@ -115,6 +116,8 @@ static const struct hc_driver ehci_atmel_hc_driver = { .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, }; +static u64 at91_ehci_dma_mask = DMA_BIT_MASK(32); + static int __devinit ehci_atmel_drv_probe(struct platform_device *pdev) { struct usb_hcd *hcd; @@ -137,6 +140,13 @@ static int __devinit ehci_atmel_drv_probe(struct platform_device *pdev) goto fail_create_hcd; } + /* Right now device-tree probed devices don't get dma_mask set. + * Since shared usb code relies on it, set it here for now. + * Once we have dma capability bindings this can go away. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &at91_ehci_dma_mask; + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd) { retval = -ENOMEM; @@ -225,9 +235,21 @@ static int __devexit ehci_atmel_drv_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id atmel_ehci_dt_ids[] = { + { .compatible = "atmel,at91sam9g45-ehci" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_ehci_dt_ids); +#endif + static struct platform_driver ehci_atmel_driver = { .probe = ehci_atmel_drv_probe, .remove = __devexit_p(ehci_atmel_drv_remove), .shutdown = usb_hcd_platform_shutdown, - .driver.name = "atmel-ehci", + .driver = { + .name = "atmel-ehci", + .of_match_table = of_match_ptr(atmel_ehci_dt_ids), + }, }; diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 77afabc77f9b..db8963f5fbce 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -14,6 +14,8 @@ #include <linux/clk.h> #include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> #include <mach/hardware.h> #include <asm/gpio.h> @@ -448,10 +450,11 @@ static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data) /* From the GPIO notifying the over-current situation, find * out the corresponding port */ - gpio = irq_to_gpio(irq); for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) { - if (pdata->overcurrent_pin[port] == gpio) + if (gpio_to_irq(pdata->overcurrent_pin[port]) == irq) { + gpio = pdata->overcurrent_pin[port]; break; + } } if (port == ARRAY_SIZE(pdata->overcurrent_pin)) { @@ -476,13 +479,109 @@ static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data) return IRQ_HANDLED; } +#ifdef CONFIG_OF +static const struct of_device_id at91_ohci_dt_ids[] = { + { .compatible = "atmel,at91rm9200-ohci" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, at91_ohci_dt_ids); + +static u64 at91_ohci_dma_mask = DMA_BIT_MASK(32); + +static int __devinit ohci_at91_of_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int i, ret, gpio; + enum of_gpio_flags flags; + struct at91_usbh_data *pdata; + u32 ports; + + if (!np) + return 0; + + /* Right now device-tree probed devices don't get dma_mask set. + * Since shared usb code relies on it, set it here for now. + * Once we have dma capability bindings this can go away. + */ + if (!pdev->dev.dma_mask) + pdev->dev.dma_mask = &at91_ohci_dma_mask; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (!of_property_read_u32(np, "num-ports", &ports)) + pdata->ports = ports; + + for (i = 0; i < 2; i++) { + gpio = of_get_named_gpio_flags(np, "atmel,vbus-gpio", i, &flags); + pdata->vbus_pin[i] = gpio; + if (!gpio_is_valid(gpio)) + continue; + pdata->vbus_pin_active_low[i] = flags & OF_GPIO_ACTIVE_LOW; + ret = gpio_request(gpio, "ohci_vbus"); + if (ret) { + dev_warn(&pdev->dev, "can't request vbus gpio %d", gpio); + continue; + } + ret = gpio_direction_output(gpio, !(flags & OF_GPIO_ACTIVE_LOW) ^ 1); + if (ret) + dev_warn(&pdev->dev, "can't put vbus gpio %d as output %d", + !(flags & OF_GPIO_ACTIVE_LOW) ^ 1, gpio); + } + + for (i = 0; i < 2; i++) { + gpio = of_get_named_gpio_flags(np, "atmel,oc-gpio", i, &flags); + pdata->overcurrent_pin[i] = gpio; + if (!gpio_is_valid(gpio)) + continue; + ret = gpio_request(gpio, "ohci_overcurrent"); + if (ret) { + dev_err(&pdev->dev, "can't request overcurrent gpio %d", gpio); + continue; + } + + ret = gpio_direction_input(gpio); + if (ret) { + dev_err(&pdev->dev, "can't configure overcurrent gpio %d as input", gpio); + continue; + } + + ret = request_irq(gpio_to_irq(gpio), + ohci_hcd_at91_overcurrent_irq, + IRQF_SHARED, "ohci_overcurrent", pdev); + if (ret) { + gpio_free(gpio); + dev_warn(& pdev->dev, "cannot get GPIO IRQ for overcurrent\n"); + } + } + + pdev->dev.platform_data = pdata; + + return 0; +} +#else +static int __devinit ohci_at91_of_init(struct platform_device *pdev) +{ + return 0; +} +#endif + /*-------------------------------------------------------------------------*/ static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) { - struct at91_usbh_data *pdata = pdev->dev.platform_data; + struct at91_usbh_data *pdata; int i; + i = ohci_at91_of_init(pdev); + + if (i) + return i; + + pdata = pdev->dev.platform_data; + if (pdata) { for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) { if (!gpio_is_valid(pdata->vbus_pin[i])) @@ -595,5 +694,6 @@ static struct platform_driver ohci_hcd_at91_driver = { .driver = { .name = "at91_ohci", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(at91_ohci_dt_ids), }, }; diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index cd5e382db89c..543e90e336b8 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -1000,7 +1000,7 @@ MODULE_LICENSE ("GPL"); #define SA1111_DRIVER ohci_hcd_sa1111_driver #endif -#if defined(CONFIG_ARCH_S3C2410) || defined(CONFIG_ARCH_S3C64XX) +#if defined(CONFIG_ARCH_S3C24XX) || defined(CONFIG_ARCH_S3C64XX) #include "ohci-s3c2410.c" #define PLATFORM_DRIVER ohci_hcd_s3c2410_driver #endif diff --git a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c index 4bde4f9821ba..e1004fb37bd9 100644 --- a/drivers/usb/host/ohci-sa1111.c +++ b/drivers/usb/host/ohci-sa1111.c @@ -16,29 +16,115 @@ #include <mach/hardware.h> #include <asm/mach-types.h> #include <mach/assabet.h> -#include <mach/badge4.h> #include <asm/hardware/sa1111.h> #ifndef CONFIG_SA1111 #error "This file is SA-1111 bus glue. CONFIG_SA1111 must be defined." #endif -extern int usb_disabled(void); +#define USB_STATUS 0x0118 +#define USB_RESET 0x011c +#define USB_IRQTEST 0x0120 + +#define USB_RESET_FORCEIFRESET (1 << 0) +#define USB_RESET_FORCEHCRESET (1 << 1) +#define USB_RESET_CLKGENRESET (1 << 2) +#define USB_RESET_SIMSCALEDOWN (1 << 3) +#define USB_RESET_USBINTTEST (1 << 4) +#define USB_RESET_SLEEPSTBYEN (1 << 5) +#define USB_RESET_PWRSENSELOW (1 << 6) +#define USB_RESET_PWRCTRLLOW (1 << 7) + +#define USB_STATUS_IRQHCIRMTWKUP (1 << 7) +#define USB_STATUS_IRQHCIBUFFACC (1 << 8) +#define USB_STATUS_NIRQHCIM (1 << 9) +#define USB_STATUS_NHCIMFCLR (1 << 10) +#define USB_STATUS_USBPWRSENSE (1 << 11) -/*-------------------------------------------------------------------------*/ +#if 0 +static void dump_hci_status(struct usb_hcd *hcd, const char *label) +{ + unsigned long status = sa1111_readl(hcd->regs + USB_STATUS); + + dbg("%s USB_STATUS = { %s%s%s%s%s}", label, + ((status & USB_STATUS_IRQHCIRMTWKUP) ? "IRQHCIRMTWKUP " : ""), + ((status & USB_STATUS_IRQHCIBUFFACC) ? "IRQHCIBUFFACC " : ""), + ((status & USB_STATUS_NIRQHCIM) ? "" : "IRQHCIM "), + ((status & USB_STATUS_NHCIMFCLR) ? "" : "HCIMFCLR "), + ((status & USB_STATUS_USBPWRSENSE) ? "USBPWRSENSE " : "")); +} +#endif -static void sa1111_start_hc(struct sa1111_dev *dev) +static int ohci_sa1111_reset(struct usb_hcd *hcd) { - unsigned int usb_rst = 0; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci_hcd_init(ohci); + return ohci_init(ohci); +} - printk(KERN_DEBUG "%s: starting SA-1111 OHCI USB Controller\n", - __FILE__); +static int __devinit ohci_sa1111_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; -#ifdef CONFIG_SA1100_BADGE4 - if (machine_is_badge4()) { - badge4_set_5V(BADGE4_5V_USB, 1); + ret = ohci_run(ohci); + if (ret < 0) { + ohci_err(ohci, "can't start\n"); + ohci_stop(hcd); } + return ret; +} + +static const struct hc_driver ohci_sa1111_hc_driver = { + .description = hcd_name, + .product_desc = "SA-1111 OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = ohci_sa1111_reset, + .start = ohci_sa1111_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, #endif + .start_port_reset = ohci_start_port_reset, +}; + +static int sa1111_start_hc(struct sa1111_dev *dev) +{ + unsigned int usb_rst = 0; + int ret; + + dev_dbg(&dev->dev, "starting SA-1111 OHCI USB Controller\n"); if (machine_is_xp860() || machine_has_neponset() || @@ -51,220 +137,121 @@ static void sa1111_start_hc(struct sa1111_dev *dev) * host controller in reset. */ sa1111_writel(usb_rst | USB_RESET_FORCEIFRESET | USB_RESET_FORCEHCRESET, - dev->mapbase + SA1111_USB_RESET); + dev->mapbase + USB_RESET); /* * Now, carefully enable the USB clock, and take * the USB host controller out of reset. */ - sa1111_enable_device(dev); - udelay(11); - sa1111_writel(usb_rst, dev->mapbase + SA1111_USB_RESET); + ret = sa1111_enable_device(dev); + if (ret == 0) { + udelay(11); + sa1111_writel(usb_rst, dev->mapbase + USB_RESET); + } + + return ret; } static void sa1111_stop_hc(struct sa1111_dev *dev) { unsigned int usb_rst; - printk(KERN_DEBUG "%s: stopping SA-1111 OHCI USB Controller\n", - __FILE__); + + dev_dbg(&dev->dev, "stopping SA-1111 OHCI USB Controller\n"); /* * Put the USB host controller into reset. */ - usb_rst = sa1111_readl(dev->mapbase + SA1111_USB_RESET); + usb_rst = sa1111_readl(dev->mapbase + USB_RESET); sa1111_writel(usb_rst | USB_RESET_FORCEIFRESET | USB_RESET_FORCEHCRESET, - dev->mapbase + SA1111_USB_RESET); + dev->mapbase + USB_RESET); /* * Stop the USB clock. */ sa1111_disable_device(dev); - -#ifdef CONFIG_SA1100_BADGE4 - if (machine_is_badge4()) { - /* Disable power to the USB bus */ - badge4_set_5V(BADGE4_5V_USB, 0); - } -#endif -} - - -/*-------------------------------------------------------------------------*/ - -#if 0 -static void dump_hci_status(struct usb_hcd *hcd, const char *label) -{ - unsigned long status = sa1111_readl(hcd->regs + SA1111_USB_STATUS); - - dbg ("%s USB_STATUS = { %s%s%s%s%s}", label, - ((status & USB_STATUS_IRQHCIRMTWKUP) ? "IRQHCIRMTWKUP " : ""), - ((status & USB_STATUS_IRQHCIBUFFACC) ? "IRQHCIBUFFACC " : ""), - ((status & USB_STATUS_NIRQHCIM) ? "" : "IRQHCIM "), - ((status & USB_STATUS_NHCIMFCLR) ? "" : "HCIMFCLR "), - ((status & USB_STATUS_USBPWRSENSE) ? "USBPWRSENSE " : "")); } -#endif - -/*-------------------------------------------------------------------------*/ - -/* configure so an HC device and id are always provided */ -/* always called with process context; sleeping is OK */ - /** - * usb_hcd_sa1111_probe - initialize SA-1111-based HCDs - * Context: !in_interrupt() + * ohci_hcd_sa1111_probe - initialize SA-1111-based HCDs * * Allocates basic resources for this USB host controller, and - * then invokes the start() method for the HCD associated with it - * through the hotplug entry's driver_data. - * - * Store this function in the HCD's struct pci_driver as probe(). + * then invokes the start() method for the HCD associated with it. */ -int usb_hcd_sa1111_probe (const struct hc_driver *driver, - struct sa1111_dev *dev) +static int ohci_hcd_sa1111_probe(struct sa1111_dev *dev) { struct usb_hcd *hcd; - int retval; + int ret; - hcd = usb_create_hcd (driver, &dev->dev, "sa1111"); + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&ohci_sa1111_hc_driver, &dev->dev, "sa1111"); if (!hcd) return -ENOMEM; + hcd->rsrc_start = dev->res.start; hcd->rsrc_len = resource_size(&dev->res); if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { dbg("request_mem_region failed"); - retval = -EBUSY; + ret = -EBUSY; goto err1; } + hcd->regs = dev->mapbase; - sa1111_start_hc(dev); - ohci_hcd_init(hcd_to_ohci(hcd)); + ret = sa1111_start_hc(dev); + if (ret) + goto err2; - retval = usb_add_hcd(hcd, dev->irq[1], 0); - if (retval == 0) - return retval; + ret = usb_add_hcd(hcd, dev->irq[1], 0); + if (ret == 0) + return ret; sa1111_stop_hc(dev); + err2: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); err1: usb_put_hcd(hcd); - return retval; + return ret; } - -/* may be called without controller electrically present */ -/* may be called with controller, bus, and devices active */ - /** - * usb_hcd_sa1111_remove - shutdown processing for SA-1111-based HCDs + * ohci_hcd_sa1111_remove - shutdown processing for SA-1111-based HCDs * @dev: USB Host Controller being removed - * Context: !in_interrupt() - * - * Reverses the effect of usb_hcd_sa1111_probe(), first invoking - * the HCD's stop() method. It is always called from a thread - * context, normally "rmmod", "apmd", or something similar. * + * Reverses the effect of ohci_hcd_sa1111_probe(), first invoking + * the HCD's stop() method. */ -void usb_hcd_sa1111_remove (struct usb_hcd *hcd, struct sa1111_dev *dev) +static int ohci_hcd_sa1111_remove(struct sa1111_dev *dev) { + struct usb_hcd *hcd = sa1111_get_drvdata(dev); + usb_remove_hcd(hcd); sa1111_stop_hc(dev); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); -} - -/*-------------------------------------------------------------------------*/ -static int __devinit -ohci_sa1111_start (struct usb_hcd *hcd) -{ - struct ohci_hcd *ohci = hcd_to_ohci (hcd); - int ret; - - if ((ret = ohci_init(ohci)) < 0) - return ret; - - if ((ret = ohci_run (ohci)) < 0) { - err ("can't start %s", hcd->self.bus_name); - ohci_stop (hcd); - return ret; - } return 0; } -/*-------------------------------------------------------------------------*/ - -static const struct hc_driver ohci_sa1111_hc_driver = { - .description = hcd_name, - .product_desc = "SA-1111 OHCI", - .hcd_priv_size = sizeof(struct ohci_hcd), - - /* - * generic hardware linkage - */ - .irq = ohci_irq, - .flags = HCD_USB11 | HCD_MEMORY, - - /* - * basic lifecycle operations - */ - .start = ohci_sa1111_start, - .stop = ohci_stop, - - /* - * managing i/o requests and associated device resources - */ - .urb_enqueue = ohci_urb_enqueue, - .urb_dequeue = ohci_urb_dequeue, - .endpoint_disable = ohci_endpoint_disable, - - /* - * scheduling support - */ - .get_frame_number = ohci_get_frame, - - /* - * root hub support - */ - .hub_status_data = ohci_hub_status_data, - .hub_control = ohci_hub_control, -#ifdef CONFIG_PM - .bus_suspend = ohci_bus_suspend, - .bus_resume = ohci_bus_resume, -#endif - .start_port_reset = ohci_start_port_reset, -}; - -/*-------------------------------------------------------------------------*/ - -static int ohci_hcd_sa1111_drv_probe(struct sa1111_dev *dev) -{ - int ret; - - if (usb_disabled()) - return -ENODEV; - - ret = usb_hcd_sa1111_probe(&ohci_sa1111_hc_driver, dev); - return ret; -} - -static int ohci_hcd_sa1111_drv_remove(struct sa1111_dev *dev) +static void ohci_hcd_sa1111_shutdown(struct sa1111_dev *dev) { struct usb_hcd *hcd = sa1111_get_drvdata(dev); - usb_hcd_sa1111_remove(hcd, dev); - return 0; + if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + hcd->driver->shutdown(hcd); + sa1111_stop_hc(dev); + } } static struct sa1111_driver ohci_hcd_sa1111_driver = { .drv = { .name = "sa1111-ohci", + .owner = THIS_MODULE, }, .devid = SA1111_DEVID_USB, - .probe = ohci_hcd_sa1111_drv_probe, - .remove = ohci_hcd_sa1111_drv_remove, + .probe = ohci_hcd_sa1111_probe, + .remove = ohci_hcd_sa1111_remove, + .shutdown = ohci_hcd_sa1111_shutdown, }; - diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 7732d69e49e0..11de5f1be981 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -893,4 +893,5 @@ static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) quirk_usb_handoff_xhci(pdev); pci_disable_device(pdev); } -DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff); +DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_SERIAL_USB, 8, quirk_usb_early_handoff); diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 6815701cf656..836cfa9a515f 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -903,8 +903,10 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0165, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0167, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1012, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1057, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1058, 0xff, 0xff, 0xff) }, diff --git a/drivers/uwb/allocator.c b/drivers/uwb/allocator.c index e45e673b8770..6e3e713f0ef7 100644 --- a/drivers/uwb/allocator.c +++ b/drivers/uwb/allocator.c @@ -334,10 +334,8 @@ int uwb_rsv_find_best_allocation(struct uwb_rsv *rsv, struct uwb_mas_bm *availab /* fill the not available vector from the available bm */ - for (bit_index = 0; bit_index < UWB_NUM_MAS; bit_index++) { - if (!test_bit(bit_index, available->bm)) - ai->bm[bit_index] = UWB_RSV_MAS_NOT_AVAIL; - } + for_each_clear_bit(bit_index, available->bm, UWB_NUM_MAS) + ai->bm[bit_index] = UWB_RSV_MAS_NOT_AVAIL; if (ai->max_interval == 1) { get_row_descriptors(ai); diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 9dab1f51dd43..f0da2c32fbde 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -588,7 +588,7 @@ static int vhost_net_release(struct inode *inode, struct file *f) vhost_net_stop(n, &tx_sock, &rx_sock); vhost_net_flush(n); - vhost_dev_cleanup(&n->dev); + vhost_dev_cleanup(&n->dev, false); if (tx_sock) fput(tx_sock->file); if (rx_sock) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index bdb2d6436b2b..947f00d8e091 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -222,6 +222,8 @@ static int vhost_worker(void *data) if (work) { __set_current_state(TASK_RUNNING); work->fn(work); + if (need_resched()) + schedule(); } else schedule(); @@ -403,7 +405,7 @@ long vhost_dev_reset_owner(struct vhost_dev *dev) if (!memory) return -ENOMEM; - vhost_dev_cleanup(dev); + vhost_dev_cleanup(dev, true); memory->nregions = 0; RCU_INIT_POINTER(dev->memory, memory); @@ -434,8 +436,8 @@ int vhost_zerocopy_signal_used(struct vhost_virtqueue *vq) return j; } -/* Caller should have device mutex */ -void vhost_dev_cleanup(struct vhost_dev *dev) +/* Caller should have device mutex if and only if locked is set */ +void vhost_dev_cleanup(struct vhost_dev *dev, bool locked) { int i; @@ -472,7 +474,8 @@ void vhost_dev_cleanup(struct vhost_dev *dev) dev->log_file = NULL; /* No one will access memory at this point */ kfree(rcu_dereference_protected(dev->memory, - lockdep_is_held(&dev->mutex))); + locked == + lockdep_is_held(&dev->mutex))); RCU_INIT_POINTER(dev->memory, NULL); WARN_ON(!list_empty(&dev->work_list)); if (dev->worker) { diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index a801e2821d03..8dcf4cca6bf2 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -163,7 +163,7 @@ struct vhost_dev { long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs); long vhost_dev_check_owner(struct vhost_dev *); long vhost_dev_reset_owner(struct vhost_dev *); -void vhost_dev_cleanup(struct vhost_dev *); +void vhost_dev_cleanup(struct vhost_dev *, bool locked); long vhost_dev_ioctl(struct vhost_dev *, unsigned int ioctl, unsigned long arg); int vhost_vq_access_ok(struct vhost_virtqueue *vq); int vhost_log_access_ok(struct vhost_dev *); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index a8a897ac5446..a290be51a1f4 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2061,7 +2061,7 @@ config FB_S3C_DEBUG_REGWRITE config FB_S3C2410 tristate "S3C2410 LCD framebuffer support" - depends on FB && ARCH_S3C2410 + depends on FB && ARCH_S3C24XX select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT diff --git a/drivers/video/backlight/88pm860x_bl.c b/drivers/video/backlight/88pm860x_bl.c index a1376dc73d71..915943af3f21 100644 --- a/drivers/video/backlight/88pm860x_bl.c +++ b/drivers/video/backlight/88pm860x_bl.c @@ -187,7 +187,8 @@ static int pm860x_backlight_probe(struct platform_device *pdev) return -EINVAL; } - data = kzalloc(sizeof(struct pm860x_backlight_data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_backlight_data), + GFP_KERNEL); if (data == NULL) return -ENOMEM; strncpy(name, res->name, MFD_NAME_SIZE); @@ -200,7 +201,6 @@ static int pm860x_backlight_probe(struct platform_device *pdev) data->port = pdata->flags; if (data->port < 0) { dev_err(&pdev->dev, "wrong platform data is assigned"); - kfree(data); return -EINVAL; } @@ -211,7 +211,6 @@ static int pm860x_backlight_probe(struct platform_device *pdev) &pm860x_backlight_ops, &props); if (IS_ERR(bl)) { dev_err(&pdev->dev, "failed to register backlight\n"); - kfree(data); return PTR_ERR(bl); } bl->props.brightness = MAX_BRIGHTNESS; @@ -247,17 +246,14 @@ static int pm860x_backlight_probe(struct platform_device *pdev) return 0; out: backlight_device_unregister(bl); - kfree(data); return ret; } static int pm860x_backlight_remove(struct platform_device *pdev) { struct backlight_device *bl = platform_get_drvdata(pdev); - struct pm860x_backlight_data *data = bl_get_data(bl); backlight_device_unregister(bl); - kfree(data); return 0; } diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 681b36929fe4..7ed9991fa747 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -334,6 +334,27 @@ config BACKLIGHT_AAT2870 If you have a AnalogicTech AAT2870 say Y to enable the backlight driver. +config BACKLIGHT_LP855X + tristate "Backlight driver for TI LP855X" + depends on BACKLIGHT_CLASS_DEVICE && I2C + help + This supports TI LP8550, LP8551, LP8552, LP8553 and LP8556 + backlight driver. + +config BACKLIGHT_OT200 + tristate "Backlight driver for ot200 visualisation device" + depends on BACKLIGHT_CLASS_DEVICE && CS5535_MFGPT && GPIO_CS5535 + help + To compile this driver as a module, choose M here: the module will be + called ot200_bl. + +config BACKLIGHT_PANDORA + tristate "Backlight driver for Pandora console" + depends on TWL4030_CORE + help + If you have a Pandora console, say Y to enable the + backlight driver. + endif # BACKLIGHT_CLASS_DEVICE endif # BACKLIGHT_LCD_SUPPORT diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index af5cf654ec7c..8071eb656147 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -22,7 +22,9 @@ obj-$(CONFIG_BACKLIGHT_GENERIC) += generic_bl.o obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o +obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o +obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o @@ -38,4 +40,4 @@ obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o obj-$(CONFIG_BACKLIGHT_AAT2870) += aat2870_bl.o - +obj-$(CONFIG_BACKLIGHT_OT200) += ot200_bl.o diff --git a/drivers/video/backlight/aat2870_bl.c b/drivers/video/backlight/aat2870_bl.c index 331f1ef1dad5..7ff752288b92 100644 --- a/drivers/video/backlight/aat2870_bl.c +++ b/drivers/video/backlight/aat2870_bl.c @@ -145,7 +145,9 @@ static int aat2870_bl_probe(struct platform_device *pdev) goto out; } - aat2870_bl = kzalloc(sizeof(struct aat2870_bl_driver_data), GFP_KERNEL); + aat2870_bl = devm_kzalloc(&pdev->dev, + sizeof(struct aat2870_bl_driver_data), + GFP_KERNEL); if (!aat2870_bl) { dev_err(&pdev->dev, "Failed to allocate memory for aat2870 backlight\n"); @@ -162,7 +164,7 @@ static int aat2870_bl_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed allocate memory for backlight device\n"); ret = PTR_ERR(bd); - goto out_kfree; + goto out; } aat2870_bl->pdev = pdev; @@ -199,8 +201,6 @@ static int aat2870_bl_probe(struct platform_device *pdev) out_bl_dev_unregister: backlight_device_unregister(bd); -out_kfree: - kfree(aat2870_bl); out: return ret; } @@ -215,7 +215,6 @@ static int aat2870_bl_remove(struct platform_device *pdev) backlight_update_status(bd); backlight_device_unregister(bd); - kfree(aat2870_bl); return 0; } diff --git a/drivers/video/backlight/adp5520_bl.c b/drivers/video/backlight/adp5520_bl.c index 2e630bf1164c..4911ea7989c8 100644 --- a/drivers/video/backlight/adp5520_bl.c +++ b/drivers/video/backlight/adp5520_bl.c @@ -289,7 +289,7 @@ static int __devinit adp5520_bl_probe(struct platform_device *pdev) struct adp5520_bl *data; int ret = 0; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (data == NULL) return -ENOMEM; @@ -298,7 +298,6 @@ static int __devinit adp5520_bl_probe(struct platform_device *pdev) if (data->pdata == NULL) { dev_err(&pdev->dev, "missing platform data\n"); - kfree(data); return -ENODEV; } @@ -314,7 +313,6 @@ static int __devinit adp5520_bl_probe(struct platform_device *pdev) &adp5520_bl_ops, &props); if (IS_ERR(bl)) { dev_err(&pdev->dev, "failed to register backlight\n"); - kfree(data); return PTR_ERR(bl); } @@ -326,7 +324,6 @@ static int __devinit adp5520_bl_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "failed to register sysfs\n"); backlight_device_unregister(bl); - kfree(data); } platform_set_drvdata(pdev, bl); @@ -348,7 +345,6 @@ static int __devexit adp5520_bl_remove(struct platform_device *pdev) &adp5520_bl_attr_group); backlight_device_unregister(bl); - kfree(data); return 0; } diff --git a/drivers/video/backlight/adp8860_bl.c b/drivers/video/backlight/adp8860_bl.c index 378276c9d3cf..550dbf0bb896 100644 --- a/drivers/video/backlight/adp8860_bl.c +++ b/drivers/video/backlight/adp8860_bl.c @@ -819,17 +819,7 @@ static struct i2c_driver adp8860_driver = { .id_table = adp8860_id, }; -static int __init adp8860_init(void) -{ - return i2c_add_driver(&adp8860_driver); -} -module_init(adp8860_init); - -static void __exit adp8860_exit(void) -{ - i2c_del_driver(&adp8860_driver); -} -module_exit(adp8860_exit); +module_i2c_driver(adp8860_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); diff --git a/drivers/video/backlight/adp8870_bl.c b/drivers/video/backlight/adp8870_bl.c index 6735059376d6..9be58c6f18f1 100644 --- a/drivers/video/backlight/adp8870_bl.c +++ b/drivers/video/backlight/adp8870_bl.c @@ -991,17 +991,7 @@ static struct i2c_driver adp8870_driver = { .id_table = adp8870_id, }; -static int __init adp8870_init(void) -{ - return i2c_add_driver(&adp8870_driver); -} -module_init(adp8870_init); - -static void __exit adp8870_exit(void) -{ - i2c_del_driver(&adp8870_driver); -} -module_exit(adp8870_exit); +module_i2c_driver(adp8870_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); diff --git a/drivers/video/backlight/ams369fg06.c b/drivers/video/backlight/ams369fg06.c index 7838a23fbdd1..7bdadc790117 100644 --- a/drivers/video/backlight/ams369fg06.c +++ b/drivers/video/backlight/ams369fg06.c @@ -629,18 +629,7 @@ static struct spi_driver ams369fg06_driver = { .resume = ams369fg06_resume, }; -static int __init ams369fg06_init(void) -{ - return spi_register_driver(&ams369fg06_driver); -} - -static void __exit ams369fg06_exit(void) -{ - spi_unregister_driver(&ams369fg06_driver); -} - -module_init(ams369fg06_init); -module_exit(ams369fg06_exit); +module_spi_driver(ams369fg06_driver); MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); MODULE_DESCRIPTION("ams369fg06 LCD Driver"); diff --git a/drivers/video/backlight/corgi_lcd.c b/drivers/video/backlight/corgi_lcd.c index c6533bad26f8..6dab13fe562e 100644 --- a/drivers/video/backlight/corgi_lcd.c +++ b/drivers/video/backlight/corgi_lcd.c @@ -629,17 +629,7 @@ static struct spi_driver corgi_lcd_driver = { .resume = corgi_lcd_resume, }; -static int __init corgi_lcd_init(void) -{ - return spi_register_driver(&corgi_lcd_driver); -} -module_init(corgi_lcd_init); - -static void __exit corgi_lcd_exit(void) -{ - spi_unregister_driver(&corgi_lcd_driver); -} -module_exit(corgi_lcd_exit); +module_spi_driver(corgi_lcd_driver); MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00"); MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"); diff --git a/drivers/video/backlight/cr_bllcd.c b/drivers/video/backlight/cr_bllcd.c index 6c8c54041fae..22489eb5f3e0 100644 --- a/drivers/video/backlight/cr_bllcd.c +++ b/drivers/video/backlight/cr_bllcd.c @@ -212,7 +212,7 @@ static int cr_backlight_probe(struct platform_device *pdev) &gpio_bar); gpio_bar &= ~0x3F; - crp = kzalloc(sizeof(*crp), GFP_KERNEL); + crp = devm_kzalloc(&pdev->dev, sizeof(*crp), GFP_KERNEL); if (!crp) { lcd_device_unregister(ldp); backlight_device_unregister(bdp); @@ -243,7 +243,6 @@ static int cr_backlight_remove(struct platform_device *pdev) backlight_device_unregister(crp->cr_backlight_device); lcd_device_unregister(crp->cr_lcd_device); pci_dev_put(lpc_dev); - kfree(crp); return 0; } diff --git a/drivers/video/backlight/da903x_bl.c b/drivers/video/backlight/da903x_bl.c index abb4a06268f1..30e19681a30b 100644 --- a/drivers/video/backlight/da903x_bl.c +++ b/drivers/video/backlight/da903x_bl.c @@ -110,7 +110,7 @@ static int da903x_backlight_probe(struct platform_device *pdev) struct backlight_properties props; int max_brightness; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (data == NULL) return -ENOMEM; @@ -124,7 +124,6 @@ static int da903x_backlight_probe(struct platform_device *pdev) default: dev_err(&pdev->dev, "invalid backlight device ID(%d)\n", pdev->id); - kfree(data); return -EINVAL; } @@ -143,7 +142,6 @@ static int da903x_backlight_probe(struct platform_device *pdev) &da903x_backlight_ops, &props); if (IS_ERR(bl)) { dev_err(&pdev->dev, "failed to register backlight\n"); - kfree(data); return PTR_ERR(bl); } @@ -157,10 +155,8 @@ static int da903x_backlight_probe(struct platform_device *pdev) static int da903x_backlight_remove(struct platform_device *pdev) { struct backlight_device *bl = platform_get_drvdata(pdev); - struct da903x_backlight_data *data = bl_get_data(bl); backlight_device_unregister(bl); - kfree(data); return 0; } diff --git a/drivers/video/backlight/ep93xx_bl.c b/drivers/video/backlight/ep93xx_bl.c index b62b8b9063b5..08214e1f0958 100644 --- a/drivers/video/backlight/ep93xx_bl.c +++ b/drivers/video/backlight/ep93xx_bl.c @@ -17,11 +17,6 @@ #include <linux/fb.h> #include <linux/backlight.h> -#include <mach/hardware.h> - -#define EP93XX_RASTER_REG(x) (EP93XX_RASTER_BASE + (x)) -#define EP93XX_RASTER_BRIGHTNESS EP93XX_RASTER_REG(0x20) - #define EP93XX_MAX_COUNT 255 #define EP93XX_MAX_BRIGHT 255 #define EP93XX_DEF_BRIGHT 128 @@ -35,7 +30,7 @@ static int ep93xxbl_set(struct backlight_device *bl, int brightness) { struct ep93xxbl *ep93xxbl = bl_get_data(bl); - __raw_writel((brightness << 8) | EP93XX_MAX_COUNT, ep93xxbl->mmio); + writel((brightness << 8) | EP93XX_MAX_COUNT, ep93xxbl->mmio); ep93xxbl->brightness = brightness; @@ -70,21 +65,29 @@ static int __init ep93xxbl_probe(struct platform_device *dev) struct ep93xxbl *ep93xxbl; struct backlight_device *bl; struct backlight_properties props; + struct resource *res; ep93xxbl = devm_kzalloc(&dev->dev, sizeof(*ep93xxbl), GFP_KERNEL); if (!ep93xxbl) return -ENOMEM; + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + /* - * This register is located in the range already ioremap'ed by - * the framebuffer driver. A MFD driver seems a bit of overkill - * to handle this so use the static I/O mapping; this address - * is already virtual. + * FIXME - We don't do a request_mem_region here because we are + * sharing the register space with the framebuffer driver (see + * drivers/video/ep93xx-fb.c) and doing so will cause the second + * loaded driver to return -EBUSY. * * NOTE: No locking is required; the framebuffer does not touch * this register. */ - ep93xxbl->mmio = EP93XX_RASTER_BRIGHTNESS; + ep93xxbl->mmio = devm_ioremap(&dev->dev, res->start, + resource_size(res)); + if (!ep93xxbl->mmio) + return -ENXIO; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; diff --git a/drivers/video/backlight/l4f00242t03.c b/drivers/video/backlight/l4f00242t03.c index 27d1d7a29c77..6022b67285ec 100644 --- a/drivers/video/backlight/l4f00242t03.c +++ b/drivers/video/backlight/l4f00242t03.c @@ -274,18 +274,7 @@ static struct spi_driver l4f00242t03_driver = { .shutdown = l4f00242t03_shutdown, }; -static __init int l4f00242t03_init(void) -{ - return spi_register_driver(&l4f00242t03_driver); -} - -static __exit void l4f00242t03_exit(void) -{ - spi_unregister_driver(&l4f00242t03_driver); -} - -module_init(l4f00242t03_init); -module_exit(l4f00242t03_exit); +module_spi_driver(l4f00242t03_driver); MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>"); MODULE_DESCRIPTION("EPSON L4F00242T03 LCD"); diff --git a/drivers/video/backlight/ld9040.c b/drivers/video/backlight/ld9040.c index 78dafc0c8fc5..efd352be21ae 100644 --- a/drivers/video/backlight/ld9040.c +++ b/drivers/video/backlight/ld9040.c @@ -856,18 +856,7 @@ static struct spi_driver ld9040_driver = { .resume = ld9040_resume, }; -static int __init ld9040_init(void) -{ - return spi_register_driver(&ld9040_driver); -} - -static void __exit ld9040_exit(void) -{ - spi_unregister_driver(&ld9040_driver); -} - -module_init(ld9040_init); -module_exit(ld9040_exit); +module_spi_driver(ld9040_driver); MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); MODULE_DESCRIPTION("ld9040 LCD Driver"); diff --git a/drivers/video/backlight/lms283gf05.c b/drivers/video/backlight/lms283gf05.c index 4ec78cfe26ea..4161f9e3982a 100644 --- a/drivers/video/backlight/lms283gf05.c +++ b/drivers/video/backlight/lms283gf05.c @@ -226,18 +226,7 @@ static struct spi_driver lms283gf05_driver = { .remove = __devexit_p(lms283gf05_remove), }; -static __init int lms283gf05_init(void) -{ - return spi_register_driver(&lms283gf05_driver); -} - -static __exit void lms283gf05_exit(void) -{ - spi_unregister_driver(&lms283gf05_driver); -} - -module_init(lms283gf05_init); -module_exit(lms283gf05_exit); +module_spi_driver(lms283gf05_driver); MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); MODULE_DESCRIPTION("LCD283GF05 LCD"); diff --git a/drivers/video/backlight/lp855x_bl.c b/drivers/video/backlight/lp855x_bl.c new file mode 100644 index 000000000000..72a0e0c917cf --- /dev/null +++ b/drivers/video/backlight/lp855x_bl.c @@ -0,0 +1,331 @@ +/* + * TI LP855x Backlight Driver + * + * Copyright (C) 2011 Texas Instruments + * + * 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/i2c.h> +#include <linux/backlight.h> +#include <linux/err.h> +#include <linux/lp855x.h> + +/* Registers */ +#define BRIGHTNESS_CTRL (0x00) +#define DEVICE_CTRL (0x01) + +#define BUF_SIZE 20 +#define DEFAULT_BL_NAME "lcd-backlight" +#define MAX_BRIGHTNESS 255 + +struct lp855x { + const char *chipname; + enum lp855x_chip_id chip_id; + struct i2c_client *client; + struct backlight_device *bl; + struct device *dev; + struct mutex xfer_lock; + struct lp855x_platform_data *pdata; +}; + +static int lp855x_read_byte(struct lp855x *lp, u8 reg, u8 *data) +{ + int ret; + + mutex_lock(&lp->xfer_lock); + ret = i2c_smbus_read_byte_data(lp->client, reg); + if (ret < 0) { + mutex_unlock(&lp->xfer_lock); + dev_err(lp->dev, "failed to read 0x%.2x\n", reg); + return ret; + } + mutex_unlock(&lp->xfer_lock); + + *data = (u8)ret; + return 0; +} + +static int lp855x_write_byte(struct lp855x *lp, u8 reg, u8 data) +{ + int ret; + + mutex_lock(&lp->xfer_lock); + ret = i2c_smbus_write_byte_data(lp->client, reg, data); + mutex_unlock(&lp->xfer_lock); + + return ret; +} + +static bool lp855x_is_valid_rom_area(struct lp855x *lp, u8 addr) +{ + u8 start, end; + + switch (lp->chip_id) { + case LP8550: + case LP8551: + case LP8552: + case LP8553: + start = EEPROM_START; + end = EEPROM_END; + break; + case LP8556: + start = EPROM_START; + end = EPROM_END; + break; + default: + return false; + } + + return (addr >= start && addr <= end); +} + +static int lp855x_init_registers(struct lp855x *lp) +{ + u8 val, addr; + int i, ret; + struct lp855x_platform_data *pd = lp->pdata; + + val = pd->initial_brightness; + ret = lp855x_write_byte(lp, BRIGHTNESS_CTRL, val); + if (ret) + return ret; + + val = pd->device_control; + ret = lp855x_write_byte(lp, DEVICE_CTRL, val); + if (ret) + return ret; + + if (pd->load_new_rom_data && pd->size_program) { + for (i = 0; i < pd->size_program; i++) { + addr = pd->rom_data[i].addr; + val = pd->rom_data[i].val; + if (!lp855x_is_valid_rom_area(lp, addr)) + continue; + + ret = lp855x_write_byte(lp, addr, val); + if (ret) + return ret; + } + } + + return ret; +} + +static int lp855x_bl_update_status(struct backlight_device *bl) +{ + struct lp855x *lp = bl_get_data(bl); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + + if (bl->props.state & BL_CORE_SUSPENDED) + bl->props.brightness = 0; + + if (mode == PWM_BASED) { + struct lp855x_pwm_data *pd = &lp->pdata->pwm_data; + int br = bl->props.brightness; + int max_br = bl->props.max_brightness; + + if (pd->pwm_set_intensity) + pd->pwm_set_intensity(br, max_br); + + } else if (mode == REGISTER_BASED) { + u8 val = bl->props.brightness; + lp855x_write_byte(lp, BRIGHTNESS_CTRL, val); + } + + return 0; +} + +static int lp855x_bl_get_brightness(struct backlight_device *bl) +{ + struct lp855x *lp = bl_get_data(bl); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + + if (mode == PWM_BASED) { + struct lp855x_pwm_data *pd = &lp->pdata->pwm_data; + int max_br = bl->props.max_brightness; + + if (pd->pwm_get_intensity) + bl->props.brightness = pd->pwm_get_intensity(max_br); + + } else if (mode == REGISTER_BASED) { + u8 val = 0; + + lp855x_read_byte(lp, BRIGHTNESS_CTRL, &val); + bl->props.brightness = val; + } + + return bl->props.brightness; +} + +static const struct backlight_ops lp855x_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = lp855x_bl_update_status, + .get_brightness = lp855x_bl_get_brightness, +}; + +static int lp855x_backlight_register(struct lp855x *lp) +{ + struct backlight_device *bl; + struct backlight_properties props; + struct lp855x_platform_data *pdata = lp->pdata; + char *name = pdata->name ? : DEFAULT_BL_NAME; + + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = MAX_BRIGHTNESS; + + if (pdata->initial_brightness > props.max_brightness) + pdata->initial_brightness = props.max_brightness; + + props.brightness = pdata->initial_brightness; + + bl = backlight_device_register(name, lp->dev, lp, + &lp855x_bl_ops, &props); + if (IS_ERR(bl)) + return PTR_ERR(bl); + + lp->bl = bl; + + return 0; +} + +static void lp855x_backlight_unregister(struct lp855x *lp) +{ + if (lp->bl) + backlight_device_unregister(lp->bl); +} + +static ssize_t lp855x_get_chip_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lp855x *lp = dev_get_drvdata(dev); + return scnprintf(buf, BUF_SIZE, "%s\n", lp->chipname); +} + +static ssize_t lp855x_get_bl_ctl_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lp855x *lp = dev_get_drvdata(dev); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + char *strmode = NULL; + + if (mode == PWM_BASED) + strmode = "pwm based"; + else if (mode == REGISTER_BASED) + strmode = "register based"; + + return scnprintf(buf, BUF_SIZE, "%s\n", strmode); +} + +static DEVICE_ATTR(chip_id, S_IRUGO, lp855x_get_chip_id, NULL); +static DEVICE_ATTR(bl_ctl_mode, S_IRUGO, lp855x_get_bl_ctl_mode, NULL); + +static struct attribute *lp855x_attributes[] = { + &dev_attr_chip_id.attr, + &dev_attr_bl_ctl_mode.attr, + NULL, +}; + +static const struct attribute_group lp855x_attr_group = { + .attrs = lp855x_attributes, +}; + +static int lp855x_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct lp855x *lp; + struct lp855x_platform_data *pdata = cl->dev.platform_data; + enum lp855x_brightness_ctrl_mode mode; + int ret; + + if (!pdata) { + dev_err(&cl->dev, "no platform data supplied\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EIO; + + lp = devm_kzalloc(&cl->dev, sizeof(struct lp855x), GFP_KERNEL); + if (!lp) + return -ENOMEM; + + mode = pdata->mode; + lp->client = cl; + lp->dev = &cl->dev; + lp->pdata = pdata; + lp->chipname = id->name; + lp->chip_id = id->driver_data; + i2c_set_clientdata(cl, lp); + + mutex_init(&lp->xfer_lock); + + ret = lp855x_init_registers(lp); + if (ret) { + dev_err(lp->dev, "i2c communication err: %d", ret); + if (mode == REGISTER_BASED) + goto err_dev; + } + + ret = lp855x_backlight_register(lp); + if (ret) { + dev_err(lp->dev, + "failed to register backlight. err: %d\n", ret); + goto err_dev; + } + + ret = sysfs_create_group(&lp->dev->kobj, &lp855x_attr_group); + if (ret) { + dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret); + goto err_sysfs; + } + + backlight_update_status(lp->bl); + return 0; + +err_sysfs: + lp855x_backlight_unregister(lp); +err_dev: + return ret; +} + +static int __devexit lp855x_remove(struct i2c_client *cl) +{ + struct lp855x *lp = i2c_get_clientdata(cl); + + lp->bl->props.brightness = 0; + backlight_update_status(lp->bl); + sysfs_remove_group(&lp->dev->kobj, &lp855x_attr_group); + lp855x_backlight_unregister(lp); + + return 0; +} + +static const struct i2c_device_id lp855x_ids[] = { + {"lp8550", LP8550}, + {"lp8551", LP8551}, + {"lp8552", LP8552}, + {"lp8553", LP8553}, + {"lp8556", LP8556}, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp855x_ids); + +static struct i2c_driver lp855x_driver = { + .driver = { + .name = "lp855x", + }, + .probe = lp855x_probe, + .remove = __devexit_p(lp855x_remove), + .id_table = lp855x_ids, +}; + +module_i2c_driver(lp855x_driver); + +MODULE_DESCRIPTION("Texas Instruments LP855x Backlight driver"); +MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/ltv350qv.c b/drivers/video/backlight/ltv350qv.c index cca43c06d3c8..333949ff3265 100644 --- a/drivers/video/backlight/ltv350qv.c +++ b/drivers/video/backlight/ltv350qv.c @@ -321,17 +321,7 @@ static struct spi_driver ltv350qv_driver = { .resume = ltv350qv_resume, }; -static int __init ltv350qv_init(void) -{ - return spi_register_driver(<v350qv_driver); -} - -static void __exit ltv350qv_exit(void) -{ - spi_unregister_driver(<v350qv_driver); -} -module_init(ltv350qv_init); -module_exit(ltv350qv_exit); +module_spi_driver(ltv350qv_driver); MODULE_AUTHOR("Haavard Skinnemoen (Atmel)"); MODULE_DESCRIPTION("Samsung LTV350QV LCD Driver"); diff --git a/drivers/video/backlight/max8925_bl.c b/drivers/video/backlight/max8925_bl.c index c915e3b53886..e833ac72e063 100644 --- a/drivers/video/backlight/max8925_bl.c +++ b/drivers/video/backlight/max8925_bl.c @@ -129,7 +129,8 @@ static int __devinit max8925_backlight_probe(struct platform_device *pdev) return -EINVAL; } - data = kzalloc(sizeof(struct max8925_backlight_data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(struct max8925_backlight_data), + GFP_KERNEL); if (data == NULL) return -ENOMEM; strncpy(name, res->name, MAX8925_NAME_SIZE); @@ -143,7 +144,6 @@ static int __devinit max8925_backlight_probe(struct platform_device *pdev) &max8925_backlight_ops, &props); if (IS_ERR(bl)) { dev_err(&pdev->dev, "failed to register backlight\n"); - kfree(data); return PTR_ERR(bl); } bl->props.brightness = MAX_BRIGHTNESS; @@ -165,17 +165,14 @@ static int __devinit max8925_backlight_probe(struct platform_device *pdev) return 0; out: backlight_device_unregister(bl); - kfree(data); return ret; } static int __devexit max8925_backlight_remove(struct platform_device *pdev) { struct backlight_device *bl = platform_get_drvdata(pdev); - struct max8925_backlight_data *data = bl_get_data(bl); backlight_device_unregister(bl); - kfree(data); return 0; } diff --git a/drivers/video/backlight/omap1_bl.c b/drivers/video/backlight/omap1_bl.c index d8cde277ec83..0175bfb08a1c 100644 --- a/drivers/video/backlight/omap1_bl.c +++ b/drivers/video/backlight/omap1_bl.c @@ -141,7 +141,8 @@ static int omapbl_probe(struct platform_device *pdev) if (!pdata) return -ENXIO; - bl = kzalloc(sizeof(struct omap_backlight), GFP_KERNEL); + bl = devm_kzalloc(&pdev->dev, sizeof(struct omap_backlight), + GFP_KERNEL); if (unlikely(!bl)) return -ENOMEM; @@ -150,10 +151,8 @@ static int omapbl_probe(struct platform_device *pdev) props.max_brightness = OMAPBL_MAX_INTENSITY; dev = backlight_device_register("omap-bl", &pdev->dev, bl, &omapbl_ops, &props); - if (IS_ERR(dev)) { - kfree(bl); + if (IS_ERR(dev)) return PTR_ERR(dev); - } bl->powermode = FB_BLANK_POWERDOWN; bl->current_intensity = 0; @@ -177,10 +176,8 @@ static int omapbl_probe(struct platform_device *pdev) static int omapbl_remove(struct platform_device *pdev) { struct backlight_device *dev = platform_get_drvdata(pdev); - struct omap_backlight *bl = dev_get_drvdata(&dev->dev); backlight_device_unregister(dev); - kfree(bl); return 0; } diff --git a/drivers/video/backlight/ot200_bl.c b/drivers/video/backlight/ot200_bl.c new file mode 100644 index 000000000000..f519d55a294c --- /dev/null +++ b/drivers/video/backlight/ot200_bl.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2012 Bachmann electronic GmbH + * Christian Gmeiner <christian.gmeiner@gmail.com> + * + * Backlight driver for ot200 visualisation device from + * Bachmann electronic 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/module.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/gpio.h> +#include <linux/cs5535.h> + +static struct cs5535_mfgpt_timer *pwm_timer; + +/* this array defines the mapping of brightness in % to pwm frequency */ +static const u8 dim_table[101] = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, + 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 14, 15, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, + 30, 31, 33, 35, 37, 39, 41, 43, 45, 47, 50, + 53, 55, 58, 61, 65, 68, 72, 75, 79, 84, 88, + 93, 97, 103, 108, 114, 120, 126, 133, 140, + 147, 155, 163}; + +struct ot200_backlight_data { + int current_brightness; +}; + +#define GPIO_DIMM 27 +#define SCALE 1 +#define CMP1MODE 0x2 /* compare on GE; output high on compare + * greater than or equal */ +#define PWM_SETUP (SCALE | CMP1MODE << 6 | MFGPT_SETUP_CNTEN) +#define MAX_COMP2 163 + +static int ot200_backlight_update_status(struct backlight_device *bl) +{ + struct ot200_backlight_data *data = bl_get_data(bl); + int brightness = bl->props.brightness; + + if (bl->props.state & BL_CORE_FBBLANK) + brightness = 0; + + /* enable or disable PWM timer */ + if (brightness == 0) + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP, 0); + else if (data->current_brightness == 0) { + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_COUNTER, 0); + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP, + MFGPT_SETUP_CNTEN); + } + + /* apply new brightness value */ + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP1, + MAX_COMP2 - dim_table[brightness]); + data->current_brightness = brightness; + + return 0; +} + +static int ot200_backlight_get_brightness(struct backlight_device *bl) +{ + struct ot200_backlight_data *data = bl_get_data(bl); + return data->current_brightness; +} + +static const struct backlight_ops ot200_backlight_ops = { + .update_status = ot200_backlight_update_status, + .get_brightness = ot200_backlight_get_brightness, +}; + +static int ot200_backlight_probe(struct platform_device *pdev) +{ + struct backlight_device *bl; + struct ot200_backlight_data *data; + struct backlight_properties props; + int retval = 0; + + /* request gpio */ + if (gpio_request(GPIO_DIMM, "ot200 backlight dimmer") < 0) { + dev_err(&pdev->dev, "failed to request GPIO %d\n", GPIO_DIMM); + return -ENODEV; + } + + /* request timer */ + pwm_timer = cs5535_mfgpt_alloc_timer(7, MFGPT_DOMAIN_ANY); + if (!pwm_timer) { + dev_err(&pdev->dev, "MFGPT 7 not available\n"); + retval = -ENODEV; + goto error_mfgpt_alloc; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + retval = -ENOMEM; + goto error_kzalloc; + } + + /* setup gpio */ + cs5535_gpio_set(GPIO_DIMM, GPIO_OUTPUT_ENABLE); + cs5535_gpio_set(GPIO_DIMM, GPIO_OUTPUT_AUX1); + + /* setup timer */ + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP1, 0); + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP2, MAX_COMP2); + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP, PWM_SETUP); + + data->current_brightness = 100; + props.max_brightness = 100; + props.brightness = 100; + props.type = BACKLIGHT_RAW; + + bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, data, + &ot200_backlight_ops, &props); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + retval = PTR_ERR(bl); + goto error_backlight_device_register; + } + + platform_set_drvdata(pdev, bl); + + return 0; + +error_backlight_device_register: + kfree(data); +error_kzalloc: + cs5535_mfgpt_free_timer(pwm_timer); +error_mfgpt_alloc: + gpio_free(GPIO_DIMM); + return retval; +} + +static int ot200_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + struct ot200_backlight_data *data = bl_get_data(bl); + + backlight_device_unregister(bl); + + /* on module unload set brightness to 100% */ + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_COUNTER, 0); + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN); + cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP1, + MAX_COMP2 - dim_table[100]); + + cs5535_mfgpt_free_timer(pwm_timer); + gpio_free(GPIO_DIMM); + + kfree(data); + return 0; +} + +static struct platform_driver ot200_backlight_driver = { + .driver = { + .name = "ot200-backlight", + .owner = THIS_MODULE, + }, + .probe = ot200_backlight_probe, + .remove = ot200_backlight_remove, +}; + +module_platform_driver(ot200_backlight_driver); + +MODULE_DESCRIPTION("backlight driver for ot200 visualisation device"); +MODULE_AUTHOR("Christian Gmeiner <christian.gmeiner@gmail.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ot200-backlight"); diff --git a/drivers/video/backlight/pandora_bl.c b/drivers/video/backlight/pandora_bl.c new file mode 100644 index 000000000000..4ec30748b447 --- /dev/null +++ b/drivers/video/backlight/pandora_bl.c @@ -0,0 +1,171 @@ +/* + * Backlight driver for Pandora handheld. + * Pandora uses TWL4030 PWM0 -> TPS61161 combo for control backlight. + * Based on pwm_bl.c + * + * Copyright 2009,2012 Gražvydas Ignotas <notasas@gmail.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/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/i2c/twl.h> +#include <linux/err.h> + +#define TWL_PWM0_ON 0x00 +#define TWL_PWM0_OFF 0x01 + +#define TWL_INTBR_GPBR1 0x0c +#define TWL_INTBR_PMBR1 0x0d + +#define TWL_PMBR1_PWM0_MUXMASK 0x0c +#define TWL_PMBR1_PWM0 0x04 +#define PWM0_CLK_ENABLE BIT(0) +#define PWM0_ENABLE BIT(2) + +/* range accepted by hardware */ +#define MIN_VALUE 9 +#define MAX_VALUE 63 +#define MAX_USER_VALUE (MAX_VALUE - MIN_VALUE) + +#define PANDORABL_WAS_OFF BL_CORE_DRIVER1 + +static int pandora_backlight_update_status(struct backlight_device *bl) +{ + int brightness = bl->props.brightness; + u8 r; + + if (bl->props.power != FB_BLANK_UNBLANK) + brightness = 0; + if (bl->props.state & BL_CORE_FBBLANK) + brightness = 0; + if (bl->props.state & BL_CORE_SUSPENDED) + brightness = 0; + + if ((unsigned int)brightness > MAX_USER_VALUE) + brightness = MAX_USER_VALUE; + + if (brightness == 0) { + if (bl->props.state & PANDORABL_WAS_OFF) + goto done; + + /* first disable PWM0 output, then clock */ + twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1); + r &= ~PWM0_ENABLE; + twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); + r &= ~PWM0_CLK_ENABLE; + twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); + + goto done; + } + + if (bl->props.state & PANDORABL_WAS_OFF) { + /* + * set PWM duty cycle to max. TPS61161 seems to use this + * to calibrate it's PWM sensitivity when it starts. + */ + twl_i2c_write_u8(TWL4030_MODULE_PWM0, MAX_VALUE, + TWL_PWM0_OFF); + + /* first enable clock, then PWM0 out */ + twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1); + r &= ~PWM0_ENABLE; + r |= PWM0_CLK_ENABLE; + twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); + r |= PWM0_ENABLE; + twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); + + /* + * TI made it very easy to enable digital control, so easy that + * it often triggers unintentionally and disabes PWM control, + * so wait until 1 wire mode detection window ends. + */ + usleep_range(2000, 10000); + } + + twl_i2c_write_u8(TWL4030_MODULE_PWM0, MIN_VALUE + brightness, + TWL_PWM0_OFF); + +done: + if (brightness != 0) + bl->props.state &= ~PANDORABL_WAS_OFF; + else + bl->props.state |= PANDORABL_WAS_OFF; + + return 0; +} + +static int pandora_backlight_get_brightness(struct backlight_device *bl) +{ + return bl->props.brightness; +} + +static const struct backlight_ops pandora_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = pandora_backlight_update_status, + .get_brightness = pandora_backlight_get_brightness, +}; + +static int pandora_backlight_probe(struct platform_device *pdev) +{ + struct backlight_properties props; + struct backlight_device *bl; + u8 r; + + memset(&props, 0, sizeof(props)); + props.max_brightness = MAX_USER_VALUE; + props.type = BACKLIGHT_RAW; + bl = backlight_device_register(pdev->name, &pdev->dev, + NULL, &pandora_backlight_ops, &props); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "failed to register backlight\n"); + return PTR_ERR(bl); + } + + platform_set_drvdata(pdev, bl); + + /* 64 cycle period, ON position 0 */ + twl_i2c_write_u8(TWL4030_MODULE_PWM0, 0x80, TWL_PWM0_ON); + + bl->props.state |= PANDORABL_WAS_OFF; + bl->props.brightness = MAX_USER_VALUE; + backlight_update_status(bl); + + /* enable PWM function in pin mux */ + twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_PMBR1); + r &= ~TWL_PMBR1_PWM0_MUXMASK; + r |= TWL_PMBR1_PWM0; + twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_PMBR1); + + return 0; +} + +static int pandora_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + backlight_device_unregister(bl); + return 0; +} + +static struct platform_driver pandora_backlight_driver = { + .driver = { + .name = "pandora-backlight", + .owner = THIS_MODULE, + }, + .probe = pandora_backlight_probe, + .remove = pandora_backlight_remove, +}; + +module_platform_driver(pandora_backlight_driver); + +MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>"); +MODULE_DESCRIPTION("Pandora Backlight Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pandora-backlight"); diff --git a/drivers/video/backlight/pcf50633-backlight.c b/drivers/video/backlight/pcf50633-backlight.c index 13e88b71daec..c65853cb9740 100644 --- a/drivers/video/backlight/pcf50633-backlight.c +++ b/drivers/video/backlight/pcf50633-backlight.c @@ -101,14 +101,13 @@ static const struct backlight_ops pcf50633_bl_ops = { static int __devinit pcf50633_bl_probe(struct platform_device *pdev) { - int ret; struct pcf50633_bl *pcf_bl; struct device *parent = pdev->dev.parent; struct pcf50633_platform_data *pcf50633_data = parent->platform_data; struct pcf50633_bl_platform_data *pdata = pcf50633_data->backlight_data; struct backlight_properties bl_props; - pcf_bl = kzalloc(sizeof(*pcf_bl), GFP_KERNEL); + pcf_bl = devm_kzalloc(&pdev->dev, sizeof(*pcf_bl), GFP_KERNEL); if (!pcf_bl) return -ENOMEM; @@ -129,10 +128,8 @@ static int __devinit pcf50633_bl_probe(struct platform_device *pdev) pcf_bl->bl = backlight_device_register(pdev->name, &pdev->dev, pcf_bl, &pcf50633_bl_ops, &bl_props); - if (IS_ERR(pcf_bl->bl)) { - ret = PTR_ERR(pcf_bl->bl); - goto err_free; - } + if (IS_ERR(pcf_bl->bl)) + return PTR_ERR(pcf_bl->bl); platform_set_drvdata(pdev, pcf_bl); @@ -145,11 +142,6 @@ static int __devinit pcf50633_bl_probe(struct platform_device *pdev) backlight_update_status(pcf_bl->bl); return 0; - -err_free: - kfree(pcf_bl); - - return ret; } static int __devexit pcf50633_bl_remove(struct platform_device *pdev) @@ -160,8 +152,6 @@ static int __devexit pcf50633_bl_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); - kfree(pcf_bl); - return 0; } diff --git a/drivers/video/backlight/platform_lcd.c b/drivers/video/backlight/platform_lcd.c index f0bf491ed087..b6672340d6c7 100644 --- a/drivers/video/backlight/platform_lcd.c +++ b/drivers/video/backlight/platform_lcd.c @@ -121,9 +121,9 @@ static int __devexit platform_lcd_remove(struct platform_device *pdev) } #ifdef CONFIG_PM -static int platform_lcd_suspend(struct platform_device *pdev, pm_message_t st) +static int platform_lcd_suspend(struct device *dev) { - struct platform_lcd *plcd = platform_get_drvdata(pdev); + struct platform_lcd *plcd = dev_get_drvdata(dev); plcd->suspended = 1; platform_lcd_set_power(plcd->lcd, plcd->power); @@ -131,29 +131,30 @@ static int platform_lcd_suspend(struct platform_device *pdev, pm_message_t st) return 0; } -static int platform_lcd_resume(struct platform_device *pdev) +static int platform_lcd_resume(struct device *dev) { - struct platform_lcd *plcd = platform_get_drvdata(pdev); + struct platform_lcd *plcd = dev_get_drvdata(dev); plcd->suspended = 0; platform_lcd_set_power(plcd->lcd, plcd->power); return 0; } -#else -#define platform_lcd_suspend NULL -#define platform_lcd_resume NULL + +static SIMPLE_DEV_PM_OPS(platform_lcd_pm_ops, platform_lcd_suspend, + platform_lcd_resume); #endif static struct platform_driver platform_lcd_driver = { .driver = { .name = "platform-lcd", .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &platform_lcd_pm_ops, +#endif }, .probe = platform_lcd_probe, .remove = __devexit_p(platform_lcd_remove), - .suspend = platform_lcd_suspend, - .resume = platform_lcd_resume, }; module_platform_driver(platform_lcd_driver); diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 7496d04e1d3c..342b7d7cbb63 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c @@ -102,7 +102,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) return ret; } - pb = kzalloc(sizeof(*pb), GFP_KERNEL); + pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL); if (!pb) { dev_err(&pdev->dev, "no memory for state\n"); ret = -ENOMEM; @@ -121,7 +121,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) if (IS_ERR(pb->pwm)) { dev_err(&pdev->dev, "unable to request PWM for backlight\n"); ret = PTR_ERR(pb->pwm); - goto err_pwm; + goto err_alloc; } else dev_dbg(&pdev->dev, "got pwm for backlight\n"); @@ -144,8 +144,6 @@ static int pwm_backlight_probe(struct platform_device *pdev) err_bl: pwm_free(pb->pwm); -err_pwm: - kfree(pb); err_alloc: if (data->exit) data->exit(&pdev->dev); @@ -162,7 +160,6 @@ static int pwm_backlight_remove(struct platform_device *pdev) pwm_config(pb->pwm, 0, pb->period); pwm_disable(pb->pwm); pwm_free(pb->pwm); - kfree(pb); if (data->exit) data->exit(&pdev->dev); return 0; diff --git a/drivers/video/backlight/s6e63m0.c b/drivers/video/backlight/s6e63m0.c index 516db703dd24..e264f55b2574 100644 --- a/drivers/video/backlight/s6e63m0.c +++ b/drivers/video/backlight/s6e63m0.c @@ -909,18 +909,7 @@ static struct spi_driver s6e63m0_driver = { .resume = s6e63m0_resume, }; -static int __init s6e63m0_init(void) -{ - return spi_register_driver(&s6e63m0_driver); -} - -static void __exit s6e63m0_exit(void) -{ - spi_unregister_driver(&s6e63m0_driver); -} - -module_init(s6e63m0_init); -module_exit(s6e63m0_exit); +module_spi_driver(s6e63m0_driver); MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>"); MODULE_DESCRIPTION("S6E63M0 LCD Driver"); diff --git a/drivers/video/backlight/tdo24m.c b/drivers/video/backlight/tdo24m.c index 1997e12a1057..2368b8e5f89e 100644 --- a/drivers/video/backlight/tdo24m.c +++ b/drivers/video/backlight/tdo24m.c @@ -459,17 +459,7 @@ static struct spi_driver tdo24m_driver = { .resume = tdo24m_resume, }; -static int __init tdo24m_init(void) -{ - return spi_register_driver(&tdo24m_driver); -} -module_init(tdo24m_init); - -static void __exit tdo24m_exit(void) -{ - spi_unregister_driver(&tdo24m_driver); -} -module_exit(tdo24m_exit); +module_spi_driver(tdo24m_driver); MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"); MODULE_DESCRIPTION("Driver for Toppoly TDO24M LCD Panel"); diff --git a/drivers/video/backlight/tosa_bl.c b/drivers/video/backlight/tosa_bl.c index 425a7365470b..2b241abced43 100644 --- a/drivers/video/backlight/tosa_bl.c +++ b/drivers/video/backlight/tosa_bl.c @@ -181,18 +181,7 @@ static struct i2c_driver tosa_bl_driver = { .id_table = tosa_bl_id, }; -static int __init tosa_bl_init(void) -{ - return i2c_add_driver(&tosa_bl_driver); -} - -static void __exit tosa_bl_exit(void) -{ - i2c_del_driver(&tosa_bl_driver); -} - -module_init(tosa_bl_init); -module_exit(tosa_bl_exit); +module_i2c_driver(tosa_bl_driver); MODULE_AUTHOR("Dmitry Baryshkov"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/backlight/tosa_lcd.c b/drivers/video/backlight/tosa_lcd.c index 772f6015219a..a2161f631a83 100644 --- a/drivers/video/backlight/tosa_lcd.c +++ b/drivers/video/backlight/tosa_lcd.c @@ -285,18 +285,7 @@ static struct spi_driver tosa_lcd_driver = { .resume = tosa_lcd_resume, }; -static int __init tosa_lcd_init(void) -{ - return spi_register_driver(&tosa_lcd_driver); -} - -static void __exit tosa_lcd_exit(void) -{ - spi_unregister_driver(&tosa_lcd_driver); -} - -module_init(tosa_lcd_init); -module_exit(tosa_lcd_exit); +module_spi_driver(tosa_lcd_driver); MODULE_AUTHOR("Dmitry Baryshkov"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/backlight/vgg2432a4.c b/drivers/video/backlight/vgg2432a4.c index b49063c831e7..b617fae9aa26 100644 --- a/drivers/video/backlight/vgg2432a4.c +++ b/drivers/video/backlight/vgg2432a4.c @@ -262,20 +262,7 @@ static struct spi_driver vgg2432a4_driver = { .resume = vgg2432a4_resume, }; -/* Device driver initialisation */ - -static int __init vgg2432a4_init(void) -{ - return spi_register_driver(&vgg2432a4_driver); -} - -static void __exit vgg2432a4_exit(void) -{ - spi_unregister_driver(&vgg2432a4_driver); -} - -module_init(vgg2432a4_init); -module_exit(vgg2432a4_exit); +module_spi_driver(vgg2432a4_driver); MODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>"); MODULE_DESCRIPTION("VGG2432A4 LCD Driver"); diff --git a/drivers/video/backlight/wm831x_bl.c b/drivers/video/backlight/wm831x_bl.c index 4e915f5eca99..5d365deb5f82 100644 --- a/drivers/video/backlight/wm831x_bl.c +++ b/drivers/video/backlight/wm831x_bl.c @@ -186,7 +186,7 @@ static int wm831x_backlight_probe(struct platform_device *pdev) if (ret < 0) return ret; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (data == NULL) return -ENOMEM; @@ -200,7 +200,6 @@ static int wm831x_backlight_probe(struct platform_device *pdev) &wm831x_backlight_ops, &props); if (IS_ERR(bl)) { dev_err(&pdev->dev, "failed to register backlight\n"); - kfree(data); return PTR_ERR(bl); } @@ -211,7 +210,6 @@ static int wm831x_backlight_probe(struct platform_device *pdev) /* Disable the DCDC if it was started so we can bootstrap */ wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0); - backlight_update_status(bl); return 0; @@ -220,10 +218,8 @@ static int wm831x_backlight_probe(struct platform_device *pdev) static int wm831x_backlight_remove(struct platform_device *pdev) { struct backlight_device *bl = platform_get_drvdata(pdev); - struct wm831x_backlight_data *data = bl_get_data(bl); backlight_device_unregister(bl); - kfree(data); return 0; } diff --git a/drivers/video/ep93xx-fb.c b/drivers/video/ep93xx-fb.c index 2e830ec52a5a..f8babbeee275 100644 --- a/drivers/video/ep93xx-fb.c +++ b/drivers/video/ep93xx-fb.c @@ -519,12 +519,15 @@ static int __devinit ep93xxfb_probe(struct platform_device *pdev) goto failed; } - res = request_mem_region(res->start, resource_size(res), pdev->name); - if (!res) { - err = -EBUSY; - goto failed; - } - + /* + * FIXME - We don't do a request_mem_region here because we are + * sharing the register space with the backlight driver (see + * drivers/video/backlight/ep93xx_bl.c) and doing so will cause + * the second loaded driver to return -EBUSY. + * + * NOTE: No locking is required; the backlight does not touch + * any of the framebuffer registers. + */ fbi->res = res; fbi->mmio_base = ioremap(res->start, resource_size(res)); if (!fbi->mmio_base) { @@ -586,8 +589,6 @@ failed: clk_put(fbi->clk); if (fbi->mmio_base) iounmap(fbi->mmio_base); - if (fbi->res) - release_mem_region(fbi->res->start, resource_size(fbi->res)); ep93xxfb_dealloc_videomem(info); if (&info->cmap) fb_dealloc_cmap(&info->cmap); @@ -608,7 +609,6 @@ static int __devexit ep93xxfb_remove(struct platform_device *pdev) clk_disable(fbi->clk); clk_put(fbi->clk); iounmap(fbi->mmio_base); - release_mem_region(fbi->res->start, resource_size(fbi->res)); ep93xxfb_dealloc_videomem(info); fb_dealloc_cmap(&info->cmap); diff --git a/drivers/video/omap/lcd_ams_delta.c b/drivers/video/omap/lcd_ams_delta.c index 0fdd6f6873bf..d3a311327227 100644 --- a/drivers/video/omap/lcd_ams_delta.c +++ b/drivers/video/omap/lcd_ams_delta.c @@ -25,6 +25,7 @@ #include <linux/io.h> #include <linux/delay.h> #include <linux/lcd.h> +#include <linux/gpio.h> #include <plat/board-ams-delta.h> #include <mach/hardware.h> @@ -98,29 +99,41 @@ static struct lcd_ops ams_delta_lcd_ops = { /* omapfb panel section */ +static const struct gpio _gpios[] = { + { + .gpio = AMS_DELTA_GPIO_PIN_LCD_VBLEN, + .flags = GPIOF_OUT_INIT_LOW, + .label = "lcd_vblen", + }, + { + .gpio = AMS_DELTA_GPIO_PIN_LCD_NDISP, + .flags = GPIOF_OUT_INIT_LOW, + .label = "lcd_ndisp", + }, +}; + static int ams_delta_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev) { - return 0; + return gpio_request_array(_gpios, ARRAY_SIZE(_gpios)); } static void ams_delta_panel_cleanup(struct lcd_panel *panel) { + gpio_free_array(_gpios, ARRAY_SIZE(_gpios)); } static int ams_delta_panel_enable(struct lcd_panel *panel) { - ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, - AMS_DELTA_LATCH2_LCD_NDISP); - ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, - AMS_DELTA_LATCH2_LCD_VBLEN); + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_NDISP, 1); + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_VBLEN, 1); return 0; } static void ams_delta_panel_disable(struct lcd_panel *panel) { - ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, 0); - ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_VBLEN, 0); + gpio_set_value(AMS_DELTA_GPIO_PIN_LCD_NDISP, 0); } static unsigned long ams_delta_panel_get_caps(struct lcd_panel *panel) diff --git a/drivers/video/omap2/dss/dispc.c b/drivers/video/omap2/dss/dispc.c index bddd64b435b9..ee30937482e1 100644 --- a/drivers/video/omap2/dss/dispc.c +++ b/drivers/video/omap2/dss/dispc.c @@ -3318,11 +3318,6 @@ static void _omap_dispc_initial_config(void) if (dss_has_feature(FEAT_FUNCGATED)) REG_FLD_MOD(DISPC_CONFIG, 1, 9, 9); - /* L3 firewall setting: enable access to OCM RAM */ - /* XXX this should be somewhere in plat-omap */ - if (cpu_is_omap24xx()) - __raw_writel(0x402000b0, OMAP2_L3_IO_ADDRESS(0x680050a0)); - _dispc_setup_color_conv_coef(); dispc_set_loadmode(OMAP_DSS_LOAD_FRAME_ONLY); diff --git a/drivers/video/omap2/dss/dss.c b/drivers/video/omap2/dss/dss.c index 4a6b5eeef6a7..bd2d5e159463 100644 --- a/drivers/video/omap2/dss/dss.c +++ b/drivers/video/omap2/dss/dss.c @@ -33,7 +33,10 @@ #include <linux/pm_runtime.h> #include <video/omapdss.h> + +#include <plat/cpu.h> #include <plat/clock.h> + #include "dss.h" #include "dss_features.h" diff --git a/drivers/video/sa1100fb.c b/drivers/video/sa1100fb.c index 98d55d0e2da5..b6325848ad61 100644 --- a/drivers/video/sa1100fb.c +++ b/drivers/video/sa1100fb.c @@ -173,282 +173,48 @@ #include <linux/init.h> #include <linux/ioport.h> #include <linux/cpufreq.h> +#include <linux/gpio.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> #include <linux/mutex.h> #include <linux/io.h> +#include <video/sa1100fb.h> + #include <mach/hardware.h> #include <asm/mach-types.h> -#include <mach/assabet.h> #include <mach/shannon.h> /* - * debugging? - */ -#define DEBUG 0 -/* * Complain if VAR is out of range. */ #define DEBUG_VAR 1 -#undef ASSABET_PAL_VIDEO - #include "sa1100fb.h" -extern void (*sa1100fb_backlight_power)(int on); -extern void (*sa1100fb_lcd_power)(int on); - -static struct sa1100fb_rgb rgb_4 = { +static const struct sa1100fb_rgb rgb_4 = { .red = { .offset = 0, .length = 4, }, .green = { .offset = 0, .length = 4, }, .blue = { .offset = 0, .length = 4, }, .transp = { .offset = 0, .length = 0, }, }; -static struct sa1100fb_rgb rgb_8 = { +static const struct sa1100fb_rgb rgb_8 = { .red = { .offset = 0, .length = 8, }, .green = { .offset = 0, .length = 8, }, .blue = { .offset = 0, .length = 8, }, .transp = { .offset = 0, .length = 0, }, }; -static struct sa1100fb_rgb def_rgb_16 = { +static const struct sa1100fb_rgb def_rgb_16 = { .red = { .offset = 11, .length = 5, }, .green = { .offset = 5, .length = 6, }, .blue = { .offset = 0, .length = 5, }, .transp = { .offset = 0, .length = 0, }, }; -#ifdef CONFIG_SA1100_ASSABET -#ifndef ASSABET_PAL_VIDEO -/* - * The assabet uses a sharp LQ039Q2DS54 LCD module. It is actually - * takes an RGB666 signal, but we provide it with an RGB565 signal - * instead (def_rgb_16). - */ -static struct sa1100fb_mach_info lq039q2ds54_info __initdata = { - .pixclock = 171521, .bpp = 16, - .xres = 320, .yres = 240, - - .hsync_len = 5, .vsync_len = 1, - .left_margin = 61, .upper_margin = 3, - .right_margin = 9, .lower_margin = 0, - - .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, - - .lccr0 = LCCR0_Color | LCCR0_Sngl | LCCR0_Act, - .lccr3 = LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2), -}; -#else -static struct sa1100fb_mach_info pal_info __initdata = { - .pixclock = 67797, .bpp = 16, - .xres = 640, .yres = 512, - - .hsync_len = 64, .vsync_len = 6, - .left_margin = 125, .upper_margin = 70, - .right_margin = 115, .lower_margin = 36, - - .lccr0 = LCCR0_Color | LCCR0_Sngl | LCCR0_Act, - .lccr3 = LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(512), -}; -#endif -#endif - -#ifdef CONFIG_SA1100_H3600 -static struct sa1100fb_mach_info h3600_info __initdata = { - .pixclock = 174757, .bpp = 16, - .xres = 320, .yres = 240, - - .hsync_len = 3, .vsync_len = 3, - .left_margin = 12, .upper_margin = 10, - .right_margin = 17, .lower_margin = 1, - - .cmap_static = 1, - - .lccr0 = LCCR0_Color | LCCR0_Sngl | LCCR0_Act, - .lccr3 = LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2), -}; - -static struct sa1100fb_rgb h3600_rgb_16 = { - .red = { .offset = 12, .length = 4, }, - .green = { .offset = 7, .length = 4, }, - .blue = { .offset = 1, .length = 4, }, - .transp = { .offset = 0, .length = 0, }, -}; -#endif - -#ifdef CONFIG_SA1100_H3100 -static struct sa1100fb_mach_info h3100_info __initdata = { - .pixclock = 406977, .bpp = 4, - .xres = 320, .yres = 240, - - .hsync_len = 26, .vsync_len = 41, - .left_margin = 4, .upper_margin = 0, - .right_margin = 4, .lower_margin = 0, - - .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, - .cmap_greyscale = 1, - .cmap_inverse = 1, - - .lccr0 = LCCR0_Mono | LCCR0_4PixMono | LCCR0_Sngl | LCCR0_Pas, - .lccr3 = LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2), -}; -#endif - -#ifdef CONFIG_SA1100_COLLIE -static struct sa1100fb_mach_info collie_info __initdata = { - .pixclock = 171521, .bpp = 16, - .xres = 320, .yres = 240, - - .hsync_len = 5, .vsync_len = 1, - .left_margin = 11, .upper_margin = 2, - .right_margin = 30, .lower_margin = 0, - - .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, - - .lccr0 = LCCR0_Color | LCCR0_Sngl | LCCR0_Act, - .lccr3 = LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2), -}; -#endif - -#ifdef LART_GREY_LCD -static struct sa1100fb_mach_info lart_grey_info __initdata = { - .pixclock = 150000, .bpp = 4, - .xres = 320, .yres = 240, - - .hsync_len = 1, .vsync_len = 1, - .left_margin = 4, .upper_margin = 0, - .right_margin = 2, .lower_margin = 0, - - .cmap_greyscale = 1, - .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, - - .lccr0 = LCCR0_Mono | LCCR0_Sngl | LCCR0_Pas | LCCR0_4PixMono, - .lccr3 = LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(512), -}; -#endif -#ifdef LART_COLOR_LCD -static struct sa1100fb_mach_info lart_color_info __initdata = { - .pixclock = 150000, .bpp = 16, - .xres = 320, .yres = 240, - - .hsync_len = 2, .vsync_len = 3, - .left_margin = 69, .upper_margin = 14, - .right_margin = 8, .lower_margin = 4, - - .lccr0 = LCCR0_Color | LCCR0_Sngl | LCCR0_Act, - .lccr3 = LCCR3_OutEnH | LCCR3_PixFlEdg | LCCR3_ACBsDiv(512), -}; -#endif -#ifdef LART_VIDEO_OUT -static struct sa1100fb_mach_info lart_video_info __initdata = { - .pixclock = 39721, .bpp = 16, - .xres = 640, .yres = 480, - - .hsync_len = 95, .vsync_len = 2, - .left_margin = 40, .upper_margin = 32, - .right_margin = 24, .lower_margin = 11, - - .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, - - .lccr0 = LCCR0_Color | LCCR0_Sngl | LCCR0_Act, - .lccr3 = LCCR3_OutEnL | LCCR3_PixFlEdg | LCCR3_ACBsDiv(512), -}; -#endif - -#ifdef LART_KIT01_LCD -static struct sa1100fb_mach_info lart_kit01_info __initdata = { - .pixclock = 63291, .bpp = 16, - .xres = 640, .yres = 480, - - .hsync_len = 64, .vsync_len = 3, - .left_margin = 122, .upper_margin = 45, - .right_margin = 10, .lower_margin = 10, - - .lccr0 = LCCR0_Color | LCCR0_Sngl | LCCR0_Act, - .lccr3 = LCCR3_OutEnH | LCCR3_PixFlEdg -}; -#endif - -#ifdef CONFIG_SA1100_SHANNON -static struct sa1100fb_mach_info shannon_info __initdata = { - .pixclock = 152500, .bpp = 8, - .xres = 640, .yres = 480, - - .hsync_len = 4, .vsync_len = 3, - .left_margin = 2, .upper_margin = 0, - .right_margin = 1, .lower_margin = 0, - - .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, - - .lccr0 = LCCR0_Color | LCCR0_Dual | LCCR0_Pas, - .lccr3 = LCCR3_ACBsDiv(512), -}; -#endif - -static struct sa1100fb_mach_info * __init -sa1100fb_get_machine_info(struct sa1100fb_info *fbi) -{ - struct sa1100fb_mach_info *inf = NULL; - - /* - * R G B T - * default {11,5}, { 5,6}, { 0,5}, { 0,0} - * h3600 {12,4}, { 7,4}, { 1,4}, { 0,0} - * freebird { 8,4}, { 4,4}, { 0,4}, {12,4} - */ -#ifdef CONFIG_SA1100_ASSABET - if (machine_is_assabet()) { -#ifndef ASSABET_PAL_VIDEO - inf = &lq039q2ds54_info; -#else - inf = &pal_info; -#endif - } -#endif -#ifdef CONFIG_SA1100_H3100 - if (machine_is_h3100()) { - inf = &h3100_info; - } -#endif -#ifdef CONFIG_SA1100_H3600 - if (machine_is_h3600()) { - inf = &h3600_info; - fbi->rgb[RGB_16] = &h3600_rgb_16; - } -#endif -#ifdef CONFIG_SA1100_COLLIE - if (machine_is_collie()) { - inf = &collie_info; - } -#endif -#ifdef CONFIG_SA1100_LART - if (machine_is_lart()) { -#ifdef LART_GREY_LCD - inf = &lart_grey_info; -#endif -#ifdef LART_COLOR_LCD - inf = &lart_color_info; -#endif -#ifdef LART_VIDEO_OUT - inf = &lart_video_info; -#endif -#ifdef LART_KIT01_LCD - inf = &lart_kit01_info; -#endif - } -#endif -#ifdef CONFIG_SA1100_SHANNON - if (machine_is_shannon()) { - inf = &shannon_info; - } -#endif - return inf; -} - static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_info *); static void set_ctrlr_state(struct sa1100fb_info *fbi, u_int state); @@ -533,7 +299,7 @@ sa1100fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, * is what you poke into the framebuffer to produce the * colour you requested. */ - if (fbi->cmap_inverse) { + if (fbi->inf->cmap_inverse) { red = 0xffff - red; green = 0xffff - green; blue = 0xffff - blue; @@ -607,14 +373,14 @@ sa1100fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) var->xres = MIN_XRES; if (var->yres < MIN_YRES) var->yres = MIN_YRES; - if (var->xres > fbi->max_xres) - var->xres = fbi->max_xres; - if (var->yres > fbi->max_yres) - var->yres = fbi->max_yres; + if (var->xres > fbi->inf->xres) + var->xres = fbi->inf->xres; + if (var->yres > fbi->inf->yres) + var->yres = fbi->inf->yres; var->xres_virtual = max(var->xres_virtual, var->xres); var->yres_virtual = max(var->yres_virtual, var->yres); - DPRINTK("var->bits_per_pixel=%d\n", var->bits_per_pixel); + dev_dbg(fbi->dev, "var->bits_per_pixel=%d\n", var->bits_per_pixel); switch (var->bits_per_pixel) { case 4: rgbidx = RGB_4; @@ -638,16 +404,16 @@ sa1100fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) var->blue = fbi->rgb[rgbidx]->blue; var->transp = fbi->rgb[rgbidx]->transp; - DPRINTK("RGBT length = %d:%d:%d:%d\n", + dev_dbg(fbi->dev, "RGBT length = %d:%d:%d:%d\n", var->red.length, var->green.length, var->blue.length, var->transp.length); - DPRINTK("RGBT offset = %d:%d:%d:%d\n", + dev_dbg(fbi->dev, "RGBT offset = %d:%d:%d:%d\n", var->red.offset, var->green.offset, var->blue.offset, var->transp.offset); #ifdef CONFIG_CPU_FREQ - printk(KERN_DEBUG "dma period = %d ps, clock = %d kHz\n", + dev_dbg(fbi->dev, "dma period = %d ps, clock = %d kHz\n", sa1100fb_display_dma_period(var), cpufreq_get(smp_processor_id())); #endif @@ -655,22 +421,10 @@ sa1100fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return 0; } -static inline void sa1100fb_set_truecolor(u_int is_true_color) +static void sa1100fb_set_visual(struct sa1100fb_info *fbi, u32 visual) { - if (machine_is_assabet()) { -#if 1 // phase 4 or newer Assabet's - if (is_true_color) - ASSABET_BCR_set(ASSABET_BCR_LCD_12RGB); - else - ASSABET_BCR_clear(ASSABET_BCR_LCD_12RGB); -#else - // older Assabet's - if (is_true_color) - ASSABET_BCR_clear(ASSABET_BCR_LCD_12RGB); - else - ASSABET_BCR_set(ASSABET_BCR_LCD_12RGB); -#endif - } + if (fbi->inf->set_visual) + fbi->inf->set_visual(visual); } /* @@ -683,11 +437,11 @@ static int sa1100fb_set_par(struct fb_info *info) struct fb_var_screeninfo *var = &info->var; unsigned long palette_mem_size; - DPRINTK("set_par\n"); + dev_dbg(fbi->dev, "set_par\n"); if (var->bits_per_pixel == 16) fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR; - else if (!fbi->cmap_static) + else if (!fbi->inf->cmap_static) fbi->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; else { /* @@ -704,7 +458,7 @@ static int sa1100fb_set_par(struct fb_info *info) palette_mem_size = fbi->palette_size * sizeof(u16); - DPRINTK("palette_mem_size = 0x%08lx\n", (u_long) palette_mem_size); + dev_dbg(fbi->dev, "palette_mem_size = 0x%08lx\n", palette_mem_size); fbi->palette_cpu = (u16 *)(fbi->map_cpu + PAGE_SIZE - palette_mem_size); fbi->palette_dma = fbi->map_dma + PAGE_SIZE - palette_mem_size; @@ -712,7 +466,7 @@ static int sa1100fb_set_par(struct fb_info *info) /* * Set (any) board control register to handle new color depth */ - sa1100fb_set_truecolor(fbi->fb.fix.visual == FB_VISUAL_TRUECOLOR); + sa1100fb_set_visual(fbi, fbi->fb.fix.visual); sa1100fb_activate_var(var, fbi); return 0; @@ -728,7 +482,7 @@ sa1100fb_set_cmap(struct fb_cmap *cmap, int kspc, int con, /* * Make sure the user isn't doing something stupid. */ - if (!kspc && (fbi->fb.var.bits_per_pixel == 16 || fbi->cmap_static)) + if (!kspc && (fbi->fb.var.bits_per_pixel == 16 || fbi->inf->cmap_static)) return -EINVAL; return gen_set_cmap(cmap, kspc, con, info); @@ -775,7 +529,7 @@ static int sa1100fb_blank(int blank, struct fb_info *info) struct sa1100fb_info *fbi = (struct sa1100fb_info *)info; int i; - DPRINTK("sa1100fb_blank: blank=%d\n", blank); + dev_dbg(fbi->dev, "sa1100fb_blank: blank=%d\n", blank); switch (blank) { case FB_BLANK_POWERDOWN: @@ -863,43 +617,43 @@ static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_ u_int half_screen_size, yres, pcd; u_long flags; - DPRINTK("Configuring SA1100 LCD\n"); + dev_dbg(fbi->dev, "Configuring SA1100 LCD\n"); - DPRINTK("var: xres=%d hslen=%d lm=%d rm=%d\n", + dev_dbg(fbi->dev, "var: xres=%d hslen=%d lm=%d rm=%d\n", var->xres, var->hsync_len, var->left_margin, var->right_margin); - DPRINTK("var: yres=%d vslen=%d um=%d bm=%d\n", + dev_dbg(fbi->dev, "var: yres=%d vslen=%d um=%d bm=%d\n", var->yres, var->vsync_len, var->upper_margin, var->lower_margin); #if DEBUG_VAR if (var->xres < 16 || var->xres > 1024) - printk(KERN_ERR "%s: invalid xres %d\n", + dev_err(fbi->dev, "%s: invalid xres %d\n", fbi->fb.fix.id, var->xres); if (var->hsync_len < 1 || var->hsync_len > 64) - printk(KERN_ERR "%s: invalid hsync_len %d\n", + dev_err(fbi->dev, "%s: invalid hsync_len %d\n", fbi->fb.fix.id, var->hsync_len); if (var->left_margin < 1 || var->left_margin > 255) - printk(KERN_ERR "%s: invalid left_margin %d\n", + dev_err(fbi->dev, "%s: invalid left_margin %d\n", fbi->fb.fix.id, var->left_margin); if (var->right_margin < 1 || var->right_margin > 255) - printk(KERN_ERR "%s: invalid right_margin %d\n", + dev_err(fbi->dev, "%s: invalid right_margin %d\n", fbi->fb.fix.id, var->right_margin); if (var->yres < 1 || var->yres > 1024) - printk(KERN_ERR "%s: invalid yres %d\n", + dev_err(fbi->dev, "%s: invalid yres %d\n", fbi->fb.fix.id, var->yres); if (var->vsync_len < 1 || var->vsync_len > 64) - printk(KERN_ERR "%s: invalid vsync_len %d\n", + dev_err(fbi->dev, "%s: invalid vsync_len %d\n", fbi->fb.fix.id, var->vsync_len); if (var->upper_margin < 0 || var->upper_margin > 255) - printk(KERN_ERR "%s: invalid upper_margin %d\n", + dev_err(fbi->dev, "%s: invalid upper_margin %d\n", fbi->fb.fix.id, var->upper_margin); if (var->lower_margin < 0 || var->lower_margin > 255) - printk(KERN_ERR "%s: invalid lower_margin %d\n", + dev_err(fbi->dev, "%s: invalid lower_margin %d\n", fbi->fb.fix.id, var->lower_margin); #endif - new_regs.lccr0 = fbi->lccr0 | + new_regs.lccr0 = fbi->inf->lccr0 | LCCR0_LEN | LCCR0_LDM | LCCR0_BAM | LCCR0_ERM | LCCR0_LtlEnd | LCCR0_DMADel(0); @@ -914,7 +668,7 @@ static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_ * the YRES parameter. */ yres = var->yres; - if (fbi->lccr0 & LCCR0_Dual) + if (fbi->inf->lccr0 & LCCR0_Dual) yres /= 2; new_regs.lccr2 = @@ -924,14 +678,14 @@ static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_ LCCR2_EndFrmDel(var->lower_margin); pcd = get_pcd(var->pixclock, cpufreq_get(0)); - new_regs.lccr3 = LCCR3_PixClkDiv(pcd) | fbi->lccr3 | + new_regs.lccr3 = LCCR3_PixClkDiv(pcd) | fbi->inf->lccr3 | (var->sync & FB_SYNC_HOR_HIGH_ACT ? LCCR3_HorSnchH : LCCR3_HorSnchL) | (var->sync & FB_SYNC_VERT_HIGH_ACT ? LCCR3_VrtSnchH : LCCR3_VrtSnchL); - DPRINTK("nlccr0 = 0x%08lx\n", new_regs.lccr0); - DPRINTK("nlccr1 = 0x%08lx\n", new_regs.lccr1); - DPRINTK("nlccr2 = 0x%08lx\n", new_regs.lccr2); - DPRINTK("nlccr3 = 0x%08lx\n", new_regs.lccr3); + dev_dbg(fbi->dev, "nlccr0 = 0x%08lx\n", new_regs.lccr0); + dev_dbg(fbi->dev, "nlccr1 = 0x%08lx\n", new_regs.lccr1); + dev_dbg(fbi->dev, "nlccr2 = 0x%08lx\n", new_regs.lccr2); + dev_dbg(fbi->dev, "nlccr3 = 0x%08lx\n", new_regs.lccr3); half_screen_size = var->bits_per_pixel; half_screen_size = half_screen_size * var->xres * var->yres / 16; @@ -951,9 +705,12 @@ static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_ * Only update the registers if the controller is enabled * and something has changed. */ - if ((LCCR0 != fbi->reg_lccr0) || (LCCR1 != fbi->reg_lccr1) || - (LCCR2 != fbi->reg_lccr2) || (LCCR3 != fbi->reg_lccr3) || - (DBAR1 != fbi->dbar1) || (DBAR2 != fbi->dbar2)) + if (readl_relaxed(fbi->base + LCCR0) != fbi->reg_lccr0 || + readl_relaxed(fbi->base + LCCR1) != fbi->reg_lccr1 || + readl_relaxed(fbi->base + LCCR2) != fbi->reg_lccr2 || + readl_relaxed(fbi->base + LCCR3) != fbi->reg_lccr3 || + readl_relaxed(fbi->base + DBAR1) != fbi->dbar1 || + readl_relaxed(fbi->base + DBAR2) != fbi->dbar2) sa1100fb_schedule_work(fbi, C_REENABLE); return 0; @@ -967,18 +724,18 @@ static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_ */ static inline void __sa1100fb_backlight_power(struct sa1100fb_info *fbi, int on) { - DPRINTK("backlight o%s\n", on ? "n" : "ff"); + dev_dbg(fbi->dev, "backlight o%s\n", on ? "n" : "ff"); - if (sa1100fb_backlight_power) - sa1100fb_backlight_power(on); + if (fbi->inf->backlight_power) + fbi->inf->backlight_power(on); } static inline void __sa1100fb_lcd_power(struct sa1100fb_info *fbi, int on) { - DPRINTK("LCD power o%s\n", on ? "n" : "ff"); + dev_dbg(fbi->dev, "LCD power o%s\n", on ? "n" : "ff"); - if (sa1100fb_lcd_power) - sa1100fb_lcd_power(on); + if (fbi->inf->lcd_power) + fbi->inf->lcd_power(on); } static void sa1100fb_setup_gpio(struct sa1100fb_info *fbi) @@ -1008,14 +765,25 @@ static void sa1100fb_setup_gpio(struct sa1100fb_info *fbi) } if (mask) { + unsigned long flags; + + /* + * SA-1100 requires the GPIO direction register set + * appropriately for the alternate function. Hence + * we set it here via bitmask rather than excessive + * fiddling via the GPIO subsystem - and even then + * we'll still have to deal with GAFR. + */ + local_irq_save(flags); GPDR |= mask; GAFR |= mask; + local_irq_restore(flags); } } static void sa1100fb_enable_controller(struct sa1100fb_info *fbi) { - DPRINTK("Enabling LCD controller\n"); + dev_dbg(fbi->dev, "Enabling LCD controller\n"); /* * Make sure the mode bits are present in the first palette entry @@ -1024,43 +792,46 @@ static void sa1100fb_enable_controller(struct sa1100fb_info *fbi) fbi->palette_cpu[0] |= palette_pbs(&fbi->fb.var); /* Sequence from 11.7.10 */ - LCCR3 = fbi->reg_lccr3; - LCCR2 = fbi->reg_lccr2; - LCCR1 = fbi->reg_lccr1; - LCCR0 = fbi->reg_lccr0 & ~LCCR0_LEN; - DBAR1 = fbi->dbar1; - DBAR2 = fbi->dbar2; - LCCR0 |= LCCR0_LEN; - - if (machine_is_shannon()) { - GPDR |= SHANNON_GPIO_DISP_EN; - GPSR |= SHANNON_GPIO_DISP_EN; - } - - DPRINTK("DBAR1 = 0x%08x\n", DBAR1); - DPRINTK("DBAR2 = 0x%08x\n", DBAR2); - DPRINTK("LCCR0 = 0x%08x\n", LCCR0); - DPRINTK("LCCR1 = 0x%08x\n", LCCR1); - DPRINTK("LCCR2 = 0x%08x\n", LCCR2); - DPRINTK("LCCR3 = 0x%08x\n", LCCR3); + writel_relaxed(fbi->reg_lccr3, fbi->base + LCCR3); + writel_relaxed(fbi->reg_lccr2, fbi->base + LCCR2); + writel_relaxed(fbi->reg_lccr1, fbi->base + LCCR1); + writel_relaxed(fbi->reg_lccr0 & ~LCCR0_LEN, fbi->base + LCCR0); + writel_relaxed(fbi->dbar1, fbi->base + DBAR1); + writel_relaxed(fbi->dbar2, fbi->base + DBAR2); + writel_relaxed(fbi->reg_lccr0 | LCCR0_LEN, fbi->base + LCCR0); + + if (machine_is_shannon()) + gpio_set_value(SHANNON_GPIO_DISP_EN, 1); + + dev_dbg(fbi->dev, "DBAR1: 0x%08x\n", readl_relaxed(fbi->base + DBAR1)); + dev_dbg(fbi->dev, "DBAR2: 0x%08x\n", readl_relaxed(fbi->base + DBAR2)); + dev_dbg(fbi->dev, "LCCR0: 0x%08x\n", readl_relaxed(fbi->base + LCCR0)); + dev_dbg(fbi->dev, "LCCR1: 0x%08x\n", readl_relaxed(fbi->base + LCCR1)); + dev_dbg(fbi->dev, "LCCR2: 0x%08x\n", readl_relaxed(fbi->base + LCCR2)); + dev_dbg(fbi->dev, "LCCR3: 0x%08x\n", readl_relaxed(fbi->base + LCCR3)); } static void sa1100fb_disable_controller(struct sa1100fb_info *fbi) { DECLARE_WAITQUEUE(wait, current); + u32 lccr0; - DPRINTK("Disabling LCD controller\n"); + dev_dbg(fbi->dev, "Disabling LCD controller\n"); - if (machine_is_shannon()) { - GPCR |= SHANNON_GPIO_DISP_EN; - } + if (machine_is_shannon()) + gpio_set_value(SHANNON_GPIO_DISP_EN, 0); set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&fbi->ctrlr_wait, &wait); - LCSR = 0xffffffff; /* Clear LCD Status Register */ - LCCR0 &= ~LCCR0_LDM; /* Enable LCD Disable Done Interrupt */ - LCCR0 &= ~LCCR0_LEN; /* Disable LCD Controller */ + /* Clear LCD Status Register */ + writel_relaxed(~0, fbi->base + LCSR); + + lccr0 = readl_relaxed(fbi->base + LCCR0); + lccr0 &= ~LCCR0_LDM; /* Enable LCD Disable Done Interrupt */ + writel_relaxed(lccr0, fbi->base + LCCR0); + lccr0 &= ~LCCR0_LEN; /* Disable LCD Controller */ + writel_relaxed(lccr0, fbi->base + LCCR0); schedule_timeout(20 * HZ / 1000); remove_wait_queue(&fbi->ctrlr_wait, &wait); @@ -1072,14 +843,15 @@ static void sa1100fb_disable_controller(struct sa1100fb_info *fbi) static irqreturn_t sa1100fb_handle_irq(int irq, void *dev_id) { struct sa1100fb_info *fbi = dev_id; - unsigned int lcsr = LCSR; + unsigned int lcsr = readl_relaxed(fbi->base + LCSR); if (lcsr & LCSR_LDD) { - LCCR0 |= LCCR0_LDM; + u32 lccr0 = readl_relaxed(fbi->base + LCCR0) | LCCR0_LDM; + writel_relaxed(lccr0, fbi->base + LCCR0); wake_up(&fbi->ctrlr_wait); } - LCSR = lcsr; + writel_relaxed(lcsr, fbi->base + LCSR); return IRQ_HANDLED; } @@ -1268,7 +1040,7 @@ sa1100fb_freq_policy(struct notifier_block *nb, unsigned long val, switch (val) { case CPUFREQ_ADJUST: case CPUFREQ_INCOMPATIBLE: - printk(KERN_DEBUG "min dma period: %d ps, " + dev_dbg(fbi->dev, "min dma period: %d ps, " "new clock %d kHz\n", sa1100fb_min_dma_period(fbi), policy->max); /* todo: fill in min/max values */ @@ -1318,7 +1090,7 @@ static int sa1100fb_resume(struct platform_device *dev) * cache. Once this area is remapped, all virtual memory * access to the video memory should occur at the new region. */ -static int __init sa1100fb_map_video_memory(struct sa1100fb_info *fbi) +static int __devinit sa1100fb_map_video_memory(struct sa1100fb_info *fbi) { /* * We reserve one page for the palette, plus the size @@ -1344,7 +1116,7 @@ static int __init sa1100fb_map_video_memory(struct sa1100fb_info *fbi) } /* Fake monspecs to fill in fbinfo structure */ -static struct fb_monspecs monspecs __initdata = { +static struct fb_monspecs monspecs __devinitdata = { .hfmin = 30000, .hfmax = 70000, .vfmin = 50, @@ -1352,10 +1124,11 @@ static struct fb_monspecs monspecs __initdata = { }; -static struct sa1100fb_info * __init sa1100fb_init_fbinfo(struct device *dev) +static struct sa1100fb_info * __devinit sa1100fb_init_fbinfo(struct device *dev) { - struct sa1100fb_mach_info *inf; + struct sa1100fb_mach_info *inf = dev->platform_data; struct sa1100fb_info *fbi; + unsigned i; fbi = kmalloc(sizeof(struct sa1100fb_info) + sizeof(u32) * 16, GFP_KERNEL); @@ -1390,8 +1163,6 @@ static struct sa1100fb_info * __init sa1100fb_init_fbinfo(struct device *dev) fbi->rgb[RGB_8] = &rgb_8; fbi->rgb[RGB_16] = &def_rgb_16; - inf = sa1100fb_get_machine_info(fbi); - /* * People just don't seem to get this. We don't support * anything but correct entries now, so panic if someone @@ -1402,13 +1173,10 @@ static struct sa1100fb_info * __init sa1100fb_init_fbinfo(struct device *dev) panic("sa1100fb error: invalid LCCR3 fields set or zero " "pixclock."); - fbi->max_xres = inf->xres; fbi->fb.var.xres = inf->xres; fbi->fb.var.xres_virtual = inf->xres; - fbi->max_yres = inf->yres; fbi->fb.var.yres = inf->yres; fbi->fb.var.yres_virtual = inf->yres; - fbi->max_bpp = inf->bpp; fbi->fb.var.bits_per_pixel = inf->bpp; fbi->fb.var.pixclock = inf->pixclock; fbi->fb.var.hsync_len = inf->hsync_len; @@ -1419,14 +1187,16 @@ static struct sa1100fb_info * __init sa1100fb_init_fbinfo(struct device *dev) fbi->fb.var.lower_margin = inf->lower_margin; fbi->fb.var.sync = inf->sync; fbi->fb.var.grayscale = inf->cmap_greyscale; - fbi->cmap_inverse = inf->cmap_inverse; - fbi->cmap_static = inf->cmap_static; - fbi->lccr0 = inf->lccr0; - fbi->lccr3 = inf->lccr3; fbi->state = C_STARTUP; fbi->task_state = (u_char)-1; - fbi->fb.fix.smem_len = fbi->max_xres * fbi->max_yres * - fbi->max_bpp / 8; + fbi->fb.fix.smem_len = inf->xres * inf->yres * + inf->bpp / 8; + fbi->inf = inf; + + /* Copy the RGB bitfield overrides */ + for (i = 0; i < NR_RGB; i++) + if (inf->rgb[i]) + fbi->rgb[i] = inf->rgb[i]; init_waitqueue_head(&fbi->ctrlr_wait); INIT_WORK(&fbi->task, sa1100fb_task); @@ -1438,13 +1208,20 @@ static struct sa1100fb_info * __init sa1100fb_init_fbinfo(struct device *dev) static int __devinit sa1100fb_probe(struct platform_device *pdev) { struct sa1100fb_info *fbi; + struct resource *res; int ret, irq; + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform LCD data\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0 || !res) return -EINVAL; - if (!request_mem_region(0xb0100000, 0x10000, "LCD")) + if (!request_mem_region(res->start, resource_size(res), "LCD")) return -EBUSY; fbi = sa1100fb_init_fbinfo(&pdev->dev); @@ -1452,6 +1229,10 @@ static int __devinit sa1100fb_probe(struct platform_device *pdev) if (!fbi) goto failed; + fbi->base = ioremap(res->start, resource_size(res)); + if (!fbi->base) + goto failed; + /* Initialize video memory */ ret = sa1100fb_map_video_memory(fbi); if (ret) @@ -1459,14 +1240,16 @@ static int __devinit sa1100fb_probe(struct platform_device *pdev) ret = request_irq(irq, sa1100fb_handle_irq, 0, "LCD", fbi); if (ret) { - printk(KERN_ERR "sa1100fb: request_irq failed: %d\n", ret); + dev_err(&pdev->dev, "request_irq failed: %d\n", ret); goto failed; } -#ifdef ASSABET_PAL_VIDEO - if (machine_is_assabet()) - ASSABET_BCR_clear(ASSABET_BCR_LCD_ON); -#endif + if (machine_is_shannon()) { + ret = gpio_request_one(SHANNON_GPIO_DISP_EN, + GPIOF_OUT_INIT_LOW, "display enable"); + if (ret) + goto err_free_irq; + } /* * This makes sure that our colour bitfield @@ -1478,7 +1261,7 @@ static int __devinit sa1100fb_probe(struct platform_device *pdev) ret = register_framebuffer(&fbi->fb); if (ret < 0) - goto err_free_irq; + goto err_reg_fb; #ifdef CONFIG_CPU_FREQ fbi->freq_transition.notifier_call = sa1100fb_freq_transition; @@ -1490,12 +1273,17 @@ static int __devinit sa1100fb_probe(struct platform_device *pdev) /* This driver cannot be unloaded at the moment */ return 0; + err_reg_fb: + if (machine_is_shannon()) + gpio_free(SHANNON_GPIO_DISP_EN); err_free_irq: free_irq(irq, fbi); failed: + if (fbi) + iounmap(fbi->base); platform_set_drvdata(pdev, NULL); kfree(fbi); - release_mem_region(0xb0100000, 0x10000); + release_mem_region(res->start, resource_size(res)); return ret; } @@ -1505,6 +1293,7 @@ static struct platform_driver sa1100fb_driver = { .resume = sa1100fb_resume, .driver = { .name = "sa11x0-fb", + .owner = THIS_MODULE, }, }; diff --git a/drivers/video/sa1100fb.h b/drivers/video/sa1100fb.h index 1c3b459865d8..fc5d4292fad6 100644 --- a/drivers/video/sa1100fb.h +++ b/drivers/video/sa1100fb.h @@ -10,44 +10,15 @@ * for more details. */ -/* - * These are the bitfields for each - * display depth that we support. - */ -struct sa1100fb_rgb { - struct fb_bitfield red; - struct fb_bitfield green; - struct fb_bitfield blue; - struct fb_bitfield transp; -}; - -/* - * This structure describes the machine which we are running on. - */ -struct sa1100fb_mach_info { - u_long pixclock; - - u_short xres; - u_short yres; - - u_char bpp; - u_char hsync_len; - u_char left_margin; - u_char right_margin; - - u_char vsync_len; - u_char upper_margin; - u_char lower_margin; - u_char sync; - - u_int cmap_greyscale:1, - cmap_inverse:1, - cmap_static:1, - unused:29; - - u_int lccr0; - u_int lccr3; -}; +#define LCCR0 0x0000 /* LCD Control Reg. 0 */ +#define LCSR 0x0004 /* LCD Status Reg. */ +#define DBAR1 0x0010 /* LCD DMA Base Address Reg. channel 1 */ +#define DCAR1 0x0014 /* LCD DMA Current Address Reg. channel 1 */ +#define DBAR2 0x0018 /* LCD DMA Base Address Reg. channel 2 */ +#define DCAR2 0x001C /* LCD DMA Current Address Reg. channel 2 */ +#define LCCR1 0x0020 /* LCD Control Reg. 1 */ +#define LCCR2 0x0024 /* LCD Control Reg. 2 */ +#define LCCR3 0x0028 /* LCD Control Reg. 3 */ /* Shadows for LCD controller registers */ struct sa1100fb_lcd_reg { @@ -57,19 +28,11 @@ struct sa1100fb_lcd_reg { unsigned long lccr3; }; -#define RGB_4 (0) -#define RGB_8 (1) -#define RGB_16 (2) -#define NR_RGB 3 - struct sa1100fb_info { struct fb_info fb; struct device *dev; - struct sa1100fb_rgb *rgb[NR_RGB]; - - u_int max_bpp; - u_int max_xres; - u_int max_yres; + const struct sa1100fb_rgb *rgb[NR_RGB]; + void __iomem *base; /* * These are the addresses we mapped @@ -88,12 +51,6 @@ struct sa1100fb_info { dma_addr_t dbar1; dma_addr_t dbar2; - u_int lccr0; - u_int lccr3; - u_int cmap_inverse:1, - cmap_static:1, - unused:30; - u_int reg_lccr0; u_int reg_lccr1; u_int reg_lccr2; @@ -109,6 +66,8 @@ struct sa1100fb_info { struct notifier_block freq_transition; struct notifier_block freq_policy; #endif + + const struct sa1100fb_mach_info *inf; }; #define TO_INF(ptr,member) container_of(ptr,struct sa1100fb_info,member) @@ -130,15 +89,6 @@ struct sa1100fb_info { #define SA1100_NAME "SA1100" /* - * Debug macros - */ -#if DEBUG -# define DPRINTK(fmt, args...) printk("%s: " fmt, __func__ , ## args) -#else -# define DPRINTK(fmt, args...) -#endif - -/* * Minimum X and Y resolutions */ #define MIN_XRES 64 diff --git a/drivers/video/uvesafb.c b/drivers/video/uvesafb.c index 9db3de3a8418..260cca7ddb41 100644 --- a/drivers/video/uvesafb.c +++ b/drivers/video/uvesafb.c @@ -121,7 +121,7 @@ static int uvesafb_helper_start(void) NULL, }; - return call_usermodehelper(v86d_path, argv, envp, 1); + return call_usermodehelper(v86d_path, argv, envp, UMH_WAIT_PROC); } /* diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 7e9e8f4d8f0c..0e7366dfc901 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -170,7 +170,7 @@ config HAVE_S3C2410_WATCHDOG config S3C2410_WATCHDOG tristate "S3C2410 Watchdog" - depends on ARCH_S3C2410 || HAVE_S3C2410_WATCHDOG + depends on HAVE_S3C2410_WATCHDOG select WATCHDOG_CORE help Watchdog timer block in the Samsung SoCs. This will reboot diff --git a/drivers/watchdog/at91rm9200_wdt.c b/drivers/watchdog/at91rm9200_wdt.c index b3046dc4b56c..7ceefd29ae14 100644 --- a/drivers/watchdog/at91rm9200_wdt.c +++ b/drivers/watchdog/at91rm9200_wdt.c @@ -51,7 +51,7 @@ static unsigned long at91wdt_busy; */ static inline void at91_wdt_stop(void) { - at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN); + at91_st_write(AT91_ST_WDMR, AT91_ST_EXTEN); } /* @@ -59,9 +59,9 @@ static inline void at91_wdt_stop(void) */ static inline void at91_wdt_start(void) { - at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN | + at91_st_write(AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN | (((65536 * wdt_time) >> 8) & AT91_ST_WDV)); - at91_sys_write(AT91_ST_CR, AT91_ST_WDRST); + at91_st_write(AT91_ST_CR, AT91_ST_WDRST); } /* @@ -69,7 +69,7 @@ static inline void at91_wdt_start(void) */ static inline void at91_wdt_reload(void) { - at91_sys_write(AT91_ST_CR, AT91_ST_WDRST); + at91_st_write(AT91_ST_CR, AT91_ST_WDRST); } /* ......................................................................... */ diff --git a/drivers/watchdog/ep93xx_wdt.c b/drivers/watchdog/ep93xx_wdt.c index 726b7df61fd0..09cd888db619 100644 --- a/drivers/watchdog/ep93xx_wdt.c +++ b/drivers/watchdog/ep93xx_wdt.c @@ -23,6 +23,7 @@ * - Add a few missing ioctls */ +#include <linux/platform_device.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/miscdevice.h> @@ -30,7 +31,6 @@ #include <linux/timer.h> #include <linux/uaccess.h> #include <linux/io.h> -#include <mach/hardware.h> #define WDT_VERSION "0.3" #define PFX "ep93xx_wdt: " @@ -41,6 +41,7 @@ static int nowayout = WATCHDOG_NOWAYOUT; static int timeout = WDT_TIMEOUT; +static void __iomem *mmio_base; static struct timer_list timer; static unsigned long next_heartbeat; static unsigned long wdt_status; @@ -49,26 +50,25 @@ static unsigned long boot_status; #define WDT_IN_USE 0 #define WDT_OK_TO_CLOSE 1 -#define EP93XX_WDT_REG(x) (EP93XX_WATCHDOG_BASE + (x)) -#define EP93XX_WDT_WATCHDOG EP93XX_WDT_REG(0x00) -#define EP93XX_WDT_WDSTATUS EP93XX_WDT_REG(0x04) +#define EP93XX_WATCHDOG 0x00 +#define EP93XX_WDSTATUS 0x04 /* reset the wdt every ~200ms */ #define WDT_INTERVAL (HZ/5) static void wdt_enable(void) { - __raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG); + writel(0xaaaa, mmio_base + EP93XX_WATCHDOG); } static void wdt_disable(void) { - __raw_writew(0xaa55, EP93XX_WDT_WATCHDOG); + writel(0xaa55, mmio_base + EP93XX_WATCHDOG); } static inline void wdt_ping(void) { - __raw_writew(0x5555, EP93XX_WDT_WATCHDOG); + writel(0x5555, mmio_base + EP93XX_WATCHDOG); } static void wdt_startup(void) @@ -206,18 +206,32 @@ static void ep93xx_timer_ping(unsigned long data) mod_timer(&timer, jiffies + WDT_INTERVAL); } -static int __init ep93xx_wdt_init(void) +static int __devinit ep93xx_wdt_probe(struct platform_device *pdev) { + struct resource *res; + unsigned long val; int err; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) + return -EBUSY; + + mmio_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!mmio_base) + return -ENXIO; + err = misc_register(&ep93xx_wdt_miscdev); - boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0; + val = readl(mmio_base + EP93XX_WATCHDOG); + boot_status = val & 0x01 ? 1 : 0; printk(KERN_INFO PFX "EP93XX watchdog, driver version " WDT_VERSION "%s\n", - (__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08) - ? " (nCS1 disable detected)" : ""); + (val & 0x08) ? " (nCS1 disable detected)" : ""); if (timeout < 1 || timeout > 3600) { timeout = WDT_TIMEOUT; @@ -230,14 +244,23 @@ static int __init ep93xx_wdt_init(void) return err; } -static void __exit ep93xx_wdt_exit(void) +static int __devexit ep93xx_wdt_remove(struct platform_device *pdev) { wdt_shutdown(); misc_deregister(&ep93xx_wdt_miscdev); + return 0; } -module_init(ep93xx_wdt_init); -module_exit(ep93xx_wdt_exit); +static struct platform_driver ep93xx_wdt_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ep93xx-wdt", + }, + .probe = ep93xx_wdt_probe, + .remove = __devexit_p(ep93xx_wdt_remove), +}; + +module_platform_driver(ep93xx_wdt_driver); module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c index 4ad78f868515..1368e4ca3100 100644 --- a/drivers/watchdog/orion_wdt.c +++ b/drivers/watchdog/orion_wdt.c @@ -28,9 +28,9 @@ /* * Watchdog timer block registers. */ -#define TIMER_CTRL (TIMER_VIRT_BASE + 0x0000) +#define TIMER_CTRL 0x0000 #define WDT_EN 0x0010 -#define WDT_VAL (TIMER_VIRT_BASE + 0x0024) +#define WDT_VAL 0x0024 #define WDT_MAX_CYCLE_COUNT 0xffffffff #define WDT_IN_USE 0 @@ -40,6 +40,7 @@ static int nowayout = WATCHDOG_NOWAYOUT; static int heartbeat = -1; /* module parameter (seconds) */ static unsigned int wdt_max_duration; /* (seconds) */ static unsigned int wdt_tclk; +static void __iomem *wdt_reg; static unsigned long wdt_status; static DEFINE_SPINLOCK(wdt_lock); @@ -48,7 +49,7 @@ static void orion_wdt_ping(void) spin_lock(&wdt_lock); /* Reload watchdog duration */ - writel(wdt_tclk * heartbeat, WDT_VAL); + writel(wdt_tclk * heartbeat, wdt_reg + WDT_VAL); spin_unlock(&wdt_lock); } @@ -60,7 +61,7 @@ static void orion_wdt_enable(void) spin_lock(&wdt_lock); /* Set watchdog duration */ - writel(wdt_tclk * heartbeat, WDT_VAL); + writel(wdt_tclk * heartbeat, wdt_reg + WDT_VAL); /* Clear watchdog timer interrupt */ reg = readl(BRIDGE_CAUSE); @@ -68,9 +69,9 @@ static void orion_wdt_enable(void) writel(reg, BRIDGE_CAUSE); /* Enable watchdog timer */ - reg = readl(TIMER_CTRL); + reg = readl(wdt_reg + TIMER_CTRL); reg |= WDT_EN; - writel(reg, TIMER_CTRL); + writel(reg, wdt_reg + TIMER_CTRL); /* Enable reset on watchdog */ reg = readl(RSTOUTn_MASK); @@ -92,9 +93,9 @@ static void orion_wdt_disable(void) writel(reg, RSTOUTn_MASK); /* Disable watchdog timer */ - reg = readl(TIMER_CTRL); + reg = readl(wdt_reg + TIMER_CTRL); reg &= ~WDT_EN; - writel(reg, TIMER_CTRL); + writel(reg, wdt_reg + TIMER_CTRL); spin_unlock(&wdt_lock); } @@ -102,7 +103,7 @@ static void orion_wdt_disable(void) static int orion_wdt_get_timeleft(int *time_left) { spin_lock(&wdt_lock); - *time_left = readl(WDT_VAL) / wdt_tclk; + *time_left = readl(wdt_reg + WDT_VAL) / wdt_tclk; spin_unlock(&wdt_lock); return 0; } @@ -236,6 +237,7 @@ static struct miscdevice orion_wdt_miscdev = { static int __devinit orion_wdt_probe(struct platform_device *pdev) { struct orion_wdt_platform_data *pdata = pdev->dev.platform_data; + struct resource *res; int ret; if (pdata) { @@ -245,6 +247,10 @@ static int __devinit orion_wdt_probe(struct platform_device *pdev) return -ENODEV; } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + wdt_reg = ioremap(res->start, resource_size(res)); + if (orion_wdt_miscdev.parent) return -EBUSY; orion_wdt_miscdev.parent = &pdev->dev; diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index eef1524ae52e..3ff9e47bd218 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -370,17 +370,7 @@ static struct amba_driver sp805_wdt_driver = { .remove = __devexit_p(sp805_wdt_remove), }; -static int __init sp805_wdt_init(void) -{ - return amba_driver_register(&sp805_wdt_driver); -} -module_init(sp805_wdt_init); - -static void __exit sp805_wdt_exit(void) -{ - amba_driver_unregister(&sp805_wdt_driver); -} -module_exit(sp805_wdt_exit); +module_amba_driver(sp805_wdt_driver); module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, diff --git a/drivers/xen/Kconfig b/drivers/xen/Kconfig index 648bcd4195c5..94243136f6bf 100644 --- a/drivers/xen/Kconfig +++ b/drivers/xen/Kconfig @@ -180,9 +180,8 @@ config XEN_PRIVCMD config XEN_ACPI_PROCESSOR tristate "Xen ACPI processor" - depends on XEN && X86 && ACPI_PROCESSOR - default y if (X86_ACPI_CPUFREQ = y || X86_POWERNOW_K8 = y) - default m if (X86_ACPI_CPUFREQ = m || X86_POWERNOW_K8 = m) + depends on XEN && X86 && ACPI_PROCESSOR && CPU_FREQ + default m help This ACPI processor uploads Power Management information to the Xen hypervisor. diff --git a/drivers/xen/events.c b/drivers/xen/events.c index e5e5812a1014..4b33acd8ed4e 100644 --- a/drivers/xen/events.c +++ b/drivers/xen/events.c @@ -37,6 +37,7 @@ #include <asm/idle.h> #include <asm/io_apic.h> #include <asm/sync_bitops.h> +#include <asm/xen/page.h> #include <asm/xen/pci.h> #include <asm/xen/hypercall.h> #include <asm/xen/hypervisor.h> @@ -109,6 +110,8 @@ struct irq_info { #define PIRQ_SHAREABLE (1 << 1) static int *evtchn_to_irq; +static unsigned long *pirq_eoi_map; +static bool (*pirq_needs_eoi)(unsigned irq); static DEFINE_PER_CPU(unsigned long [NR_EVENT_CHANNELS/BITS_PER_LONG], cpu_evtchn_mask); @@ -269,10 +272,14 @@ static unsigned int cpu_from_evtchn(unsigned int evtchn) return ret; } -static bool pirq_needs_eoi(unsigned irq) +static bool pirq_check_eoi_map(unsigned irq) { - struct irq_info *info = info_for_irq(irq); + return test_bit(irq, pirq_eoi_map); +} +static bool pirq_needs_eoi_flag(unsigned irq) +{ + struct irq_info *info = info_for_irq(irq); BUG_ON(info->type != IRQT_PIRQ); return info->u.pirq.flags & PIRQ_NEEDS_EOI; @@ -1768,7 +1775,7 @@ void xen_callback_vector(void) {} void __init xen_init_IRQ(void) { - int i; + int i, rc; evtchn_to_irq = kcalloc(NR_EVENT_CHANNELS, sizeof(*evtchn_to_irq), GFP_KERNEL); @@ -1782,6 +1789,8 @@ void __init xen_init_IRQ(void) for (i = 0; i < NR_EVENT_CHANNELS; i++) mask_evtchn(i); + pirq_needs_eoi = pirq_needs_eoi_flag; + if (xen_hvm_domain()) { xen_callback_vector(); native_init_IRQ(); @@ -1789,8 +1798,19 @@ void __init xen_init_IRQ(void) * __acpi_register_gsi can point at the right function */ pci_xen_hvm_init(); } else { + struct physdev_pirq_eoi_gmfn eoi_gmfn; + irq_ctx_init(smp_processor_id()); if (xen_initial_domain()) pci_xen_initial_domain(); + + pirq_eoi_map = (void *)__get_free_page(GFP_KERNEL|__GFP_ZERO); + eoi_gmfn.gmfn = virt_to_mfn(pirq_eoi_map); + rc = HYPERVISOR_physdev_op(PHYSDEVOP_pirq_eoi_gmfn_v2, &eoi_gmfn); + if (rc != 0) { + free_page((unsigned long) pirq_eoi_map); + pirq_eoi_map = NULL; + } else + pirq_needs_eoi = pirq_check_eoi_map; } } diff --git a/drivers/xen/platform-pci.c b/drivers/xen/platform-pci.c index 319dd0a94d51..2389e581e23c 100644 --- a/drivers/xen/platform-pci.c +++ b/drivers/xen/platform-pci.c @@ -186,11 +186,6 @@ static struct pci_driver platform_driver = { static int __init platform_pci_module_init(void) { - /* no unplug has been done, IGNORE hasn't been specified: just - * return now */ - if (!xen_platform_pci_unplug) - return -ENODEV; - return pci_register_driver(&platform_driver); } diff --git a/drivers/xen/tmem.c b/drivers/xen/tmem.c index 17d9e37beba4..dcb79521e6c8 100644 --- a/drivers/xen/tmem.c +++ b/drivers/xen/tmem.c @@ -9,7 +9,6 @@ #include <linux/types.h> #include <linux/init.h> #include <linux/pagemap.h> -#include <linux/module.h> #include <linux/cleancache.h> /* temporary ifdef until include/linux/frontswap.h is upstream */ @@ -128,15 +127,13 @@ 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); } -int tmem_enabled __read_mostly; -EXPORT_SYMBOL(tmem_enabled); +bool __read_mostly tmem_enabled = false; static int __init enable_tmem(char *s) { - tmem_enabled = 1; + tmem_enabled = true; return 1; } - __setup("tmem", enable_tmem); #ifdef CONFIG_CLEANCACHE @@ -229,17 +226,16 @@ static int tmem_cleancache_init_shared_fs(char *uuid, size_t pagesize) return xen_tmem_new_pool(shared_uuid, TMEM_POOL_SHARED, pagesize); } -static int use_cleancache = 1; +static bool __initdata use_cleancache = true; static int __init no_cleancache(char *s) { - use_cleancache = 0; + use_cleancache = false; return 1; } - __setup("nocleancache", no_cleancache); -static struct cleancache_ops tmem_cleancache_ops = { +static struct cleancache_ops __initdata tmem_cleancache_ops = { .put_page = tmem_cleancache_put_page, .get_page = tmem_cleancache_get_page, .invalidate_page = tmem_cleancache_flush_page, @@ -356,17 +352,16 @@ static void tmem_frontswap_init(unsigned ignored) xen_tmem_new_pool(private, TMEM_POOL_PERSIST, PAGE_SIZE); } -static int __initdata use_frontswap = 1; +static bool __initdata use_frontswap = true; static int __init no_frontswap(char *s) { - use_frontswap = 0; + use_frontswap = false; return 1; } - __setup("nofrontswap", no_frontswap); -static struct frontswap_ops tmem_frontswap_ops = { +static struct frontswap_ops __initdata tmem_frontswap_ops = { .put_page = tmem_frontswap_put_page, .get_page = tmem_frontswap_get_page, .invalidate_page = tmem_frontswap_flush_page, diff --git a/drivers/xen/xen-acpi-processor.c b/drivers/xen/xen-acpi-processor.c index 5c2be963aa18..174b5653cd8a 100644 --- a/drivers/xen/xen-acpi-processor.c +++ b/drivers/xen/xen-acpi-processor.c @@ -501,11 +501,11 @@ static int __init xen_acpi_processor_init(void) perf = per_cpu_ptr(acpi_perf_data, i); rc = acpi_processor_register_performance(perf, i); - if (WARN_ON(rc)) + if (rc) goto err_out; } rc = acpi_processor_notify_smm(THIS_MODULE); - if (WARN_ON(rc)) + if (rc) goto err_unregister; for_each_possible_cpu(i) { |