Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

  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
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
/*-
 * Copyright (c) 2012 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Paul Fleischer <paul@xpg.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <sys/types.h>

#include <arm/armreg.h>
#include <arm/s3c2xx0/s3c2440reg.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>

#include <lib/libkern/libkern.h>
#include <lib/libsa/stand.h>
#include <lib/libsa/loadfile.h>
#include <lib/libsa/iodesc.h>

#include <arch/evbarm/mini2440/mini2440_bootinfo.h>

#define CSR_READ(reg) \
	*(volatile uint32_t *)(reg)
#define CSR_WRITE(reg, val) do { \
	    *(volatile uint32_t *)((reg)) = val; \
	} while (0)

#define UART_BAUDRATE		115200
#define S3C2XX0_XTAL_CLK	12000000
#define BOOTINFO_ADDR		0x31500000

/* Macros to turn on/off LEDs. Numbering is 1-4. */
#define LED_REG (volatile uint16_t*)(S3C2440_GPIO_BASE+GPIO_PBDAT)
#define CLEAR_LEDS() *LED_REG = *LED_REG | 0x1e0
#define LED_ON(led) *LED_REG = *LED_REG & ( ~(1<<(led+4)) & 0x1E0 )
#define LED_OFF(led) *LED_REG = *LED_REG | ( ~(1<<(led+4)) & 0x1E0 )

/* Local variables */
static time_t	wallclock = 0;
static uint32_t timer_inc_rate;
void *bootinfo;
int bi_size;
char *bi_next;

#define STR_EXPAND(tok) #tok
#define STR(tok) STR_EXPAND(tok)

#if defined(DEFAULT_BOOTFILE)
static char *default_boot=STR(DEFAULT_BOOTFILE);
#else
static char *default_boot="net:";
#endif

time_t getsecs();
time_t getusecs();

/* Local functions */
static void s3c24x0_clock_freq2(vaddr_t clkman_base, int *fclk, int *hclk,
				int *pclk);
static void uart_init(uint32_t pclk);
static void time_init(uint32_t pclk);
static void bi_init(void *addr);
static void bi_add(void *new, int type, int size);
static void parse_mac_address(const char *str, uint8_t *enaddr);
static void brdsetup(void);
static void iomux(int, const char *);

extern void* dm9k_init(unsigned int tag, void *macaddr);

/* External variables */
extern char bootprog_name[], bootprog_rev[];

/* External functions */
extern void netif_match(unsigned int tag, uint8_t *macaddr);
/*  extern int sdif_init(unsigned int tag);*/

/* Global variables */
uint32_t socmodel;
int pclk;
struct btinfo_rootdevice	bi_rdev;

/* This is not very flexible, as only one net device is allowed */
struct btinfo_net		bi_net;

struct btinfo_bootpath		bi_path;

