aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristoph Lameter <cl@linux-foundation.org>2009-12-18 17:26:20 -0500
committerPekka Enberg <penberg@cs.helsinki.fi>2009-12-20 02:29:18 -0500
commit9dfc6e68bfe6ee452efb1a4e9ca26a9007f2b864 (patch)
tree40e54f2819e176ceb95b8899265bd48751965c27
parent55639353a0035052d9ea6cfe4dde0ac7fcbb2c9f (diff)
SLUB: Use this_cpu operations in slub
Using per cpu allocations removes the needs for the per cpu arrays in the kmem_cache struct. These could get quite big if we have to support systems with thousands of cpus. The use of this_cpu_xx operations results in: 1. The size of kmem_cache for SMP configuration shrinks since we will only need 1 pointer instead of NR_CPUS. The same pointer can be used by all processors. Reduces cache footprint of the allocator. 2. We can dynamically size kmem_cache according to the actual nodes in the system meaning less memory overhead for configurations that may potentially support up to 1k NUMA nodes / 4k cpus. 3. We can remove the diddle widdle with allocating and releasing of kmem_cache_cpu structures when bringing up and shutting down cpus. The cpu alloc logic will do it all for us. Removes some portions of the cpu hotplug functionality. 4. Fastpath performance increases since per cpu pointer lookups and address calculations are avoided. V7-V8 - Convert missed get_cpu_slab() under CONFIG_SLUB_STATS Signed-off-by: Christoph Lameter <cl@linux-foundation.org> Signed-off-by: Pekka Enberg <penberg@cs.helsinki.fi>
-rw-r--r--include/linux/slub_def.h6
-rw-r--r--mm/slub.c202
2 files changed, 49 insertions, 159 deletions
diff --git a/include/linux/slub_def.h b/include/linux/slub_def.h
index 1e14beb23f9b..17ebe0f89bf3 100644
--- a/include/linux/slub_def.h
+++ b/include/linux/slub_def.h
@@ -69,6 +69,7 @@ struct kmem_cache_order_objects {
69 * Slab cache management. 69 * Slab cache management.
70 */ 70 */
71struct kmem_cache { 71struct kmem_cache {
72 struct kmem_cache_cpu *cpu_slab;
72 /* Used for retriving partial slabs etc */ 73 /* Used for retriving partial slabs etc */
73 unsigned long flags; 74 unsigned long flags;
74 int size; /* The size of an object including meta data */ 75 int size; /* The size of an object including meta data */
@@ -104,11 +105,6 @@ struct kmem_cache {
104 int remote_node_defrag_ratio; 105 int remote_node_defrag_ratio;
105 struct kmem_cache_node *node[MAX_NUMNODES]; 106 struct kmem_cache_node *node[MAX_NUMNODES];
106#endif 107#endif
107#ifdef CONFIG_SMP
108 struct kmem_cache_cpu *cpu_slab[NR_CPUS];
109#else
110 struct kmem_cache_cpu cpu_slab;
111#endif
112}; 108};
113 109
114/* 110/*
diff --git a/mm/slub.c b/mm/slub.c
index 8d71aaf888d7..d6c9ecf629d5 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -242,15 +242,6 @@ static inline struct kmem_cache_node *get_node(struct kmem_cache *s, int node)
242#endif 242#endif
243} 243}
244 244
245static inline struct kmem_cache_cpu *get_cpu_slab(struct kmem_cache *s, int cpu)
246{
247#ifdef CONFIG_SMP
248 return s->cpu_slab[cpu];
249#else
250 return &s->cpu_slab;
251#endif
252}
253
254/* Verify that a pointer has an address that is valid within a slab page */ 245/* Verify that a pointer has an address that is valid within a slab page */
255static inline int check_valid_pointer(struct kmem_cache *s, 246static inline int check_valid_pointer(struct kmem_cache *s,
256 struct page *page, const void *object) 247 struct page *page, const void *object)
@@ -1124,7 +1115,7 @@ static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
1124 if (!page) 1115 if (!page)
1125 return NULL; 1116 return NULL;
1126 1117
1127 stat(get_cpu_slab(s, raw_smp_processor_id()), ORDER_FALLBACK); 1118 stat(this_cpu_ptr(s->cpu_slab), ORDER_FALLBACK);
1128 } 1119 }
1129 1120
1130 if (kmemcheck_enabled 1121 if (kmemcheck_enabled
@@ -1422,7 +1413,7 @@ static struct page *get_partial(struct kmem_cache *s, gfp_t flags, int node)
1422static void unfreeze_slab(struct kmem_cache *s, struct page *page, int tail) 1413static void unfreeze_slab(struct kmem_cache *s, struct page *page, int tail)
1423{ 1414{
1424 struct kmem_cache_node *n = get_node(s, page_to_nid(page)); 1415 struct kmem_cache_node *n = get_node(s, page_to_nid(page));
1425 struct kmem_cache_cpu *c = get_cpu_slab(s, smp_processor_id()); 1416 struct kmem_cache_cpu *c = this_cpu_ptr(s->cpu_slab);
1426 1417
1427 __ClearPageSlubFrozen(page); 1418 __ClearPageSlubFrozen(page);
1428 if (page->inuse) { 1419 if (page->inuse) {
@@ -1454,7 +1445,7 @@ static void unfreeze_slab(struct kmem_cache *s, struct page *page, int tail)
1454 slab_unlock(page); 1445 slab_unlock(page);
1455 } else { 1446 } else {
1456 slab_unlock(page); 1447 slab_unlock(page);
1457 stat(get_cpu_slab(s, raw_smp_processor_id()), FREE_SLAB); 1448 stat(__this_cpu_ptr(s->cpu_slab), FREE_SLAB);
1458 discard_slab(s, page); 1449 discard_slab(s, page);
1459 } 1450 }
1460 } 1451 }
@@ -1507,7 +1498,7 @@ static inline void flush_slab(struct kmem_cache *s, struct kmem_cache_cpu *c)
1507 */ 1498 */
1508static inline void __flush_cpu_slab(struct kmem_cache *s, int cpu) 1499static inline void __flush_cpu_slab(struct kmem_cache *s, int cpu)
1509{ 1500{
1510 struct kmem_cache_cpu *c = get_cpu_slab(s, cpu); 1501 struct kmem_cache_cpu *c = per_cpu_ptr(s->cpu_slab, cpu);
1511 1502
1512 if (likely(c && c->page)) 1503 if (likely(c && c->page))
1513 flush_slab(s, c); 1504 flush_slab(s, c);
@@ -1673,7 +1664,7 @@ new_slab:
1673 local_irq_disable(); 1664 local_irq_disable();
1674 1665
1675 if (new) { 1666 if (new) {
1676 c = get_cpu_slab(s, smp_processor_id()); 1667 c = __this_cpu_ptr(s->cpu_slab);
1677 stat(c, ALLOC_SLAB); 1668 stat(c, ALLOC_SLAB);
1678 if (c->page) 1669 if (c->page)
1679 flush_slab(s, c); 1670 flush_slab(s, c);
@@ -1711,7 +1702,7 @@ static __always_inline void *slab_alloc(struct kmem_cache *s,
1711 void **object; 1702 void **object;
1712 struct kmem_cache_cpu *c; 1703 struct kmem_cache_cpu *c;
1713 unsigned long flags; 1704 unsigned long flags;
1714 unsigned int objsize; 1705 unsigned long objsize;
1715 1706
1716 gfpflags &= gfp_allowed_mask; 1707 gfpflags &= gfp_allowed_mask;
1717 1708
@@ -1722,14 +1713,14 @@ static __always_inline void *slab_alloc(struct kmem_cache *s,
1722 return NULL; 1713 return NULL;
1723 1714
1724 local_irq_save(flags); 1715 local_irq_save(flags);
1725 c = get_cpu_slab(s, smp_processor_id()); 1716 c = __this_cpu_ptr(s->cpu_slab);
1717 object = c->freelist;
1726 objsize = c->objsize; 1718 objsize = c->objsize;
1727 if (unlikely(!c->freelist || !node_match(c, node))) 1719 if (unlikely(!object || !node_match(c, node)))
1728 1720
1729 object = __slab_alloc(s, gfpflags, node, addr, c); 1721 object = __slab_alloc(s, gfpflags, node, addr, c);
1730 1722
1731 else { 1723 else {
1732 object = c->freelist;
1733 c->freelist = object[c->offset]; 1724 c->freelist = object[c->offset];
1734 stat(c, ALLOC_FASTPATH); 1725 stat(c, ALLOC_FASTPATH);
1735 } 1726 }
@@ -1800,7 +1791,7 @@ static void __slab_free(struct kmem_cache *s, struct page *page,
1800 void **object = (void *)x; 1791 void **object = (void *)x;
1801 struct kmem_cache_cpu *c; 1792 struct kmem_cache_cpu *c;
1802 1793
1803 c = get_cpu_slab(s, raw_smp_processor_id()); 1794 c = __this_cpu_ptr(s->cpu_slab);
1804 stat(c, FREE_SLOWPATH); 1795 stat(c, FREE_SLOWPATH);
1805 slab_lock(page); 1796 slab_lock(page);
1806 1797
@@ -1872,7 +1863,7 @@ static __always_inline void slab_free(struct kmem_cache *s,
1872 1863
1873 kmemleak_free_recursive(x, s->flags); 1864 kmemleak_free_recursive(x, s->flags);
1874 local_irq_save(flags); 1865 local_irq_save(flags);
1875 c = get_cpu_slab(s, smp_processor_id()); 1866 c = __this_cpu_ptr(s->cpu_slab);
1876 kmemcheck_slab_free(s, object, c->objsize); 1867 kmemcheck_slab_free(s, object, c->objsize);
1877 debug_check_no_locks_freed(object, c->objsize); 1868 debug_check_no_locks_freed(object, c->objsize);
1878 if (!(s->flags & SLAB_DEBUG_OBJECTS)) 1869 if (!(s->flags & SLAB_DEBUG_OBJECTS))
@@ -2095,130 +2086,28 @@ init_kmem_cache_node(struct kmem_cache_node *n, struct kmem_cache *s)
2095#endif 2086#endif
2096} 2087}
2097 2088
2098#ifdef CONFIG_SMP 2089static DEFINE_PER_CPU(struct kmem_cache_cpu, kmalloc_percpu[SLUB_PAGE_SHIFT]);
2099/*
2100 * Per cpu array for per cpu structures.
2101 *
2102 * The per cpu array places all kmem_cache_cpu structures from one processor
2103 * close together meaning that it becomes possible that multiple per cpu
2104 * structures are contained in one cacheline. This may be particularly
2105 * beneficial for the kmalloc caches.
2106 *
2107 * A desktop system typically has around 60-80 slabs. With 100 here we are
2108 * likely able to get per cpu structures for all caches from the array defined
2109 * here. We must be able to cover all kmalloc caches during bootstrap.
2110 *
2111 * If the per cpu array is exhausted then fall back to kmalloc
2112 * of individual cachelines. No sharing is possible then.
2113 */
2114#define NR_KMEM_CACHE_CPU 100
2115
2116static DEFINE_PER_CPU(struct kmem_cache_cpu [NR_KMEM_CACHE_CPU],
2117 kmem_cache_cpu);
2118
2119static DEFINE_PER_CPU(struct kmem_cache_cpu *, kmem_cache_cpu_free);
2120static DECLARE_BITMAP(kmem_cach_cpu_free_init_once, CONFIG_NR_CPUS);
2121
2122static struct kmem_cache_cpu *alloc_kmem_cache_cpu(struct kmem_cache *s,
2123 int cpu, gfp_t flags)
2124{
2125 struct kmem_cache_cpu *c = per_cpu(kmem_cache_cpu_free, cpu);
2126
2127 if (c)
2128 per_cpu(kmem_cache_cpu_free, cpu) =
2129 (void *)c->freelist;
2130 else {
2131 /* Table overflow: So allocate ourselves */
2132 c = kmalloc_node(
2133 ALIGN(sizeof(struct kmem_cache_cpu), cache_line_size()),
2134 flags, cpu_to_node(cpu));
2135 if (!c)
2136 return NULL;
2137 }
2138
2139 init_kmem_cache_cpu(s, c);
2140 return c;
2141}
2142
2143static void free_kmem_cache_cpu(struct kmem_cache_cpu *c, int cpu)
2144{
2145 if (c < per_cpu(kmem_cache_cpu, cpu) ||
2146 c >= per_cpu(kmem_cache_cpu, cpu) + NR_KMEM_CACHE_CPU) {
2147 kfree(c);
2148 return;
2149 }
2150 c->freelist = (void *)per_cpu(kmem_cache_cpu_free, cpu);
2151 per_cpu(kmem_cache_cpu_free, cpu) = c;
2152}
2153
2154static void free_kmem_cache_cpus(struct kmem_cache *s)
2155{
2156 int cpu;
2157
2158 for_each_online_cpu(cpu) {
2159 struct kmem_cache_cpu *c = get_cpu_slab(s, cpu);
2160
2161 if (c) {
2162 s->cpu_slab[cpu] = NULL;
2163 free_kmem_cache_cpu(c, cpu);
2164 }
2165 }
2166}
2167
2168static int alloc_kmem_cache_cpus(struct kmem_cache *s, gfp_t flags)
2169{
2170 int cpu;
2171
2172 for_each_online_cpu(cpu) {
2173 struct kmem_cache_cpu *c = get_cpu_slab(s, cpu);
2174 2090
2175 if (c) 2091static inline int alloc_kmem_cache_cpus(struct kmem_cache *s, gfp_t flags)
2176 continue;
2177
2178 c = alloc_kmem_cache_cpu(s, cpu, flags);
2179 if (!c) {
2180 free_kmem_cache_cpus(s);
2181 return 0;
2182 }
2183 s->cpu_slab[cpu] = c;
2184 }
2185 return 1;
2186}
2187
2188/*
2189 * Initialize the per cpu array.
2190 */
2191static void init_alloc_cpu_cpu(int cpu)
2192{
2193 int i;
2194
2195 if (cpumask_test_cpu(cpu, to_cpumask(kmem_cach_cpu_free_init_once)))
2196 return;
2197
2198 for (i = NR_KMEM_CACHE_CPU - 1; i >= 0; i--)
2199 free_kmem_cache_cpu(&per_cpu(kmem_cache_cpu, cpu)[i], cpu);
2200
2201 cpumask_set_cpu(cpu, to_cpumask(kmem_cach_cpu_free_init_once));
2202}
2203
2204static void __init init_alloc_cpu(void)
2205{ 2092{
2206 int cpu; 2093 int cpu;
2207 2094
2208 for_each_online_cpu(cpu) 2095 if (s < kmalloc_caches + SLUB_PAGE_SHIFT && s >= kmalloc_caches)
2209 init_alloc_cpu_cpu(cpu); 2096 /*
2210 } 2097 * Boot time creation of the kmalloc array. Use static per cpu data
2098 * since the per cpu allocator is not available yet.
2099 */
2100 s->cpu_slab = per_cpu_var(kmalloc_percpu) + (s - kmalloc_caches);
2101 else
2102 s->cpu_slab = alloc_percpu(struct kmem_cache_cpu);
2211 2103
2212#else 2104 if (!s->cpu_slab)
2213static inline void free_kmem_cache_cpus(struct kmem_cache *s) {} 2105 return 0;
2214static inline void init_alloc_cpu(void) {}
2215 2106
2216static inline int alloc_kmem_cache_cpus(struct kmem_cache *s, gfp_t flags) 2107 for_each_possible_cpu(cpu)
2217{ 2108 init_kmem_cache_cpu(s, per_cpu_ptr(s->cpu_slab, cpu));
2218 init_kmem_cache_cpu(s, &s->cpu_slab);
2219 return 1; 2109 return 1;
2220} 2110}
2221#endif
2222 2111
2223#ifdef CONFIG_NUMA 2112#ifdef CONFIG_NUMA
2224/* 2113/*
@@ -2609,9 +2498,8 @@ static inline int kmem_cache_close(struct kmem_cache *s)
2609 int node; 2498 int node;
2610 2499
2611 flush_all(s); 2500 flush_all(s);
2612 2501 free_percpu(s->cpu_slab);
2613 /* Attempt to free all objects */ 2502 /* Attempt to free all objects */
2614 free_kmem_cache_cpus(s);
2615 for_each_node_state(node, N_NORMAL_MEMORY) { 2503 for_each_node_state(node, N_NORMAL_MEMORY) {
2616 struct kmem_cache_node *n = get_node(s, node); 2504 struct kmem_cache_node *n = get_node(s, node);
2617 2505
@@ -2760,7 +2648,19 @@ static noinline struct kmem_cache *dma_kmalloc_cache(int index, gfp_t flags)
2760 realsize = kmalloc_caches[index].objsize; 2648 realsize = kmalloc_caches[index].objsize;
2761 text = kasprintf(flags & ~SLUB_DMA, "kmalloc_dma-%d", 2649 text = kasprintf(flags & ~SLUB_DMA, "kmalloc_dma-%d",
2762 (unsigned int)realsize); 2650 (unsigned int)realsize);
2763 s = kmalloc(kmem_size, flags & ~SLUB_DMA); 2651
2652 if (flags & __GFP_WAIT)
2653 s = kmalloc(kmem_size, flags & ~SLUB_DMA);
2654 else {
2655 int i;
2656
2657 s = NULL;
2658 for (i = 0; i < SLUB_PAGE_SHIFT; i++)
2659 if (kmalloc_caches[i].size) {
2660 s = kmalloc_caches + i;
2661 break;
2662 }
2663 }
2764 2664
2765 /* 2665 /*
2766 * Must defer sysfs creation to a workqueue because we don't know 2666 * Must defer sysfs creation to a workqueue because we don't know
@@ -3176,8 +3076,6 @@ void __init kmem_cache_init(void)
3176 int i; 3076 int i;
3177 int caches = 0; 3077 int caches = 0;
3178 3078
3179 init_alloc_cpu();
3180
3181#ifdef CONFIG_NUMA 3079#ifdef CONFIG_NUMA
3182 /* 3080 /*
3183 * Must first have the slab cache available for the allocations of the 3081 * Must first have the slab cache available for the allocations of the
@@ -3261,8 +3159,10 @@ void __init kmem_cache_init(void)
3261 3159
3262#ifdef CONFIG_SMP 3160#ifdef CONFIG_SMP
3263 register_cpu_notifier(&slab_notifier); 3161 register_cpu_notifier(&slab_notifier);
3264 kmem_size = offsetof(struct kmem_cache, cpu_slab) + 3162#endif
3265 nr_cpu_ids * sizeof(struct kmem_cache_cpu *); 3163#ifdef CONFIG_NUMA
3164 kmem_size = offsetof(struct kmem_cache, node) +
3165 nr_node_ids * sizeof(struct kmem_cache_node *);
3266#else 3166#else
3267 kmem_size = sizeof(struct kmem_cache); 3167 kmem_size = sizeof(struct kmem_cache);
3268#endif 3168#endif
@@ -3365,7 +3265,7 @@ struct kmem_cache *kmem_cache_create(const char *name, size_t size,
3365 * per cpu structures 3265 * per cpu structures
3366 */ 3266 */
3367 for_each_online_cpu(cpu) 3267 for_each_online_cpu(cpu)
3368 get_cpu_slab(s, cpu)->objsize = s->objsize; 3268 per_cpu_ptr(s->cpu_slab, cpu)->objsize = s->objsize;
3369 3269
3370 s->inuse = max_t(int, s->inuse, ALIGN(size, sizeof(void *))); 3270 s->inuse = max_t(int, s->inuse, ALIGN(size, sizeof(void *)));
3371 up_write(&slub_lock); 3271 up_write(&slub_lock);
@@ -3422,11 +3322,9 @@ static int __cpuinit slab_cpuup_callback(struct notifier_block *nfb,
3422 switch (action) { 3322 switch (action) {
3423 case CPU_UP_PREPARE: 3323 case CPU_UP_PREPARE:
3424 case CPU_UP_PREPARE_FROZEN: 3324 case CPU_UP_PREPARE_FROZEN:
3425 init_alloc_cpu_cpu(cpu);
3426 down_read(&slub_lock); 3325 down_read(&slub_lock);
3427 list_for_each_entry(s, &slab_caches, list) 3326 list_for_each_entry(s, &slab_caches, list)
3428 s->cpu_slab[cpu] = alloc_kmem_cache_cpu(s, cpu, 3327 init_kmem_cache_cpu(s, per_cpu_ptr(s->cpu_slab, cpu));
3429 GFP_KERNEL);
3430 up_read(&slub_lock); 3328 up_read(&slub_lock);
3431 break; 3329 break;
3432 3330
@@ -3436,13 +3334,9 @@ static int __cpuinit slab_cpuup_callback(struct notifier_block *nfb,
3436 case CPU_DEAD_FROZEN: 3334 case CPU_DEAD_FROZEN:
3437 down_read(&slub_lock); 3335 down_read(&slub_lock);
3438 list_for_each_entry(s, &slab_caches, list) { 3336 list_for_each_entry(s, &slab_caches, list) {
3439 struct kmem_cache_cpu *c = get_cpu_slab(s, cpu);
3440
3441 local_irq_save(flags); 3337 local_irq_save(flags);
3442 __flush_cpu_slab(s, cpu); 3338 __flush_cpu_slab(s, cpu);
3443 local_irq_restore(flags); 3339 local_irq_restore(flags);
3444 free_kmem_cache_cpu(c, cpu);
3445 s->cpu_slab[cpu] = NULL;
3446 } 3340 }
3447 up_read(&slub_lock); 3341 up_read(&slub_lock);
3448 break; 3342 break;
@@ -3928,7 +3822,7 @@ static ssize_t show_slab_objects(struct kmem_cache *s,
3928 int cpu; 3822 int cpu;
3929 3823
3930 for_each_possible_cpu(cpu) { 3824 for_each_possible_cpu(cpu) {
3931 struct kmem_cache_cpu *c = get_cpu_slab(s, cpu); 3825 struct kmem_cache_cpu *c = per_cpu_ptr(s->cpu_slab, cpu);
3932 3826
3933 if (!c || c->node < 0) 3827 if (!c || c->node < 0)
3934 continue; 3828 continue;
@@ -4353,7 +4247,7 @@ static int show_stat(struct kmem_cache *s, char *buf, enum stat_item si)
4353 return -ENOMEM; 4247 return -ENOMEM;
4354 4248
4355 for_each_online_cpu(cpu) { 4249 for_each_online_cpu(cpu) {
4356 unsigned x = get_cpu_slab(s, cpu)->stat[si]; 4250 unsigned x = per_cpu_ptr(s->cpu_slab, cpu)->stat[si];
4357 4251
4358 data[cpu] = x; 4252 data[cpu] = x;
4359 sum += x; 4253 sum += x;
@@ -4376,7 +4270,7 @@ static void clear_stat(struct kmem_cache *s, enum stat_item si)
4376 int cpu; 4270 int cpu;
4377 4271
4378 for_each_online_cpu(cpu) 4272 for_each_online_cpu(cpu)
4379 get_cpu_slab(s, cpu)->stat[si] = 0; 4273 per_cpu_ptr(s->cpu_slab, cpu)->stat[si] = 0;
4380} 4274}
4381 4275
4382#define STAT_ATTR(si, text) \ 4276#define STAT_ATTR(si, text) \
3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043
/*
 * cpia CPiA driver
 *
 * Supports CPiA based Video Camera's.
 *
 * (C) Copyright 1999-2000 Peter Pregler
 * (C) Copyright 1999-2000 Scott J. Bertin
 * (C) Copyright 1999-2000 Johannes Erdfelt <johannes@erdfelt.com>
 * (C) Copyright 2000 STMicroelectronics
 *
 * 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.
 */

/* define _CPIA_DEBUG_ for verbose debug output (see cpia.h) */
/* #define _CPIA_DEBUG_  1 */


#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/pagemap.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <linux/mutex.h>

#include "cpia.h"

static int video_nr = -1;

#ifdef MODULE
module_param(video_nr, int, 0);
MODULE_AUTHOR("Scott J. Bertin <sbertin@securenym.net> & Peter Pregler <Peter_Pregler@email.com> & Johannes Erdfelt <johannes@erdfelt.com>");
MODULE_DESCRIPTION("V4L-driver for Vision CPiA based cameras");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("video");
#endif

static unsigned short colorspace_conv;
module_param(colorspace_conv, ushort, 0444);
MODULE_PARM_DESC(colorspace_conv,
		 " Colorspace conversion:"
		 "\n  0 = disable, 1 = enable"
		 "\n  Default value is 0"
		 );

#define ABOUT "V4L-Driver for Vision CPiA based cameras"

#define CPIA_MODULE_CPIA			(0<<5)
#define CPIA_MODULE_SYSTEM			(1<<5)
#define CPIA_MODULE_VP_CTRL			(5<<5)
#define CPIA_MODULE_CAPTURE			(6<<5)
#define CPIA_MODULE_DEBUG			(7<<5)

#define INPUT (DATA_IN << 8)
#define OUTPUT (DATA_OUT << 8)

#define CPIA_COMMAND_GetCPIAVersion	(INPUT | CPIA_MODULE_CPIA | 1)
#define CPIA_COMMAND_GetPnPID		(INPUT | CPIA_MODULE_CPIA | 2)
#define CPIA_COMMAND_GetCameraStatus	(INPUT | CPIA_MODULE_CPIA | 3)
#define CPIA_COMMAND_GotoHiPower	(OUTPUT | CPIA_MODULE_CPIA | 4)
#define CPIA_COMMAND_GotoLoPower	(OUTPUT | CPIA_MODULE_CPIA | 5)
#define CPIA_COMMAND_GotoSuspend	(OUTPUT | CPIA_MODULE_CPIA | 7)
#define CPIA_COMMAND_GotoPassThrough	(OUTPUT | CPIA_MODULE_CPIA | 8)
#define CPIA_COMMAND_ModifyCameraStatus	(OUTPUT | CPIA_MODULE_CPIA | 10)

#define CPIA_COMMAND_ReadVCRegs		(INPUT | CPIA_MODULE_SYSTEM | 1)
#define CPIA_COMMAND_WriteVCReg		(OUTPUT | CPIA_MODULE_SYSTEM | 2)
#define CPIA_COMMAND_ReadMCPorts	(INPUT | CPIA_MODULE_SYSTEM | 3)
#define CPIA_COMMAND_WriteMCPort	(OUTPUT | CPIA_MODULE_SYSTEM | 4)
#define CPIA_COMMAND_SetBaudRate	(OUTPUT | CPIA_MODULE_SYSTEM | 5)
#define CPIA_COMMAND_SetECPTiming	(OUTPUT | CPIA_MODULE_SYSTEM | 6)
#define CPIA_COMMAND_ReadIDATA		(INPUT | CPIA_MODULE_SYSTEM | 7)
#define CPIA_COMMAND_WriteIDATA		(OUTPUT | CPIA_MODULE_SYSTEM | 8)
#define CPIA_COMMAND_GenericCall	(OUTPUT | CPIA_MODULE_SYSTEM | 9)
#define CPIA_COMMAND_I2CStart		(OUTPUT | CPIA_MODULE_SYSTEM | 10)
#define CPIA_COMMAND_I2CStop		(OUTPUT | CPIA_MODULE_SYSTEM | 11)
#define CPIA_COMMAND_I2CWrite		(OUTPUT | CPIA_MODULE_SYSTEM | 12)
#define CPIA_COMMAND_I2CRead		(INPUT | CPIA_MODULE_SYSTEM | 13)

#define CPIA_COMMAND_GetVPVersion	(INPUT | CPIA_MODULE_VP_CTRL | 1)
#define CPIA_COMMAND_ResetFrameCounter	(INPUT | CPIA_MODULE_VP_CTRL | 2)
#define CPIA_COMMAND_SetColourParams	(OUTPUT | CPIA_MODULE_VP_CTRL | 3)
#define CPIA_COMMAND_SetExposure	(OUTPUT | CPIA_MODULE_VP_CTRL | 4)
#define CPIA_COMMAND_SetColourBalance	(OUTPUT | CPIA_MODULE_VP_CTRL | 6)
#define CPIA_COMMAND_SetSensorFPS	(OUTPUT | CPIA_MODULE_VP_CTRL | 7)
#define CPIA_COMMAND_SetVPDefaults	(OUTPUT | CPIA_MODULE_VP_CTRL | 8)
#define CPIA_COMMAND_SetApcor		(OUTPUT | CPIA_MODULE_VP_CTRL | 9)
#define CPIA_COMMAND_SetFlickerCtrl	(OUTPUT | CPIA_MODULE_VP_CTRL | 10)
#define CPIA_COMMAND_SetVLOffset	(OUTPUT | CPIA_MODULE_VP_CTRL | 11)
#define CPIA_COMMAND_GetColourParams	(INPUT | CPIA_MODULE_VP_CTRL | 16)
#define CPIA_COMMAND_GetColourBalance	(INPUT | CPIA_MODULE_VP_CTRL | 17)
#define CPIA_COMMAND_GetExposure	(INPUT | CPIA_MODULE_VP_CTRL | 18)
#define CPIA_COMMAND_SetSensorMatrix	(OUTPUT | CPIA_MODULE_VP_CTRL | 19)
#define CPIA_COMMAND_ColourBars		(OUTPUT | CPIA_MODULE_VP_CTRL | 25)
#define CPIA_COMMAND_ReadVPRegs		(INPUT | CPIA_MODULE_VP_CTRL | 30)
#define CPIA_COMMAND_WriteVPReg		(OUTPUT | CPIA_MODULE_VP_CTRL | 31)

#define CPIA_COMMAND_GrabFrame		(OUTPUT | CPIA_MODULE_CAPTURE | 1)
#define CPIA_COMMAND_UploadFrame	(OUTPUT | CPIA_MODULE_CAPTURE | 2)
#define CPIA_COMMAND_SetGrabMode	(OUTPUT | CPIA_MODULE_CAPTURE | 3)
#define CPIA_COMMAND_InitStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 4)
#define CPIA_COMMAND_FiniStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 5)
#define CPIA_COMMAND_StartStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 6)
#define CPIA_COMMAND_EndStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 7)
#define CPIA_COMMAND_SetFormat		(OUTPUT | CPIA_MODULE_CAPTURE | 8)
#define CPIA_COMMAND_SetROI		(OUTPUT | CPIA_MODULE_CAPTURE | 9)
#define CPIA_COMMAND_SetCompression	(OUTPUT | CPIA_MODULE_CAPTURE | 10)
#define CPIA_COMMAND_SetCompressionTarget (OUTPUT | CPIA_MODULE_CAPTURE | 11)
#define CPIA_COMMAND_SetYUVThresh	(OUTPUT | CPIA_MODULE_CAPTURE | 12)
#define CPIA_COMMAND_SetCompressionParams (OUTPUT | CPIA_MODULE_CAPTURE | 13)
#define CPIA_COMMAND_DiscardFrame	(OUTPUT | CPIA_MODULE_CAPTURE | 14)
#define CPIA_COMMAND_GrabReset		(OUTPUT | CPIA_MODULE_CAPTURE | 15)

#define CPIA_COMMAND_OutputRS232	(OUTPUT | CPIA_MODULE_DEBUG | 1)
#define CPIA_COMMAND_AbortProcess	(OUTPUT | CPIA_MODULE_DEBUG | 4)
#define CPIA_COMMAND_SetDramPage	(OUTPUT | CPIA_MODULE_DEBUG | 5)
#define CPIA_COMMAND_StartDramUpload	(OUTPUT | CPIA_MODULE_DEBUG | 6)
#define CPIA_COMMAND_StartDummyDtream	(OUTPUT | CPIA_MODULE_DEBUG | 8)
#define CPIA_COMMAND_AbortStream	(OUTPUT | CPIA_MODULE_DEBUG | 9)
#define CPIA_COMMAND_DownloadDRAM	(OUTPUT | CPIA_MODULE_DEBUG | 10)
#define CPIA_COMMAND_Null		(OUTPUT | CPIA_MODULE_DEBUG | 11)

enum {
	FRAME_READY,		/* Ready to grab into */
	FRAME_GRABBING,		/* In the process of being grabbed into */
	FRAME_DONE,		/* Finished grabbing, but not been synced yet */
	FRAME_UNUSED,		/* Unused (no MCAPTURE) */
};

#define COMMAND_NONE			0x0000
#define COMMAND_SETCOMPRESSION		0x0001
#define COMMAND_SETCOMPRESSIONTARGET	0x0002
#define COMMAND_SETCOLOURPARAMS		0x0004
#define COMMAND_SETFORMAT		0x0008
#define COMMAND_PAUSE			0x0010
#define COMMAND_RESUME			0x0020
#define COMMAND_SETYUVTHRESH		0x0040
#define COMMAND_SETECPTIMING		0x0080
#define COMMAND_SETCOMPRESSIONPARAMS	0x0100
#define COMMAND_SETEXPOSURE		0x0200
#define COMMAND_SETCOLOURBALANCE	0x0400
#define COMMAND_SETSENSORFPS		0x0800
#define COMMAND_SETAPCOR		0x1000
#define COMMAND_SETFLICKERCTRL		0x2000
#define COMMAND_SETVLOFFSET		0x4000
#define COMMAND_SETLIGHTS		0x8000

#define ROUND_UP_EXP_FOR_FLICKER 15

/* Constants for automatic frame rate adjustment */
#define MAX_EXP       302
#define MAX_EXP_102   255
#define LOW_EXP       140
#define VERY_LOW_EXP   70
#define TC             94
#define	EXP_ACC_DARK   50
#define	EXP_ACC_LIGHT  90
#define HIGH_COMP_102 160
#define MAX_COMP      239
#define DARK_TIME       3
#define LIGHT_TIME      3

/* Maximum number of 10ms loops to wait for the stream to become ready */
#define READY_TIMEOUT 100

/* Developer's Guide Table 5 p 3-34
 * indexed by [mains][sensorFps.baserate][sensorFps.divisor]*/
static u8 flicker_jumps[2][2][4] =
{ { { 76, 38, 19, 9 }, { 92, 46, 23, 11 } },
  { { 64, 32, 16, 8 }, { 76, 38, 19, 9} }
};

/* forward declaration of local function */
static void reset_camera_struct(struct cam_data *cam);
static int find_over_exposure(int brightness);
static void set_flicker(struct cam_params *params, volatile u32 *command_flags,
			int on);


/**********************************************************************
 *
 * Memory management
 *
 **********************************************************************/
static void *rvmalloc(unsigned long size)
{
	void *mem;
	unsigned long adr;

	size = PAGE_ALIGN(size);
	mem = vmalloc_32(size);
	if (!mem)
		return NULL;

	memset(mem, 0, size); /* Clear the ram out, no junk to the user */
	adr = (unsigned long) mem;
	while (size > 0) {
		SetPageReserved(vmalloc_to_page((void *)adr));
		adr += PAGE_SIZE;
		size -= PAGE_SIZE;
	}

	return mem;
}

static void rvfree(void *mem, unsigned long size)
{
	unsigned long adr;

	if (!mem)
		return;

	adr = (unsigned long) mem;
	while ((long) size > 0) {
		ClearPageReserved(vmalloc_to_page((void *)adr));
		adr += PAGE_SIZE;
		size -= PAGE_SIZE;
	}
	vfree(mem);
}

/**********************************************************************
 *
 * /proc interface
 *
 **********************************************************************/
#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *cpia_proc_root=NULL;

static int cpia_read_proc(char *page, char **start, off_t off,
			  int count, int *eof, void *data)
{
	char *out = page;
	int len, tmp;
	struct cam_data *cam = data;
	char tmpstr[29];

	/* IMPORTANT: This output MUST be kept under PAGE_SIZE
	 *            or we need to get more sophisticated. */

	out += sprintf(out, "read-only\n-----------------------\n");
	out += sprintf(out, "V4L Driver version:       %d.%d.%d\n",
		       CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER);
	out += sprintf(out, "CPIA Version:             %d.%02d (%d.%d)\n",
		       cam->params.version.firmwareVersion,
		       cam->params.version.firmwareRevision,
		       cam->params.version.vcVersion,
		       cam->params.version.vcRevision);
	out += sprintf(out, "CPIA PnP-ID:              %04x:%04x:%04x\n",
		       cam->params.pnpID.vendor, cam->params.pnpID.product,
		       cam->params.pnpID.deviceRevision);
	out += sprintf(out, "VP-Version:               %d.%d %04x\n",
		       cam->params.vpVersion.vpVersion,
		       cam->params.vpVersion.vpRevision,
		       cam->params.vpVersion.cameraHeadID);

	out += sprintf(out, "system_state:             %#04x\n",
		       cam->params.status.systemState);
	out += sprintf(out, "grab_state:               %#04x\n",
		       cam->params.status.grabState);
	out += sprintf(out, "stream_state:             %#04x\n",
		       cam->params.status.streamState);
	out += sprintf(out, "fatal_error:              %#04x\n",
		       cam->params.status.fatalError);
	out += sprintf(out, "cmd_error:                %#04x\n",
		       cam->params.status.cmdError);
	out += sprintf(out, "debug_flags:              %#04x\n",
		       cam->params.status.debugFlags);
	out += sprintf(out, "vp_status:                %#04x\n",
		       cam->params.status.vpStatus);
	out += sprintf(out, "error_code:               %#04x\n",
		       cam->params.status.errorCode);
	/* QX3 specific entries */
	if (cam->params.qx3.qx3_detected) {
		out += sprintf(out, "button:                   %4d\n",
			       cam->params.qx3.button);
		out += sprintf(out, "cradled:                  %4d\n",
			       cam->params.qx3.cradled);
	}
	out += sprintf(out, "video_size:               %s\n",
		       cam->params.format.videoSize == VIDEOSIZE_CIF ?
		       "CIF " : "QCIF");
	out += sprintf(out, "roi:                      (%3d, %3d) to (%3d, %3d)\n",
		       cam->params.roi.colStart*8,
		       cam->params.roi.rowStart*4,
		       cam->params.roi.colEnd*8,
		       cam->params.roi.rowEnd*4);
	out += sprintf(out, "actual_fps:               %3d\n", cam->fps);
	out += sprintf(out, "transfer_rate:            %4dkB/s\n",
		       cam->transfer_rate);

	out += sprintf(out, "\nread-write\n");
	out += sprintf(out, "-----------------------  current       min"
		       "       max   default  comment\n");
	out += sprintf(out, "brightness:             %8d  %8d  %8d  %8d\n",
		       cam->params.colourParams.brightness, 0, 100, 50);
	if (cam->params.version.firmwareVersion == 1 &&
	   cam->params.version.firmwareRevision == 2)
		/* 1-02 firmware limits contrast to 80 */
		tmp = 80;
	else
		tmp = 96;

	out += sprintf(out, "contrast:               %8d  %8d  %8d  %8d"
		       "  steps of 8\n",
		       cam->params.colourParams.contrast, 0, tmp, 48);
	out += sprintf(out, "saturation:             %8d  %8d  %8d  %8d\n",
		       cam->params.colourParams.saturation, 0, 100, 50);
	tmp = (25000+5000*cam->params.sensorFps.baserate)/
	      (1<<cam->params.sensorFps.divisor);
	out += sprintf(out, "sensor_fps:             %4d.%03d  %8d  %8d  %8d\n",
		       tmp/1000, tmp%1000, 3, 30, 15);
	out += sprintf(out, "stream_start_line:      %8d  %8d  %8d  %8d\n",
		       2*cam->params.streamStartLine, 0,
		       cam->params.format.videoSize == VIDEOSIZE_CIF ? 288:144,
		       cam->params.format.videoSize == VIDEOSIZE_CIF ? 240:120);
	out += sprintf(out, "sub_sample:             %8s  %8s  %8s  %8s\n",
		       cam->params.format.subSample == SUBSAMPLE_420 ?
		       "420" : "422", "420", "422", "422");
	out += sprintf(out, "yuv_order:              %8s  %8s  %8s  %8s\n",
		       cam->params.format.yuvOrder == YUVORDER_YUYV ?
		       "YUYV" : "UYVY", "YUYV" , "UYVY", "YUYV");
	out += sprintf(out, "ecp_timing:             %8s  %8s  %8s  %8s\n",
		       cam->params.ecpTiming ? "slow" : "normal", "slow",
		       "normal", "normal");

	if (cam->params.colourBalance.balanceMode == 2) {
		sprintf(tmpstr, "auto");
	} else {
		sprintf(tmpstr, "manual");
	}
	out += sprintf(out, "color_balance_mode:     %8s  %8s  %8s"
		       "  %8s\n",  tmpstr, "manual", "auto", "auto");
	out += sprintf(out, "red_gain:               %8d  %8d  %8d  %8d\n",
		       cam->params.colourBalance.redGain, 0, 212, 32);
	out += sprintf(out, "green_gain:             %8d  %8d  %8d  %8d\n",
		       cam->params.colourBalance.greenGain, 0, 212, 6);
	out += sprintf(out, "blue_gain:              %8d  %8d  %8d  %8d\n",
		       cam->params.colourBalance.blueGain, 0, 212, 92);

	if (cam->params.version.firmwareVersion == 1 &&
	   cam->params.version.firmwareRevision == 2)
		/* 1-02 firmware limits gain to 2 */
		sprintf(tmpstr, "%8d  %8d  %8d", 1, 2, 2);
	else
		sprintf(tmpstr, "%8d  %8d  %8d", 1, 8, 2);

	if (cam->params.exposure.gainMode == 0)
		out += sprintf(out, "max_gain:                unknown  %28s"
			       "  powers of 2\n", tmpstr);
	else
		out += sprintf(out, "max_gain:               %8d  %28s"
			       "  1,2,4 or 8 \n",
			       1<<(cam->params.exposure.gainMode-1), tmpstr);

	switch(cam->params.exposure.expMode) {
	case 1:
	case 3:
		sprintf(tmpstr, "manual");
		break;
	case 2:
		sprintf(tmpstr, "auto");
		break;
	default:
		sprintf(tmpstr, "unknown");
		break;
	}
	out += sprintf(out, "exposure_mode:          %8s  %8s  %8s"
		       "  %8s\n",  tmpstr, "manual", "auto", "auto");
	out += sprintf(out, "centre_weight:          %8s  %8s  %8s  %8s\n",
		       (2-cam->params.exposure.centreWeight) ? "on" : "off",
		       "off", "on", "on");
	out += sprintf(out, "gain:                   %8d  %8d  max_gain  %8d  1,2,4,8 possible\n",
		       1<<cam->params.exposure.gain, 1, 1);
	if (cam->params.version.firmwareVersion == 1 &&
	   cam->params.version.firmwareRevision == 2)
		/* 1-02 firmware limits fineExp/2 to 127 */
		tmp = 254;
	else
		tmp = 510;

	out += sprintf(out, "fine_exp:               %8d  %8d  %8d  %8d\n",
		       cam->params.exposure.fineExp*2, 0, tmp, 0);
	if (cam->params.version.firmwareVersion == 1 &&
	   cam->params.version.firmwareRevision == 2)
		/* 1-02 firmware limits coarseExpHi to 0 */
		tmp = MAX_EXP_102;
	else
		tmp = MAX_EXP;

	out += sprintf(out, "coarse_exp:             %8d  %8d  %8d"
		       "  %8d\n", cam->params.exposure.coarseExpLo+
		       256*cam->params.exposure.coarseExpHi, 0, tmp, 185);
	out += sprintf(out, "red_comp:               %8d  %8d  %8d  %8d\n",
		       cam->params.exposure.redComp, COMP_RED, 255, COMP_RED);
	out += sprintf(out, "green1_comp:            %8d  %8d  %8d  %8d\n",
		       cam->params.exposure.green1Comp, COMP_GREEN1, 255,
		       COMP_GREEN1);
	out += sprintf(out, "green2_comp:            %8d  %8d  %8d  %8d\n",
		       cam->params.exposure.green2Comp, COMP_GREEN2, 255,
		       COMP_GREEN2);
	out += sprintf(out, "blue_comp:              %8d  %8d  %8d  %8d\n",
		       cam->params.exposure.blueComp, COMP_BLUE, 255, COMP_BLUE);

	out += sprintf(out, "apcor_gain1:            %#8x  %#8x  %#8x  %#8x\n",
		       cam->params.apcor.gain1, 0, 0xff, 0x1c);
	out += sprintf(out, "apcor_gain2:            %#8x  %#8x  %#8x  %#8x\n",
		       cam->params.apcor.gain2, 0, 0xff, 0x1a);
	out += sprintf(out, "apcor_gain4:            %#8x  %#8x  %#8x  %#8x\n",
		       cam->params.apcor.gain4, 0, 0xff, 0x2d);
	out += sprintf(out, "apcor_gain8:            %#8x  %#8x  %#8x  %#8x\n",
		       cam->params.apcor.gain8, 0, 0xff, 0x2a);
	out += sprintf(out, "vl_offset_gain1:        %8d  %8d  %8d  %8d\n",
		       cam->params.vlOffset.gain1, 0, 255, 24);
	out += sprintf(out, "vl_offset_gain2:        %8d  %8d  %8d  %8d\n",
		       cam->params.vlOffset.gain2, 0, 255, 28);
	out += sprintf(out, "vl_offset_gain4:        %8d  %8d  %8d  %8d\n",
		       cam->params.vlOffset.gain4, 0, 255, 30);
	out += sprintf(out, "vl_offset_gain8:        %8d  %8d  %8d  %8d\n",
		       cam->params.vlOffset.gain8, 0, 255, 30);
	out += sprintf(out, "flicker_control:        %8s  %8s  %8s  %8s\n",
		       cam->params.flickerControl.flickerMode ? "on" : "off",
		       "off", "on", "off");
	out += sprintf(out, "mains_frequency:        %8d  %8d  %8d  %8d"
		       " only 50/60\n",
		       cam->mainsFreq ? 60 : 50, 50, 60, 50);
	if(cam->params.flickerControl.allowableOverExposure < 0)
		out += sprintf(out, "allowable_overexposure: %4dauto      auto  %8d      auto\n",
			       -cam->params.flickerControl.allowableOverExposure,
			       255);
	else
		out += sprintf(out, "allowable_overexposure: %8d      auto  %8d      auto\n",
			       cam->params.flickerControl.allowableOverExposure,
			       255);
	out += sprintf(out, "compression_mode:       ");
	switch(cam->params.compression.mode) {
	case CPIA_COMPRESSION_NONE:
		out += sprintf(out, "%8s", "none");
		break;
	case CPIA_COMPRESSION_AUTO:
		out += sprintf(out, "%8s", "auto");
		break;
	case CPIA_COMPRESSION_MANUAL:
		out += sprintf(out, "%8s", "manual");
		break;
	default:
		out += sprintf(out, "%8s", "unknown");
		break;
	}
	out += sprintf(out, "    none,auto,manual      auto\n");
	out += sprintf(out, "decimation_enable:      %8s  %8s  %8s  %8s\n",
		       cam->params.compression.decimation ==
		       DECIMATION_ENAB ? "on":"off", "off", "on",
		       "off");
	out += sprintf(out, "compression_target:    %9s %9s %9s %9s\n",
		       cam->params.compressionTarget.frTargeting  ==
		       CPIA_COMPRESSION_TARGET_FRAMERATE ?
		       "framerate":"quality",
		       "framerate", "quality", "quality");
	out += sprintf(out, "target_framerate:       %8d  %8d  %8d  %8d\n",
		       cam->params.compressionTarget.targetFR, 1, 30, 15);
	out += sprintf(out, "target_quality:         %8d  %8d  %8d  %8d\n",
		       cam->params.compressionTarget.targetQ, 1, 64, 5);
	out += sprintf(out, "y_threshold:            %8d  %8d  %8d  %8d\n",
		       cam->params.yuvThreshold.yThreshold, 0, 31, 6);
	out += sprintf(out, "uv_threshold:           %8d  %8d  %8d  %8d\n",
		       cam->params.yuvThreshold.uvThreshold, 0, 31, 6);
	out += sprintf(out, "hysteresis:             %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.hysteresis, 0, 255, 3);
	out += sprintf(out, "threshold_max:          %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.threshMax, 0, 255, 11);
	out += sprintf(out, "small_step:             %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.smallStep, 0, 255, 1);
	out += sprintf(out, "large_step:             %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.largeStep, 0, 255, 3);
	out += sprintf(out, "decimation_hysteresis:  %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.decimationHysteresis,
		       0, 255, 2);
	out += sprintf(out, "fr_diff_step_thresh:    %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.frDiffStepThresh,
		       0, 255, 5);
	out += sprintf(out, "q_diff_step_thresh:     %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.qDiffStepThresh,
		       0, 255, 3);
	out += sprintf(out, "decimation_thresh_mod:  %8d  %8d  %8d  %8d\n",
		       cam->params.compressionParams.decimationThreshMod,
		       0, 255, 2);
	/* QX3 specific entries */
	if (cam->params.qx3.qx3_detected) {
		out += sprintf(out, "toplight:               %8s  %8s  %8s  %8s\n",
			       cam->params.qx3.toplight ? "on" : "off",
			       "off", "on", "off");
		out += sprintf(out, "bottomlight:            %8s  %8s  %8s  %8s\n",
			       cam->params.qx3.bottomlight ? "on" : "off",
			       "off", "on", "off");
	}

	len = out - page;
	len -= off;
	if (len < count) {
		*eof = 1;
		if (len <= 0) return 0;
	} else
		len = count;

	*start = page + off;
	return len;
}


static int match(char *checkstr, char **buffer, unsigned long *count,
		 int *find_colon, int *err)
{
	int ret, colon_found = 1;
	int len = strlen(checkstr);
	ret = (len <= *count && strncmp(*buffer, checkstr, len) == 0);
	if (ret) {
		*buffer += len;
		*count -= len;
		if (*find_colon) {
			colon_found = 0;
			while (*count && (**buffer == ' ' || **buffer == '\t' ||
					  (!colon_found && **buffer == ':'))) {
				if (**buffer == ':')
					colon_found = 1;
				--*count;
				++*buffer;
			}
			if (!*count || !colon_found)
				*err = -EINVAL;
			*find_colon = 0;
		}
	}
	return ret;
}

static unsigned long int value(char **buffer, unsigned long *count, int *err)
{
	char *p;
	unsigned long int ret;
	ret = simple_strtoul(*buffer, &p, 0);
	if (p == *buffer)
		*err = -EINVAL;
	else {
		*count -= p - *buffer;
		*buffer = p;
	}
	return ret;
}

static int cpia_write_proc(struct file *file, const char __user *buf,
			   unsigned long count, void *data)
{
	struct cam_data *cam = data;
	struct cam_params new_params;
	char *page, *buffer;
	int retval, find_colon;
	int size = count;
	unsigned long val = 0;
	u32 command_flags = 0;
	u8 new_mains;

	/*
	 * This code to copy from buf to page is shamelessly copied
	 * from the comx driver
	 */
	if (count > PAGE_SIZE) {
		printk(KERN_ERR "count is %lu > %d!!!\n", count, (int)PAGE_SIZE);
		return -ENOSPC;
	}

	if (!(page = (char *)__get_free_page(GFP_KERNEL))) return -ENOMEM;

	if(copy_from_user(page, buf, count))
	{
		retval = -EFAULT;
		goto out;
	}

	if (page[count-1] == '\n')
		page[count-1] = '\0';
	else if (count < PAGE_SIZE)
		page[count] = '\0';
	else if (page[count]) {
		retval = -EINVAL;
		goto out;
	}

	buffer = page;

	if (mutex_lock_interruptible(&cam->param_lock))
		return -ERESTARTSYS;

	/*
	 * Skip over leading whitespace
	 */
	while (count && isspace(*buffer)) {
		--count;
		++buffer;
	}

	memcpy(&new_params, &cam->params, sizeof(struct cam_params));
	new_mains = cam->mainsFreq;

#define MATCH(x) (match(x, &buffer, &count, &find_colon, &retval))
#define VALUE (value(&buffer,&count, &retval))
#define FIRMWARE_VERSION(x,y) (new_params.version.firmwareVersion == (x) && \
			       new_params.version.firmwareRevision == (y))

	retval = 0;
	while (count && !retval) {
		find_colon = 1;
		if (MATCH("brightness")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 100)
					new_params.colourParams.brightness = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOLOURPARAMS;
			if(new_params.flickerControl.allowableOverExposure < 0)
				new_params.flickerControl.allowableOverExposure =
					-find_over_exposure(new_params.colourParams.brightness);
			if(new_params.flickerControl.flickerMode != 0)
				command_flags |= COMMAND_SETFLICKERCTRL;

		} else if (MATCH("contrast")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 100) {
					/* contrast is in steps of 8, so round*/
					val = ((val + 3) / 8) * 8;
					/* 1-02 firmware limits contrast to 80*/
					if (FIRMWARE_VERSION(1,2) && val > 80)
						val = 80;

					new_params.colourParams.contrast = val;
				} else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOLOURPARAMS;
		} else if (MATCH("saturation")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 100)
					new_params.colourParams.saturation = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOLOURPARAMS;
		} else if (MATCH("sensor_fps")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				/* find values so that sensorFPS is minimized,
				 * but >= val */
				if (val > 30)
					retval = -EINVAL;
				else if (val > 25) {
					new_params.sensorFps.divisor = 0;
					new_params.sensorFps.baserate = 1;
				} else if (val > 15) {
					new_params.sensorFps.divisor = 0;
					new_params.sensorFps.baserate = 0;
				} else if (val > 12) {
					new_params.sensorFps.divisor = 1;
					new_params.sensorFps.baserate = 1;
				} else if (val > 7) {
					new_params.sensorFps.divisor = 1;
					new_params.sensorFps.baserate = 0;
				} else if (val > 6) {
					new_params.sensorFps.divisor = 2;
					new_params.sensorFps.baserate = 1;
				} else if (val > 3) {
					new_params.sensorFps.divisor = 2;
					new_params.sensorFps.baserate = 0;
				} else {
					new_params.sensorFps.divisor = 3;
					/* Either base rate would work here */
					new_params.sensorFps.baserate = 1;
				}
				new_params.flickerControl.coarseJump =
					flicker_jumps[new_mains]
					[new_params.sensorFps.baserate]
					[new_params.sensorFps.divisor];
				if (new_params.flickerControl.flickerMode)
					command_flags |= COMMAND_SETFLICKERCTRL;
			}
			command_flags |= COMMAND_SETSENSORFPS;
			cam->exposure_status = EXPOSURE_NORMAL;
		} else if (MATCH("stream_start_line")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				int max_line = 288;

				if (new_params.format.videoSize == VIDEOSIZE_QCIF)
					max_line = 144;
				if (val <= max_line)
					new_params.streamStartLine = val/2;
				else
					retval = -EINVAL;
			}
		} else if (MATCH("sub_sample")) {
			if (!retval && MATCH("420"))
				new_params.format.subSample = SUBSAMPLE_420;
			else if (!retval && MATCH("422"))
				new_params.format.subSample = SUBSAMPLE_422;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETFORMAT;
		} else if (MATCH("yuv_order")) {
			if (!retval && MATCH("YUYV"))
				new_params.format.yuvOrder = YUVORDER_YUYV;
			else if (!retval && MATCH("UYVY"))
				new_params.format.yuvOrder = YUVORDER_UYVY;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETFORMAT;
		} else if (MATCH("ecp_timing")) {
			if (!retval && MATCH("normal"))
				new_params.ecpTiming = 0;
			else if (!retval && MATCH("slow"))
				new_params.ecpTiming = 1;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETECPTIMING;
		} else if (MATCH("color_balance_mode")) {
			if (!retval && MATCH("manual"))
				new_params.colourBalance.balanceMode = 3;
			else if (!retval && MATCH("auto"))
				new_params.colourBalance.balanceMode = 2;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETCOLOURBALANCE;
		} else if (MATCH("red_gain")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 212) {
					new_params.colourBalance.redGain = val;
					new_params.colourBalance.balanceMode = 1;
				} else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOLOURBALANCE;
		} else if (MATCH("green_gain")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 212) {
					new_params.colourBalance.greenGain = val;
					new_params.colourBalance.balanceMode = 1;
				} else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOLOURBALANCE;
		} else if (MATCH("blue_gain")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 212) {
					new_params.colourBalance.blueGain = val;
					new_params.colourBalance.balanceMode = 1;
				} else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOLOURBALANCE;
		} else if (MATCH("max_gain")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				/* 1-02 firmware limits gain to 2 */
				if (FIRMWARE_VERSION(1,2) && val > 2)
					val = 2;
				switch(val) {
				case 1:
					new_params.exposure.gainMode = 1;
					break;
				case 2:
					new_params.exposure.gainMode = 2;
					break;
				case 4:
					new_params.exposure.gainMode = 3;
					break;
				case 8:
					new_params.exposure.gainMode = 4;
					break;
				default:
					retval = -EINVAL;
					break;
				}
			}
			command_flags |= COMMAND_SETEXPOSURE;
		} else if (MATCH("exposure_mode")) {
			if (!retval && MATCH("auto"))
				new_params.exposure.expMode = 2;
			else if (!retval && MATCH("manual")) {
				if (new_params.exposure.expMode == 2)
					new_params.exposure.expMode = 3;
				if(new_params.flickerControl.flickerMode != 0)
					command_flags |= COMMAND_SETFLICKERCTRL;
				new_params.flickerControl.flickerMode = 0;
			} else
				retval = -EINVAL;

			command_flags |= COMMAND_SETEXPOSURE;
		} else if (MATCH("centre_weight")) {
			if (!retval && MATCH("on"))
				new_params.exposure.centreWeight = 1;
			else if (!retval && MATCH("off"))
				new_params.exposure.centreWeight = 2;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETEXPOSURE;
		} else if (MATCH("gain")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				switch(val) {
				case 1:
					new_params.exposure.gain = 0;
					break;
				case 2:
					new_params.exposure.gain = 1;
					break;
				case 4:
					new_params.exposure.gain = 2;
					break;
				case 8:
					new_params.exposure.gain = 3;
					break;
				default:
					retval = -EINVAL;
					break;
				}
				new_params.exposure.expMode = 1;
				if(new_params.flickerControl.flickerMode != 0)
					command_flags |= COMMAND_SETFLICKERCTRL;
				new_params.flickerControl.flickerMode = 0;
				command_flags |= COMMAND_SETEXPOSURE;
				if (new_params.exposure.gain >
				    new_params.exposure.gainMode-1)
					retval = -EINVAL;
			}
		} else if (MATCH("fine_exp")) {
			if (!retval)
				val = VALUE/2;

			if (!retval) {
				if (val < 256) {
					/* 1-02 firmware limits fineExp/2 to 127*/
					if (FIRMWARE_VERSION(1,2) && val > 127)
						val = 127;
					new_params.exposure.fineExp = val;
					new_params.exposure.expMode = 1;
					command_flags |= COMMAND_SETEXPOSURE;
					if(new_params.flickerControl.flickerMode != 0)
						command_flags |= COMMAND_SETFLICKERCTRL;
					new_params.flickerControl.flickerMode = 0;
					command_flags |= COMMAND_SETFLICKERCTRL;
				} else
					retval = -EINVAL;
			}
		} else if (MATCH("coarse_exp")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= MAX_EXP) {
					if (FIRMWARE_VERSION(1,2) &&
					    val > MAX_EXP_102)
						val = MAX_EXP_102;
					new_params.exposure.coarseExpLo =
						val & 0xff;
					new_params.exposure.coarseExpHi =
						val >> 8;
					new_params.exposure.expMode = 1;
					command_flags |= COMMAND_SETEXPOSURE;
					if(new_params.flickerControl.flickerMode != 0)
						command_flags |= COMMAND_SETFLICKERCTRL;
					new_params.flickerControl.flickerMode = 0;
					command_flags |= COMMAND_SETFLICKERCTRL;
				} else
					retval = -EINVAL;
			}
		} else if (MATCH("red_comp")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val >= COMP_RED && val <= 255) {
					new_params.exposure.redComp = val;
					new_params.exposure.compMode = 1;
					command_flags |= COMMAND_SETEXPOSURE;
				} else
					retval = -EINVAL;
			}
		} else if (MATCH("green1_comp")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val >= COMP_GREEN1 && val <= 255) {
					new_params.exposure.green1Comp = val;
					new_params.exposure.compMode = 1;
					command_flags |= COMMAND_SETEXPOSURE;
				} else
					retval = -EINVAL;
			}
		} else if (MATCH("green2_comp")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val >= COMP_GREEN2 && val <= 255) {
					new_params.exposure.green2Comp = val;
					new_params.exposure.compMode = 1;
					command_flags |= COMMAND_SETEXPOSURE;
				} else
					retval = -EINVAL;
			}
		} else if (MATCH("blue_comp")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val >= COMP_BLUE && val <= 255) {
					new_params.exposure.blueComp = val;
					new_params.exposure.compMode = 1;
					command_flags |= COMMAND_SETEXPOSURE;
				} else
					retval = -EINVAL;
			}
		} else if (MATCH("apcor_gain1")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				command_flags |= COMMAND_SETAPCOR;
				if (val <= 0xff)
					new_params.apcor.gain1 = val;
				else
					retval = -EINVAL;
			}
		} else if (MATCH("apcor_gain2")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				command_flags |= COMMAND_SETAPCOR;
				if (val <= 0xff)
					new_params.apcor.gain2 = val;
				else
					retval = -EINVAL;
			}
		} else if (MATCH("apcor_gain4")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				command_flags |= COMMAND_SETAPCOR;
				if (val <= 0xff)
					new_params.apcor.gain4 = val;
				else
					retval = -EINVAL;
			}
		} else if (MATCH("apcor_gain8")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				command_flags |= COMMAND_SETAPCOR;
				if (val <= 0xff)
					new_params.apcor.gain8 = val;
				else
					retval = -EINVAL;
			}
		} else if (MATCH("vl_offset_gain1")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.vlOffset.gain1 = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETVLOFFSET;
		} else if (MATCH("vl_offset_gain2")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.vlOffset.gain2 = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETVLOFFSET;
		} else if (MATCH("vl_offset_gain4")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.vlOffset.gain4 = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETVLOFFSET;
		} else if (MATCH("vl_offset_gain8")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.vlOffset.gain8 = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETVLOFFSET;
		} else if (MATCH("flicker_control")) {
			if (!retval && MATCH("on")) {
				set_flicker(&new_params, &command_flags, 1);
			} else if (!retval && MATCH("off")) {
				set_flicker(&new_params, &command_flags, 0);
			} else
				retval = -EINVAL;

			command_flags |= COMMAND_SETFLICKERCTRL;
		} else if (MATCH("mains_frequency")) {
			if (!retval && MATCH("50")) {
				new_mains = 0;
				new_params.flickerControl.coarseJump =
					flicker_jumps[new_mains]
					[new_params.sensorFps.baserate]
					[new_params.sensorFps.divisor];
				if (new_params.flickerControl.flickerMode)
					command_flags |= COMMAND_SETFLICKERCTRL;
			} else if (!retval && MATCH("60")) {
				new_mains = 1;
				new_params.flickerControl.coarseJump =
					flicker_jumps[new_mains]
					[new_params.sensorFps.baserate]
					[new_params.sensorFps.divisor];
				if (new_params.flickerControl.flickerMode)
					command_flags |= COMMAND_SETFLICKERCTRL;
			} else
				retval = -EINVAL;
		} else if (MATCH("allowable_overexposure")) {
			if (!retval && MATCH("auto")) {
				new_params.flickerControl.allowableOverExposure =
					-find_over_exposure(new_params.colourParams.brightness);
				if(new_params.flickerControl.flickerMode != 0)
					command_flags |= COMMAND_SETFLICKERCTRL;
			} else {
				if (!retval)
					val = VALUE;

				if (!retval) {
					if (val <= 0xff) {
						new_params.flickerControl.
							allowableOverExposure = val;
						if(new_params.flickerControl.flickerMode != 0)
							command_flags |= COMMAND_SETFLICKERCTRL;
					} else
						retval = -EINVAL;
				}
			}
		} else if (MATCH("compression_mode")) {
			if (!retval && MATCH("none"))
				new_params.compression.mode =
					CPIA_COMPRESSION_NONE;
			else if (!retval && MATCH("auto"))
				new_params.compression.mode =
					CPIA_COMPRESSION_AUTO;
			else if (!retval && MATCH("manual"))
				new_params.compression.mode =
					CPIA_COMPRESSION_MANUAL;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETCOMPRESSION;
		} else if (MATCH("decimation_enable")) {
			if (!retval && MATCH("off"))
				new_params.compression.decimation = 0;
			else if (!retval && MATCH("on"))
				new_params.compression.decimation = 1;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETCOMPRESSION;
		} else if (MATCH("compression_target")) {
			if (!retval && MATCH("quality"))
				new_params.compressionTarget.frTargeting =
					CPIA_COMPRESSION_TARGET_QUALITY;
			else if (!retval && MATCH("framerate"))
				new_params.compressionTarget.frTargeting =
					CPIA_COMPRESSION_TARGET_FRAMERATE;
			else
				retval = -EINVAL;

			command_flags |= COMMAND_SETCOMPRESSIONTARGET;
		} else if (MATCH("target_framerate")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if(val > 0 && val <= 30)
					new_params.compressionTarget.targetFR = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONTARGET;
		} else if (MATCH("target_quality")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if(val > 0 && val <= 64)
					new_params.compressionTarget.targetQ = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONTARGET;
		} else if (MATCH("y_threshold")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val < 32)
					new_params.yuvThreshold.yThreshold = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETYUVTHRESH;
		} else if (MATCH("uv_threshold")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val < 32)
					new_params.yuvThreshold.uvThreshold = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETYUVTHRESH;
		} else if (MATCH("hysteresis")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.hysteresis = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("threshold_max")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.threshMax = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("small_step")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.smallStep = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("large_step")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.largeStep = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("decimation_hysteresis")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.decimationHysteresis = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("fr_diff_step_thresh")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.frDiffStepThresh = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("q_diff_step_thresh")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.qDiffStepThresh = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("decimation_thresh_mod")) {
			if (!retval)
				val = VALUE;

			if (!retval) {
				if (val <= 0xff)
					new_params.compressionParams.decimationThreshMod = val;
				else
					retval = -EINVAL;
			}
			command_flags |= COMMAND_SETCOMPRESSIONPARAMS;
		} else if (MATCH("toplight")) {
			if (!retval && MATCH("on"))
				new_params.qx3.toplight = 1;
			else if (!retval && MATCH("off"))
				new_params.qx3.toplight = 0;
			else
				retval = -EINVAL;
			command_flags |= COMMAND_SETLIGHTS;
		} else if (MATCH("bottomlight")) {
			if (!retval && MATCH("on"))
				new_params.qx3.bottomlight = 1;
			else if (!retval && MATCH("off"))
				new_params.qx3.bottomlight = 0;
			else
				retval = -EINVAL;
			command_flags |= COMMAND_SETLIGHTS;
		} else {
			DBG("No match found\n");
			retval = -EINVAL;
		}

		if (!retval) {
			while (count && isspace(*buffer) && *buffer != '\n') {
				--count;
				++buffer;
			}
			if (count) {
				if (*buffer == '\0' && count != 1)
					retval = -EINVAL;
				else if (*buffer != '\n' && *buffer != ';' &&
					 *buffer != '\0')
					retval = -EINVAL;
				else {
					--count;
					++buffer;
				}
			}
		}
	}
