summaryrefslogtreecommitdiff
path: root/src/hal/drivers/opto_ac5.c
blob: 9dcaedfb665dad0fb7a4b782d0e8440a2b949d41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
#include <linux/pci.h>
#include "rtapi.h"			// RTAPI realtime OS API.
#include "rtapi_app.h"			// RTAPI realtime module decls.
#include "hal.h"			// HAL public API decls.
#include "opto_ac5.h"			// Hardware dependent defines.

#ifndef MODULE
#define MODULE
#endif

#ifdef MODULE
// Module information.
MODULE_AUTHOR("Chris Morley");
MODULE_DESCRIPTION("Driver for opto22 pci AC5 for EMC HAL");
MODULE_LICENSE("GPL");
#endif // MODULE


/*************************************************************************
                          typedefs and defines
*************************************************************************/


// Vendor and device ID. "magic numbers"
#define opto22_VENDOR_ID			0x148a		// opto22 corp
#define opto22_pci_AC5_DEVICE_ID		0xac05		// AC5 relay board interface
#define MAX_BOARDS	4					// could be higher uses more memory

// OFFSETS FROM BASE ADDRESS FOR COMMANDS TO OPTO CARD
// for port 0
#define CONFIG_WRITE_OFFSET_0  0x00000004
#define CONFIG_READ_OFFSET_0   0x00000100
#define DATA_WRITE_OFFSET_0    0X00000008
#define DATA_READ_OFFSET_0     0X00000300

// for port 1
#define CONFIG_WRITE_OFFSET_1  0x00000014
#define CONFIG_READ_OFFSET_1   0x00000500
#define DATA_WRITE_OFFSET_1    0X00000018
#define DATA_READ_OFFSET_1     0X00000700

// These methods are used for initialization.
static int Device_Init(board_data_t *pboard);
static int Device_ExportPinsParametersFunctions(board_data_t *pboard, int comp_id, int boardId);
static int Device_ExportDigitalInPinsParametersFunctions(board_data_t *pboard, int comp_id, int boardId);
static int Device_ExportDigitalOutPinsParametersFunctions(board_data_t *pboard, int comp_id, int boardId);
// These methods are exported to the HAL.
static void Device_DigitalInRead(void *this, long period);
static void Device_DigitalOutWrite(void *this, long period);
/*************************************************************************
                                   Globals
*************************************************************************/


typedef struct {
	int comp_id;	/* HAL component ID */
	board_data_t *boards[MAX_BOARDS];
	int num_of_boards;
} Driver_t;

static Driver_t				driver;

// in case no portconfig is given when loading driver
// sets 12 inputs then 12 outputs per port and sets leds (bit 31,32) for output
// could define more variables so the ports of extra cards could be configured differently but
// I doubt if anyone will use more then one card

static unsigned long		portconfig0 = 0Xc0fff000;
RTAPI_MP_LONG(portconfig0, "port 0 i/o configuration number");
static unsigned long		portconfig1 = 0Xc0fff000;
RTAPI_MP_LONG(portconfig1, "port 1 i/o configuration number");

/******************************************************************************
                              Init and exit code
 ******************************************************************************/