void
main(int argc, char *argv[])
{
	int fclk, hclk;
	int fd;
	unsigned long marks[MARK_MAX];
	unsigned char hdr[0x28];
	void (*entry)(void*);
	unsigned elfpriv;
	char *bootfile;
	char *bf;
	bool kernel_loaded;
	uint8_t enaddr[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

	socmodel = CSR_READ(S3C2440_GPIO_BASE + GPIO_GSTATUS1);

	brdsetup();

	/* Give some indication that main() has been reached */
	CLEAR_LEDS();
	LED_ON(4);

	/* Next, we setup the clock of the S3C2440 such that we are not
	   dependent on any other bootloader in this regard.
	   Target FCLK is 405MHz, and we assume an input crystal of 12MHz
	*/
	*(volatile uint32_t*)(S3C2440_CLKMAN_BASE+CLKMAN_MPLLCON) =
		((0x7F << PLLCON_MDIV_SHIFT) & PLLCON_MDIV_MASK) |
		((2 << PLLCON_PDIV_SHIFT) & PLLCON_PDIV_MASK) |
		((1 << PLLCON_SDIV_SHIFT) & PLLCON_SDIV_MASK);
	*(volatile uint32_t*)(S3C2440_CLKMAN_BASE+CLKMAN_UPLLCON) =
		((0x38 << PLLCON_MDIV_SHIFT) & PLLCON_MDIV_MASK) |
		((2 << PLLCON_PDIV_SHIFT) & PLLCON_PDIV_MASK) |
		((2 << PLLCON_SDIV_SHIFT) & PLLCON_SDIV_MASK);

	LED_ON(1);

	s3c24x0_clock_freq2(S3C2440_CLKMAN_BASE, &fclk, &hclk, &pclk);

	uart_init(pclk);
	time_init(pclk);

	/* Let the user know we are alive */
	printf("\n");
	printf(">> %s boot2440, revision %s\n", bootprog_name, bootprog_rev);
	printf("SoC model:");
	switch (socmodel) {
	case 0x32440000:
		printf(" S3C2440"); break;
	case 0x32440001:
		printf(" S3C2440A"); break;
	}
	printf(" (chipid %08x)\n", socmodel);

	bootinfo = (void*) BOOTINFO_ADDR;
	bi_init(bootinfo);

	bi_net.devname[0] = 0;
	bi_path.bootpath[0] = 0;

	/* Try to get boot arguments from any previous boot-loader */
	{
		struct btinfo_bootstring ba;
		int j, i;

		j = 0;
		for (i = 0; i < argc; i++) {
			if (j == MAX_BOOT_STRING-1) {
				ba.bootstring[j] = '\0';
				continue;
			}
			if (strncmp(argv[i], "mac=", 4) == 0) {
				parse_mac_address(argv[i]+4, enaddr);
			} else {
				if (j != 0)
					ba.bootstring[j++] = ' ';

				strncpy(ba.bootstring+j, argv[i], MAX_BOOT_STRING-j);
				j += strlen(argv[i]);
			}
		}
		bi_add(&ba, BTINFO_BOOTSTRING, sizeof(ba));
	}

	LED_ON(3);

	if (argc > 1) {
		bf = argv[argc-1];
	} else {
		bf = default_boot;
	}

	/* Detect networking devices */
	netif_match(0, enaddr);

	kernel_loaded = FALSE;
	do {
		bootfile = strsep(&bf, ";");
		printf("Trying \"%s\"...\n", bootfile);
		fd = open(bootfile, 0);
		if (fd < 0) {
			printf("Failed: %d\n", errno);
			close(fd);
			continue;
		}

		if (fdloadfile(fd, marks, LOAD_ALL) == 0) {
			kernel_loaded = TRUE;
			break;
		}
	} while(bf != NULL);

	if (!kernel_loaded) {
		panic("Failed to load kernel\n");
		_rtt();
	}

#if 1
	/* Set MAC address of the 'dme' net device, if
	 * it isn't set already */
	if (bi_net.devname[0] == 0) {
		uint8_t en[6] = {DM9000MAC};
		snprintf(bi_net.devname, sizeof(bi_net.devname), "dme");
		bi_net.cookie = 0;

		memcpy(bi_net.mac_address, en, sizeof(bi_net.mac_address));
	}
#endif
	/*
	 * ARM ELF header has a distinctive value in "private flags"
	 * field of offset [0x24-x027];
	 * - NetBSD 02 06 (oarm)
	 * - Linux  02 00 (2.4) or 02 02 (2.6)
	 * - NetBSD 02 00 00 05 (earm)
	 */
	lseek(fd, (off_t)0, SEEK_SET);
	read(fd, &hdr, sizeof(hdr));
	memcpy(&elfpriv, &hdr[0x24], sizeof(elfpriv));

	entry = (void *)marks[MARK_ENTRY];
	if (elfpriv == 0x0602 || elfpriv == 0x5000002) {
		struct btinfo_symtab bi_syms;

		bi_syms.nsym = marks[MARK_NSYM];
		bi_syms.ssym = (void*)marks[MARK_SYM];
		bi_syms.esym = (void*)marks[MARK_END];
		bi_add(&bi_syms, BTINFO_SYMTAB, sizeof(bi_syms));
		if (bi_path.bootpath[0] != 0)
		  bi_add(&bi_path, BTINFO_BOOTPATH, sizeof(bi_path));
		bi_add(&bi_rdev, BTINFO_ROOTDEVICE, sizeof(bi_rdev));
		if (bi_net.devname[0] != 0 )
			bi_add(&bi_net, BTINFO_NET, sizeof(bi_net));
	} else {
		printf("Loaded object is not NetBSD ARM ELF");
		_rtt();
	}

	printf("entry=%p, nsym=%lu, ssym=%p, esym=%p\n",
	       (void *)marks[MARK_ENTRY],
	       marks[MARK_NSYM],
	       (void *)marks[MARK_SYM],
	       (void *)marks[MARK_END]);
	(*entry)(bootinfo);

	printf("exec returned, restarting...\n");
	_rtt();
}

void
uart_init(uint32_t pclk)
{
	/* Setup UART0 clocking: Use PCLK */
	*(volatile uint32_t*)(S3C2440_UART_BASE(0)+SSCOM_UBRDIV) =
		(pclk/(UART_BAUDRATE*16)) - 1;

	*(volatile uint32_t*)(S3C2440_UART_BASE(0)+SSCOM_UCON) =
		UCON_TXMODE_INT | UCON_RXMODE_INT;

	*(volatile uint32_t*)(S3C2440_UART_BASE(0)+SSCOM_ULCON) =
		ULCON_PARITY_NONE | ULCON_LENGTH_8;

	*(volatile uint32_t*)(S3C2440_UART_BASE(0)+SSCOM_UFCON) =
		UFCON_TXTRIGGER_0 | UFCON_TXFIFO_RESET | UFCON_FIFO_ENABLE;
}

static uint32_t countdown_duration;

static
void time_init(uint32_t pclk)
{
	/* Configure timer0 to be as slow as possible:
	   Prescaler = 255
	   Divider = 16
	 */

	/* First, configure the prescaler */
	*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCFG0) = 0xff;

	/* Next, the divider */
	*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCFG1) |=
		(TCFG1_MUX_DIV16 <<TCFG1_MUX_SHIFT(0)) & TCFG1_MUX_MASK(0);

		*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCON) =
			TCON_MANUALUPDATE(0);
		*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCNTB(0)) =
			0xffff;
		*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCON) =
			TCON_START(0);


	/* Timer count down duration */
	countdown_duration = 65535/(pclk/256/16);
	timer_inc_rate = pclk/256/16;
	//	printf("Countdown duration is: %ds\n", countdown_duration);