#undef MATCH
#undef VALUE
#undef FIRMWARE_VERSION
	if (!retval) {
		if (command_flags & COMMAND_SETCOLOURPARAMS) {
			/* Adjust cam->vp to reflect these changes */
			cam->vp.brightness =
				new_params.colourParams.brightness*65535/100;
			cam->vp.contrast =
				new_params.colourParams.contrast*65535/100;
			cam->vp.colour =
				new_params.colourParams.saturation*65535/100;
		}
		if((command_flags & COMMAND_SETEXPOSURE) &&
		   new_params.exposure.expMode == 2)
			cam->exposure_status = EXPOSURE_NORMAL;

		memcpy(&cam->params, &new_params, sizeof(struct cam_params));
		cam->mainsFreq = new_mains;
		cam->cmd_queue |= command_flags;
		retval = size;
	} else
		DBG("error: %d\n", retval);

	mutex_unlock(&cam->param_lock);

out:
	free_page((unsigned long)page);
	return retval;
}

static void create_proc_cpia_cam(struct cam_data *cam)
{
	char name[5 + 1 + 10 + 1];
	struct proc_dir_entry *ent;

	if (!cpia_proc_root || !cam)
		return;

	snprintf(name, sizeof(name), "video%d", cam->vdev.num);

	ent = create_proc_entry(name, S_IFREG|S_IRUGO|S_IWUSR, cpia_proc_root);
	if (!ent)
		return;

	ent->data = cam;
	ent->read_proc = cpia_read_proc;
	ent->write_proc = cpia_write_proc;
	/*
	   size of the proc entry is 3736 bytes for the standard webcam;
	   the extra features of the QX3 microscope add 189 bytes.
	   (we have not yet probed the camera to see which type it is).
	*/
	ent->size = 3736 + 189;
	cam->proc_entry = ent;
}

