/* * This file contains the code for directly accessing the GPIO pins. */ #include "tcl.h" #include #include #include #include #include #include #define BCM2708_PERI_BASE_DEFAULT 0x20000000 #define BCM2709_PERI_BASE_DEFAULT 0x3f000000 #define GPIO_BASE_OFFSET 0x200000 #define PAGE_SIZE (4*1024) #define BLOCK_SIZE (4*1024) #define GPSET0 7 #define GPSET1 8 #define GPCLR0 10 #define GPCLR1 11 #define GPLEV0 13 #define GPLEV1 14 #define GPPUD 37 #define GPPUDCLK0 38 #define GPPUDCLK1 39 TCL_DECLARE_MUTEX(setupMutex); static volatile unsigned *gpio = NULL; static const char *edgestr[] = { "none", "falling", "rising", "both" }; enum { GPIO_INT_NONE, GPIO_INT_FALLING, GPIO_INT_RISING, GPIO_INT_BOTH }; typedef struct { Tcl_Interp *interp; int fd; int state; Tcl_Obj *rising, *falling; } GpioEvent; typedef struct { Tcl_Event event; Tcl_Interp *interp; Tcl_Obj *script; int num, edge; } Tcl_GpioEvent; static GpioEvent *gpio_events[54]; static void GpioExitHandler(ClientData); static int setup_io(Tcl_Interp *interp) { int mem_fd; char *gpio_mem, *gpio_map; unsigned peri_base, gpio_base; unsigned char buf[4]; FILE *fp; char line [120]; /* First check for gpiomem - this will always give us the correct * memory location and does not need root access */ if ((mem_fd = open("/dev/gpiomem", O_RDWR|O_SYNC)) > 0) { gpio_map = (char *)mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0); if (gpio_map == MAP_FAILED) { Tcl_AppendResult(interp, "couldn't map memory: ", Tcl_PosixError(interp), NULL); close(mem_fd); return TCL_ERROR; } else { /* Always use volatile pointer! */ gpio = (volatile unsigned *)gpio_map; return TCL_OK; } } /* Fall back to /dev/mem and requiring root */ /* Determine the peri base */ if ((fp = fopen("/proc/device-tree/soc/ranges", "rb")) != NULL) { /* get peri base from device tree */ fseek(fp, 4, SEEK_SET); if (fread(buf, 1, sizeof buf, fp) == sizeof buf) { peri_base = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; fclose(fp); } else { fclose(fp); Tcl_AppendResult(interp, "couldn't read device tree", NULL); return TCL_ERROR; } } else { /* guess peri base based on /proc/cpuinfo hardware field */ if ((fp = fopen("/proc/cpuinfo", "r")) == NULL) { Tcl_AppendResult(interp, "couldn't open /proc/cpuinfo: ", Tcl_PosixError(interp), NULL); return TCL_ERROR; } while (fgets(line, 120, fp) != NULL) if (strncmp(line, "Hardware", 8) == 0) break; fclose(fp); if (strstr(line, "BCM2708") != NULL) /* Model 1 */ peri_base = BCM2708_PERI_BASE_DEFAULT; else if (strstr(line, "BCM2709") != NULL) peri_base = BCM2709_PERI_BASE_DEFAULT; else { Tcl_AppendResult(interp, "couldn't determine peri base", NULL); return TCL_ERROR; } } gpio_base = peri_base + GPIO_BASE_OFFSET; /* mmap the GPIO memory registers */ if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { Tcl_AppendResult(interp, "couldn't open /dev/mem: ", Tcl_PosixError(interp), NULL); return TCL_ERROR; } /* Allocate MAP block */ if ((gpio_mem = ckalloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) { close(mem_fd); return TCL_ERROR; } if ((unsigned long)gpio_mem % PAGE_SIZE) gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE); /* Now map it */ gpio_map = (char *)mmap((void *)gpio_mem, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, mem_fd, gpio_base); if ((long)gpio_map < 0) { close(mem_fd); ckfree(gpio_mem); Tcl_AppendResult(interp, "couldn't map memory: ", Tcl_PosixError(interp), NULL); return TCL_ERROR; } /* Always use volatile pointer! */ gpio = (volatile unsigned *)gpio_map; Tcl_CreateExitHandler(GpioExitHandler, NULL); return TCL_OK; } #define GPIO_INIT if (gpio == NULL && setup_io(interp) != TCL_OK) return TCL_ERROR; int GpioFunctionCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int i, j, val, num; Tcl_Obj *result, *func[8]; static const char *funcstr[] = { "input", "output", "altfunc5", "altfunc4", "altfunc0", "altfunc1", "altfunc2", "altfunc3", NULL }; if (objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "?gpiopin? ?function?"); return TCL_ERROR; } GPIO_INIT; if (objc == 1) { for (i = 0; funcstr[i] != NULL; i++) { func[i] = Tcl_NewStringObj(funcstr[i], -1); } result = Tcl_NewObj(); /* Read GPFSELn values */ for (i = 0; i < 6; i++) { val = *(gpio + i); for (j = 0; j < 10 && 10 * i + j < 54; j++) { Tcl_ListObjAppendElement(NULL, result, func[val & 7]); val >>= 3; } } Tcl_SetObjResult(interp, result); return TCL_OK; } if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK) return TCL_ERROR; if (num < 0 || num > 53) { Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL); return TCL_ERROR; } if (objc == 2) { val = (*(gpio + num / 10) >> ((num % 10) * 3)) & 7; Tcl_SetObjResult(interp, Tcl_NewStringObj(funcstr[val], -1)); return TCL_OK; } if (Tcl_GetIndexFromObj(interp, objv[2], funcstr, "function", 0, &val) != TCL_OK) return TCL_ERROR; val = *(gpio + num / 10) ^ (val << ((num % 10) * 3)); *(gpio + num / 10) ^= val & (7 << ((num % 10) * 3)); return TCL_OK; } int GpioOutputCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int num, state, offset; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "gpiopin state"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK) return TCL_ERROR; if (num < 0 || num > 53) { Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[2], &state) != TCL_OK) return TCL_ERROR; GPIO_INIT; offset = (state != 0 ? GPSET0 : GPCLR0) + num / 32; *(gpio + offset) = 1 << (num % 32); return TCL_OK; } int GpioInputCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int num, state, offset; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "gpiopin"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK) return TCL_ERROR; if (num < 0 || num > 53) { Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL); return TCL_ERROR; } GPIO_INIT; offset = GPLEV0 + num / 32; state = (*(gpio + offset) >> (num % 32)) & 1; Tcl_SetObjResult(interp, Tcl_NewBooleanObj(state)); return TCL_OK; } int GpioUSleepCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int usec; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "usec"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &usec) != TCL_OK) return TCL_ERROR; if (usec < 0) { Tcl_AppendResult(interp, "usec must be a positive integer", NULL); return TCL_ERROR; } usleep(usec); return TCL_OK; } int GpioPullCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { unsigned int mask[2] = {0, 0}; int pull, num, x; static const char *pullstr[] = { "none", "down", "up", NULL }; enum { GPIO_PULL_NONE = 0, GPIO_PULL_DOWN = 1, GPIO_PULL_UP = 2 }; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "level gpiopin ?...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], pullstr, "level", 0, &pull) != TCL_OK) { return TCL_ERROR; } for (x = 2; x < objc; x++) { if (Tcl_GetIntFromObj(interp, objv[x], &num) != TCL_OK) { return TCL_ERROR; } if (num < 0 || num > 53) { Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL); return TCL_ERROR; } mask[num / 32] |= 1 << (num % 32); } GPIO_INIT; *(gpio + GPPUD) = pull; usleep(10); *(gpio + GPPUDCLK0) = mask[0]; *(gpio + GPPUDCLK1) = mask[1]; usleep(10); *(gpio + GPPUD) = GPIO_PULL_NONE; *(gpio + GPPUDCLK0) = 0; *(gpio + GPPUDCLK1) = 0; return TCL_OK; } static int GpioExport(int num) { int fd, n; char line[32]; sprintf(line, "/sys/class/gpio/gpio%d", num); if (access(line, R_OK | W_OK | X_OK) != 0) { /* Pin is not yet exported */ fd = open("/sys/class/gpio/export", O_WRONLY); if (fd < 0) { return TCL_ERROR; } n = sprintf(line, "%d\n", num); if (write(fd, line, n) != n) { close(fd); return TCL_ERROR; } close(fd); } return TCL_OK; } static int GpioEdge(int num, const char *str) { int fd, n, cnt = 0; char line[32]; sprintf(line, "/sys/class/gpio/gpio%d/edge", num); while (access(line, R_OK | W_OK) != 0) { /* Allow some time for the file to spring into existence */ /* This is normally accomplished within around 20 ms, */ /* But on older hardware it can take around 100 ms */ if (cnt++ > 25) return TCL_ERROR; usleep(5000); } fd = open(line, O_WRONLY); if (fd < 0) return TCL_ERROR; n = sprintf(line, "%s\n", str); if (write(fd, line, n) != n) { close(fd); return TCL_ERROR; } close(fd); return TCL_OK; } static void GpioRelease(int num) { GpioEvent *evPtr; Tcl_MutexLock(&setupMutex); evPtr = gpio_events[num]; gpio_events[num] = NULL; Tcl_MutexUnlock(&setupMutex); if (evPtr != NULL) { if (evPtr->fd >= 0) { GpioEdge(num, "none"); Tcl_DeleteFileHandler(evPtr->fd); close(evPtr->fd); } if (evPtr->rising != NULL) Tcl_DecrRefCount(evPtr->rising); if (evPtr->falling != NULL) Tcl_DecrRefCount(evPtr->falling); ckfree((void *)evPtr); } } static void GpioUnbind(Tcl_Interp *interp, int num, int edge) { int current = GPIO_INT_NONE; GpioEvent *evPtr; evPtr = gpio_events[num]; /* In theory the pin may already have been released and */ /* posibly even reassigned to another interpreter */ if (evPtr != NULL && evPtr->interp == interp) { if (evPtr->rising != NULL) current |= GPIO_INT_RISING; if (evPtr->falling != NULL) current |= GPIO_INT_FALLING; if ((current & edge) == current) { GpioRelease(num); } else if ((current & edge) != 0) { if ((current & edge & GPIO_INT_RISING) != 0) { Tcl_DecrRefCount(evPtr->rising); evPtr->rising = NULL; } if ((current & edge & GPIO_INT_FALLING) != 0) { Tcl_DecrRefCount(evPtr->falling); evPtr->falling = NULL; } GpioEdge(num, edgestr[current & ~edge]); } } } static int GpioCallBackHandler(Tcl_Event *evPtr, int flags) { Tcl_GpioEvent *ev = (Tcl_GpioEvent *) evPtr; Tcl_Preserve(ev->interp); if (Tcl_EvalObjEx(ev->interp, ev->script, TCL_EVAL_GLOBAL) != TCL_OK) { Tcl_AddErrorInfo(ev->interp, "\n (\"gpio event\")"); Tcl_BackgroundError(ev->interp); GpioUnbind(ev->interp, ev->num, ev->edge); } Tcl_Release(ev->interp); return 1; } static void GpioEventHandler(ClientData clientData, int mask) { int n, edge, num = (int)(intptr_t)clientData; Tcl_Obj *script; Tcl_GpioEvent *cbPtr; GpioEvent *evPtr; char line[32]; if (!(mask & TCL_EXCEPTION)) return; evPtr = gpio_events[num]; lseek(evPtr->fd, 0, SEEK_SET); n = read(evPtr->fd, line, sizeof line); if (n > 0) { if (evPtr->state != '1') { script = evPtr->rising; edge = GPIO_INT_RISING; } else { script = evPtr->falling; edge = GPIO_INT_FALLING; } if (script != NULL) { cbPtr = (Tcl_GpioEvent *) ckalloc(sizeof(Tcl_GpioEvent)); cbPtr->event.proc = GpioCallBackHandler; cbPtr->interp = evPtr->interp; cbPtr->script = script; cbPtr->num = num; cbPtr->edge = edge; Tcl_IncrRefCount(cbPtr->script); Tcl_QueueEvent((Tcl_Event *)cbPtr, TCL_QUEUE_TAIL); } if (line[0] == evPtr->state) { /* Must have been a short pulse */ if (line[0] == '1') { script = evPtr->rising; edge = GPIO_INT_RISING; } else { script = evPtr->falling; edge = GPIO_INT_FALLING; } if (script != NULL) { cbPtr = (Tcl_GpioEvent *) ckalloc(sizeof(Tcl_GpioEvent)); cbPtr->event.proc = GpioCallBackHandler; cbPtr->interp = evPtr->interp; cbPtr->script = script; cbPtr->num = num; cbPtr->edge = edge; Tcl_IncrRefCount(cbPtr->script); Tcl_QueueEvent((Tcl_Event *)cbPtr, TCL_QUEUE_TAIL); } } evPtr->state = line[0]; } } static void GpioEventCleanup(ClientData dummy, Tcl_Interp *interp) { int i; for (i = 0; i <= 53; i++) if (gpio_events[i] != NULL && gpio_events[i]->interp == interp) GpioRelease(i); } static void GpioExitHandler(ClientData dummy) { int i; for (i = 0; i <= 53; i++) if (gpio_events[i] != NULL) GpioRelease(i); } int GpioEventCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int edge, num, fd, owner; GpioEvent *evPtr; char line[32]; Tcl_Obj *command = NULL; static const char *edgeopt[] = { "falling", "rising", NULL }; enum { GPIO_OPT_FALLING, GPIO_OPT_RISING }; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "gpiopin edge ?command?"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK) { return TCL_ERROR; } if (num < 0 || num > 53) { Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[2], edgeopt, "edge", 0, &edge) != TCL_OK) { return TCL_ERROR; } GPIO_INIT; Tcl_MutexLock(&setupMutex); evPtr = gpio_events[num]; owner = (evPtr != NULL && evPtr->interp == interp); if (evPtr == NULL && objc > 3) { /* Claim the pin */ evPtr = (GpioEvent *) ckalloc(sizeof(GpioEvent)); evPtr->interp = interp; evPtr->fd = -1; evPtr->rising = NULL; evPtr->falling = NULL; gpio_events[num] = evPtr; owner = 1; } Tcl_MutexUnlock(&setupMutex); if (objc < 4) { if (!owner) return TCL_OK; /* This interp owns the pin, so no other interp should be */ /* touching it and we don't need mutex protection */ if (edge == GPIO_OPT_RISING) { if (evPtr->rising != NULL) { Tcl_SetObjResult(interp, evPtr->rising); } } else { if (evPtr->falling != NULL) { Tcl_SetObjResult(interp, evPtr->falling); } } return TCL_OK; } if (!owner) { Tcl_AppendResult(interp, "gpio pin already in use by another ", "interpreter", NULL); return TCL_ERROR; } if (GpioExport(num) != TCL_OK) { GpioRelease(num); Tcl_AppendResult(interp, "couldn't export gpio pin: ", Tcl_PosixError(interp), NULL); return TCL_ERROR; } if (Tcl_GetString(objv[3])[0] != '\0') { command = objv[3]; Tcl_IncrRefCount(command); } if (edge == GPIO_OPT_RISING) { if (evPtr->rising != NULL) Tcl_DecrRefCount(evPtr->rising); evPtr->rising = command; } else { if (evPtr->falling != NULL) Tcl_DecrRefCount(evPtr->falling); evPtr->falling = command; } edge = GPIO_INT_NONE; if (evPtr->falling != NULL) edge |= GPIO_INT_FALLING; if (evPtr->rising != NULL) edge |= GPIO_INT_RISING; if (GpioEdge(num, edgestr[edge]) != TCL_OK) { GpioRelease(num); Tcl_AppendResult(interp, "couldn't change trigger mode on gpio pin: ", Tcl_PosixError(interp), NULL); return TCL_ERROR; } if (edge == GPIO_INT_NONE) { GpioRelease(num); return TCL_OK; } if (evPtr->fd < 0) { sprintf(line, "/sys/class/gpio/gpio%d/value", num); fd = open(line, O_RDONLY | O_NONBLOCK); if (fd < 0) { GpioRelease(num); return TCL_ERROR; } read(fd, line, sizeof line); evPtr->state = line[0]; evPtr->fd = fd; Tcl_CreateFileHandler(evPtr->fd, TCL_EXCEPTION, GpioEventHandler, (void *)(intptr_t)num); Tcl_DontCallWhenDeleted(interp, GpioEventCleanup, NULL); Tcl_CallWhenDeleted(interp, GpioEventCleanup, NULL); } return TCL_OK; }