#if 0
	{
		/* Timer test */
		time_t time, old_time;

		while(1) {
			time = old_time = getsecs();
			do {
				time = getsecs();
			} while(time == old_time);
			printf("Count %u\n", (int)time);
		}
	}
#endif
}

time_t
getsecs()
{
	time_t secs = getusecs()/1000000;
	return secs;
}

time_t
getusecs() {
	uint32_t count;
	//do {
		count = *(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCNTO(0));
//} while( count > 65500);

	*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCON) =
		TCON_MANUALUPDATE(0);
	*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCNTB(0)) =
		0xffff;
	*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCON) =
		TCON_START(0);

	wallclock += ((65535-count)*1000000) / timer_inc_rate;

	return wallclock;
}

void
usleep(int us) {
	uint32_t count;
	uint32_t target_clock = wallclock+us;

	while( wallclock < target_clock) {
		do {
			count = *(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCNTO(0));
		} while( count > 65500);

		*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCON) =
			TCON_MANUALUPDATE(0);
		*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCNTB(0)) =
			0xffff;
		*(volatile uint32_t*)(S3C2440_TIMER_BASE+TIMER_TCON) =
			TCON_START(0);

		wallclock += ((65535-count)*1000000) / timer_inc_rate;
	}
}


void
mini2440_panic()
{
	int i, l;
	int v;
	while(1) {
		CLEAR_LEDS();
		for(l=0; l<0xffffff; l++) {
			v = *((int*)(S3C2440_TIMER_BASE+TIMER_TCNTO(0)));
		}
		for(i=1; i<=4; i++) {
			LED_ON(i);
		}
		for(l=0; l<0xffffff; l++) {
			v = *((int*)(S3C2440_TIMER_BASE+TIMER_TCNTO(0)));
		}
		__USE(v);
	}
}