static void destroy_proc_cpia_cam(struct cam_data *cam)
{
	char name[5 + 1 + 10 + 1];

	if (!cam || !cam->proc_entry)
		return;

	snprintf(name, sizeof(name), "video%d", cam->vdev.num);
	remove_proc_entry(name, cpia_proc_root);
	cam->proc_entry = NULL;
}

static void proc_cpia_create(void)
{
	cpia_proc_root = proc_mkdir("cpia", NULL);

	if (cpia_proc_root)
		cpia_proc_root->owner = THIS_MODULE;
	else
		LOG("Unable to initialise /proc/cpia\n");
}

static void __exit proc_cpia_destroy(void)
{
	remove_proc_entry("cpia", NULL);
}
#endif /* CONFIG_PROC_FS */

/* ----------------------- debug functions ---------------------- */

#define printstatus(cam) \
  DBG("%02x %02x %02x %02x %02x %02x %02x %02x\n",\
	cam->params.status.systemState, cam->params.status.grabState, \
	cam->params.status.streamState, cam->params.status.fatalError, \
	cam->params.status.cmdError, cam->params.status.debugFlags, \
	cam->params.status.vpStatus, cam->params.status.errorCode);

/* ----------------------- v4l helpers -------------------------- */

/* supported frame palettes and depths */
static inline int valid_mode(u16 palette, u16 depth)
{
	if ((palette == VIDEO_PALETTE_YUV422 && depth == 16) ||
	    (palette == VIDEO_PALETTE_YUYV && depth == 16))
		return 1;

	if (colorspace_conv)
		return (palette == VIDEO_PALETTE_GREY && depth == 8) ||
		       (palette == VIDEO_PALETTE_RGB555 && depth == 16) ||
		       (palette == VIDEO_PALETTE_RGB565 && depth == 16) ||
		       (palette == VIDEO_PALETTE_RGB24 && depth == 24) ||
		       (palette == VIDEO_PALETTE_RGB32 && depth == 32) ||
		       (palette == VIDEO_PALETTE_UYVY && depth == 16);

	return 0;
}

static int match_videosize( int width, int height )
{
	/* return the best match, where 'best' is as always
	 * the largest that is not bigger than what is requested. */
	if (width>=352 && height>=288)
		return VIDEOSIZE_352_288; /* CIF */

	if (width>=320 && height>=240)
		return VIDEOSIZE_320_240; /* SIF */

	if (width>=288 && height>=216)
		return VIDEOSIZE_288_216;

	if (width>=256 && height>=192)
		return VIDEOSIZE_256_192;

	if (width>=224 && height>=168)
		return VIDEOSIZE_224_168;