int rtapi_app_main(void)
{
    int n;
    board_data_t *pboard;
    struct pci_dev *pDev;

    // Connect to the HAL.
    driver.comp_id = hal_init("opto_ac5");
    if (driver.comp_id < 0) {
	rtapi_print_msg(RTAPI_MSG_ERR, " ERROR OPTO_AC5--- hal_init() failed\n");
	return(-1);
    }	
    for ( n = 0 ; n < MAX_BOARDS ; n++ ) {
	driver.boards[n] = NULL;
    }
    pDev = NULL;
    for ( n = 0 ; n < MAX_BOARDS ; n++ ) 
    {
	// Find a M5I20 card.
	pDev = pci_get_device(opto22_VENDOR_ID, opto22_pci_AC5_DEVICE_ID, pDev);
	if ( pDev == NULL ) { /* no more boards */break;}
	

	/* Allocate HAL memory for the board */
	pboard = (board_data_t *)(hal_malloc(sizeof(board_data_t)));
	if ( pboard == NULL ) {
	    rtapi_print_msg(RTAPI_MSG_ERR, "ERROR OPTO_AC5--- hal_malloc() failed\n");
	    rtapi_app_exit();
	    return -1;
	}
	// save pointer
	driver.boards[n] = pboard;

	/* gather info about the board and save it */
	pboard->pci_dev = pDev;
	pboard->slot = PCI_SLOT(pDev->devfn);
	pboard->num = n;
	rtapi_print("INFO OPTO_AC5--- Board %d detected in PCI Slot: %2x\n", pboard->num, pboard->slot);
	/* region 0 is the 32 bit memory mapped I/O region */
	pboard->len = pci_resource_len(pDev, 0);
	pboard->base = ioremap_nocache(pci_resource_start(pDev, 0), pboard->len);
	if ( pboard->base == NULL ) {
	    rtapi_print_msg(RTAPI_MSG_ERR,
		"ERROR OPTO_AC5---  could not find board %d PCI base address\n", pboard->num );
	    rtapi_app_exit();
	    return -1;
	} else {
	 	    rtapi_print(
		"INFO OPTO_AC5--- board %d mapped to %08lx, Len = %ld\n",
		pboard->num, (long)pboard->base,(long)pboard->len);
		}
	// Initialize device.
	if(Device_Init(pboard)){
	    hal_exit(driver.comp_id);
	    return(-1);
	}

	// Export pins, parameters, and functions.
	if(Device_ExportPinsParametersFunctions(pboard, driver.comp_id, n)){
	    hal_exit(driver.comp_id);
	    return(-1);
	}
	
    }
    
    if(n == 0){
	/* No cards detected */
	rtapi_print ("ERROR OPTO_AC5---  No opto PCI-AC5 card(s) detected\n");
	rtapi_app_exit();
	return(-1);
    	}
	
    hal_ready(driver.comp_id);
    return(0);
}

// here we turn the leds and outputs off and unmap the pci driver
void rtapi_app_exit(void)
{ 
  	int n;
	board_data_t 	*pDevice;

	pDevice = driver.boards[0];


    hal_exit(driver.comp_id);
    for ( n = 0; n < MAX_BOARDS; n++ ) 
	{
		if ( driver.boards[n] != NULL)
		 {
			rtapi_print ("INFO OPTO_AC5--- board %i driver removed.\n",n);
			writel(0XC0FFFFFF, driver.boards[n]->base + (DATA_WRITE_OFFSET_0));
			writel(0XC0FFFFFF, driver.boards[n]->base + (DATA_WRITE_OFFSET_1));
		   	// Unmap board memory
			if ( driver.boards[n]->base != NULL ) 
			{
				iounmap(driver.boards[n]->base);
	   		}
		}
   	}
}

/******************************************************************************
 * DEVICE OBJECT FUNCTION DEFINITIONS
 ******************************************************************************/

/*
 * LOCAL FUNCTIONS
 */
static int Device_Init(board_data_t *pboard)
{

 // Initialize hardware.
// portconfig[0 or 1] are either the default number from global or mapped from the command line 
// make sure last two bits are set for output so leds will work 

	if ((portconfig0 & 0x80000000) ==0) { 	portconfig0 |=0x80000000;	}
	if ((portconfig0 & 0x40000000) ==0) { 	portconfig0 |=0x40000000;	}
	if ((portconfig1 & 0x80000000) ==0) { 	portconfig1 |=0x80000000;	}
	if ((portconfig1 & 0x40000000) ==0) { 	portconfig1 |=0x40000000;	}
	writel(portconfig0, pboard->base + (CONFIG_WRITE_OFFSET_0));
	writel(portconfig1, pboard->base + (CONFIG_WRITE_OFFSET_1));

 // Initialize digital I/O structure mask.
    
	pboard->port[0].mask = portconfig0;
	pboard->port[1].mask = portconfig1;
    
    return(0);
}

static int Device_ExportPinsParametersFunctions(board_data_t *this, int componentId, int boardId)
{
    int					msgLevel, error=0;

// This function exports a lot of stuff, which results in a lot of
// logging if msg_level is at INFO or ALL. So we save the current value
// of msg_level and restore it later.  If you actually need to log this
// function's actions, change the second line below.

 	msgLevel = rtapi_get_msg_level();
   	rtapi_set_msg_level(RTAPI_MSG_WARN);

// Export digital I/O.

    if(!error) error = Device_ExportDigitalInPinsParametersFunctions(this, componentId, boardId);
    if(!error) error = Device_ExportDigitalOutPinsParametersFunctions(this, componentId, boardId);

// Restore saved message level.

    rtapi_set_msg_level(msgLevel);

    return(error);
}