void
s3c24x0_clock_freq2(vaddr_t clkman_base, int *fclk, int *hclk, int *pclk)
{
	uint32_t pllcon, divn, camdivn;
	int mdiv, pdiv, sdiv;
	uint32_t f, h, p;

	pllcon = *(volatile uint32_t *)(clkman_base + CLKMAN_MPLLCON);
	divn = *(volatile uint32_t *)(clkman_base + CLKMAN_CLKDIVN);
	camdivn = *(volatile uint32_t *)(clkman_base + CLKMAN_CAMDIVN);

	mdiv = (pllcon & PLLCON_MDIV_MASK) >> PLLCON_MDIV_SHIFT;
	pdiv = (pllcon & PLLCON_PDIV_MASK) >> PLLCON_PDIV_SHIFT;
	sdiv = (pllcon & PLLCON_SDIV_MASK) >> PLLCON_SDIV_SHIFT;

	f = ((mdiv + 8) * S3C2XX0_XTAL_CLK) / ((pdiv + 2) * (1 << sdiv)) * 2;
	h = f;

	/* HDIVN of CLKDIVN can have 4 distinct values */
	switch( (divn & CLKDIVN_HDIVN_MASK) >> CLKDIVN_HDIVN_SHIFT )
		{
		case 0:
			/* 00b: HCLK = FCLK/1*/
			break;
		case 1:
			/* 01b: HCLK = FCLK/2*/
			h /= 2;
			break;
		case 2:
			/* 10b: HCLK = FCLK/4 when CAMDIVN[9] (HCLK4_HALF) = 0
			 *      HCLK = FCLK/8 when CAMDIVN[9] (HCLK4_HALF) = 1 */
			if( camdivn & CLKCAMDIVN_HCLK4_HALF )
				h /= 8;
			else
				h /= 4;
			break;
		case 3:
			/* 11b: HCLK = FCLK/3 when CAMDIVN[8] (HCLK3_HALF) = 0
			 *      HCLK = FCLK/6 when CAMDIVN[8] (HCLK3_HALF) = 1 */
			if( camdivn & CLKCAMDIVN_HCLK3_HALF )
				h /= 6;
			else
				h /= 3;
			break;
		}

	p = h;

	if (divn & CLKDIVN_PDIVN)
		p /= 2;

	if (fclk) *fclk = f;
	if (hclk) *hclk = h;
	if (pclk) *pclk = p;
}

void
putchar(int c)
{
	uint32_t stat;

	if (c == '\n')
		putchar('\r');
	
	do {
		stat = CSR_READ(S3C2440_UART_BASE(0) + SSCOM_UTRSTAT);
	} while ((stat & UTRSTAT_TXEMPTY) == 0);

	CSR_WRITE(S3C2440_UART_BASE(0) + SSCOM_UTXH, c);
}

void
_rtt()
{
	int cpsr_save, tmp;
	/* Disable interrupts */
	__asm volatile("mrs %0, cpsr;"
		       "orr %1, %0, %2;"
		       "msr cpsr_c, %1;"
		       : "=r" (cpsr_save), "=r" (tmp)
		       : "I" (I32_bit)
		       );

	/* Disable MMU */
	__asm volatile("mrc p15, 0, %0, c1, c0, 0;"
		       "bic %0, %0, %1;"
		       "mcr p15, 0, %0, c1, c0, 0;"
		       : "=r" (tmp)
		       : "I" (CPU_CONTROL_MMU_ENABLE)
		       );

	/* Configure watchdog to fire now */
	*(volatile uint32_t *)(S3C2440_WDT_BASE + WDT_WTCON) =
		(0 << WTCON_PRESCALE_SHIFT) | WTCON_ENABLE |
		WTCON_CLKSEL_16 | WTCON_ENRST;
	__builtin_unreachable();
}

