Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[PATCH] perf_events: improve Intel event scheduling

71 views
Skip to first unread message

Stephane Eranian

unread,
Oct 19, 2009, 11:10:03 AM10/19/09
to
This patch improves Intel event scheduling by maximizing
the use of PMU registers regardless of the order in which
events are submitted.

The algorithm takes into account the list of counter constraints
for each event. It assigns events to counters from the most
constrained, i.e., works on only one counter, to the least
constrained, i.e., works on any counter.

Fixed counter events and the BTS special event are also handled via
this algorithm which is designed to be fairly generic.

The patch also updates the validation of an event to use the
scheduling algorithm. This will cause early failure in
perf_event_open().

Signed-off-by: Stephane Eranian <era...@gmail.com>
---
arch/x86/include/asm/perf_event.h | 6 +-
arch/x86/kernel/cpu/perf_event.c | 497 +++++++++++++++++++++++--------------
2 files changed, 318 insertions(+), 185 deletions(-)

diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h
index 8d9f854..7c737af 100644
--- a/arch/x86/include/asm/perf_event.h
+++ b/arch/x86/include/asm/perf_event.h
@@ -26,7 +26,9 @@
/*
* Includes eventsel and unit mask as well:
*/
-#define ARCH_PERFMON_EVENT_MASK 0xffff
+#define ARCH_PERFMON_EVENTSEL_EVENT_MASK 0x00ff
+#define ARCH_PERFMON_EVENTSEL_UNIT_MASK 0xff00
+#define ARCH_PERFMON_EVENT_MASK (ARCH_PERFMON_EVENTSEL_UNIT_MASK|ARCH_PERFMON_EVENTSEL_EVENT_MASK)

/*
* filter mask to validate fixed counter events.
@@ -38,6 +40,8 @@
* The any-thread option is supported starting with v3.
*/
#define ARCH_PERFMON_EVENT_FILTER_MASK 0xff840000
+#define ARCH_PERFMON_FIXED_EVENT_MASK (ARCH_PERFMON_EVENT_FILTER_MASK|ARCH_PERFMON_EVENT_MASK)
+

#define ARCH_PERFMON_UNHALTED_CORE_CYCLES_SEL 0x3c
#define ARCH_PERFMON_UNHALTED_CORE_CYCLES_UMASK (0x00 << 8)
diff --git a/arch/x86/kernel/cpu/perf_event.c b/arch/x86/kernel/cpu/perf_event.c
index 2e20bca..0f96c51 100644
--- a/arch/x86/kernel/cpu/perf_event.c
+++ b/arch/x86/kernel/cpu/perf_event.c
@@ -7,6 +7,7 @@
* Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter
* Copyright (C) 2008-2009 Red Hat, Inc., Peter Zijlstra <pzij...@redhat.com>
* Copyright (C) 2009 Intel Corporation, <markus.t...@intel.com>
+ * Copyright (C) 2009 Google, Inc., Stephane Eranian
*
* For licencing details see kernel-base/COPYING
*/
@@ -68,6 +69,15 @@ struct debug_store {
u64 pebs_event_reset[MAX_PEBS_EVENTS];
};

+#define BITS_TO_U64(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(u64))
+
+struct event_constraint {
+ u64 idxmsk[BITS_TO_U64(X86_PMC_IDX_MAX)];
+ int code;
+ int mask;
+ int weight;
+};
+
struct cpu_hw_events {
struct perf_event *events[X86_PMC_IDX_MAX];
unsigned long used_mask[BITS_TO_LONGS(X86_PMC_IDX_MAX)];
@@ -75,19 +85,23 @@ struct cpu_hw_events {
unsigned long interrupts;
int enabled;
struct debug_store *ds;
-};

-struct event_constraint {
- unsigned long idxmsk[BITS_TO_LONGS(X86_PMC_IDX_MAX)];
- int code;
+ int n_events;
+ struct event_constraint *constraints[X86_PMC_IDX_MAX];
+ struct perf_event *event_list[X86_PMC_IDX_MAX]; /* in order they are collected */
};

-#define EVENT_CONSTRAINT(c, m) { .code = (c), .idxmsk[0] = (m) }
-#define EVENT_CONSTRAINT_END { .code = 0, .idxmsk[0] = 0 }
+#define EVENT_CONSTRAINT(c, n, w, m) { \
+ .code = (c), \
+ .mask = (m), \
+ .weight = (w), \
+ .idxmsk[0] = (n) }

-#define for_each_event_constraint(e, c) \
- for ((e) = (c); (e)->idxmsk[0]; (e)++)
+#define EVENT_CONSTRAINT_END \
+ { .code = 0, .mask = 0, .weight = 0, .idxmsk[0] = 0 }

+#define for_each_event_constraint(e, c) \
+ for ((e) = (c); (e)->weight; (e)++)

/*
* struct x86_pmu - generic x86 pmu
@@ -114,8 +128,7 @@ struct x86_pmu {
u64 intel_ctrl;
void (*enable_bts)(u64 config);
void (*disable_bts)(void);
- int (*get_event_idx)(struct cpu_hw_events *cpuc,
- struct hw_perf_event *hwc);
+ struct event_constraint *(*get_event_constraints)(struct perf_event *event);
};

static struct x86_pmu x86_pmu __read_mostly;
@@ -124,7 +137,7 @@ static DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events) = {
.enabled = 1,
};

-static const struct event_constraint *event_constraints;
+static struct event_constraint *event_constraints;

/*
* Not sure about some of these
@@ -171,14 +184,14 @@ static u64 p6_pmu_raw_event(u64 hw_event)
return hw_event & P6_EVNTSEL_MASK;
}

-static const struct event_constraint intel_p6_event_constraints[] =
+static struct event_constraint intel_p6_event_constraints[] =
{
- EVENT_CONSTRAINT(0xc1, 0x1), /* FLOPS */
- EVENT_CONSTRAINT(0x10, 0x1), /* FP_COMP_OPS_EXE */
- EVENT_CONSTRAINT(0x11, 0x1), /* FP_ASSIST */
- EVENT_CONSTRAINT(0x12, 0x2), /* MUL */
- EVENT_CONSTRAINT(0x13, 0x2), /* DIV */
- EVENT_CONSTRAINT(0x14, 0x1), /* CYCLES_DIV_BUSY */
+ EVENT_CONSTRAINT(0xc1, 0x1, 1, 0xff), /* FLOPS */
+ EVENT_CONSTRAINT(0x10, 0x1, 1, 0xff), /* FP_COMP_OPS_EXE */
+ EVENT_CONSTRAINT(0x11, 0x1, 1, 0xff), /* FP_ASSIST */
+ EVENT_CONSTRAINT(0x12, 0x2, 1, 0xff), /* MUL */
+ EVENT_CONSTRAINT(0x13, 0x2, 1, 0xff), /* DIV */
+ EVENT_CONSTRAINT(0x14, 0x1, 1, 0xff), /* CYCLES_DIV_BUSY */
EVENT_CONSTRAINT_END
};

@@ -196,32 +209,35 @@ static const u64 intel_perfmon_event_map[] =
[PERF_COUNT_HW_BUS_CYCLES] = 0x013c,
};

-static const struct event_constraint intel_core_event_constraints[] =
-{
- EVENT_CONSTRAINT(0x10, 0x1), /* FP_COMP_OPS_EXE */
- EVENT_CONSTRAINT(0x11, 0x2), /* FP_ASSIST */
- EVENT_CONSTRAINT(0x12, 0x2), /* MUL */
- EVENT_CONSTRAINT(0x13, 0x2), /* DIV */
- EVENT_CONSTRAINT(0x14, 0x1), /* CYCLES_DIV_BUSY */
- EVENT_CONSTRAINT(0x18, 0x1), /* IDLE_DURING_DIV */
- EVENT_CONSTRAINT(0x19, 0x2), /* DELAYED_BYPASS */
- EVENT_CONSTRAINT(0xa1, 0x1), /* RS_UOPS_DISPATCH_CYCLES */
- EVENT_CONSTRAINT(0xcb, 0x1), /* MEM_LOAD_RETIRED */
+static struct event_constraint intel_core_event_constraints[] =
+{
+ EVENT_CONSTRAINT(0xc0, (0x3|(1ULL<<32)), 3, ARCH_PERFMON_FIXED_EVENT_MASK), /* INSTRUCTIONS_RETIRED */
+ EVENT_CONSTRAINT(0x3c, (0x3|(1ULL<<33)), 3, ARCH_PERFMON_FIXED_EVENT_MASK), /* UNHALTED_CORE_CYCLES */
+ EVENT_CONSTRAINT(0x10, 0x1, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* FP_COMP_OPS_EXE */
+ EVENT_CONSTRAINT(0x11, 0x2, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* FP_ASSIST */
+ EVENT_CONSTRAINT(0x12, 0x2, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* MUL */
+ EVENT_CONSTRAINT(0x13, 0x2, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* DIV */
+ EVENT_CONSTRAINT(0x14, 0x1, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* CYCLES_DIV_BUSY */
+ EVENT_CONSTRAINT(0x18, 0x1, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* IDLE_DURING_DIV */
+ EVENT_CONSTRAINT(0x19, 0x2, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* DELAYED_BYPASS */
+ EVENT_CONSTRAINT(0xa1, 0x1, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* RS_UOPS_DISPATCH_CYCLES */
+ EVENT_CONSTRAINT(0xcb, 0x1, 1, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* MEM_LOAD_RETIRED */
EVENT_CONSTRAINT_END
};

-static const struct event_constraint intel_nehalem_event_constraints[] =
-{
- EVENT_CONSTRAINT(0x40, 0x3), /* L1D_CACHE_LD */
- EVENT_CONSTRAINT(0x41, 0x3), /* L1D_CACHE_ST */
- EVENT_CONSTRAINT(0x42, 0x3), /* L1D_CACHE_LOCK */
- EVENT_CONSTRAINT(0x43, 0x3), /* L1D_ALL_REF */
- EVENT_CONSTRAINT(0x4e, 0x3), /* L1D_PREFETCH */
- EVENT_CONSTRAINT(0x4c, 0x3), /* LOAD_HIT_PRE */
- EVENT_CONSTRAINT(0x51, 0x3), /* L1D */
- EVENT_CONSTRAINT(0x52, 0x3), /* L1D_CACHE_PREFETCH_LOCK_FB_HIT */
- EVENT_CONSTRAINT(0x53, 0x3), /* L1D_CACHE_LOCK_FB_HIT */
- EVENT_CONSTRAINT(0xc5, 0x3), /* CACHE_LOCK_CYCLES */
+static struct event_constraint intel_nehalem_event_constraints[] = {
+ EVENT_CONSTRAINT(0xc0, (0x3|(1ULL<<32)), 3, ARCH_PERFMON_FIXED_EVENT_MASK), /* INSTRUCTIONS_RETIRED */
+ EVENT_CONSTRAINT(0x3c, (0x3|(1ULL<<33)), 3, ARCH_PERFMON_FIXED_EVENT_MASK), /* UNHALTED_CORE_CYCLES */
+ EVENT_CONSTRAINT(0x40, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_LD */
+ EVENT_CONSTRAINT(0x41, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_ST */
+ EVENT_CONSTRAINT(0x42, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_LOCK */
+ EVENT_CONSTRAINT(0x43, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_ALL_REF */
+ EVENT_CONSTRAINT(0x4e, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_PREFETCH */
+ EVENT_CONSTRAINT(0x4c, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* LOAD_HIT_PRE */
+ EVENT_CONSTRAINT(0x51, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D */
+ EVENT_CONSTRAINT(0x52, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_PREFETCH_LOCK_FB_HIT */
+ EVENT_CONSTRAINT(0x53, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_LOCK_FB_HIT */
+ EVENT_CONSTRAINT(0xc5, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* CACHE_LOCK_CYCLES */
EVENT_CONSTRAINT_END
};

@@ -1120,9 +1136,15 @@ static void amd_pmu_disable_all(void)

void hw_perf_disable(void)
{
+ struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+
if (!x86_pmu_initialized())
return;
- return x86_pmu.disable_all();
+
+ if (cpuc->enabled)
+ cpuc->n_events = 0;
+
+ x86_pmu.disable_all();
}

static void p6_pmu_enable_all(void)
@@ -1391,124 +1413,6 @@ static void amd_pmu_enable_event(struct hw_perf_event *hwc, int idx)
x86_pmu_enable_event(hwc, idx);
}

-static int fixed_mode_idx(struct hw_perf_event *hwc)
-{
- unsigned int hw_event;
-
- hw_event = hwc->config & ARCH_PERFMON_EVENT_MASK;
-
- if (unlikely((hw_event ==
- x86_pmu.event_map(PERF_COUNT_HW_BRANCH_INSTRUCTIONS)) &&
- (hwc->sample_period == 1)))
- return X86_PMC_IDX_FIXED_BTS;
-
- if (!x86_pmu.num_events_fixed)
- return -1;
-
- /*
- * fixed counters do not take all possible filters
- */
- if (hwc->config & ARCH_PERFMON_EVENT_FILTER_MASK)
- return -1;
-
- if (unlikely(hw_event == x86_pmu.event_map(PERF_COUNT_HW_INSTRUCTIONS)))
- return X86_PMC_IDX_FIXED_INSTRUCTIONS;
- if (unlikely(hw_event == x86_pmu.event_map(PERF_COUNT_HW_CPU_CYCLES)))
- return X86_PMC_IDX_FIXED_CPU_CYCLES;
- if (unlikely(hw_event == x86_pmu.event_map(PERF_COUNT_HW_BUS_CYCLES)))
- return X86_PMC_IDX_FIXED_BUS_CYCLES;
-
- return -1;
-}
-
-/*
- * generic counter allocator: get next free counter
- */
-static int
-gen_get_event_idx(struct cpu_hw_events *cpuc, struct hw_perf_event *hwc)
-{
- int idx;
-
- idx = find_first_zero_bit(cpuc->used_mask, x86_pmu.num_events);
- return idx == x86_pmu.num_events ? -1 : idx;
-}
-
-/*
- * intel-specific counter allocator: check event constraints
- */
-static int
-intel_get_event_idx(struct cpu_hw_events *cpuc, struct hw_perf_event *hwc)
-{
- const struct event_constraint *event_constraint;
- int i, code;
-
- if (!event_constraints)
- goto skip;
-
- code = hwc->config & CORE_EVNTSEL_EVENT_MASK;
-
- for_each_event_constraint(event_constraint, event_constraints) {
- if (code == event_constraint->code) {
- for_each_bit(i, event_constraint->idxmsk, X86_PMC_IDX_MAX) {
- if (!test_and_set_bit(i, cpuc->used_mask))
- return i;
- }
- return -1;
- }
- }
-skip:
- return gen_get_event_idx(cpuc, hwc);
-}
-
-static int
-x86_schedule_event(struct cpu_hw_events *cpuc, struct hw_perf_event *hwc)
-{
- int idx;
-
- idx = fixed_mode_idx(hwc);
- if (idx == X86_PMC_IDX_FIXED_BTS) {
- /* BTS is already occupied. */
- if (test_and_set_bit(idx, cpuc->used_mask))
- return -EAGAIN;
-
- hwc->config_base = 0;
- hwc->event_base = 0;
- hwc->idx = idx;
- } else if (idx >= 0) {
- /*
- * Try to get the fixed event, if that is already taken
- * then try to get a generic event:
- */
- if (test_and_set_bit(idx, cpuc->used_mask))
- goto try_generic;
-
- hwc->config_base = MSR_ARCH_PERFMON_FIXED_CTR_CTRL;
- /*
- * We set it so that event_base + idx in wrmsr/rdmsr maps to
- * MSR_ARCH_PERFMON_FIXED_CTR0 ... CTR2:
- */
- hwc->event_base =
- MSR_ARCH_PERFMON_FIXED_CTR0 - X86_PMC_IDX_FIXED;
- hwc->idx = idx;
- } else {
- idx = hwc->idx;
- /* Try to get the previous generic event again */
- if (idx == -1 || test_and_set_bit(idx, cpuc->used_mask)) {
-try_generic:
- idx = x86_pmu.get_event_idx(cpuc, hwc);
- if (idx == -1)
- return -EAGAIN;
-
- set_bit(idx, cpuc->used_mask);
- hwc->idx = idx;
- }
- hwc->config_base = x86_pmu.eventsel;
- hwc->event_base = x86_pmu.perfctr;
- }
-
- return idx;
-}
-
/*
* Find a PMC slot for the freshly enabled / scheduled in event:
*/
@@ -1518,7 +1422,7 @@ static int x86_pmu_enable(struct perf_event *event)
struct hw_perf_event *hwc = &event->hw;
int idx;

- idx = x86_schedule_event(cpuc, hwc);
+ idx = hwc->idx;
if (idx < 0)
return idx;

@@ -1958,6 +1862,224 @@ perf_event_nmi_handler(struct notifier_block *self,
return NOTIFY_STOP;
}

+static struct event_constraint bts_constraint={
+ .code = 0,
+ .mask = 0,
+ .weight = 1,
+ .idxmsk[0] = 1ULL << X86_PMC_IDX_FIXED_BTS
+};
+
+static struct event_constraint *intel_special_constraints(struct perf_event *event)
+{
+ unsigned int hw_event;
+
+ hw_event = event->hw.config & ARCH_PERFMON_EVENT_MASK;
+
+ if (unlikely((hw_event ==
+ x86_pmu.event_map(PERF_COUNT_HW_BRANCH_INSTRUCTIONS)) &&
+ (event->hw.sample_period == 1)))
+ return &bts_constraint;
+
+ return NULL;
+}
+
+static struct event_constraint *intel_get_event_constraints(struct perf_event *event)
+{
+ struct event_constraint *c;
+
+ c = intel_special_constraints(event);
+ if (c)
+ return c;
+
+ if (event_constraints)
+ for_each_event_constraint(c, event_constraints) {
+ if ((event->hw.config & c->mask) == c->code)
+ return c;
+ }
+
+ return NULL;
+}
+
+static struct event_constraint *amd_get_event_constraints(struct perf_event *event)
+{
+ return NULL;
+}
+
+static int schedule_events(struct cpu_hw_events *cpuhw, int n, bool assign)
+{
+ int i, j , w, num, lim;
+ int weight, wmax;
+ struct event_constraint *c;
+ unsigned long used_mask[BITS_TO_LONGS(X86_PMC_IDX_MAX)];
+ int assignments[X86_PMC_IDX_MAX];
+ struct hw_perf_event *hwc;
+
+ bitmap_zero(used_mask, X86_PMC_IDX_MAX);
+
+ /*
+ * weight = number of possible counters
+ *
+ * 1 = most constrained, only works on one counter
+ * wmax = least constrained, works on 1 fixed counter
+ * or any generic counter
+ *
+ * assign events to counters starting with most
+ * constrained events.
+ */
+ wmax = 1 + x86_pmu.num_events;
+ num = n;
+ for(w=1; num && w <= wmax; w++) {
+
+ /* for each event */
+ for(i=0; i < n; i++) {
+ c = cpuhw->constraints[i];
+ hwc = &cpuhw->event_list[i]->hw;
+
+ weight = c ? c->weight : x86_pmu.num_events;
+ if (weight != w)
+ continue;
+
+ /*
+ * try to reuse previous assignment
+ *
+ * This is possible despite the fact that
+ * events or events order may have changed.
+ *
+ * What matters is the level of constraints
+ * of an event and this is constant for now.
+ *
+ * This is possible also because we always
+ * scan from most to least constrained. Thus,
+ * if a counter can be reused, it means no,
+ * more constrained events, needed it. And
+ * next events will either compete for it
+ * (which cannot be solved anyway) or they
+ * have fewer constraints, and they can use
+ * another counter.
+ */
+ j = hwc->idx;
+ if (j != -1 && !test_bit(j, used_mask))
+ goto skip;
+
+ if (c) {
+ lim = X86_PMC_IDX_MAX;
+ for_each_bit(j, (unsigned long *)c->idxmsk, lim)
+ if (!test_bit(j, used_mask))
+ break;
+
+ } else {
+ lim = x86_pmu.num_events;
+ /*
+ * fixed counter events have necessarily a
+ * constraint thus we come here only for
+ * generic counters and thus we limit the
+ * scan to those
+ */
+ j = find_first_zero_bit(used_mask, lim);
+ }
+ if (j == lim)
+ return -EAGAIN;
+skip:
+ set_bit(j, used_mask);
+ assignments[i] = j;
+ num--;
+ }
+ }
+ if (num)
+ return -ENOSPC;
+
+ /* just simulate scheduling */
+ if (!assign)
+ return 0;
+
+ /*
+ * commit assignments
+ */
+ for(i=0; i < n; i++) {
+ hwc = &cpuhw->event_list[i]->hw;
+
+ hwc->idx = assignments[i];
+
+ set_bit(hwc->idx, cpuhw->used_mask);
+
+ if (hwc->idx == X86_PMC_IDX_FIXED_BTS) {
+ hwc->config_base = 0;
+ hwc->event_base = 0;
+ } else if (hwc->idx >= X86_PMC_IDX_FIXED) {
+ hwc->config_base = MSR_ARCH_PERFMON_FIXED_CTR_CTRL;
+ /*
+ * We set it so that event_base + idx in wrmsr/rdmsr maps to
+ * MSR_ARCH_PERFMON_FIXED_CTR0 ... CTR2:
+ */
+ hwc->event_base =
+ MSR_ARCH_PERFMON_FIXED_CTR0 - X86_PMC_IDX_FIXED;
+ } else {
+ hwc->config_base = x86_pmu.eventsel;
+ hwc->event_base = x86_pmu.perfctr;
+ }
+ }
+ cpuhw->n_events = n;
+ return 0;
+}
+
+static int collect_events(struct cpu_hw_events *cpuhw, struct perf_event *leader)
+{
+ struct perf_event *event;
+ int n, max_count;
+
+ max_count = x86_pmu.num_events + x86_pmu.num_events_fixed;
+
+ /* current number of events already accepted */
+ n = cpuhw->n_events;
+
+ if (!is_software_event(leader)) {
+ if (n >= max_count)
+ return -ENOSPC;
+ cpuhw->constraints[n] = x86_pmu.get_event_constraints(leader);
+ cpuhw->event_list[n] = leader;
+ n++;
+ }
+
+ list_for_each_entry(event, &leader->sibling_list, group_entry) {
+ if (is_software_event(event) ||
+ event->state == PERF_EVENT_STATE_OFF)
+ continue;
+
+ if (n >= max_count)
+ return -ENOSPC;
+
+ cpuhw->constraints[n] = x86_pmu.get_event_constraints(event);
+ cpuhw->event_list[n] = event;
+ n++;
+ }
+ return n;
+}
+
+/*
+ * Called to enable a whole group of events.
+ * Returns 1 if the group was enabled, or -EAGAIN if it could not be.
+ * Assumes the caller has disabled interrupts and has
+ * frozen the PMU with hw_perf_save_disable.
+ */
+int hw_perf_group_sched_in(struct perf_event *leader,
+ struct perf_cpu_context *cpuctx,
+ struct perf_event_context *ctx, int cpu)
+{
+ struct cpu_hw_events *cpuhw = &per_cpu(cpu_hw_events, cpu);
+ int n, ret;
+
+ n = collect_events(cpuhw, leader);
+ if (n < 0)
+ return n;
+
+ ret = schedule_events(cpuhw, n, true);
+ if (ret)
+ return ret;
+
+ /* 0 means successful and enable each event in caller */
+ return 0;
+}
+
static __read_mostly struct notifier_block perf_event_nmi_notifier = {
.notifier_call = perf_event_nmi_handler,
.next = NULL,
@@ -1989,7 +2111,7 @@ static struct x86_pmu p6_pmu = {
*/
.event_bits = 32,
.event_mask = (1ULL << 32) - 1,
- .get_event_idx = intel_get_event_idx,
+ .get_event_constraints = intel_get_event_constraints
};

static struct x86_pmu intel_pmu = {
@@ -2013,7 +2135,7 @@ static struct x86_pmu intel_pmu = {
.max_period = (1ULL << 31) - 1,
.enable_bts = intel_pmu_enable_bts,
.disable_bts = intel_pmu_disable_bts,
- .get_event_idx = intel_get_event_idx,
+ .get_event_constraints = intel_get_event_constraints
};

static struct x86_pmu amd_pmu = {
@@ -2034,7 +2156,7 @@ static struct x86_pmu amd_pmu = {
.apic = 1,
/* use highest bit to detect overflow */
.max_period = (1ULL << 47) - 1,
- .get_event_idx = gen_get_event_idx,
+ .get_event_constraints = amd_get_event_constraints
};

static int p6_pmu_init(void)
@@ -2123,8 +2245,8 @@ static int intel_pmu_init(void)
memcpy(hw_cache_event_ids, core2_hw_cache_event_ids,
sizeof(hw_cache_event_ids));

- pr_cont("Core2 events, ");
event_constraints = intel_core_event_constraints;
+ pr_cont("Core2 events, ");
break;
default:
case 26:
@@ -2224,36 +2346,43 @@ static const struct pmu pmu = {
.unthrottle = x86_pmu_unthrottle,
};

-static int
-validate_event(struct cpu_hw_events *cpuc, struct perf_event *event)
-{
- struct hw_perf_event fake_event = event->hw;
-
- if (event->pmu != &pmu)
- return 0;
-
- return x86_schedule_event(cpuc, &fake_event);
-}
-
+/*
+ * validate a single event group
+ *
+ * validation include:
+ * - check events are compatible which each other
+ * - events do not compete for the same counter
+ * - number of events <= number of counters
+ *
+ * validation ensures the group can be loaded onto the
+ * PMU if it were the only group available.
+ */
static int validate_group(struct perf_event *event)
{
- struct perf_event *sibling, *leader = event->group_leader;
+ struct perf_event *leader = event->group_leader;
struct cpu_hw_events fake_pmu;
+ int n, ret;

memset(&fake_pmu, 0, sizeof(fake_pmu));

- if (!validate_event(&fake_pmu, leader))
+ /*
+ * the event is not yet connected with its
+ * siblings thererfore we must first collect
+ * existing siblings, then add the new event
+ * before we can simulate the scheduling
+ */
+ n = collect_events(&fake_pmu, leader);
+ if (n < 0)
return -ENOSPC;

- list_for_each_entry(sibling, &leader->sibling_list, group_entry) {
- if (!validate_event(&fake_pmu, sibling))
- return -ENOSPC;
- }
+ fake_pmu.n_events = n;

- if (!validate_event(&fake_pmu, event))
+ n = collect_events(&fake_pmu, event);
+ if (n < 0)
return -ENOSPC;

- return 0;
+ ret = schedule_events(&fake_pmu, n, false);
+ return ret ? -ENOSPC : 0;
}

const struct pmu *hw_perf_event_init(struct perf_event *event)
--
1.5.4.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majo...@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

Stephane Eranian

unread,
Oct 22, 2009, 11:00:04 AM10/22/09
to
The validate_event() was failing on valid event
combinations. The function was assuming that if
x86_schedule_event() returned 0, it meant error.
But x86_schedule_event() returns the counter index
and 0 is a perfectly valid value. An error is returned
if the function returns a negative value.

Furthermore, validate_event() was also failing for
event groups because the event->pmu was not set until
after hw_perf_pmu_init().

Signed-off-by: Stephane Eranian <era...@gmail.com>
--
arch/x86/kernel/cpu/perf_event.c | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/arch/x86/kernel/cpu/perf_event.c b/arch/x86/kernel/cpu/perf_event.c
index 2e20bca..d321ff7 100644
--- a/arch/x86/kernel/cpu/perf_event.c
+++ b/arch/x86/kernel/cpu/perf_event.c
@@ -2229,10 +2229,7 @@ validate_event(struct cpu_hw_events *cpuc, struct perf_event *event)
{


struct hw_perf_event fake_event = event->hw;

- if (event->pmu != &pmu)
- return 0;
-
- return x86_schedule_event(cpuc, &fake_event);

+ return x86_schedule_event(cpuc, &fake_event) >= 0;


}

static int validate_group(struct perf_event *event)

Stephane Eranian

unread,
Oct 24, 2009, 1:30:27 PM10/24/09
to
This patch fixes the default watermark value for
the sampling buffer. With the existing calculation
(watermark = max(PAGE_SIZE, max_size / 2)), no
notification was ever received when the buffer was
exactly 1 page. This was because you would never
cross the threshold (there is no partial samples).

In certain configuration, there was no possibilty
detecting the problem because there was not enough
space left to store the LOST record.In fact, there
may be a more generic problem here. The kernel should
ensure that there is alaways enough space to store
one LOST record.

This patch sets the default watermark to half the
buffer size. With such limit, we are guaranteed to
get a notification even with a single page buffer
assuming no sample is bigger than a page.

Signed-off-by: Stephane Eranian <era...@gmail.com>

---
kernel/perf_event.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/perf_event.c b/kernel/perf_event.c
index a69d4ed..e8ec4b7 100644
--- a/kernel/perf_event.c
+++ b/kernel/perf_event.c
@@ -2315,7 +2315,7 @@ perf_mmap_data_init(struct perf_event *event, struct perf_mmap_data *data)
}

if (!data->watermark)
- data->watermark = max_t(long, PAGE_SIZE, max_size / 2);
+ data->watermark = max_size / 2;


rcu_assign_pointer(event->data, data);

Peter Zijlstra

unread,
Nov 18, 2009, 9:40:04 AM11/18/09
to
On Fri, 2009-10-23 at 14:56 +0200, Stephane Eranian wrote:
> This patch fixes the default watermark value for
> the sampling buffer. With the existing calculation
> (watermark = max(PAGE_SIZE, max_size / 2)), no
> notification was ever received when the buffer was
> exactly 1 page. This was because you would never
> cross the threshold (there is no partial samples).

Right, silly thinko, thanks for catching this.

> In certain configuration, there was no possibilty
> detecting the problem because there was not enough
> space left to store the LOST record.In fact, there
> may be a more generic problem here. The kernel should
> ensure that there is alaways enough space to store
> one LOST record.

It tries to prepend LOST records for each new event (when there is data
lost), so as soon as it manages to write a new event, it will include a
LOST record when appropriate.

> This patch sets the default watermark to half the
> buffer size. With such limit, we are guaranteed to
> get a notification even with a single page buffer
> assuming no sample is bigger than a page.
>
> Signed-off-by: Stephane Eranian <era...@gmail.com>

Acked-by: Peter Zijlstra <a.p.zi...@chello.nl>

Peter Zijlstra

unread,
Nov 18, 2009, 11:40:01 AM11/18/09
to
Sorry on the delay.

In general I'd very much like to see some of this generalized because I
think Sparc64 has very similar 'simple' constraints, I'll have to defer
to Paul on how much of this could possibly be re-used for powerpc.

On Mon, 2009-10-19 at 17:03 +0200, Stephane Eranian wrote:
> arch/x86/include/asm/perf_event.h | 6 +-
> arch/x86/kernel/cpu/perf_event.c | 497 +++++++++++++++++++++++--------------
> 2 files changed, 318 insertions(+), 185 deletions(-)
>
> diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h
> index 8d9f854..7c737af 100644
> --- a/arch/x86/include/asm/perf_event.h
> +++ b/arch/x86/include/asm/perf_event.h
> @@ -26,7 +26,9 @@
> /*
> * Includes eventsel and unit mask as well:
> */
> -#define ARCH_PERFMON_EVENT_MASK 0xffff
> +#define ARCH_PERFMON_EVENTSEL_EVENT_MASK 0x00ff
> +#define ARCH_PERFMON_EVENTSEL_UNIT_MASK 0xff00
> +#define ARCH_PERFMON_EVENT_MASK (ARCH_PERFMON_EVENTSEL_UNIT_MASK|ARCH_PERFMON_EVENTSEL_EVENT_MASK)

There's various forms of the above throughout the code, it would be nice
not to add more..

And in general this patch has way too many >80 lines and other style
nits, but those can be fixed up easily enough I guess.

> diff --git a/arch/x86/kernel/cpu/perf_event.c b/arch/x86/kernel/cpu/perf_event.c
> index 2e20bca..0f96c51 100644
> --- a/arch/x86/kernel/cpu/perf_event.c
> +++ b/arch/x86/kernel/cpu/perf_event.c

> @@ -68,6 +69,15 @@ struct debug_store {


> u64 pebs_event_reset[MAX_PEBS_EVENTS];
> };
>
> +#define BITS_TO_U64(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(u64))

Do we need this, is it realistic to expect X86_PMC_IDX_MAX to be
anything else than 64?

> -#define EVENT_CONSTRAINT(c, m) { .code = (c), .idxmsk[0] = (m) }
> -#define EVENT_CONSTRAINT_END { .code = 0, .idxmsk[0] = 0 }
> +#define EVENT_CONSTRAINT(c, n, w, m) { \
> + .code = (c), \
> + .mask = (m), \
> + .weight = (w), \
> + .idxmsk[0] = (n) }

If not, we can do away with the weight argument here and use hweight64()
which should reduce to a compile time constant.

> @@ -124,7 +137,7 @@ static DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events) = {
> .enabled = 1,
> };
>
> -static const struct event_constraint *event_constraints;
> +static struct event_constraint *event_constraints;

I'm thinking this ought to live in x86_pmu, or possible, if we can
generalize this enough, in pmu.

> +static struct event_constraint intel_core_event_constraints[] =
> +{

Inconsistent style with below:

> +static struct event_constraint intel_nehalem_event_constraints[] = {
> + EVENT_CONSTRAINT(0xc0, (0x3|(1ULL<<32)), 3, ARCH_PERFMON_FIXED_EVENT_MASK), /* INSTRUCTIONS_RETIRED */
> + EVENT_CONSTRAINT(0x3c, (0x3|(1ULL<<33)), 3, ARCH_PERFMON_FIXED_EVENT_MASK), /* UNHALTED_CORE_CYCLES */
> + EVENT_CONSTRAINT(0x40, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_LD */
> + EVENT_CONSTRAINT(0x41, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_ST */
> + EVENT_CONSTRAINT(0x42, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_LOCK */
> + EVENT_CONSTRAINT(0x43, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_ALL_REF */
> + EVENT_CONSTRAINT(0x4e, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_PREFETCH */
> + EVENT_CONSTRAINT(0x4c, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* LOAD_HIT_PRE */
> + EVENT_CONSTRAINT(0x51, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D */
> + EVENT_CONSTRAINT(0x52, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_PREFETCH_LOCK_FB_HIT */
> + EVENT_CONSTRAINT(0x53, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* L1D_CACHE_LOCK_FB_HIT */
> + EVENT_CONSTRAINT(0xc5, 0x3, 2, ARCH_PERFMON_EVENTSEL_EVENT_MASK), /* CACHE_LOCK_CYCLES */
> EVENT_CONSTRAINT_END
> };

Would be nice to get rid of that EVENT_MASK part, maybe write
EVENT_CONSTRAINT like:

.mask = ARCH_PERFMON_EVENTSEL_EVENT_MASK | ((n)>>32 ? ARCH_PERFMON_FIXED_MASK : 0),

Which ought to work for Intel based things, AMD will need a different
base event mask.



> @@ -1120,9 +1136,15 @@ static void amd_pmu_disable_all(void)
>
> void hw_perf_disable(void)
> {
> + struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
> +
> if (!x86_pmu_initialized())
> return;
> - return x86_pmu.disable_all();
> +
> + if (cpuc->enabled)
> + cpuc->n_events = 0;
> +
> + x86_pmu.disable_all();
> }

Right, so I cannot directly see the above being correct. You fully erase
the PMU state when we disable it, but things like single counter
add/remove doesn't reprogram the full PMU afaict.

The powerpc code has n_added, which indicates a delta algorithm is used
there.

> +static struct event_constraint *intel_get_event_constraints(struct perf_event *event)
> +{
> + struct event_constraint *c;
> +
> + c = intel_special_constraints(event);
> + if (c)
> + return c;
> +
> + if (event_constraints)
> + for_each_event_constraint(c, event_constraints) {
> + if ((event->hw.config & c->mask) == c->code)
> + return c;
> + }

This wants extra { }, since its a multi-line stmt.

> + return NULL;
> +}
> +
> +static struct event_constraint *amd_get_event_constraints(struct perf_event *event)
> +{
> + return NULL;
> +}

I guess we'll need to fill that out a bit more, but that can be another
patch.

Straight forward O(n^2) algorithm looking for the best match, seems good
from a single read -- will go over it again on another day to find more
details.

> +static int collect_events(struct cpu_hw_events *cpuhw, struct perf_event *leader)
> +{
> + struct perf_event *event;
> + int n, max_count;
> +
> + max_count = x86_pmu.num_events + x86_pmu.num_events_fixed;
> +
> + /* current number of events already accepted */
> + n = cpuhw->n_events;
> +
> + if (!is_software_event(leader)) {

With things like the hw-breakpoint stuff also growing a pmu !
is_software() isn't strong enough, something like:

static inline int is_x86_event(struct perf_event *event)
{
return event->pmu == &pmu;
}

Should work though.

This is where powerpc does n_added += n, and it delays the
schedule_events() bit to hw_perf_enable() conditional on n_added > 0.
When you remove events it simply keeps the current layout and disables
the one.

> @@ -2123,8 +2245,8 @@ static int intel_pmu_init(void)
> memcpy(hw_cache_event_ids, core2_hw_cache_event_ids,
> sizeof(hw_cache_event_ids));
>
> - pr_cont("Core2 events, ");
> event_constraints = intel_core_event_constraints;
> + pr_cont("Core2 events, ");
> break;
> default:
> case 26:

Not that I object to the above change, but it seems out of place in this
patch.

This seems good.

Peter Zijlstra

unread,
Nov 18, 2009, 11:50:03 AM11/18/09
to
On Thu, 2009-10-22 at 16:51 +0200, Stephane Eranian wrote:
> The validate_event() was failing on valid event
> combinations. The function was assuming that if
> x86_schedule_event() returned 0, it meant error.
> But x86_schedule_event() returns the counter index
> and 0 is a perfectly valid value. An error is returned
> if the function returns a negative value.

Good point.

> Furthermore, validate_event() was also failing for
> event groups because the event->pmu was not set until
> after hw_perf_pmu_init().

(hw_perf_event_init, right?)

Won't this give very funny results for mixed pmu groups?

How about something like:

if (event->pmu && event->pmu != &pmu)
return 0;

That should deal with new events, who do not yet have their pmu set and
for those we know they're for us, but exclude events for other PMUs.

tip-bot for Stephane Eranian

unread,
Nov 21, 2009, 8:50:04 AM11/21/09
to
Commit-ID: 8904b18046c2f050107f6449e887e7c1142b9ab9
Gitweb: http://git.kernel.org/tip/8904b18046c2f050107f6449e887e7c1142b9ab9
Author: Stephane Eranian <era...@gmail.com>
AuthorDate: Fri, 20 Nov 2009 22:19:57 +0100
Committer: Ingo Molnar <mi...@elte.hu>
CommitDate: Sat, 21 Nov 2009 14:11:41 +0100

perf_events: Fix default watermark calculation

This patch fixes the default watermark value for the sampling
buffer. With the existing calculation (watermark =
max(PAGE_SIZE, max_size / 2)), no notification was ever received
when the buffer was exactly 1 page. This was because you would
never cross the threshold (there is no partial samples).

In certain configuration, there was no possibilty detecting the
problem because there was not enough space left to store the
LOST record.In fact, there may be a more generic problem here.
The kernel should ensure that there is alaways enough space to
store one LOST record.

This patch sets the default watermark to half the buffer size.
With such limit, we are guaranteed to get a notification even
with a single page buffer assuming no sample is bigger than a
page.

Signed-off-by: Stephane Eranian <era...@gmail.com>
Signed-off-by: Peter Zijlstra <a.p.zi...@chello.nl>
Cc: Paul Mackerras <pau...@samba.org>
LKML-Reference: <200911202125...@chello.nl>
Signed-off-by: Ingo Molnar <mi...@elte.hu>
LKML-Reference: <1256302576-6169-1-gi...@gmail.com>
---
kernel/perf_event.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/kernel/perf_event.c b/kernel/perf_event.c
index 3ede098..718fa93 100644
--- a/kernel/perf_event.c
+++ b/kernel/perf_event.c
@@ -2340,7 +2340,7 @@ perf_mmap_data_init(struct perf_event *event, struct perf_mmap_data *data)

stephane eranian

unread,
Nov 23, 2009, 8:40:02 AM11/23/09
to
Hi,

Sorry for the delay,

On Wed, Nov 18, 2009 at 5:46 PM, Peter Zijlstra <pet...@infradead.org> wrote:
> On Thu, 2009-10-22 at 16:51 +0200, Stephane Eranian wrote:
>>       The validate_event() was failing on valid event
>>       combinations. The function was assuming that if
>>       x86_schedule_event() returned 0, it meant error.
>>       But x86_schedule_event() returns the counter index
>>       and 0 is a perfectly valid value. An error is returned
>>       if the function returns a negative value.
>
> Good point.
>
>>       Furthermore, validate_event() was also failing for
>>       event groups because the event->pmu was not set until
>>       after hw_perf_pmu_init().
>
> (hw_perf_event_init, right?)
>

Yes.

> Won't this give very funny results for mixed pmu groups?
>

What do you mean by 'mixed pmu groups'?

Peter Zijlstra

unread,
Nov 23, 2009, 8:50:02 AM11/23/09
to
On Mon, 2009-11-23 at 14:34 +0100, stephane eranian wrote:

> > Won't this give very funny results for mixed pmu groups?
> >
>
> What do you mean by 'mixed pmu groups'?

We currently have a number of struct pmu objects:

perf_ops_generic
perf_ops_cpu_clock
perf_ops_task_clock

which are all software based PMUs, and one of:

pmu (arch/x86/kernel/cpu/perf_event.c)
power_pmu (arch/powerpc/kernel/perf_event.c)

To represent the hardware PMU.

Now say you mix software events and hardware events into a single group,
the loop in validate_group:

list_for_each_entry(sibling, &leader->sibling_list, group_entry) {
if (!validate_event(&fake_pmu, sibling))
return -ENOSPC;
}

could pass a !hardware event into validate_event(), which currently
ignores it because event->pmu won't be &pmu, however if you remove that
check, it'll try and call x86 routines on a software event, which is
bound to go funny.

Now Frederic is going to make things more interesting by representing HW
breakpoints as another HW PMU (the distinction between hw/sw pmu is in
scheduling, you can always schedule a software event).

This weakens the !is_software_event(), in that !software doesn't tell
you which hardware event it is -- something which needs mending in your
more complex x86 constraints scheduling patch.

Mark Brown

unread,
Nov 24, 2009, 5:50:03 AM11/24/09
to
This works around issues with allmodconfig where it won't propagate the
dependency from the WM831x core back to the I2C and MFD cores. When
doing allmodconfig this causes WM831x to be omitted and ensures that in
normal builds the dependencies get shaken out.

Signed-off-by: Mark Brown <bro...@opensource.wolfsonmicro.com>
---
drivers/mfd/Kconfig | 2 +-


1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 434bc2f..c33a526 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -183,7 +183,7 @@ config MFD_WM8400
config MFD_WM831X
bool "Support Wolfson Microelectronics WM831x/2x PMICs"
select MFD_CORE
- depends on I2C
+ depends on I2C=y
help
Support for the Wolfson Microelecronics WM831x and WM832x PMICs.
This driver provides common support for accessing the device,
--
1.6.3.3

Mark Brown

unread,
Nov 24, 2009, 6:20:02 AM11/24/09
to
Systems using the WM835x need to choose which of the default register
settings are required on the system. Currently there is a compile time
warning as well as a runtime error intended to flag up to users that
this is required but this also triggers for people building the driver
in order to obtain build coverage.

Remove the build warning, leaving only the runtime error, in order to
reduce noise for people doing generic kernel work.

Signed-off-by: Mark Brown <bro...@opensource.wolfsonmicro.com>
---

drivers/mfd/wm8350-regmap.c | 8 --------
1 files changed, 0 insertions(+), 8 deletions(-)

diff --git a/drivers/mfd/wm8350-regmap.c b/drivers/mfd/wm8350-regmap.c
index 7ccc1ea..e965139 100644
--- a/drivers/mfd/wm8350-regmap.c
+++ b/drivers/mfd/wm8350-regmap.c
@@ -3170,14 +3170,6 @@ const u16 wm8352_mode3_defaults[] = {
};
#endif

-/* The register defaults for the config mode used must be compiled in but
- * due to the impact on kernel size it is possible to disable
- */
-#ifndef WM8350_HAVE_CONFIG_MODE
-#warning No WM8350 config modes supported - select at least one of the
-#warning MFD_WM8350_CONFIG_MODE_n options from the board driver.
-#endif
-
/*
* Access masks.
*/

stephane eranian

unread,
Nov 24, 2009, 8:20:02 AM11/24/09
to
On Mon, Nov 23, 2009 at 2:45 PM, Peter Zijlstra <pet...@infradead.org> wrote:
> On Mon, 2009-11-23 at 14:34 +0100, stephane eranian wrote:
>
>> > Won't this give very funny results for mixed pmu groups?
>> >
>>
>> What do you mean by 'mixed pmu groups'?
>
> We currently have a number of struct pmu objects:
>
>  perf_ops_generic
>  perf_ops_cpu_clock
>  perf_ops_task_clock
>
> which are all software based PMUs, and one of:
>
>  pmu        (arch/x86/kernel/cpu/perf_event.c)
>  power_pmu  (arch/powerpc/kernel/perf_event.c)
>
> To represent the hardware PMU.
>
> Now say you mix software events and hardware events into a single group,
> the loop in validate_group:
>
>  list_for_each_entry(sibling, &leader->sibling_list, group_entry) {
>        if (!validate_event(&fake_pmu, sibling))
>                        return -ENOSPC;
>  }
>
> could pass a !hardware event into validate_event(), which currently
> ignores it because event->pmu won't be &pmu, however if you remove that
> check, it'll try and call x86 routines on a software event, which is
> bound to go funny.
>
Ok, so it seems the only valid test to check if the event is related to the
HW PMU is to compare event->pmu with pmu (arch/x86/.../perf_event.c).

In that case you first suggestion is fine.

> Now Frederic is going to make things more interesting by representing HW
> breakpoints as another HW PMU (the distinction between hw/sw pmu is in
> scheduling, you can always schedule a software event).
>
> This weakens the !is_software_event(), in that !software doesn't tell
> you which hardware event it is -- something which needs mending in your
> more complex x86 constraints scheduling patch.
>

That means we can drop is_software_event() in this code and instead
define locally
in x86 a is_hw_pmu_event() function as event->pmu == &pmu.

Stephane Eranian

unread,
Nov 24, 2009, 8:30:02 AM11/24/09
to
The validate_event() was failing on valid event
combinations. The function was assuming that if
x86_schedule_event() returned 0, it meant error.
But x86_schedule_event() returns the counter index
and 0 is a perfectly valid value. An error is returned
if the function returns a negative value.

Furthermore, validate_event() was also failing for
event groups because the event->pmu was not set until

after hw_perf_event_init().

Signed-off-by: Stephane Eranian <era...@google.com>
--
arch/x86/kernel/cpu/perf_event.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kernel/cpu/perf_event.c b/arch/x86/kernel/cpu/perf_event.c
index bd87430..c1bbed1 100644
--- a/arch/x86/kernel/cpu/perf_event.c
+++ b/arch/x86/kernel/cpu/perf_event.c
@@ -2229,10 +2229,10 @@ validate_event(struct cpu_hw_events *cpuc, struct perf_event *event)


{
struct hw_perf_event fake_event = event->hw;

- if (event->pmu != &pmu)

+ if (event->pmu && event->pmu != &pmu)
return 0;

tip-bot for Stephane Eranian

unread,
Nov 24, 2009, 2:10:02 PM11/24/09
to
Commit-ID: 1261a02a0c0ab8e643125705f0d1d83e5090e4d1
Gitweb: http://git.kernel.org/tip/1261a02a0c0ab8e643125705f0d1d83e5090e4d1
Author: Stephane Eranian <era...@google.com>
AuthorDate: Tue, 24 Nov 2009 05:27:18 -0800
Committer: Ingo Molnar <mi...@elte.hu>
CommitDate: Tue, 24 Nov 2009 19:23:48 +0100

perf_events, x86: Fix validate_event bug

The validate_event() was failing on valid event combinations. The
function was assuming that if x86_schedule_event() returned 0, it
meant error. But x86_schedule_event() returns the counter index and
0 is a perfectly valid value. An error is returned if the function
returns a negative value.

Furthermore, validate_event() was also failing for event groups
because the event->pmu was not set until after
hw_perf_event_init().

Signed-off-by: Stephane Eranian <era...@google.com>
Cc: pet...@infradead.org
Cc: pau...@samba.org
Cc: perfmon...@lists.sourceforge.net
Cc: era...@gmail.com
LKML-Reference: <4b0bdf36.1818...@mx.google.com>
Signed-off-by: Ingo Molnar <mi...@elte.hu>


--
arch/x86/kernel/cpu/perf_event.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)

---
arch/x86/kernel/cpu/perf_event.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)

Samuel Ortiz

unread,
Nov 24, 2009, 6:20:02 PM11/24/09
to
Hi Mark,

On Tue, Nov 24, 2009 at 10:48:56AM +0000, Mark Brown wrote:
> This works around issues with allmodconfig where it won't propagate the
> dependency from the WM831x core back to the I2C and MFD cores. When
> doing allmodconfig this causes WM831x to be omitted and ensures that in
> normal builds the dependencies get shaken out.

Applied, thanks for looking at this.

Cheers,
Samuel.


> Signed-off-by: Mark Brown <bro...@opensource.wolfsonmicro.com>
> ---
> drivers/mfd/Kconfig | 2 +-
> 1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 434bc2f..c33a526 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -183,7 +183,7 @@ config MFD_WM8400
> config MFD_WM831X
> bool "Support Wolfson Microelectronics WM831x/2x PMICs"
> select MFD_CORE
> - depends on I2C
> + depends on I2C=y
> help
> Support for the Wolfson Microelecronics WM831x and WM832x PMICs.
> This driver provides common support for accessing the device,
> --
> 1.6.3.3
>

--
Intel Open Source Technology Centre
http://oss.intel.com/

Samuel Ortiz

unread,
Nov 24, 2009, 6:20:02 PM11/24/09
to
On Tue, Nov 24, 2009 at 11:16:45AM +0000, Mark Brown wrote:
> Systems using the WM835x need to choose which of the default register
> settings are required on the system. Currently there is a compile time
> warning as well as a runtime error intended to flag up to users that
> this is required but this also triggers for people building the driver
> in order to obtain build coverage.
Patch applied, thanks.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

Paul Mackerras

unread,
Nov 24, 2009, 7:50:02 PM11/24/09
to
stephane eranian writes:

> That means we can drop is_software_event() in this code and instead
> define locally
> in x86 a is_hw_pmu_event() function as event->pmu == &pmu.

I'd have to see the patch, but that doesn't feel entirely right,
because there is a unique characteristic of software events, compared
to hardware or breakpoint events: they are never capacity
constrained. In the past, only hardware events were capacity
constrained, which meant that all the decisions about whether a group
could go on could be done in the hardware PMU backend. Now we have
two sources of capacity constraints, so it may be that a group can't
go on even if the hardware PMU has capacity. That's going to be
somewhat interesting to get completely right, I think.

Paul.

stephane eranian

unread,
Nov 25, 2009, 12:50:02 AM11/25/09
to
On Tue, Nov 24, 2009 at 11:00 PM, Paul Mackerras <pau...@samba.org> wrote:
> stephane eranian writes:
>
>> That means we can drop is_software_event() in this code and instead
>> define locally
>> in x86 a is_hw_pmu_event() function as event->pmu == &pmu.
>
> I'd have to see the patch, but that doesn't feel entirely right,
> because there is a unique characteristic of software events, compared
> to hardware or breakpoint events: they are never capacity
> constrained.  In the past, only hardware events were capacity
> constrained, which meant that all the decisions about whether a group
> could go on could be done in the hardware PMU backend.  Now we have
> two sources of capacity constraints, so it may be that a group can't
> go on even if the hardware PMU has capacity.  That's going to be
> somewhat interesting to get completely right, I think.
>
I was talking of is_software_event() in the context of the hardware PMU
code. The reason you were using this function is simply to skip
SW events because, as you said, they are never constrained
and also because they are not related to HW PMU.

You are right about HW breakpoints, because now you have a new source of
constraints. Only the breakpoint code knows about those constraints.

It seems to me you have two ways of solving this:
1- push the algorithm to assign events to counters up in the generic code
2- have the generic code invoke all possible constraint sources on each group

I have already said that I would not recommend initially going with 1- because
constraints are very diverse in their nature. It is not as simple as 1 event = 1
bitmask of valid counters. Things can be more dynamic than that, e.g., on AMD64,
whereby for certain events the bitmask depends on what is going on in the other
cores on the socket. There are also similar constraints on advanced
Intel features.
So unlike what I heard early on, constraints are not going away,
instead they are
changing in nature and to something much more complex to deal with.

I believe that until all PMU event assignment logic is implemented in the PMU
specific code, it would be very presumptuous to try and design that
generic algorithm.
So I would go with 2, at least initially.

Peter Zijlstra

unread,
Nov 25, 2009, 3:00:02 AM11/25/09
to
On Wed, 2009-11-25 at 06:47 +0100, stephane eranian wrote:
> It seems to me you have two ways of solving this:
> 1- push the algorithm to assign events to counters up in the generic code
> 2- have the generic code invoke all possible constraint sources on each group

2, is what it currently does.

Since we can only add a single event to a group at a time, and we know
the previous group was schedulable, we only need to validate the
constraints on the pmu of the new event.

stephane eranian

unread,
Dec 11, 2009, 6:10:02 AM12/11/09
to
On Wed, Nov 18, 2009 at 5:32 PM, Peter Zijlstra <pet...@infradead.org> wrote:

> In general I'd very much like to see some of this generalized because I
> think Sparc64 has very similar 'simple' constraints, I'll have to defer
> to Paul on how much of this could possibly be re-used for powerpc.
>

Let's wait until we have all X86 constraint scheduling code. There is
way more needed. I have a first cut on the AMD64 constraints but
there is an issue in the generic/arch specific API that needs to be
flushed out first (see below).

> On Mon, 2009-10-19 at 17:03 +0200, Stephane Eranian wrote:
>> diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h
>> index 8d9f854..7c737af 100644
>> --- a/arch/x86/include/asm/perf_event.h
>> +++ b/arch/x86/include/asm/perf_event.h
>> @@ -26,7 +26,9 @@
>>  /*
>>   * Includes eventsel and unit mask as well:
>>   */
>> -#define ARCH_PERFMON_EVENT_MASK                                  0xffff
>> +#define ARCH_PERFMON_EVENTSEL_EVENT_MASK                 0x00ff
>> +#define ARCH_PERFMON_EVENTSEL_UNIT_MASK                          0xff00
>> +#define ARCH_PERFMON_EVENT_MASK                                  (ARCH_PERFMON_EVENTSEL_UNIT_MASK|ARCH_PERFMON_EVENTSEL_EVENT_MASK)
>
> There's various forms of the above throughout the code, it would be nice
> not to add more..
>

I will simplify this.


>
>> diff --git a/arch/x86/kernel/cpu/perf_event.c b/arch/x86/kernel/cpu/perf_event.c
>> index 2e20bca..0f96c51 100644
>> --- a/arch/x86/kernel/cpu/perf_event.c
>> +++ b/arch/x86/kernel/cpu/perf_event.c
>
>> @@ -68,6 +69,15 @@ struct debug_store {
>>       u64     pebs_event_reset[MAX_PEBS_EVENTS];
>>  };
>>
>> +#define BITS_TO_U64(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(u64))
>
> Do we need this, is it realistic to expect X86_PMC_IDX_MAX to be
> anything else than 64?
>

The issue had to do with i386 mode where long are 32 bits < 64. And in
particular with the initializer .idxmsk[0] = V. In the future we may exceed
32 registers. That means the initializer would have to change. But I guess
we have quite some ways before this case is reached. So I will revert all
of this to unsigned long.

>> -#define EVENT_CONSTRAINT(c, m) { .code = (c), .idxmsk[0] = (m) }
>> -#define EVENT_CONSTRAINT_END  { .code = 0, .idxmsk[0] = 0 }
>> +#define EVENT_CONSTRAINT(c, n, w, m) { \
>> +     .code = (c),    \
>> +     .mask = (m),    \
>> +     .weight = (w),  \
>> +     .idxmsk[0] = (n) }
>
> If not, we can do away with the weight argument here and use hweight64()
> which should reduce to a compile time constant.
>

I have dropped weight and I use bitmap_weight() to recompute, with all
the inlining
it should come out to be a simple popcnt instruction.


>> @@ -124,7 +137,7 @@ static DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events) = {
>>         .enabled = 1,
>>  };
>>
>> -static const struct event_constraint *event_constraints;
>> +static struct event_constraint *event_constraints;
>
> I'm thinking this ought to live in x86_pmu, or possible, if we can
> generalize this enough, in pmu.

True.

> Inconsistent style with below:
>
>> +static struct event_constraint intel_nehalem_event_constraints[] = {
>> +     EVENT_CONSTRAINT(0xc0, (0x3|(1ULL<<32)), 3, ARCH_PERFMON_FIXED_EVENT_MASK), /* INSTRUCTIONS_RETIRED */
>

> Would be nice to get rid of that EVENT_MASK part, maybe write
> EVENT_CONSTRAINT like:
>
>  .mask = ARCH_PERFMON_EVENTSEL_EVENT_MASK | ((n)>>32 ? ARCH_PERFMON_FIXED_MASK : 0),
>
> Which ought to work for Intel based things, AMD will need a different
> base event mask.
>

Will try to clean this up. AMD does not need a mask, it is a
completely different kind of constraints.

>> @@ -1120,9 +1136,15 @@ static void amd_pmu_disable_all(void)
>>
>>  void hw_perf_disable(void)
>>  {
>> +     struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
>> +
>>       if (!x86_pmu_initialized())
>>               return;
>> -     return x86_pmu.disable_all();
>> +
>> +     if (cpuc->enabled)
>> +             cpuc->n_events = 0;
>> +
>> +     x86_pmu.disable_all();
>>  }
>
> Right, so I cannot directly see the above being correct. You fully erase
> the PMU state when we disable it, but things like single counter
> add/remove doesn't reprogram the full PMU afaict.
>

Here is the key point. There is no clear API that says 'this is the final stop
for this event group, i.e., next start will be with a different
group'. In other words,
there is no signal that the 'resource' can be freed. This is a
showstopper for the
AMD constraints. The generic code needs to signal that this is the
final stop for
the event group, so the machine specific layer can release the registers. The
hw_perf_disable() is used in many places and does not indicate the same thing
as what I just described.

>> +static int collect_events(struct cpu_hw_events *cpuhw, struct perf_event *leader)
>> +{
>> +     struct perf_event *event;
>> +     int n, max_count;
>> +
>> +     max_count = x86_pmu.num_events + x86_pmu.num_events_fixed;
>> +
>> +     /* current number of events already accepted */
>> +     n = cpuhw->n_events;
>> +
>> +     if (!is_software_event(leader)) {
>
> With things like the hw-breakpoint stuff also growing a pmu !
> is_software() isn't strong enough, something like:
>
> static inline int is_x86_event(struct perf_event *event)
> {
>        return event->pmu == &pmu;
> }
>

Agreed, I am using something like this now.

>> +int hw_perf_group_sched_in(struct perf_event *leader,
>> +            struct perf_cpu_context *cpuctx,
>> +            struct perf_event_context *ctx, int cpu)
>> +{
>> +     struct cpu_hw_events *cpuhw = &per_cpu(cpu_hw_events, cpu);
>> +     int n, ret;
>> +
>> +     n = collect_events(cpuhw, leader);
>> +     if (n < 0)
>> +             return n;
>> +
>> +     ret = schedule_events(cpuhw, n, true);
>> +     if (ret)
>> +             return ret;
>> +
>> +     /* 0 means successful and enable each event in caller */
>> +     return 0;
>> +}
>
> This is where powerpc does n_added += n, and it delays the
> schedule_events() bit to hw_perf_enable() conditional on n_added > 0.
> When you remove events it simply keeps the current layout and disables
> the one.
>

Will look into this.

>> @@ -2123,8 +2245,8 @@ static int intel_pmu_init(void)
>>               memcpy(hw_cache_event_ids, core2_hw_cache_event_ids,
>>                      sizeof(hw_cache_event_ids));
>>
>> -             pr_cont("Core2 events, ");
>>               event_constraints = intel_core_event_constraints;
>> +             pr_cont("Core2 events, ");
>>               break;
>>       default:
>>       case 26:
>
> Not that I object to the above change, but it seems out of place in this
> patch.
>

Will remove that.

stephane eranian

unread,
Dec 11, 2009, 7:00:02 AM12/11/09
to
>
>>> @@ -1120,9 +1136,15 @@ static void amd_pmu_disable_all(void)
>>>
>>>  void hw_perf_disable(void)
>>>  {
>>> +     struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
>>> +
>>>       if (!x86_pmu_initialized())
>>>               return;
>>> -     return x86_pmu.disable_all();
>>> +
>>> +     if (cpuc->enabled)
>>> +             cpuc->n_events = 0;
>>> +
>>> +     x86_pmu.disable_all();
>>>  }
>>
>> Right, so I cannot directly see the above being correct. You fully erase
>> the PMU state when we disable it, but things like single counter
>> add/remove doesn't reprogram the full PMU afaict.
>>

>>> +int hw_perf_group_sched_in(struct perf_event *leader,


>>> +            struct perf_cpu_context *cpuctx,
>>> +            struct perf_event_context *ctx, int cpu)
>>> +{
>>> +     struct cpu_hw_events *cpuhw = &per_cpu(cpu_hw_events, cpu);
>>> +     int n, ret;
>>> +
>>> +     n = collect_events(cpuhw, leader);
>>> +     if (n < 0)
>>> +             return n;
>>> +
>>> +     ret = schedule_events(cpuhw, n, true);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     /* 0 means successful and enable each event in caller */
>>> +     return 0;
>>> +}
>>
>> This is where powerpc does n_added += n, and it delays the
>> schedule_events() bit to hw_perf_enable() conditional on n_added > 0.
>> When you remove events it simply keeps the current layout and disables
>> the one.
>>

There is a major difference between PPC and X86 here. PPC has a centralized
register to control start/stop. This register uses bitmask to enable
or disable counters. Thus, in hw_perf_enable(), if n_added=0, then you
just need to
use the pre-computed bitmask. Otherwise, you need to recompute the bitmask to
include the new registers. The assignment of events and validation is done in
hw_group_sched_in().

In X86, assignment and validation is done in hw_group_sched_in(). Activation is
done individually for each counter. There is no centralized register
used here, thus
no bitmask to update.

Disabling a counter does not trigger a complete reschedule of events.
This happens
only when hw_group_sched_in() is called.

The n_events = 0 in hw_perf_disable() is used to signal that something
is changing.
It should not be here but here. The problem is that
hw_group_sched_in() needs a way
to know that it is called for a completely new series of group
scheduling so it can
discard any previous assignment. This goes back to the issue I raised
in my previous
email. You could add a parameter to hw_group_sched_in() that would
indicate this is
the first group. that would cause n_events =0 and the function would
start accumulating
events for the new scheduling period.

Stephane Eranian

unread,
Dec 11, 2009, 10:00:02 AM12/11/09
to
On Fri, Dec 11, 2009 at 12:00 PM, stephane eranian
<era...@googlemail.com> wrote:
>>> --- a/arch/x86/kernel/cpu/perf_event.c
>>> +++ b/arch/x86/kernel/cpu/perf_event.c
>>
>>> @@ -68,6 +69,15 @@ struct debug_store {
>>>       u64     pebs_event_reset[MAX_PEBS_EVENTS];
>>>  };
>>>
>>> +#define BITS_TO_U64(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(u64))
>>
>> Do we need this, is it realistic to expect X86_PMC_IDX_MAX to be
>> anything else than 64?
>>
> The issue had to do with i386 mode where long are 32 bits  < 64. And in
> particular with the initializer .idxmsk[0] = V. In the future we may exceed
> 32 registers. That means the initializer would have to change. But I guess
> we have quite some ways before this case is reached. So I will revert all
> of this to unsigned long.
>
Well, in fact we have to use u64 because you are already using register
indexes > 32, e.g., for the Intel fixed counters which start at position 32.
Using unsigned long would make the static initializer uglier in this case.

Peter Zijlstra

unread,
Dec 21, 2009, 10:50:02 AM12/21/09
to
On Fri, 2009-12-11 at 12:59 +0100, stephane eranian wrote:

> There is a major difference between PPC and X86 here. PPC has a
> centralized register to control start/stop. This register uses
> bitmask to enable or disable counters. Thus, in hw_perf_enable(), if
> n_added=0, then you just need to use the pre-computed bitmask.
> Otherwise, you need to recompute the bitmask to include the new
> registers. The assignment of events and validation is done in
> hw_group_sched_in().
>
> In X86, assignment and validation is done in hw_group_sched_in().
> Activation is done individually for each counter. There is no
> centralized register used here, thus no bitmask to update.

intel core2 has the global control reg, but for all intents and purposes
the perf_enable/disable calls emulate this global enable/disable.

> Disabling a counter does not trigger a complete reschedule of events.
> This happens only when hw_group_sched_in() is called.
>
> The n_events = 0 in hw_perf_disable() is used to signal that something
> is changing. It should not be here but here. The problem is that
> hw_group_sched_in() needs a way to know that it is called for a
> completely new series of group scheduling so it can discard any
> previous assignment. This goes back to the issue I raised in my
> previous email. You could add a parameter to hw_group_sched_in() that
> would indicate this is the first group. that would cause n_events =0
> and the function would start accumulating events for the new
> scheduling period.

I'm not really seeing the problem here...


perf_disable() <-- shut down the full pmu

pmu->disable() <-- hey someone got removed (easy free the reg)
pmu->enable() <-- hey someone got added (harder, check constraints)

hw_perf_group_sched_in() <-- hey a full group got added
(better than multiple ->enable)

perf_enable() <-- re-enable pmu


So ->disable() is used to track freeing, ->enable is used to add
individual counters, check constraints etc..

hw_perf_group_sched_in() is used to optimize the full group enable.

Afaict that is what power does (Paul?) and that should I think be
sufficient to track x86 as well.

Since sched_in() is balanced with sched_out(), the ->disable() calls
should provide the required information as to the occupation of the pmu.
I don't see the need for more hooks.

Paul, could you comment, since you did all this for power?

Stephane Eranian

unread,
Dec 21, 2009, 2:10:01 PM12/21/09
to
Hi,

[Repost because of HTML]

Does that mean that after a disable() I can assume that there won't
be an enable() without a group_sched_in()?

I suspect not. In fact, there is a counter-example in perf_ctx_adjust_freq().

Peter Zijlstra

unread,
Dec 21, 2009, 2:40:01 PM12/21/09
to
On Mon, 2009-12-21 at 20:00 +0100, Stephane Eranian wrote:

> > perf_disable() <-- shut down the full pmu
> >
> > pmu->disable() <-- hey someone got removed (easy free the reg)
> > pmu->enable() <-- hey someone got added (harder, check constraints)
> >
> > hw_perf_group_sched_in() <-- hey a full group got added
> > (better than multiple ->enable)
> >
> > perf_enable() <-- re-enable pmu
> >
> >
> > So ->disable() is used to track freeing, ->enable is used to add
> > individual counters, check constraints etc..
> >
> > hw_perf_group_sched_in() is used to optimize the full group enable.
> >
>
> Does that mean that after a disable() I can assume that there won't
> be an enable() without a group_sched_in()?
>
> I suspect not. In fact, there is a counter-example in perf_ctx_adjust_freq().

Why would that be a problem?

A perf_disable() will simply disable the pmu, but not alter its
configuration, perf_enable() will program the pmu and re-enable it (with
the obvious optimization that if the current state and the new state
match we can skip the actual programming).

If a ->disable() was observed between it will simply not re-enable that
particular counter, that is it will remove that counters' config, and
perf_enable() can do a smart reprogram by simply no re-enabling that
counter.

If an ->enable() or hw_perf_group_sched_in() was observed in between,
you have to recompute the full state in order to validate the
availability, if that fails, no state change, if it succeeds you have a
new pmu state and perf_enable() will program that.

In the case of perf_ctx_adjust_freq():

if (!interrupts) {
perf_disable();
event->pmu->disable(event);
atomic64_set(&hwc->period_left, 0);
event->pmu->enable(event);
perf_enable();
}

You'll very likely end up with the same state you had before, but if not
who cares -- its supposed to be an unlikely case.

That is, the ->disable() will clear the cpu_hw_events::used_mask bit.
The ->enable() will want to place the counter on its last know reg,
which (not so very surprisingly) will be available, hence it will
trivially succeed, depending on its smarts it might or might not find
that the 'new' counter's config is identical to the current hw state, if
not it might set a cpu_hw_event::reprogram state.

perf_enable() will, when it sees cpu_hw_event::reprogram, re-write the
pmu config and re-enable the whole thing (global ctrl reg, or iterate
individual EN bits).

Stephane Eranian

unread,
Dec 21, 2009, 4:00:02 PM12/21/09
to

Ok, so what you are suggesting is that the assignment is actually done
incrementally in ->enable(). hw_group_sched_in() would simply validate
that a single group is sane (i.e., can be scheduled if it was alone).

> In the case of perf_ctx_adjust_freq():
>
>        if (!interrupts) {
>                perf_disable();
>                event->pmu->disable(event);
>                atomic64_set(&hwc->period_left, 0);
>                event->pmu->enable(event);
>                perf_enable();
>        }
>
> You'll very likely end up with the same state you had before, but if not
> who cares -- its supposed to be an unlikely case.
>
> That is, the ->disable() will clear the cpu_hw_events::used_mask bit.
> The ->enable() will want to place the counter on its last know reg,
> which (not so very surprisingly) will be available, hence it will

Not in the case I am looking at on AMD. The counter you need to grab
depends on what is going on on the other cores on the socket. So
there is no guarantee that you will get the same. Something similar
exists on Nehalem but it has to do with HT.

Peter Zijlstra

unread,
Dec 21, 2009, 4:20:02 PM12/21/09
to

hw_perf_group_sched_in() can be used to do a whole group at once (see
how a !0 return value will short-circuit the whole incremental group
buildup), but yes ->enable() is incremental.

> > In the case of perf_ctx_adjust_freq():
> >
> > if (!interrupts) {
> > perf_disable();
> > event->pmu->disable(event);
> > atomic64_set(&hwc->period_left, 0);
> > event->pmu->enable(event);
> > perf_enable();
> > }
> >
> > You'll very likely end up with the same state you had before, but if not
> > who cares -- its supposed to be an unlikely case.
> >
> > That is, the ->disable() will clear the cpu_hw_events::used_mask bit.
> > The ->enable() will want to place the counter on its last know reg,
> > which (not so very surprisingly) will be available, hence it will
>
> Not in the case I am looking at on AMD. The counter you need to grab
> depends on what is going on on the other cores on the socket. So
> there is no guarantee that you will get the same. Something similar
> exists on Nehalem but it has to do with HT.

Well the above code is assumed correct, so we need to make it true,
which should not be too hard to do.

If there are cross cpu constraints (AMD has per node constraints IIRC)
then we need a structure that describes these (a per node structure for
AND, a per core structure for HT), if we take a lock on this structure
the first time we touch it in a perf_disable() region, and release it on
perf_enable(), then we ensure those constraints remain invariant.

With something like that in place the above code will still be valid,
since the act of removing the counter will freeze all needed state to
re-instate it.

Does that make sense?

Paul Mackerras

unread,
Dec 21, 2009, 8:20:01 PM12/21/09
to
On Mon, Dec 21, 2009 at 09:59:45PM +0100, Stephane Eranian wrote:

> Ok, so what you are suggesting is that the assignment is actually done
> incrementally in ->enable(). hw_group_sched_in() would simply validate
> that a single group is sane (i.e., can be scheduled if it was alone).

No, hw_group_sched_in needs to validate that this group can go on
along with everything else that has already been enabled. But as I
have said, if you have the complete list of enabled events to hand,
that's not hard.

On the other hand, hw_perf_event_init does need to validate that a
single group is sane by itself.

Paul.

Paul Mackerras

unread,
Dec 21, 2009, 8:20:02 PM12/21/09
to
On Fri, Dec 11, 2009 at 12:59:16PM +0100, stephane eranian wrote:

> There is a major difference between PPC and X86 here. PPC has a centralized
> register to control start/stop. This register uses bitmask to enable
> or disable counters. Thus, in hw_perf_enable(), if n_added=0, then you
> just need to
> use the pre-computed bitmask. Otherwise, you need to recompute the bitmask to
> include the new registers. The assignment of events and validation is done in
> hw_group_sched_in().

That's not entirely accurate. Yes there is a global start/stop bit,
but there isn't a bitmask to enable or disable counters. There is a
selector bitfield for each counter (except the limited-function
counters) and you can set the selector to the 'count nothing' value if
you don't want a particular counter to count.

Validation is done in hw_group_sched_in() but not the assignment of
events to counters. That's done in hw_perf_enable(), via the
model-specific ppmu->compute_mmcr() call.

> In X86, assignment and validation is done in hw_group_sched_in(). Activation is
> done individually for each counter. There is no centralized register
> used here, thus
> no bitmask to update.
>
> Disabling a counter does not trigger a complete reschedule of events.
> This happens
> only when hw_group_sched_in() is called.
>
> The n_events = 0 in hw_perf_disable() is used to signal that something
> is changing.
> It should not be here but here.

The meaning of "It should not be here but here" is quite unclear to me.

> The problem is that
> hw_group_sched_in() needs a way
> to know that it is called for a completely new series of group
> scheduling so it can
> discard any previous assignment. This goes back to the issue I raised
> in my previous
> email. You could add a parameter to hw_group_sched_in() that would
> indicate this is
> the first group. that would cause n_events =0 and the function would
> start accumulating
> events for the new scheduling period.

I don't think hw_group_sched_in is ever called for a completely new
series of group scheduling. If you have per-cpu counters active, they
don't get scheduled out and in again with each task switch. So you
will tend to get a hw_pmu_disable call, then a series of disable calls
for the per-task events for the old task, then a series of
hw_group_sched_in calls for the per-task events for the new task, then
a hw_pmu_enable call.

On powerpc we maintain an array with pointers to all the currently
active events. That makes it easy to know at hw_pmu_enable() time
what events need to be put on the PMU. Also it means that at
hw_group_sched_in time you can look at the whole set of events,
including the ones just added, to see if it's feasible to put them all
on. At that point we just check feasibility, which is quite quick and
easy using the bit-vector encoding of constraints. The bit-vector
encoding lets us represent multiple constraints of various forms in
one pair of 64-bit values per event. We can express constraints such
as "you can have at most N events in a class X" or "you can't have
events in all of classes A, B, C and D" or "control register bitfield
X must be set to Y", and then check that a set of events satisfies all
the constraints with some simple integer arithmetic. I don't know
exactly what constraints you have on x86 but I would be surprised if
you couldn't handle them the same way.

Paul.

Paul Mackerras

unread,
Dec 21, 2009, 8:20:01 PM12/21/09
to
On Mon, Dec 21, 2009 at 04:40:40PM +0100, Peter Zijlstra wrote:

> I'm not really seeing the problem here...
>
>
> perf_disable() <-- shut down the full pmu
>
> pmu->disable() <-- hey someone got removed (easy free the reg)
> pmu->enable() <-- hey someone got added (harder, check constraints)
>
> hw_perf_group_sched_in() <-- hey a full group got added
> (better than multiple ->enable)
>
> perf_enable() <-- re-enable pmu
>
>
> So ->disable() is used to track freeing, ->enable is used to add
> individual counters, check constraints etc..
>
> hw_perf_group_sched_in() is used to optimize the full group enable.
>
> Afaict that is what power does (Paul?) and that should I think be
> sufficient to track x86 as well.

That sounds right to me.

> Since sched_in() is balanced with sched_out(), the ->disable() calls
> should provide the required information as to the occupation of the pmu.
> I don't see the need for more hooks.
>
> Paul, could you comment, since you did all this for power?

On powerpc we maintain a list of currently enabled events in the arch
code. Does x86 do that as well?

If you have the list (or array) of events easily accessible, it's
relatively easy to check whether the whole set is feasible at any
point, without worrying about which events were recently added. The
perf_event structure has a spot where the arch code can store which
PMU register is used for that event, so you can easily optimize the
case where the event doesn't move.

Like you, I'm not seeing where the difficulty lies. Perhaps Stephane
could give us a detailed example if he still thinks there's a
difficulty.

Paul.

Stephane Eranian

unread,
Dec 29, 2009, 9:50:02 AM12/29/09
to
Paul,


So if I understand what both of you are saying, it seems that
event scheduling has to take place in the pmu->enable() callback
which is per-event.

In the case of X86, you can chose to do a best-effort scheduling,
i.e., only assign
the new event if there is a compatible free counter. That would be incremental.

But the better solution would be to re-examine the whole situation and
potentially
move existing enabled events around to free a counter if the new event is more
constrained. That would require stopping the PMU, rewriting config and
data registers
and re-enabling the PMU. This latter solution is the only possibility
to avoid ordering
side effects, i.e., the assignment of events to counters depends on
the order in which
events are created (or enabled).

The register can be considered freed by pmu->disable() if scheduling takes place
in pmu->enable().

From what Paul was saying about hw_perf_group_sched_in(), it seems like this
function can be used to check if a new group is compatible with the existing
enabled events. Compatible means that there is a possible assignment of
events to counters.

As for the n_added logic, it seems like perf_disable() resets n_added to zero.
N_added is incremented in pmu->enable(), i.e., add one event, or the
hw_perf_group_sched_in(), i.e., add a whole group. Scheduling is based on
n_events. The point of n_added is to verify whether something needs to be
done, i.e., event scheduling, if an event or group was added between
perf_disable()
and perf_enable(). In pmu->disable(), the list of enabled events is
compacted and
n_events is decremented.

Did I get this right?

All the enable and disable calls can be called from NMI interrupt context
and thus must be very careful with locks.

--
Stephane Eranian | EMEA Software Engineering
Google France | 38 avenue de l'Opéra | 75002 Paris
Tel : +33 (0) 1 42 68 53 00
This email may be confidential or privileged. If you received this
communication by mistake, please
don't forward it to anyone else, please erase all copies and
attachments, and please let me know that
it went to the wrong person. Thanks

Paul Mackerras

unread,
Jan 6, 2010, 11:20:02 PM1/6/10
to
Stephane,

> So if I understand what both of you are saying, it seems that
> event scheduling has to take place in the pmu->enable() callback
> which is per-event.

How much you have to do in the pmu->enable() function depends on
whether the PMU is already stopped (via a call hw_perf_disable()) or
not. If it is already stopped you don't have to do the full event
scheduling computation, you only have to do enough to work out if the
current set of events plus the event being enabled is feasible, and
record the event for later. In other words, you can defer the actual
scheduling until hw_perf_enable() time. Most calls to the
pmu->enable() function are with the PMU already stopped, so it's a
worthwhile optimization.

> In the case of X86, you can chose to do a best-effort scheduling,
> i.e., only assign
> the new event if there is a compatible free counter. That would be incremental.
>
> But the better solution would be to re-examine the whole situation and
> potentially
> move existing enabled events around to free a counter if the new event is more
> constrained. That would require stopping the PMU, rewriting config and
> data registers
> and re-enabling the PMU. This latter solution is the only possibility
> to avoid ordering
> side effects, i.e., the assignment of events to counters depends on
> the order in which
> events are created (or enabled).

On powerpc, the pmu->enable() function always stops the PMU if it
wasn't already stopped. That simplifies the code a lot because it
means that I can do all the actual event scheduling (i.e. deciding on
which event goes on which counter and working out PMU control register
values) in hw_perf_enable().

> The register can be considered freed by pmu->disable() if scheduling takes place
> in pmu->enable().
>
> >From what Paul was saying about hw_perf_group_sched_in(), it seems like this
> function can be used to check if a new group is compatible with the existing
> enabled events. Compatible means that there is a possible assignment of
> events to counters.

The hw_perf_group_sched_in() function is an optimization so I can add
all the counters in the group to the set under consideration and then
do just one feasibility check, rather than adding each counter in
the group in turn and doing the feasibility check for each one.

> As for the n_added logic, it seems like perf_disable() resets n_added to zero.
> N_added is incremented in pmu->enable(), i.e., add one event, or the
> hw_perf_group_sched_in(), i.e., add a whole group. Scheduling is based on
> n_events. The point of n_added is to verify whether something needs to be
> done, i.e., event scheduling, if an event or group was added between
> perf_disable()
> and perf_enable(). In pmu->disable(), the list of enabled events is
> compacted and
> n_events is decremented.
>
> Did I get this right?

The main point of n_added was so that hw_perf_enable() could know
whether the current set of events is a subset of the last set. If it
is a subset, the scheduling decisions are much simpler.

> All the enable and disable calls can be called from NMI interrupt context
> and thus must be very careful with locks.

I didn't think the pmu->enable() and pmu->disable() functions could be
called from NMI context. Also, I would expect that if hw_perf_enable
and hw_perf_disable are called from NMI context, that the calls would
be balanced. That simplifies things a lot.

Paul.

Peter Zijlstra

unread,
Jan 7, 2010, 4:00:02 AM1/7/10
to
On Thu, 2010-01-07 at 15:13 +1100, Paul Mackerras wrote:
>
> On powerpc, the pmu->enable() function always stops the PMU if it
> wasn't already stopped.

I just looked at the generic code and it looks like kernel/perf_event.c
always calls perf_disable() before calling ->enable().

Peter Zijlstra

unread,
Jan 7, 2010, 4:10:02 AM1/7/10
to
On Thu, 2010-01-07 at 15:13 +1100, Paul Mackerras wrote:
>
> > All the enable and disable calls can be called from NMI interrupt context
> > and thus must be very careful with locks.
>
> I didn't think the pmu->enable() and pmu->disable() functions could be
> called from NMI context.

I don't think they're called from NMI context either, most certainly not
from the generic code.

The x86 calls the raw disable from nmi to throttle the counter, but all
that (should) do is disable that counter, which is limited to a single
msr write. After that it schedules a full disable by sending a self-ipi.

Stephane Eranian

unread,
Jan 7, 2010, 5:00:02 AM1/7/10
to
Hi,

Ok, so I made some progress yesterday on all of this.

The key elements are:
- pmu->enable() is always called from generic with PMU disabled
- pmu->disable() is called with PMU possibly enabled
- hw_perf_group_sched_in() is always called with PMU disabled

I got the n_added logic working now on X86.

I noticed the difference in pmu->enabled() between Power and X86.
On PPC, you disable the whole PMU. On X86, that's not the case.

Now, I do the scheduling in hw_perf_enable(). Just like on PPC, I also
move events around if their register assignment has changed. It is not
quite working yet. I must have something wrong with the read and rewrite
code.

I will experiment with pmu->enable(). Given the key elements above, I think
Paul is right, all scheduling can be deferred until hw_perf_enable().

But there is a catch. I noticed that hw_perf_enable() is void. In
other words, it
means that if scheduling fails, you won't notice. This is not a problem on PPC
but will be on AMD64. That's because the scheduling depends on what goes on
on the other cores on the socket. In other words, things can change between
pmu->enable()/hw_perf_group_sched_in() and hw_perf_enable(). Unless we lock
something down in between.


On Thu, Jan 7, 2010 at 10:00 AM, Peter Zijlstra <pet...@infradead.org> wrote:
> On Thu, 2010-01-07 at 15:13 +1100, Paul Mackerras wrote:
>>
>> > All the enable and disable calls can be called from NMI interrupt context
>> > and thus must be very careful with locks.
>>
>> I didn't think the pmu->enable() and pmu->disable() functions could be
>> called from NMI context.
>
> I don't think they're called from NMI context either, most certainly not
> from the generic code.
>
> The x86 calls the raw disable from nmi to throttle the counter, but all
> that (should) do is disable that counter, which is limited to a single
> msr write. After that it schedules a full disable by sending a self-ipi.
>
>
>
>

--

Stephane Eranian | EMEA Software Engineering
Google France | 38 avenue de l'Opéra | 75002 Paris
Tel : +33 (0) 1 42 68 53 00
This email may be confidential or privileged. If you received this
communication by mistake, please
don't forward it to anyone else, please erase all copies and
attachments, and please let me know that
it went to the wrong person. Thanks

Peter Zijlstra

unread,
Jan 7, 2010, 5:10:02 AM1/7/10
to
On Thu, 2010-01-07 at 10:54 +0100, Stephane Eranian wrote:
>
> Ok, so I made some progress yesterday on all of this.
>
> The key elements are:
> - pmu->enable() is always called from generic with PMU disabled
> - pmu->disable() is called with PMU possibly enabled
> - hw_perf_group_sched_in() is always called with PMU disabled
>
> I got the n_added logic working now on X86.
>
> I noticed the difference in pmu->enabled() between Power and X86.
> On PPC, you disable the whole PMU. On X86, that's not the case.
>
> Now, I do the scheduling in hw_perf_enable(). Just like on PPC, I also
> move events around if their register assignment has changed. It is not
> quite working yet. I must have something wrong with the read and rewrite
> code.
>
> I will experiment with pmu->enable(). Given the key elements above, I think
> Paul is right, all scheduling can be deferred until hw_perf_enable().
>
> But there is a catch. I noticed that hw_perf_enable() is void. In
> other words, it
> means that if scheduling fails, you won't notice. This is not a problem on PPC
> but will be on AMD64. That's because the scheduling depends on what goes on
> on the other cores on the socket. In other words, things can change between
> pmu->enable()/hw_perf_group_sched_in() and hw_perf_enable(). Unless we lock
> something down in between.

You have to lock stuff, you can't fail hw_perf_enable() because at that
point we've lost all track of what failed.

Ira W. Snyder

unread,
Mar 18, 2010, 1:10:02 PM3/18/10
to
The Janz VMOD-TTL is a MODULbus daughterboard which fits onto any MODULbus
carrier board. It essentially consists of some various logic and a Zilog
Z8536 CIO Counter/Timer and Parallel IO Unit.

The board must be physically configured with jumpers to enable a user to
drive output signals. I am only interested in outputs, so I have made this
driver as simple as possible. It only supports a very minimal amount of the
features provided by the Z8536 chip.

Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>
---
drivers/gpio/Kconfig | 10 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/janz-ttl.c | 257 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 268 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/janz-ttl.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 1f1d88a..0da364d 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -255,4 +255,14 @@ config GPIO_UCB1400
To compile this driver as a module, choose M here: the
module will be called ucb1400_gpio.

+comment "MODULbus GPIO expanders:"
+
+config GPIO_JANZ_TTL
+ tristate "Janz VMOD-TTL Digital IO Module"
+ depends on MFD_JANZ_CMODIO
+ help
+ This enables support for the Janz VMOD-TTL Digital IO module.
+ This driver provides support for driving the pins in output
+ mode only. Input mode is not supported.
+
endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 4868723..2263966 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_GPIO_CS5535) += cs5535-gpio.o
obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o
obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o
+obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
diff --git a/drivers/gpio/janz-ttl.c b/drivers/gpio/janz-ttl.c
new file mode 100644
index 0000000..d97eeda
--- /dev/null
+++ b/drivers/gpio/janz-ttl.c
@@ -0,0 +1,257 @@
+/*
+ * Janz MODULbus VMOD-TTL GPIO Driver
+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+
+#include <linux/mfd/janz.h>
+
+#define DRV_NAME "janz-ttl"
+
+#define PORTA_DIRECTION 0x23
+#define PORTB_DIRECTION 0x2B
+#define PORTC_DIRECTION 0x06
+#define PORTA_IOCTL 0x24
+#define PORTB_IOCTL 0x2C
+#define PORTC_IOCTL 0x07
+
+#define MASTER_INT_CTL 0x00
+#define MASTER_CONF_CTL 0x01
+
+#define CONF_PAE (1 << 2)
+#define CONF_PBE (1 << 7)
+#define CONF_PCE (1 << 4)
+
+struct ttl_control_regs {
+ __be16 portc;
+ __be16 portb;
+ __be16 porta;
+ __be16 control;
+};
+
+struct ttl_module {
+ struct gpio_chip gpio;
+
+ /* base address of registers */
+ struct ttl_control_regs __iomem *regs;
+
+ u8 portc_shadow;
+ u8 portb_shadow;
+ u8 porta_shadow;
+
+ spinlock_t lock;
+};
+
+static int ttl_get_value(struct gpio_chip *gpio, unsigned offset)
+{
+ struct ttl_module *mod = dev_get_drvdata(gpio->dev);
+ u8 *shadow;
+ int ret;
+
+ if (offset < 8) {
+ shadow = &mod->porta_shadow;
+ } else if (offset < 16) {
+ shadow = &mod->portb_shadow;
+ offset -= 8;
+ } else {
+ shadow = &mod->portc_shadow;
+ offset -= 16;
+ }
+
+ spin_lock(&mod->lock);
+ ret = *shadow & (1 << offset);
+ spin_unlock(&mod->lock);


+ return ret;
+}
+

+static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value)
+{
+ struct ttl_module *mod = dev_get_drvdata(gpio->dev);
+ void __iomem *port;
+ u8 *shadow;
+
+ if (offset < 8) {
+ port = &mod->regs->porta;
+ shadow = &mod->porta_shadow;
+ } else if (offset < 16) {
+ port = &mod->regs->portb;
+ shadow = &mod->portb_shadow;
+ offset -= 8;
+ } else {
+ port = &mod->regs->portc;
+ shadow = &mod->portc_shadow;
+ offset -= 16;
+ }
+
+ spin_lock(&mod->lock);
+ if (value)
+ *shadow |= (1 << offset);
+ else
+ *shadow &= ~(1 << offset);
+
+ iowrite16be(*shadow, port);
+ spin_unlock(&mod->lock);
+}
+
+static void __devinit ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val)
+{
+ iowrite16be(reg, &mod->regs->control);
+ iowrite16be(val, &mod->regs->control);
+}
+
+static void __devinit ttl_setup_device(struct ttl_module *mod)
+{
+ /* reset the device to a known state */
+ iowrite16be(0x0000, &mod->regs->control);
+ iowrite16be(0x0001, &mod->regs->control);
+ iowrite16be(0x0000, &mod->regs->control);
+
+ /* put all ports in open-drain mode */
+ ttl_write_reg(mod, PORTA_IOCTL, 0x00ff);
+ ttl_write_reg(mod, PORTB_IOCTL, 0x00ff);
+ ttl_write_reg(mod, PORTC_IOCTL, 0x000f);
+
+ /* set all ports as outputs */
+ ttl_write_reg(mod, PORTA_DIRECTION, 0x0000);
+ ttl_write_reg(mod, PORTB_DIRECTION, 0x0000);
+ ttl_write_reg(mod, PORTC_DIRECTION, 0x0000);
+
+ /* set all ports to drive zeroes */
+ iowrite16be(0x0000, &mod->regs->porta);
+ iowrite16be(0x0000, &mod->regs->portb);
+ iowrite16be(0x0000, &mod->regs->portc);
+
+ /* enable all ports */
+ ttl_write_reg(mod, MASTER_CONF_CTL, CONF_PAE | CONF_PBE | CONF_PCE);
+}
+
+static int __devinit ttl_probe(struct platform_device *pdev)
+{
+ struct janz_platform_data *pdata;
+ struct device *dev = &pdev->dev;
+ struct ttl_module *mod;
+ struct gpio_chip *gpio;
+ struct resource *res;
+ int ret;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(dev, "no platform data\n");
+ ret = -ENXIO;
+ goto out_return;
+ }
+
+ mod = kzalloc(sizeof(*mod), GFP_KERNEL);
+ if (!mod) {
+ dev_err(dev, "unable to allocate private data\n");
+ ret = -ENOMEM;
+ goto out_return;
+ }
+
+ platform_set_drvdata(pdev, mod);
+ spin_lock_init(&mod->lock);
+
+ /* get access to the MODULbus registers for this module */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "MODULbus registers not found\n");
+ ret = -ENODEV;
+ goto out_free_mod;
+ }
+
+ mod->regs = ioremap(res->start, resource_size(res));
+ if (!mod->regs) {
+ dev_err(dev, "MODULbus registers not ioremap\n");
+ ret = -ENOMEM;
+ goto out_free_mod;
+ }
+
+ ttl_setup_device(mod);
+
+ /* Initialize the GPIO data structures */
+ gpio = &mod->gpio;
+ gpio->dev = &pdev->dev;
+ gpio->label = pdev->name;
+ gpio->get = ttl_get_value;
+ gpio->set = ttl_set_value;
+ gpio->owner = THIS_MODULE;
+
+ /* request dynamic allocation */
+ gpio->base = -1;
+ gpio->ngpio = 20;
+
+ ret = gpiochip_add(gpio);
+ if (ret) {
+ dev_err(dev, "unable to add GPIO chip\n");
+ goto out_iounmap_regs;
+ }
+
+ dev_info(&pdev->dev, "module %d: registered GPIO device\n",
+ pdata->modno);
+ return 0;
+
+out_iounmap_regs:
+ iounmap(mod->regs);
+out_free_mod:
+ kfree(mod);
+out_return:


+ return ret;
+}
+

+static int __devexit ttl_remove(struct platform_device *pdev)
+{
+ struct ttl_module *mod = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ ret = gpiochip_remove(&mod->gpio);
+ if (ret) {
+ dev_err(dev, "unable to remove GPIO chip\n");


+ return ret;
+ }
+

+ iounmap(mod->regs);
+ kfree(mod);
+ return 0;
+}
+
+static struct platform_driver ttl_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ttl_probe,
+ .remove = __devexit_p(ttl_remove),
+};
+
+static int __init ttl_init(void)
+{
+ return platform_driver_register(&ttl_driver);
+}
+
+static void __exit ttl_exit(void)
+{
+ platform_driver_unregister(&ttl_driver);
+}
+
+MODULE_AUTHOR("Ira W. Snyder <i...@ovro.caltech.edu>");
+MODULE_DESCRIPTION("Janz MODULbus VMOD-TTL Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:janz-ttl");
+
+module_init(ttl_init);
+module_exit(ttl_exit);
--
1.5.4.3

Ira W. Snyder

unread,
Mar 18, 2010, 1:10:02 PM3/18/10
to
The Janz VMOD-ICAN3 is a MODULbus daughterboard which fits onto any
MODULbus carrier board. It is an intelligent CAN controller with a
microcontroller and associated firmware.

Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>

Cc: socketc...@lists.berlios.de
Cc: net...@vger.kernel.org
---
drivers/net/can/Kconfig | 10 +
drivers/net/can/Makefile | 1 +
drivers/net/can/janz-ican3.c | 1659 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1670 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/can/janz-ican3.c

diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig
index 05b7517..2c5227c 100644
--- a/drivers/net/can/Kconfig
+++ b/drivers/net/can/Kconfig
@@ -63,6 +63,16 @@ config CAN_BFIN


To compile this driver as a module, choose M here: the

module will be called bfin_can.

+config CAN_JANZ_ICAN3
+ tristate "Janz VMOD-ICAN3 Intelligent CAN controller"
+ depends on CAN_DEV && MFD_JANZ_CMODIO
+ ---help---
+ Driver for Janz VMOD-ICAN3 Intelligent CAN controller module, which
+ connects to a MODULbus carrier board.
+
+ This driver can also be built as a module. If so, the module will be
+ called janz-ican3.ko.
+
source "drivers/net/can/mscan/Kconfig"

source "drivers/net/can/sja1000/Kconfig"
diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile
index 7a702f2..9047cd0 100644
--- a/drivers/net/can/Makefile
+++ b/drivers/net/can/Makefile
@@ -15,5 +15,6 @@ obj-$(CONFIG_CAN_AT91) += at91_can.o
obj-$(CONFIG_CAN_TI_HECC) += ti_hecc.o
obj-$(CONFIG_CAN_MCP251X) += mcp251x.o
obj-$(CONFIG_CAN_BFIN) += bfin_can.o
+obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o

ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG
diff --git a/drivers/net/can/janz-ican3.c b/drivers/net/can/janz-ican3.c
new file mode 100644
index 0000000..94d4995
--- /dev/null
+++ b/drivers/net/can/janz-ican3.c
@@ -0,0 +1,1659 @@
+/*
+ * Janz MODULbus VMOD-ICAN3 CAN Interface Driver


+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+

+#include <linux/netdevice.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+
+#include <linux/mfd/janz.h>
+
+/* the DPM has 64k of memory, organized into 256x 256 byte pages */
+#define DPM_NUM_PAGES 256
+#define DPM_PAGE_SIZE 256
+#define DPM_PAGE_ADDR(p) ((p) * DPM_PAGE_SIZE)
+
+/* JANZ ICAN3 "old-style" host interface queue page numbers */
+#define QUEUE_OLD_CONTROL 0
+#define QUEUE_OLD_RB0 1
+#define QUEUE_OLD_RB1 2
+#define QUEUE_OLD_WB0 3
+#define QUEUE_OLD_WB1 4
+
+/* Janz ICAN3 "old-style" host interface control registers */
+#define MSYNC_PEER 0x00 /* ICAN only */
+#define MSYNC_LOCL 0x01 /* host only */
+#define TARGET_RUNNING 0x02
+
+#define MSYNC_RB0 0x01
+#define MSYNC_RB1 0x02
+#define MSYNC_RBLW 0x04
+#define MSYNC_RB_MASK (MSYNC_RB0 | MSYNC_RB1)
+
+#define MSYNC_WB0 0x10
+#define MSYNC_WB1 0x20
+#define MSYNC_WBLW 0x40
+#define MSYNC_WB_MASK (MSYNC_WB0 | MSYNC_WB1)
+
+/* Janz ICAN3 "new-style" host interface queue page numbers */
+#define QUEUE_TOHOST 5
+#define QUEUE_FROMHOST_MID 6
+#define QUEUE_FROMHOST_HIGH 7
+#define QUEUE_FROMHOST_LOW 8
+
+/* The first free page in the DPM is #9 */
+#define DPM_FREE_START 9
+
+/* Janz ICAN3 "new-style" and "fast" host interface descriptor flags */
+#define DESC_VALID 0x80
+#define DESC_WRAP 0x40
+#define DESC_INTERRUPT 0x20
+#define DESC_IVALID 0x10
+#define DESC_LEN(len) (len)
+
+/* Janz ICAN3 Firmware Messages */
+#define MSG_CONNECTI 0x02
+#define MSG_DISCONNECT 0x03
+#define MSG_IDVERS 0x04
+#define MSG_MSGLOST 0x05
+#define MSG_NEWHOSTIF 0x08
+#define MSG_SETAFILMASK 0x10
+#define MSG_INITFDPMQUEUE 0x11
+#define MSG_HWCONF 0x12
+#define MSG_FMSGLOST 0x15
+#define MSG_CEVTIND 0x37
+#define MSG_CBTRREQ 0x41
+#define MSG_COFFREQ 0x42
+#define MSG_CONREQ 0x43
+#define MSG_CCONFREQ 0x47
+
+/* Janz ICAN3 CAN Set Acceptance Filter Mask Message Types */
+#define SETAFILMASK_REJECT 0x00
+#define SETAFILMASK_FASTIF 0x02
+
+/* Janz ICAN3 CAN Hardware Configuration Message Types */
+#define HWCONF_TERMINATE_ON 0x01
+#define HWCONF_TERMINATE_OFF 0x00
+
+/* Janz ICAN3 CAN Event Indication Message Types */
+#define CEVTIND_EI 0x01
+#define CEVTIND_DOI 0x02
+#define CEVTIND_LOST 0x04
+#define CEVTIND_FULL 0x08
+#define CEVTIND_BEI 0x10
+
+#define CEVTIND_CHIP_SJA1000 0x02
+
+#define ICAN3_BUSERR_QUOTA_MAX 255
+
+/* Janz ICAN3 CAN Frame Conversion */
+#define ICAN3_ECHO 0x10
+#define ICAN3_EFF_RTR 0x40
+#define ICAN3_SFF_RTR 0x10
+#define ICAN3_EFF 0x80
+
+#define ICAN3_CAN_TYPE_MASK 0x0f
+#define ICAN3_CAN_TYPE_SFF 0x00
+#define ICAN3_CAN_TYPE_EFF 0x01
+
+#define ICAN3_CAN_DLC_MASK 0x0f
+
+/*
+ * SJA1000 Status and Error Register Definitions
+ *
+ * Copied from drivers/net/can/sja1000/sja1000.h
+ */
+
+/* status register content */
+#define SR_BS 0x80
+#define SR_ES 0x40
+#define SR_TS 0x20
+#define SR_RS 0x10
+#define SR_TCS 0x08
+#define SR_TBS 0x04
+#define SR_DOS 0x02
+#define SR_RBS 0x01
+
+#define SR_CRIT (SR_BS|SR_ES)
+
+/* ECC register */
+#define ECC_SEG 0x1F
+#define ECC_DIR 0x20
+#define ECC_ERR 6
+#define ECC_BIT 0x00
+#define ECC_FORM 0x40
+#define ECC_STUFF 0x80
+#define ECC_MASK 0xc0
+
+/* Number of buffers for use in the "new-style" host interface */
+#define ICAN3_NEW_BUFFERS 16
+
+/* Number of buffers for use in the "fast" host interface */
+#define ICAN3_TX_BUFFERS 512
+#define ICAN3_RX_BUFFERS 1024
+
+/* Driver Name */
+#define DRV_NAME "janz-ican3"
+
+/* DPM Control Registers -- starts at offset 0x100 in the MODULbus registers */
+struct ican3_dpm_control {
+ /* window address register */
+ u8 window_address;
+ u8 unused1;
+
+ /*
+ * Read access: clear interrupt from microcontroller
+ * Write access: send interrupt to microcontroller
+ */
+ u8 interrupt;
+ u8 unused2;
+
+ /* write-only: reset all hardware on the module */
+ u8 hwreset;
+ u8 unused3;
+
+ /* write-only: generate an interrupt to the TPU */
+ u8 tpuinterrupt;
+};
+
+struct ican3_dev {
+
+ /* must be the first member */
+ struct can_priv can;
+
+ /* CAN network device */
+ struct net_device *ndev;
+ struct napi_struct napi;
+
+ /* Device for printing */
+ struct device *dev;
+
+ /* module number */
+ unsigned int num;
+
+ /* base address of registers and IRQ */
+ struct janz_cmodio_onboard_regs __iomem *ctrl;
+ struct ican3_dpm_control *dpmctrl;
+ void __iomem *dpm;
+ int irq;
+
+ /* old and new style host interface */
+ unsigned int iftype;
+ spinlock_t lock;
+
+ /* new host interface */
+ unsigned int rx_int;
+ unsigned int rx_num;
+ unsigned int tx_num;
+
+ /* fast host interface */
+ unsigned int fastrx_start;
+ unsigned int fastrx_int;
+ unsigned int fastrx_num;
+ unsigned int fasttx_start;
+ unsigned int fasttx_num;
+
+ /* first free DPM page */
+ unsigned int free_page;
+};
+
+struct ican3_msg {
+ u8 control;
+ u8 spec;
+ __le16 len;
+ u8 data[252];
+};
+
+struct ican3_new_desc {
+ u8 control;
+ u8 pointer;
+};
+
+struct ican3_fast_desc {
+ u8 control;
+ u8 command;
+ u8 data[14];
+};
+
+/* write to the window basic address register */
+static inline void ican3_set_page(struct ican3_dev *mod, unsigned int page)
+{
+ BUG_ON(page >= DPM_NUM_PAGES);
+ iowrite8(page, &mod->dpmctrl->window_address);
+}
+
+/*
+ * ICAN3 "old-style" host interface
+ */
+
+/*
+ * Recieve a message from the ICAN3 "old-style" firmware interface
+ *
+ * LOCKING: must hold mod->lock
+ *
+ * returns 0 on success, -ENOMEM when no message exists
+ */
+static int ican3_old_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned int mbox, mbox_page;
+ u8 locl, peer, xord;
+
+ /* get the MSYNC registers */
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ peer = ioread8(mod->dpm + MSYNC_PEER);
+ locl = ioread8(mod->dpm + MSYNC_LOCL);
+ xord = locl ^ peer;
+
+ if ((xord & MSYNC_RB_MASK) == 0x00) {
+ dev_dbg(mod->dev, "no mbox for reading\n");
+ return -ENOMEM;
+ }
+
+ /* find the first free mbox to read */
+ if ((xord & MSYNC_RB_MASK) == MSYNC_RB_MASK)
+ mbox = (xord & MSYNC_RBLW) ? MSYNC_RB0 : MSYNC_RB1;
+ else
+ mbox = (xord & MSYNC_RB0) ? MSYNC_RB0 : MSYNC_RB1;
+
+ /* copy the message */
+ mbox_page = (mbox == MSYNC_RB0) ? QUEUE_OLD_RB0 : QUEUE_OLD_RB1;
+ ican3_set_page(mod, mbox_page);
+ memcpy_fromio(msg, mod->dpm, sizeof(*msg));
+
+ /*
+ * notify the firmware that the read buffer is available
+ * for it to fill again
+ */
+ locl ^= mbox;
+
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ iowrite8(locl, mod->dpm + MSYNC_LOCL);


+ return 0;
+}
+

+/*
+ * Send a message through the "old-style" firmware interface
+ *
+ * LOCKING: must hold mod->lock
+ *
+ * returns 0 on success, -ENOMEM when no free space exists
+ */
+static int ican3_old_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned int mbox, mbox_page;
+ u8 locl, peer, xord;
+
+ /* get the MSYNC registers */
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ peer = ioread8(mod->dpm + MSYNC_PEER);
+ locl = ioread8(mod->dpm + MSYNC_LOCL);
+ xord = locl ^ peer;
+
+ if ((xord & MSYNC_WB_MASK) == MSYNC_WB_MASK) {
+ dev_err(mod->dev, "no mbox for writing\n");
+ return -ENOMEM;
+ }
+
+ /* calculate a free mbox to use */
+ mbox = (xord & MSYNC_WB0) ? MSYNC_WB1 : MSYNC_WB0;
+
+ /* copy the message to the DPM */
+ mbox_page = (mbox == MSYNC_WB0) ? QUEUE_OLD_WB0 : QUEUE_OLD_WB1;
+ ican3_set_page(mod, mbox_page);
+ memcpy_toio(mod->dpm, msg, sizeof(*msg));
+
+ locl ^= mbox;
+ if (mbox == MSYNC_WB1)
+ locl |= MSYNC_WBLW;
+
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ iowrite8(locl, mod->dpm + MSYNC_LOCL);


+ return 0;
+}
+

+/*
+ * ICAN3 "new-style" Host Interface Setup
+ */
+
+static void __devinit ican3_init_new_host_interface(struct ican3_dev *mod)
+{
+ struct ican3_new_desc desc;
+ unsigned long flags;
+ void __iomem *dst;
+ int i;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* setup the internal datastructures for RX */
+ mod->rx_num = 0;
+ mod->rx_int = 0;
+
+ /* tohost queue descriptors are in page 5 */
+ ican3_set_page(mod, QUEUE_TOHOST);
+ dst = mod->dpm;
+
+ /* initialize the tohost (rx) queue descriptors: pages 9-24 */
+ for (i = 0; i < ICAN3_NEW_BUFFERS; i++) {
+ desc.control = DESC_INTERRUPT | DESC_LEN(1); /* I L=1 */
+ desc.pointer = mod->free_page;
+
+ /* set wrap flag on last buffer */
+ if (i == ICAN3_NEW_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ memcpy_toio(dst, &desc, sizeof(desc));
+ dst += sizeof(desc);
+ mod->free_page++;
+ }
+
+ /* fromhost (tx) mid queue descriptors are in page 6 */
+ ican3_set_page(mod, QUEUE_FROMHOST_MID);
+ dst = mod->dpm;
+
+ /* setup the internal datastructures for TX */
+ mod->tx_num = 0;
+
+ /* initialize the fromhost mid queue descriptors: pages 25-40 */
+ for (i = 0; i < ICAN3_NEW_BUFFERS; i++) {
+ desc.control = DESC_VALID | DESC_LEN(1); /* V L=1 */
+ desc.pointer = mod->free_page;
+
+ /* set wrap flag on last buffer */
+ if (i == ICAN3_NEW_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ memcpy_toio(dst, &desc, sizeof(desc));
+ dst += sizeof(desc);
+ mod->free_page++;
+ }
+
+ /* fromhost hi queue descriptors are in page 7 */
+ ican3_set_page(mod, QUEUE_FROMHOST_HIGH);
+ dst = mod->dpm;
+
+ /* initialize only a single buffer in the fromhost hi queue (unused) */
+ desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */
+ desc.pointer = mod->free_page;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ mod->free_page++;
+
+ /* fromhost low queue descriptors are in page 8 */
+ ican3_set_page(mod, QUEUE_FROMHOST_LOW);
+ dst = mod->dpm;
+
+ /* initialize only a single buffer in the fromhost low queue (unused) */
+ desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */
+ desc.pointer = mod->free_page;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ mod->free_page++;
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+}
+
+/*
+ * ICAN3 Fast Host Interface Setup
+ */
+
+static void __devinit ican3_init_fast_host_interface(struct ican3_dev *mod)
+{
+ struct ican3_fast_desc desc;
+ unsigned long flags;
+ unsigned int addr;
+ void __iomem *dst;
+ int i;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* save the start recv page */
+ mod->fastrx_start = mod->free_page;
+ mod->fastrx_num = 0;
+ mod->fastrx_int = 0;
+
+ /* build a single fast tohost queue descriptor */
+ memset(&desc, 0, sizeof(desc));
+ desc.control = 0x00;
+ desc.command = 1;
+
+ /* build the tohost queue descriptor ring in memory */
+ addr = 0;
+ for (i = 0; i < ICAN3_RX_BUFFERS; i++) {
+
+ /* set the wrap bit on the last buffer */
+ if (i == ICAN3_RX_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ /* switch to the correct page */
+ ican3_set_page(mod, mod->free_page);
+
+ /* copy the descriptor to the DPM */
+ dst = mod->dpm + addr;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ addr += sizeof(desc);
+
+ /* move to the next page if necessary */
+ if (addr >= DPM_PAGE_SIZE) {
+ addr = 0;
+ mod->free_page++;
+ }
+ }
+
+ /* make sure we page-align the next queue */
+ if (addr != 0)
+ mod->free_page++;
+
+ /* save the start xmit page */
+ mod->fasttx_start = mod->free_page;
+ mod->fasttx_num = 0;
+
+ /* build a single fast fromhost queue descriptor */
+ memset(&desc, 0, sizeof(desc));
+ desc.control = DESC_VALID;
+ desc.command = 1;
+
+ /* build the fromhost queue descriptor ring in memory */
+ addr = 0;
+ for (i = 0; i < ICAN3_TX_BUFFERS; i++) {
+
+ /* set the wrap bit on the last buffer */
+ if (i == ICAN3_TX_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ /* switch to the correct page */
+ ican3_set_page(mod, mod->free_page);
+
+ /* copy the descriptor to the DPM */
+ dst = mod->dpm + addr;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ addr += sizeof(desc);
+
+ /* move to the next page if necessary */
+ if (addr >= DPM_PAGE_SIZE) {
+ addr = 0;
+ mod->free_page++;
+ }
+ }
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+}
+
+/*
+ * ICAN3 "new-style" Host Interface Message Helpers
+ */
+
+/*
+ * LOCKING: must hold mod->lock
+ */
+static int ican3_new_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ struct ican3_new_desc desc;
+ void __iomem *desc_addr = mod->dpm + (mod->tx_num * sizeof(desc));
+
+ /* switch to the fromhost mid queue, and read the buffer descriptor */
+ ican3_set_page(mod, QUEUE_FROMHOST_MID);
+ memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+ if (!(desc.control & DESC_VALID)) {
+ dev_dbg(mod->dev, "%s: no free buffers\n", __func__);
+ return -ENOMEM;
+ }
+
+ /* switch to the data page, copy the data */
+ ican3_set_page(mod, desc.pointer);
+ memcpy_toio(mod->dpm, msg, sizeof(*msg));
+
+ /* switch back to the descriptor, set the valid bit, write it back */
+ ican3_set_page(mod, QUEUE_FROMHOST_MID);
+ desc.control ^= DESC_VALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* update the tx number */
+ mod->tx_num = (desc.control & DESC_WRAP) ? 0 : (mod->tx_num + 1);


+ return 0;
+}
+

+/*
+ * LOCKING: must hold mod->lock
+ */
+static int ican3_new_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ struct ican3_new_desc desc;
+ void __iomem *desc_addr = mod->dpm + (mod->rx_num * sizeof(desc));
+
+ /* switch to the tohost queue, and read the buffer descriptor */
+ ican3_set_page(mod, QUEUE_TOHOST);
+ memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+ if (!(desc.control & DESC_VALID)) {
+ dev_dbg(mod->dev, "%s: no buffers to recv\n", __func__);
+ return -ENOMEM;
+ }
+
+ /* switch to the data page, copy the data */
+ ican3_set_page(mod, desc.pointer);
+ memcpy_fromio(msg, mod->dpm, sizeof(*msg));
+
+ /* switch back to the descriptor, toggle the valid bit, write it back */
+ ican3_set_page(mod, QUEUE_TOHOST);
+ desc.control ^= DESC_VALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* update the rx number */
+ mod->rx_num = (desc.control & DESC_WRAP) ? 0 : (mod->rx_num + 1);


+ return 0;
+}
+

+/*
+ * Message Send / Recv Helpers
+ */
+
+static int ican3_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ if (mod->iftype == 0)
+ ret = ican3_old_send_msg(mod, msg);
+ else
+ ret = ican3_new_send_msg(mod, msg);
+
+ spin_unlock_irqrestore(&mod->lock, flags);


+ return ret;
+}
+

+static int ican3_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ if (mod->iftype == 0)
+ ret = ican3_old_recv_msg(mod, msg);
+ else
+ ret = ican3_new_recv_msg(mod, msg);
+
+ spin_unlock_irqrestore(&mod->lock, flags);


+ return ret;
+}
+

+/*
+ * Quick Pre-constructed Messages
+ */
+
+static int __devinit ican3_msg_connect(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_CONNECTI;
+ msg.len = cpu_to_le16(0);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int __devexit ican3_msg_disconnect(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_DISCONNECT;
+ msg.len = cpu_to_le16(0);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int __devinit ican3_msg_newhostif(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+ int ret;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_NEWHOSTIF;
+ msg.len = cpu_to_le16(0);
+
+ /* If we're not using the old interface, switching seems bogus */
+ WARN_ON(mod->iftype != 0);
+
+ ret = ican3_send_msg(mod, &msg);
+ if (ret)
+ return ret;
+
+ /* mark the module as using the new host interface */
+ mod->iftype = 1;


+ return 0;
+}
+

+static int __devinit ican3_msg_fasthostif(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+ unsigned int addr;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_INITFDPMQUEUE;
+ msg.len = cpu_to_le16(8);
+
+ /* write the tohost queue start address */
+ addr = DPM_PAGE_ADDR(mod->fastrx_start);
+ msg.data[0] = addr & 0xff;
+ msg.data[1] = (addr >> 8) & 0xff;
+ msg.data[2] = (addr >> 16) & 0xff;
+ msg.data[3] = (addr >> 24) & 0xff;
+
+ /* write the fromhost queue start address */
+ addr = DPM_PAGE_ADDR(mod->fasttx_start);
+ msg.data[4] = addr & 0xff;
+ msg.data[5] = (addr >> 8) & 0xff;
+ msg.data[6] = (addr >> 16) & 0xff;
+ msg.data[7] = (addr >> 24) & 0xff;
+
+ /* If we're not using the new interface yet, we cannot do this */
+ WARN_ON(mod->iftype != 1);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * Setup the CAN filter to either accept or reject all
+ * messages from the CAN bus.
+ */
+static int __devinit ican3_set_id_filter(struct ican3_dev *mod, bool accept)
+{
+ struct ican3_msg msg;
+ int ret;
+
+ /* Standard Frame Format */
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_SETAFILMASK;
+ msg.len = cpu_to_le16(5);
+ msg.data[0] = 0x00; /* IDLo LSB */
+ msg.data[1] = 0x00; /* IDLo MSB */
+ msg.data[2] = 0xff; /* IDHi LSB */
+ msg.data[3] = 0x07; /* IDHi MSB */
+
+ /* accept all frames for fast host if, or reject all frames */
+ msg.data[4] = accept ? SETAFILMASK_FASTIF : SETAFILMASK_REJECT;
+
+ ret = ican3_send_msg(mod, &msg);
+ if (ret)
+ return ret;
+
+ /* Extended Frame Format */
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_SETAFILMASK;
+ msg.len = cpu_to_le16(13);
+ msg.data[0] = 0; /* MUX = 0 */
+ msg.data[1] = 0x00; /* IDLo LSB */
+ msg.data[2] = 0x00;
+ msg.data[3] = 0x00;
+ msg.data[4] = 0x20; /* IDLo MSB */
+ msg.data[5] = 0xff; /* IDHi LSB */
+ msg.data[6] = 0xff;
+ msg.data[7] = 0xff;
+ msg.data[8] = 0x3f; /* IDHi MSB */
+
+ /* accept all frames for fast host if, or reject all frames */
+ msg.data[9] = accept ? SETAFILMASK_FASTIF : SETAFILMASK_REJECT;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * Bring the CAN bus online or offline
+ */
+static int ican3_set_bus_state(struct ican3_dev *mod, bool on)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = on ? MSG_CONREQ : MSG_COFFREQ;
+ msg.len = cpu_to_le16(0);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int __devinit ican3_set_termination(struct ican3_dev *mod, bool on)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_HWCONF;
+ msg.len = cpu_to_le16(2);
+ msg.data[0] = 0x00;
+ msg.data[1] = on ? HWCONF_TERMINATE_ON : HWCONF_TERMINATE_OFF;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int __devinit ican3_set_buserror(struct ican3_dev *mod, u8 quota)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_CCONFREQ;
+ msg.len = cpu_to_le16(2);
+ msg.data[0] = 0x00;
+ msg.data[1] = quota;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * ICAN3 to Linux CAN Frame Conversion
+ */
+
+static void ican3_to_can_frame(struct ican3_dev *mod,
+ struct ican3_fast_desc *desc,
+ struct can_frame *cf)
+{
+ if ((desc->command & ICAN3_CAN_TYPE_MASK) == ICAN3_CAN_TYPE_SFF) {
+ dev_dbg(mod->dev, "%s: old frame format\n", __func__);
+ if (desc->data[1] & ICAN3_SFF_RTR)
+ cf->can_id |= CAN_RTR_FLAG;
+
+ cf->can_id |= desc->data[0] << 3;
+ cf->can_id |= (desc->data[1] & 0xe0) >> 5;
+ cf->can_dlc = desc->data[1] & ICAN3_CAN_DLC_MASK;
+ memcpy(cf->data, &desc->data[2], sizeof(cf->data));
+ } else {
+ dev_dbg(mod->dev, "%s: new frame format\n", __func__);
+ cf->can_dlc = desc->data[0] & ICAN3_CAN_DLC_MASK;
+ if (desc->data[0] & ICAN3_EFF_RTR)
+ cf->can_id |= CAN_RTR_FLAG;
+
+ if (desc->data[0] & ICAN3_EFF) {
+ cf->can_id |= CAN_EFF_FLAG;
+ cf->can_id |= desc->data[2] << 21; /* 28-21 */
+ cf->can_id |= desc->data[3] << 13; /* 20-13 */
+ cf->can_id |= desc->data[4] << 5; /* 12-5 */
+ cf->can_id |= (desc->data[5] & 0xf8) >> 3;
+ } else {
+ cf->can_id |= desc->data[2] << 3; /* 10-3 */
+ cf->can_id |= desc->data[3] >> 5; /* 2-0 */
+ }
+
+ memcpy(cf->data, &desc->data[6], sizeof(cf->data));
+ }
+}
+
+static void can_frame_to_ican3(struct ican3_dev *mod,
+ struct can_frame *cf,
+ struct ican3_fast_desc *desc)
+{
+ /* clear out any stale data in the descriptor */
+ memset(desc->data, 0, sizeof(desc->data));
+
+ /* we always use the extended format, with the ECHO flag set */
+ desc->command = ICAN3_CAN_TYPE_EFF;
+ desc->data[0] |= cf->can_dlc;
+ desc->data[1] |= ICAN3_ECHO;
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ desc->data[0] |= ICAN3_EFF_RTR;
+
+ /* pack the id into the correct places */
+ if (cf->can_id & CAN_EFF_FLAG) {
+ dev_dbg(mod->dev, "%s: extended frame\n", __func__);
+ desc->data[0] |= ICAN3_EFF;
+ desc->data[2] = (cf->can_id & 0x1fe00000) >> 21; /* 28-21 */
+ desc->data[3] = (cf->can_id & 0x001fe000) >> 13; /* 20-13 */
+ desc->data[4] = (cf->can_id & 0x00001fe0) >> 5; /* 12-5 */
+ desc->data[5] = (cf->can_id & 0x0000001f) << 3; /* 4-0 */
+ } else {
+ dev_dbg(mod->dev, "%s: standard frame\n", __func__);
+ desc->data[2] = (cf->can_id & 0x7F8) >> 3; /* bits 10-3 */
+ desc->data[3] = (cf->can_id & 0x007) << 5; /* bits 2-0 */
+ }
+
+ /* copy the data bits into the descriptor */
+ memcpy(&desc->data[6], cf->data, sizeof(cf->data));
+}
+
+/*
+ * Interrupt Handling
+ */
+
+/*
+ * Handle an ID + Version message response from the firmware. We never generate
+ * this message in production code, but it is very useful when debugging to be
+ * able to display this message.
+ */
+static void ican3_handle_idvers(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ dev_dbg(mod->dev, "IDVERS response: %s\n", msg->data);
+}
+
+static void ican3_handle_msglost(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ struct net_device *dev = mod->ndev;
+ struct net_device_stats *stats = &dev->stats;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+
+ /*
+ * Report that communication messages with the microcontroller firmware
+ * are being lost. These are never CAN frames, so we do not generate an
+ * error frame for userspace
+ */
+ if (msg->spec == MSG_MSGLOST) {
+ dev_err(mod->dev, "lost %d control messages\n", msg->data[0]);
+ return;
+ }
+
+ /*
+ * Oops, this indicates that we have lost messages in the fast queue,
+ * which are exclusively CAN messages. Our driver isn't reading CAN
+ * frames fast enough.
+ *
+ * We'll pretend that the SJA1000 told us that it ran out of buffer
+ * space, because there is not a better message for this.
+ */
+ skb = alloc_can_err_skb(dev, &cf);
+ if (skb) {
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+ stats->rx_errors++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_rx(skb);
+ }
+}
+
+/*
+ * Handle CAN Event Indication Messages from the firmware
+ *
+ * The ICAN3 firmware provides the values of some SJA1000 registers when it
+ * generates this message. The code below is largely copied from the
+ * drivers/net/can/sja1000/sja1000.c file, and adapted as necessary
+ */
+static int ican3_handle_cevtind(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ struct net_device *dev = mod->ndev;
+ struct net_device_stats *stats = &dev->stats;
+ enum can_state state = mod->can.state;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ u8 status, isrc;
+
+ /* we can only handle the SJA1000 part */
+ if (msg->data[1] != CEVTIND_CHIP_SJA1000) {
+ dev_err(mod->dev, "unable to handle errors on non-SJA1000\n");
+ return -ENODEV;
+ }
+
+ /* check the message length for sanity */
+ if (msg->len < 6) {
+ dev_err(mod->dev, "error message too short\n");
+ return -EINVAL;
+ }
+
+ skb = alloc_can_err_skb(dev, &cf);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ isrc = msg->data[0];
+ status = msg->data[3];
+
+ /* data overrun interrupt */
+ if (isrc == CEVTIND_DOI || isrc == CEVTIND_LOST) {
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+ stats->rx_over_errors++;
+ stats->rx_errors++;
+ dev_info(mod->dev, "%s: overflow frame generated\n", __func__);
+ }
+
+ /* error warning interrupt */
+ if (isrc == CEVTIND_EI) {
+ if (status & SR_BS) {
+ state = CAN_STATE_BUS_OFF;
+ cf->can_id |= CAN_ERR_BUSOFF;
+ can_bus_off(dev);
+ } else if (status & SR_ES) {
+ state = CAN_STATE_ERROR_WARNING;
+ } else {
+ state = CAN_STATE_ERROR_ACTIVE;
+ }
+ }
+
+ /* bus error interrupt */
+ if (isrc == CEVTIND_BEI) {
+ u8 ecc = msg->data[2];
+ mod->can.can_stats.bus_error++;
+ stats->rx_errors++;
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+ switch (ecc & ECC_MASK) {
+ case ECC_BIT:
+ cf->data[2] |= CAN_ERR_PROT_BIT;
+ break;
+ case ECC_FORM:
+ cf->data[2] |= CAN_ERR_PROT_FORM;
+ break;
+ case ECC_STUFF:
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
+ break;
+ default:
+ cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+ cf->data[3] = ecc & ECC_SEG;
+ break;
+ }
+
+ if ((ecc & ECC_DIR) == 0)
+ cf->data[2] |= CAN_ERR_PROT_TX;
+ }
+
+ if (state != mod->can.state && (state == CAN_STATE_ERROR_WARNING ||
+ state == CAN_STATE_ERROR_PASSIVE)) {
+ u8 rxerr = msg->data[4];
+ u8 txerr = msg->data[5];
+ cf->can_id |= CAN_ERR_CRTL;
+ if (state == CAN_STATE_ERROR_WARNING) {
+ mod->can.can_stats.error_warning++;
+ cf->data[1] = (txerr > rxerr) ?
+ CAN_ERR_CRTL_TX_WARNING :
+ CAN_ERR_CRTL_RX_WARNING;
+ } else {
+ mod->can.can_stats.error_passive++;
+ cf->data[1] = (txerr > rxerr) ?
+ CAN_ERR_CRTL_TX_PASSIVE :
+ CAN_ERR_CRTL_RX_PASSIVE;
+ }
+ }
+
+ mod->can.state = state;
+ stats->rx_errors++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_rx(skb);


+ return 0;
+}
+

+static void ican3_handle_unknown_message(struct ican3_dev *mod,
+ struct ican3_msg *msg)
+{
+ dev_warn(mod->dev, "recieved unknown message: spec 0x%.2x length %d\n",
+ msg->spec, le16_to_cpu(msg->len));
+}
+
+/*
+ * Handle a control message from the firmware
+ */
+static void ican3_handle_message(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ dev_dbg(mod->dev, "%s: modno %d spec 0x%.2x len %d bytes\n", __func__,
+ mod->num, msg->spec, le16_to_cpu(msg->len));
+
+ switch (msg->spec) {
+ case MSG_IDVERS:
+ ican3_handle_idvers(mod, msg);
+ break;
+ case MSG_MSGLOST:
+ case MSG_FMSGLOST:
+ ican3_handle_msglost(mod, msg);
+ break;
+ case MSG_CEVTIND:
+ ican3_handle_cevtind(mod, msg);
+ break;
+ default:
+ ican3_handle_unknown_message(mod, msg);
+ break;
+ }
+}
+
+/*
+ * Check that there is room in the TX ring to transmit another skb
+ *
+ * LOCKING: must hold mod->lock
+ */
+static bool ican3_txok(struct ican3_dev *mod)
+{
+ struct ican3_fast_desc __iomem *desc;
+ u8 control;
+
+ /* copy the control bits of the descriptor */
+ ican3_set_page(mod, mod->fasttx_start + (mod->fasttx_num / 16));
+ desc = mod->dpm + ((mod->fasttx_num % 16) * sizeof(*desc));
+ control = ioread8(&desc->control);
+
+ /* if the control bits are not valid, then we have no more space */
+ if (!(control & DESC_VALID))
+ return false;
+
+ return true;
+}
+
+/*
+ * Recieve one CAN frame from the hardware
+ *
+ * This works like the core of a NAPI function, but is intended to be called
+ * from workqueue context instead. This driver already needs a workqueue to
+ * process control messages, so we use the workqueue instead of using NAPI.
+ * This was done to simplify locking.
+ *
+ * CONTEXT: must be called from user context
+ */
+static int ican3_recv_skb(struct ican3_dev *mod)
+{
+ struct net_device *ndev = mod->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ struct ican3_fast_desc desc;
+ void __iomem *desc_addr;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* copy the whole descriptor */
+ ican3_set_page(mod, mod->fastrx_start + (mod->fastrx_num / 16));
+ desc_addr = mod->dpm + ((mod->fastrx_num % 16) * sizeof(desc));
+ memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+
+ /* check that we actually have a CAN frame */
+ if (!(desc.control & DESC_VALID))
+ return -ENOBUFS;
+
+ /* allocate an skb */
+ skb = alloc_can_skb(ndev, &cf);
+ if (unlikely(skb == NULL)) {
+ stats->rx_dropped++;
+ goto err_noalloc;
+ }
+
+ /* convert the ICAN3 frame into Linux CAN format */
+ ican3_to_can_frame(mod, &desc, cf);
+
+ /* receive the skb, update statistics */
+ netif_receive_skb(skb);
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+
+err_noalloc:
+ /* toggle the valid bit and return the descriptor to the ring */
+ desc.control ^= DESC_VALID;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ ican3_set_page(mod, mod->fastrx_start + (mod->fastrx_num / 16));
+ memcpy_toio(desc_addr, &desc, 1);
+
+ /* update the next buffer pointer */
+ mod->fastrx_num = (desc.control & DESC_WRAP) ? 0
+ : (mod->fastrx_num + 1);
+
+ /* there are still more buffers to process */
+ spin_unlock_irqrestore(&mod->lock, flags);


+ return 0;
+}
+

+static int ican3_napi(struct napi_struct *napi, int budget)
+{
+ struct ican3_dev *mod = container_of(napi, struct ican3_dev, napi);
+ struct ican3_msg msg;
+ unsigned long flags;
+ int received = 0;
+ int ret;
+
+ /* process all communication messages */
+ while (true) {
+ ret = ican3_recv_msg(mod, &msg);
+ if (ret)
+ break;
+
+ ican3_handle_message(mod, &msg);
+ }
+
+ /* process all CAN frames from the fast interface */
+ while (received < budget) {
+ ret = ican3_recv_skb(mod);
+ if (ret)
+ break;
+
+ received++;
+ }
+
+ /* We have processed all packets that the adapter had, but it
+ * was less than our budget, stop polling */
+ if (received < budget)
+ napi_complete(napi);
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* Wake up the transmit queue if necessary */
+ if (netif_queue_stopped(mod->ndev) && ican3_txok(mod))
+ netif_wake_queue(mod->ndev);
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+
+ /* re-enable interrupt generation */
+ iowrite8(1 << mod->num, &mod->ctrl->int_enable);
+ return received;
+}
+
+static irqreturn_t ican3_irq(int irq, void *dev_id)
+{
+ struct ican3_dev *mod = dev_id;
+ u8 stat;
+
+ /*
+ * The interrupt status register on this device reports interrupts
+ * as zeroes instead of using ones like most other devices
+ */
+ stat = ioread8(&mod->ctrl->int_disable) & (1 << mod->num);
+ if (stat == (1 << mod->num))
+ return IRQ_NONE;
+
+ dev_dbg(mod->dev, "IRQ: module %d\n", mod->num);
+
+ /* clear the MODULbus interrupt from the microcontroller */
+ ioread8(&mod->dpmctrl->interrupt);
+
+ /* disable interrupt generation, schedule the NAPI poller */
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ napi_schedule(&mod->napi);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Firmware reset, startup, and shutdown
+ */
+
+/*
+ * Reset an ICAN module to its power-on state
+ *
+ * CONTEXT: no network device registered
+ * LOCKING: work function disabled
+ */
+static int ican3_reset_module(struct ican3_dev *mod)
+{
+ u8 val = 1 << mod->num;
+ unsigned long start;
+ u8 runold, runnew;
+
+ /* disable interrupts so no more work is scheduled */
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+
+ /* flush any pending work */
+ flush_scheduled_work();
+
+ /* the first unallocated page in the DPM is #9 */
+ mod->free_page = DPM_FREE_START;
+
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ runold = ioread8(mod->dpm + TARGET_RUNNING);
+
+ /* reset the module */
+ iowrite8(val, &mod->ctrl->reset_assert);
+ iowrite8(val, &mod->ctrl->reset_deassert);
+
+ /* wait until the module has finished resetting and is running */
+ start = jiffies;
+ do {
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ runnew = ioread8(mod->dpm + TARGET_RUNNING);
+ if (runnew == (runold ^ 0xff))
+ return 0;
+
+ msleep(10);
+ } while (time_before(jiffies, start + HZ / 4));
+
+ dev_err(mod->dev, "failed to reset CAN module\n");
+ return -ETIMEDOUT;
+}
+
+static void __devexit ican3_shutdown_module(struct ican3_dev *mod)
+{
+ ican3_msg_disconnect(mod);
+ ican3_reset_module(mod);
+}
+
+/*
+ * Startup an ICAN module, bringing it into fast mode
+ */
+static int __devinit ican3_startup_module(struct ican3_dev *mod)
+{
+ int ret;
+
+ ret = ican3_reset_module(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to reset module\n");


+ return ret;
+ }
+

+ /* re-enable interrupts so we can send messages */
+ iowrite8(1 << mod->num, &mod->ctrl->int_enable);
+
+ ret = ican3_msg_connect(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to connect to module\n");


+ return ret;
+ }
+

+ ican3_init_new_host_interface(mod);
+ ret = ican3_msg_newhostif(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to switch to new-style interface\n");


+ return ret;
+ }
+

+ ret = ican3_set_termination(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to enable termination\n");


+ return ret;
+ }
+

+ ret = ican3_set_buserror(mod, ICAN3_BUSERR_QUOTA_MAX);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-error\n");


+ return ret;
+ }
+

+ ican3_init_fast_host_interface(mod);
+ ret = ican3_msg_fasthostif(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to switch to fast host interface\n");


+ return ret;
+ }
+

+ ret = ican3_set_id_filter(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to set acceptance filter\n");


+ return ret;
+ }
+

+ return 0;
+}
+
+/*
+ * CAN Network Device
+ */
+
+static int ican3_open(struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ int ret;
+
+ /* open the CAN layer */
+ ret = open_candev(ndev);
+ if (ret) {
+ dev_err(mod->dev, "unable to start CAN layer\n");


+ return ret;
+ }
+

+ /* bring the bus online */
+ ret = ican3_set_bus_state(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-on\n");
+ close_candev(ndev);


+ return ret;
+ }
+

+ /* start up the network device */
+ mod->can.state = CAN_STATE_ERROR_ACTIVE;
+ netif_start_queue(ndev);
+


+ return 0;
+}
+

+static int ican3_stop(struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ int ret;
+
+ /* stop the network device xmit routine */
+ netif_stop_queue(ndev);
+ mod->can.state = CAN_STATE_STOPPED;
+
+ /* bring the bus offline, stop receiving packets */
+ ret = ican3_set_bus_state(mod, false);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-off\n");


+ return ret;
+ }
+

+ /* close the CAN layer */
+ close_candev(ndev);


+ return 0;
+}
+

+static int ican3_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf = (struct can_frame *)skb->data;
+ struct ican3_fast_desc desc;
+ void __iomem *desc_addr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* check that we can actually transmit */
+ if (!ican3_txok(mod)) {
+ dev_err(mod->dev, "no free descriptors, stopping queue\n");
+ netif_stop_queue(ndev);
+ spin_unlock_irqrestore(&mod->lock, flags);
+ return NETDEV_TX_BUSY;
+ }
+
+ /* copy the control bits of the descriptor */
+ ican3_set_page(mod, mod->fasttx_start + (mod->fasttx_num / 16));
+ desc_addr = mod->dpm + ((mod->fasttx_num % 16) * sizeof(desc));
+ memset(&desc, 0, sizeof(desc));
+ memcpy_fromio(&desc, desc_addr, 1);
+
+ /* convert the Linux CAN frame into ICAN3 format */
+ can_frame_to_ican3(mod, cf, &desc);
+
+ /*
+ * the programming manual says that you must set the IVALID bit, then
+ * interrupt, then set the valid bit. Quite weird, but it seems to be
+ * required for this to work
+ */
+ desc.control |= DESC_IVALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* generate a MODULbus interrupt to the microcontroller */
+ iowrite8(0x01, &mod->dpmctrl->interrupt);
+
+ desc.control ^= DESC_VALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* update the next buffer pointer */
+ mod->fasttx_num = (desc.control & DESC_WRAP) ? 0
+ : (mod->fasttx_num + 1);
+
+ /* update statistics */
+ stats->tx_packets++;
+ stats->tx_bytes += cf->can_dlc;
+ kfree_skb(skb);
+
+ /*
+ * This hardware doesn't have TX-done notifications, so we'll try and
+ * emulate it the best we can using ECHO skbs. Get the next TX
+ * descriptor, and see if we have room to send. If not, stop the queue.
+ * It will be woken when the ECHO skb for the current packet is recv'd.
+ */
+
+ /* copy the control bits of the descriptor */
+ if (!ican3_txok(mod))
+ netif_stop_queue(ndev);
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+ return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops ican3_netdev_ops = {
+ .ndo_open = ican3_open,
+ .ndo_stop = ican3_stop,
+ .ndo_start_xmit = ican3_xmit,
+};
+
+/*
+ * Low-level CAN Device
+ */
+
+/* This structure was stolen from drivers/net/can/sja1000/sja1000.c */
+static struct can_bittiming_const ican3_bittiming_const = {
+ .name = DRV_NAME,
+ .tseg1_min = 1,
+ .tseg1_max = 16,
+ .tseg2_min = 1,
+ .tseg2_max = 8,
+ .sjw_max = 4,
+ .brp_min = 1,
+ .brp_max = 64,
+ .brp_inc = 1,
+};
+
+/*
+ * This routine was stolen from drivers/net/can/sja1000/sja1000.c
+ *
+ * The bittiming register command for the ICAN3 just sets the bit timing
+ * registers on the SJA1000 chip directly
+ */
+static int ican3_set_bittiming(struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ struct can_bittiming *bt = &mod->can.bittiming;
+ struct ican3_msg msg;
+ u8 btr0, btr1;
+
+ btr0 = ((bt->brp - 1) & 0x3f) | (((bt->sjw - 1) & 0x3) << 6);
+ btr1 = ((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) |
+ (((bt->phase_seg2 - 1) & 0x7) << 4);
+ if (mod->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES)
+ btr1 |= 0x80;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_CBTRREQ;
+ msg.len = cpu_to_le16(4);
+ msg.data[0] = 0x00;
+ msg.data[1] = 0x00;
+ msg.data[2] = btr0;
+ msg.data[3] = btr1;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ int ret;
+
+ if (mode != CAN_MODE_START)
+ return -ENOTSUPP;
+
+ /* bring the bus online */
+ ret = ican3_set_bus_state(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-on\n");


+ return ret;
+ }
+

+ /* start up the network device */
+ mod->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ if (netif_queue_stopped(ndev))
+ netif_wake_queue(ndev);
+


+ return 0;
+}
+

+/*
+ * PCI Subsystem
+ */
+
+static int __devinit ican3_probe(struct platform_device *pdev)


+{
+ struct janz_platform_data *pdata;

+ struct net_device *ndev;
+ struct ican3_dev *mod;
+ struct resource *res;
+ struct device *dev;


+ int ret;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata)

+ return -ENXIO;
+
+ dev_dbg(&pdev->dev, "probe: module number %d\n", pdata->modno);
+
+ /* save the struct device for printing */
+ dev = &pdev->dev;
+
+ /* allocate the CAN device and private data */
+ ndev = alloc_candev(sizeof(*mod), 0);
+ if (!ndev) {
+ dev_err(dev, "unable to allocate CANdev\n");


+ ret = -ENOMEM;
+ goto out_return;
+ }
+

+ platform_set_drvdata(pdev, ndev);
+ mod = netdev_priv(ndev);
+ mod->ndev = ndev;
+ mod->dev = &pdev->dev;
+ mod->num = pdata->modno;
+ netif_napi_add(ndev, &mod->napi, ican3_napi, ICAN3_RX_BUFFERS);
+ spin_lock_init(&mod->lock);
+
+ /* the first unallocated page in the DPM is 9 */
+ mod->free_page = DPM_FREE_START;
+
+ ndev->netdev_ops = &ican3_netdev_ops;
+ ndev->flags |= IFF_ECHO;
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ mod->can.clock.freq = 8000000;
+ mod->can.bittiming_const = &ican3_bittiming_const;
+ mod->can.do_set_bittiming = ican3_set_bittiming;
+ mod->can.do_set_mode = ican3_set_mode;
+
+ /* find our IRQ number */
+ mod->irq = platform_get_irq(pdev, 0);
+ if (mod->irq < 0) {
+ dev_err(dev, "IRQ line not found\n");
+ ret = -ENODEV;
+ goto out_free_ndev;
+ }
+
+ ndev->irq = mod->irq;


+
+ /* get access to the MODULbus registers for this module */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "MODULbus registers not found\n");
+ ret = -ENODEV;

+ goto out_free_ndev;
+ }
+
+ mod->dpm = ioremap(res->start, resource_size(res));
+ if (!mod->dpm) {


+ dev_err(dev, "MODULbus registers not ioremap\n");
+ ret = -ENOMEM;

+ goto out_free_ndev;
+ }
+
+ mod->dpmctrl = mod->dpm + DPM_PAGE_SIZE;
+
+ /* get access to the control registers for this module */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(dev, "CONTROL registers not found\n");
+ ret = -ENODEV;
+ goto out_iounmap_dpm;
+ }
+
+ mod->ctrl = ioremap(res->start, resource_size(res));
+ if (!mod->ctrl) {
+ dev_err(dev, "CONTROL registers not ioremap\n");
+ ret = -ENOMEM;
+ goto out_iounmap_dpm;
+ }
+
+ /* disable our IRQ, then hookup the IRQ handler */
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ ret = request_irq(mod->irq, ican3_irq, IRQF_SHARED, DRV_NAME, mod);
+ if (ret) {
+ dev_err(dev, "unable to request IRQ\n");
+ goto out_iounmap_ctrl;
+ }
+
+ /* reset and initialize the CAN controller into fast mode */
+ napi_enable(&mod->napi);
+ ret = ican3_startup_module(mod);
+ if (ret) {
+ dev_err(dev, "%s: unable to start CANdev\n", __func__);
+ goto out_free_irq;
+ }
+
+ /* register with the Linux CAN layer */
+ ret = register_candev(ndev);
+ if (ret) {
+ dev_err(dev, "%s: unable to register CANdev\n", __func__);
+ goto out_free_irq;
+ }
+
+ dev_info(dev, "module %d: registered CAN device\n", pdata->modno);
+ return 0;
+
+out_free_irq:
+ napi_disable(&mod->napi);
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ free_irq(mod->irq, mod);
+out_iounmap_ctrl:
+ iounmap(mod->ctrl);
+out_iounmap_dpm:
+ iounmap(mod->dpm);
+out_free_ndev:
+ free_candev(ndev);


+out_return:
+ return ret;
+}
+

+static int __devexit ican3_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct ican3_dev *mod = netdev_priv(ndev);
+
+ /* unregister the netdevice, stop interrupts */
+ unregister_netdev(ndev);
+ napi_disable(&mod->napi);
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ free_irq(mod->irq, mod);
+
+ /* put the module into reset */
+ ican3_shutdown_module(mod);
+
+ /* unmap all registers */
+ iounmap(mod->ctrl);
+ iounmap(mod->dpm);
+
+ free_candev(ndev);
+


+ return 0;
+}
+

+static struct platform_driver ican3_driver = {


+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },

+ .probe = ican3_probe,
+ .remove = __devexit_p(ican3_remove),
+};
+
+static int __init ican3_init(void)
+{
+ return platform_driver_register(&ican3_driver);
+}
+
+static void __exit ican3_exit(void)
+{
+ platform_driver_unregister(&ican3_driver);


+}
+
+MODULE_AUTHOR("Ira W. Snyder <i...@ovro.caltech.edu>");

+MODULE_DESCRIPTION("Janz MODULbus VMOD-ICAN3 Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:janz-ican3");
+
+module_init(ican3_init);
+module_exit(ican3_exit);

Ira W. Snyder

unread,
Mar 18, 2010, 1:10:03 PM3/18/10
to
The Janz CMOD-IO PCI MODULbus carrier board is a PCI to MODULbus bridge,
which may host many different types of MODULbus daughterboards, including
CAN and GPIO controllers.

Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>

Cc: Samuel Ortiz <sa...@linux.intel.com>
---
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/janz-cmodio.c | 339 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/janz.h | 54 +++++++
4 files changed, 402 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/janz-cmodio.c
create mode 100644 include/linux/mfd/janz.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 8782978..f1858d7 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -27,6 +27,14 @@ config MFD_SM501_GPIO
lines on the SM501. The platform data is used to supply the
base number for the first GPIO line to register.

+config MFD_JANZ_CMODIO
+ tristate "Support for Janz CMOD-IO PCI MODULbus Carrier Board"
+ ---help---
+ This is the core driver for the Janz CMOD-IO PCI MODULbus
+ carrier board. This device is a PCI to MODULbus bridge which may
+ host many different types of MODULbus daughterboards, including
+ CAN and GPIO controllers.
+
config MFD_ASIC3
bool "Support for Compaq ASIC3"
depends on GENERIC_HARDIRQS && GPIOLIB && ARM
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e09eb48..e8fa905 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -3,6 +3,7 @@
#

obj-$(CONFIG_MFD_SM501) += sm501.o
+obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o
obj-$(CONFIG_MFD_SH_MOBILE_SDHI) += sh_mobile_sdhi.o

diff --git a/drivers/mfd/janz-cmodio.c b/drivers/mfd/janz-cmodio.c
new file mode 100644
index 0000000..914280e
--- /dev/null
+++ b/drivers/mfd/janz-cmodio.c
@@ -0,0 +1,339 @@
+/*
+ * Janz CMOD-IO MODULbus Carrier Board PCI Driver


+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *

+ * Lots of inspiration and code was copied from drivers/mfd/sm501.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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>

+#include <linux/pci.h>


+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+

+#include <linux/mfd/janz.h>
+
+#define DRV_NAME "janz-cmodio"
+
+/* Size of each MODULbus module in PCI BAR4 */
+#define CMODIO_MODULBUS_SIZE 0x200
+
+/* Maximum number of MODULbus modules on a CMOD-IO carrier board */
+#define CMODIO_MAX_MODULES 4
+
+/* Module Parameters */
+static unsigned int num_modules = CMODIO_MAX_MODULES;
+static unsigned char *modules[CMODIO_MAX_MODULES] = {
+ "janz-ican3",
+ "janz-ican3",
+ "",
+ "janz-ttl",
+};
+
+module_param_array(modules, charp, &num_modules, S_IRUGO);
+MODULE_PARM_DESC(modules, "MODULbus modules attached to the carrier board");
+
+struct cmodio_device {
+ /* Parent PCI device */
+ struct pci_dev *pdev;
+
+ /* PLX control registers */


+ struct janz_cmodio_onboard_regs __iomem *ctrl;
+

+ /* hex switch position */
+ u8 hex;
+
+ /* Subdevice ID numbers */
+ unsigned int subdev_id;
+};
+
+/*
+ * Subdevice Support
+ */
+
+static int cmodio_remove_subdev(struct device *dev, void *data)
+{
+ platform_device_unregister(to_platform_device(dev));


+ return 0;
+}
+

+static void cmodio_subdev_release(struct device *dev)
+{
+ kfree(to_platform_device(dev));
+}
+
+static struct platform_device *cmodio_create_subdev(struct cmodio_device *priv,
+ char *name,
+ unsigned int res_count,
+ unsigned int pdata_size)
+{
+ struct platform_device *pdev;
+ size_t res_size;
+
+ res_size = sizeof(struct resource) * res_count;
+ pdev = kzalloc(sizeof(*pdev) + res_size + pdata_size, GFP_KERNEL);
+ if (!pdev)
+ return NULL;
+
+ pdev->dev.release = cmodio_subdev_release;
+ pdev->dev.parent = &priv->pdev->dev;
+ pdev->name = name;
+
+ if (res_count) {
+ pdev->resource = (struct resource *)(pdev + 1);
+ pdev->num_resources = res_count;
+ }
+
+ if (pdata_size)
+ pdev->dev.platform_data = (void *)(pdev + 1) + res_size;
+
+ return pdev;
+}
+
+/* Create a memory resource for a subdevice */
+static void cmodio_create_mem(struct resource *parent, struct resource *res,
+ resource_size_t offset, resource_size_t size)
+{
+ res->flags = IORESOURCE_MEM;
+ res->parent = parent;
+ res->start = parent->start + offset;
+ res->end = parent->start + offset + size - 1;
+}
+
+/* Create an IRQ resource for a subdevice */
+static void cmodio_create_irq(struct resource *res, unsigned int irq)
+{
+ res->flags = IORESOURCE_IRQ;
+ res->parent = NULL;
+ res->start = irq;
+ res->end = irq;
+}
+
+static int __devinit cmodio_probe_subdevice(struct cmodio_device *priv,
+ char *name, unsigned int modno)


+{
+ struct janz_platform_data *pdata;

+ struct platform_device *pdev;
+ resource_size_t offset, size;
+ struct pci_dev *pci;
+ int ret;
+
+ pci = priv->pdev;
+ pdev = cmodio_create_subdev(priv, name, 3, sizeof(*pdata));
+ if (!pdev) {
+ dev_err(&pci->dev, "MODULbus slot %d alloc failed\n", modno);


+ ret = -ENOMEM;
+ goto out_return;
+ }
+

+ pdata = pdev->dev.platform_data;

+ pdata->modno = modno;
+ pdev->id = priv->subdev_id++;
+
+ /* MODULbus registers -- PCI BAR3 is big-endian MODULbus access */
+ offset = CMODIO_MODULBUS_SIZE * modno;
+ size = CMODIO_MODULBUS_SIZE;
+ cmodio_create_mem(&pci->resource[3], &pdev->resource[0], offset, size);
+
+ /* PLX Control Registers -- PCI BAR4 is interrupt and other registers */
+ offset = 0;
+ size = resource_size(&pci->resource[4]);
+ cmodio_create_mem(&pci->resource[4], &pdev->resource[1], offset, size);
+
+ /* IRQ */
+ cmodio_create_irq(&pdev->resource[2], pci->irq);
+
+ /* Register the device */
+ ret = platform_device_register(pdev);
+ if (ret) {
+ dev_err(&pci->dev, "MODULbus slot %d register failed\n", modno);
+ goto out_free;


+ }
+
+ return 0;
+

+out_free:
+ cmodio_subdev_release(&pdev->dev);


+out_return:
+ return ret;
+}
+

+/* Probe each submodule using kernel parameters */
+static int __devinit cmodio_probe_submodules(struct cmodio_device *priv)
+{
+ char *name;
+ int i;
+
+ for (i = 0; i < num_modules; i++) {
+ name = modules[i];
+ if (!strcmp(name, ""))
+ continue;
+
+ dev_dbg(&priv->pdev->dev, "MODULbus %d: name %s\n", i, name);
+ cmodio_probe_subdevice(priv, name, i);


+ }
+
+ return 0;
+}
+
+/*

+ * SYSFS Attributes
+ */
+
+static ssize_t mbus_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct cmodio_device *priv = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", priv->hex);
+}
+
+static DEVICE_ATTR(modulbus_number, S_IRUGO, mbus_show, NULL);
+
+static struct attribute *cmodio_sysfs_attrs[] = {
+ &dev_attr_modulbus_number.attr,
+ NULL,
+};
+
+static const struct attribute_group cmodio_sysfs_attr_group = {
+ .attrs = cmodio_sysfs_attrs,
+};
+
+/*
+ * PCI Driver
+ */
+
+static int __devinit cmodio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ struct cmodio_device *priv;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&dev->dev, "unable to allocate private data\n");


+ ret = -ENOMEM;
+ goto out_return;
+ }
+

+ pci_set_drvdata(dev, priv);
+ priv->pdev = dev;
+
+ /* Hardware Initialization */
+ ret = pci_enable_device(dev);
+ if (ret) {
+ dev_err(&dev->dev, "unable to enable device\n");
+ goto out_free_priv;
+ }
+
+ pci_set_master(dev);
+ ret = pci_request_regions(dev, DRV_NAME);
+ if (ret) {
+ dev_err(&dev->dev, "unable to request regions\n");
+ goto out_pci_disable_device;
+ }
+
+ /* Onboard configuration registers */
+ priv->ctrl = pci_ioremap_bar(dev, 4);
+ if (!priv->ctrl) {
+ dev_err(&dev->dev, "unable to remap onboard regs\n");
+ ret = -ENOMEM;
+ goto out_pci_release_regions;
+ }
+
+ /* Read the hex switch on the carrier board */
+ priv->hex = ioread8(&priv->ctrl->int_enable);
+
+ /* Add the MODULbus number (hex switch value) to the device's sysfs */
+ ret = sysfs_create_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+ if (ret) {
+ dev_err(&dev->dev, "unable to create sysfs attributes\n");
+ goto out_unmap_ctrl;
+ }
+
+ /*
+ * Disable all interrupt lines, each submodule will enable its
+ * own interrupt line if needed
+ */
+ iowrite8(0xf, &priv->ctrl->int_disable);
+
+ /* Register drivers for all submodules */
+ ret = cmodio_probe_submodules(priv);
+ if (ret) {
+ dev_err(&dev->dev, "unable to probe submodules\n");
+ goto out_sysfs_remove_group;


+ }
+
+ return 0;
+

+out_sysfs_remove_group:
+ sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+out_unmap_ctrl:
+ iounmap(priv->ctrl);
+out_pci_release_regions:
+ pci_release_regions(dev);
+out_pci_disable_device:
+ pci_disable_device(dev);
+out_free_priv:
+ kfree(priv);


+out_return:
+ return ret;
+}
+

+static void __devexit cmodio_pci_remove(struct pci_dev *dev)
+{
+ struct cmodio_device *priv = pci_get_drvdata(dev);
+
+ device_for_each_child(&dev->dev, NULL, cmodio_remove_subdev);
+ sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+ iounmap(priv->ctrl);
+ pci_release_regions(dev);
+ pci_disable_device(dev);
+ kfree(priv);
+}
+
+#define PCI_VENDOR_ID_JANZ 0x13c3
+
+/* The list of devices that this module will support */
+static DEFINE_PCI_DEVICE_TABLE(cmodio_pci_ids) = {
+ { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9030, PCI_VENDOR_ID_JANZ, 0x0101 },
+ { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_JANZ, 0x0100 },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, cmodio_pci_ids);
+
+static struct pci_driver cmodio_pci_driver = {
+ .name = DRV_NAME,
+ .id_table = cmodio_pci_ids,
+ .probe = cmodio_pci_probe,
+ .remove = __devexit_p(cmodio_pci_remove),
+};
+
+/*
+ * Module Init / Exit
+ */
+
+static int __init cmodio_init(void)
+{
+ return pci_register_driver(&cmodio_pci_driver);
+}
+
+static void __exit cmodio_exit(void)
+{
+ pci_unregister_driver(&cmodio_pci_driver);


+}
+
+MODULE_AUTHOR("Ira W. Snyder <i...@ovro.caltech.edu>");

+MODULE_DESCRIPTION("Janz CMOD-IO PCI MODULbus Carrier Board Driver");
+MODULE_LICENSE("GPL");
+
+module_init(cmodio_init);
+module_exit(cmodio_exit);
diff --git a/include/linux/mfd/janz.h b/include/linux/mfd/janz.h
new file mode 100644
index 0000000..e9994c4
--- /dev/null
+++ b/include/linux/mfd/janz.h
@@ -0,0 +1,54 @@
+/*
+ * Common Definitions for Janz MODULbus devices


+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+

+#ifndef JANZ_H
+#define JANZ_H
+
+struct janz_platform_data {
+ /* MODULbus Module Number */
+ unsigned int modno;
+};
+
+/* PLX bridge chip onboard registers */
+struct janz_cmodio_onboard_regs {


+ u8 unused1;
+
+ /*

+ * Read access: interrupt status
+ * Write access: interrupt disable
+ */
+ u8 int_disable;


+ u8 unused2;
+
+ /*

+ * Read access: MODULbus number (hex switch)
+ * Write access: interrupt enable
+ */
+ u8 int_enable;
+ u8 unused3;
+
+ /* write-only */
+ u8 reset_assert;
+ u8 unused4;
+
+ /* write-only */
+ u8 reset_deassert;
+ u8 unused5;
+
+ /* read-write access to serial EEPROM */
+ u8 eep;
+ u8 unused6;
+
+ /* write-only access to EEPROM chip select */
+ u8 enid;
+};
+
+#endif /* JANZ_H */

Ira W. Snyder

unread,
Mar 18, 2010, 1:10:04 PM3/18/10
to
This patch series adds support for the Janz CMOD-IO carrier board, as well
as the Janz VMOD-ICAN3 Intelligent CAN controller and the Janz VMOD-TTL
Digital IO controller. The CMOD-IO carrier board is a PCI to MODULbus
bridge, into which plug MODULbus daughterboards. I only have access to two
types of daughtercards, the VMOD-ICAN3 and VMOD-TTL boards mentioned above.

The CAN driver has been tested under high loads. I am able to generate ~60%
bus utilization. With two VMOD-ICAN3 boards looped back to each other,
neither one loses any packets when only a single board is generating
packets at maximum speed. Once both boards start generating packets, one
board will sometimes loose arbitration, and cause some lost packets.

RFCv3 -> RFCv4:
- addressed many review comments
- switch to NAPI
- add TX flow control
- mark functions with __devinit and __devexit
- add sysfs readout of MODULbus number (hex switch)
- implement GPIO driver for VMOD-TTL

RFCv2 -> RFCv3:
- addressed many review comments
- correct CAN bus error handling
- use struct device to track subdevices
- use structures for register layout
- add lots of #defines for register values
- use better function prefixes

RFCv1 -> RFCv2:
- converted to a multi-driver model
- addressed many review comments
- added CAN bus error handling
- use a work function only instead of work + NAPI
- use SJA1000 bittiming calculation code

I apologize if I've CC'd too many people here, but I'm unsure if people
would rather see the whole patch series, or just each part.

I appreciate any review you can offer.

Thanks,
Ira

Wolfgang Grandegger

unread,
Mar 19, 2010, 5:10:02 AM3/19/10
to
Hi Ira,

we already discussed this patch on the SocketCAN mailing list and there
are just a few minor issues and the request to add support for the new
"berr-reporting" option, if feasible. See:

commit 52c793f24054f5dc30d228e37e0e19cc8313f086
Author: Wolfgang Grandegger <w...@grandegger.com>
Date: Mon Feb 22 22:21:17 2010 +0000

can: netlink support for bus-error reporting and counters

This patch makes the bus-error reporting configurable and allows to
retrieve the CAN TX and RX bus error counters via netlink interface.
I have added support for the SJA1000. The TX and RX bus error counters
are also copied to the data fields 6..7 of error messages when state
changes are reported.

Should not be a big deal.

More inline...

[snip]


+struct ican3_dev {
> +
> + /* must be the first member */
> + struct can_priv can;
> +
> + /* CAN network device */
> + struct net_device *ndev;
> + struct napi_struct napi;
> +
> + /* Device for printing */
> + struct device *dev;
> +
> + /* module number */
> + unsigned int num;
> +
> + /* base address of registers and IRQ */
> + struct janz_cmodio_onboard_regs __iomem *ctrl;
> + struct ican3_dpm_control *dpmctrl;
> + void __iomem *dpm;
> + int irq;
> +
> + /* old and new style host interface */
> + unsigned int iftype;
> + spinlock_t lock;

Please describe what the lock is used for.

> + /* new host interface */
> + unsigned int rx_int;
> + unsigned int rx_num;
> + unsigned int tx_num;
> +
> + /* fast host interface */
> + unsigned int fastrx_start;
> + unsigned int fastrx_int;
> + unsigned int fastrx_num;
> + unsigned int fasttx_start;
> + unsigned int fasttx_num;
> +
> + /* first free DPM page */
> + unsigned int free_page;
> +};

[snip]


> +static void ican3_to_can_frame(struct ican3_dev *mod,
> + struct ican3_fast_desc *desc,
> + struct can_frame *cf)
> +{
> + if ((desc->command & ICAN3_CAN_TYPE_MASK) == ICAN3_CAN_TYPE_SFF) {
> + dev_dbg(mod->dev, "%s: old frame format\n", __func__);

This prints a debug message for every message which is not really
useful for the user. Please remove.

> + if (desc->data[1] & ICAN3_SFF_RTR)
> + cf->can_id |= CAN_RTR_FLAG;
> +
> + cf->can_id |= desc->data[0] << 3;
> + cf->can_id |= (desc->data[1] & 0xe0) >> 5;
> + cf->can_dlc = desc->data[1] & ICAN3_CAN_DLC_MASK;
> + memcpy(cf->data, &desc->data[2], sizeof(cf->data));
> + } else {
> + dev_dbg(mod->dev, "%s: new frame format\n", __func__);

Ditto.

Ditto.

> + desc->data[0] |= ICAN3_EFF;
> + desc->data[2] = (cf->can_id & 0x1fe00000) >> 21; /* 28-21 */
> + desc->data[3] = (cf->can_id & 0x001fe000) >> 13; /* 20-13 */
> + desc->data[4] = (cf->can_id & 0x00001fe0) >> 5; /* 12-5 */
> + desc->data[5] = (cf->can_id & 0x0000001f) << 3; /* 4-0 */
> + } else {
> + dev_dbg(mod->dev, "%s: standard frame\n", __func__);

Ditto.

> + desc->data[2] = (cf->can_id & 0x7F8) >> 3; /* bits 10-3 */
> + desc->data[3] = (cf->can_id & 0x007) << 5; /* bits 2-0 */
> + }
> +
> + /* copy the data bits into the descriptor */
> + memcpy(&desc->data[6], cf->data, sizeof(cf->data));
> +}

[snip]

Here and for the other errors below a dev_dbg() would be useful. Please
check sja1000.c.

> + cf->can_id |= CAN_ERR_CRTL;
> + cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
> + stats->rx_over_errors++;
> + stats->rx_errors++;
> + dev_info(mod->dev, "%s: overflow frame generated\n", __func__);

s/dev_info/dev_dbg/ ?

> + }
> +
> + /* error warning interrupt */
> + if (isrc == CEVTIND_EI) {
> + if (status & SR_BS) {
> + state = CAN_STATE_BUS_OFF;
> + cf->can_id |= CAN_ERR_BUSOFF;
> + can_bus_off(dev);
> + } else if (status & SR_ES) {
> + state = CAN_STATE_ERROR_WARNING;
> + } else {
> + state = CAN_STATE_ERROR_ACTIVE;
> + }
> + }
> +
> + /* bus error interrupt */
> + if (isrc == CEVTIND_BEI) {
> + u8 ecc = msg->data[2];

Add an empty line, please.

Ditto.

> + cf->can_id |= CAN_ERR_CRTL;
> + if (state == CAN_STATE_ERROR_WARNING) {
> + mod->can.can_stats.error_warning++;
> + cf->data[1] = (txerr > rxerr) ?
> + CAN_ERR_CRTL_TX_WARNING :
> + CAN_ERR_CRTL_RX_WARNING;
> + } else {
> + mod->can.can_stats.error_passive++;
> + cf->data[1] = (txerr > rxerr) ?
> + CAN_ERR_CRTL_TX_PASSIVE :
> + CAN_ERR_CRTL_RX_PASSIVE;
> + }
> + }
> +
> + mod->can.state = state;
> + stats->rx_errors++;
> + stats->rx_bytes += cf->can_dlc;
> + netif_rx(skb);
> + return 0;
> +}

[snip]


> +static irqreturn_t ican3_irq(int irq, void *dev_id)
> +{
> + struct ican3_dev *mod = dev_id;
> + u8 stat;
> +
> + /*
> + * The interrupt status register on this device reports interrupts
> + * as zeroes instead of using ones like most other devices
> + */
> + stat = ioread8(&mod->ctrl->int_disable) & (1 << mod->num);
> + if (stat == (1 << mod->num))
> + return IRQ_NONE;
> +
> + dev_dbg(mod->dev, "IRQ: module %d\n", mod->num);

Please remove this dev_dbg() as well.

[snip]


Could you please allow the user to disable termination, e.g. via module parameter
"bus_termination=0" in case he uses a cable with built-in termination.

[snip]

Please use a constant here.
[snip]

Please fix and resubmit with my:

"Acked-by: Wolfgang Grandegger <w...@grandegger.com>"

for the SocketCAN part.

Thanks,

Wolfgang.

Wolfgang Grandegger

unread,
Mar 19, 2010, 5:20:03 AM3/19/10
to
Ira W. Snyder wrote:
> The Janz CMOD-IO PCI MODULbus carrier board is a PCI to MODULbus bridge,
> which may host many different types of MODULbus daughterboards, including
> CAN and GPIO controllers.
>
> Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>
> Cc: Samuel Ortiz <sa...@linux.intel.com>

You can add my "Reviewed-by: Wolfgang Grandegger <w...@grandegger.com>".

Just one concern:

This is probably not a good default but just your private configuration.

Wolfgang.

Ira W. Snyder

unread,
Mar 19, 2010, 11:20:02 AM3/19/10
to

Yep, that's the configuration I have. Do you have any suggestions for a
better default? I could make them all blank strings, which means "no
daughtercard in this slot". That is my only idea for a different
default.

Thanks for the review!
Ira

Ira W. Snyder

unread,
Mar 19, 2010, 11:30:02 AM3/19/10
to
On Fri, Mar 19, 2010 at 10:01:14AM +0100, Wolfgang Grandegger wrote:
> Hi Ira,
>
> we already discussed this patch on the SocketCAN mailing list and there
> are just a few minor issues and the request to add support for the new
> "berr-reporting" option, if feasible. See:
>
> commit 52c793f24054f5dc30d228e37e0e19cc8313f086
> Author: Wolfgang Grandegger <w...@grandegger.com>
> Date: Mon Feb 22 22:21:17 2010 +0000
>
> can: netlink support for bus-error reporting and counters
>
> This patch makes the bus-error reporting configurable and allows to
> retrieve the CAN TX and RX bus error counters via netlink interface.
> I have added support for the SJA1000. The TX and RX bus error counters
> are also copied to the data fields 6..7 of error messages when state
> changes are reported.
>
> Should not be a big deal.
>

I think this patch came along since my last post of the driver. I must
have missed it. I'll try and add support.

Whoops, a leftover from debugging. Will change to dev_dbg().

What would you think about a sysfs node instead, so it could be changed
at runtime, on a per-daughtercard basis? Do you think enabling bus
termination is a good default? IMO, it is a pretty safe default for most
users.

Thanks for the review!
Ira

Wolfgang Grandegger

unread,
Mar 19, 2010, 11:40:02 AM3/19/10
to

That's probably better. And if no boards are defined via module
parameter, return with an error and print an error message telling the
user what to do. Also maybe s/""/"empty"/ and a better the
MODULE_DESCRIPTION could be useful.

Wolfgang.

Wolfgang Grandegger

unread,
Mar 19, 2010, 11:50:02 AM3/19/10
to
Ira W. Snyder wrote:
> On Fri, Mar 19, 2010 at 10:01:14AM +0100, Wolfgang Grandegger wrote:
>> Hi Ira,
>>
>> we already discussed this patch on the SocketCAN mailing list and there
>> are just a few minor issues and the request to add support for the new
>> "berr-reporting" option, if feasible. See:
>>
>> commit 52c793f24054f5dc30d228e37e0e19cc8313f086
>> Author: Wolfgang Grandegger <w...@grandegger.com>
>> Date: Mon Feb 22 22:21:17 2010 +0000
>>
>> can: netlink support for bus-error reporting and counters
>>
>> This patch makes the bus-error reporting configurable and allows to
>> retrieve the CAN TX and RX bus error counters via netlink interface.
>> I have added support for the SJA1000. The TX and RX bus error counters
>> are also copied to the data fields 6..7 of error messages when state
>> changes are reported.
>>
>> Should not be a big deal.
>>
>
> I think this patch came along since my last post of the driver. I must
> have missed it. I'll try and add support.

No problem, it's really new. Just just need to enable BEI depending on
CAN_CTRLMODE_BERR_REPORTING.

I have no problem with the default. There should just be a way to
disable termination somehow. A dev_info(dev, "CAN bus termination
enabled") would furthermore make clear, that it's active. Setting
termination via SysFS is the better solution, of course, as it's per
device. It might even be useful to handle this in future in a common way
via CAN_CTRLMODE_BUS_TERMINATION.

Wolfgang.

Samuel Ortiz

unread,
Mar 19, 2010, 12:40:02 PM3/19/10
to
Hi Ira,

Some comments below:

On Thu, Mar 18, 2010 at 09:38:42AM -0700, Ira W. Snyder wrote:
> +/* Module Parameters */
> +static unsigned int num_modules = CMODIO_MAX_MODULES;
> +static unsigned char *modules[CMODIO_MAX_MODULES] = {
> + "janz-ican3",
> + "janz-ican3",
> + "",
> + "janz-ttl",
> +};

That's not very nice, but I honestly dont know how to make it less ugly...
At least this should be all left blank by default, as Wolfgang hinted.

> +/*
> + * Subdevice Support
> + */

Please use the mfd-core API for building and registering platform sub devices.
The pieces of code below should shrink significantly.

Why 4 ?


> +#define PCI_VENDOR_ID_JANZ 0x13c3
That probably belongs to pci_ids.h

Cheers,
Samuel.
--
Intel Open Source Technology Centre
http://oss.intel.com/

Ira W. Snyder

unread,
Mar 19, 2010, 2:30:01 PM3/19/10
to
On Fri, Mar 19, 2010 at 05:38:51PM +0100, Samuel Ortiz wrote:
> Hi Ira,
>
> Some comments below:
>
> On Thu, Mar 18, 2010 at 09:38:42AM -0700, Ira W. Snyder wrote:
> > +/* Module Parameters */
> > +static unsigned int num_modules = CMODIO_MAX_MODULES;
> > +static unsigned char *modules[CMODIO_MAX_MODULES] = {
> > + "janz-ican3",
> > + "janz-ican3",
> > + "",
> > + "janz-ttl",
> > +};
> That's not very nice, but I honestly dont know how to make it less ugly...
> At least this should be all left blank by default, as Wolfgang hinted.
>

Agreed, I've made the change in my tree.

> > +/*
> > + * Subdevice Support
> > + */
> Please use the mfd-core API for building and registering platform sub devices.
> The pieces of code below should shrink significantly.
>

Using this framework, how is it possible to create the devices that I
do down below. For each subdevice, I need three resources:

1) MODULbus registers -- PCI BAR3 + (0x200 * module_num)
2) PLX Control Registers -- PCI BAR4
3) IRQ

Specifically, the way IORESOURCE_MEM resources are copied seems wrong.
They start at the base address of only one resource and use the offsets
provided in the struct mfd_cell. See the if-statement at
drivers/mfd/mfd-core.c line 48.

I need two use two different parent resources. The mfd_add_devices()
function doesn't support this.

Because that is how the device works ;) There is a comment up above that
describes them as the "PLX control registers". Are you suggesting that I
add a comment here too?

I think lots of other PCI devices just use hard-coded numbers here.
They're device specific. You won't be able to program for the device at
all if you don't know the meaning of each PCI BAR.

> > +#define PCI_VENDOR_ID_JANZ 0x13c3
> That probably belongs to pci_ids.h
>

Should I add a patch to the series for this?

Thanks,
Ira

Ira W. Snyder

unread,
Mar 19, 2010, 4:10:02 PM3/19/10
to
On Fri, Mar 19, 2010 at 04:45:09PM +0100, Wolfgang Grandegger wrote:
> Ira W. Snyder wrote:
> > On Fri, Mar 19, 2010 at 10:01:14AM +0100, Wolfgang Grandegger wrote:
> >> Hi Ira,
> >>
> >> we already discussed this patch on the SocketCAN mailing list and there
> >> are just a few minor issues and the request to add support for the new
> >> "berr-reporting" option, if feasible. See:
> >>
> >> commit 52c793f24054f5dc30d228e37e0e19cc8313f086
> >> Author: Wolfgang Grandegger <w...@grandegger.com>
> >> Date: Mon Feb 22 22:21:17 2010 +0000
> >>
> >> can: netlink support for bus-error reporting and counters
> >>
> >> This patch makes the bus-error reporting configurable and allows to
> >> retrieve the CAN TX and RX bus error counters via netlink interface.
> >> I have added support for the SJA1000. The TX and RX bus error counters
> >> are also copied to the data fields 6..7 of error messages when state
> >> changes are reported.
> >>
> >> Should not be a big deal.
> >>
> >
> > I think this patch came along since my last post of the driver. I must
> > have missed it. I'll try and add support.
>
> No problem, it's really new. Just just need to enable BEI depending on
> CAN_CTRLMODE_BERR_REPORTING.
>

I have one final question about this.

The documentation for the firmware isn't very specific here. I believe
that in order to get any kind of error messages, I need the bus error
feature turned on. What is the expected behavior of an SJA1000 with the
BEI (bus error interrupt) turned off? Will you still get warning
messages for ERROR_ACTIVE -> ERROR_PASSIVE state transitions?

I'm not sure how I would go about testing this feature, either. Ideas?

I also noticed that I can enable "self test mode" and "listen only mode"
using the same firmware command. It appears that there are netlink
messages for this as well. Should I try and support these, too? I don't
really have any use for them (yet). I assume "self test mode" is
equivalent to "loopback mode" in the netlink messages.

Thanks,
Ira

Wolfgang Grandegger

unread,
Mar 19, 2010, 4:20:02 PM3/19/10
to

Yes. State transitions are enabled with EI and EPI.

> I'm not sure how I would go about testing this feature, either. Ideas?

Send messages without cable connected and watch the error messages with
"candump any,0:0,#ffffffff". With "ip ... berr-reporting on" you should
see additional bus-errors.

> I also noticed that I can enable "self test mode" and "listen only mode"
> using the same firmware command. It appears that there are netlink
> messages for this as well. Should I try and support these, too? I don't
> really have any use for them (yet). I assume "self test mode" is
> equivalent to "loopback mode" in the netlink messages.

List-only is straight forward while "self test mode" is not exactly like
"loopback mode", IIRC. Feel free to send a follow-up patch when you have
time for a thorough implementation and testing. It's also on my to-do
list for the SJA1000.

Wolfgang.

Ira W. Snyder

unread,
Mar 19, 2010, 6:00:03 PM3/19/10
to

I cannot set the registers directly, but I think I got it right. See
below.

> > I'm not sure how I would go about testing this feature, either. Ideas?
>
> Send messages without cable connected and watch the error messages with
> "candump any,0:0,#ffffffff". With "ip ... berr-reporting on" you should
> see additional bus-errors.
>

Ok, I tried this. On one controller, I turned on bus-error reporting. On
the other, I turn off bus-error reporting. I then tried sending lots of
messages with the cable unplugged. Here is what happened:

bus-error reporting on:
Lots of CAN_ERR_BUSERR messages are flooded in candump. There is also a
CAN_ERR_CRTL_TX_WARNING message, when there are too many TX errors.

bus-error reporting off:
There was only one message reported before the controller went into
ERROR-WARNING state. It was the same CAN_ERR_CRTL_TX_WARNING message as
above. There was no flooding of CAN_ERR_BUSERR messages.

Does this seem right? It seems pretty good to me.

> > I also noticed that I can enable "self test mode" and "listen only mode"
> > using the same firmware command. It appears that there are netlink
> > messages for this as well. Should I try and support these, too? I don't
> > really have any use for them (yet). I assume "self test mode" is
> > equivalent to "loopback mode" in the netlink messages.
>
> List-only is straight forward while "self test mode" is not exactly like
> "loopback mode", IIRC. Feel free to send a follow-up patch when you have
> time for a thorough implementation and testing. It's also on my to-do
> list for the SJA1000.
>

Ok, then I'll put this off for a while. Feel free to pester me about it
when there is a working implementation in the SJA1000 driver for me to
borrow from. :)

Thanks for all the help.

Wolfgang Grandegger

unread,
Mar 20, 2010, 4:00:02 AM3/20/10
to

OK, you will now also understand why bus-error reporting is off by
default. On low-end systems bus-error flooding may even hang the system.

> bus-error reporting off:
> There was only one message reported before the controller went into
> ERROR-WARNING state. It was the same CAN_ERR_CRTL_TX_WARNING message as
> above. There was no flooding of CAN_ERR_BUSERR messages.
>
> Does this seem right? It seems pretty good to me.

Yes, I'm just missing an error-passive message. What state does "ip -d
link show can0" report.

>>> I also noticed that I can enable "self test mode" and "listen only mode"
>>> using the same firmware command. It appears that there are netlink
>>> messages for this as well. Should I try and support these, too? I don't
>>> really have any use for them (yet). I assume "self test mode" is
>>> equivalent to "loopback mode" in the netlink messages.
>> List-only is straight forward while "self test mode" is not exactly like
>> "loopback mode", IIRC. Feel free to send a follow-up patch when you have
>> time for a thorough implementation and testing. It's also on my to-do
>> list for the SJA1000.
>>
>
> Ok, then I'll put this off for a while. Feel free to pester me about it
> when there is a working implementation in the SJA1000 driver for me to
> borrow from. :)

I will let you know when I have something working.

Wolfgang.

Ira W. Snyder

unread,
Mar 22, 2010, 12:00:02 PM3/22/10
to

Ok, here is what I did:

$ ip link set can0 up type can bitrate 1000000
$ ip link set can1 up type can bitrate 1000000 berr-reporting on
$ ip -d -s link
5: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
link/can
can state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
bitrate 1000000 sample-point 0.750
tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off
0 0 0 0 0 0
RX: bytes packets errors dropped overrun mcast
0 0 0 0 0 0
TX: bytes packets errors dropped carrier collsns
0 0 0 0 0 0
6: can1: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
link/can
can <BERR-REPORTING> state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
bitrate 1000000 sample-point 0.750
tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off
0 0 0 0 0 0
RX: bytes packets errors dropped overrun mcast
0 0 0 0 0 0
TX: bytes packets errors dropped carrier collsns
0 0 0 0 0 0

Now, in seperate windows, I ran cansequence and candump. I stopped
cansequence when it could not send any more packets (due to the cable
being unplugged).

$ cansequence -v -e -p can0
$ cansequence -v -e -p can1
$ candump any,0~0,#FFFFFFFF
can0 20000004 [8] 00 08 00 00 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000004 [8] 00 08 00 00 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME
can1 20000088 [8] 00 00 80 19 00 00 00 00 ERRORFRAME

This last message is repeated lots more times. That's the flooding we're
avoiding with berr-reporting off.

I see two types of messages here:
1) bus error (only on can1)
2) controller problems -- tx warning limit reached (both)

Am I missing some message? My error frame generation was mostly copied
from the sja1000 driver.

$ ip -d -s link
5: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
link/can
can state ERROR-WARNING (berr-counter tx 128 rx 0) restart-ms 0
bitrate 1000000 sample-point 0.750
tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off
0 0 0 1 0 0
RX: bytes packets errors dropped overrun mcast
16 0 2 0 0 0
TX: bytes packets errors dropped carrier collsns
513 513 0 0 0 0
6: can1: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
link/can
can <BERR-REPORTING> state ERROR-WARNING (berr-counter tx 128 rx 0) restart-ms 0
bitrate 1000000 sample-point 0.750
tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off
0 126 0 1 0 0
RX: bytes packets errors dropped overrun mcast
1024 0 254 0 0 0
TX: bytes packets errors dropped carrier collsns
513 513 0 0 0 0


> >>> I also noticed that I can enable "self test mode" and "listen only mode"
> >>> using the same firmware command. It appears that there are netlink
> >>> messages for this as well. Should I try and support these, too? I don't
> >>> really have any use for them (yet). I assume "self test mode" is
> >>> equivalent to "loopback mode" in the netlink messages.
> >> List-only is straight forward while "self test mode" is not exactly like
> >> "loopback mode", IIRC. Feel free to send a follow-up patch when you have
> >> time for a thorough implementation and testing. It's also on my to-do
> >> list for the SJA1000.
> >>
> >
> > Ok, then I'll put this off for a while. Feel free to pester me about it
> > when there is a working implementation in the SJA1000 driver for me to
> > borrow from. :)
>
> I will let you know when I have something working.
>

Thanks,
Ira

Wolfgang Grandegger

unread,
Mar 22, 2010, 3:20:03 PM3/22/10
to
Ira W. Snyder wrote:
> On Sat, Mar 20, 2010 at 08:55:16AM +0100, Wolfgang Grandegger wrote:
>> Ira W. Snyder wrote:
[snip]

It seem that you are not getting the error passive interrupt even...

> $ ip -d -s link
> 5: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
> link/can
> can state ERROR-WARNING (berr-counter tx 128 rx 0) restart-ms 0

if the hardware already reports >= 128 errors --^.

> bitrate 1000000 sample-point 0.750
> tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
> janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
> clock 8000000
> re-started bus-errors arbit-lost error-warn error-pass bus-off
> 0 0 0 1 0 0
> RX: bytes packets errors dropped overrun mcast
> 16 0 2 0 0 0
> TX: bytes packets errors dropped carrier collsns
> 513 513 0 0 0 0
> 6: can1: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
> link/can
> can <BERR-REPORTING> state ERROR-WARNING (berr-counter tx 128 rx 0) restart-ms 0
> bitrate 1000000 sample-point 0.750
> tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
> janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
> clock 8000000
> re-started bus-errors arbit-lost error-warn error-pass bus-off
> 0 126 0 1 0 0

But that's mabe because you stopped the test too early (just 126 bus errors).

> RX: bytes packets errors dropped overrun mcast
> 1024 0 254 0 0 0
> TX: bytes packets errors dropped carrier collsns
> 513 513 0 0 0 0

When I send out messages without cable connected I get:

-bash-3.2# ./ip -d -s link show can0
2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
link/can
can <BERR-REPORTING> state ERROR-PASSIVE (berr-counter tx 128 rx 0) restart-ms 0
bitrate 500000 sample-point 0.875
tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1


clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off

0 54101 0 1 1 0

RX: bytes packets errors dropped overrun mcast

432808 54101 54101 0 0 0

TX: bytes packets errors dropped carrier collsns
0 0 0 0 0 0

The following output is without BERR-REPORTING:

-bash-3.2# ./candump -t d any,0:0,#FFFFFFFF
(0.000000) can0 20000004 [8] 00 08 00 00 00 00 60 00 ERRORFRAME
(0.000474) can0 20000004 [8] 00 20 00 00 00 00 80 00 ERRORFRAME
^ ^
TX RX error counter

The patch I mentioned also copies the rx and tx error counter values to
the data field 6 and 7.

Wolfgang.

Wolfgang Grandegger

unread,
Mar 22, 2010, 3:30:02 PM3/22/10
to

Because you do not enable/handle it. CEVTIND_EPI seems to be missing:

http://lxr.linux.no/#linux+v2.6.33/drivers/net/can/sja1000/sja1000.c#L403

Ira W. Snyder

unread,
Mar 22, 2010, 4:20:02 PM3/22/10
to

Re-reading the documentation, it appears that the firmware uses the
error interrupt for two different indications. In the SJA1000 driver,
they map to IRQ_EI and IRQ_EPI.

The documentation says that you can tell when you get an error-passive
only by checking the rxerr + txerr registers in the message. You'll note
I omitted the IRQ_EPI-equivalent code from my driver when I copied the
sja1000.c implementation.

I've added an if-statement in the CEVTIND_EI path, which now looks like
this. It handles both cases now.

/* error warning interrupt */

if (isrc == CEVTIND_EI) {


u8 rxerr = msg->data[4];

u8 txerr = msg->data[5];

dev_dbg(mod->dev, "error warning interrupt\n");
if (status & SR_BS) {
state = CAN_STATE_BUS_OFF;
cf->can_id |= CAN_ERR_BUSOFF;
can_bus_off(dev);


} else if (status & SR_ES) {

if (rxerr >= 127 || txerr >= 127)
state = CAN_STATE_ERROR_PASSIVE;
else
state = CAN_STATE_ERROR_WARNING;
} else {
state = CAN_STATE_ERROR_ACTIVE;
}
}

The only change is in the "else if (status & SR_ES)" path. I had to add
the if-statement that checks the rxerr and txerr registers. Does that
seem ok? I got the 127 values from this webpage (provided to me on this
mailing list).

http://www.softing.com/home/en/industrial-automation/products/can-bus/more-can-bus/error-handling/error-states.php?navanchor=3010510

> > bitrate 1000000 sample-point 0.750
> > tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
> > janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
> > clock 8000000
> > re-started bus-errors arbit-lost error-warn error-pass bus-off
> > 0 0 0 1 0 0
> > RX: bytes packets errors dropped overrun mcast
> > 16 0 2 0 0 0
> > TX: bytes packets errors dropped carrier collsns
> > 513 513 0 0 0 0
> > 6: can1: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
> > link/can
> > can <BERR-REPORTING> state ERROR-WARNING (berr-counter tx 128 rx 0) restart-ms 0
> > bitrate 1000000 sample-point 0.750
> > tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
> > janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
> > clock 8000000
> > re-started bus-errors arbit-lost error-warn error-pass bus-off
> > 0 126 0 1 0 0
>
> But that's mabe because you stopped the test too early (just 126 bus errors).
>

This is the best I could do. Without the cable connected, that's where
the controller stops sending messages (cansequence just hangs waiting
for buffer space to become available).

With my newest changes, I get:

8: can1: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN qlen 10
link/can
can state ERROR-PASSIVE (berr-counter tx 128 rx 0) restart-ms 0

bitrate 1000000 sample-point 0.750
tq 125 prop-seg 2 phase-seg1 3 phase-seg2 2 sjw 1
janz-ican3: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off

0 0 0 3 3 0

RX: bytes packets errors dropped overrun mcast

236045 235949 12 0 0 0

TX: bytes packets errors dropped carrier collsns

235938 235938 0 0 0 0

can1 20000004 [8] 00 08 00 00 00 00 60 00 ERRORFRAME
can1 20000004 [8] 00 20 00 00 00 00 80 00 ERRORFRAME

So it looks like both drivers agree (finally!). :)

With berr-reporting on, I get the same flood of bus-error messages, with
these two messages as well.

>
> The patch I mentioned also copies the rx and tx error counter values to
> the data field 6 and 7.
>

I missed this. It has been added. Thanks for pointing it out.

I haven't heard back from Samuel Ortiz yet about the changes for the mfd
layer. Would you like me to send out my latest CAN driver changes, or
should I just wait until I hear back?

Ira

Ira W. Snyder

unread,
Mar 22, 2010, 4:20:03 PM3/22/10
to

See the message I just sent. In short, the firmware coalesces the IRQ_EI
and IRQ_EPI messages into CEVTIND_EI. You can only tell them apart via


the rxerr and txerr registers.

Ira

Wolfgang Grandegger

unread,
Mar 22, 2010, 4:40:01 PM3/22/10
to

It should be >= 128.

Looks good now.

>> The patch I mentioned also copies the rx and tx error counter values to
>> the data field 6 and 7.
>>
>
> I missed this. It has been added. Thanks for pointing it out.

You could even add the tx/rx values for each error message (for both,
state changes and bus-errors).

> I haven't heard back from Samuel Ortiz yet about the changes for the mfd
> layer. Would you like me to send out my latest CAN driver changes, or
> should I just wait until I hear back?

As you need patch 1/3 anyway, just wait some more time. From my point of
view the next version of the patch will be OK.

Wolfgang.

Ira W. Snyder

unread,
Mar 22, 2010, 5:00:02 PM3/22/10
to
On Mon, Mar 22, 2010 at 09:28:25PM +0100, Wolfgang Grandegger wrote:

[ big snip ]

>
> You could even add the tx/rx values for each error message (for both,
> state changes and bus-errors).
>

Ok, with that change, I get the following:

berr-reporting on:

can0 20000088 [8] 00 00 80 19 00 00 08 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 10 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 18 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 20 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 28 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 30 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 38 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 40 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 48 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 50 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 58 00 ERRORFRAME


can0 20000004 [8] 00 08 00 00 00 00 60 00 ERRORFRAME

can0 20000088 [8] 00 00 80 19 00 00 60 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 68 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 70 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 78 00 ERRORFRAME


can0 20000004 [8] 00 20 00 00 00 00 80 00 ERRORFRAME

can0 20000088 [8] 00 00 80 19 00 00 80 00 ERRORFRAME
can0 20000088 [8] 00 00 80 19 00 00 80 00 ERRORFRAME

And now lots more of this last frame repeated, until the controller
decides to stop. Seems fine. It has always done this.

berr-reporting off:

can1 20000004 [8] 00 08 00 00 00 00 60 00 ERRORFRAME
can1 20000004 [8] 00 20 00 00 00 00 80 00 ERRORFRAME


Same as before. Excellent.

> > I haven't heard back from Samuel Ortiz yet about the changes for the mfd
> > layer. Would you like me to send out my latest CAN driver changes, or
> > should I just wait until I hear back?
>
> As you need patch 1/3 anyway, just wait some more time. From my point of
> view the next version of the patch will be OK.
>

Ok, I'll wait a few more days before pinging him again. He's CC'd on all
of these emails anyway. :)

Thanks for all the help,
Ira

Wolfgang Grandegger

unread,
Mar 22, 2010, 5:30:02 PM3/22/10
to

Yes, below is some more theory from the AT91 CAN manual, in case you are
interested in technical details.

Wolfgang.

-----------------------------------------------------------------------
o REC: Receive Error Counter
When a receiver detects an error, REC will be increased by one, except
when the detected error is a BIT ERROR while sending an ACTIVE ERROR
FLAG or an OVERLOAD FLAG. When a receiver detects a dominant bit as
the first bit after sending an ERROR FLAG, REC is increased by 8.
When a receiver detects a BIT ERROR while sending an ACTIVE ERROR
FLAG, REC is increased by 8. Any node tolerates up to 7 consecutive
dominant bits after sending an ACTIVE ERROR FLAG, PASSIVE ERROR FLAG
or OVERLOAD FLAG. After detecting the 14th consecutive dominant bit
(in case of an ACTIVE ERROR FLAG or an OVER-LOAD FLAG) or after
detecting the 8th consecutive dominant bit following a PASSIVE ERROR
FLAG, and after each sequence of additional eight consecutive dominant
bits, each receiver increases its REC by 8. After successful reception
of a message, REC is decreased by 1 if it was between 1 and 127. If
REC was 0, it stays 0, and if it was greater than 127, then it is set
to a value between 119 and 127.

o TEC: Transmit Error Counter
When a transmitter sends an ERROR FLAG, TEC is increased by 8 except
when:
- the transmitter is error passive and detects an ACKNOWLEDGMENT ERROR
because of not detecting a dominant ACK and does not detect a
dominant bit while sending its PASSIVE ERROR FLAG.
- the transmitter sends an ERROR FLAG because a STUFF ERROR occurred
during arbitration and should have been recessive and has been sent
as recessive but monitored as dominant.
When a transmitter detects a BIT ERROR while sending an ACTIVE ERROR
FLAG or an OVERLOAD FLAG, the TEC will be increased by 8.
Any node tolerates up to 7 consecutive dominant bits after sending an
ACTIVE ERROR FLAG, PASSIVE ERROR FLAG or OVERLOAD FLAG. After
detecting the 14th consecutive dominant bit (in case of an ACTIVE
ERROR FLAG or an OVERLOAD FLAG) or after detecting the 8th consecutive
dominant bit following a PASSIVE ERROR FLAG, and after each
sequence of additional eight consecutive dominant bits every
transmitter increases its TEC by 8. After a successful transmission
the TEC is decreased by 1 unless it was already 0.

Samuel Ortiz

unread,
Mar 25, 2010, 7:00:03 PM3/25/10
to
Hi Ira,

First of all, sorry for the late reply. Then my answers:

On Fri, Mar 19, 2010 at 11:22:09AM -0700, Ira W. Snyder wrote:
>
> > > +/*

> > > + * Subdevice Support
> > > + */
> > Please use the mfd-core API for building and registering platform sub devices.
> > The pieces of code below should shrink significantly.
> >
>
> Using this framework, how is it possible to create the devices that I
> do down below. For each subdevice, I need three resources:
>
> 1) MODULbus registers -- PCI BAR3 + (0x200 * module_num)
> 2) PLX Control Registers -- PCI BAR4
> 3) IRQ
>
> Specifically, the way IORESOURCE_MEM resources are copied seems wrong.
> They start at the base address of only one resource and use the offsets
> provided in the struct mfd_cell. See the if-statement at
> drivers/mfd/mfd-core.c line 48.
>
> I need two use two different parent resources. The mfd_add_devices()
> function doesn't support this.

I would still like you to use the mfd-core API. Here is my proposal:

1) I modify mfd_add_device() to support a NULL mem_base argument. When
mem_base is NULL, we would have:

res[r].parent = NULL and res[r].start = cell->resources[r].start;

The platform code will use iomem_resource as the parent for this resource.

2) Your mfd_cell cells would have 3 resources, and you just need to set the
IORESOURCE_MEM ones at probe time, with pci->resource[n]->start + offset as
the start field.

Would that make sense to you ?

> > > + /* Onboard configuration registers */
> > > + priv->ctrl = pci_ioremap_bar(dev, 4);
> > Why 4 ?
> >
> >
>
> Because that is how the device works ;) There is a comment up above that
> describes them as the "PLX control registers". Are you suggesting that I
> add a comment here too?

No, that's ok, I missed the comment.

> > > +#define PCI_VENDOR_ID_JANZ 0x13c3
> > That probably belongs to pci_ids.h
> >
>
> Should I add a patch to the series for this?

Either that or merge the pci_ids.h changes with this patch.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

Ira W. Snyder

unread,
Mar 25, 2010, 7:30:02 PM3/25/10
to

I don't know the implications of using iomem_resource as the parent
resource. If you think it is ok, I have no objections.

If it helps, I can provide the PCI resource as a parent resource in my
resources. Then, when mem_base is NULL, the mfd-core could do this:

res[r].parent = cell->resources[r].parent

This is basically what I did in my patch. I used the PCI resource as the
parent of all child resources. I don't know if that is safe, but it
works. :)

The mfd_add_device() function does this for IORESOURCE_IO resources. It
only tries to be smart for IORESOURCE_MEM and IORESOURCE_IRQ resources.

> 2) Your mfd_cell cells would have 3 resources, and you just need to set the
> IORESOURCE_MEM ones at probe time, with pci->resource[n]->start + offset as
> the start field.
>
> Would that make sense to you ?
>

Yep, that makes sense.

> > > > + /* Onboard configuration registers */
> > > > + priv->ctrl = pci_ioremap_bar(dev, 4);
> > > Why 4 ?
> > >
> > >
> >
> > Because that is how the device works ;) There is a comment up above that
> > describes them as the "PLX control registers". Are you suggesting that I
> > add a comment here too?
> No, that's ok, I missed the comment.
>
> > > > +#define PCI_VENDOR_ID_JANZ 0x13c3
> > > That probably belongs to pci_ids.h
> > >
> >
> > Should I add a patch to the series for this?
> Either that or merge the pci_ids.h changes with this patch.
>

I guess it is a trivial enough change to merge with this patch.

I'll wait for your patch to the mfd-core API before making changes and
sending out the next round of updates.

Thanks for replying,
Ira

Samuel Ortiz

unread,
Mar 25, 2010, 8:30:02 PM3/25/10
to
I pushed an mfd-core change that basically falls back to the default resource
copying when mem_base is NULL. That should allow you to use the API now.

> > > > > +#define PCI_VENDOR_ID_JANZ 0x13c3
> > > > That probably belongs to pci_ids.h
> > > >
> > >
> > > Should I add a patch to the series for this?
> > Either that or merge the pci_ids.h changes with this patch.
> >
>
> I guess it is a trivial enough change to merge with this patch.
>
> I'll wait for your patch to the mfd-core API before making changes and
> sending out the next round of updates.

Very nice. The above mentioned change in my for-next branch, commit
6802a325f541bbea3168cf61ba239443193e1f9a.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

Ira W. Snyder

unread,
Mar 29, 2010, 1:00:02 PM3/29/10
to
This patch series adds support for the Janz CMOD-IO carrier board, as well
as the Janz VMOD-ICAN3 Intelligent CAN controller and the Janz VMOD-TTL
Digital IO controller. The CMOD-IO carrier board is a PCI to MODULbus
bridge, into which plug MODULbus daughterboards. I only have access to two
types of daughtercards, the VMOD-ICAN3 and VMOD-TTL boards mentioned above.

The CAN driver has been tested under high loads. I am able to generate ~60%
bus utilization. With two VMOD-ICAN3 boards looped back to each other,
neither one loses any packets when only a single board is generating
packets at maximum speed. Once both boards start generating packets, one
board will sometimes loose arbitration, and cause some lost packets.

After much review from the last posting of the series, I believe that this
should be ready for mainline. Many thanks to everyone that helped!

RFCv4 -> RFCv5:
- removed some dev_dbg() statements
- change default MODULbus modules to "none"
- accept both "empty" and "" in the MODULbus modules parameter
- print an error message when no MODULbus modules are specified
- add sysfs support to get/set CAN bus termination
- fix error-active and error-passive state reporting
- add support for netlink bus-error messages
- fixed sparse warnings
- used mfd-core API for multifunction device probing

RFCv3 -> RFCv4:
- addressed many review comments
- switch to NAPI
- add TX flow control
- mark functions with __devinit and __devexit
- add sysfs readout of MODULbus number (hex switch)
- implement GPIO driver for VMOD-TTL

RFCv2 -> RFCv3:
- addressed many review comments
- correct CAN bus error handling
- use struct device to track subdevices
- use structures for register layout
- add lots of #defines for register values
- use better function prefixes

RFCv1 -> RFCv2:
- converted to a multi-driver model
- addressed many review comments
- added CAN bus error handling
- use a work function only instead of work + NAPI
- use SJA1000 bittiming calculation code

Thanks,
Ira

Ira W. Snyder

unread,
Mar 29, 2010, 1:00:02 PM3/29/10
to
The Janz CMOD-IO PCI MODULbus carrier board is a PCI to MODULbus bridge,
which may host many different types of MODULbus daughterboards, including
CAN and GPIO controllers.

Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>
Reviewed-by: Wolfgang Grandegger <w...@grandegger.com>
---
drivers/mfd/Kconfig | 10 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/janz-cmodio.c | 302 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/janz.h | 54 ++++++++
include/linux/pci_ids.h | 2 +
5 files changed, 369 insertions(+), 0 deletions(-)


create mode 100644 drivers/mfd/janz-cmodio.c
create mode 100644 include/linux/mfd/janz.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 02fcd09..41329b4 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -408,6 +408,16 @@ config MFD_RDC321X
southbridge which provides access to GPIOs and Watchdog using the
southbridge PCI device configuration space.



+config MFD_JANZ_CMODIO
+ tristate "Support for Janz CMOD-IO PCI MODULbus Carrier Board"

+ select MFD_CORE
+ depends on PCI
+ help


+ This is the core driver for the Janz CMOD-IO PCI MODULbus
+ carrier board. This device is a PCI to MODULbus bridge which may
+ host many different types of MODULbus daughterboards, including
+ CAN and GPIO controllers.
+

endmenu

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index f5daffe..42a35e4 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -64,3 +64,4 @@ obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
+obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
diff --git a/drivers/mfd/janz-cmodio.c b/drivers/mfd/janz-cmodio.c
new file mode 100644
index 0000000..ac7d59b
--- /dev/null
+++ b/drivers/mfd/janz-cmodio.c
@@ -0,0 +1,302 @@


+/*
+ * Janz CMOD-IO MODULbus Carrier Board PCI Driver
+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *
+ * Lots of inspiration and code was copied from drivers/mfd/sm501.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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>

+#include <linux/mfd/core.h>


+
+#include <linux/mfd/janz.h>
+
+#define DRV_NAME "janz-cmodio"
+
+/* Size of each MODULbus module in PCI BAR4 */
+#define CMODIO_MODULBUS_SIZE 0x200
+
+/* Maximum number of MODULbus modules on a CMOD-IO carrier board */
+#define CMODIO_MAX_MODULES 4
+

+/* Module Parameters */
+static unsigned int num_modules = CMODIO_MAX_MODULES;
+static unsigned char *modules[CMODIO_MAX_MODULES] = {

+ "empty", "empty", "empty", "empty",
+};
+
+module_param_array(modules, charp, &num_modules, S_IRUGO);
+MODULE_PARM_DESC(modules, "MODULbus modules attached to the carrier board");
+
+/* Unique Device Id */
+static unsigned int cmodio_id;
+
+struct cmodio_device {
+ /* Parent PCI device */
+ struct pci_dev *pdev;
+
+ /* PLX control registers */


+ struct janz_cmodio_onboard_regs __iomem *ctrl;
+

+ /* hex switch position */
+ u8 hex;
+
+ /* mfd-core API */
+ struct mfd_cell cells[CMODIO_MAX_MODULES];
+ struct resource resources[3 * CMODIO_MAX_MODULES];
+ struct janz_platform_data pdata[CMODIO_MAX_MODULES];
+};
+
+/*
+ * Subdevices using the mfd-core API
+ */
+
+static int __devinit cmodio_setup_subdevice(struct cmodio_device *priv,
+ char *name, unsigned int devno,
+ unsigned int modno)


+{
+ struct janz_platform_data *pdata;

+ struct mfd_cell *cell;
+ struct resource *res;


+ struct pci_dev *pci;
+

+ pci = priv->pdev;

+ cell = &priv->cells[devno];
+ res = &priv->resources[devno * 3];
+ pdata = &priv->pdata[devno];
+
+ cell->name = name;
+ cell->resources = res;
+ cell->num_resources = 3;
+
+ /* Setup the subdevice ID -- must be unique */
+ cell->id = cmodio_id++;
+
+ /* Add platform data */


+ pdata->modno = modno;

+ cell->platform_data = pdata;
+ cell->data_size = sizeof(*pdata);


+
+ /* MODULbus registers -- PCI BAR3 is big-endian MODULbus access */

+ res->flags = IORESOURCE_MEM;

+ res->parent = &pci->resource[3];
+ res->start = pci->resource[3].start + (CMODIO_MODULBUS_SIZE * modno);
+ res->end = res->start + CMODIO_MODULBUS_SIZE - 1;
+ res++;


+
+ /* PLX Control Registers -- PCI BAR4 is interrupt and other registers */

+ res->flags = IORESOURCE_MEM;

+ res->parent = &pci->resource[4];
+ res->start = pci->resource[4].start;
+ res->end = pci->resource[4].end;
+ res++;
+
+ /*
+ * IRQ
+ *
+ * The start and end fields are used as an offset to the irq_base
+ * parameter passed into the mfd_add_devices() function call. All
+ * devices share the same IRQ.
+ */


+ res->flags = IORESOURCE_IRQ;
+ res->parent = NULL;

+ res->start = 0;
+ res->end = 0;
+ res++;


+
+ return 0;
+}

+
+/* Probe each submodule using kernel parameters */
+static int __devinit cmodio_probe_submodules(struct cmodio_device *priv)
+{

+ struct pci_dev *pdev = priv->pdev;
+ unsigned int num_probed = 0;


+ char *name;
+ int i;
+
+ for (i = 0; i < num_modules; i++) {
+ name = modules[i];

+ if (!strcmp(name, "") || !strcmp(name, "empty"))


+ continue;
+
+ dev_dbg(&priv->pdev->dev, "MODULbus %d: name %s\n", i, name);

+ cmodio_setup_subdevice(priv, name, num_probed, i);
+ num_probed++;
+ }
+
+ /* print an error message if no modules were probed */
+ if (num_probed == 0) {
+ dev_err(&priv->pdev->dev, "no MODULbus modules specified, "
+ "please set the ``modules'' kernel "
+ "parameter according to your "
+ "hardware configuration\n");


+ return -ENODEV;
+ }
+

+ return mfd_add_devices(&pdev->dev, 0, priv->cells,
+ num_probed, NULL, pdev->irq);
+}
+
+/*
+ * SYSFS Attributes
+ */
+
+static ssize_t mbus_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct cmodio_device *priv = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", priv->hex);
+}
+
+static DEVICE_ATTR(modulbus_number, S_IRUGO, mbus_show, NULL);
+
+static struct attribute *cmodio_sysfs_attrs[] = {
+ &dev_attr_modulbus_number.attr,
+ NULL,
+};
+
+static const struct attribute_group cmodio_sysfs_attr_group = {
+ .attrs = cmodio_sysfs_attrs,
+};

+ /* Onboard configuration registers */
+ priv->ctrl = pci_ioremap_bar(dev, 4);

+ if (!priv->ctrl) {
+ dev_err(&dev->dev, "unable to remap onboard regs\n");
+ ret = -ENOMEM;
+ goto out_pci_release_regions;
+ }
+
+ /* Read the hex switch on the carrier board */
+ priv->hex = ioread8(&priv->ctrl->int_enable);
+
+ /* Add the MODULbus number (hex switch value) to the device's sysfs */
+ ret = sysfs_create_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+ if (ret) {
+ dev_err(&dev->dev, "unable to create sysfs attributes\n");
+ goto out_unmap_ctrl;
+ }
+
+ /*
+ * Disable all interrupt lines, each submodule will enable its
+ * own interrupt line if needed
+ */
+ iowrite8(0xf, &priv->ctrl->int_disable);
+
+ /* Register drivers for all submodules */
+ ret = cmodio_probe_submodules(priv);
+ if (ret) {
+ dev_err(&dev->dev, "unable to probe submodules\n");
+ goto out_sysfs_remove_group;


+ }
+
+ return 0;
+

+out_sysfs_remove_group:
+ sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+out_unmap_ctrl:
+ iounmap(priv->ctrl);
+out_pci_release_regions:
+ pci_release_regions(dev);
+out_pci_disable_device:
+ pci_disable_device(dev);
+out_free_priv:
+ kfree(priv);


+out_return:
+ return ret;
+}
+

+static void __devexit cmodio_pci_remove(struct pci_dev *dev)
+{
+ struct cmodio_device *priv = pci_get_drvdata(dev);
+
+ mfd_remove_devices(&dev->dev);
+ sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+ iounmap(priv->ctrl);
+ pci_release_regions(dev);
+ pci_disable_device(dev);
+ kfree(priv);
+}
+
+/* The list of devices that this module will support */
+static DEFINE_PCI_DEVICE_TABLE(cmodio_pci_ids) = {
+ { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9030, PCI_VENDOR_ID_JANZ, 0x0101 },
+ { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_JANZ, 0x0100 },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, cmodio_pci_ids);
+
+static struct pci_driver cmodio_pci_driver = {
+ .name = DRV_NAME,
+ .id_table = cmodio_pci_ids,
+ .probe = cmodio_pci_probe,
+ .remove = __devexit_p(cmodio_pci_remove),
+};
+
+/*
+ * Module Init / Exit
+ */
+
+static int __init cmodio_init(void)
+{
+ return pci_register_driver(&cmodio_pci_driver);
+}
+
+static void __exit cmodio_exit(void)
+{
+ pci_unregister_driver(&cmodio_pci_driver);
+}
+
+MODULE_AUTHOR("Ira W. Snyder <i...@ovro.caltech.edu>");
+MODULE_DESCRIPTION("Janz CMOD-IO PCI MODULbus Carrier Board Driver");
+MODULE_LICENSE("GPL");
+
+module_init(cmodio_init);
+module_exit(cmodio_exit);
diff --git a/include/linux/mfd/janz.h b/include/linux/mfd/janz.h
new file mode 100644
index 0000000..e9994c4
--- /dev/null
+++ b/include/linux/mfd/janz.h
@@ -0,0 +1,54 @@
+/*
+ * Common Definitions for Janz MODULbus devices


+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *

+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+

+#ifndef JANZ_H
+#define JANZ_H
+
+struct janz_platform_data {
+ /* MODULbus Module Number */
+ unsigned int modno;
+};
+
+/* PLX bridge chip onboard registers */
+struct janz_cmodio_onboard_regs {
+ u8 unused1;
+
+ /*
+ * Read access: interrupt status
+ * Write access: interrupt disable
+ */
+ u8 int_disable;
+ u8 unused2;
+
+ /*
+ * Read access: MODULbus number (hex switch)
+ * Write access: interrupt enable
+ */
+ u8 int_enable;
+ u8 unused3;
+
+ /* write-only */
+ u8 reset_assert;
+ u8 unused4;
+
+ /* write-only */
+ u8 reset_deassert;
+ u8 unused5;
+
+ /* read-write access to serial EEPROM */
+ u8 eep;
+ u8 unused6;
+
+ /* write-only access to EEPROM chip select */
+ u8 enid;
+};
+
+#endif /* JANZ_H */
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 9f688d2..5b42bb2 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -2718,3 +2718,5 @@
#define PCI_DEVICE_ID_RME_DIGI32 0x9896
#define PCI_DEVICE_ID_RME_DIGI32_PRO 0x9897
#define PCI_DEVICE_ID_RME_DIGI32_8 0x9898
+
+#define PCI_VENDOR_ID_JANZ 0x13c3
--
1.5.4.3

Ira W. Snyder

unread,
Mar 29, 2010, 1:10:02 PM3/29/10
to
The Janz VMOD-ICAN3 is a MODULbus daughterboard which fits onto any
MODULbus carrier board. It is an intelligent CAN controller with a
microcontroller and associated firmware.

Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>


---
drivers/net/can/Kconfig | 10 +
drivers/net/can/Makefile | 1 +

drivers/net/can/janz-ican3.c | 1830 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1841 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/can/janz-ican3.c

new file mode 100644
index 0000000..6e533dc
--- /dev/null
+++ b/drivers/net/can/janz-ican3.c
@@ -0,0 +1,1830 @@
+/*
+ * Janz MODULbus VMOD-ICAN3 CAN Interface Driver


+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *

+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>

+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+

+#include <linux/netdevice.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+
+#include <linux/mfd/janz.h>
+
+/* the DPM has 64k of memory, organized into 256x 256 byte pages */
+#define DPM_NUM_PAGES 256
+#define DPM_PAGE_SIZE 256
+#define DPM_PAGE_ADDR(p) ((p) * DPM_PAGE_SIZE)
+
+/* JANZ ICAN3 "old-style" host interface queue page numbers */
+#define QUEUE_OLD_CONTROL 0
+#define QUEUE_OLD_RB0 1
+#define QUEUE_OLD_RB1 2
+#define QUEUE_OLD_WB0 3
+#define QUEUE_OLD_WB1 4
+
+/* Janz ICAN3 "old-style" host interface control registers */
+#define MSYNC_PEER 0x00 /* ICAN only */
+#define MSYNC_LOCL 0x01 /* host only */
+#define TARGET_RUNNING 0x02
+
+#define MSYNC_RB0 0x01
+#define MSYNC_RB1 0x02
+#define MSYNC_RBLW 0x04
+#define MSYNC_RB_MASK (MSYNC_RB0 | MSYNC_RB1)
+
+#define MSYNC_WB0 0x10
+#define MSYNC_WB1 0x20
+#define MSYNC_WBLW 0x40
+#define MSYNC_WB_MASK (MSYNC_WB0 | MSYNC_WB1)
+
+/* Janz ICAN3 "new-style" host interface queue page numbers */
+#define QUEUE_TOHOST 5
+#define QUEUE_FROMHOST_MID 6
+#define QUEUE_FROMHOST_HIGH 7
+#define QUEUE_FROMHOST_LOW 8
+
+/* The first free page in the DPM is #9 */
+#define DPM_FREE_START 9
+
+/* Janz ICAN3 "new-style" and "fast" host interface descriptor flags */
+#define DESC_VALID 0x80
+#define DESC_WRAP 0x40
+#define DESC_INTERRUPT 0x20
+#define DESC_IVALID 0x10
+#define DESC_LEN(len) (len)
+
+/* Janz ICAN3 Firmware Messages */
+#define MSG_CONNECTI 0x02
+#define MSG_DISCONNECT 0x03
+#define MSG_IDVERS 0x04
+#define MSG_MSGLOST 0x05
+#define MSG_NEWHOSTIF 0x08
+#define MSG_INQUIRY 0x0a
+#define MSG_SETAFILMASK 0x10
+#define MSG_INITFDPMQUEUE 0x11
+#define MSG_HWCONF 0x12
+#define MSG_FMSGLOST 0x15
+#define MSG_CEVTIND 0x37
+#define MSG_CBTRREQ 0x41
+#define MSG_COFFREQ 0x42
+#define MSG_CONREQ 0x43
+#define MSG_CCONFREQ 0x47
+
+/*
+ * Janz ICAN3 CAN Inquiry Message Types
+ *
+ * NOTE: there appears to be a firmware bug here. You must send
+ * NOTE: INQUIRY_STATUS and expect to receive an INQUIRY_EXTENDED
+ * NOTE: response. The controller never responds to a message with
+ * NOTE: the INQUIRY_EXTENDED subspec :(
+ */
+#define INQUIRY_STATUS 0x00
+#define INQUIRY_TERMINATION 0x01
+#define INQUIRY_EXTENDED 0x04
+
+/* Janz ICAN3 CAN Set Acceptance Filter Mask Message Types */
+#define SETAFILMASK_REJECT 0x00
+#define SETAFILMASK_FASTIF 0x02
+
+/* Janz ICAN3 CAN Hardware Configuration Message Types */
+#define HWCONF_TERMINATE_ON 0x01
+#define HWCONF_TERMINATE_OFF 0x00
+
+/* Janz ICAN3 CAN Event Indication Message Types */
+#define CEVTIND_EI 0x01
+#define CEVTIND_DOI 0x02
+#define CEVTIND_LOST 0x04
+#define CEVTIND_FULL 0x08
+#define CEVTIND_BEI 0x10
+
+#define CEVTIND_CHIP_SJA1000 0x02
+
+#define ICAN3_BUSERR_QUOTA_MAX 255
+
+/* Janz ICAN3 CAN Frame Conversion */
+#define ICAN3_ECHO 0x10
+#define ICAN3_EFF_RTR 0x40
+#define ICAN3_SFF_RTR 0x10
+#define ICAN3_EFF 0x80
+
+#define ICAN3_CAN_TYPE_MASK 0x0f
+#define ICAN3_CAN_TYPE_SFF 0x00
+#define ICAN3_CAN_TYPE_EFF 0x01
+
+#define ICAN3_CAN_DLC_MASK 0x0f
+
+/*
+ * SJA1000 Status and Error Register Definitions
+ *
+ * Copied from drivers/net/can/sja1000/sja1000.h
+ */
+
+/* status register content */
+#define SR_BS 0x80
+#define SR_ES 0x40
+#define SR_TS 0x20
+#define SR_RS 0x10
+#define SR_TCS 0x08
+#define SR_TBS 0x04
+#define SR_DOS 0x02
+#define SR_RBS 0x01
+
+#define SR_CRIT (SR_BS|SR_ES)
+
+/* ECC register */
+#define ECC_SEG 0x1F
+#define ECC_DIR 0x20
+#define ECC_ERR 6
+#define ECC_BIT 0x00
+#define ECC_FORM 0x40
+#define ECC_STUFF 0x80
+#define ECC_MASK 0xc0
+
+/* Number of buffers for use in the "new-style" host interface */
+#define ICAN3_NEW_BUFFERS 16
+
+/* Number of buffers for use in the "fast" host interface */
+#define ICAN3_TX_BUFFERS 512
+#define ICAN3_RX_BUFFERS 1024
+
+/* SJA1000 Clock Input */
+#define ICAN3_CAN_CLOCK 8000000
+
+/* Driver Name */
+#define DRV_NAME "janz-ican3"
+
+/* DPM Control Registers -- starts at offset 0x100 in the MODULbus registers */
+struct ican3_dpm_control {
+ /* window address register */
+ u8 window_address;


+ u8 unused1;
+
+ /*

+ * Read access: clear interrupt from microcontroller
+ * Write access: send interrupt to microcontroller
+ */
+ u8 interrupt;
+ u8 unused2;
+
+ /* write-only: reset all hardware on the module */
+ u8 hwreset;
+ u8 unused3;
+
+ /* write-only: generate an interrupt to the TPU */
+ u8 tpuinterrupt;
+};
+


+struct ican3_dev {
+
+ /* must be the first member */
+ struct can_priv can;
+
+ /* CAN network device */
+ struct net_device *ndev;
+ struct napi_struct napi;
+
+ /* Device for printing */
+ struct device *dev;
+
+ /* module number */
+ unsigned int num;
+

+ /* base address of registers and IRQ */


+ struct janz_cmodio_onboard_regs __iomem *ctrl;

+ struct ican3_dpm_control __iomem *dpmctrl;


+ void __iomem *dpm;
+ int irq;
+

+ /* CAN bus termination status */
+ struct completion termination_comp;
+ bool termination_enabled;
+
+ /* CAN bus error status registers */
+ struct completion buserror_comp;
+ struct can_berr_counter bec;


+
+ /* old and new style host interface */
+ unsigned int iftype;

+
+ /*
+ * Any function which changes the current DPM page must hold this
+ * lock while it is performing data accesses. This ensures that the
+ * function will not be preempted and end up reading data from a
+ * different DPM page than it expects.
+ */
+ spinlock_t lock;
+


+ /* new host interface */
+ unsigned int rx_int;
+ unsigned int rx_num;
+ unsigned int tx_num;
+
+ /* fast host interface */
+ unsigned int fastrx_start;
+ unsigned int fastrx_int;
+ unsigned int fastrx_num;
+ unsigned int fasttx_start;
+ unsigned int fasttx_num;
+
+ /* first free DPM page */
+ unsigned int free_page;
+};

+
+struct ican3_msg {
+ u8 control;
+ u8 spec;
+ __le16 len;
+ u8 data[252];
+};
+
+struct ican3_new_desc {
+ u8 control;
+ u8 pointer;
+};
+
+struct ican3_fast_desc {
+ u8 control;
+ u8 command;
+ u8 data[14];
+};
+
+/* write to the window basic address register */
+static inline void ican3_set_page(struct ican3_dev *mod, unsigned int page)
+{
+ BUG_ON(page >= DPM_NUM_PAGES);
+ iowrite8(page, &mod->dpmctrl->window_address);
+}
+
+/*
+ * ICAN3 "old-style" host interface
+ */
+
+/*
+ * Recieve a message from the ICAN3 "old-style" firmware interface
+ *
+ * LOCKING: must hold mod->lock
+ *
+ * returns 0 on success, -ENOMEM when no message exists
+ */
+static int ican3_old_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned int mbox, mbox_page;
+ u8 locl, peer, xord;
+
+ /* get the MSYNC registers */
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ peer = ioread8(mod->dpm + MSYNC_PEER);
+ locl = ioread8(mod->dpm + MSYNC_LOCL);
+ xord = locl ^ peer;
+
+ if ((xord & MSYNC_RB_MASK) == 0x00) {
+ dev_dbg(mod->dev, "no mbox for reading\n");


+ return -ENOMEM;
+ }
+

+ /* find the first free mbox to read */
+ if ((xord & MSYNC_RB_MASK) == MSYNC_RB_MASK)
+ mbox = (xord & MSYNC_RBLW) ? MSYNC_RB0 : MSYNC_RB1;
+ else
+ mbox = (xord & MSYNC_RB0) ? MSYNC_RB0 : MSYNC_RB1;
+
+ /* copy the message */
+ mbox_page = (mbox == MSYNC_RB0) ? QUEUE_OLD_RB0 : QUEUE_OLD_RB1;
+ ican3_set_page(mod, mbox_page);
+ memcpy_fromio(msg, mod->dpm, sizeof(*msg));
+
+ /*
+ * notify the firmware that the read buffer is available
+ * for it to fill again
+ */
+ locl ^= mbox;
+
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ iowrite8(locl, mod->dpm + MSYNC_LOCL);


+ return 0;
+}
+
+/*

+ * Send a message through the "old-style" firmware interface
+ *
+ * LOCKING: must hold mod->lock
+ *
+ * returns 0 on success, -ENOMEM when no free space exists
+ */
+static int ican3_old_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned int mbox, mbox_page;
+ u8 locl, peer, xord;
+
+ /* get the MSYNC registers */
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ peer = ioread8(mod->dpm + MSYNC_PEER);
+ locl = ioread8(mod->dpm + MSYNC_LOCL);
+ xord = locl ^ peer;
+
+ if ((xord & MSYNC_WB_MASK) == MSYNC_WB_MASK) {
+ dev_err(mod->dev, "no mbox for writing\n");


+ return -ENOMEM;
+ }
+

+ /* calculate a free mbox to use */
+ mbox = (xord & MSYNC_WB0) ? MSYNC_WB1 : MSYNC_WB0;
+
+ /* copy the message to the DPM */
+ mbox_page = (mbox == MSYNC_WB0) ? QUEUE_OLD_WB0 : QUEUE_OLD_WB1;
+ ican3_set_page(mod, mbox_page);
+ memcpy_toio(mod->dpm, msg, sizeof(*msg));
+
+ locl ^= mbox;
+ if (mbox == MSYNC_WB1)
+ locl |= MSYNC_WBLW;
+
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ iowrite8(locl, mod->dpm + MSYNC_LOCL);


+ return 0;
+}
+
+/*

+ * ICAN3 "new-style" Host Interface Setup
+ */
+
+static void __devinit ican3_init_new_host_interface(struct ican3_dev *mod)
+{
+ struct ican3_new_desc desc;
+ unsigned long flags;
+ void __iomem *dst;
+ int i;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* setup the internal datastructures for RX */
+ mod->rx_num = 0;
+ mod->rx_int = 0;
+
+ /* tohost queue descriptors are in page 5 */
+ ican3_set_page(mod, QUEUE_TOHOST);
+ dst = mod->dpm;
+
+ /* initialize the tohost (rx) queue descriptors: pages 9-24 */
+ for (i = 0; i < ICAN3_NEW_BUFFERS; i++) {
+ desc.control = DESC_INTERRUPT | DESC_LEN(1); /* I L=1 */
+ desc.pointer = mod->free_page;
+
+ /* set wrap flag on last buffer */
+ if (i == ICAN3_NEW_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ memcpy_toio(dst, &desc, sizeof(desc));
+ dst += sizeof(desc);
+ mod->free_page++;
+ }
+
+ /* fromhost (tx) mid queue descriptors are in page 6 */
+ ican3_set_page(mod, QUEUE_FROMHOST_MID);
+ dst = mod->dpm;
+
+ /* setup the internal datastructures for TX */
+ mod->tx_num = 0;
+
+ /* initialize the fromhost mid queue descriptors: pages 25-40 */
+ for (i = 0; i < ICAN3_NEW_BUFFERS; i++) {
+ desc.control = DESC_VALID | DESC_LEN(1); /* V L=1 */
+ desc.pointer = mod->free_page;
+
+ /* set wrap flag on last buffer */
+ if (i == ICAN3_NEW_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ memcpy_toio(dst, &desc, sizeof(desc));
+ dst += sizeof(desc);
+ mod->free_page++;
+ }
+
+ /* fromhost hi queue descriptors are in page 7 */
+ ican3_set_page(mod, QUEUE_FROMHOST_HIGH);
+ dst = mod->dpm;
+
+ /* initialize only a single buffer in the fromhost hi queue (unused) */
+ desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */
+ desc.pointer = mod->free_page;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ mod->free_page++;
+
+ /* fromhost low queue descriptors are in page 8 */
+ ican3_set_page(mod, QUEUE_FROMHOST_LOW);
+ dst = mod->dpm;
+
+ /* initialize only a single buffer in the fromhost low queue (unused) */
+ desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */
+ desc.pointer = mod->free_page;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ mod->free_page++;
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+}
+
+/*
+ * ICAN3 Fast Host Interface Setup
+ */
+
+static void __devinit ican3_init_fast_host_interface(struct ican3_dev *mod)
+{
+ struct ican3_fast_desc desc;
+ unsigned long flags;
+ unsigned int addr;
+ void __iomem *dst;
+ int i;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* save the start recv page */
+ mod->fastrx_start = mod->free_page;
+ mod->fastrx_num = 0;
+ mod->fastrx_int = 0;
+
+ /* build a single fast tohost queue descriptor */
+ memset(&desc, 0, sizeof(desc));
+ desc.control = 0x00;
+ desc.command = 1;
+
+ /* build the tohost queue descriptor ring in memory */
+ addr = 0;
+ for (i = 0; i < ICAN3_RX_BUFFERS; i++) {
+
+ /* set the wrap bit on the last buffer */
+ if (i == ICAN3_RX_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ /* switch to the correct page */
+ ican3_set_page(mod, mod->free_page);
+
+ /* copy the descriptor to the DPM */
+ dst = mod->dpm + addr;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ addr += sizeof(desc);
+
+ /* move to the next page if necessary */
+ if (addr >= DPM_PAGE_SIZE) {
+ addr = 0;
+ mod->free_page++;
+ }
+ }
+
+ /* make sure we page-align the next queue */
+ if (addr != 0)
+ mod->free_page++;
+
+ /* save the start xmit page */
+ mod->fasttx_start = mod->free_page;
+ mod->fasttx_num = 0;
+
+ /* build a single fast fromhost queue descriptor */
+ memset(&desc, 0, sizeof(desc));
+ desc.control = DESC_VALID;
+ desc.command = 1;
+
+ /* build the fromhost queue descriptor ring in memory */
+ addr = 0;
+ for (i = 0; i < ICAN3_TX_BUFFERS; i++) {
+
+ /* set the wrap bit on the last buffer */
+ if (i == ICAN3_TX_BUFFERS - 1)
+ desc.control |= DESC_WRAP;
+
+ /* switch to the correct page */
+ ican3_set_page(mod, mod->free_page);
+
+ /* copy the descriptor to the DPM */
+ dst = mod->dpm + addr;
+ memcpy_toio(dst, &desc, sizeof(desc));
+ addr += sizeof(desc);
+
+ /* move to the next page if necessary */
+ if (addr >= DPM_PAGE_SIZE) {
+ addr = 0;
+ mod->free_page++;
+ }
+ }
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+}
+
+/*
+ * ICAN3 "new-style" Host Interface Message Helpers
+ */
+
+/*
+ * LOCKING: must hold mod->lock
+ */
+static int ican3_new_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ struct ican3_new_desc desc;
+ void __iomem *desc_addr = mod->dpm + (mod->tx_num * sizeof(desc));
+
+ /* switch to the fromhost mid queue, and read the buffer descriptor */
+ ican3_set_page(mod, QUEUE_FROMHOST_MID);
+ memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+ if (!(desc.control & DESC_VALID)) {
+ dev_dbg(mod->dev, "%s: no free buffers\n", __func__);


+ return -ENOMEM;
+ }
+

+ /* switch to the data page, copy the data */
+ ican3_set_page(mod, desc.pointer);
+ memcpy_toio(mod->dpm, msg, sizeof(*msg));
+
+ /* switch back to the descriptor, set the valid bit, write it back */
+ ican3_set_page(mod, QUEUE_FROMHOST_MID);
+ desc.control ^= DESC_VALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* update the tx number */
+ mod->tx_num = (desc.control & DESC_WRAP) ? 0 : (mod->tx_num + 1);


+ return 0;
+}
+
+/*

+ * LOCKING: must hold mod->lock
+ */
+static int ican3_new_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ struct ican3_new_desc desc;
+ void __iomem *desc_addr = mod->dpm + (mod->rx_num * sizeof(desc));
+
+ /* switch to the tohost queue, and read the buffer descriptor */
+ ican3_set_page(mod, QUEUE_TOHOST);
+ memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+ if (!(desc.control & DESC_VALID)) {
+ dev_dbg(mod->dev, "%s: no buffers to recv\n", __func__);


+ return -ENOMEM;
+ }
+

+ /* switch to the data page, copy the data */
+ ican3_set_page(mod, desc.pointer);
+ memcpy_fromio(msg, mod->dpm, sizeof(*msg));
+
+ /* switch back to the descriptor, toggle the valid bit, write it back */
+ ican3_set_page(mod, QUEUE_TOHOST);
+ desc.control ^= DESC_VALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* update the rx number */
+ mod->rx_num = (desc.control & DESC_WRAP) ? 0 : (mod->rx_num + 1);


+ return 0;
+}
+
+/*

+ * Message Send / Recv Helpers
+ */
+
+static int ican3_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ if (mod->iftype == 0)
+ ret = ican3_old_send_msg(mod, msg);
+ else
+ ret = ican3_new_send_msg(mod, msg);
+
+ spin_unlock_irqrestore(&mod->lock, flags);


+ return ret;
+}
+

+static int ican3_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ if (mod->iftype == 0)
+ ret = ican3_old_recv_msg(mod, msg);
+ else
+ ret = ican3_new_recv_msg(mod, msg);
+
+ spin_unlock_irqrestore(&mod->lock, flags);


+ return ret;
+}
+

+/*
+ * Quick Pre-constructed Messages
+ */
+
+static int __devinit ican3_msg_connect(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_CONNECTI;
+ msg.len = cpu_to_le16(0);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int __devexit ican3_msg_disconnect(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_DISCONNECT;
+ msg.len = cpu_to_le16(0);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int __devinit ican3_msg_newhostif(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+ int ret;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_NEWHOSTIF;
+ msg.len = cpu_to_le16(0);
+
+ /* If we're not using the old interface, switching seems bogus */
+ WARN_ON(mod->iftype != 0);
+
+ ret = ican3_send_msg(mod, &msg);
+ if (ret)
+ return ret;
+
+ /* mark the module as using the new host interface */
+ mod->iftype = 1;


+ return 0;
+}
+

+static int __devinit ican3_msg_fasthostif(struct ican3_dev *mod)
+{
+ struct ican3_msg msg;
+ unsigned int addr;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_INITFDPMQUEUE;
+ msg.len = cpu_to_le16(8);
+
+ /* write the tohost queue start address */
+ addr = DPM_PAGE_ADDR(mod->fastrx_start);
+ msg.data[0] = addr & 0xff;
+ msg.data[1] = (addr >> 8) & 0xff;
+ msg.data[2] = (addr >> 16) & 0xff;
+ msg.data[3] = (addr >> 24) & 0xff;
+
+ /* write the fromhost queue start address */
+ addr = DPM_PAGE_ADDR(mod->fasttx_start);
+ msg.data[4] = addr & 0xff;
+ msg.data[5] = (addr >> 8) & 0xff;
+ msg.data[6] = (addr >> 16) & 0xff;
+ msg.data[7] = (addr >> 24) & 0xff;
+
+ /* If we're not using the new interface yet, we cannot do this */
+ WARN_ON(mod->iftype != 1);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * Setup the CAN filter to either accept or reject all
+ * messages from the CAN bus.
+ */
+static int __devinit ican3_set_id_filter(struct ican3_dev *mod, bool accept)
+{
+ struct ican3_msg msg;
+ int ret;
+
+ /* Standard Frame Format */
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_SETAFILMASK;
+ msg.len = cpu_to_le16(5);
+ msg.data[0] = 0x00; /* IDLo LSB */
+ msg.data[1] = 0x00; /* IDLo MSB */
+ msg.data[2] = 0xff; /* IDHi LSB */
+ msg.data[3] = 0x07; /* IDHi MSB */
+
+ /* accept all frames for fast host if, or reject all frames */
+ msg.data[4] = accept ? SETAFILMASK_FASTIF : SETAFILMASK_REJECT;
+
+ ret = ican3_send_msg(mod, &msg);
+ if (ret)
+ return ret;
+
+ /* Extended Frame Format */
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_SETAFILMASK;
+ msg.len = cpu_to_le16(13);
+ msg.data[0] = 0; /* MUX = 0 */
+ msg.data[1] = 0x00; /* IDLo LSB */
+ msg.data[2] = 0x00;
+ msg.data[3] = 0x00;
+ msg.data[4] = 0x20; /* IDLo MSB */
+ msg.data[5] = 0xff; /* IDHi LSB */
+ msg.data[6] = 0xff;
+ msg.data[7] = 0xff;
+ msg.data[8] = 0x3f; /* IDHi MSB */
+
+ /* accept all frames for fast host if, or reject all frames */
+ msg.data[9] = accept ? SETAFILMASK_FASTIF : SETAFILMASK_REJECT;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * Bring the CAN bus online or offline
+ */
+static int ican3_set_bus_state(struct ican3_dev *mod, bool on)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = on ? MSG_CONREQ : MSG_COFFREQ;
+ msg.len = cpu_to_le16(0);
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_set_termination(struct ican3_dev *mod, bool on)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_HWCONF;
+ msg.len = cpu_to_le16(2);
+ msg.data[0] = 0x00;
+ msg.data[1] = on ? HWCONF_TERMINATE_ON : HWCONF_TERMINATE_OFF;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_send_inquiry(struct ican3_dev *mod, u8 subspec)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_INQUIRY;
+ msg.len = cpu_to_le16(2);
+ msg.data[0] = subspec;
+ msg.data[1] = 0x00;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_set_buserror(struct ican3_dev *mod, u8 quota)
+{
+ struct ican3_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_CCONFREQ;
+ msg.len = cpu_to_le16(2);
+ msg.data[0] = 0x00;
+ msg.data[1] = quota;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * ICAN3 to Linux CAN Frame Conversion
+ */
+


+static void ican3_to_can_frame(struct ican3_dev *mod,
+ struct ican3_fast_desc *desc,
+ struct can_frame *cf)
+{
+ if ((desc->command & ICAN3_CAN_TYPE_MASK) == ICAN3_CAN_TYPE_SFF) {

+ if (desc->data[1] & ICAN3_SFF_RTR)
+ cf->can_id |= CAN_RTR_FLAG;
+
+ cf->can_id |= desc->data[0] << 3;
+ cf->can_id |= (desc->data[1] & 0xe0) >> 5;
+ cf->can_dlc = desc->data[1] & ICAN3_CAN_DLC_MASK;
+ memcpy(cf->data, &desc->data[2], sizeof(cf->data));
+ } else {

+ desc->data[0] |= ICAN3_EFF;
+ desc->data[2] = (cf->can_id & 0x1fe00000) >> 21; /* 28-21 */
+ desc->data[3] = (cf->can_id & 0x001fe000) >> 13; /* 20-13 */
+ desc->data[4] = (cf->can_id & 0x00001fe0) >> 5; /* 12-5 */
+ desc->data[5] = (cf->can_id & 0x0000001f) << 3; /* 4-0 */
+ } else {

+ desc->data[2] = (cf->can_id & 0x7F8) >> 3; /* bits 10-3 */
+ desc->data[3] = (cf->can_id & 0x007) << 5; /* bits 2-0 */
+ }
+
+ /* copy the data bits into the descriptor */
+ memcpy(&desc->data[6], cf->data, sizeof(cf->data));

+}
+
+/*
+ * Interrupt Handling
+ */
+
+/*
+ * Handle an ID + Version message response from the firmware. We never generate
+ * this message in production code, but it is very useful when debugging to be
+ * able to display this message.
+ */
+static void ican3_handle_idvers(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ dev_dbg(mod->dev, "IDVERS response: %s\n", msg->data);
+}
+
+static void ican3_handle_msglost(struct ican3_dev *mod, struct ican3_msg *msg)


+{
+ struct net_device *dev = mod->ndev;
+ struct net_device_stats *stats = &dev->stats;

+ struct can_frame *cf;
+ struct sk_buff *skb;

+
+ /*
+ * Report that communication messages with the microcontroller firmware
+ * are being lost. These are never CAN frames, so we do not generate an
+ * error frame for userspace
+ */
+ if (msg->spec == MSG_MSGLOST) {
+ dev_err(mod->dev, "lost %d control messages\n", msg->data[0]);
+ return;
+ }
+
+ /*
+ * Oops, this indicates that we have lost messages in the fast queue,
+ * which are exclusively CAN messages. Our driver isn't reading CAN
+ * frames fast enough.
+ *
+ * We'll pretend that the SJA1000 told us that it ran out of buffer
+ * space, because there is not a better message for this.
+ */


+ skb = alloc_can_err_skb(dev, &cf);

+ if (skb) {


+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;

+ stats->rx_errors++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_rx(skb);
+ }

+}
+
+/*


+ * Handle CAN Event Indication Messages from the firmware

+ *


+ * The ICAN3 firmware provides the values of some SJA1000 registers when it
+ * generates this message. The code below is largely copied from the
+ * drivers/net/can/sja1000/sja1000.c file, and adapted as necessary
+ */
+static int ican3_handle_cevtind(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ struct net_device *dev = mod->ndev;
+ struct net_device_stats *stats = &dev->stats;
+ enum can_state state = mod->can.state;

+ u8 status, isrc, rxerr, txerr;


+ struct can_frame *cf;
+ struct sk_buff *skb;
+

+ /* we can only handle the SJA1000 part */
+ if (msg->data[1] != CEVTIND_CHIP_SJA1000) {

+ dev_err(mod->dev, "unable to handle errors on non-SJA1000\n");


+ return -ENODEV;
+ }
+

+ /* check the message length for sanity */

+ if (le16_to_cpu(msg->len) < 6) {


+ dev_err(mod->dev, "error message too short\n");
+ return -EINVAL;
+ }
+
+ skb = alloc_can_err_skb(dev, &cf);
+ if (skb == NULL)
+ return -ENOMEM;
+
+ isrc = msg->data[0];
+ status = msg->data[3];

+ rxerr = msg->data[4];
+ txerr = msg->data[5];


+
+ /* data overrun interrupt */
+ if (isrc == CEVTIND_DOI || isrc == CEVTIND_LOST) {

+ dev_dbg(mod->dev, "data overrun interrupt\n");


+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+ stats->rx_over_errors++;
+ stats->rx_errors++;
+ }

+
+ /* error warning + passive interrupt */


+ if (isrc == CEVTIND_EI) {

+ dev_dbg(mod->dev, "error warning + passive interrupt\n");


+ if (status & SR_BS) {
+ state = CAN_STATE_BUS_OFF;
+ cf->can_id |= CAN_ERR_BUSOFF;
+ can_bus_off(dev);

+ } else if (status & SR_ES) {
+ if (rxerr >= 128 || txerr >= 128)
+ state = CAN_STATE_ERROR_PASSIVE;


+ else
+ state = CAN_STATE_ERROR_WARNING;
+ } else {
+ state = CAN_STATE_ERROR_ACTIVE;
+ }
+ }
+
+ /* bus error interrupt */
+ if (isrc == CEVTIND_BEI) {
+ u8 ecc = msg->data[2];

+
+ dev_dbg(mod->dev, "bus error interrupt\n");


+ mod->can.can_stats.bus_error++;
+ stats->rx_errors++;
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+ switch (ecc & ECC_MASK) {
+ case ECC_BIT:
+ cf->data[2] |= CAN_ERR_PROT_BIT;
+ break;
+ case ECC_FORM:
+ cf->data[2] |= CAN_ERR_PROT_FORM;
+ break;
+ case ECC_STUFF:
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
+ break;
+ default:
+ cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+ cf->data[3] = ecc & ECC_SEG;
+ break;
+ }
+
+ if ((ecc & ECC_DIR) == 0)
+ cf->data[2] |= CAN_ERR_PROT_TX;
+

+ cf->data[6] = txerr;
+ cf->data[7] = rxerr;


+ }
+
+ if (state != mod->can.state && (state == CAN_STATE_ERROR_WARNING ||
+ state == CAN_STATE_ERROR_PASSIVE)) {

+ cf->can_id |= CAN_ERR_CRTL;
+ if (state == CAN_STATE_ERROR_WARNING) {
+ mod->can.can_stats.error_warning++;
+ cf->data[1] = (txerr > rxerr) ?
+ CAN_ERR_CRTL_TX_WARNING :
+ CAN_ERR_CRTL_RX_WARNING;
+ } else {
+ mod->can.can_stats.error_passive++;
+ cf->data[1] = (txerr > rxerr) ?
+ CAN_ERR_CRTL_TX_PASSIVE :
+ CAN_ERR_CRTL_RX_PASSIVE;
+ }
+

+ cf->data[6] = txerr;
+ cf->data[7] = rxerr;


+ }
+
+ mod->can.state = state;
+ stats->rx_errors++;
+ stats->rx_bytes += cf->can_dlc;
+ netif_rx(skb);

+ return 0;
+}
+

+static void ican3_handle_inquiry(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ switch (msg->data[0]) {
+ case INQUIRY_STATUS:
+ case INQUIRY_EXTENDED:
+ mod->bec.rxerr = msg->data[5];
+ mod->bec.txerr = msg->data[6];
+ complete(&mod->buserror_comp);
+ break;
+ case INQUIRY_TERMINATION:
+ mod->termination_enabled = msg->data[6] & HWCONF_TERMINATE_ON;
+ complete(&mod->termination_comp);
+ break;
+ default:
+ dev_err(mod->dev, "recieved an unknown inquiry response\n");


+ break;
+ }
+}
+

+static void ican3_handle_unknown_message(struct ican3_dev *mod,
+ struct ican3_msg *msg)
+{
+ dev_warn(mod->dev, "recieved unknown message: spec 0x%.2x length %d\n",
+ msg->spec, le16_to_cpu(msg->len));
+}
+
+/*
+ * Handle a control message from the firmware
+ */
+static void ican3_handle_message(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+ dev_dbg(mod->dev, "%s: modno %d spec 0x%.2x len %d bytes\n", __func__,
+ mod->num, msg->spec, le16_to_cpu(msg->len));
+
+ switch (msg->spec) {
+ case MSG_IDVERS:
+ ican3_handle_idvers(mod, msg);
+ break;
+ case MSG_MSGLOST:
+ case MSG_FMSGLOST:
+ ican3_handle_msglost(mod, msg);
+ break;
+ case MSG_CEVTIND:
+ ican3_handle_cevtind(mod, msg);
+ break;
+ case MSG_INQUIRY:
+ ican3_handle_inquiry(mod, msg);
+ break;
+ default:
+ ican3_handle_unknown_message(mod, msg);
+ break;
+ }
+}
+
+/*
+ * Check that there is room in the TX ring to transmit another skb
+ *
+ * LOCKING: must hold mod->lock
+ */
+static bool ican3_txok(struct ican3_dev *mod)
+{
+ struct ican3_fast_desc __iomem *desc;
+ u8 control;
+
+ /* copy the control bits of the descriptor */
+ ican3_set_page(mod, mod->fasttx_start + (mod->fasttx_num / 16));
+ desc = mod->dpm + ((mod->fasttx_num % 16) * sizeof(*desc));
+ control = ioread8(&desc->control);
+
+ /* if the control bits are not valid, then we have no more space */
+ if (!(control & DESC_VALID))
+ return false;
+
+ return true;
+}
+
+/*
+ * Recieve one CAN frame from the hardware
+ *
+ * This works like the core of a NAPI function, but is intended to be called
+ * from workqueue context instead. This driver already needs a workqueue to
+ * process control messages, so we use the workqueue instead of using NAPI.
+ * This was done to simplify locking.
+ *
+ * CONTEXT: must be called from user context
+ */
+static int ican3_recv_skb(struct ican3_dev *mod)
+{
+ struct net_device *ndev = mod->ndev;
+ struct net_device_stats *stats = &ndev->stats;
+ struct ican3_fast_desc desc;
+ void __iomem *desc_addr;


+ struct can_frame *cf;
+ struct sk_buff *skb;

+ unsigned long flags;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* copy the whole descriptor */
+ ican3_set_page(mod, mod->fastrx_start + (mod->fastrx_num / 16));
+ desc_addr = mod->dpm + ((mod->fastrx_num % 16) * sizeof(desc));
+ memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+
+ /* check that we actually have a CAN frame */
+ if (!(desc.control & DESC_VALID))
+ return -ENOBUFS;
+
+ /* allocate an skb */
+ skb = alloc_can_skb(ndev, &cf);
+ if (unlikely(skb == NULL)) {
+ stats->rx_dropped++;
+ goto err_noalloc;
+ }
+
+ /* convert the ICAN3 frame into Linux CAN format */
+ ican3_to_can_frame(mod, &desc, cf);
+
+ /* receive the skb, update statistics */
+ netif_receive_skb(skb);
+ stats->rx_packets++;


+ stats->rx_bytes += cf->can_dlc;
+

+err_noalloc:
+ /* toggle the valid bit and return the descriptor to the ring */
+ desc.control ^= DESC_VALID;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ ican3_set_page(mod, mod->fastrx_start + (mod->fastrx_num / 16));
+ memcpy_toio(desc_addr, &desc, 1);
+
+ /* update the next buffer pointer */
+ mod->fastrx_num = (desc.control & DESC_WRAP) ? 0
+ : (mod->fastrx_num + 1);
+
+ /* there are still more buffers to process */
+ spin_unlock_irqrestore(&mod->lock, flags);


+ return 0;
+}
+

+static int ican3_napi(struct napi_struct *napi, int budget)
+{
+ struct ican3_dev *mod = container_of(napi, struct ican3_dev, napi);
+ struct ican3_msg msg;
+ unsigned long flags;
+ int received = 0;
+ int ret;
+
+ /* process all communication messages */
+ while (true) {
+ ret = ican3_recv_msg(mod, &msg);
+ if (ret)
+ break;
+
+ ican3_handle_message(mod, &msg);
+ }
+
+ /* process all CAN frames from the fast interface */
+ while (received < budget) {
+ ret = ican3_recv_skb(mod);
+ if (ret)
+ break;
+
+ received++;
+ }
+
+ /* We have processed all packets that the adapter had, but it
+ * was less than our budget, stop polling */
+ if (received < budget)
+ napi_complete(napi);
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* Wake up the transmit queue if necessary */
+ if (netif_queue_stopped(mod->ndev) && ican3_txok(mod))
+ netif_wake_queue(mod->ndev);
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+
+ /* re-enable interrupt generation */


+ iowrite8(1 << mod->num, &mod->ctrl->int_enable);

+ return received;
+}
+


+static irqreturn_t ican3_irq(int irq, void *dev_id)
+{
+ struct ican3_dev *mod = dev_id;
+ u8 stat;

+
+ /*


+ * The interrupt status register on this device reports interrupts
+ * as zeroes instead of using ones like most other devices
+ */
+ stat = ioread8(&mod->ctrl->int_disable) & (1 << mod->num);
+ if (stat == (1 << mod->num))
+ return IRQ_NONE;
+

+ /* clear the MODULbus interrupt from the microcontroller */
+ ioread8(&mod->dpmctrl->interrupt);
+
+ /* disable interrupt generation, schedule the NAPI poller */
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ napi_schedule(&mod->napi);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Firmware reset, startup, and shutdown
+ */
+
+/*
+ * Reset an ICAN module to its power-on state
+ *
+ * CONTEXT: no network device registered
+ * LOCKING: work function disabled
+ */
+static int ican3_reset_module(struct ican3_dev *mod)
+{
+ u8 val = 1 << mod->num;
+ unsigned long start;
+ u8 runold, runnew;
+
+ /* disable interrupts so no more work is scheduled */
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+
+ /* flush any pending work */
+ flush_scheduled_work();
+
+ /* the first unallocated page in the DPM is #9 */


+ mod->free_page = DPM_FREE_START;
+

+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ runold = ioread8(mod->dpm + TARGET_RUNNING);
+
+ /* reset the module */
+ iowrite8(val, &mod->ctrl->reset_assert);
+ iowrite8(val, &mod->ctrl->reset_deassert);
+
+ /* wait until the module has finished resetting and is running */
+ start = jiffies;
+ do {
+ ican3_set_page(mod, QUEUE_OLD_CONTROL);
+ runnew = ioread8(mod->dpm + TARGET_RUNNING);
+ if (runnew == (runold ^ 0xff))
+ return 0;
+
+ msleep(10);
+ } while (time_before(jiffies, start + HZ / 4));
+
+ dev_err(mod->dev, "failed to reset CAN module\n");
+ return -ETIMEDOUT;
+}
+
+static void __devexit ican3_shutdown_module(struct ican3_dev *mod)
+{
+ ican3_msg_disconnect(mod);
+ ican3_reset_module(mod);
+}
+
+/*


+ * Startup an ICAN module, bringing it into fast mode
+ */
+static int __devinit ican3_startup_module(struct ican3_dev *mod)
+{

+ int ret;
+


+ ret = ican3_reset_module(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to reset module\n");

+ return ret;
+ }
+

+ /* re-enable interrupts so we can send messages */
+ iowrite8(1 << mod->num, &mod->ctrl->int_enable);
+
+ ret = ican3_msg_connect(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to connect to module\n");

+ return ret;
+ }
+

+ ican3_init_new_host_interface(mod);
+ ret = ican3_msg_newhostif(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to switch to new-style interface\n");

+ return ret;
+ }
+

+ /* default to "termination on" */


+ ret = ican3_set_termination(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to enable termination\n");

+ return ret;
+ }
+

+ /* default to "bus errors enabled" */
+ ret = ican3_set_buserror(mod, ICAN3_BUSERR_QUOTA_MAX);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-error\n");


+ return ret;
+ }
+

+ ican3_init_fast_host_interface(mod);
+ ret = ican3_msg_fasthostif(mod);
+ if (ret) {
+ dev_err(mod->dev, "unable to switch to fast host interface\n");


+ return ret;
+ }
+

+ ret = ican3_set_id_filter(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to set acceptance filter\n");


+ return ret;
+ }
+

+ return 0;
+}
+
+/*

+ * CAN Network Device
+ */
+
+static int ican3_open(struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ u8 quota;
+ int ret;
+
+ /* open the CAN layer */
+ ret = open_candev(ndev);
+ if (ret) {
+ dev_err(mod->dev, "unable to start CAN layer\n");


+ return ret;
+ }
+

+ /* set the bus error generation state appropriately */
+ if (mod->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+ quota = ICAN3_BUSERR_QUOTA_MAX;
+ else
+ quota = 0;
+
+ ret = ican3_set_buserror(mod, quota);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-error\n");
+ close_candev(ndev);


+ return ret;
+ }
+

+ /* bring the bus online */
+ ret = ican3_set_bus_state(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-on\n");
+ close_candev(ndev);


+ return ret;
+ }
+

+ /* start up the network device */
+ mod->can.state = CAN_STATE_ERROR_ACTIVE;
+ netif_start_queue(ndev);


+
+ return 0;
+}
+

+static int ican3_stop(struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ int ret;
+
+ /* stop the network device xmit routine */
+ netif_stop_queue(ndev);
+ mod->can.state = CAN_STATE_STOPPED;
+
+ /* bring the bus offline, stop receiving packets */
+ ret = ican3_set_bus_state(mod, false);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-off\n");


+ return ret;
+ }
+

+ /* close the CAN layer */
+ close_candev(ndev);


+ return 0;
+}
+

+static int ican3_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ struct net_device_stats *stats = &ndev->stats;
+ struct can_frame *cf = (struct can_frame *)skb->data;
+ struct ican3_fast_desc desc;
+ void __iomem *desc_addr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mod->lock, flags);
+
+ /* check that we can actually transmit */
+ if (!ican3_txok(mod)) {
+ dev_err(mod->dev, "no free descriptors, stopping queue\n");
+ netif_stop_queue(ndev);
+ spin_unlock_irqrestore(&mod->lock, flags);
+ return NETDEV_TX_BUSY;
+ }
+
+ /* copy the control bits of the descriptor */
+ ican3_set_page(mod, mod->fasttx_start + (mod->fasttx_num / 16));
+ desc_addr = mod->dpm + ((mod->fasttx_num % 16) * sizeof(desc));
+ memset(&desc, 0, sizeof(desc));
+ memcpy_fromio(&desc, desc_addr, 1);
+
+ /* convert the Linux CAN frame into ICAN3 format */
+ can_frame_to_ican3(mod, cf, &desc);
+
+ /*
+ * the programming manual says that you must set the IVALID bit, then
+ * interrupt, then set the valid bit. Quite weird, but it seems to be
+ * required for this to work
+ */
+ desc.control |= DESC_IVALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* generate a MODULbus interrupt to the microcontroller */
+ iowrite8(0x01, &mod->dpmctrl->interrupt);
+
+ desc.control ^= DESC_VALID;
+ memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+ /* update the next buffer pointer */
+ mod->fasttx_num = (desc.control & DESC_WRAP) ? 0
+ : (mod->fasttx_num + 1);
+
+ /* update statistics */
+ stats->tx_packets++;
+ stats->tx_bytes += cf->can_dlc;
+ kfree_skb(skb);
+
+ /*
+ * This hardware doesn't have TX-done notifications, so we'll try and
+ * emulate it the best we can using ECHO skbs. Get the next TX
+ * descriptor, and see if we have room to send. If not, stop the queue.
+ * It will be woken when the ECHO skb for the current packet is recv'd.
+ */
+
+ /* copy the control bits of the descriptor */
+ if (!ican3_txok(mod))
+ netif_stop_queue(ndev);
+
+ spin_unlock_irqrestore(&mod->lock, flags);
+ return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops ican3_netdev_ops = {
+ .ndo_open = ican3_open,
+ .ndo_stop = ican3_stop,
+ .ndo_start_xmit = ican3_xmit,
+};
+
+/*
+ * Low-level CAN Device
+ */
+
+/* This structure was stolen from drivers/net/can/sja1000/sja1000.c */
+static struct can_bittiming_const ican3_bittiming_const = {
+ .name = DRV_NAME,
+ .tseg1_min = 1,
+ .tseg1_max = 16,
+ .tseg2_min = 1,
+ .tseg2_max = 8,
+ .sjw_max = 4,
+ .brp_min = 1,
+ .brp_max = 64,
+ .brp_inc = 1,
+};
+
+/*
+ * This routine was stolen from drivers/net/can/sja1000/sja1000.c
+ *
+ * The bittiming register command for the ICAN3 just sets the bit timing
+ * registers on the SJA1000 chip directly
+ */
+static int ican3_set_bittiming(struct net_device *ndev)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ struct can_bittiming *bt = &mod->can.bittiming;
+ struct ican3_msg msg;
+ u8 btr0, btr1;
+
+ btr0 = ((bt->brp - 1) & 0x3f) | (((bt->sjw - 1) & 0x3) << 6);
+ btr1 = ((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) |
+ (((bt->phase_seg2 - 1) & 0x7) << 4);
+ if (mod->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES)
+ btr1 |= 0x80;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.spec = MSG_CBTRREQ;
+ msg.len = cpu_to_le16(4);
+ msg.data[0] = 0x00;
+ msg.data[1] = 0x00;
+ msg.data[2] = btr0;
+ msg.data[3] = btr1;
+
+ return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ int ret;
+
+ if (mode != CAN_MODE_START)
+ return -ENOTSUPP;
+
+ /* bring the bus online */
+ ret = ican3_set_bus_state(mod, true);
+ if (ret) {
+ dev_err(mod->dev, "unable to set bus-on\n");


+ return ret;
+ }
+

+ /* start up the network device */
+ mod->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ if (netif_queue_stopped(ndev))
+ netif_wake_queue(ndev);


+
+ return 0;
+}
+

+static int ican3_get_berr_counter(const struct net_device *ndev,
+ struct can_berr_counter *bec)
+{
+ struct ican3_dev *mod = netdev_priv(ndev);
+ int ret;
+
+ ret = ican3_send_inquiry(mod, INQUIRY_STATUS);
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_timeout(&mod->buserror_comp, HZ);
+ if (ret <= 0) {
+ dev_info(mod->dev, "%s timed out\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ bec->rxerr = mod->bec.rxerr;
+ bec->txerr = mod->bec.txerr;


+ return 0;
+}
+
+/*

+ * Sysfs Attributes
+ */
+
+static ssize_t ican3_sysfs_show_term(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ican3_dev *mod = netdev_priv(to_net_dev(dev));
+ int ret;
+
+ ret = ican3_send_inquiry(mod, INQUIRY_TERMINATION);
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_timeout(&mod->termination_comp, HZ);
+ if (ret <= 0) {
+ dev_info(mod->dev, "%s timed out\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", mod->termination_enabled);
+}
+
+static ssize_t ican3_sysfs_set_term(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ican3_dev *mod = netdev_priv(to_net_dev(dev));
+ unsigned long enable;
+ int ret;
+
+ if (strict_strtoul(buf, 0, &enable))
+ return -EINVAL;
+
+ ret = ican3_set_termination(mod, enable);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(termination, S_IWUGO | S_IRUGO, ican3_sysfs_show_term,
+ ican3_sysfs_set_term);
+
+static struct attribute *ican3_sysfs_attrs[] = {
+ &dev_attr_termination.attr,
+ NULL,
+};
+
+static struct attribute_group ican3_sysfs_attr_group = {
+ .attrs = ican3_sysfs_attrs,
+};
+
+/*
+ * PCI Subsystem
+ */
+
+static int __devinit ican3_probe(struct platform_device *pdev)


+{
+ struct janz_platform_data *pdata;

+ struct net_device *ndev;
+ struct ican3_dev *mod;

+ struct resource *res;
+ struct device *dev;
+ int ret;
+


+ pdata = pdev->dev.platform_data;

+ if (!pdata)
+ return -ENXIO;
+
+ dev_dbg(&pdev->dev, "probe: module number %d\n", pdata->modno);
+
+ /* save the struct device for printing */
+ dev = &pdev->dev;
+
+ /* allocate the CAN device and private data */
+ ndev = alloc_candev(sizeof(*mod), 0);
+ if (!ndev) {

+ dev_err(dev, "unable to allocate CANdev\n");


+ ret = -ENOMEM;
+ goto out_return;
+ }
+

+ platform_set_drvdata(pdev, ndev);
+ mod = netdev_priv(ndev);
+ mod->ndev = ndev;
+ mod->dev = &pdev->dev;
+ mod->num = pdata->modno;
+ netif_napi_add(ndev, &mod->napi, ican3_napi, ICAN3_RX_BUFFERS);
+ spin_lock_init(&mod->lock);

+ init_completion(&mod->termination_comp);
+ init_completion(&mod->buserror_comp);
+
+ /* setup device-specific sysfs attributes */
+ ndev->sysfs_groups[0] = &ican3_sysfs_attr_group;


+
+ /* the first unallocated page in the DPM is 9 */
+ mod->free_page = DPM_FREE_START;
+
+ ndev->netdev_ops = &ican3_netdev_ops;
+ ndev->flags |= IFF_ECHO;
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+

+ mod->can.clock.freq = ICAN3_CAN_CLOCK;
+ mod->can.bittiming_const = &ican3_bittiming_const;
+ mod->can.do_set_bittiming = ican3_set_bittiming;
+ mod->can.do_set_mode = ican3_set_mode;
+ mod->can.do_get_berr_counter = ican3_get_berr_counter;
+ mod->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES
+ | CAN_CTRLMODE_BERR_REPORTING;
+
+ /* find our IRQ number */
+ mod->irq = platform_get_irq(pdev, 0);
+ if (mod->irq < 0) {
+ dev_err(dev, "IRQ line not found\n");
+ ret = -ENODEV;
+ goto out_free_ndev;
+ }
+
+ ndev->irq = mod->irq;
+
+ /* get access to the MODULbus registers for this module */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "MODULbus registers not found\n");
+ ret = -ENODEV;
+ goto out_free_ndev;
+ }
+
+ mod->dpm = ioremap(res->start, resource_size(res));
+ if (!mod->dpm) {
+ dev_err(dev, "MODULbus registers not ioremap\n");
+ ret = -ENOMEM;
+ goto out_free_ndev;
+ }
+
+ mod->dpmctrl = mod->dpm + DPM_PAGE_SIZE;
+
+ /* get access to the control registers for this module */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(dev, "CONTROL registers not found\n");
+ ret = -ENODEV;
+ goto out_iounmap_dpm;
+ }
+
+ mod->ctrl = ioremap(res->start, resource_size(res));
+ if (!mod->ctrl) {
+ dev_err(dev, "CONTROL registers not ioremap\n");
+ ret = -ENOMEM;
+ goto out_iounmap_dpm;
+ }
+
+ /* disable our IRQ, then hookup the IRQ handler */
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ ret = request_irq(mod->irq, ican3_irq, IRQF_SHARED, DRV_NAME, mod);
+ if (ret) {
+ dev_err(dev, "unable to request IRQ\n");
+ goto out_iounmap_ctrl;
+ }
+
+ /* reset and initialize the CAN controller into fast mode */
+ napi_enable(&mod->napi);
+ ret = ican3_startup_module(mod);
+ if (ret) {
+ dev_err(dev, "%s: unable to start CANdev\n", __func__);
+ goto out_free_irq;
+ }
+
+ /* register with the Linux CAN layer */
+ ret = register_candev(ndev);
+ if (ret) {
+ dev_err(dev, "%s: unable to register CANdev\n", __func__);
+ goto out_free_irq;
+ }
+
+ dev_info(dev, "module %d: registered CAN device\n", pdata->modno);
+ return 0;
+
+out_free_irq:
+ napi_disable(&mod->napi);
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ free_irq(mod->irq, mod);
+out_iounmap_ctrl:
+ iounmap(mod->ctrl);
+out_iounmap_dpm:
+ iounmap(mod->dpm);
+out_free_ndev:
+ free_candev(ndev);


+out_return:
+ return ret;
+}
+

+static int __devexit ican3_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct ican3_dev *mod = netdev_priv(ndev);
+
+ /* unregister the netdevice, stop interrupts */
+ unregister_netdev(ndev);
+ napi_disable(&mod->napi);
+ iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+ free_irq(mod->irq, mod);
+
+ /* put the module into reset */
+ ican3_shutdown_module(mod);
+
+ /* unmap all registers */
+ iounmap(mod->ctrl);
+ iounmap(mod->dpm);
+
+ free_candev(ndev);


+
+ return 0;
+}
+

+static struct platform_driver ican3_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ican3_probe,
+ .remove = __devexit_p(ican3_remove),
+};
+
+static int __init ican3_init(void)
+{
+ return platform_driver_register(&ican3_driver);
+}
+
+static void __exit ican3_exit(void)
+{
+ platform_driver_unregister(&ican3_driver);


+}
+
+MODULE_AUTHOR("Ira W. Snyder <i...@ovro.caltech.edu>");

+MODULE_DESCRIPTION("Janz MODULbus VMOD-ICAN3 Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:janz-ican3");
+
+module_init(ican3_init);
+module_exit(ican3_exit);

Ira W. Snyder

unread,
Mar 29, 2010, 1:10:02 PM3/29/10
to
The Janz VMOD-TTL is a MODULbus daughterboard which fits onto any MODULbus
carrier board. It essentially consists of some various logic and a Zilog
Z8536 CIO Counter/Timer and Parallel IO Unit.

The board must be physically configured with jumpers to enable a user to
drive output signals. I am only interested in outputs, so I have made this
driver as simple as possible. It only supports a very minimal subset of the
features provided by the Z8536 chip.

Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>
---

drivers/gpio/Kconfig | 10 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/janz-ttl.c | 257 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 268 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/janz-ttl.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index dd10eb8..1f38ff3 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -318,4 +318,14 @@ config GPIO_UCB1400


To compile this driver as a module, choose M here: the

module will be called ucb1400_gpio.

+comment "MODULbus GPIO expanders:"
+
+config GPIO_JANZ_TTL
+ tristate "Janz VMOD-TTL Digital IO Module"
+ depends on MFD_JANZ_CMODIO
+ help
+ This enables support for the Janz VMOD-TTL Digital IO module.
+ This driver provides support for driving the pins in output
+ mode only. Input mode is not supported.
+
endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index d3226d3..94a96c5 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -29,3 +29,4 @@ obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o
obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o
obj-$(CONFIG_GPIO_SCH) += sch_gpio.o
obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
+obj-$(CONFIG_GPIO_JANZ_TTL) += janz-ttl.o
diff --git a/drivers/gpio/janz-ttl.c b/drivers/gpio/janz-ttl.c
new file mode 100644
index 0000000..d97eeda
--- /dev/null
+++ b/drivers/gpio/janz-ttl.c
@@ -0,0 +1,257 @@
+/*
+ * Janz MODULbus VMOD-TTL GPIO Driver


+ *
+ * Copyright (c) 2010 Ira W. Snyder <i...@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>

+#include <linux/io.h>
+#include <linux/gpio.h>
+
+#include <linux/mfd/janz.h>
+
+#define DRV_NAME "janz-ttl"
+
+#define PORTA_DIRECTION 0x23
+#define PORTB_DIRECTION 0x2B
+#define PORTC_DIRECTION 0x06
+#define PORTA_IOCTL 0x24
+#define PORTB_IOCTL 0x2C
+#define PORTC_IOCTL 0x07
+
+#define MASTER_INT_CTL 0x00
+#define MASTER_CONF_CTL 0x01
+
+#define CONF_PAE (1 << 2)
+#define CONF_PBE (1 << 7)
+#define CONF_PCE (1 << 4)
+
+struct ttl_control_regs {
+ __be16 portc;
+ __be16 portb;
+ __be16 porta;
+ __be16 control;
+};
+
+struct ttl_module {
+ struct gpio_chip gpio;
+
+ /* base address of registers */
+ struct ttl_control_regs __iomem *regs;
+
+ u8 portc_shadow;
+ u8 portb_shadow;
+ u8 porta_shadow;
+


+ spinlock_t lock;
+};
+

+static int ttl_get_value(struct gpio_chip *gpio, unsigned offset)
+{
+ struct ttl_module *mod = dev_get_drvdata(gpio->dev);
+ u8 *shadow;
+ int ret;
+
+ if (offset < 8) {
+ shadow = &mod->porta_shadow;
+ } else if (offset < 16) {
+ shadow = &mod->portb_shadow;
+ offset -= 8;
+ } else {
+ shadow = &mod->portc_shadow;
+ offset -= 16;
+ }
+
+ spin_lock(&mod->lock);
+ ret = *shadow & (1 << offset);
+ spin_unlock(&mod->lock);


+ return ret;
+}
+

+static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value)
+{
+ struct ttl_module *mod = dev_get_drvdata(gpio->dev);
+ void __iomem *port;
+ u8 *shadow;
+
+ if (offset < 8) {
+ port = &mod->regs->porta;
+ shadow = &mod->porta_shadow;
+ } else if (offset < 16) {
+ port = &mod->regs->portb;
+ shadow = &mod->portb_shadow;
+ offset -= 8;
+ } else {
+ port = &mod->regs->portc;
+ shadow = &mod->portc_shadow;
+ offset -= 16;
+ }
+
+ spin_lock(&mod->lock);
+ if (value)
+ *shadow |= (1 << offset);
+ else
+ *shadow &= ~(1 << offset);
+
+ iowrite16be(*shadow, port);
+ spin_unlock(&mod->lock);
+}
+
+static void __devinit ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val)
+{
+ iowrite16be(reg, &mod->regs->control);
+ iowrite16be(val, &mod->regs->control);
+}
+
+static void __devinit ttl_setup_device(struct ttl_module *mod)
+{
+ /* reset the device to a known state */
+ iowrite16be(0x0000, &mod->regs->control);
+ iowrite16be(0x0001, &mod->regs->control);
+ iowrite16be(0x0000, &mod->regs->control);
+
+ /* put all ports in open-drain mode */
+ ttl_write_reg(mod, PORTA_IOCTL, 0x00ff);
+ ttl_write_reg(mod, PORTB_IOCTL, 0x00ff);
+ ttl_write_reg(mod, PORTC_IOCTL, 0x000f);
+
+ /* set all ports as outputs */
+ ttl_write_reg(mod, PORTA_DIRECTION, 0x0000);
+ ttl_write_reg(mod, PORTB_DIRECTION, 0x0000);
+ ttl_write_reg(mod, PORTC_DIRECTION, 0x0000);
+
+ /* set all ports to drive zeroes */
+ iowrite16be(0x0000, &mod->regs->porta);
+ iowrite16be(0x0000, &mod->regs->portb);
+ iowrite16be(0x0000, &mod->regs->portc);
+
+ /* enable all ports */
+ ttl_write_reg(mod, MASTER_CONF_CTL, CONF_PAE | CONF_PBE | CONF_PCE);
+}
+
+static int __devinit ttl_probe(struct platform_device *pdev)


+{
+ struct janz_platform_data *pdata;

+ struct device *dev = &pdev->dev;
+ struct ttl_module *mod;
+ struct gpio_chip *gpio;
+ struct resource *res;


+ int ret;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {

+ dev_err(dev, "no platform data\n");
+ ret = -ENXIO;


+ goto out_return;
+ }
+

+ mod = kzalloc(sizeof(*mod), GFP_KERNEL);
+ if (!mod) {
+ dev_err(dev, "unable to allocate private data\n");


+ ret = -ENOMEM;
+ goto out_return;
+ }
+

+ platform_set_drvdata(pdev, mod);
+ spin_lock_init(&mod->lock);


+
+ /* get access to the MODULbus registers for this module */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "MODULbus registers not found\n");
+ ret = -ENODEV;

+ goto out_free_mod;
+ }
+
+ mod->regs = ioremap(res->start, resource_size(res));
+ if (!mod->regs) {


+ dev_err(dev, "MODULbus registers not ioremap\n");
+ ret = -ENOMEM;

+ goto out_free_mod;
+ }
+
+ ttl_setup_device(mod);
+
+ /* Initialize the GPIO data structures */
+ gpio = &mod->gpio;
+ gpio->dev = &pdev->dev;
+ gpio->label = pdev->name;
+ gpio->get = ttl_get_value;
+ gpio->set = ttl_set_value;
+ gpio->owner = THIS_MODULE;
+
+ /* request dynamic allocation */
+ gpio->base = -1;
+ gpio->ngpio = 20;
+
+ ret = gpiochip_add(gpio);
+ if (ret) {
+ dev_err(dev, "unable to add GPIO chip\n");
+ goto out_iounmap_regs;
+ }
+
+ dev_info(&pdev->dev, "module %d: registered GPIO device\n",
+ pdata->modno);
+ return 0;
+
+out_iounmap_regs:
+ iounmap(mod->regs);
+out_free_mod:
+ kfree(mod);


+out_return:
+ return ret;
+}
+

+static int __devexit ttl_remove(struct platform_device *pdev)
+{
+ struct ttl_module *mod = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ ret = gpiochip_remove(&mod->gpio);
+ if (ret) {
+ dev_err(dev, "unable to remove GPIO chip\n");


+ return ret;
+ }
+

+ iounmap(mod->regs);
+ kfree(mod);


+ return 0;
+}
+

+static struct platform_driver ttl_driver = {


+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },

+ .probe = ttl_probe,
+ .remove = __devexit_p(ttl_remove),
+};
+
+static int __init ttl_init(void)
+{
+ return platform_driver_register(&ttl_driver);
+}
+
+static void __exit ttl_exit(void)
+{
+ platform_driver_unregister(&ttl_driver);


+}
+
+MODULE_AUTHOR("Ira W. Snyder <i...@ovro.caltech.edu>");

+MODULE_DESCRIPTION("Janz MODULbus VMOD-TTL Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:janz-ttl");
+
+module_init(ttl_init);
+module_exit(ttl_exit);

Wolfgang Grandegger

unread,
Mar 30, 2010, 4:20:03 AM3/30/10
to
Ira W. Snyder wrote:
> The Janz VMOD-ICAN3 is a MODULbus daughterboard which fits onto any
> MODULbus carrier board. It is an intelligent CAN controller with a
> microcontroller and associated firmware.
>
> Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>

Acked-by: Wolfgang Grandegger <w...@grandegger.com>

David Miller

unread,
Mar 31, 2010, 2:50:02 AM3/31/10
to
From: Wolfgang Grandegger <w...@grandegger.com>
Date: Tue, 30 Mar 2010 10:14:18 +0200

> Ira W. Snyder wrote:
>> The Janz VMOD-ICAN3 is a MODULbus daughterboard which fits onto any
>> MODULbus carrier board. It is an intelligent CAN controller with a
>> microcontroller and associated firmware.
>>
>> Signed-off-by: Ira W. Snyder <i...@ovro.caltech.edu>
>
> Acked-by: Wolfgang Grandegger <w...@grandegger.com>

Since this driver depends upon the MFD stuff and that goes
through other maintainers, just toss this CAN driver in
via whatever tree the MFD thing goes through and add my:

Acked-by: David S. Miller <da...@davemloft.net>

Thanks!

Andrew Morton

unread,
Apr 1, 2010, 4:10:01 PM4/1/10
to
On Mon, 29 Mar 2010 09:58:51 -0700
"Ira W. Snyder" <i...@ovro.caltech.edu> wrote:

> The Janz VMOD-ICAN3 is a MODULbus daughterboard which fits onto any
> MODULbus carrier board. It is an intelligent CAN controller with a
> microcontroller and associated firmware.
>

A neat-looking driver.

> ...
>
> + spin_lock_irqsave(&mod->lock, flags);
>
> ...

It does this rather a lot. it seems to be doing quite a lot of work
under that lock, too - quite a lot of memcpy_toio(), other stuff.

Is there potential here to disable interrupt for too long? Not
possible to use spin_lock_bh() here?

Ira W. Snyder

unread,
Apr 1, 2010, 8:50:01 PM4/1/10
to
On Thu, Apr 01, 2010 at 01:03:59PM -0700, Andrew Morton wrote:
> On Mon, 29 Mar 2010 09:58:51 -0700
> "Ira W. Snyder" <i...@ovro.caltech.edu> wrote:
>
> > The Janz VMOD-ICAN3 is a MODULbus daughterboard which fits onto any
> > MODULbus carrier board. It is an intelligent CAN controller with a
> > microcontroller and associated firmware.
> >
>
> A neat-looking driver.
>
> > ...
> >
> > + spin_lock_irqsave(&mod->lock, flags);
> >
> > ...
>
> It does this rather a lot. it seems to be doing quite a lot of work
> under that lock, too - quite a lot of memcpy_toio(), other stuff.
>

Like most similar cards, the host computer communicates to the
microcontroller through a dual ported memory (DPM) interface. In this
card, it is split into 256x 256 byte pages/windows.

The lock ensures that once code sets a window, it doesn't change while
the memcpy/iowrite happens.

> Is there potential here to disable interrupt for too long? Not
> possible to use spin_lock_bh() here?
>

The largest possible memcpy_(to|from)io() in the driver is 256 bytes.
Not too huge, but I understand the concern.

Looking at this again, I don't take the lock in the interrupt handler
(nor do I need to). What contexts do the network driver's xmit() and
napi() routines run in? hardirq and softirq respectively, right? In that
case, I think spin_lock_bh() is probably enough.

Ira

Samuel Ortiz

unread,
Apr 2, 2010, 6:00:01 AM4/2/10
to
Hi Ira,

On Mon, Mar 29, 2010 at 09:58:49AM -0700, Ira W. Snyder wrote:
> This patch series adds support for the Janz CMOD-IO carrier board, as well
> as the Janz VMOD-ICAN3 Intelligent CAN controller and the Janz VMOD-TTL
> Digital IO controller. The CMOD-IO carrier board is a PCI to MODULbus
> bridge, into which plug MODULbus daughterboards. I only have access to two
> types of daughtercards, the VMOD-ICAN3 and VMOD-TTL boards mentioned above.
>
> The CAN driver has been tested under high loads. I am able to generate ~60%
> bus utilization. With two VMOD-ICAN3 boards looped back to each other,
> neither one loses any packets when only a single board is generating
> packets at maximum speed. Once both boards start generating packets, one
> board will sometimes loose arbitration, and cause some lost packets.
>
> After much review from the last posting of the series, I believe that this
> should be ready for mainline. Many thanks to everyone that helped!

All 3 patches applied, thanks a lot.
After talking with Jesse Barnes (PCI maintainer), I moved the JANZ PCI id back
to the mfd driver. Jesse was saying that when a PCI id is used by only one
driver, it should be defined there and not in pci_ids.h. Sorry for the
mislead.

Cheers,
Samuel.

--

Intel Open Source Technology Centre
http://oss.intel.com/

Gustavo Silva

unread,
Apr 13, 2010, 2:20:02 AM4/13/10
to
This is a patch to the comedidev.h file that fixes up two macros do - while
loop and a space before tabs warnings found by the checkpatch.pl tool.

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---
drivers/staging/comedi/comedidev.h | 11 ++++++-----
1 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/drivers/staging/comedi/comedidev.h b/drivers/staging/comedi/comedidev.h
index ebdccfd..1b0d84b 100644
--- a/drivers/staging/comedi/comedidev.h
+++ b/drivers/staging/comedi/comedidev.h
@@ -57,14 +57,15 @@
static int __init x ## _init_module(void) \
{return comedi_driver_register(&(x)); } \
static void __exit x ## _cleanup_module(void) \
- {comedi_driver_unregister(&(x)); } \
+ {comedi_driver_unregister(&(x)); } \
module_init(x ## _init_module); \
module_exit(x ## _cleanup_module);

-#define COMEDI_MODULE_MACROS \
- MODULE_AUTHOR("Comedi http://www.comedi.org"); \
+#define COMEDI_MODULE_MACROS do { \
+ MODULE_AUTHOR("Comedi http://www.comedi.org"); \
MODULE_DESCRIPTION("Comedi low-level driver"); \
- MODULE_LICENSE("GPL");
+ MODULE_LICENSE("GPL"); \
+} while (0)

#define COMEDI_INITCLEANUP(x) \
COMEDI_MODULE_MACROS \
@@ -402,7 +403,7 @@ int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s,
#define RANGE(a, b) {(a)*1e6, (b)*1e6, 0}
#define RANGE_ext(a, b) {(a)*1e6, (b)*1e6, RF_EXTERNAL}
#define RANGE_mA(a, b) {(a)*1e6, (b)*1e6, UNIT_mA}
-#define RANGE_unitless(a, b) {(a)*1e6, (b)*1e6, 0} /* XXX */
+#define RANGE_unitless(a, b) do {(a)*1e6, (b)*1e6, 0} while (0) /* XXX */
#define BIP_RANGE(a) {-(a)*1e6, (a)*1e6, 0}
#define UNI_RANGE(a) {0, (a)*1e6, 0}

--
1.5.4.3

Gustavo Silva

unread,
Apr 19, 2010, 2:30:02 AM4/19/10
to
This is a patch to the das08.c file that fixes up the following issues

found by the checkpatch.pl tool.

WARNING: line over 80 characters x 6
ERROR: code indent should use tabs where possible x 3
ERROR: spaces required around that '?' (ctx:VxV) x 4
ERROR: spaces required around that ':' (ctx:VxV) x 4
ERROR: that open brace { should be on the previous line x 1
WARNING: printk() should include KERN_ facility level x 9
WARNING: braces {} are not necessary for single statement blocks x 1
WARNING: EXPORT_SYMBOL(foo); should immediately follow its
function/variable x 2

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/das08.c | 156 +++++++++++++++++---------------
1 files changed, 82 insertions(+), 74 deletions(-)

diff --git a/drivers/staging/comedi/drivers/das08.c b/drivers/staging/comedi/drivers/das08.c
index f425833..9cb144f 100644
--- a/drivers/staging/comedi/drivers/das08.c
+++ b/drivers/staging/comedi/drivers/das08.c
@@ -1,55 +1,55 @@
/*
- comedi/drivers/das08.c
- DAS08 driver
-
- COMEDI - Linux Control and Measurement Device Interface
- Copyright (C) 2000 David A. Schleef <d...@schleef.org>
- Copyright (C) 2001,2002,2003 Frank Mori Hess <fmh...@users.sourceforge.net>
- Copyright (C) 2004 Salvador E. Tropea <s...@users.sf.net> <s...@ieee.org>
-
- 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.
-
-*****************************************************************
+ * comedi/drivers/das08.c
+ * DAS08 driver
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <d...@schleef.org>
+ * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmh...@users.sourceforge.net>
+ * Copyright (C) 2004 Salvador E. Tropea <s...@users.sf.net> <s...@ieee.org>


+ *
+ * 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.
+ *
+ *****************************************************************
+ */

-*/
/*
-Driver: das08
-Description: DAS-08 compatible boards
-Author: Warren Jasper, ds, Frank Hess
-Devices: [Keithley Metrabyte] DAS08 (isa-das08), [ComputerBoards] DAS08 (isa-das08),
- DAS08-PGM (das08-pgm),
- DAS08-PGH (das08-pgh), DAS08-PGL (das08-pgl), DAS08-AOH (das08-aoh),
- DAS08-AOL (das08-aol), DAS08-AOM (das08-aom), DAS08/JR-AO (das08/jr-ao),
- DAS08/JR-16-AO (das08jr-16-ao), PCI-DAS08 (das08),
- PC104-DAS08 (pc104-das08), DAS08/JR/16 (das08jr/16)
-Status: works
-
-This is a rewrite of the das08 and das08jr drivers.
-
-Options (for ISA cards):
- [0] - base io address
-
-Options (for pci-das08):
- [0] - bus (optional)
- [1] = slot (optional)
-
-The das08 driver doesn't support asynchronous commands, since
-the cheap das08 hardware doesn't really support them. The
-comedi_rt_timer driver can be used to emulate commands for this
-driver.
-*/
+ * Driver: das08
+ * Description: DAS-08 compatible boards
+ * Author: Warren Jasper, ds, Frank Hess
+ * Devices: [Keithley Metrabyte] DAS08 (isa-das08),
+ * [ComputerBoards] DAS08 (isa-das08), DAS08-PGM (das08-pgm),
+ * DAS08-PGH (das08-pgh), DAS08-PGL (das08-pgl), DAS08-AOH (das08-aoh),
+ * DAS08-AOL (das08-aol), DAS08-AOM (das08-aom), DAS08/JR-AO (das08/jr-ao),
+ * DAS08/JR-16-AO (das08jr-16-ao), PCI-DAS08 (das08),
+ * PC104-DAS08 (pc104-das08), DAS08/JR/16 (das08jr/16)
+ * Status: works
+ *
+ * This is a rewrite of the das08 and das08jr drivers.
+ *
+ * Options (for ISA cards):
+ * [0] - base io address
+ *
+ * Options (for pci-das08):
+ * [0] - bus (optional)
+ * [1] = slot (optional)
+ *
+ * The das08 driver doesn't support asynchronous commands, since
+ * the cheap das08 hardware doesn't really support them. The
+ * comedi_rt_timer driver can be used to emulate commands for this
+ * driver.
+ */

#include "../comedidev.h"

@@ -122,8 +122,8 @@ driver.
*/

#define DAS08JR_DIO 3
-#define DAS08JR_AO_LSB(x) ((x)?6:4)
-#define DAS08JR_AO_MSB(x) ((x)?7:5)
+#define DAS08JR_AO_LSB(x) ((x) ? 6 : 4)
+#define DAS08JR_AO_MSB(x) ((x) ? 7 : 5)

/*
cio-das08_aox.pdf
@@ -148,8 +148,8 @@ driver.
#define DAS08AO_GAIN_CONTROL 3
#define DAS08AO_GAIN_STATUS 3

-#define DAS08AO_AO_LSB(x) ((x)?0xa:8)
-#define DAS08AO_AO_MSB(x) ((x)?0xb:9)
+#define DAS08AO_AO_LSB(x) ((x) ? 0xa : 8)
+#define DAS08AO_AO_MSB(x) ((x) ? 0xb : 9)
#define DAS08AO_AO_UPDATE 8

/* gainlist same as _pgx_ below */
@@ -239,8 +239,9 @@ static const struct comedi_lrange *const das08_ai_lranges[] = {
&range_das08_pgm,
};

-static const int das08_pgh_gainlist[] =
- { 8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7 };
+static const int das08_pgh_gainlist[] = {
+ 8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7
+};
static const int das08_pgl_gainlist[] = { 8, 0, 2, 4, 6, 1, 3, 5, 7 };
static const int das08_pgm_gainlist[] = { 8, 0, 10, 12, 14, 9, 11, 13, 15 };

@@ -535,7 +536,8 @@ static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
inb(dev->iobase + DAS08_MSB);

/* set multiplexer */
- spin_lock(&dev->spinlock); /* lock to prevent race with digital output */
+ /* lock to prevent race with digital output */
+ spin_lock(&dev->spinlock);
devpriv->do_mux_bits &= ~DAS08_MUX_MASK;
devpriv->do_mux_bits |= DAS08_MUX(chan);
outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL);
@@ -552,7 +554,7 @@ static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
/* clear over-range bits for 16-bit boards */
if (thisboard->ai_nbits == 16)
if (inb(dev->iobase + DAS08_MSB) & 0x80)
- printk("das08: over-range\n");
+ printk(KERN_INFO "das08: over-range\n");

/* trigger conversion */
outb_p(0, dev->iobase + DAS08_TRIG_12BIT);
@@ -562,7 +564,7 @@ static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
break;
}
if (i == TIMEOUT) {
- printk("das08: timeout\n");
+ printk(KERN_ERR "das08: timeout\n");
return -ETIME;
}
msb = inb(dev->iobase + DAS08_MSB);
@@ -607,7 +609,8 @@ static int das08_do_wbits(struct comedi_device *dev, struct comedi_subdevice *s,
/* set new bit values */
wbits |= data[0] & data[1];
/* remember digital output bits */
- spin_lock(&dev->spinlock); /* prevent race with setting of analog input mux */
+ /* prevent race with setting of analog input mux */
+ spin_lock(&dev->spinlock);
devpriv->do_mux_bits &= ~DAS08_DO_MASK;
devpriv->do_mux_bits |= DAS08_OP(wbits);
outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL);
@@ -860,9 +863,9 @@ int das08_common_attach(struct comedi_device *dev, unsigned long iobase)

/* allocate ioports for non-pcmcia, non-pci boards */
if ((thisboard->bustype != pcmcia) && (thisboard->bustype != pci)) {
- printk(" iobase 0x%lx\n", iobase);
+ printk(KERN_INFO " iobase 0x%lx\n", iobase);
if (!request_region(iobase, thisboard->iosize, DRV_NAME)) {
- printk(" I/O port conflict\n");
+ printk(KERN_ERR " I/O port conflict\n");
return -EIO;
}
}
@@ -878,8 +881,11 @@ int das08_common_attach(struct comedi_device *dev, unsigned long iobase)
/* ai */
if (thisboard->ai) {
s->type = COMEDI_SUBD_AI;
- /* XXX some boards actually have differential inputs instead of single ended.
- * The driver does nothing with arefs though, so it's no big deal. */
+ /* XXX some boards actually have differential
+ * inputs instead of single ended.
+ * The driver does nothing with arefs though,
+ * so it's no big deal.
+ */
s->subdev_flags = SDF_READABLE | SDF_GROUND;
s->n_chan = 8;
s->maxdata = (1 << thisboard->ai_nbits) - 1;
@@ -966,6 +972,7 @@ int das08_common_attach(struct comedi_device *dev, unsigned long iobase)

return 0;
}
+EXPORT_SYMBOL_GPL(das08_common_attach);

static int das08_attach(struct comedi_device *dev, struct comedi_devconfig *it)
{
@@ -980,7 +987,7 @@ static int das08_attach(struct comedi_device *dev, struct comedi_devconfig *it)
if (ret < 0)
return ret;

- printk("comedi%d: das08: ", dev->minor);
+ printk(KERN_INFO "comedi%d: das08: ", dev->minor);
/* deal with a pci board */
if (thisboard->bustype == pci) {
#ifdef CONFIG_COMEDI_PCI
@@ -1007,20 +1014,21 @@ static int das08_attach(struct comedi_device *dev, struct comedi_devconfig *it)
}
}
if (!pdev) {
- printk("No pci das08 cards found\n");
+ printk(KERN_ERR "No pci das08 cards found\n");
return -EIO;
}
devpriv->pdev = pdev;
/* enable PCI device and reserve I/O spaces */
if (comedi_pci_enable(pdev, DRV_NAME)) {
- printk
- (" Error enabling PCI device and requesting regions\n");
+ printk(KERN_ERR " Error enabling PCI device and "
+ "requesting regions\n");
return -EIO;
}
/* read base addresses */
pci_iobase = pci_resource_start(pdev, 1);
iobase = pci_resource_start(pdev, 2);
- printk("pcibase 0x%lx iobase 0x%lx\n", pci_iobase, iobase);
+ printk(KERN_INFO "pcibase 0x%lx iobase 0x%lx\n",
+ pci_iobase, iobase);
devpriv->pci_iobase = pci_iobase;
#if 0
/* We could enable to pci-das08's interrupt here to make it possible
@@ -1034,17 +1042,18 @@ static int das08_attach(struct comedi_device *dev, struct comedi_devconfig *it)
outw(INTR1_ENABLE | PCI_INTR_ENABLE, pci_iobase + INTCSR);
#endif
#else /* CONFIG_COMEDI_PCI */
- printk("this driver has not been built with PCI support.\n");
+ printk(KERN_ERR "this driver has not been built with PCI support.\n");
return -EINVAL;
#endif /* CONFIG_COMEDI_PCI */
} else {
iobase = it->options[0];
}
- printk("\n");
+ printk(KERN_INFO "\n");

return das08_common_attach(dev, iobase);
}

+
int das08_common_detach(struct comedi_device *dev)
{
printk(KERN_INFO "comedi%d: das08: remove\n", dev->minor);
@@ -1060,9 +1069,9 @@ int das08_common_detach(struct comedi_device *dev)
#ifdef CONFIG_COMEDI_PCI
if (devpriv) {
if (devpriv->pdev) {
- if (devpriv->pci_iobase) {
+ if (devpriv->pci_iobase)
comedi_pci_disable(devpriv->pdev);
- }
+
pci_dev_put(devpriv->pdev);
}
}
@@ -1070,6 +1079,7 @@ int das08_common_detach(struct comedi_device *dev)

return 0;
}
+EXPORT_SYMBOL_GPL(das08_common_detach);

#ifdef CONFIG_COMEDI_PCI
COMEDI_PCI_INITCLEANUP(driver_das08, das08_pci_table);
@@ -1077,8 +1087,6 @@ COMEDI_PCI_INITCLEANUP(driver_das08, das08_pci_table);
COMEDI_INITCLEANUP(driver_das08);
#endif

-EXPORT_SYMBOL_GPL(das08_common_attach);
-EXPORT_SYMBOL_GPL(das08_common_detach);
#ifdef CONFIG_COMEDI_PCMCIA
EXPORT_SYMBOL_GPL(das08_cs_boards);
#endif

Ian Abbott

unread,
Apr 20, 2010, 8:30:02 AM4/20/10
to
On 19/04/10 07:21, Gustavo Silva wrote:
> This is a patch to the das08.c file that fixes up the following issues
> found by the checkpatch.pl tool.

> -*/

This part of the patch would screw up some scripts we have (in the CVS
version of comedi) for extracting driver documentation from the source code.

--
-=( Ian Abbott @ MEV Ltd. E-mail: <abb...@mev.co.uk> )=-
-=( Tel: +44 (0)161 477 1898 FAX: +44 (0)161 718 3587 )=-

Greg KH

unread,
Apr 20, 2010, 9:50:03 AM4/20/10
to

What type of scripts are they and what is the requirements for them so
that we know not to mess them up?

And how about just using kerneldoc?

thanks,

greg k-h

Ian Abbott

unread,
Apr 20, 2010, 11:10:02 AM4/20/10
to

It's just a simple Perl script:

http://www.comedi.org/cgi-bin/viewvc.cgi/comedi/scripts/dump_doc?revision=1.2

It looks for a line beginning with 'Driver: ' and dumps that line and
following lines to standard output until it sees a line containing '*/'.
Then all these blocks of text for the different drivers get
concatenated into a plain text drivers.txt file using a Makefile rule
like this:

Documentation/comedi/drivers.txt: $(COMEDI_DRIVER_FILES)
for each in $(COMEDI_DRIVER_FILES); do $(srcdir)/scripts/dump_doc $$each;\
done >$(srcdir)/Documentation/comedi/drivers.txt

(that's from
http://www.comedi.org/cgi-bin/viewvc.cgi/comedi/Makefile.am?revision=1.29 )

I dare say the script could be easily modified to cope with the initial
' * ' at the start of each line.

The continuation lines following the 'Devices: ' line will need to be
indented with one or more whitespace characters as in the original version.

There is another script in comedilib that take the contents of this file
and incorporates it into the Comedilib manual (in DocBook-XML format),
with some data extraction from the 'Driver:', 'Description:',
'Devices:', 'Author:' and 'Status:' header lines (and following
continuation lines) to produce document section headings and a table of
device names for each driver. The resulting Comedilib chapter looks
like this:

http://www.comedi.org/doc/x1781.html

> And how about just using kerneldoc?

That should be possible, I guess by using a different script to produce
the drivers.txt file.

--
-=( Ian Abbott @ MEV Ltd. E-mail: <abb...@mev.co.uk> )=-
-=( Tel: +44 (0)161 477 1898 FAX: +44 (0)161 718 3587 )=-

Gustavo Silva

unread,
Apr 21, 2010, 1:40:03 AM4/21/10
to
This is a patch to the das16.c file that fixes up the following issues

found by the checkpatch.pl tool.

WARNING: line over 80 characters x 23
ERROR: spaces required around that '?' (ctx:VxV) x 2
ERROR: spaces required around that ':' (ctx:VxV) x 2
WARNING: printk() should include KERN_ facility level x 17
WARNING: braces {} are not necessary for single statement blocks x 8

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/das16.c | 159 ++++++++++++++++++--------------
1 files changed, 90 insertions(+), 69 deletions(-)

diff --git a/drivers/staging/comedi/drivers/das16.c b/drivers/staging/comedi/drivers/das16.c
index f2aadda..ccee4f1 100644
--- a/drivers/staging/comedi/drivers/das16.c
+++ b/drivers/staging/comedi/drivers/das16.c
@@ -74,7 +74,8 @@ Keithley Manuals:
4922.PDF (das-1400)
4923.PDF (das1200, 1400, 1600)

-Computer boards manuals also available from their website www.measurementcomputing.com
+Computer boards manuals also available from their website
+www.measurementcomputing.com

*/

@@ -92,7 +93,8 @@ Computer boards manuals also available from their website www.measurementcomputi
/* #define DEBUG */

#ifdef DEBUG
-#define DEBUG_PRINT(format, args...) printk("das16: " format, ## args)
+#define DEBUG_PRINT(format, args...) \
+ printk(KERN_DEBUG "das16: " format, ## args)
#else
#define DEBUG_PRINT(format, args...)
#endif
@@ -186,15 +188,16 @@ Computer boards manuals also available from their website www.measurementcomputi

*/

-static const int sample_size = 2; /* size in bytes of a sample from board */
+/* size in bytes of a sample from board */
+static const int sample_size = 2;

#define DAS16_TRIG 0
#define DAS16_AI_LSB 0
#define DAS16_AI_MSB 1
#define DAS16_MUX 2
#define DAS16_DIO 3
-#define DAS16_AO_LSB(x) ((x)?6:4)
-#define DAS16_AO_MSB(x) ((x)?7:5)
+#define DAS16_AO_LSB(x) ((x) ? 6 : 4)
+#define DAS16_AO_MSB(x) ((x) ? 7 : 5)
#define DAS16_STATUS 8
#define BUSY (1<<7)
#define UNIPOLAR (1<<6)
@@ -271,7 +274,7 @@ static const struct comedi_lrange range_das1x02_unip = { 4, {
};

static const struct comedi_lrange range_das16jr = { 9, {
- /* also used by 16/330 */
+ /* also used by 16/330 */
BIP_RANGE(10),
BIP_RANGE(5),
BIP_RANGE(2.5),
@@ -547,7 +550,8 @@ static const struct das16_board das16_boards[] = {
.id = 0x20,
},
{
- .name = "das-1401", /* 4919.pdf and 4922.pdf (keithley user's manual) */
+ /* 4919.pdf and 4922.pdf (keithley user's manual) */
+ .name = "das-1401",
.ai = das16_ai_rinsn,
.ai_nbits = 12,
.ai_speed = 10000,
@@ -558,10 +562,11 @@ static const struct das16_board das16_boards[] = {
.i8255_offset = 0x0,
.i8254_offset = 0x0c,
.size = 0x408,
- .id = 0xc0 /* 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 */
+ .id = 0xc0 /* 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 */
},
{
- .name = "das-1402", /* 4919.pdf and 4922.pdf (keithley user's manual) */
+ /* 4919.pdf and 4922.pdf (keithley user's manual) */
+ .name = "das-1402",
.ai = das16_ai_rinsn,
.ai_nbits = 12,
.ai_speed = 10000,
@@ -572,7 +577,7 @@ static const struct das16_board das16_boards[] = {
.i8255_offset = 0x0,
.i8254_offset = 0x0c,
.size = 0x408,
- .id = 0xc0 /* 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 */
+ .id = 0xc0 /* 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 */
},
{
.name = "das-1601", /* 4919.pdf */
@@ -704,7 +709,8 @@ static const struct das16_board das16_boards[] = {
.name = "das16/jr/ctr5", /* ? */
},
{
- .name = "cio-das16/m1/16", /* cio-das16_m1_16.pdf, this board is a bit quirky, no dma */
+ /* cio-das16_m1_16.pdf, this board is a bit quirky, no dma */
+ .name = "cio-das16/m1/16",
},
#endif
};
@@ -736,14 +742,19 @@ struct das16_private_struct {
unsigned int clockbase; /* master clock speed in ns */
volatile unsigned int control_state; /* dma, interrupt and trigger control bits */
volatile unsigned long adc_byte_count; /* number of bytes remaining */
- unsigned int divisor1; /* divisor dividing master clock to get conversion frequency */
- unsigned int divisor2; /* divisor dividing master clock to get conversion frequency */
+ /* divisor dividing master clock to get conversion frequency */
+ unsigned int divisor1;
+ /* divisor dividing master clock to get conversion frequency */
+ unsigned int divisor2;
unsigned int dma_chan; /* dma channel */
uint16_t *dma_buffer[2];
dma_addr_t dma_buffer_addr[2];
unsigned int current_buffer;
volatile unsigned int dma_transfer_size; /* target number of bytes to transfer per dma shot */
- /* user-defined analog input and output ranges defined from config options */
+ /**
+ * user-defined analog input and output ranges
+ * defined from config options
+ */
struct comedi_lrange *user_ai_range_table;
struct comedi_lrange *user_ao_range_table;

@@ -798,7 +809,10 @@ static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s,
if (err)
return 1;

- /* step 2: make sure trigger sources are unique and mutually compatible */
+ /**
+ * step 2: make sure trigger sources are unique and
+ * mutually compatible
+ */
if (cmd->scan_begin_src != TRIG_TIMER &&
cmd->scan_begin_src != TRIG_EXT &&
cmd->scan_begin_src != TRIG_FOLLOW)
@@ -893,12 +907,15 @@ static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s,
if (CR_CHAN(cmd->chanlist[i]) !=
(start_chan + i) % s->n_chan) {
comedi_error(dev,
- "entries in chanlist must be consecutive channels, counting upwards\n");
+ "entries in chanlist must be "
+ "consecutive channels, "
+ "counting upwards\n");
err++;
}
if (CR_RANGE(cmd->chanlist[i]) != gain) {
comedi_error(dev,
- "entries in chanlist must all have the same gain\n");
+ "entries in chanlist must all "
+ "have the same gain\n");
err++;
}
}
@@ -920,12 +937,13 @@ static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s)
if (devpriv->dma_chan == 0 || (dev->irq == 0
&& devpriv->timer_mode == 0)) {
comedi_error(dev,
- "irq (or use of 'timer mode') dma required to execute comedi_cmd");
+ "irq (or use of 'timer mode') dma required to "
+ "execute comedi_cmd");
return -1;
}
if (cmd->flags & TRIG_RT) {
- comedi_error(dev,
- "isa dma transfers cannot be performed with TRIG_RT, aborting");
+ comedi_error(dev, "isa dma transfers cannot be performed with "
+ "TRIG_RT, aborting");
return -1;
}

@@ -933,16 +951,17 @@ static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s)
cmd->stop_arg * cmd->chanlist_len * sizeof(uint16_t);

/* disable conversions for das1600 mode */
- if (thisboard->size > 0x400) {
+ if (thisboard->size > 0x400)
outb(DAS1600_CONV_DISABLE, dev->iobase + DAS1600_CONV);
- }
+
/* set scan limits */
byte = CR_CHAN(cmd->chanlist[0]);
byte |= CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]) << 4;
outb(byte, dev->iobase + DAS16_MUX);

/* set gain (this is also burst rate register but according to
- * computer boards manual, burst rate does nothing, even on keithley cards) */
+ * computer boards manual, burst rate does nothing, even on
+ * keithley cards) */
if (thisboard->ai_pg != das16_pg_none) {
range = CR_RANGE(cmd->chanlist[0]);
outb((das16_gainlists[thisboard->ai_pg])[range],
@@ -1005,9 +1024,9 @@ static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s)
outb(devpriv->control_state, dev->iobase + DAS16_CONTROL);

/* Enable conversions if using das1600 mode */
- if (thisboard->size > 0x400) {
+ if (thisboard->size > 0x400)
outb(0, dev->iobase + DAS1600_CONV);
- }
+

return 0;
}
@@ -1030,9 +1049,9 @@ static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
}

/* disable burst mode */
- if (thisboard->size > 0x400) {
+ if (thisboard->size > 0x400)
outb(0, dev->iobase + DAS1600_BURST);
- }
+

spin_unlock_irqrestore(&dev->spinlock, flags);

@@ -1085,11 +1104,11 @@ static int das16_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
}
msb = inb(dev->iobase + DAS16_AI_MSB);
lsb = inb(dev->iobase + DAS16_AI_LSB);
- if (thisboard->ai_nbits == 12) {
+ if (thisboard->ai_nbits == 12)
data[n] = ((lsb >> 4) & 0xf) | (msb << 4);
- } else {
+ else
data[n] = lsb | (msb << 8);
- }
+
}

return n;
@@ -1207,8 +1226,8 @@ static int disable_dma_on_even(struct comedi_device *dev)
residue = get_dma_residue(devpriv->dma_chan);
}
if (i == disable_limit) {
- comedi_error(dev,
- "failed to get an even dma transfer, could be trouble.");
+ comedi_error(dev, "failed to get an even dma transfer, "
+ "could be trouble.");
}
return residue;
}
@@ -1254,7 +1273,8 @@ static void das16_interrupt(struct comedi_device *dev)
} else
num_bytes = devpriv->dma_transfer_size - residue;

- if (cmd->stop_src == TRIG_COUNT && num_bytes >= devpriv->adc_byte_count) {
+ if (cmd->stop_src == TRIG_COUNT &&
+ num_bytes >= devpriv->adc_byte_count) {
num_bytes = devpriv->adc_byte_count;
async->events |= COMEDI_CB_EOA;
}
@@ -1275,9 +1295,9 @@ static void das16_interrupt(struct comedi_device *dev)
set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size);
enable_dma(devpriv->dma_chan);
/* reenable conversions for das1600 mode, (stupid hardware) */
- if (thisboard->size > 0x400 && devpriv->timer_mode == 0) {
+ if (thisboard->size > 0x400 && devpriv->timer_mode == 0)
outb(0x00, dev->iobase + DAS1600_CONV);
- }
+
}
release_dma_lock(dma_flags);

@@ -1330,25 +1350,25 @@ static int das16_probe(struct comedi_device *dev, struct comedi_devconfig *it)

status = inb(dev->iobase + DAS16_STATUS);

- if ((status & UNIPOLAR)) {
+ if ((status & UNIPOLAR))
devpriv->ai_unipolar = 1;
- } else {
+ else
devpriv->ai_unipolar = 0;
- }

- if ((status & DAS16_MUXBIT)) {
+
+ if ((status & DAS16_MUXBIT))
devpriv->ai_singleended = 1;
- } else {
+ else
devpriv->ai_singleended = 0;
- }
+

/* diobits indicates boards */

diobits = inb(dev->iobase + DAS16_DIO) & 0xf0;

- printk(" id bits are 0x%02x\n", diobits);
+ printk(KERN_INFO " id bits are 0x%02x\n", diobits);
if (thisboard->id != diobits) {
- printk(" requested board's id bits are 0x%x (ignore)\n",
+ printk(KERN_INFO " requested board's id bits are 0x%x (ignore)\n",
thisboard->id);
}

@@ -1363,10 +1383,10 @@ static int das1600_mode_detect(struct comedi_device *dev)

if (status & DAS1600_CLK_10MHZ) {
devpriv->clockbase = 100;
- printk(" 10MHz pacer clock\n");
+ printk(KERN_INFO " 10MHz pacer clock\n");
} else {
devpriv->clockbase = 1000;
- printk(" 1MHz pacer clock\n");
+ printk(KERN_INFO " 1MHz pacer clock\n");
}

reg_dump(dev);
@@ -1406,14 +1426,15 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
if (timer_mode)
irq = 0;

- printk("comedi%d: das16:", dev->minor);
+ printk(KERN_INFO "comedi%d: das16:", dev->minor);

/* check that clock setting is valid */
if (it->options[3]) {
if (it->options[3] != 0 &&
it->options[3] != 1 && it->options[3] != 10) {
printk
- ("\n Invalid option. Master clock must be set to 1 or 10 (MHz)\n");
+ ("\n Invalid option. Master clock must be set "
+ "to 1 or 10 (MHz)\n");
return -EINVAL;
}
}
@@ -1425,23 +1446,23 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
if (thisboard->size < 0x400) {
printk(" 0x%04lx-0x%04lx\n", iobase, iobase + thisboard->size);
if (!request_region(iobase, thisboard->size, "das16")) {


- printk(" I/O port conflict\n");
+ printk(KERN_ERR " I/O port conflict\n");
return -EIO;
}

} else {
- printk(" 0x%04lx-0x%04lx 0x%04lx-0x%04lx\n",
+ printk(KERN_INFO " 0x%04lx-0x%04lx 0x%04lx-0x%04lx\n",
iobase, iobase + 0x0f,
iobase + 0x400,
iobase + 0x400 + (thisboard->size & 0x3ff));
if (!request_region(iobase, 0x10, "das16")) {
- printk(" I/O port conflict: 0x%04lx-0x%04lx\n",
+ printk(KERN_ERR " I/O port conflict: 0x%04lx-0x%04lx\n",
iobase, iobase + 0x0f);
return -EIO;
}
if (!request_region(iobase + 0x400, thisboard->size & 0x3ff,
"das16")) {
release_region(iobase, 0x10);
- printk(" I/O port conflict: 0x%04lx-0x%04lx\n",
+ printk(KERN_ERR " I/O port conflict: 0x%04lx-0x%04lx\n",
iobase + 0x400,
iobase + 0x400 + (thisboard->size & 0x3ff));
return -EIO;
@@ -1452,7 +1473,7 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)

/* probe id bits to make sure they are consistent */
if (das16_probe(dev, it)) {
- printk(" id bits do not match selected board, aborting\n");
+ printk(KERN_ERR " id bits do not match selected board, aborting\n");
return -EINVAL;
}
dev->board_name = thisboard->name;
@@ -1474,7 +1495,7 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)


if (ret < 0)
return ret;

dev->irq = irq;
- printk(" ( irq = %u )", irq);
+ printk(KERN_INFO " ( irq = %u )", irq);
} else if (irq == 0) {
printk(" ( no irq )");
} else {
@@ -1488,16 +1509,15 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
/* allocate dma buffers */
int i;
for (i = 0; i < 2; i++) {
- devpriv->dma_buffer[i] = pci_alloc_consistent(NULL,
- DAS16_DMA_SIZE,
- &devpriv->
- dma_buffer_addr
- [i]);
+ devpriv->dma_buffer[i] = pci_alloc_consistent(
+ NULL, DAS16_DMA_SIZE,
+ &devpriv->dma_buffer_addr[i]);
+
if (devpriv->dma_buffer[i] == NULL)
return -ENOMEM;
}
if (request_dma(dma_chan, "das16")) {
- printk(" failed to allocate dma channel %i\n",
+ printk(KERN_ERR " failed to allocate dma channel %i\n",
dma_chan);
return -EINVAL;
}
@@ -1506,11 +1526,11 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
disable_dma(devpriv->dma_chan);
set_dma_mode(devpriv->dma_chan, DMA_MODE_READ);
release_dma_lock(flags);
- printk(" ( dma = %u)\n", dma_chan);
+ printk(KERN_INFO " ( dma = %u)\n", dma_chan);
} else if (dma_chan == 0) {
- printk(" ( no dma )\n");
+ printk(KERN_INFO " ( no dma )\n");
} else {
- printk(" invalid dma channel\n");
+ printk(KERN_ERR " invalid dma channel\n");
return -EINVAL;
}

@@ -1569,7 +1589,7 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
s->subdev_flags |= SDF_DIFF;


}
s->maxdata = (1 << thisboard->ai_nbits) - 1;

- if (devpriv->user_ai_range_table) { /* user defined ai range */
+ if (devpriv->user_ai_range_table) { /* user defined ai range */
s->range_table = devpriv->user_ai_range_table;
} else if (devpriv->ai_unipolar) {
s->range_table = das16_ai_uni_lranges[thisboard->ai_pg];
@@ -1592,11 +1612,12 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
s->subdev_flags = SDF_WRITABLE;
s->n_chan = 2;
s->maxdata = (1 << thisboard->ao_nbits) - 1;
- if (devpriv->user_ao_range_table) { /* user defined ao range */
+ /* user defined ao range */
+ if (devpriv->user_ao_range_table)
s->range_table = devpriv->user_ao_range_table;
- } else {
+ else
s->range_table = &range_unknown;
- }
+
s->insn_write = thisboard->ao;
} else {
s->type = COMEDI_SUBD_UNUSED;
@@ -1656,7 +1677,7 @@ static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)

static int das16_detach(struct comedi_device *dev)
{
- printk("comedi%d: das16: remove\n", dev->minor);
+ printk(KERN_INFO "comedi%d: das16: remove\n", dev->minor);

das16_reset(dev);

@@ -1750,8 +1771,8 @@ static void das16_ai_munge(struct comedi_device *dev,

for (i = 0; i < num_samples; i++) {
data[i] = le16_to_cpu(data[i]);
- if (thisboard->ai_nbits == 12) {
+ if (thisboard->ai_nbits == 12)
data[i] = (data[i] >> 4) & 0xfff;
- }
+
}
}
--
1.5.4.3

Gustavo Silva

unread,
Apr 21, 2010, 11:50:02 PM4/21/10
to
This is a patch to the dt2801.c file that fixes up all coding style

issues found by the checkpatch.pl tool.

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---
drivers/staging/comedi/drivers/dt2801.c | 100 ++++++++++++++-----------------
1 files changed, 44 insertions(+), 56 deletions(-)

diff --git a/drivers/staging/comedi/drivers/dt2801.c b/drivers/staging/comedi/drivers/dt2801.c
index 3f365ae..c4408d8 100644
--- a/drivers/staging/comedi/drivers/dt2801.c
+++ b/drivers/staging/comedi/drivers/dt2801.c
@@ -102,58 +102,46 @@ COMEDI_INITCLEANUP(driver_dt2801);

#if 0
/* ignore 'defined but not used' warning */
-static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = { 4, {
- RANGE(-10,
- 10),
- RANGE(-5,
- 5),
- RANGE
- (-2.5,
- 2.5),
- RANGE
- (-1.25,
- 1.25),
- }
+static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = {
+ 4,
+ {
+ RANGE(-10, 10),
+ RANGE(-5, 5),
+ RANGE(-2.5, 2.5),
+ RANGE(-1.25, 1.25),
+ }
};
#endif
-static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = { 4, {
- RANGE(-10,
- 10),
- RANGE(-1,
- 1),
- RANGE
- (-0.1,
- 0.1),
- RANGE
- (-0.02,
- 0.02),
- }
+static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = {
+ 4,
+ {
+ RANGE(-10, 10),
+ RANGE(-1, 1),
+ RANGE(-0.1, 0.1),
+ RANGE(-0.02, 0.02),
+ }
};

#if 0
/* ignore 'defined but not used' warning */
-static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = { 4, {
- RANGE(0,
- 10),
- RANGE(0,
- 5),
- RANGE(0,
- 2.5),
- RANGE(0,
- 1.25),
- }
+static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = {
+ 4,
+ {
+ RANGE(0, 10),
+ RANGE(0, 5),
+ RANGE(0, 2.5),
+ RANGE(0, 1.25),
+ }
};
#endif
-static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = { 4, {
- RANGE(0,
- 10),
- RANGE(0,
- 1),
- RANGE(0,
- 0.1),
- RANGE(0,
- 0.02),
- }
+static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = {
+ 4,
+ {
+ RANGE(0, 10),
+ RANGE(0, 1),
+ RANGE(0, 0.1),
+ RANGE(0, 0.02),
+ }
};

struct dt2801_board {
@@ -322,8 +310,8 @@ static int dt2801_writedata(struct comedi_device *dev, unsigned int data)
}
#if 0
if (stat & DT_S_READY) {
- printk
- ("dt2801: ready flag set (bad!) in dt2801_writedata()\n");
+ printk(KERN_ERR "dt2801: ready flag set (bad!) in "
+ "dt2801_writedata()\n");
return -EIO;
}
#endif
@@ -374,8 +362,8 @@ static int dt2801_writecmd(struct comedi_device *dev, int command)

stat = inb_p(dev->iobase + DT2801_STATUS);
if (stat & DT_S_COMPOSITE_ERROR) {
- printk
- ("dt2801: composite-error in dt2801_writecmd(), ignoring\n");
+ printk(KERN_INFO "dt2801: composite-error in dt2801_writecmd()"
+ ", ignoring\n");
}
if (!(stat & DT_S_READY))
printk("dt2801: !ready in dt2801_writecmd(), ignoring\n");
@@ -413,7 +401,7 @@ static int dt2801_reset(struct comedi_device *dev)
break;
} while (timeout--);
if (!timeout)
- printk("dt2801: timeout 1 status=0x%02x\n", stat);
+ printk(KERN_INFO "dt2801: timeout 1 status=0x%02x\n", stat);

/* printk("dt2801: reading dummy\n"); */
/* dt2801_readdata(dev,&board_code); */
@@ -430,7 +418,7 @@ static int dt2801_reset(struct comedi_device *dev)
break;
} while (timeout--);
if (!timeout)
- printk("dt2801: timeout 2 status=0x%02x\n", stat);
+ printk(KERN_INFO "dt2801: timeout 2 status=0x%02x\n", stat);

DPRINTK("dt2801: reading code\n");
dt2801_readdata(dev, &board_code);
@@ -528,13 +516,13 @@ static int dt2801_attach(struct comedi_device *dev, struct comedi_devconfig *it)
if (boardtypes[type].boardcode == board_code)
goto havetype;
}
- printk("dt2801: unrecognized board code=0x%02x, contact author\n",
- board_code);
+ printk(KERN_WARNING "dt2801: unrecognized board code=0x%02x, "
+ "contact author\n", board_code);
type = 0;

havetype:
dev->board_ptr = boardtypes + type;
- printk("dt2801: %s at port 0x%lx", boardtype.name, iobase);
+ printk(KERN_INFO "dt2801: %s at port 0x%lx", boardtype.name, iobase);

n_ai_chans = probe_number_of_ai_chans(dev);
printk(" (ai channels = %d)", n_ai_chans);
@@ -616,12 +604,12 @@ static int dt2801_error(struct comedi_device *dev, int stat)
{
if (stat < 0) {
if (stat == -ETIME)
- printk("dt2801: timeout\n");
+ printk(KERN_ERR "dt2801: timeout\n");
else
- printk("dt2801: error %d\n", stat);
+ printk(KERN_ERR "dt2801: error %d\n", stat);
return stat;
}
- printk("dt2801: error status 0x%02x, resetting...\n", stat);
+ printk(KERN_ERR "dt2801: error status 0x%02x, resetting...\n", stat);

dt2801_reset(dev);
dt2801_reset(dev);

Joe Perches

unread,
Apr 22, 2010, 12:10:01 AM4/22/10
to
On Wed, 2010-04-21 at 22:46 -0500, Gustavo Silva wrote:
> drivers/staging/comedi/drivers/dt2801.c | 100 ++++++++++++++-----------------
> 1 files changed, 44 insertions(+), 56 deletions(-)

Hi Gustavo. A couple of trivial comments.

> diff --git a/drivers/staging/comedi/drivers/dt2801.c b/drivers/staging/comedi/drivers/dt2801.c
> index 3f365ae..c4408d8 100644
> --- a/drivers/staging/comedi/drivers/dt2801.c
> +++ b/drivers/staging/comedi/drivers/dt2801.c
> @@ -102,58 +102,46 @@ COMEDI_INITCLEANUP(driver_dt2801);
>
> #if 0

You could just as well remove the #if 0 blocks

> @@ -374,8 +362,8 @@ static int dt2801_writecmd(struct comedi_device *dev, int command)
>
> stat = inb_p(dev->iobase + DT2801_STATUS);
> if (stat & DT_S_COMPOSITE_ERROR) {
> - printk
> - ("dt2801: composite-error in dt2801_writecmd(), ignoring\n");
> + printk(KERN_INFO "dt2801: composite-error in dt2801_writecmd()"
> + ", ignoring\n");

It's good to preface with KERN_, but better might be to use
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
and strip the "dt2801: " intro and use
pr_<level>("%s(): composite-error, ignoring\n", __func__)

It's a bit shorter, always gets the appropriate prefix, and
would emit the proper function name if ever refactored.

cheers, Joe

Wolfram Sang

unread,
Apr 22, 2010, 12:20:01 AM4/22/10
to
On Wed, Apr 21, 2010 at 09:00:11PM -0700, Joe Perches wrote:

> > @@ -374,8 +362,8 @@ static int dt2801_writecmd(struct comedi_device *dev, int command)
> >
> > stat = inb_p(dev->iobase + DT2801_STATUS);
> > if (stat & DT_S_COMPOSITE_ERROR) {
> > - printk
> > - ("dt2801: composite-error in dt2801_writecmd(), ignoring\n");
> > + printk(KERN_INFO "dt2801: composite-error in dt2801_writecmd()"
> > + ", ignoring\n");
>
> It's good to preface with KERN_, but better might be to use
> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> and strip the "dt2801: " intro and use
> pr_<level>("%s(): composite-error, ignoring\n", __func__)
>
> It's a bit shorter, always gets the appropriate prefix, and
> would emit the proper function name if ever refactored.

What about dev_* in case there are more than one of those devices?
Also, KERN_INFO doesn't look right, KERN_WARN?

--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |

signature.asc

Gustavo Silva

unread,
Apr 22, 2010, 12:30:02 AM4/22/10
to
2010/4/21 Joe Perches <j...@perches.com>

Hi Joe!

Thank you very much for your comments.

I will put in practice what you suggest.

No comment is trivial when it comes to help. :-]

Cheers from Mexico!
Gustavo Silva.

Joe Perches

unread,
Apr 22, 2010, 12:40:02 AM4/22/10
to
On Thu, 2010-04-22 at 06:13 +0200, Wolfram Sang wrote:
> What about dev_* in case there are more than one of those devices?

or netdev_<level>

> Also, KERN_INFO doesn't look right, KERN_WARN?

KERN_WARNING. Maybe kernel.h should add:
#define pr_warn pr_warning

so there would be symmetry between levels of
pr_<level>, dev_<level>, netdev_<level>, and netif_<level>

Wolfram Sang

unread,
Apr 22, 2010, 2:40:02 AM4/22/10
to
> KERN_WARNING. Maybe kernel.h should add:
> #define pr_warn pr_warning
>
> so there would be symmetry between levels of
> pr_<level>, dev_<level>, netdev_<level>, and netif_<level>

I'd like the symmetry, haven't checked your approach, though.

signature.asc

Greg KH

unread,
Apr 27, 2010, 6:40:02 PM4/27/10
to
On Tue, Apr 13, 2010 at 01:13:01AM -0500, Gustavo Silva wrote:
> This is a patch to the comedidev.h file that fixes up two macros do - while
> loop and a space before tabs warnings found by the checkpatch.pl tool.
>
> Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
>

This is not correct, you need to at least build your changes after
making them :(

Care to resend this, without this one change?

thanks,

greg k-h

Gustavo Silva

unread,
Jun 12, 2010, 5:40:03 PM6/12/10
to
This is a patch to the me4000.c file that fixes up the following
issues:

ERROR: space required after that close brace '}' x 13
ERROR: spaces required around that ':' (ctx:VxV) x 3
WARNING: line over 80 characters x 96
WARNING: braces {} are not necessary for any arm of this statement x 2

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/me4000.c | 416 +++++++++++++++++--------------
1 files changed, 227 insertions(+), 189 deletions(-)

diff --git a/drivers/staging/comedi/drivers/me4000.c b/drivers/staging/comedi/drivers/me4000.c
index 8b9fa0f..ab4ab75 100644
--- a/drivers/staging/comedi/drivers/me4000.c
+++ b/drivers/staging/comedi/drivers/me4000.c
@@ -91,22 +91,22 @@ static DEFINE_PCI_DEVICE_TABLE(me4000_pci_table) = {
MODULE_DEVICE_TABLE(pci, me4000_pci_table);

static const struct me4000_board me4000_boards[] = {
- {"ME-4650", 0x4650, {0, 0}, {16, 0, 0, 0}, {4}, {0}},
+ {"ME-4650", 0x4650, {0, 0}, {16, 0, 0, 0}, {4}, {0} },

- {"ME-4660", 0x4660, {0, 0}, {32, 0, 16, 0}, {4}, {3}},
- {"ME-4660i", 0x4661, {0, 0}, {32, 0, 16, 0}, {4}, {3}},
- {"ME-4660s", 0x4662, {0, 0}, {32, 8, 16, 0}, {4}, {3}},
- {"ME-4660is", 0x4663, {0, 0}, {32, 8, 16, 0}, {4}, {3}},
+ {"ME-4660", 0x4660, {0, 0}, {32, 0, 16, 0}, {4}, {3} },
+ {"ME-4660i", 0x4661, {0, 0}, {32, 0, 16, 0}, {4}, {3} },
+ {"ME-4660s", 0x4662, {0, 0}, {32, 8, 16, 0}, {4}, {3} },
+ {"ME-4660is", 0x4663, {0, 0}, {32, 8, 16, 0}, {4}, {3} },

- {"ME-4670", 0x4670, {4, 0}, {32, 0, 16, 1}, {4}, {3}},
- {"ME-4670i", 0x4671, {4, 0}, {32, 0, 16, 1}, {4}, {3}},
- {"ME-4670s", 0x4672, {4, 0}, {32, 8, 16, 1}, {4}, {3}},
- {"ME-4670is", 0x4673, {4, 0}, {32, 8, 16, 1}, {4}, {3}},
+ {"ME-4670", 0x4670, {4, 0}, {32, 0, 16, 1}, {4}, {3} },
+ {"ME-4670i", 0x4671, {4, 0}, {32, 0, 16, 1}, {4}, {3} },
+ {"ME-4670s", 0x4672, {4, 0}, {32, 8, 16, 1}, {4}, {3} },
+ {"ME-4670is", 0x4673, {4, 0}, {32, 8, 16, 1}, {4}, {3} },

- {"ME-4680", 0x4680, {4, 4}, {32, 0, 16, 1}, {4}, {3}},
- {"ME-4680i", 0x4681, {4, 4}, {32, 0, 16, 1}, {4}, {3}},
- {"ME-4680s", 0x4682, {4, 4}, {32, 8, 16, 1}, {4}, {3}},
- {"ME-4680is", 0x4683, {4, 4}, {32, 8, 16, 1}, {4}, {3}},
+ {"ME-4680", 0x4680, {4, 4}, {32, 0, 16, 1}, {4}, {3} },
+ {"ME-4680i", 0x4681, {4, 4}, {32, 0, 16, 1}, {4}, {3} },
+ {"ME-4680s", 0x4682, {4, 4}, {32, 8, 16, 1}, {4}, {3} },
+ {"ME-4680is", 0x4683, {4, 4}, {32, 8, 16, 1}, {4}, {3} },

{0},
};
@@ -120,10 +120,10 @@ static int me4000_attach(struct comedi_device *dev,
struct comedi_devconfig *it);
static int me4000_detach(struct comedi_device *dev);
static struct comedi_driver driver_me4000 = {
-driver_name:"me4000",
-module:THIS_MODULE,
-attach:me4000_attach,
-detach:me4000_detach,
+driver_name: "me4000",
+module : THIS_MODULE,
+attach : me4000_attach,
+detach : me4000_detach,
};

/*-----------------------------------------------------------------------------
@@ -302,8 +302,8 @@ static int me4000_attach(struct comedi_device *dev, struct comedi_devconfig *it)
if (request_irq(info->irq, me4000_ai_isr,
IRQF_SHARED, "ME-4000", dev)) {
printk
- ("comedi%d: me4000: me4000_attach(): Unable to allocate irq\n",
- dev->minor);
+ ("comedi%d: me4000: me4000_attach(): "
+ "Unable to allocate irq\n", dev->minor);
} else {
dev->read_subdev = s;
s->subdev_flags |= SDF_CMD_READ;
@@ -313,8 +313,8 @@ static int me4000_attach(struct comedi_device *dev, struct comedi_devconfig *it)
}
} else {
printk(KERN_WARNING
- "comedi%d: me4000: me4000_attach(): No interrupt available\n",
- dev->minor);
+ "comedi%d: me4000: me4000_attach(): "
+ "No interrupt available\n", dev->minor);


}
} else {
s->type = COMEDI_SUBD_UNUSED;

@@ -409,10 +409,16 @@ static int me4000_probe(struct comedi_device *dev, struct comedi_devconfig *it)
for (i = 0; i < ME4000_BOARD_VERSIONS; i++) {
if (me4000_boards[i].device_id ==
pci_device->device) {
- /* Was a particular bus/slot requested? */
+ /*
+ * Was a particular
+ * bus/slot requested?
+ */
if ((it->options[0] != 0)
|| (it->options[1] != 0)) {
- /* Are we on the wrong bus/slot? */
+ /*
+ * Are we on the wrong
+ * bus/slot?
+ */
if (pci_device->bus->number !=
it->options[0]
||
@@ -433,14 +439,16 @@ static int me4000_probe(struct comedi_device *dev, struct comedi_devconfig *it)
}

printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): No supported board found (req. bus/slot : %d/%d)\n",
+ "comedi%d: me4000: me4000_probe(): "
+ "No supported board found (req. bus/slot : %d/%d)\n",
dev->minor, it->options[0], it->options[1]);
return -ENODEV;

found:

printk(KERN_INFO
- "comedi%d: me4000: me4000_probe(): Found %s at PCI bus %d, slot %d\n",
+ "comedi%d: me4000: me4000_probe(): "
+ "Found %s at PCI bus %d, slot %d\n",
dev->minor, me4000_boards[i].name, pci_device->bus->number,
PCI_SLOT(pci_device->devfn));

@@ -451,8 +459,8 @@ found:
result = comedi_pci_enable(pci_device, dev->board_name);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Cannot enable PCI device and request I/O regions\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): Cannot enable PCI "
+ "device and request I/O regions\n", dev->minor);
return result;
}

@@ -460,16 +468,16 @@ found:
result = get_registers(dev, pci_device);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Cannot get registers\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): "
+ "Cannot get registers\n", dev->minor);
return result;
}
/* Initialize board info */
result = init_board_info(dev, pci_device);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Cannot init baord info\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): "
+ "Cannot init baord info\n", dev->minor);
return result;
}

@@ -477,8 +485,8 @@ found:
result = init_ao_context(dev);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Cannot init ao context\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): "
+ "Cannot init ao context\n", dev->minor);
return result;
}

@@ -486,8 +494,8 @@ found:
result = init_ai_context(dev);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Cannot init ai context\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): "
+ "Cannot init ai context\n", dev->minor);
return result;
}

@@ -495,8 +503,8 @@ found:
result = init_dio_context(dev);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Cannot init dio context\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): "
+ "Cannot init dio context\n", dev->minor);
return result;
}

@@ -504,8 +512,8 @@ found:
result = init_cnt_context(dev);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Cannot init cnt context\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): "
+ "Cannot init cnt context\n", dev->minor);
return result;
}

@@ -513,8 +521,8 @@ found:
result = xilinx_download(dev);
if (result) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_probe(): Can't download firmware\n",
- dev->minor);
+ "comedi%d: me4000: me4000_probe(): "
+ "Can't download firmware\n", dev->minor);
return result;
}

@@ -535,24 +543,24 @@ static int get_registers(struct comedi_device *dev, struct pci_dev *pci_dev_p)

CALL_PDEBUG("In get_registers()\n");

- /*--------------------------- plx regbase ---------------------------------*/
+ /*--------------------------- plx regbase -------------------------------*/

info->plx_regbase = pci_resource_start(pci_dev_p, 1);
if (info->plx_regbase == 0) {
printk(KERN_ERR
- "comedi%d: me4000: get_registers(): PCI base address 1 is not available\n",
- dev->minor);
+ "comedi%d: me4000: get_registers(): "
+ "PCI base address 1 is not available\n", dev->minor);
return -ENODEV;
}
info->plx_regbase_size = pci_resource_len(pci_dev_p, 1);

- /*--------------------------- me4000 regbase ------------------------------*/
+ /*--------------------------- me4000 regbase ----------------------------*/

info->me4000_regbase = pci_resource_start(pci_dev_p, 2);
if (info->me4000_regbase == 0) {
printk(KERN_ERR
- "comedi%d: me4000: get_registers(): PCI base address 2 is not available\n",
- dev->minor);
+ "comedi%d: me4000: get_registers(): "
+ "PCI base address 2 is not available\n", dev->minor);
return -ENODEV;
}
info->me4000_regbase_size = pci_resource_len(pci_dev_p, 2);
@@ -562,19 +570,19 @@ static int get_registers(struct comedi_device *dev, struct pci_dev *pci_dev_p)
info->timer_regbase = pci_resource_start(pci_dev_p, 3);
if (info->timer_regbase == 0) {
printk(KERN_ERR
- "comedi%d: me4000: get_registers(): PCI base address 3 is not available\n",
- dev->minor);
+ "comedi%d: me4000: get_registers(): "
+ "PCI base address 3 is not available\n", dev->minor);
return -ENODEV;
}
info->timer_regbase_size = pci_resource_len(pci_dev_p, 3);

- /*--------------------------- program regbase ------------------------------*/
+ /*--------------------------- program regbase ----------------------------*/

info->program_regbase = pci_resource_start(pci_dev_p, 5);
if (info->program_regbase == 0) {
printk(KERN_ERR
- "comedi%d: me4000: get_registers(): PCI base address 5 is not available\n",
- dev->minor);
+ "comedi%d: me4000: get_registers(): "
+ "PCI base address 5 is not available\n", dev->minor);
return -ENODEV;
}
info->program_regbase_size = pci_resource_len(pci_dev_p, 5);
@@ -800,8 +808,8 @@ static int xilinx_download(struct comedi_device *dev)
udelay(20);
if (!(inl(info->plx_regbase + PLX_INTCSR) & 0x20)) {
printk(KERN_ERR
- "comedi%d: me4000: xilinx_download(): Can't init Xilinx\n",
- dev->minor);
+ "comedi%d: me4000: xilinx_download(): "
+ "Can't init Xilinx\n", dev->minor);
return -EIO;
}

@@ -810,8 +818,8 @@ static int xilinx_download(struct comedi_device *dev)
value &= ~0x100;
outl(value, info->plx_regbase + PLX_ICR);
if (FIRMWARE_NOT_AVAILABLE) {
- comedi_error(dev,
- "xilinx firmware unavailable due to licensing, aborting");
+ comedi_error(dev, "xilinx firmware unavailable "
+ "due to licensing, aborting");
return -EIO;
} else {
/* Download Xilinx firmware */
@@ -826,7 +834,8 @@ static int xilinx_download(struct comedi_device *dev)
/* Check if BUSY flag is low */
if (inl(info->plx_regbase + PLX_ICR) & 0x20) {
printk(KERN_ERR
- "comedi%d: me4000: xilinx_download(): Xilinx is still busy (idx = %d)\n",
+ "comedi%d: me4000: xilinx_download(): "
+ "Xilinx is still busy (idx = %d)\n",
dev->minor, idx);
return -EIO;
}
@@ -837,11 +846,11 @@ static int xilinx_download(struct comedi_device *dev)
if (inl(info->plx_regbase + PLX_ICR) & 0x4) {
} else {
printk(KERN_ERR
- "comedi%d: me4000: xilinx_download(): DONE flag is not set\n",
- dev->minor);
+ "comedi%d: me4000: xilinx_download(): "
+ "DONE flag is not set\n", dev->minor);
printk(KERN_ERR
- "comedi%d: me4000: xilinx_download(): Download not successful\n",
- dev->minor);
+ "comedi%d: me4000: xilinx_download(): "
+ "Download not successful\n", dev->minor);
return -EIO;
}

@@ -902,7 +911,10 @@ static int reset_board(struct comedi_device *dev)
me4000_outl(dev, ME4000_AO_DEMUX_ADJUST_VALUE,
info->me4000_regbase + ME4000_AO_DEMUX_ADJUST_REG);

- /* Set digital I/O direction for port 0 to output on isolated versions */
+ /*
+ * Set digital I/O direction for port 0
+ * to output on isolated versions
+ */
if (!(me4000_inl(dev, info->me4000_regbase + ME4000_DIO_DIR_REG) & 0x1)) {
me4000_outl(dev, 0x1,
info->me4000_regbase + ME4000_DIO_CTRL_REG);
@@ -950,8 +962,8 @@ static int me4000_ai_insn_read(struct comedi_device *dev,
return 0;
} else if (insn->n > 1) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_insn_read(): Invalid instruction length %d\n",
- dev->minor, insn->n);
+ "comedi%d: me4000: me4000_ai_insn_read(): "
+ "Invalid instruction length %d\n", dev->minor, insn->n);
return -EINVAL;
}

@@ -970,8 +982,8 @@ static int me4000_ai_insn_read(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_insn_read(): Invalid range specified\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_insn_read(): "
+ "Invalid range specified\n", dev->minor);
return -EINVAL;
}

@@ -980,8 +992,8 @@ static int me4000_ai_insn_read(struct comedi_device *dev,
case AREF_COMMON:
if (chan >= thisboard->ai.count) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_insn_read(): Analog input is not available\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_insn_read(): "
+ "Analog input is not available\n", dev->minor);
return -EINVAL;
}
entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED | chan;
@@ -990,23 +1002,24 @@ static int me4000_ai_insn_read(struct comedi_device *dev,
case AREF_DIFF:
if (rang == 0 || rang == 1) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_insn_read(): Range must be bipolar when aref = diff\n",
+ "comedi%d: me4000: me4000_ai_insn_read(): "
+ "Range must be bipolar when aref = diff\n",
dev->minor);
return -EINVAL;
}

if (chan >= thisboard->ai.diff_count) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_insn_read(): Analog input is not available\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_insn_read(): "
+ "Analog input is not available\n", dev->minor);
return -EINVAL;
}
entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL | chan;
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_insn_read(): Invalid aref specified\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_insn_read(): "
+ "Invalid aref specified\n", dev->minor);
return -EINVAL;
}

@@ -1045,8 +1058,8 @@ static int me4000_ai_insn_read(struct comedi_device *dev,
(me4000_inl(dev, info->ai_context.status_reg) &
ME4000_AI_STATUS_BIT_EF_DATA)) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_insn_read(): Value not available after wait\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_insn_read(): "
+ "Value not available after wait\n", dev->minor);
return -EIO;
}

@@ -1086,24 +1099,24 @@ static int ai_check_chanlist(struct comedi_device *dev,
/* Check whether a channel list is available */
if (!cmd->chanlist_len) {
printk(KERN_ERR
- "comedi%d: me4000: ai_check_chanlist(): No channel list available\n",
- dev->minor);
+ "comedi%d: me4000: ai_check_chanlist(): "
+ "No channel list available\n", dev->minor);
return -EINVAL;
}

/* Check the channel list size */
if (cmd->chanlist_len > ME4000_AI_CHANNEL_LIST_COUNT) {
printk(KERN_ERR
- "comedi%d: me4000: ai_check_chanlist(): Channel list is to large\n",
- dev->minor);
+ "comedi%d: me4000: ai_check_chanlist(): "
+ "Channel list is to large\n", dev->minor);
return -EINVAL;
}

/* Check the pointer */
if (!cmd->chanlist) {
printk(KERN_ERR
- "comedi%d: me4000: ai_check_chanlist(): NULL pointer to channel list\n",
- dev->minor);
+ "comedi%d: me4000: ai_check_chanlist(): "
+ "NULL pointer to channel list\n", dev->minor);
return -EFAULT;
}

@@ -1112,7 +1125,8 @@ static int ai_check_chanlist(struct comedi_device *dev,
for (i = 0; i < cmd->chanlist_len; i++) {
if (CR_AREF(cmd->chanlist[i]) != aref) {
printk(KERN_ERR
- "comedi%d: me4000: ai_check_chanlist(): Mode is not equal for all entries\n",
+ "comedi%d: me4000: ai_check_chanlist(): "
+ "Mode is not equal for all entries\n",
dev->minor);
return -EINVAL;
}
@@ -1124,8 +1138,8 @@ static int ai_check_chanlist(struct comedi_device *dev,
if (CR_CHAN(cmd->chanlist[i]) >=
thisboard->ai.diff_count) {
printk(KERN_ERR
- "comedi%d: me4000: ai_check_chanlist(): Channel number to high\n",
- dev->minor);
+ "comedi%d: me4000: ai_check_chanlist():"
+ " Channel number to high\n", dev->minor);
return -EINVAL;
}
}
@@ -1133,8 +1147,8 @@ static int ai_check_chanlist(struct comedi_device *dev,
for (i = 0; i < cmd->chanlist_len; i++) {
if (CR_CHAN(cmd->chanlist[i]) >= thisboard->ai.count) {
printk(KERN_ERR
- "comedi%d: me4000: ai_check_chanlist(): Channel number to high\n",
- dev->minor);
+ "comedi%d: me4000: ai_check_chanlist(): "
+ "Channel number to high\n", dev->minor);
return -EINVAL;
}
}
@@ -1146,7 +1160,9 @@ static int ai_check_chanlist(struct comedi_device *dev,
if (CR_RANGE(cmd->chanlist[i]) != 1 &&
CR_RANGE(cmd->chanlist[i]) != 2) {
printk(KERN_ERR
- "comedi%d: me4000: ai_check_chanlist(): Bipolar is not selected in differential mode\n",
+ "comedi%d: me4000: ai_check_chanlist(): "
+ "Bipolar is not selected in "
+ "differential mode\n",
dev->minor);
return -EINVAL;
}
@@ -1330,21 +1346,19 @@ static int ai_write_chanlist(struct comedi_device *dev,

entry = chan;

- if (rang == 0) {
+ if (rang == 0)
entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5;
- } else if (rang == 1) {
+ else if (rang == 1)
entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10;
- } else if (rang == 2) {
+ else if (rang == 2)
entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5;
- } else {
+ else
entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10;
- }

- if (aref == SDF_DIFF) {
+ if (aref == SDF_DIFF)
entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL;
- } else {
+ else
entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED;
- }

me4000_outl(dev, entry, info->ai_context.channel_list_reg);
}
@@ -1454,8 +1468,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start source\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start source\n", dev->minor);
cmd->start_src = TRIG_NOW;
err++;
}
@@ -1470,8 +1484,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan begin source\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid scan begin source\n", dev->minor);
cmd->scan_begin_src = TRIG_FOLLOW;
err++;
}
@@ -1485,8 +1499,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert source\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid convert source\n", dev->minor);
cmd->convert_src = TRIG_TIMER;
err++;
}
@@ -1500,8 +1514,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end source\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid scan end source\n", dev->minor);
cmd->scan_end_src = TRIG_NONE;
err++;
}
@@ -1515,8 +1529,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop source\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid stop source\n", dev->minor);
cmd->stop_src = TRIG_NONE;
err++;
}
@@ -1546,8 +1560,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
cmd->convert_src == TRIG_EXT) {
} else {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start trigger combination\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start trigger combination\n", dev->minor);
cmd->start_src = TRIG_NOW;
cmd->scan_begin_src = TRIG_FOLLOW;
cmd->convert_src = TRIG_TIMER;
@@ -1563,8 +1577,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
cmd->scan_end_src == TRIG_COUNT) {
} else {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop trigger combination\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid stop trigger combination\n", dev->minor);
cmd->stop_src = TRIG_NONE;
cmd->scan_end_src = TRIG_NONE;
err++;
@@ -1577,29 +1591,29 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
*/
if (cmd->chanlist_len < 1) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): No channel list\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "No channel list\n", dev->minor);
cmd->chanlist_len = 1;
err++;
}
if (init_ticks < 66) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Start arg to low\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Start arg to low\n", dev->minor);
cmd->start_arg = 2000;
err++;
}
if (scan_ticks && scan_ticks < 67) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Scan begin arg to low\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Scan begin arg to low\n", dev->minor);
cmd->scan_begin_arg = 2031;
err++;
}
if (chan_ticks < 66) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Convert arg to low\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Convert arg to low\n", dev->minor);
cmd->convert_arg = 2000;
err++;
}
@@ -1617,23 +1631,25 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
/* Check timer arguments */
if (init_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start arg\n", dev->minor);
cmd->start_arg = 2000; /* 66 ticks at least */
err++;
}
if (chan_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid convert arg\n", dev->minor);
cmd->convert_arg = 2000; /* 66 ticks at least */
err++;
}
if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n",
- dev->minor);
- cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; /* At least one tick more */
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid scan end arg\n", dev->minor);
+
+ /* At least one tick more */
+ cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;
err++;
}
} else if (cmd->start_src == TRIG_NOW &&
@@ -1643,15 +1659,15 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
/* Check timer arguments */
if (init_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start arg\n", dev->minor);
cmd->start_arg = 2000; /* 66 ticks at least */
err++;
}
if (chan_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid convert arg\n", dev->minor);
cmd->convert_arg = 2000; /* 66 ticks at least */
err++;
}
@@ -1662,23 +1678,25 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
/* Check timer arguments */
if (init_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start arg\n", dev->minor);
cmd->start_arg = 2000; /* 66 ticks at least */
err++;
}
if (chan_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid convert arg\n", dev->minor);
cmd->convert_arg = 2000; /* 66 ticks at least */
err++;
}
if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n",
- dev->minor);
- cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; /* At least one tick more */
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid scan end arg\n", dev->minor);
+
+ /* At least one tick more */
+ cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31;
err++;
}
} else if (cmd->start_src == TRIG_EXT &&
@@ -1688,15 +1706,15 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
/* Check timer arguments */
if (init_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start arg\n", dev->minor);
cmd->start_arg = 2000; /* 66 ticks at least */
err++;
}
if (chan_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid convert arg\n", dev->minor);
cmd->convert_arg = 2000; /* 66 ticks at least */
err++;
}
@@ -1707,15 +1725,15 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
/* Check timer arguments */
if (init_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start arg\n", dev->minor);
cmd->start_arg = 2000; /* 66 ticks at least */
err++;
}
if (chan_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid convert arg\n", dev->minor);
cmd->convert_arg = 2000; /* 66 ticks at least */
err++;
}
@@ -1726,8 +1744,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
/* Check timer arguments */
if (init_ticks < ME4000_AI_MIN_TICKS) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid start arg\n", dev->minor);
cmd->start_arg = 2000; /* 66 ticks at least */
err++;
}
@@ -1735,8 +1753,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
if (cmd->stop_src == TRIG_COUNT) {
if (cmd->stop_arg == 0) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid stop arg\n", dev->minor);
cmd->stop_arg = 1;
err++;
}
@@ -1744,8 +1762,8 @@ static int me4000_ai_do_cmd_test(struct comedi_device *dev,
if (cmd->scan_end_src == TRIG_COUNT) {
if (cmd->scan_end_arg == 0) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_do_cmd_test(): "
+ "Invalid scan end arg\n", dev->minor);
cmd->scan_end_arg = 1;
err++;
}
@@ -1786,8 +1804,8 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
/* Check if irq number is right */
if (irq != ai_context->irq) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_isr(): Incorrect interrupt num: %d\n",
- dev->minor, irq);
+ "comedi%d: me4000: me4000_ai_isr(): "
+ "Incorrect interrupt num: %d\n", dev->minor, irq);
return IRQ_HANDLED;
}

@@ -1806,7 +1824,10 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
ISR_PDEBUG("me4000_ai_isr(): Fifo full\n");
c = ME4000_AI_FIFO_COUNT;

- /* FIFO overflow, so stop conversion and disable all interrupts */
+ /*
+ * FIFO overflow, so stop conversion
+ * and disable all interrupts
+ */
tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
ME4000_AI_CTRL_BIT_SC_IRQ);
@@ -1815,8 +1836,8 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;

printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_isr(): FIFO overflow\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_isr(): "
+ "FIFO overflow\n", dev->minor);
} else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA)
&& !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
&& (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
@@ -1827,11 +1848,14 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
c = ME4000_AI_FIFO_COUNT / 2;
} else {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_isr(): Can't determine state of fifo\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_isr(): "
+ "Can't determine state of fifo\n", dev->minor);
c = 0;

- /* Undefined state, so stop conversion and disable all interrupts */
+ /*
+ * Undefined state, so stop conversion
+ * and disable all interrupts
+ */
tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
ME4000_AI_CTRL_BIT_SC_IRQ);
@@ -1840,8 +1864,8 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;

printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_isr(): Undefined FIFO state\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_isr(): "
+ "Undefined FIFO state\n", dev->minor);
}

ISR_PDEBUG("me4000_ai_isr(): Try to read %d values\n", c);
@@ -1852,7 +1876,10 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
lval ^= 0x8000;

if (!comedi_buf_put(s->async, lval)) {
- /* Buffer overflow, so stop conversion and disable all interrupts */
+ /*
+ * Buffer overflow, so stop conversion
+ * and disable all interrupts
+ */
tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ |
ME4000_AI_CTRL_BIT_SC_IRQ);
@@ -1861,8 +1888,8 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)
s->async->events |= COMEDI_CB_OVERFLOW;

printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_isr(): Buffer overflow\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_isr(): "
+ "Buffer overflow\n", dev->minor);

break;
}
@@ -1883,7 +1910,10 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)

s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOA;

- /* Acquisition is complete, so stop conversion and disable all interrupts */
+ /*
+ * Acquisition is complete, so stop
+ * conversion and disable all interrupts
+ */
tmp = me4000_inl(dev, ai_context->ctrl_reg);
tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ);
@@ -1897,8 +1927,8 @@ static irqreturn_t me4000_ai_isr(int irq, void *dev_id)

if (!comedi_buf_put(s->async, lval)) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ai_isr(): Buffer overflow\n",
- dev->minor);
+ "comedi%d: me4000: me4000_ai_isr(): "
+ "Buffer overflow\n", dev->minor);
s->async->events |= COMEDI_CB_OVERFLOW;
break;
}
@@ -1941,29 +1971,29 @@ static int me4000_ao_insn_write(struct comedi_device *dev,
return 0;
} else if (insn->n > 1) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ao_insn_write(): Invalid instruction length %d\n",
- dev->minor, insn->n);
+ "comedi%d: me4000: me4000_ao_insn_write(): "
+ "Invalid instruction length %d\n", dev->minor, insn->n);
return -EINVAL;
}

if (chan >= thisboard->ao.count) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ao_insn_write(): Invalid channel %d\n",
- dev->minor, insn->n);
+ "comedi%d: me4000: me4000_ao_insn_write(): "
+ "Invalid channel %d\n", dev->minor, insn->n);
return -EINVAL;
}

if (rang != 0) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ao_insn_write(): Invalid range %d\n",
- dev->minor, insn->n);
+ "comedi%d: me4000: me4000_ao_insn_write(): "
+ "Invalid range %d\n", dev->minor, insn->n);
return -EINVAL;
}

if (aref != AREF_GROUND && aref != AREF_COMMON) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_ao_insn_write(): Invalid aref %d\n",
- dev->minor, insn->n);
+ "comedi%d: me4000: me4000_ao_insn_write(): "
+ "Invalid aref %d\n", dev->minor, insn->n);
return -EINVAL;
}

@@ -1994,8 +2024,8 @@ static int me4000_ao_insn_read(struct comedi_device *dev,
return 0;
} else if (insn->n > 1) {
printk
- ("comedi%d: me4000: me4000_ao_insn_read(): Invalid instruction length\n",
- dev->minor);
+ ("comedi%d: me4000: me4000_ao_insn_read(): "
+ "Invalid instruction length\n", dev->minor);
return -EINVAL;
}

@@ -2021,8 +2051,8 @@ static int me4000_dio_insn_bits(struct comedi_device *dev,

if (insn->n != 2) {
printk
- ("comedi%d: me4000: me4000_dio_insn_bits(): Invalid instruction length\n",
- dev->minor);
+ ("comedi%d: me4000: me4000_dio_insn_bits(): "
+ "Invalid instruction length\n", dev->minor);
return -EINVAL;
}

@@ -2095,8 +2125,9 @@ static int me4000_dio_insn_config(struct comedi_device *dev,
tmp |= ME4000_DIO_CTRL_BIT_MODE_0;
} else if (chan < 16) {
/*
- * Chech for optoisolated ME-4000 version. If one the first
- * port is a fixed output port and the second is a fixed input port.
+ * Chech for optoisolated ME-4000 version.
+ * If one the first port is a fixed output
+ * port and the second is a fixed input port.
*/
if (!me4000_inl(dev, info->dio_context.dir_reg))
return -ENODEV;
@@ -2121,8 +2152,9 @@ static int me4000_dio_insn_config(struct comedi_device *dev,
} else {
if (chan < 8) {
/*
- * Chech for optoisolated ME-4000 version. If one the first
- * port is a fixed output port and the second is a fixed input port.
+ * Chech for optoisolated ME-4000 version.
+ * If one the first port is a fixed output
+ * port and the second is a fixed input port.
*/
if (!me4000_inl(dev, info->dio_context.dir_reg))
return -ENODEV;
@@ -2257,7 +2289,8 @@ static int me4000_cnt_insn_config(struct comedi_device *dev,
case GPCT_RESET:
if (insn->n != 1) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction length%d\n",
+ "comedi%d: me4000: me4000_cnt_insn_config(): "
+ "Invalid instruction length%d\n",
dev->minor, insn->n);
return -EINVAL;
}
@@ -2269,7 +2302,8 @@ static int me4000_cnt_insn_config(struct comedi_device *dev,
case GPCT_SET_OPERATION:
if (insn->n != 2) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction length%d\n",
+ "comedi%d: me4000: me4000_cnt_insn_config(): "
+ "Invalid instruction length%d\n",
dev->minor, insn->n);
return -EINVAL;
}
@@ -2280,8 +2314,8 @@ static int me4000_cnt_insn_config(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction\n",
- dev->minor);
+ "comedi%d: me4000: me4000_cnt_insn_config(): "
+ "Invalid instruction\n", dev->minor);
return -EINVAL;
}

@@ -2302,7 +2336,8 @@ static int me4000_cnt_insn_read(struct comedi_device *dev,

if (insn->n > 1) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_cnt_insn_read(): Invalid instruction length %d\n",
+ "comedi%d: me4000: me4000_cnt_insn_read(): "
+ "Invalid instruction length %d\n",
dev->minor, insn->n);
return -EINVAL;
}
@@ -2328,7 +2363,8 @@ static int me4000_cnt_insn_read(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_cnt_insn_read(): Invalid channel %d\n",
+ "comedi%d: me4000: me4000_cnt_insn_read(): "
+ "Invalid channel %d\n",
dev->minor, insn->chanspec);
return -EINVAL;
}
@@ -2349,7 +2385,8 @@ static int me4000_cnt_insn_write(struct comedi_device *dev,
return 0;
} else if (insn->n > 1) {
printk(KERN_ERR
- "comedi%d: me4000: me4000_cnt_insn_write(): Invalid instruction length %d\n",
+ "comedi%d: me4000: me4000_cnt_insn_write(): "
+ "Invalid instruction length %d\n",
dev->minor, insn->n);
return -EINVAL;
}
@@ -2375,7 +2412,8 @@ static int me4000_cnt_insn_write(struct comedi_device *dev,
break;
default:
printk(KERN_ERR
- "comedi%d: me4000: me4000_cnt_insn_write(): Invalid channel %d\n",
+ "comedi%d: me4000: me4000_cnt_insn_write(): "
+ "Invalid channel %d\n",
dev->minor, insn->chanspec);
return -EINVAL;
}
--
1.5.4.3

Gustavo Silva

unread,
Jun 12, 2010, 8:20:02 PM6/12/10
to
This is a patch to the pcl711.c file that fixes up printk()
warning issues.


Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/pcl711.c | 14 +++++++-------
1 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/staging/comedi/drivers/pcl711.c b/drivers/staging/comedi/drivers/pcl711.c
index a499f70..d129ca7 100644
--- a/drivers/staging/comedi/drivers/pcl711.c
+++ b/drivers/staging/comedi/drivers/pcl711.c
@@ -270,7 +270,7 @@ static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
goto ok;
udelay(1);
}
- printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
+ printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
return -ETIME;

ok:
@@ -505,7 +505,7 @@ static int pcl711_do_insn_bits(struct comedi_device *dev,
/* Free any resources that we have claimed */
static int pcl711_detach(struct comedi_device *dev)
{
- printk("comedi%d: pcl711: remove\n", dev->minor);
+ printk(KERN_INFO "comedi%d: pcl711: remove\n", dev->minor);

if (dev->irq)
free_irq(dev->irq, dev);
@@ -527,7 +527,7 @@ static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
/* claim our I/O space */



iobase = it->options[0];

- printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
+ printk(KERN_INFO "comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
if (!request_region(iobase, PCL711_SIZE, "pcl711")) {


printk("I/O port conflict\n");

return -EIO;
@@ -542,15 +542,15 @@ static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
/* grab our IRQ */
irq = it->options[1];
if (irq > this_board->maxirq) {
- printk("irq out of range\n");
+ printk(KERN_ERR "irq out of range\n");
return -EINVAL;
}
if (irq) {
if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
- printk("unable to allocate irq %u\n", irq);
+ printk(KERN_ERR "unable to allocate irq %u\n", irq);
return -EINVAL;
} else {
- printk("( irq = %u )\n", irq);
+ printk(KERN_INFO "( irq = %u )\n", irq);
}
}
dev->irq = irq;
@@ -624,7 +624,7 @@ static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
outb(0, dev->iobase + PCL711_DA1_LO);
outb(0, dev->iobase + PCL711_DA1_HI);



- printk("\n");
+ printk(KERN_INFO "\n");

return 0;

Gustavo Silva

unread,
Jun 12, 2010, 11:30:01 PM6/12/10
to
This is a patch to the pcl812.c file that fixes up the following
issues:

ERROR: code indent should use tabs where possible x 27
WARNING: line over 80 characters x 37
WARNING: please, no space before tabs x 13
WARNING: braces {} are not necessary for single statement blocks x 2
WARNING: printk() should include KERN_ facility level x 22
WARNING: braces {} are not necessary for any arm of this statement x 5


Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/pcl812.c | 476 +++++++++++++++++--------------
1 files changed, 259 insertions(+), 217 deletions(-)

diff --git a/drivers/staging/comedi/drivers/pcl812.c b/drivers/staging/comedi/drivers/pcl812.c
index 1ddc19c..ce9c254 100644
--- a/drivers/staging/comedi/drivers/pcl812.c
+++ b/drivers/staging/comedi/drivers/pcl812.c
@@ -15,97 +15,98 @@
* card: A-823PGH, A-823PGL, A-826PG
* driver: a823pgh, a823pgl, a826pg
*/
+
/*
-Driver: pcl812
-Description: Advantech PCL-812/PG, PCL-813/B,
- ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216,
- ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG,
- ICP DAS ISO-813
-Author: Michal Dobes <do...@tesnet.cz>
-Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg),
- PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg),
- ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216),
- [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl),
- A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl),
- A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg)
-Updated: Mon, 06 Aug 2007 12:03:15 +0100
-Status: works (I hope. My board fire up under my hands
- and I cann't test all features.)
-
-This driver supports insn and cmd interfaces. Some boards support only insn
-becouse their hardware don't allow more (PCL-813/B, ACL-8113, ISO-813).
-Data transfer over DMA is supported only when you measure only one
-channel, this is too hardware limitation of these boards.
-
-Options for PCL-812:
- [0] - IO Base
- [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
- [2] - DMA (0=disable, 1, 3)
- [3] - 0=trigger source is internal 8253 with 2MHz clock
- 1=trigger source is external
- [4] - 0=A/D input range is +/-10V
- 1=A/D input range is +/-5V
- 2=A/D input range is +/-2.5V
- 3=A/D input range is +/-1.25V
- 4=A/D input range is +/-0.625V
- 5=A/D input range is +/-0.3125V
- [5] - 0=D/A outputs 0-5V (internal reference -5V)
- 1=D/A outputs 0-10V (internal reference -10V)
- 2=D/A outputs unknown (external reference)
-
-Options for PCL-812PG, ACL-8112PG:
- [0] - IO Base
- [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
- [2] - DMA (0=disable, 1, 3)
- [3] - 0=trigger source is internal 8253 with 2MHz clock
- 1=trigger source is external
- [4] - 0=A/D have max +/-5V input
- 1=A/D have max +/-10V input
- [5] - 0=D/A outputs 0-5V (internal reference -5V)
- 1=D/A outputs 0-10V (internal reference -10V)
- 2=D/A outputs unknown (external reference)
-
-Options for ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH, ACL-8216, A-826PG:
- [0] - IO Base
- [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
- [2] - DMA (0=disable, 1, 3)
- [3] - 0=trigger source is internal 8253 with 2MHz clock
- 1=trigger source is external
- [4] - 0=A/D channels are S.E.
- 1=A/D channels are DIFF
- [5] - 0=D/A outputs 0-5V (internal reference -5V)
- 1=D/A outputs 0-10V (internal reference -10V)
- 2=D/A outputs unknown (external reference)
-
-Options for A-821PGL/PGH:
- [0] - IO Base
- [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
- [2] - 0=A/D channels are S.E.
- 1=A/D channels are DIFF
- [3] - 0=D/A output 0-5V (internal reference -5V)
- 1=D/A output 0-10V (internal reference -10V)
-
-Options for A-821PGL-NDA:
- [0] - IO Base
- [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
- [2] - 0=A/D channels are S.E.
- 1=A/D channels are DIFF
-
-Options for PCL-813:
- [0] - IO Base
-
-Options for PCL-813B:
- [0] - IO Base
- [1] - 0= bipolar inputs
- 1= unipolar inputs
-
-Options for ACL-8113, ISO-813:
- [0] - IO Base
- [1] - 0= 10V bipolar inputs
- 1= 10V unipolar inputs
- 2= 20V bipolar inputs
- 3= 20V unipolar inputs
-*/
+ * Driver: pcl812
+ * Description: Advantech PCL-812/PG, PCL-813/B,
+ * ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216,
+ * ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG,
+ * ICP DAS ISO-813
+ * Author: Michal Dobes <do...@tesnet.cz>
+ * Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg),
+ * PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg),
+ * ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216),
+ * [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl),
+ * A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl),
+ * A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg)
+ * Updated: Mon, 06 Aug 2007 12:03:15 +0100
+ * Status: works (I hope. My board fire up under my hands
+ * and I cann't test all features.)
+ *
+ * This driver supports insn and cmd interfaces. Some boards support only insn
+ * becouse their hardware don't allow more (PCL-813/B, ACL-8113, ISO-813).
+ * Data transfer over DMA is supported only when you measure only one
+ * channel, this is too hardware limitation of these boards.
+ *
+ * Options for PCL-812:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0=trigger source is internal 8253 with 2MHz clock
+ * 1=trigger source is external
+ * [4] - 0=A/D input range is +/-10V
+ * 1=A/D input range is +/-5V
+ * 2=A/D input range is +/-2.5V
+ * 3=A/D input range is +/-1.25V
+ * 4=A/D input range is +/-0.625V
+ * 5=A/D input range is +/-0.3125V
+ * [5] - 0=D/A outputs 0-5V (internal reference -5V)
+ * 1=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ *
+ * Options for PCL-812PG, ACL-8112PG:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0=trigger source is internal 8253 with 2MHz clock
+ * 1=trigger source is external
+ * [4] - 0=A/D have max +/-5V input
+ * 1=A/D have max +/-10V input
+ * [5] - 0=D/A outputs 0-5V (internal reference -5V)
+ * 1=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ *
+ * Options for ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH, ACL-8216, A-826PG:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)
+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0=trigger source is internal 8253 with 2MHz clock
+ * 1=trigger source is external
+ * [4] - 0=A/D channels are S.E.
+ * 1=A/D channels are DIFF
+ * [5] - 0=D/A outputs 0-5V (internal reference -5V)
+ * 1=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ *
+ * Options for A-821PGL/PGH:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - 0=A/D channels are S.E.
+ * 1=A/D channels are DIFF
+ * [3] - 0=D/A output 0-5V (internal reference -5V)
+ * 1=D/A output 0-10V (internal reference -10V)
+ *
+ * Options for A-821PGL-NDA:
+ * [0] - IO Base
+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)
+ * [2] - 0=A/D channels are S.E.
+ * 1=A/D channels are DIFF
+ *
+ * Options for PCL-813:
+ * [0] - IO Base
+ *
+ * Options for PCL-813B:
+ * [0] - IO Base
+ * [1] - 0= bipolar inputs
+ * 1= unipolar inputs
+ *
+ * Options for ACL-8113, ISO-813:
+ * [0] - IO Base
+ * [1] - 0= 10V bipolar inputs
+ * 1= 10V unipolar inputs
+ * 2= 20V bipolar inputs
+ * 3= 20V unipolar inputs
+ */

#include <linux/interrupt.h>
#include <linux/gfp.h>
@@ -117,49 +118,50 @@ Options for ACL-8113, ISO-813:

#include "8253.h"

-#undef PCL812_EXTDEBUG /* if this is defined then a lot of messages is printed */
+/* if this is defined then a lot of messages is printed */
+#undef PCL812_EXTDEBUG

/* hardware types of the cards */
-#define boardPCL812PG 0 /* and ACL-8112PG */
-#define boardPCL813B 1
-#define boardPCL812 2
-#define boardPCL813 3
-#define boardISO813 5
-#define boardACL8113 6
-#define boardACL8112 7 /* ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH */
-#define boardACL8216 8 /* and ICP DAS A-826PG */
-#define boardA821 9 /* PGH, PGL, PGL/NDA versions */
-
-#define PCLx1x_IORANGE 16
-
-#define PCL812_CTR0 0
-#define PCL812_CTR1 1
-#define PCL812_CTR2 2
-#define PCL812_CTRCTL 3
-#define PCL812_AD_LO 4
-#define PCL812_DA1_LO 4
-#define PCL812_AD_HI 5
-#define PCL812_DA1_HI 5
-#define PCL812_DA2_LO 6
-#define PCL812_DI_LO 6
-#define PCL812_DA2_HI 7
-#define PCL812_DI_HI 7
-#define PCL812_CLRINT 8
-#define PCL812_GAIN 9
-#define PCL812_MUX 10
-#define PCL812_MODE 11
-#define PCL812_CNTENABLE 10
-#define PCL812_SOFTTRIG 12
-#define PCL812_DO_LO 13
-#define PCL812_DO_HI 14
-
-#define PCL812_DRDY 0x10 /* =0 data ready */
-
-#define ACL8216_STATUS 8 /* 5. bit signalize data ready */
-
-#define ACL8216_DRDY 0x20 /* =0 data ready */
-
-#define MAX_CHANLIST_LEN 256 /* length of scan list */
+#define boardPCL812PG 0 /* and ACL-8112PG */
+#define boardPCL813B 1
+#define boardPCL812 2
+#define boardPCL813 3
+#define boardISO813 5
+#define boardACL8113 6
+#define boardACL8112 7 /* ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH */
+#define boardACL8216 8 /* and ICP DAS A-826PG */
+#define boardA821 9 /* PGH, PGL, PGL/NDA versions */
+
+#define PCLx1x_IORANGE 16
+
+#define PCL812_CTR0 0
+#define PCL812_CTR1 1
+#define PCL812_CTR2 2
+#define PCL812_CTRCTL 3
+#define PCL812_AD_LO 4
+#define PCL812_DA1_LO 4
+#define PCL812_AD_HI 5
+#define PCL812_DA1_HI 5
+#define PCL812_DA2_LO 6
+#define PCL812_DI_LO 6
+#define PCL812_DA2_HI 7
+#define PCL812_DI_HI 7
+#define PCL812_CLRINT 8
+#define PCL812_GAIN 9
+#define PCL812_MUX 10
+#define PCL812_MODE 11
+#define PCL812_CNTENABLE 10
+#define PCL812_SOFTTRIG 12
+#define PCL812_DO_LO 13
+#define PCL812_DO_HI 14
+
+#define PCL812_DRDY 0x10 /* =0 data ready */
+
+#define ACL8216_STATUS 8 /* 5. bit signalize data ready */
+
+#define ACL8216_DRDY 0x20 /* =0 data ready */
+
+#define MAX_CHANLIST_LEN 256 /* length of scan list */

static const struct comedi_lrange range_pcl812pg_ai = { 5, {
BIP_RANGE(5),
@@ -466,10 +468,13 @@ static int pcl812_ai_insn_read(struct comedi_device *dev,
int n;
int timeout, hi;

- outb(devpriv->mode_reg_int | 1, dev->iobase + PCL812_MODE); /* select software trigger */
- setup_range_channel(dev, s, insn->chanspec, 1); /* select channel and renge */
+ /* select software trigger */
+ outb(devpriv->mode_reg_int | 1, dev->iobase + PCL812_MODE);
+ /* select channel and renge */
+ setup_range_channel(dev, s, insn->chanspec, 1);
for (n = 0; n < insn->n; n++) {
- outb(255, dev->iobase + PCL812_SOFTTRIG); /* start conversion */
+ /* start conversion */
+ outb(255, dev->iobase + PCL812_SOFTTRIG);
udelay(5);
timeout = 50; /* wait max 50us, it must finish under 33us */
while (timeout--) {
@@ -501,10 +506,13 @@ static int acl8216_ai_insn_read(struct comedi_device *dev,
int n;
int timeout;

- outb(1, dev->iobase + PCL812_MODE); /* select software trigger */
- setup_range_channel(dev, s, insn->chanspec, 1); /* select channel and renge */
+ /* select software trigger */
+ outb(1, dev->iobase + PCL812_MODE);
+ /* select channel and renge */
+ setup_range_channel(dev, s, insn->chanspec, 1);
for (n = 0; n < insn->n; n++) {
- outb(255, dev->iobase + PCL812_SOFTTRIG); /* start conversion */
+ /* start conversion */
+ outb(255, dev->iobase + PCL812_SOFTTRIG);
udelay(5);
timeout = 50; /* wait max 50us, it must finish under 33us */
while (timeout--) {
@@ -558,9 +566,8 @@ static int pcl812_ao_insn_read(struct comedi_device *dev,
int chan = CR_CHAN(insn->chanspec);
int i;

- for (i = 0; i < insn->n; i++) {
+ for (i = 0; i < insn->n; i++)
data[i] = devpriv->ao_readback[chan];
- }

return i;
}
@@ -608,14 +615,15 @@ static int pcl812_do_insn_bits(struct comedi_device *dev,
*/
static void pcl812_cmdtest_out(int e, struct comedi_cmd *cmd)
{
- printk("pcl812 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,
+ printk(KERN_INFO "pcl812 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,
cmd->start_src, cmd->scan_begin_src, cmd->convert_src);
- printk("pcl812 e=%d startarg=%d scanarg=%d convarg=%d\n", e,
+ printk(KERN_INFO "pcl812 e=%d startarg=%d scanarg=%d convarg=%d\n", e,
cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg);
- printk("pcl812 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src,
- cmd->scan_end_src);
- printk("pcl812 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", e,
- cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len);
+ printk(KERN_INFO "pcl812 e=%d stopsrc=%x scanend=%x\n", e,
+ cmd->stop_src, cmd->scan_end_src);
+ printk(KERN_INFO "pcl812 e=%d stoparg=%d scanendarg=%d "
+ "chanlistlen=%d\n", e, cmd->stop_arg, cmd->scan_end_arg,
+ cmd->chanlist_len);
}
#endif

@@ -645,11 +653,11 @@ static int pcl812_ai_cmdtest(struct comedi_device *dev,
err++;

tmp = cmd->convert_src;
- if (devpriv->use_ext_trg) {
+ if (devpriv->use_ext_trg)
cmd->convert_src &= TRIG_EXT;
- } else {
+ else
cmd->convert_src &= TRIG_TIMER;
- }
+
if (!cmd->convert_src || tmp != cmd->convert_src)
err++;

@@ -673,7 +681,10 @@ static int pcl812_ai_cmdtest(struct comedi_device *dev,
return 1;
}

- /* step 2: make sure trigger sources are unique and mutually compatible */
+ /*
+ * step 2: make sure trigger sources are
+ * unique and mutually compatible
+ */

if (cmd->start_src != TRIG_NOW) {
cmd->start_src = TRIG_NOW;
@@ -807,7 +818,7 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
struct comedi_cmd *cmd = &s->async->cmd;

#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: BGN: pcl812_ai_cmd(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: BGN: pcl812_ai_cmd(...)\n");
#endif

if (cmd->start_src != TRIG_NOW)
@@ -842,13 +853,15 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
devpriv->ai_n_chan = cmd->chanlist_len;
memcpy(devpriv->ai_chanlist, cmd->chanlist,
sizeof(unsigned int) * cmd->scan_end_arg);
- setup_range_channel(dev, s, devpriv->ai_chanlist[0], 1); /* select first channel and range */
+ /* select first channel and range */
+ setup_range_channel(dev, s, devpriv->ai_chanlist[0], 1);

if (devpriv->dma) { /* check if we can use DMA transfer */
devpriv->ai_dma = 1;
for (i = 1; i < devpriv->ai_n_chan; i++)
if (devpriv->ai_chanlist[0] != devpriv->ai_chanlist[i]) {
- devpriv->ai_dma = 0; /* we cann't use DMA :-( */
+ /* we cann't use DMA :-( */
+ devpriv->ai_dma = 0;
break;
}
} else
@@ -869,14 +882,18 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
devpriv->ai_poll_ptr = 0;
s->async->cur_chan = 0;

- if ((devpriv->ai_flags & TRIG_WAKE_EOS)) { /* don't we want wake up every scan? */
+ /* don't we want wake up every scan? */
+ if ((devpriv->ai_flags & TRIG_WAKE_EOS)) {
devpriv->ai_eos = 1;
+
+ /* DMA is useless for this situation */
if (devpriv->ai_n_chan == 1)
- devpriv->ai_dma = 0; /* DMA is useless for this situation */
+ devpriv->ai_dma = 0;
}

if (devpriv->ai_dma) {
- if (devpriv->ai_eos) { /* we use EOS, so adapt DMA buffer to one scan */
+ /* we use EOS, so adapt DMA buffer to one scan */
+ if (devpriv->ai_eos) {
devpriv->dmabytestomove[0] =
devpriv->ai_n_chan * sizeof(short);
devpriv->dmabytestomove[1] =
@@ -894,9 +911,17 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
if (devpriv->ai_neverending) {
devpriv->dma_runs_to_end = 1;
} else {
- bytes = devpriv->ai_n_chan * devpriv->ai_scans * sizeof(short); /* how many samples we must transfer? */
- devpriv->dma_runs_to_end = bytes / devpriv->dmabytestomove[0]; /* how many DMA pages we must fill */
- devpriv->last_dma_run = bytes % devpriv->dmabytestomove[0]; /* on last dma transfer must be moved */
+ /* how many samples we must transfer? */
+ bytes = devpriv->ai_n_chan *
+ devpriv->ai_scans * sizeof(short);
+
+ /* how many DMA pages we must fill */
+ devpriv->dma_runs_to_end =
+ bytes / devpriv->dmabytestomove[0];
+
+ /* on last dma transfer must be moved */
+ devpriv->last_dma_run =
+ bytes % devpriv->dmabytestomove[0];
if (devpriv->dma_runs_to_end == 0)
devpriv->dmabytestomove[0] =
devpriv->last_dma_run;
@@ -934,14 +959,13 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
break;
}

- if (devpriv->ai_dma) {
- outb(devpriv->mode_reg_int | 2, dev->iobase + PCL812_MODE); /* let's go! */
- } else {
- outb(devpriv->mode_reg_int | 6, dev->iobase + PCL812_MODE); /* let's go! */
- }
+ if (devpriv->ai_dma) /* let's go! */
+ outb(devpriv->mode_reg_int | 2, dev->iobase + PCL812_MODE);
+ else /* let's go! */
+ outb(devpriv->mode_reg_int | 6, dev->iobase + PCL812_MODE);

#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: END: pcl812_ai_cmd(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: END: pcl812_ai_cmd(...)\n");
#endif

return 0;
@@ -983,7 +1007,8 @@ static irqreturn_t interrupt_pcl812_ai_int(int irq, void *d)

if (err) {
printk
- ("comedi%d: pcl812: (%s at 0x%lx) A/D cmd IRQ without DRDY!\n",
+ ("comedi%d: pcl812: (%s at 0x%lx) "
+ "A/D cmd IRQ without DRDY!\n",
dev->minor, dev->board_name, dev->iobase);
pcl812_ai_cancel(dev, s);
s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
@@ -1009,7 +1034,8 @@ static irqreturn_t interrupt_pcl812_ai_int(int irq, void *d)
if (next_chan == 0) { /* one scan done */
devpriv->ai_act_scan++;
if (!(devpriv->ai_neverending))
- if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */
+ /* all data sampled */
+ if (devpriv->ai_act_scan >= devpriv->ai_scans) {
pcl812_ai_cancel(dev, s);
s->async->events |= COMEDI_CB_EOA;
}
@@ -1030,14 +1056,16 @@ static void transfer_from_dma_buf(struct comedi_device *dev,

s->async->events = 0;
for (i = len; i; i--) {
- comedi_buf_put(s->async, ptr[bufptr++]); /* get one sample */
+ /* get one sample */
+ comedi_buf_put(s->async, ptr[bufptr++]);

s->async->cur_chan++;
if (s->async->cur_chan >= devpriv->ai_n_chan) {
s->async->cur_chan = 0;
devpriv->ai_act_scan++;
if (!devpriv->ai_neverending)
- if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */
+ /* all data sampled */
+ if (devpriv->ai_act_scan >= devpriv->ai_scans) {
pcl812_ai_cancel(dev, s);
s->async->events |= COMEDI_CB_EOA;
break;
@@ -1060,7 +1088,7 @@ static irqreturn_t interrupt_pcl812_ai_dma(int irq, void *d)
short *ptr;

#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: BGN: interrupt_pcl812_ai_dma(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: BGN: interrupt_pcl812_ai_dma(...)\n");
#endif
ptr = (short *)devpriv->dmabuf[devpriv->next_dma_buf];
len = (devpriv->dmabytestomove[devpriv->next_dma_buf] >> 1) -
@@ -1095,7 +1123,7 @@ static irqreturn_t interrupt_pcl812_ai_dma(int irq, void *d)
transfer_from_dma_buf(dev, s, ptr, bufptr, len);

#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: END: interrupt_pcl812_ai_dma(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: END: interrupt_pcl812_ai_dma(...)\n");
#endif
return IRQ_HANDLED;
}
@@ -1111,11 +1139,10 @@ static irqreturn_t interrupt_pcl812(int irq, void *d)
comedi_error(dev, "spurious interrupt");
return IRQ_HANDLED;
}
- if (devpriv->ai_dma) {
+ if (devpriv->ai_dma)
return interrupt_pcl812_ai_dma(irq, d);
- } else {
+ else
return interrupt_pcl812_ai_int(irq, d);
- };
}

/*
@@ -1132,7 +1159,8 @@ static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
spin_lock_irqsave(&dev->spinlock, flags);

for (i = 0; i < 10; i++) {
- top1 = get_dma_residue(devpriv->ai_dma); /* where is now DMA */
+ /* where is now DMA */
+ top1 = get_dma_residue(devpriv->ai_dma);
top2 = get_dma_residue(devpriv->ai_dma);
if (top1 == top2)
break;
@@ -1142,8 +1170,8 @@ static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
spin_unlock_irqrestore(&dev->spinlock, flags);
return 0;
}
-
- top1 = devpriv->dmabytestomove[1 - devpriv->next_dma_buf] - top1; /* where is now DMA in buffer */
+ /* where is now DMA in buffer */
+ top1 = devpriv->dmabytestomove[1 - devpriv->next_dma_buf] - top1;
top1 >>= 1; /* sample position */
top2 = top1 - devpriv->ai_poll_ptr;
if (top2 < 1) { /* no new samples */
@@ -1171,7 +1199,9 @@ static void setup_range_channel(struct comedi_device *dev,
unsigned int rangechan, char wait)
{
unsigned char chan_reg = CR_CHAN(rangechan); /* normal board */
- unsigned char gain_reg = CR_RANGE(rangechan) + devpriv->range_correction; /* gain index */
+ /* gain index */
+ unsigned char gain_reg = CR_RANGE(rangechan) +
+ devpriv->range_correction;

if ((chan_reg == devpriv->old_chan_reg)
&& (gain_reg == devpriv->old_gain_reg))
@@ -1184,20 +1214,25 @@ static void setup_range_channel(struct comedi_device *dev,
if (devpriv->use_diff) {
chan_reg = chan_reg | 0x30; /* DIFF inputs */
} else {
- if (chan_reg & 0x80) {
- chan_reg = chan_reg | 0x20; /* SE inputs 8-15 */
- } else {
- chan_reg = chan_reg | 0x10; /* SE inputs 0-7 */
- }
+ if (chan_reg & 0x80)
+ /* SE inputs 8-15 */
+ chan_reg = chan_reg | 0x20;
+ else
+ /* SE inputs 0-7 */
+ chan_reg = chan_reg | 0x10;
}
}

outb(chan_reg, dev->iobase + PCL812_MUX); /* select channel */
outb(gain_reg, dev->iobase + PCL812_GAIN); /* select gain */

- if (wait) {
- udelay(devpriv->max_812_ai_mode0_rangewait); /* XXX this depends on selected range and can be very long for some high gain ranges! */
- }
+
+ if (wait)
+ /*
+ * XXX this depends on selected range and can be very long for
+ * some high gain ranges!
+ */
+ udelay(devpriv->max_812_ai_mode0_rangewait);
}

/*
@@ -1207,8 +1242,8 @@ static void start_pacer(struct comedi_device *dev, int mode,
unsigned int divisor1, unsigned int divisor2)
{
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: BGN: start_pacer(%d,%u,%u)\n", mode, divisor1,
- divisor2);
+ printk(KERN_DEBUG "pcl812 EDBG: BGN: start_pacer(%d,%u,%u)\n", mode,
+ divisor1, divisor2);
#endif
outb(0xb4, dev->iobase + PCL812_CTRCTL);
outb(0x74, dev->iobase + PCL812_CTRCTL);
@@ -1221,7 +1256,7 @@ static void start_pacer(struct comedi_device *dev, int mode,
outb((divisor1 >> 8) & 0xff, dev->iobase + PCL812_CTR1);
}
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: END: start_pacer(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: END: start_pacer(...)\n");
#endif
}

@@ -1252,16 +1287,17 @@ static int pcl812_ai_cancel(struct comedi_device *dev,
struct comedi_subdevice *s)
{
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: BGN: pcl812_ai_cancel(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: BGN: pcl812_ai_cancel(...)\n");
#endif
if (devpriv->ai_dma)
disable_dma(devpriv->dma);
outb(0, dev->iobase + PCL812_CLRINT); /* clear INT request */
- outb(devpriv->mode_reg_int | 0, dev->iobase + PCL812_MODE); /* Stop A/D */
+ /* Stop A/D */
+ outb(devpriv->mode_reg_int | 0, dev->iobase + PCL812_MODE);
start_pacer(dev, -1, 0, 0); /* stop 8254 */
outb(0, dev->iobase + PCL812_CLRINT); /* clear INT request */
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: END: pcl812_ai_cancel(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: END: pcl812_ai_cancel(...)\n");
#endif
return 0;
}
@@ -1272,7 +1308,7 @@ static int pcl812_ai_cancel(struct comedi_device *dev,
static void pcl812_reset(struct comedi_device *dev)
{
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: BGN: pcl812_reset(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: BGN: pcl812_reset(...)\n");
#endif
outb(0, dev->iobase + PCL812_MUX);
outb(0 + devpriv->range_correction, dev->iobase + PCL812_GAIN);
@@ -1304,7 +1340,7 @@ static void pcl812_reset(struct comedi_device *dev)
}
udelay(5);
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: END: pcl812_reset(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: END: pcl812_reset(...)\n");
#endif
}

@@ -1322,8 +1358,8 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
int n_subdevices;



iobase = it->options[0];

- printk("comedi%d: pcl812: board=%s, ioport=0x%03lx", dev->minor,
- this_board->name, iobase);
+ printk(KERN_INFO "comedi%d: pcl812: board=%s, ioport=0x%03lx",
+ dev->minor, this_board->name, iobase);

if (!request_region(iobase, this_board->io_range, "pcl812")) {


printk("I/O port conflict\n");

@@ -1345,18 +1381,18 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
if (irq) { /* we want to use IRQ */
if (((1 << irq) & this_board->IRQbits) == 0) {
printk
- (", IRQ %u is out of allowed range, DISABLING IT",
- irq);
+ (", IRQ %u is out of allowed range, "
+ "DISABLING IT", irq);
irq = 0; /* Bad IRQ */
} else {
if (request_irq
(irq, interrupt_pcl812, 0, "pcl812", dev)) {
printk
- (", unable to allocate IRQ %u, DISABLING IT",
- irq);
+ (", unable to allocate IRQ %u, "
+ "DISABLING IT", irq);
irq = 0; /* Can't use IRQ */
} else {
- printk(", irq=%u", irq);
+ printk(KERN_INFO ", irq=%u", irq);
}
}
}
@@ -1376,16 +1412,20 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
}
ret = request_dma(dma, "pcl812");
if (ret) {
- printk(", unable to allocate DMA %u, FAIL!\n", dma);
+ printk(KERN_ERR ", unable to allocate DMA %u, FAIL!\n",
+ dma);
return -EBUSY; /* DMA isn't free */
}
devpriv->dma = dma;
- printk(", dma=%u", dma);
+ printk(KERN_INFO ", dma=%u", dma);
pages = 1; /* we want 8KB */
devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages);
if (!devpriv->dmabuf[0]) {
printk(", unable to allocate DMA buffer, FAIL!\n");
- /* maybe experiment with try_to_free_pages() will help .... */
+ /*
+ * maybe experiment with try_to_free_pages()
+ * will help ....
+ */
free_resources(dev);
return -EBUSY; /* no buffer :-( */
}
@@ -1394,7 +1434,7 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
devpriv->hwdmasize[0] = PAGE_SIZE * (1 << pages);
devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages);
if (!devpriv->dmabuf[1]) {
- printk(", unable to allocate DMA buffer, FAIL!\n");
+ printk(KERN_ERR ", unable to allocate DMA buffer, FAIL!\n");
free_resources(dev);
return -EBUSY;
}
@@ -1457,11 +1497,11 @@ no_dma:
s->maxdata = this_board->ai_maxdata;
s->len_chanlist = MAX_CHANLIST_LEN;
s->range_table = this_board->rangelist_ai;
- if (this_board->board_type == boardACL8216) {
+ if (this_board->board_type == boardACL8216)
s->insn_read = acl8216_ai_insn_read;
- } else {
+ else
s->insn_read = pcl812_ai_insn_read;
- }
+
devpriv->use_MPC = this_board->haveMPC508;
s->cancel = pcl812_ai_cancel;
if (dev->irq) {
@@ -1500,8 +1540,8 @@ no_dma:
s->range_table = &range_bipolar10;
break;
printk
- (", incorrect range number %d, changing to 0 (+/-10V)",
- it->options[4]);
+ (", incorrect range number %d, changing "
+ "to 0 (+/-10V)", it->options[4]);
break;
}
break;
@@ -1530,8 +1570,8 @@ no_dma:
s->range_table = &range_iso813_1_ai;
break;
printk
- (", incorrect range number %d, changing to 0 ",
- it->options[1]);
+ (", incorrect range number %d, "
+ "changing to 0 ", it->options[1]);
break;
}
break;
@@ -1555,8 +1595,8 @@ no_dma:
s->range_table = &range_acl8113_1_ai;
break;
printk
- (", incorrect range number %d, changing to 0 ",
- it->options[1]);
+ (", incorrect range number %d, "
+ "changing to 0 ", it->options[1]);
break;
}
break;
@@ -1627,7 +1667,8 @@ no_dma:
case boardACL8112:
devpriv->max_812_ai_mode0_rangewait = 1;
if (it->options[3] > 0)
- devpriv->use_ext_trg = 1; /* we use external trigger */
+ /* we use external trigger */
+ devpriv->use_ext_trg = 1;
case boardA821:
devpriv->max_812_ai_mode0_rangewait = 1;
devpriv->mode_reg_int = (irq << 4) & 0xf0;
@@ -1636,11 +1677,12 @@ no_dma:
case boardPCL813:
case boardISO813:
case boardACL8113:
- devpriv->max_812_ai_mode0_rangewait = 5; /* maybe there must by greatest timeout */
+ /* maybe there must by greatest timeout */
+ devpriv->max_812_ai_mode0_rangewait = 5;
break;


}

- printk("\n");
+ printk(KERN_INFO "\n");

devpriv->valid = 1;

pcl812_reset(dev);
@@ -1655,7 +1697,7 @@ static int pcl812_detach(struct comedi_device *dev)
{

#ifdef PCL812_EXTDEBUG
- printk("comedi%d: pcl812: remove\n", dev->minor);
+ printk(KERN_DEBUG "comedi%d: pcl812: remove\n", dev->minor);
#endif
free_resources(dev);

Ian Abbott

unread,
Jun 14, 2010, 6:20:01 AM6/14/10
to
On 13/06/10 04:25, Gustavo Silva wrote:
> + * Driver: pcl812
> + * Description: Advantech PCL-812/PG, PCL-813/B,
> + * ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216,
> + * ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG,
> + * ICP DAS ISO-813
> + * Author: Michal Dobes <do...@tesnet.cz>
> + * Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg),
> + * PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg),
> + * ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216),
> + * [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl),
> + * A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl),
> + * A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg)

Please add extra whitespace (a tab or a couple of spaces) to the
"Devices:" continuation lines here, e.g.:

* Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg),

* PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg),

* ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216),

* [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl),

* A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl),

* A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg)

This extra whitespace is for the benefit of some scripts we have in
comedi and comedilib for extracting the documentation. (Actually, these
scripts don't handle the " * " at the beginning of the line yet, but I'm
working on that!)

Thanks.

--
-=( Ian Abbott @ MEV Ltd. E-mail: <abb...@mev.co.uk> )=-
-=( Tel: +44 (0)161 477 1898 FAX: +44 (0)161 718 3587 )=-

Gustavo Silva

unread,
Jun 15, 2010, 1:50:01 AM6/15/10
to
2010/6/14 Ian Abbott <abb...@mev.co.uk>:

Hi Ian,

Thanks for your comments,
let me known if this OK for you now.

Best Regards,
Gus.

This is a patch to the pcl812.c file that fixes up the following
issues:

ERROR: code indent should use tabs where possible x 27
WARNING: line over 80 characters x 37
WARNING: please, no space before tabs x 13
WARNING: braces {} are not necessary for single statement blocks x 2
WARNING: printk() should include KERN_ facility level x 22
WARNING: braces {} are not necessary for any arm of this statement x 5

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---
drivers/staging/comedi/drivers/pcl812.c | 476 +++++++++++++++++--------------
1 files changed, 259 insertions(+), 217 deletions(-)

diff --git a/drivers/staging/comedi/drivers/pcl812.c
b/drivers/staging/comedi/drivers/pcl812.c
index 1ddc19c..1ea239a 100644

+ * Driver: pcl812
+ * Description: Advantech PCL-812/PG, PCL-813/B,
+ * ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216,
+ * ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG,
+ * ICP DAS ISO-813
+ * Author: Michal Dobes <do...@tesnet.cz>
+ * Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg),
+ * PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg),
+ * ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216),
+ * [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl),
+ * A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl),
+ * A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg)

#include "8253.h"

Ian Abbott

unread,
Jun 15, 2010, 5:00:02 AM6/15/10
to

Hi Gus,

It looks fine. You can add my signed-off-by line if you want to.

Signed-off-by: Ian Abbott <abb...@mev.co.uk>


--

-=( Ian Abbott @ MEV Ltd. E-mail: <abb...@mev.co.uk> )=-
-=( Tel: +44 (0)161 477 1898 FAX: +44 (0)161 718 3587 )=-

Gustavo Silva

unread,
Jun 16, 2010, 1:20:02 AM6/16/10
to
This is a patch to the pcl816.c file that fixes up the following
issues:

ERROR: code indent should use tabs where possible x 2

WARNING: line over 80 characters x 34


WARNING: please, no space before tabs x 1

WARNING: braces {} are not necessary for single statement blocks x 6
WARNING: printk() should include KERN_ facility level x 15
WARNING: braces {} are not necessary for any arm of this statement x 1

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/pcl816.c | 223 +++++++++++++++++++------------
1 files changed, 140 insertions(+), 83 deletions(-)

diff --git a/drivers/staging/comedi/drivers/pcl816.c b/drivers/staging/comedi/drivers/pcl816.c
index 71c2a3a..4bfdda7 100644
--- a/drivers/staging/comedi/drivers/pcl816.c
+++ b/drivers/staging/comedi/drivers/pcl816.c
@@ -2,7 +2,7 @@
comedi/drivers/pcl816.c

Author: Juan Grigera <ju...@grigera.com.ar>
- based on pcl818 by Michal Dobes <do...@tesnet.cz> and bits of pcl812
+ based on pcl818 by Michal Dobes <do...@tesnet.cz> and bits of pcl812

hardware driver for Advantech cards:
card: PCL-816, PCL814B
@@ -28,7 +28,7 @@ Configuration Options:


[1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7)

[2] - DMA (0=disable, 1, 3)

[3] - 0, 10=10MHz clock for 8254
- 1= 1MHz clock for 8254
+ 1= 1MHz clock for 8254

*/

@@ -85,7 +85,7 @@ Configuration Options:
#define INT_TYPE_AI3_DMA_RTC 10

/* RTC stuff... */
-#define RTC_IRQ 8
+#define RTC_IRQ 8
#define RTC_IO_EXTENT 0x10
#endif

@@ -253,7 +253,8 @@ static int pcl816_ai_insn_read(struct comedi_device *dev,

/* Set the input channel */
outb(CR_CHAN(insn->chanspec) & 0xf, dev->iobase + PCL816_MUX);
- outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE); /* select gain */
+ /* select gain */
+ outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE);



for (n = 0; n < insn->n; n++) {

@@ -268,8 +269,8 @@ static int pcl816_ai_insn_read(struct comedi_device *dev,
((inb(dev->iobase +
PCL816_AD_HI) << 8) |
(inb(dev->iobase + PCL816_AD_LO)));
-
- outb(0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */
+ /* clear INT (conversion end) flag */
+ outb(0, dev->iobase + PCL816_CLRINT);
break;
}
udelay(1);
@@ -278,7 +279,8 @@ static int pcl816_ai_insn_read(struct comedi_device *dev,
if (!timeout) {
comedi_error(dev, "A/D insn timeout\n");
data[0] = 0;
- outb(0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */
+ /* clear INT (conversion end) flag */
+ outb(0, dev->iobase + PCL816_CLRINT);
return -EIO;
}

@@ -332,7 +334,8 @@ static irqreturn_t interrupt_pcl816_ai_mode13_int(int irq, void *d)


}

if (!devpriv->ai_neverending)
- if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */
+ /* all data sampled */
+ if (devpriv->ai_act_scan >= devpriv->ai_scans) {

/* all data sampled */

pcl816_ai_cancel(dev, s);


s->async->events |= COMEDI_CB_EOA;

@@ -369,7 +372,8 @@ static void transfer_from_dma_buf(struct comedi_device *dev,


}

if (!devpriv->ai_neverending)
- if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */
+ /* all data sampled */
+ if (devpriv->ai_act_scan >= devpriv->ai_scans) {

pcl816_ai_cancel(dev, s);
s->async->events |= COMEDI_CB_EOA;
s->async->events |= COMEDI_CB_BLOCK;
@@ -391,7 +395,8 @@ static irqreturn_t interrupt_pcl816_ai_mode13_dma(int irq, void *d)
disable_dma(devpriv->dma);
this_dma_buf = devpriv->next_dma_buf;

- if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) { /* switch dma bufs */
+ /* switch dma bufs */
+ if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) {

devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
set_dma_mode(devpriv->dma, DMA_MODE_READ);
@@ -467,14 +472,14 @@ static irqreturn_t interrupt_pcl816(int irq, void *d)
*/
static void pcl816_cmdtest_out(int e, struct comedi_cmd *cmd)
{
- printk("pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,
+ printk(KERN_INFO "pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,


cmd->start_src, cmd->scan_begin_src, cmd->convert_src);

- printk("pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e,
+ printk(KERN_INFO "pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e,


cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg);

- printk("pcl816 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src,
- cmd->scan_end_src);
- printk("pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", e,


- cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len);

+ printk(KERN_INFO "pcl816 e=%d stopsrc=%x scanend=%x\n", e,


+ cmd->stop_src, cmd->scan_end_src);

+ printk(KERN_INFO "pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n",
+ e, cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len);
}

/*
@@ -486,8 +491,9 @@ static int pcl816_ai_cmdtest(struct comedi_device *dev,
int err = 0;
int tmp, divisor1 = 0, divisor2 = 0;

- DEBUG(printk("pcl816 pcl812_ai_cmdtest\n"); pcl816_cmdtest_out(-1, cmd);
- );
+ DEBUG(printk(KERN_INFO "pcl816 pcl812_ai_cmdtest\n");
+ pcl816_cmdtest_out(-1, cmd);
+ );

/* step 1: make sure trigger sources are trivially valid */
tmp = cmd->start_src;
@@ -515,11 +521,14 @@ static int pcl816_ai_cmdtest(struct comedi_device *dev,
if (!cmd->stop_src || tmp != cmd->stop_src)
err++;

- if (err) {
+ if (err)
return 1;
- }


- /* step 2: make sure trigger sources are unique and mutually compatible */
+
+ /*
+ * step 2: make sure trigger sources

+ * are unique and mutually compatible


+ */

if (cmd->start_src != TRIG_NOW) {
cmd->start_src = TRIG_NOW;

@@ -544,9 +553,9 @@ static int pcl816_ai_cmdtest(struct comedi_device *dev,
if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT)
err++;

- if (err) {
+ if (err)
return 2;
- }
+

/* step 3: make sure arguments are trivially compatible */
if (cmd->start_arg != 0) {
@@ -586,9 +595,9 @@ static int pcl816_ai_cmdtest(struct comedi_device *dev,
}
}

- if (err) {
+ if (err)
return 3;
- }
+

/* step 4: fix up any arguments */
if (cmd->convert_src == TRIG_TIMER) {
@@ -603,9 +612,9 @@ static int pcl816_ai_cmdtest(struct comedi_device *dev,
err++;
}

- if (err) {
+ if (err)
return 4;
- }
+

/* step 5: complain about special chanlist considerations */

@@ -643,7 +652,9 @@ static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
i8253_cascade_ns_to_timer(this_board->i8254_osc_base, &divisor1,
&divisor2, &cmd->convert_arg,
cmd->flags & TRIG_ROUND_MASK);
- if (divisor1 == 1) { /* PCL816 crash if any divisor is set to 1 */
+
+ /* PCL816 crash if any divisor is set to 1 */
+ if (divisor1 == 1) {
divisor1 = 2;
divisor2 /= 2;
}
@@ -676,8 +687,10 @@ static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
devpriv->ai_neverending = 1;
}

- if ((cmd->flags & TRIG_WAKE_EOS)) { /* don't we want wake up every scan? */
- printk("pl816: You wankt WAKE_EOS but I dont want handle it");


+ /* don't we want wake up every scan? */

+ if ((cmd->flags & TRIG_WAKE_EOS)) {
+ printk(KERN_INFO
+ "pl816: You wankt WAKE_EOS but I dont want handle it");
/* devpriv->ai_eos=1; */
/* if (devpriv->ai_n_chan==1) */
/* devpriv->dma=0; // DMA is useless for this situation */
@@ -686,9 +699,17 @@ static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
if (devpriv->dma) {
bytes = devpriv->hwdmasize[0];
if (!devpriv->ai_neverending) {
- bytes = s->async->cmd.chanlist_len * s->async->cmd.chanlist_len * sizeof(short); /* how many */
- devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0]; /* how many DMA pages we must fill */
- devpriv->last_dma_run = bytes % devpriv->hwdmasize[0]; /* on last dma transfer must be moved */
+ /* how many */
+ bytes = s->async->cmd.chanlist_len *
+ s->async->cmd.chanlist_len *
+ sizeof(short);


+
+ /* how many DMA pages we must fill */

+ devpriv->dma_runs_to_end = bytes /
+ devpriv->hwdmasize[0];


+
+ /* on last dma transfer must be moved */

+ devpriv->last_dma_run = bytes % devpriv->hwdmasize[0];
devpriv->dma_runs_to_end--;
if (devpriv->dma_runs_to_end >= 0)
bytes = devpriv->hwdmasize[0];
@@ -711,14 +732,22 @@ static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
switch (cmd->convert_src) {
case TRIG_TIMER:
devpriv->int816_mode = INT_TYPE_AI1_DMA;
- outb(0x32, dev->iobase + PCL816_CONTROL); /* Pacer+IRQ+DMA */
- outb(dmairq, dev->iobase + PCL816_STATUS); /* write irq and DMA to card */
+
+ /* Pacer+IRQ+DMA */
+ outb(0x32, dev->iobase + PCL816_CONTROL);
+
+ /* write irq and DMA to card */
+ outb(dmairq, dev->iobase + PCL816_STATUS);
break;

default:
devpriv->int816_mode = INT_TYPE_AI3_DMA;
- outb(0x34, dev->iobase + PCL816_CONTROL); /* Ext trig+IRQ+DMA */
- outb(dmairq, dev->iobase + PCL816_STATUS); /* write irq to card */
+
+ /* Ext trig+IRQ+DMA */
+ outb(0x34, dev->iobase + PCL816_CONTROL);
+
+ /* write irq to card */
+ outb(dmairq, dev->iobase + PCL816_STATUS);
break;
}

@@ -747,7 +776,8 @@ static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
return 0;
}

- top1 = devpriv->hwdmasize[0] - top1; /* where is now DMA in buffer */


+ /* where is now DMA in buffer */

+ top1 = devpriv->hwdmasize[0] - top1;


top1 >>= 1; /* sample position */
top2 = top1 - devpriv->ai_poll_ptr;
if (top2 < 1) { /* no new samples */

@@ -787,16 +817,23 @@ static int pcl816_ai_cancel(struct comedi_device *dev,
disable_dma(devpriv->dma);
case INT_TYPE_AI1_INT:
case INT_TYPE_AI3_INT:
- outb(inb(dev->iobase + PCL816_CONTROL) & 0x73, dev->iobase + PCL816_CONTROL); /* Stop A/D */
+ outb(inb(dev->iobase + PCL816_CONTROL) & 0x73,
+ dev->iobase + PCL816_CONTROL); /* Stop A/D */
udelay(1);
outb(0, dev->iobase + PCL816_CONTROL); /* Stop A/D */
- outb(0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */
+
+ /* Stop pacer */
+ outb(0xb0, dev->iobase + PCL816_CTRCTL);
outb(0x70, dev->iobase + PCL816_CTRCTL);
outb(0, dev->iobase + PCL816_AD_LO);
inb(dev->iobase + PCL816_AD_LO);
inb(dev->iobase + PCL816_AD_HI);
- outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */
- outb(0, dev->iobase + PCL816_CONTROL); /* Stop A/D */
+
+ /* clear INT request */
+ outb(0, dev->iobase + PCL816_CLRINT);
+


+ /* Stop A/D */

+ outb(0, dev->iobase + PCL816_CONTROL);
devpriv->irq_blocked = 0;
devpriv->irq_was_now_closed = devpriv->int816_mode;
devpriv->int816_mode = 0;
@@ -866,8 +903,11 @@ start_pacer(struct comedi_device *dev, int mode, unsigned int divisor1,
outb(0xff, dev->iobase + PCL816_CTR0);
outb(0x00, dev->iobase + PCL816_CTR0);
udelay(1);
- outb(0xb4, dev->iobase + PCL816_CTRCTL); /* set counter 2 as mode 3 */
- outb(0x74, dev->iobase + PCL816_CTRCTL); /* set counter 1 as mode 3 */
+
+ /* set counter 2 as mode 3 */
+ outb(0xb4, dev->iobase + PCL816_CTRCTL);
+ /* set counter 1 as mode 3 */
+ outb(0x74, dev->iobase + PCL816_CTRCTL);
udelay(1);

if (mode == 1) {
@@ -903,41 +943,51 @@ check_channel_list(struct comedi_device *dev,
}

if (chanlen > 1) {
- chansegment[0] = chanlist[0]; /* first channel is everytime ok */
+ /* first channel is everytime ok */
+ chansegment[0] = chanlist[0];
for (i = 1, seglen = 1; i < chanlen; i++, seglen++) {
/* build part of chanlist */
- DEBUG(printk("%d. %d %d\n", i, CR_CHAN(chanlist[i]),
+ DEBUG(printk(KERN_INFO "%d. %d %d\n", i,
+ CR_CHAN(chanlist[i]),
CR_RANGE(chanlist[i]));)
+
+ /* we detect loop, this must by finish */
if (chanlist[0] == chanlist[i])
- break; /* we detect loop, this must by finish */
+ break;
nowmustbechan =
(CR_CHAN(chansegment[i - 1]) + 1) % chanlen;
if (nowmustbechan != CR_CHAN(chanlist[i])) {
/* channel list isn't continous :-( */
- printk
- ("comedi%d: pcl816: channel list must be continous! chanlist[%i]=%d but must be %d or %d!\n",
- dev->minor, i, CR_CHAN(chanlist[i]),
- nowmustbechan, CR_CHAN(chanlist[0]));
+ printk(KERN_WARNING
+ "comedi%d: pcl816: channel list must "
+ "be continous! chanlist[%i]=%d but "
+ "must be %d or %d!\n", dev->minor,
+ i, CR_CHAN(chanlist[i]), nowmustbechan,
+ CR_CHAN(chanlist[0]));
return 0;
}
- chansegment[i] = chanlist[i]; /* well, this is next correct channel in list */
+ /* well, this is next correct channel in list */
+ chansegment[i] = chanlist[i];
}

- for (i = 0, segpos = 0; i < chanlen; i++) { /* check whole chanlist */
+ /* check whole chanlist */
+ for (i = 0, segpos = 0; i < chanlen; i++) {
DEBUG(printk("%d %d=%d %d\n",
CR_CHAN(chansegment[i % seglen]),
CR_RANGE(chansegment[i % seglen]),
CR_CHAN(chanlist[i]),
CR_RANGE(chanlist[i]));)
if (chanlist[i] != chansegment[i % seglen]) {
- printk
- ("comedi%d: pcl816: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
- dev->minor, i, CR_CHAN(chansegment[i]),
- CR_RANGE(chansegment[i]),
- CR_AREF(chansegment[i]),
- CR_CHAN(chanlist[i % seglen]),
- CR_RANGE(chanlist[i % seglen]),
- CR_AREF(chansegment[i % seglen]));
+ printk(KERN_WARNING
+ "comedi%d: pcl816: bad channel or range"
+ " number! chanlist[%i]=%d,%d,%d and not"
+ " %d,%d,%d!\n", dev->minor, i,
+ CR_CHAN(chansegment[i]),
+ CR_RANGE(chansegment[i]),
+ CR_AREF(chansegment[i]),
+ CR_CHAN(chanlist[i % seglen]),
+ CR_RANGE(chanlist[i % seglen]),
+ CR_AREF(chansegment[i % seglen]));
return 0; /* chan/gain list is strange */
}
}
@@ -965,12 +1015,15 @@ setup_channel_list(struct comedi_device *dev,
for (i = 0; i < seglen; i++) { /* store range list to card */
devpriv->ai_act_chanlist[i] = CR_CHAN(chanlist[i]);
outb(CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX);
- outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE); /* select gain */
+ /* select gain */
+ outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE);
}

udelay(1);
-
- outb(devpriv->ai_act_chanlist[0] | (devpriv->ai_act_chanlist[seglen - 1] << 4), dev->iobase + PCL816_MUX); /* select channel interval to scan */
+ /* select channel interval to scan */
+ outb(devpriv->ai_act_chanlist[0] |
+ (devpriv->ai_act_chanlist[seglen - 1] << 4),
+ dev->iobase + PCL816_MUX);
}

#ifdef unused
@@ -998,11 +1051,11 @@ static int set_rtc_irq_bit(unsigned char bit)
save_flags(flags);
cli();
val = CMOS_READ(RTC_CONTROL);
- if (bit) {
+ if (bit)
val |= RTC_PIE;
- } else {
+ else
val &= ~RTC_PIE;
- }
+
CMOS_WRITE(val, RTC_CONTROL);
CMOS_READ(RTC_INTR_FLAGS);
restore_flags(flags);
@@ -1072,7 +1125,7 @@ static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
dev->iobase = iobase;

if (pcl816_check(iobase)) {
- printk(", I cann't detect board. FAIL!\n");
+ printk(KERN_ERR ", I cann't detect board. FAIL!\n");
return -EIO;
}

@@ -1090,30 +1143,29 @@ static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)


if (irq) { /* we want to use IRQ */
if (((1 << irq) & this_board->IRQbits) == 0) {
printk
- (", IRQ %u is out of allowed range, DISABLING IT",
- irq);
+ (", IRQ %u is out of allowed range, "
+ "DISABLING IT", irq);
irq = 0; /* Bad IRQ */
} else {
if (request_irq

(irq, interrupt_pcl816, 0, "pcl816", dev)) {


printk
- (", unable to allocate IRQ %u, DISABLING IT",
- irq);
+ (", unable to allocate IRQ %u, "
+ "DISABLING IT", irq);
irq = 0; /* Can't use IRQ */
} else {
- printk(", irq=%u", irq);
+ printk(KERN_INFO ", irq=%u", irq);
}
}
}
}

dev->irq = irq;
- if (irq) {
+ if (irq) /* 1=we have allocated irq */
devpriv->irq_free = 1;
- } /* 1=we have allocated irq */
- else {
+ else
devpriv->irq_free = 0;
- }
+
devpriv->irq_blocked = 0; /* number of subdevice which use IRQ */
devpriv->int816_mode = 0; /* mode of irq */

@@ -1170,18 +1222,22 @@ no_rtc:
}
ret = request_dma(dma, "pcl816");


if (ret) {
- printk(", unable to allocate DMA %u, FAIL!\n", dma);
+ printk(KERN_ERR

+ ", unable to allocate DMA %u, FAIL!\n", dma);


return -EBUSY; /* DMA isn't free */
}

devpriv->dma = dma;
- printk(", dma=%u", dma);
+ printk(KERN_INFO ", dma=%u", dma);

pages = 2; /* we need 16KB */


devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages);

if (!devpriv->dmabuf[0]) {
printk(", unable to allocate DMA buffer, FAIL!\n");
- /* maybe experiment with try_to_free_pages() will help .... */
+ /*
+ * maybe experiment with try_to_free_pages()
+ * will help ....
+ */

return -EBUSY; /* no buffer :-( */
}

devpriv->dmapages[0] = pages;
@@ -1192,8 +1248,9 @@ no_rtc:
if (devpriv->dma_rtc == 0) { /* we must do duble buff :-( */


devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages);
if (!devpriv->dmabuf[1]) {

- printk
- (", unable to allocate DMA buffer, FAIL!\n");
+ printk(KERN_ERR
+ ", unable to allocate DMA buffer, "
+ "FAIL!\n");
return -EBUSY;
}
devpriv->dmapages[1] = pages;
@@ -1277,7 +1334,7 @@ case COMEDI_SUBD_DO:
*/
static int pcl816_detach(struct comedi_device *dev)
{
- DEBUG(printk("comedi%d: pcl816: remove\n", dev->minor);)
+ DEBUG(printk(KERN_INFO "comedi%d: pcl816: remove\n", dev->minor);)
free_resources(dev);
#ifdef unused
if (devpriv->dma_rtc)
--
1.5.4.3

Gustavo Silva

unread,
Jun 16, 2010, 1:40:01 AM6/16/10
to
This is a patch to the pcmda12.c file that fixes up five printk() warning issues

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/pcmda12.c | 12 +++++++-----
1 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/drivers/staging/comedi/drivers/pcmda12.c b/drivers/staging/comedi/drivers/pcmda12.c
index 7133eb0..fd12b50 100644
--- a/drivers/staging/comedi/drivers/pcmda12.c
+++ b/drivers/staging/comedi/drivers/pcmda12.c
@@ -157,7 +157,8 @@ static int pcmda12_attach(struct comedi_device *dev,
unsigned long iobase;



iobase = it->options[0];

- printk("comedi%d: %s: io: %lx %s ", dev->minor, driver.driver_name,
+ printk(KERN_INFO
+ "comedi%d: %s: io: %lx %s ", dev->minor, driver.driver_name,
iobase, it->options[1] ? "simultaneous xfer mode enabled" : "");

if (!request_region(iobase, IOSIZE, driver.driver_name)) {
@@ -177,7 +178,7 @@ static int pcmda12_attach(struct comedi_device *dev,
* convenient macro defined in comedidev.h.
*/
if (alloc_private(dev, sizeof(struct pcmda12_private)) < 0) {
- printk("cannot allocate private data structure\n");
+ printk(KERN_ERR "cannot allocate private data structure\n");
return -ENOMEM;
}

@@ -191,7 +192,7 @@ static int pcmda12_attach(struct comedi_device *dev,
* 96-channel version of the board.
*/
if (alloc_subdevices(dev, 1) < 0) {
- printk("cannot allocate subdevice data structures\n");
+ printk(KERN_ERR "cannot allocate subdevice data structures\n");
return -ENOMEM;
}

@@ -207,7 +208,7 @@ static int pcmda12_attach(struct comedi_device *dev,

zero_chans(dev); /* clear out all the registers, basically */

- printk("attached\n");
+ printk(KERN_INFO "attached\n");

return 1;
}
@@ -222,7 +223,8 @@ static int pcmda12_attach(struct comedi_device *dev,
*/
static int pcmda12_detach(struct comedi_device *dev)
{
- printk("comedi%d: %s: remove\n", dev->minor, driver.driver_name);
+ printk(KERN_INFO
+ "comedi%d: %s: remove\n", dev->minor, driver.driver_name);
if (dev->iobase)
release_region(dev->iobase, IOSIZE);
return 0;

Gustavo Silva

unread,
Jun 16, 2010, 2:10:02 AM6/16/10
to
This is a patch to the quatech_daqp_cs.c file that fixes up the following
issues:

ERROR: code indent should use tabs where possible x 1
WARNING: line over 80 characters x 1
WARNING: braces {} are not necessary for single statement blocks x 10
WARNING: printk() should include KERN_ facility level x 6

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
---

drivers/staging/comedi/drivers/quatech_daqp_cs.c | 53 +++++++++++-----------
1 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/drivers/staging/comedi/drivers/quatech_daqp_cs.c b/drivers/staging/comedi/drivers/quatech_daqp_cs.c
index a91db6c..70c820a 100644
--- a/drivers/staging/comedi/drivers/quatech_daqp_cs.c
+++ b/drivers/staging/comedi/drivers/quatech_daqp_cs.c
@@ -14,7 +14,7 @@

Documentation for the DAQP PCMCIA cards can be found on Quatech's site:

- ftp://ftp.quatech.com/Manuals/daqp-208.pdf
+ ftp://ftp.quatech.com/Manuals/daqp-208.pdf

This manual is for both the DAQP-208 and the DAQP-308.

@@ -195,7 +195,7 @@ static struct comedi_driver driver_daqp = {

static void daqp_dump(struct comedi_device *dev)
{
- printk("DAQP: status %02x; aux status %02x\n",
+ printk(KERN_INFO "DAQP: status %02x; aux status %02x\n",
inb(dev->iobase + DAQP_STATUS), inb(dev->iobase + DAQP_AUX));
}

@@ -207,9 +207,9 @@ static void hex_dump(char *str, void *ptr, int len)
printk(str);

for (i = 0; i < len; i++) {
- if (i % 16 == 0) {
+ if (i % 16 == 0)
printk("\n0x%08x:", (unsigned int)cptr);
- }
+
printk(" %02x", *(cptr++));
}
printk("\n");
@@ -223,9 +223,9 @@ static int daqp_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
{
struct local_info_t *local = (struct local_info_t *)s->private;

- if (local->stop) {
+ if (local->stop)
return -EIO;
- }
+

outb(DAQP_COMMAND_STOP, dev->iobase + DAQP_COMMAND);

@@ -355,9 +355,9 @@ static int daqp_ai_insn_read(struct comedi_device *dev,
int v;
int counter = 10000;

- if (local->stop) {
+ if (local->stop)
return -EIO;
- }
+

/* Stop any running conversion */
daqp_ai_cancel(dev, s);
@@ -372,9 +372,9 @@ static int daqp_ai_insn_read(struct comedi_device *dev,
v = DAQP_SCANLIST_CHANNEL(CR_CHAN(insn->chanspec))
| DAQP_SCANLIST_GAIN(CR_RANGE(insn->chanspec));

- if (CR_AREF(insn->chanspec) == AREF_DIFF) {
+ if (CR_AREF(insn->chanspec) == AREF_DIFF)
v |= DAQP_SCANLIST_DIFFERENTIAL;
- }
+

v |= DAQP_SCANLIST_START;

@@ -488,7 +488,10 @@ static int daqp_ai_cmdtest(struct comedi_device *dev,


if (err)
return 1;

- /* step 2: make sure trigger sources are unique and mutually compatible */
+ /*
+ * step 2: make sure trigger sources
+ * are unique and mutually compatible
+ */

/* note that mutual compatibility is not an issue here */


if (cmd->scan_begin_src != TRIG_TIMER &&

@@ -588,9 +591,9 @@ static int daqp_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
int i;
int v;

- if (local->stop) {
+ if (local->stop)
return -EIO;
- }
+

/* Stop any running conversion */
daqp_ai_cancel(dev, s);
@@ -640,13 +643,11 @@ static int daqp_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
v = DAQP_SCANLIST_CHANNEL(CR_CHAN(chanspec))
| DAQP_SCANLIST_GAIN(CR_RANGE(chanspec));

- if (CR_AREF(chanspec) == AREF_DIFF) {
+ if (CR_AREF(chanspec) == AREF_DIFF)
v |= DAQP_SCANLIST_DIFFERENTIAL;
- }

- if (i == 0 || scanlist_start_on_every_entry) {
+ if (i == 0 || scanlist_start_on_every_entry)
v |= DAQP_SCANLIST_START;
- }

outb(v & 0xff, dev->iobase + DAQP_SCANLIST);
outb(v >> 8, dev->iobase + DAQP_SCANLIST);
@@ -760,7 +761,8 @@ static int daqp_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
while (--counter
&& (inb(dev->iobase + DAQP_STATUS) & DAQP_STATUS_EVENTS)) ;
if (!counter) {
- printk("daqp: couldn't clear interrupts in status register\n");
+ printk(KERN_ERR
+ "daqp: couldn't clear interrupts in status register\n");
return -1;
}

@@ -785,9 +787,8 @@ static int daqp_ao_insn_write(struct comedi_device *dev,
int d;
unsigned int chan;

- if (local->stop) {
+ if (local->stop)
return -EIO;
- }

chan = CR_CHAN(insn->chanspec);
d = data[0];
@@ -811,9 +812,8 @@ static int daqp_di_insn_read(struct comedi_device *dev,
{
struct local_info_t *local = (struct local_info_t *)s->private;

- if (local->stop) {
+ if (local->stop)
return -EIO;
- }

data[0] = inb(dev->iobase + DAQP_DIGITAL_IO);

@@ -828,9 +828,8 @@ static int daqp_do_insn_write(struct comedi_device *dev,
{
struct local_info_t *local = (struct local_info_t *)s->private;

- if (local->stop) {
+ if (local->stop)
return -EIO;
- }

outw(data[0] & 0xf, dev->iobase + DAQP_DIGITAL_IO);

@@ -878,7 +877,7 @@ static int daqp_attach(struct comedi_device *dev, struct comedi_devconfig *it)


if (ret < 0)
return ret;

- printk("comedi%d: attaching daqp%d (io 0x%04lx)\n",
+ printk(KERN_INFO "comedi%d: attaching daqp%d (io 0x%04lx)\n",
dev->minor, it->options[0], dev->iobase);

s = dev->subdevices + 0;
@@ -931,7 +930,7 @@ static int daqp_attach(struct comedi_device *dev, struct comedi_devconfig *it)

static int daqp_detach(struct comedi_device *dev)
{
- printk("comedi%d: detaching daqp\n", dev->minor);
+ printk(KERN_INFO "comedi%d: detaching daqp\n", dev->minor);

return 0;
}
@@ -1153,7 +1152,7 @@ static void daqp_cs_config(struct pcmcia_device *link)
/* Finally, report what we've done */
dev_info(&link->dev, "index 0x%02x", link->conf.ConfigIndex);
if (link->conf.Attributes & CONF_ENABLE_IRQ)
- printk(", irq %u", link->irq);
+ printk(KERN_INFO ", irq %u", link->irq);
if (link->io.NumPorts1)
printk(", io 0x%04x-0x%04x", link->io.BasePort1,
link->io.BasePort1 + link->io.NumPorts1 - 1);

Greg KH

unread,
Jun 17, 2010, 7:10:02 PM6/17/10
to

This patch ended up line-wrapped, can you resend it please?

You can add Ian's signed-off-by to it as well.

thanks,

greg k-h

Gustavo Silva

unread,
Jun 18, 2010, 1:10:01 AM6/18/10
to
This is a patch to the pcl812.c file that fixes up the following
issues:

ERROR: code indent should use tabs where possible x 27
WARNING: line over 80 characters x 37
WARNING: please, no space before tabs x 13
WARNING: braces {} are not necessary for single statement blocks x 2
WARNING: printk() should include KERN_ facility level x 22
WARNING: braces {} are not necessary for any arm of this statement x 5

Signed-off-by: Gustavo Silva <silvag...@users.sourceforge.net>
Acked-by: Ian Abbott <abb...@mev.co.uk>

- [2] - DMA (0=disable, 1, 3)
- [3] - 0=trigger source is internal 8253 with 2MHz clock
- 1=trigger source is external
- [4] - 0=A/D have max +/-5V input
- 1=A/D have max +/-10V input
- [5] - 0=D/A outputs 0-5V (internal reference -5V)
- 1=D/A outputs 0-10V (internal reference -10V)
- 2=D/A outputs unknown (external reference)
-
-Options for ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH, ACL-8216, A-826PG:
- [0] - IO Base

- [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)

+ * Driver: pcl812
+ * Description: Advantech PCL-812/PG, PCL-813/B,
+ * ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216,
+ * ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG,
+ * ICP DAS ISO-813
+ * Author: Michal Dobes <do...@tesnet.cz>
+ * Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg),
+ * PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg),
+ * ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216),
+ * [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl),
+ * A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl),
+ * A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg)

+ * [2] - DMA (0=disable, 1, 3)
+ * [3] - 0=trigger source is internal 8253 with 2MHz clock
+ * 1=trigger source is external
+ * [4] - 0=A/D have max +/-5V input
+ * 1=A/D have max +/-10V input
+ * [5] - 0=D/A outputs 0-5V (internal reference -5V)
+ * 1=D/A outputs 0-10V (internal reference -10V)
+ * 2=D/A outputs unknown (external reference)
+ *
+ * Options for ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH, ACL-8216, A-826PG:
+ * [0] - IO Base

+ * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15)

for (n = 0; n < insn->n; n++) {

- outb(255, dev->iobase + PCL812_SOFTTRIG); /* start conversion */
+ /* start conversion */
+ outb(255, dev->iobase + PCL812_SOFTTRIG);
udelay(5);
timeout = 50; /* wait max 50us, it must finish under 33us */
while (timeout--) {
@@ -501,10 +506,13 @@ static int acl8216_ai_insn_read(struct comedi_device *dev,
int n;
int timeout;

- outb(1, dev->iobase + PCL812_MODE); /* select software trigger */
- setup_range_channel(dev, s, insn->chanspec, 1); /* select channel and renge */
+ /* select software trigger */
+ outb(1, dev->iobase + PCL812_MODE);
+ /* select channel and renge */
+ setup_range_channel(dev, s, insn->chanspec, 1);

for (n = 0; n < insn->n; n++) {

- outb(255, dev->iobase + PCL812_SOFTTRIG); /* start conversion */
+ /* start conversion */
+ outb(255, dev->iobase + PCL812_SOFTTRIG);
udelay(5);
timeout = 50; /* wait max 50us, it must finish under 33us */
while (timeout--) {
@@ -558,9 +566,8 @@ static int pcl812_ao_insn_read(struct comedi_device *dev,
int chan = CR_CHAN(insn->chanspec);
int i;

- for (i = 0; i < insn->n; i++) {
+ for (i = 0; i < insn->n; i++)
data[i] = devpriv->ao_readback[chan];
- }

return i;
}
@@ -608,14 +615,15 @@ static int pcl812_do_insn_bits(struct comedi_device *dev,
*/

static void pcl812_cmdtest_out(int e, struct comedi_cmd *cmd)
{
- printk("pcl812 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,
+ printk(KERN_INFO "pcl812 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,


cmd->start_src, cmd->scan_begin_src, cmd->convert_src);

- printk("pcl812 e=%d startarg=%d scanarg=%d convarg=%d\n", e,
+ printk(KERN_INFO "pcl812 e=%d startarg=%d scanarg=%d convarg=%d\n", e,


cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg);

- printk("pcl812 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src,
- cmd->scan_end_src);
- printk("pcl812 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", e,


- cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len);

+ printk(KERN_INFO "pcl812 e=%d stopsrc=%x scanend=%x\n", e,


+ cmd->stop_src, cmd->scan_end_src);

+ printk(KERN_INFO "pcl812 e=%d stoparg=%d scanendarg=%d "
+ "chanlistlen=%d\n", e, cmd->stop_arg, cmd->scan_end_arg,
+ cmd->chanlist_len);
}
#endif

@@ -645,11 +653,11 @@ static int pcl812_ai_cmdtest(struct comedi_device *dev,
err++;

tmp = cmd->convert_src;
- if (devpriv->use_ext_trg) {
+ if (devpriv->use_ext_trg)
cmd->convert_src &= TRIG_EXT;
- } else {
+ else
cmd->convert_src &= TRIG_TIMER;
- }
+
if (!cmd->convert_src || tmp != cmd->convert_src)
err++;

@@ -673,7 +681,10 @@ static int pcl812_ai_cmdtest(struct comedi_device *dev,

return 1;
}

- /* step 2: make sure trigger sources are unique and mutually compatible */
+ /*

+ * step 2: make sure trigger sources are
+ * unique and mutually compatible
+ */


if (cmd->start_src != TRIG_NOW) {
cmd->start_src = TRIG_NOW;

@@ -807,7 +818,7 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)


struct comedi_cmd *cmd = &s->async->cmd;

#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: BGN: pcl812_ai_cmd(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: BGN: pcl812_ai_cmd(...)\n");
#endif

if (cmd->start_src != TRIG_NOW)

@@ -842,13 +853,15 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)


devpriv->ai_n_chan = cmd->chanlist_len;
memcpy(devpriv->ai_chanlist, cmd->chanlist,
sizeof(unsigned int) * cmd->scan_end_arg);
- setup_range_channel(dev, s, devpriv->ai_chanlist[0], 1); /* select first channel and range */
+ /* select first channel and range */
+ setup_range_channel(dev, s, devpriv->ai_chanlist[0], 1);

if (devpriv->dma) { /* check if we can use DMA transfer */
devpriv->ai_dma = 1;
for (i = 1; i < devpriv->ai_n_chan; i++)
if (devpriv->ai_chanlist[0] != devpriv->ai_chanlist[i]) {
- devpriv->ai_dma = 0; /* we cann't use DMA :-( */
+ /* we cann't use DMA :-( */
+ devpriv->ai_dma = 0;
break;
}
} else

@@ -869,14 +882,18 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)


devpriv->ai_poll_ptr = 0;
s->async->cur_chan = 0;

- if ((devpriv->ai_flags & TRIG_WAKE_EOS)) { /* don't we want wake up every scan? */


+ /* don't we want wake up every scan? */

+ if ((devpriv->ai_flags & TRIG_WAKE_EOS)) {
devpriv->ai_eos = 1;
+
+ /* DMA is useless for this situation */
if (devpriv->ai_n_chan == 1)
- devpriv->ai_dma = 0; /* DMA is useless for this situation */
+ devpriv->ai_dma = 0;
}

if (devpriv->ai_dma) {
- if (devpriv->ai_eos) { /* we use EOS, so adapt DMA buffer to one scan */
+ /* we use EOS, so adapt DMA buffer to one scan */
+ if (devpriv->ai_eos) {
devpriv->dmabytestomove[0] =
devpriv->ai_n_chan * sizeof(short);
devpriv->dmabytestomove[1] =

@@ -894,9 +911,17 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)


if (devpriv->ai_neverending) {
devpriv->dma_runs_to_end = 1;
} else {

- bytes = devpriv->ai_n_chan * devpriv->ai_scans * sizeof(short); /* how many samples we must transfer? */
- devpriv->dma_runs_to_end = bytes / devpriv->dmabytestomove[0]; /* how many DMA pages we must fill */
- devpriv->last_dma_run = bytes % devpriv->dmabytestomove[0]; /* on last dma transfer must be moved */


+ /* how many samples we must transfer? */
+ bytes = devpriv->ai_n_chan *

+ devpriv->ai_scans * sizeof(short);


+
+ /* how many DMA pages we must fill */
+ devpriv->dma_runs_to_end =

+ bytes / devpriv->dmabytestomove[0];


+
+ /* on last dma transfer must be moved */
+ devpriv->last_dma_run =

+ bytes % devpriv->dmabytestomove[0];
if (devpriv->dma_runs_to_end == 0)
devpriv->dmabytestomove[0] =
devpriv->last_dma_run;

@@ -934,14 +959,13 @@ static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)

- if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */
+ /* all data sampled */
+ if (devpriv->ai_act_scan >= devpriv->ai_scans) {

pcl812_ai_cancel(dev, s);


s->async->events |= COMEDI_CB_EOA;
}

@@ -1030,14 +1056,16 @@ static void transfer_from_dma_buf(struct comedi_device *dev,



s->async->events = 0;
for (i = len; i; i--) {
- comedi_buf_put(s->async, ptr[bufptr++]); /* get one sample */
+ /* get one sample */
+ comedi_buf_put(s->async, ptr[bufptr++]);

s->async->cur_chan++;
if (s->async->cur_chan >= devpriv->ai_n_chan) {
s->async->cur_chan = 0;
devpriv->ai_act_scan++;

if (!devpriv->ai_neverending)
- if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */
+ /* all data sampled */
+ if (devpriv->ai_act_scan >= devpriv->ai_scans) {

pcl812_ai_cancel(dev, s);


s->async->events |= COMEDI_CB_EOA;

@@ -1132,7 +1159,8 @@ static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)


spin_lock_irqsave(&dev->spinlock, flags);

for (i = 0; i < 10; i++) {
- top1 = get_dma_residue(devpriv->ai_dma); /* where is now DMA */
+ /* where is now DMA */
+ top1 = get_dma_residue(devpriv->ai_dma);
top2 = get_dma_residue(devpriv->ai_dma);
if (top1 == top2)
break;

@@ -1142,8 +1170,8 @@ static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)


spin_unlock_irqrestore(&dev->spinlock, flags);
return 0;
}
-

- top1 = devpriv->dmabytestomove[1 - devpriv->next_dma_buf] - top1; /* where is now DMA in buffer */


+ /* where is now DMA in buffer */

+ top1 = devpriv->dmabytestomove[1 - devpriv->next_dma_buf] - top1;


top1 >>= 1; /* sample position */
top2 = top1 - devpriv->ai_poll_ptr;
if (top2 < 1) { /* no new samples */

@@ -1207,8 +1242,8 @@ static void start_pacer(struct comedi_device *dev, int mode,


unsigned int divisor1, unsigned int divisor2)
{
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: BGN: start_pacer(%d,%u,%u)\n", mode, divisor1,
- divisor2);
+ printk(KERN_DEBUG "pcl812 EDBG: BGN: start_pacer(%d,%u,%u)\n", mode,
+ divisor1, divisor2);
#endif
outb(0xb4, dev->iobase + PCL812_CTRCTL);
outb(0x74, dev->iobase + PCL812_CTRCTL);

@@ -1221,7 +1256,7 @@ static void start_pacer(struct comedi_device *dev, int mode,


outb((divisor1 >> 8) & 0xff, dev->iobase + PCL812_CTR1);
}
#ifdef PCL812_EXTDEBUG
- printk("pcl812 EDBG: END: start_pacer(...)\n");
+ printk(KERN_DEBUG "pcl812 EDBG: END: start_pacer(...)\n");
#endif
}

@@ -1252,16 +1287,17 @@ static int pcl812_ai_cancel(struct comedi_device *dev,
struct comedi_subdevice *s)
{

@@ -1322,8 +1358,8 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
int n_subdevices;



iobase = it->options[0];

- printk("comedi%d: pcl812: board=%s, ioport=0x%03lx", dev->minor,
- this_board->name, iobase);
+ printk(KERN_INFO "comedi%d: pcl812: board=%s, ioport=0x%03lx",
+ dev->minor, this_board->name, iobase);

if (!request_region(iobase, this_board->io_range, "pcl812")) {
printk("I/O port conflict\n");

@@ -1345,18 +1381,18 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)


if (irq) { /* we want to use IRQ */
if (((1 << irq) & this_board->IRQbits) == 0) {
printk
- (", IRQ %u is out of allowed range, DISABLING IT",
- irq);
+ (", IRQ %u is out of allowed range, "
+ "DISABLING IT", irq);
irq = 0; /* Bad IRQ */
} else {
if (request_irq

(irq, interrupt_pcl812, 0, "pcl812", dev)) {


printk
- (", unable to allocate IRQ %u, DISABLING IT",
- irq);
+ (", unable to allocate IRQ %u, "
+ "DISABLING IT", irq);
irq = 0; /* Can't use IRQ */
} else {
- printk(", irq=%u", irq);
+ printk(KERN_INFO ", irq=%u", irq);
}
}
}

@@ -1376,16 +1412,20 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
}
ret = request_dma(dma, "pcl812");


if (ret) {
- printk(", unable to allocate DMA %u, FAIL!\n", dma);

+ printk(KERN_ERR ", unable to allocate DMA %u, FAIL!\n",
+ dma);


return -EBUSY; /* DMA isn't free */
}
devpriv->dma = dma;
- printk(", dma=%u", dma);
+ printk(KERN_INFO ", dma=%u", dma);

pages = 1; /* we want 8KB */


devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages);
if (!devpriv->dmabuf[0]) {
printk(", unable to allocate DMA buffer, FAIL!\n");

- /* maybe experiment with try_to_free_pages() will help .... */
+ /*


+ * maybe experiment with try_to_free_pages()
+ * will help ....
+ */

free_resources(dev);


return -EBUSY; /* no buffer :-( */
}

@@ -1394,7 +1434,7 @@ static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)


devpriv->hwdmasize[0] = PAGE_SIZE * (1 << pages);

devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages);
if (!devpriv->dmabuf[1]) {

--

Greg KH

unread,
Nov 9, 2010, 12:50:03 PM11/9/10
to
On Tue, Nov 09, 2010 at 06:25:27PM +0100, mems applications wrote:
> Add STMicroelectronics LIS331DLH digital accelerometer device driver
>
> From: Carmine Iascone <carmine...@st.com>
> ---
> drivers/staging/Kconfig | 2 +
> drivers/staging/Makefile | 1 +
> drivers/staging/lis331dlh/Kconfig | 7 +
> drivers/staging/lis331dlh/Makefile | 1 +
> drivers/staging/lis331dlh/lis331dlh.c | 836 +++++++++++++++++++++++++++++++++
> drivers/staging/lis331dlh/lis331dlh.h | 83 ++++
> 6 files changed, 930 insertions(+), 0 deletions(-)

Why is this a staging driver? What is keeping it from being merged to
the main portion of the kernel tree?

Can you please provide a TODO file that lists the things left to do on
it, and provide some email addresses to contact you for it? Look at the
other TODO files in drivers/staging/*/TODO for examples.

thanks,

greg k-h

mems applications

unread,
Nov 9, 2010, 1:00:02 PM11/9/10
to
Add STMicroelectronics LIS331DLH digital accelerometer device driver

From: Carmine Iascone <carmine...@st.com>
---
drivers/staging/Kconfig | 2 +
drivers/staging/Makefile | 1 +
drivers/staging/lis331dlh/Kconfig | 7 +
drivers/staging/lis331dlh/Makefile | 1 +
drivers/staging/lis331dlh/lis331dlh.c | 836 +++++++++++++++++++++++++++++++++
drivers/staging/lis331dlh/lis331dlh.h | 83 ++++
6 files changed, 930 insertions(+), 0 deletions(-)

create mode 100644 drivers/staging/lis331dlh/Kconfig
create mode 100644 drivers/staging/lis331dlh/Makefile
create mode 100644 drivers/staging/lis331dlh/lis331dlh.c
create mode 100644 drivers/staging/lis331dlh/lis331dlh.h

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 5eafdf4..f1d70c6 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -175,5 +175,7 @@ source "drivers/staging/intel_sst/Kconfig"

source "drivers/staging/speakup/Kconfig"

+source "drivers/staging/lis331dlh/Kconfig"
+
endif # !STAGING_EXCLUDE_BUILD
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index a97a955..a925237 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -68,3 +68,4 @@ obj-$(CONFIG_BCM_WIMAX) += bcm/
obj-$(CONFIG_FT1000) += ft1000/
obj-$(CONFIG_SND_INTEL_SST) += intel_sst/
obj-$(CONFIG_SPEAKUP) += speakup/
+obj-$(CONFIG_LIS331DLH) += lis331dlh/
diff --git a/drivers/staging/lis331dlh/Kconfig b/drivers/staging/lis331dlh/Kconfig
new file mode 100644
index 0000000..5d56504
--- /dev/null
+++ b/drivers/staging/lis331dlh/Kconfig
@@ -0,0 +1,7 @@
+config LIS331DLH
+ tristate "STMicroelectronics LIS331DLH Accelerometer"
+ depends on I2C
+ ---help---
+ If you say yes here you get support for the STMicroelectronics
+ LIS331DLH accelerometer sensor
+
diff --git a/drivers/staging/lis331dlh/Makefile b/drivers/staging/lis331dlh/Makefile
new file mode 100644
index 0000000..a8ee120
--- /dev/null
+++ b/drivers/staging/lis331dlh/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_LIS331DLH) += lis331dlh.o
diff --git a/drivers/staging/lis331dlh/lis331dlh.c b/drivers/staging/lis331dlh/lis331dlh.c
new file mode 100644
index 0000000..2b72c12
--- /dev/null
+++ b/drivers/staging/lis331dlh/lis331dlh.c
@@ -0,0 +1,836 @@
+/******************** (C) COPYRIGHT 2010 STMicroelectronics ********************
+*
+* File Name : lis331dlh.c
+* Authors : MSH - Motion Mems BU - Application Team
+* : Carmine Iascone (carmine...@st.com)
+* : Matteo Dameno (matteo...@st.com)
+* Version : V 1.0.0
+* Date : 08/11/2010
+* Description : LIS331DLH sensor API
+*
+********************************************************************************
+*
+* 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.
+*
+* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
+* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE
+* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT.
+* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
+* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
+* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
+* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
+*
+* THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS.
+*
+******************************************************************************/
+#include "lis331dlh.h"
+
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/input-polldev.h>
+#include <linux/slab.h>
+
+
+#define LIS331DLH_DEV_NAME "lis331dlh"
+
+/** Maximum polled-device-reported g value */
+#define G_MAX 8000
+
+#define SHIFT_ADJ_2G 4
+#define SHIFT_ADJ_4G 3
+#define SHIFT_ADJ_8G 2
+
+#define AXISDATA_REG 0x28
+
+/* ctrl 1: pm2 pm1 pm0 dr1 dr0 zenable yenable zenable */
+#define CTRL_REG1 0x20 /* power control reg */
+#define CTRL_REG2 0x21 /* power control reg */
+#define CTRL_REG3 0x22 /* power control reg */
+#define CTRL_REG4 0x23 /* interrupt control reg */
+
+#define FUZZ 0
+#define FLAT 0
+#define I2C_RETRY_DELAY 5
+#define I2C_RETRIES 5
+#define AUTO_INCREMENT 0x80
+
+static struct {
+ unsigned int cutoff;
+ unsigned int mask;
+} odr_table[] = {
+ {
+ 3, LIS331DLH_PM_NORMAL | LIS331DLH_ODR1000}, {
+ 10, LIS331DLH_PM_NORMAL | LIS331DLH_ODR400}, {
+ 20, LIS331DLH_PM_NORMAL | LIS331DLH_ODR100}, {
+ 100, LIS331DLH_PM_NORMAL | LIS331DLH_ODR50}, {
+ 200, LIS331DLH_ODR1000 | LIS331DLH_ODR10}, {
+ 500, LIS331DLH_ODR1000 | LIS331DLH_ODR5}, {
+ 1000, LIS331DLH_ODR1000 | LIS331DLH_ODR2}, {
+ 2000, LIS331DLH_ODR1000 | LIS331DLH_ODR1}, {
+ 0, LIS331DLH_ODR1000 | LIS331DLH_ODRHALF},};
+
+struct lis331dlh_data {
+ struct i2c_client *client;
+ struct lis331dlh_platform_data *pdata;
+
+ struct mutex lock;
+
+ struct input_polled_dev *input_poll_dev;
+
+ int hw_initialized;
+ atomic_t enabled;
+ int on_before_suspend;
+
+ u8 reg_addr;
+
+ u8 shift_adj;
+ u8 resume_state[5];
+};
+
+/*
+ * Because misc devices can not carry a pointer from driver register to
+ * open, we keep this global. This limits the driver to a single instance.
+ */
+struct lis331dlh_data *lis331dlh_misc_data;
+
+static int lis331dlh_i2c_read(struct lis331dlh_data *acc,
+ u8 *buf, int len)
+{
+ int err;
+
+ struct i2c_msg msgs[] = {
+ {
+ .addr = acc->client->addr,
+ .flags = acc->client->flags & I2C_M_TEN,
+ .len = 1,
+ .buf = buf,
+ },
+ {
+ .addr = acc->client->addr,
+ .flags = (acc->client->flags & I2C_M_TEN) | I2C_M_RD,
+ .len = len,
+ .buf = buf,
+ },
+ };
+
+ err = i2c_transfer(acc->client->adapter, msgs, 2);
+
+ if (err != 2) {
+ dev_err(&acc->client->dev, "read transfer error\n");
+ err = -EIO;
+ } else {
+ err = 0;
+ }
+
+ return err;
+}
+
+static int lis331dlh_i2c_write(struct lis331dlh_data *acc,
+ u8 *buf, int len)
+{
+ int err;
+
+ struct i2c_msg msgs[] = {
+ {
+ .addr = acc->client->addr,
+ .flags = acc->client->flags & I2C_M_TEN,
+ .len = len + 1,
+ .buf = buf,
+ },
+ };
+
+ err = i2c_transfer(acc->client->adapter, msgs, 1);
+
+ if (err != 1) {
+ dev_err(&acc->client->dev, "write transfer error\n");
+ err = -EIO;
+ } else {
+ err = 0;
+ }
+
+ return err;
+}
+
+static int lis331dlh_hw_init(struct lis331dlh_data *acc)
+{
+ int err = -1;
+ u8 buf[6];
+
+ buf[0] = (AUTO_INCREMENT | CTRL_REG1);
+ buf[1] = acc->resume_state[0];
+ buf[2] = acc->resume_state[1];
+ buf[3] = acc->resume_state[2];
+ buf[4] = acc->resume_state[3];
+ buf[5] = acc->resume_state[4];
+ err = lis331dlh_i2c_write(acc, buf, 5);
+ if (err < 0)
+ return err;
+
+ acc->hw_initialized = 1;
+
+ return 0;
+}
+
+static void lis331dlh_device_power_off(struct lis331dlh_data *acc)
+{
+ int err;
+ u8 buf[2] = { CTRL_REG1,
+ LIS331DLH_PM_OFF | LIS331DLH_ENABLE_ALL_AXES };
+
+ err = lis331dlh_i2c_write(acc, buf, 1);
+ if (err < 0)
+ dev_err(&acc->client->dev, "soft power off failed\n");
+
+ if (acc->pdata->power_off)
+ acc->pdata->power_off();
+
+ acc->hw_initialized = 0;
+
+}
+
+static int lis331dlh_device_power_on(struct lis331dlh_data *acc)
+{
+ int err;
+
+ if (acc->pdata->power_on) {
+ err = acc->pdata->power_on();
+ if (err < 0)
+ return err;
+ }
+
+ if (!acc->hw_initialized) {
+ err = lis331dlh_hw_init(acc);
+ if (err < 0) {
+ lis331dlh_device_power_off(acc);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int lis331dlh_update_g_range(struct lis331dlh_data *acc, u8 new_g_range)
+{
+ int err;
+ u8 shift;
+ u8 buf[2];
+ switch (new_g_range) {
+ case LIS331DLH_G_2G:
+ shift = SHIFT_ADJ_2G;
+ break;
+ case LIS331DLH_G_4G:
+ shift = SHIFT_ADJ_4G;
+ break;
+ case LIS331DLH_G_8G:
+ shift = SHIFT_ADJ_8G;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (atomic_read(&acc->enabled)) {
+ /* Set configuration register 4, which contains g range setting
+ * NOTE: this is a straight overwrite because this driver does
+ * not use any of the other configuration bits in this
+ * register. Should this become untrue, we will have to read
+ * out the value and only change the relevant bits --XX----
+ * (marked by X) */
+ buf[0] = CTRL_REG4;
+ buf[1] = new_g_range;
+ err = lis331dlh_i2c_write(acc, buf, 1);
+ if (err < 0)
+ return err;
+ }
+
+ acc->resume_state[3] = new_g_range;
+ acc->shift_adj = shift;
+
+ return 0;
+}
+
+int lis331dlh_update_odr(struct lis331dlh_data *acc, int poll_interval)
+{
+ int err = -1;
+ int i;
+ u8 config[2];
+
+ /* Convert the poll interval into an output data rate configuration
+ * that is as low as possible. The ordering of these checks must be
+ * maintained due to the cascading cut off values - poll intervals are
+ * checked from shortest to longest. At each check, if the next lower
+ * ODR cannot support the current poll interval, we stop searching */
+ for (i = 0; i < ARRAY_SIZE(odr_table); i++) {
+ config[1] = odr_table[i].mask;
+ if (poll_interval < odr_table[i].cutoff)
+ break;
+ }
+
+ config[1] |= LIS331DLH_ENABLE_ALL_AXES;
+
+ /* If device is currently enabled, we need to write new
+ * configuration out to it */
+ if (atomic_read(&acc->enabled)) {
+ config[0] = CTRL_REG1;
+ err = lis331dlh_i2c_write(acc, config, 1);
+ if (err < 0)
+ return err;
+ }
+
+ acc->resume_state[0] = config[1];
+
+ return 0;
+}
+
+static int decode(u8 *p, int adj)
+{
+ s16 v = p[0] | (p[1] << 8);
+ return (int) v >> adj;
+}
+
+static int lis331dlh_get_data(struct lis331dlh_data *acc,
+ int *xyz)
+{
+ int err = -1;
+ /* Data bytes from hardware xL, xH, yL, yH, zL, zH */
+ u8 acc_data[6];
+ /* x,y,z hardware data */
+ int hw_d[3] = { 0 };
+
+ acc_data[0] = (AUTO_INCREMENT | AXISDATA_REG);
+ err = lis331dlh_i2c_read(acc, acc_data, 6);
+ if (err < 0)
+ return err;
+
+ hw_d[0] = decode(acc_data, acc->shift_adj);
+ hw_d[1] = decode(acc_data + 2, acc->shift_adj);
+ hw_d[2] = decode(acc_data + 4, acc->shift_adj);
+
+ xyz[0] = ((acc->pdata->negate_x) ? (-hw_d[acc->pdata->axis_map_x])
+ : (hw_d[acc->pdata->axis_map_x]));
+ xyz[1] = ((acc->pdata->negate_y) ? (-hw_d[acc->pdata->axis_map_y])
+ : (hw_d[acc->pdata->axis_map_y]));
+ xyz[2] = ((acc->pdata->negate_z) ? (-hw_d[acc->pdata->axis_map_z])
+ : (hw_d[acc->pdata->axis_map_z]));
+
+ return err;
+}
+
+static void lis331dlh_report_values(struct lis331dlh_data *acc,
+ int *xyz)
+{
+ struct input_dev *input = acc->input_poll_dev->input;
+ input_report_abs(input, ABS_X, xyz[0]);
+ input_report_abs(input, ABS_Y, xyz[1]);
+ input_report_abs(input, ABS_Z, xyz[2]);
+ input_sync(input);
+}
+
+static int lis331dlh_enable(struct lis331dlh_data *acc)
+{
+ int err;
+
+ if (!atomic_cmpxchg(&acc->enabled, 0, 1)) {
+
+ err = lis331dlh_device_power_on(acc);
+ if (err < 0) {
+ atomic_set(&acc->enabled, 0);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int lis331dlh_disable(struct lis331dlh_data *acc)
+{
+ if (atomic_cmpxchg(&acc->enabled, 1, 0))
+ lis331dlh_device_power_off(acc);
+
+ return 0;
+}
+
+static ssize_t attr_get_polling_rate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ int val;
+ mutex_lock(&acc->lock);
+ val = acc->pdata->poll_interval;
+ mutex_unlock(&acc->lock);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t attr_set_polling_rate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ unsigned long interval_ms;
+
+ if (strict_strtoul(buf, 10, &interval_ms))
+ return -EINVAL;
+ if (!interval_ms)
+ return -EINVAL;
+ mutex_lock(&acc->lock);
+ acc->pdata->poll_interval = interval_ms;
+ lis331dlh_update_odr(acc, interval_ms);
+ mutex_unlock(&acc->lock);
+ return size;
+}
+
+static ssize_t attr_get_range(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ char range;
+ char val;
+ mutex_lock(&acc->lock);
+ val = acc->pdata->g_range ;
+ switch (val) {
+ case LIS331DLH_G_2G:
+ range = 2;
+ break;
+ case LIS331DLH_G_4G:
+ range = 4;
+ break;
+ case LIS331DLH_G_8G:
+ range = 8;
+ break;
+ default:
+ range = 2;
+ break;
+ }
+ mutex_unlock(&acc->lock);
+ return sprintf(buf, "%d\n", range);
+}
+
+static ssize_t attr_set_range(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ unsigned long val;
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+ mutex_lock(&acc->lock);
+ acc->pdata->g_range = val;
+ lis331dlh_update_g_range(acc, val);
+ mutex_unlock(&acc->lock);
+ return size;
+}
+
+static ssize_t attr_get_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ int val = atomic_read(&acc->enabled);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t attr_set_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ unsigned long val;
+
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+
+ if (val)
+ lis331dlh_enable(acc);
+ else
+ lis331dlh_disable(acc);
+
+ return size;
+}
+
+static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int rc;
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ u8 x[2];
+ unsigned long val;
+
+ if (strict_strtoul(buf, 16, &val))
+ return -EINVAL;
+ mutex_lock(&acc->lock);
+ x[0] = acc->reg_addr;
+ mutex_unlock(&acc->lock);
+ x[1] = val;
+ rc = lis331dlh_i2c_write(acc, x, 1);
+ return size;
+}
+
+static ssize_t attr_reg_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t ret;
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ int rc;
+ u8 data;
+
+ mutex_lock(&acc->lock);
+ data = acc->reg_addr;
+ mutex_unlock(&acc->lock);
+ rc = lis331dlh_i2c_read(acc, &data, 1);
+ ret = sprintf(buf, "0x%02x\n", data);
+ return ret;
+}
+
+static ssize_t attr_addr_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lis331dlh_data *acc = dev_get_drvdata(dev);
+ unsigned long val;
+ if (strict_strtoul(buf, 16, &val))
+ return -EINVAL;
+ mutex_lock(&acc->lock);
+ acc->reg_addr = val;
+ mutex_unlock(&acc->lock);
+ return size;
+}
+
+static struct device_attribute attributes[] = {
+ __ATTR(pollrate_ms, 0666, attr_get_polling_rate, attr_set_polling_rate),
+ __ATTR(range, 0666, attr_get_range, attr_set_range),
+ __ATTR(enable, 0666, attr_get_enable, attr_set_enable),
+ __ATTR(reg_value, 0600, attr_reg_get, attr_reg_set),
+ __ATTR(reg_addr, 0200, NULL, attr_addr_set),
+};
+
+static int create_sysfs_interfaces(struct device *dev)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(attributes); i++)
+ if (device_create_file(dev, attributes + i))
+ goto error;
+ return 0;
+
+error:
+ for ( ; i >= 0; i--)
+ device_remove_file(dev, attributes + i);
+ dev_err(dev, "%s:Unable to create interface\n", __func__);
+ return -1;
+}
+
+static int remove_sysfs_interfaces(struct device *dev)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(attributes); i++)
+ device_remove_file(dev, attributes + i);
+ return 0;
+}
+
+
+static void lis331dlh_input_poll_func(struct input_polled_dev *dev)
+{
+ struct lis331dlh_data *acc = dev->private;
+
+ int xyz[3] = { 0 };
+ int err;
+
+ /* acc = container_of((struct delayed_work *)work,
+ struct lis331dlh_data, input_work); */
+
+ mutex_lock(&acc->lock);
+ err = lis331dlh_get_data(acc, xyz);
+ if (err < 0)
+ dev_err(&acc->client->dev, "get_acceleration_data failed\n");
+ else
+ lis331dlh_report_values(acc, xyz);
+
+ mutex_unlock(&acc->lock);
+}
+
+int lis331dlh_input_open(struct input_dev *input)
+{
+ struct lis331dlh_data *acc = input_get_drvdata(input);
+
+ return lis331dlh_enable(acc);
+}
+
+void lis331dlh_input_close(struct input_dev *dev)
+{
+ struct lis331dlh_data *acc = input_get_drvdata(dev);
+
+ lis331dlh_disable(acc);
+}
+
+static int lis331dlh_validate_pdata(struct lis331dlh_data *acc)
+{
+ acc->pdata->poll_interval = max(acc->pdata->poll_interval,
+ acc->pdata->min_interval);
+
+ if (acc->pdata->axis_map_x > 2 ||
+ acc->pdata->axis_map_y > 2 || acc->pdata->axis_map_z > 2) {
+ dev_err(&acc->client->dev,
+ "invalid axis_map value x:%u y:%u z%u\n",
+ acc->pdata->axis_map_x, acc->pdata->axis_map_y,
+ acc->pdata->axis_map_z);
+ return -EINVAL;
+ }
+
+ /* Only allow 0 and 1 for negation boolean flag */
+ if (acc->pdata->negate_x > 1 || acc->pdata->negate_y > 1 ||
+ acc->pdata->negate_z > 1) {
+ dev_err(&acc->client->dev,
+ "invalid negate value x:%u y:%u z:%u\n",
+ acc->pdata->negate_x, acc->pdata->negate_y,
+ acc->pdata->negate_z);
+ return -EINVAL;
+ }
+
+ /* Enforce minimum polling interval */
+ if (acc->pdata->poll_interval < acc->pdata->min_interval) {
+ dev_err(&acc->client->dev, "minimum poll interval violated\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lis331dlh_input_init(struct lis331dlh_data *acc)
+{
+ int err;
+ struct input_dev *input;
+
+ acc->input_poll_dev = input_allocate_polled_device();
+ if (!acc->input_poll_dev) {
+ err = -ENOMEM;
+ dev_err(&acc->client->dev, "input device allocate failed\n");
+ goto err0;
+ }
+
+ /* set input-polldev handlers */
+ acc->input_poll_dev->private = acc;
+ acc->input_poll_dev->poll = lis331dlh_input_poll_func;
+ acc->input_poll_dev->poll_interval = acc->pdata->poll_interval;
+
+ input = acc->input_poll_dev->input;
+
+ input->open = lis331dlh_input_open;
+ input->close = lis331dlh_input_close;
+ input->name = LIS331DLH_DEV_NAME;
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &acc->client->dev;
+
+ input_set_drvdata(acc->input_poll_dev->input, acc);
+
+ set_bit(EV_ABS, input->evbit);
+
+ input_set_abs_params(input, ABS_X, -G_MAX, G_MAX, FUZZ, FLAT);
+ input_set_abs_params(input, ABS_Y, -G_MAX, G_MAX, FUZZ, FLAT);
+ input_set_abs_params(input, ABS_Z, -G_MAX, G_MAX, FUZZ, FLAT);
+
+ input->name = "accelerometer";
+
+ err = input_register_polled_device(acc->input_poll_dev);
+ if (err) {
+ dev_err(&acc->client->dev,
+ "unable to register input polled device %s\n",
+ acc->input_poll_dev->input->name);
+ goto err1;
+ }
+
+ return 0;
+
+err1:
+ input_free_polled_device(acc->input_poll_dev);
+err0:
+ return err;
+}
+
+static void lis331dlh_input_cleanup(struct lis331dlh_data *acc)
+{
+ input_unregister_polled_device(acc->input_poll_dev);
+ input_free_polled_device(acc->input_poll_dev);
+}
+
+static int lis331dlh_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lis331dlh_data *acc;
+ int err = -1;
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODEV;
+ goto err0;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "client not i2c capable\n");
+ err = -ENODEV;
+ goto err0;
+ }
+
+ acc = kzalloc(sizeof(*acc), GFP_KERNEL);
+ if (acc == NULL) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto err0;
+ }
+
+ mutex_init(&acc->lock);
+ mutex_lock(&acc->lock);
+ acc->client = client;
+
+ acc->pdata = kmalloc(sizeof(*acc->pdata), GFP_KERNEL);
+ if (acc->pdata == NULL)
+ goto err1;
+
+ memcpy(acc->pdata, client->dev.platform_data, sizeof(*acc->pdata));
+
+ err = lis331dlh_validate_pdata(acc);
+ if (err < 0) {
+ dev_err(&client->dev, "failed to validate platform data\n");
+ goto err1_1;
+ }
+
+ i2c_set_clientdata(client, acc);
+
+ if (acc->pdata->init) {
+ err = acc->pdata->init();
+ if (err < 0)
+ goto err1_1;
+ }
+
+ memset(acc->resume_state, 0, ARRAY_SIZE(acc->resume_state));
+
+ acc->resume_state[0] = 0x27;
+ acc->resume_state[1] = 0x00;
+ acc->resume_state[2] = 0x00;
+ acc->resume_state[3] = 0x00;
+ acc->resume_state[4] = 0x00;
+
+ err = lis331dlh_device_power_on(acc);
+ if (err < 0)
+ goto err2;
+
+ atomic_set(&acc->enabled, 1);
+
+ err = lis331dlh_update_g_range(acc, acc->pdata->g_range);
+ if (err < 0) {
+ dev_err(&client->dev, "update_g_range failed\n");
+ goto err2;
+ }
+
+ err = lis331dlh_update_odr(acc, acc->pdata->poll_interval);
+ if (err < 0) {
+ dev_err(&client->dev, "update_odr failed\n");
+ goto err2;
+ }
+
+ err = lis331dlh_input_init(acc);
+ if (err < 0)
+ goto err3;
+
+ err = create_sysfs_interfaces(&client->dev);
+ if (err < 0) {
+ dev_err(&client->dev, "lsm_acc_device register failed\n");
+ goto err4;
+ }
+
+ lis331dlh_device_power_off(acc);
+
+ /* As default, do not report information */
+ atomic_set(&acc->enabled, 0);
+
+ mutex_unlock(&acc->lock);
+
+ dev_info(&client->dev, "lis331dlh probed\n");
+
+ return 0;
+
+err4:
+ lis331dlh_input_cleanup(acc);
+err3:
+ lis331dlh_device_power_off(acc);
+err2:
+ if (acc->pdata->exit)
+ acc->pdata->exit();
+err1_1:
+ mutex_unlock(&acc->lock);
+ kfree(acc->pdata);
+err1:
+ kfree(acc);
+err0:
+ return err;
+}
+
+static int __devexit lis331dlh_remove(struct i2c_client *client)
+{
+ /* TODO: revisit ordering here once _probe order is finalized */
+ struct lis331dlh_data *acc = i2c_get_clientdata(client);
+ lis331dlh_input_cleanup(acc);
+ lis331dlh_device_power_off(acc);
+ remove_sysfs_interfaces(&client->dev);
+ if (acc->pdata->exit)
+ acc->pdata->exit();
+ kfree(acc->pdata);
+ kfree(acc);
+
+ return 0;
+}
+
+static int lis331dlh_resume(struct i2c_client *client)
+{
+ struct lis331dlh_data *acc = i2c_get_clientdata(client);
+
+ if (acc->on_before_suspend)
+ return lis331dlh_enable(acc);
+ return 0;
+}
+
+static int lis331dlh_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct lis331dlh_data *acc = i2c_get_clientdata(client);
+
+ acc->on_before_suspend = atomic_read(&acc->enabled);
+ return lis331dlh_disable(acc);
+}
+
+static const struct i2c_device_id lis331dlh_id[] = {
+ {LIS331DLH_DEV_NAME, 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, lis331dlh_id);
+
+static struct i2c_driver lis331dlh_driver = {
+ .driver = {
+ .name = LIS331DLH_DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = lis331dlh_probe,
+ .remove = __devexit_p(lis331dlh_remove),
+ .resume = lis331dlh_resume,
+ .suspend = lis331dlh_suspend,
+ .id_table = lis331dlh_id,
+};
+
+static int __init lis331dlh_init(void)
+{
+ pr_debug("LIS331DLH driver for the accelerometer part\n");
+ return i2c_add_driver(&lis331dlh_driver);
+}
+
+static void __exit lis331dlh_exit(void)
+{
+ i2c_del_driver(&lis331dlh_driver);
+ return;
+}
+
+module_init(lis331dlh_init);
+module_exit(lis331dlh_exit);
+
+MODULE_DESCRIPTION("lis331dlh accelerometer driver");
+MODULE_AUTHOR("STMicroelectronics");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/staging/lis331dlh/lis331dlh.h b/drivers/staging/lis331dlh/lis331dlh.h
new file mode 100644
index 0000000..4ea008a
--- /dev/null
+++ b/drivers/staging/lis331dlh/lis331dlh.h
@@ -0,0 +1,83 @@
+/******************** (C) COPYRIGHT 2010 STMicroelectronics ********************
+*
+* File Name : lis331dlh.h
+* Authors : MSH - Motion Mems BU - Application Team
+* : Carmine Iascone (carmine...@st.com)
+* : Matteo Dameno (matteo...@st.com)
+* Version : V 1.0.0
+* Date : 08/11/2010
+* Description : LIS331DLH sensor API
+*
+********************************************************************************
+*
+* 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.
+*
+* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
+* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE
+* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT.
+* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
+* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
+* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
+* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
+*
+* THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS.
+*
+*******************************************************************************/
+
+#ifndef __LIS331DLH_H__
+#define __LIS331DLH_H__
+
+#include <linux/types.h>
+
+/************************************************/
+/* Accelerometer section defines */
+/************************************************/
+
+/* Accelerometer Sensor Full Scale */
+#define LIS331DLH_G_2G 0x00
+#define LIS331DLH_G_4G 0x10
+#define LIS331DLH_G_8G 0x30
+
+/* Accelerometer Sensor Operating Mode */
+#define LIS331DLH_PM_OFF 0x00
+#define LIS331DLH_PM_NORMAL 0x20
+#define LIS331DLH_ENABLE_ALL_AXES 0x07
+
+/* Accelerometer output data rate */
+#define LIS331DLH_ODRHALF 0x40 /* 0.5Hz output data rate */
+#define LIS331DLH_ODR1 0x60 /* 1Hz output data rate */
+#define LIS331DLH_ODR2 0x80 /* 2Hz output data rate */
+#define LIS331DLH_ODR5 0xA0 /* 5Hz output data rate */
+#define LIS331DLH_ODR10 0xC0 /* 10Hz output data rate */
+#define LIS331DLH_ODR50 0x00 /* 50Hz output data rate */
+#define LIS331DLH_ODR100 0x08 /* 100Hz output data rate */
+#define LIS331DLH_ODR400 0x10 /* 400Hz output data rate */
+#define LIS331DLH_ODR1000 0x18 /* 1000Hz output data rate */
+
+#ifdef __KERNEL__
+struct lis331dlh_platform_data {
+
+ int poll_interval;
+ int min_interval;
+
+ u8 g_range;
+
+ u8 axis_map_x;
+ u8 axis_map_y;
+ u8 axis_map_z;
+
+ u8 negate_x;
+ u8 negate_y;
+ u8 negate_z;
+
+ int (*init)(void);
+ void (*exit)(void);
+ int (*power_on)(void);
+ int (*power_off)(void);
+
+};
+#endif /* __KERNEL__ */
+
+#endif /* __LIS331DLH_H__ */
--
1.5.4.3

Jiri Slaby

unread,
Nov 9, 2010, 3:40:01 PM11/9/10
to
On 11/09/2010 06:25 PM, mems applications wrote:
> Add STMicroelectronics LIS331DLH digital accelerometer device driver

I'm no IIC guy, but why this goes to staging?

> From: Carmine Iascone <carmine...@st.com>
> ---
> drivers/staging/Kconfig | 2 +
> drivers/staging/Makefile | 1 +
> drivers/staging/lis331dlh/Kconfig | 7 +
> drivers/staging/lis331dlh/Makefile | 1 +
> drivers/staging/lis331dlh/lis331dlh.c | 836 +++++++++++++++++++++++++++++++++
> drivers/staging/lis331dlh/lis331dlh.h | 83 ++++
> 6 files changed, 930 insertions(+), 0 deletions(-)
> create mode 100644 drivers/staging/lis331dlh/Kconfig
> create mode 100644 drivers/staging/lis331dlh/Makefile
> create mode 100644 drivers/staging/lis331dlh/lis331dlh.c
> create mode 100644 drivers/staging/lis331dlh/lis331dlh.h


regards,
--
js

Carmine IASCONE

unread,
Nov 10, 2010, 7:00:02 AM11/10/10
to
Hi Greg, Hi JS,
Matteo and I have started to develop linux device drivers for our STMicroelectronics sensors about one year ago. Our main target is the Android platform (mobile phone or tablet pc), and for this reason the drivers are thought to be used on I2C bus. The drivers are enough stable, we have several customers that are using them, and also with the advice of these customers, we would like to make available them for all the linux community, merging them in the kernel upstream to be used in all linux supported platforms.
The drivers have had a initial review by Alan Cox, that gives us some very precious advices to improve the style and robustness of the drivers.
We are newbies in patch generation and submission: we have followed the instructions in the Greg's video on you tube to create this first patch, so sorry if there is something that we missed. We thought to put the driver in staging directory, because before merging them in the main tree we would like to have a general revision, and also because in this first release we haven't managed the device interrupts yet. At the end the right position for these drivers could be drivers/input/misc.
How do we proceed now? Do we need to generate a new patch adding the TODO file? Please advice.

Our email references are:

Carmine Iascone <carmine...@st.com>
Matteo Dameno <matteo...@st.com>

Best regards,
Carmine

It is loading more messages.
0 new messages