1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * ssv_dnp.c |
4 | * generic comedi driver for SSV Embedded Systems' DIL/Net-PCs |
5 | * Copyright (C) 2001 Robert Schwebel <robert@schwebel.de> |
6 | * |
7 | * COMEDI - Linux Control and Measurement Device Interface |
8 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
9 | */ |
10 | |
11 | /* |
12 | * Driver: ssv_dnp |
13 | * Description: SSV Embedded Systems DIL/Net-PC |
14 | * Author: Robert Schwebel <robert@schwebel.de> |
15 | * Devices: [SSV Embedded Systems] DIL/Net-PC 1486 (dnp-1486) |
16 | * Status: unknown |
17 | */ |
18 | |
19 | /* include files ----------------------------------------------------------- */ |
20 | |
21 | #include <linux/module.h> |
22 | #include <linux/comedi/comedidev.h> |
23 | |
24 | /* Some global definitions: the registers of the DNP ----------------------- */ |
25 | /* */ |
26 | /* For port A and B the mode register has bits corresponding to the output */ |
27 | /* pins, where Bit-N = 0 -> input, Bit-N = 1 -> output. Note that bits */ |
28 | /* 4 to 7 correspond to pin 0..3 for port C data register. Ensure that bits */ |
29 | /* 0..3 remain unchanged! For details about Port C Mode Register see */ |
30 | /* the remarks in dnp_insn_config() below. */ |
31 | |
32 | #define CSCIR 0x22 /* Chip Setup and Control Index Register */ |
33 | #define CSCDR 0x23 /* Chip Setup and Control Data Register */ |
34 | #define PAMR 0xa5 /* Port A Mode Register */ |
35 | #define PADR 0xa9 /* Port A Data Register */ |
36 | #define PBMR 0xa4 /* Port B Mode Register */ |
37 | #define PBDR 0xa8 /* Port B Data Register */ |
38 | #define PCMR 0xa3 /* Port C Mode Register */ |
39 | #define PCDR 0xa7 /* Port C Data Register */ |
40 | |
41 | static int dnp_dio_insn_bits(struct comedi_device *dev, |
42 | struct comedi_subdevice *s, |
43 | struct comedi_insn *insn, |
44 | unsigned int *data) |
45 | { |
46 | unsigned int mask; |
47 | unsigned int val; |
48 | |
49 | /* |
50 | * Ports A and B are straight forward: each bit corresponds to an |
51 | * output pin with the same order. Port C is different: bits 0...3 |
52 | * correspond to bits 4...7 of the output register (PCDR). |
53 | */ |
54 | |
55 | mask = comedi_dio_update_state(s, data); |
56 | if (mask) { |
57 | outb(PADR, CSCIR); |
58 | outb(value: s->state & 0xff, CSCDR); |
59 | |
60 | outb(PBDR, CSCIR); |
61 | outb(value: (s->state >> 8) & 0xff, CSCDR); |
62 | |
63 | outb(PCDR, CSCIR); |
64 | val = inb(CSCDR) & 0x0f; |
65 | outb(value: ((s->state >> 12) & 0xf0) | val, CSCDR); |
66 | } |
67 | |
68 | outb(PADR, CSCIR); |
69 | val = inb(CSCDR); |
70 | outb(PBDR, CSCIR); |
71 | val |= (inb(CSCDR) << 8); |
72 | outb(PCDR, CSCIR); |
73 | val |= ((inb(CSCDR) & 0xf0) << 12); |
74 | |
75 | data[1] = val; |
76 | |
77 | return insn->n; |
78 | } |
79 | |
80 | static int dnp_dio_insn_config(struct comedi_device *dev, |
81 | struct comedi_subdevice *s, |
82 | struct comedi_insn *insn, |
83 | unsigned int *data) |
84 | { |
85 | unsigned int chan = CR_CHAN(insn->chanspec); |
86 | unsigned int mask; |
87 | unsigned int val; |
88 | int ret; |
89 | |
90 | ret = comedi_dio_insn_config(dev, s, insn, data, mask: 0); |
91 | if (ret) |
92 | return ret; |
93 | |
94 | if (chan < 8) { /* Port A */ |
95 | mask = 1 << chan; |
96 | outb(PAMR, CSCIR); |
97 | } else if (chan < 16) { /* Port B */ |
98 | mask = 1 << (chan - 8); |
99 | outb(PBMR, CSCIR); |
100 | } else { /* Port C */ |
101 | /* |
102 | * We have to pay attention with port C. |
103 | * This is the meaning of PCMR: |
104 | * Bit in PCMR: 7 6 5 4 3 2 1 0 |
105 | * Corresponding port C pin: d 3 d 2 d 1 d 0 d= don't touch |
106 | * |
107 | * Multiplication by 2 brings bits into correct position |
108 | * for PCMR! |
109 | */ |
110 | mask = 1 << ((chan - 16) * 2); |
111 | outb(PCMR, CSCIR); |
112 | } |
113 | |
114 | val = inb(CSCDR); |
115 | if (data[0] == COMEDI_OUTPUT) |
116 | val |= mask; |
117 | else |
118 | val &= ~mask; |
119 | outb(value: val, CSCDR); |
120 | |
121 | return insn->n; |
122 | } |
123 | |
124 | static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
125 | { |
126 | struct comedi_subdevice *s; |
127 | int ret; |
128 | |
129 | /* |
130 | * We use I/O ports 0x22, 0x23 and 0xa3-0xa9, which are always |
131 | * allocated for the primary 8259, so we don't need to allocate |
132 | * them ourselves. |
133 | */ |
134 | |
135 | ret = comedi_alloc_subdevices(dev, num_subdevices: 1); |
136 | if (ret) |
137 | return ret; |
138 | |
139 | s = &dev->subdevices[0]; |
140 | /* digital i/o subdevice */ |
141 | s->type = COMEDI_SUBD_DIO; |
142 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
143 | s->n_chan = 20; |
144 | s->maxdata = 1; |
145 | s->range_table = &range_digital; |
146 | s->insn_bits = dnp_dio_insn_bits; |
147 | s->insn_config = dnp_dio_insn_config; |
148 | |
149 | /* configure all ports as input (default) */ |
150 | outb(PAMR, CSCIR); |
151 | outb(value: 0x00, CSCDR); |
152 | outb(PBMR, CSCIR); |
153 | outb(value: 0x00, CSCDR); |
154 | outb(PCMR, CSCIR); |
155 | outb(value: (inb(CSCDR) & 0xAA), CSCDR); |
156 | |
157 | return 0; |
158 | } |
159 | |
160 | static void dnp_detach(struct comedi_device *dev) |
161 | { |
162 | outb(PAMR, CSCIR); |
163 | outb(value: 0x00, CSCDR); |
164 | outb(PBMR, CSCIR); |
165 | outb(value: 0x00, CSCDR); |
166 | outb(PCMR, CSCIR); |
167 | outb(value: (inb(CSCDR) & 0xAA), CSCDR); |
168 | } |
169 | |
170 | static struct comedi_driver dnp_driver = { |
171 | .driver_name = "dnp-1486" , |
172 | .module = THIS_MODULE, |
173 | .attach = dnp_attach, |
174 | .detach = dnp_detach, |
175 | }; |
176 | module_comedi_driver(dnp_driver); |
177 | |
178 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
179 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
180 | MODULE_LICENSE("GPL" ); |
181 | |