// we check each ports mask (port[portnum].mask) against a mask bit to see which of the 24 points of i/o are inputs (a false bit is an input)
// then export HAL pins mapped to the proper io structure
// HAL pin numbers represent position in an opto22 relay rack (eg. pin 00 is position 0)
// this way you can configure the i/o in any way for all the 24 points of each port

static int Device_ExportDigitalInPinsParametersFunctions(board_data_t *this, int comp_id, int boardId)
{
    int					halError=0, channel,mask,portnum=0;
    char				name[HAL_NAME_LEN + 1];

    // Export pins and parameters.
	while (portnum<2)
		{
		mask=1;
   		 for(channel = 0; channel < 24; channel++)
		   {
			if ((this->port[portnum].mask & mask)==0)//physical input?
			{
			// Pins.
			if((halError = hal_pin_bit_newf(HAL_OUT, &(this->port[portnum].io[channel].pValue),
			  comp_id, "opto-ac5.%d.port%d.in-%02d", boardId, portnum, channel)) != 0)
			    break;

			if((halError = hal_pin_bit_newf(HAL_OUT, &(this->port[portnum].io[channel].pValueNot),
			  comp_id, "opto-ac5.%d.port%d.in-%02d-not", boardId, portnum, channel)) != 0)
			    break;

			// Init pin.
			*(this->port[portnum].io[channel].pValue) = 0;
			*(this->port[portnum].io[channel].pValueNot) = 1;
			}
			mask <<=1;
		   }

		   portnum ++;	
	 	}

    // Export functions.
    if(!halError){
	rtapi_snprintf(name, sizeof(name), "opto-ac5.%d.digital-read", boardId);
	halError = hal_export_funct(name, Device_DigitalInRead, this, 0, 0, comp_id);
    }

    if(halError){
	rtapi_print_msg(RTAPI_MSG_ERR, "ERROR OPTO22_AC5---exporting digital inputs to HAL failed\n");
	return(-1);
    }

    return(0);
}


// we check each ports mask (port[portnum].mask) against a mask bit to see which of the 24 points of i/o are outputs (a true bit is an output)
// then export HAL pins mapped to the proper io structure
// this way you can configure the i/o in any way for all the 24 points of each port
// HAL pin numbers represent position in an opto22 relay rack (eg. pin 00 is position 0)
// the LEDS are bits 31 and 32 of the portmask but are mapped to 24 and 25 of the i0 structure

static int Device_ExportDigitalOutPinsParametersFunctions(board_data_t *this, int comp_id, int boardId)
{
    int					halError=0, channel,mask,portnum=0;
    char				name[HAL_NAME_LEN + 1];

    // Export pins and parameters.
    
	while (portnum<2)
		{
		mask=1;
   		 for(channel = 0; channel < 24; channel++)
		   {
			if ((this->port[portnum].mask & mask)!=0)//phyical output?
			{
			// Pins.
			if((halError = hal_pin_bit_newf(HAL_IN, &(this->port[portnum].io[channel].pValue),
			  comp_id, "opto-ac5.%d.port%d.out-%02d", boardId, portnum, channel)) != 0)
			    break;

			// Parameters.
			if((halError = hal_param_bit_newf(HAL_RW, &(this->port[portnum].io[channel].invert),
			  comp_id, "opto-ac5.%d.port%d.out-%02d-invert", boardId, portnum, channel)) != 0)
			    break;

			// Init pin.
			*(this->port[portnum].io[channel].pValue) = 0;
			this->port[portnum].io[channel].invert = 0;
		   	}
			mask <<=1;
		   }
		   portnum++;
		}
		// led Pins.
		portnum=0;
		for(channel = 0; channel < 2; channel++)
		{
			if((halError = hal_pin_bit_newf(HAL_IN, &(this->port[portnum].io[24].pValue),
			  comp_id, "opto-ac5.%d.led%d", boardId, channel+portnum)) != 0)
			    break;

			if((halError = hal_pin_bit_newf(HAL_IN, &(this->port[portnum].io[25].pValue),
			  comp_id, "opto-ac5.%d.led%d", boardId, channel+portnum+1)) != 0)
			    break;
			portnum++;
		}
    // Export functions.
    if(!halError){
	rtapi_snprintf(name, sizeof(name), "opto-ac5.%d.digital-write", boardId);
	halError = hal_export_funct(name, Device_DigitalOutWrite, this, 0, 0, comp_id);
    }

    if(halError){
	rtapi_print_msg(RTAPI_MSG_ERR, "ERROR OPTO_AC5---exporting digital outputs to HAL failed\n");
	return(-1);
    }

    return(0);
}