void
bi_init(void *addr)
{
	struct btinfo_magic bi_magic;

	memset(addr, 0, BOOTINFO_MAXSIZE);
	bi_next = (char*) addr;
	bi_size = 0;

	bi_magic.magic = BOOTINFO_MAGIC;
	bi_add(&bi_magic, BTINFO_MAGIC, sizeof(bi_magic));
}


void
bi_add(void *new, int type, int size)
{
	struct btinfo_common *bi;

	if (bi_size + size > BOOTINFO_MAXSIZE)
		return;

	bi = new;
	bi->next = size;
	bi->type = type;
	memcpy(bi_next, new, size);
	bi_next += size;
}

static void
parse_mac_address(const char *str, uint8_t *enaddr)
{
	int i;
	char *next = (char*)str;

	for(i=0;i<6;i++) {
		str = next;
		enaddr[i] = (unsigned char)strtoll(str, &next, 16);
		if( *next == ':' ) {
			next++;
		} else {
			break;
		}
	}
}

static void
brdsetup(void)
{
/*
 * MINI2440 pin usage summary
 *
 *  B5	output	LED1 control
 *  B6	output	LED2 control
 *  B7	output	LED3 control
 *  B8	output	LED4 control
 *  G0	EINT8	K1 button
 *  G3	EINT11	K2 button
 *  G5	EINT13	K3 button
 *  G6	EINT14	K4 button
 *  G7	EINT15	K5 button
 *  G11	EINT19	K6 button
 *  F7	EINT7	DM9000 interrupt
 *  G12	EINT20	camera interrupt
 *  G8	input	SD card presense detect
 *  H8	input	SD write protect sense
 *  B0	TOUT0	buzzer PWM
 *  B1	TOUT1	LCD backlight PWM
 *  B2	output	UDA1341 audio L3MODE
 *  B3	output	UDA1341 audio L3DATA
 *  B4	output	UDA1341 audio L3LOCK
 *
 *  A21, A11, G15, G14, G13: not used.
 *
 *      i       input sense
 *      o       output control
 *      2       function 2
 *      3       function 3
 *      0       output control (A only)
 *      1       function 1 (A only)
 *      ./x     no function, not connected or don't-care
 *  
 * A ........ .1x11111 1111x111 11111111
 * B                   .....22o ooooooo2
 * C                   22222222 22222222
 * D                   22222222 22222222
 * E                   22222222 22222222
 * F                   ........ 22222222
 * G                   xxx2222i 22232322
 * H                   .....22i 22222222
 * J                   ...22222 22222222
 */
	iomux('A', "........ .1x11111 1111x111 11111111");
	iomux('B', ".....22o ooooooo2");
	iomux('C', "22222222 22222222");
	iomux('D', "22222222 22222222");
	iomux('E', "22222222 22222222");
	iomux('F', "........ 22222222");
	iomux('G', "xxx2222i 22232322");
	iomux('H', ".....22i 22222222");
	iomux('J', "...22222 22222222");

	/* mask all possible external interrupt source [23:3] */
	CSR_WRITE(S3C2440_GPIO_BASE + GPIO_EINTMASK, ~0);
}

static void
iomux(int grp, const char *cnf)
{
	uint32_t con;
	int sft, i, v;

	con = v = 0;
	sft = (grp != 'A') ? 2 : 1;
	for (i = 0; cnf[i] != '\0'; i++) {
		switch (cnf[i]) {
		case 'i':
		case '0':
		case '.':
		case 'x':
			v = 0; break;
		case 'o':
		case '1':
			v = 1; break;
		case '2':
			v = 2; break;
		case '3':
			v = 3; break;
		default:
			continue;
		}
		con = (con << sft) | v;
	}
	CSR_WRITE(S3C2440_GPIO_BASE + 0x10 * (grp - 'A'), con);
}