// here we read inputs from board
// we read the current data (variable 'pins') of the first port. Then for each of the 24 points
// we compare to the mask of the first port to see which of the 24 io points are inputs (the bits that are false)
// if it is an input then check 'pins' against the mask to see if input bit is true
// update the HAL pin and not-pin accoringly. shift the mask then do the next point (of 24 io points)
// after all pins done-increase 'portnum' to 1 set offset to the offset for port1
// then do it all again on the second port

static void
Device_DigitalInRead(void *arg, long period)
{
    board_data_t			*pboard = (board_data_t *)arg;
    DigitalPinsParams			*pDigital;
    int					i, portnum=0;
    unsigned long			pins, offset=DATA_READ_OFFSET_0, mask;

    // For each port.
	while (portnum<2)
		{
			mask=1;
			pDigital = &pboard->port[portnum].io[0];

			// Read digital I/O register.
			pins=	readl (pboard->base + (offset));
   			for(i = 0; i < 24; i++,pDigital++)
			{
				if ((pboard->port[portnum].mask & mask) ==0) // is it an input bit ?
				{
				    if ((pins & mask) !=0){	*(pDigital->pValue) =0;
					}else{	*(pDigital->pValue) = 1;	}
					// Update not pin.
				    *(pDigital->pValueNot) = !*(pDigital->pValue);
				}
	  		 	 mask <<=1;// shift mask
			}
			portnum++;
			offset=DATA_READ_OFFSET_1;// change to offset for port1
 		}
}

// here we output data and update LEDS
// we look at the mask of the first port to see which of the 24 io points are outputs (the bits that are true)
// then check the OUT HAL pins to see if output should be on and if it should - OR the bit (using 'mask') to the variable 'pins' 
// then set 'mask' to the last bit (32) and check the HAL led pin to see if true- OR the bit to 'pins' if it is
// set 'mask to second to second to last bit (31) do the same
// have to remember that a 1 sent to the hardware turns an output OFF
// finally write the variable 'pins' to the boards first port
// reset offset to the next port offset, increase portnum for port 1
// then do it all again on the second port

static void
Device_DigitalOutWrite(void *arg, long period)
{
    board_data_t			*pboard = (board_data_t *)arg;
    DigitalPinsParams			*pDigital;
    int					i, j, portnum=0;
    unsigned long			pins, offset=DATA_WRITE_OFFSET_0,mask;

    // For each port.
	while (portnum<2)
		{
			pDigital = &pboard->port[portnum].io[0];
			mask=1;
			pins=0;

   			// For each pin.
			for(j = 0; j < 24; j++, pDigital++)
			{
				if ((pboard->port[portnum].mask & mask) !=0) //is it an output?
				{
			 	   // add mask to pins if HAL pin + invert =true.
				    if( (!*(pDigital->pValue) && !(pDigital->invert) ) ||
				       ( *(pDigital->pValue) &&  (pDigital->invert) ))
					 {	pins |= mask;	    }
				}
	   			 mask <<=1; // shift mask
				
			}

			// CHECK LED PINS
			pDigital = &pboard->port[portnum].io[23];//one before what we want to check
			for (i = 0;i < 2;i++)
				{
			 		mask=1<<(31-i);
					pDigital++;
				
					if ( *(pDigital->pValue) ==0 ) {	pins |= mask;	    }	
				}
			// Write digital I/O register.
			writel(pins,pboard->base + (offset));
			portnum ++;
			offset=DATA_WRITE_OFFSET_1; // set to port1 offset
   		 }
}