| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796 | 
							- /* GDS Example
 
-    This example code is in the Public Domain (or CC0 licensed, at your option.)
 
-    Unless required by applicable law or agreed to in writing, this
 
-    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 
-    CONDITIONS OF ANY KIND, either express or implied.
 
- */
 
- #include <string.h>
 
- #include <stdlib.h>
 
- #include "freertos/FreeRTOS.h"
 
- #include "freertos/task.h"
 
- #include "freertos/timers.h"
 
- #include "freertos/queue.h"
 
- #include "esp_task.h"
 
- #include "esp_log.h"
 
- #include "driver/gpio.h"
 
- #include "driver/i2c.h"
 
- #include "driver/spi_master.h"
 
- #include "gpio_exp.h"
 
- #define GPIO_EXP_INTR	0x100
 
- #define	GPIO_EXP_WRITE	0x200
 
- /* 
 
-  shadow register is both output and input, so we assume that reading to the
 
-  ports also reads the value set on output
 
- */
 
-  
 
- typedef struct gpio_exp_s {
 
- 	uint32_t first, last;
 
- 	int intr;
 
- 	bool intr_pending;
 
- 	struct  {
 
- 		struct gpio_exp_phy_s phy;
 
- 		spi_device_handle_t spi_handle;
 
- 	};
 
- 	uint32_t shadow, pending;
 
- 	TickType_t age;
 
- 	SemaphoreHandle_t mutex;
 
- 	uint32_t r_mask, w_mask;
 
- 	uint32_t pullup, pulldown;
 
- 	struct gpio_exp_isr_s {
 
- 		gpio_isr_t handler;
 
- 		void *arg;
 
- 		TimerHandle_t timer;
 
- 	} isr[32];
 
- 	struct gpio_exp_model_s const *model;
 
- } gpio_exp_t;
 
- typedef struct {
 
- 	enum { ASYNC_WRITE } type;
 
- 	int gpio;
 
- 	int level;
 
- 	gpio_exp_t *expander;
 
- } queue_request_t;
 
- static const char TAG[] = "gpio expander";
 
- static void   IRAM_ATTR intr_isr_handler(void* arg);
 
- static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio);
 
- static esp_err_t mpr121_init(gpio_exp_t* self);
 
- static uint32_t  mpr121_read(gpio_exp_t* self);
 
- static void      mpr121_write(gpio_exp_t* self);
 
- static void 	pca9535_set_direction(gpio_exp_t* self);
 
- static uint32_t pca9535_read(gpio_exp_t* self);
 
- static void 	pca9535_write(gpio_exp_t* self);
 
- static uint32_t	pca85xx_read(gpio_exp_t* self);
 
- static void 	pca85xx_write(gpio_exp_t* self);
 
- static esp_err_t mcp23017_init(gpio_exp_t* self);
 
- static void      mcp23017_set_pull_mode(gpio_exp_t* self);
 
- static void      mcp23017_set_direction(gpio_exp_t* self);
 
- static uint32_t  mcp23017_read(gpio_exp_t* self);
 
- static void      mcp23017_write(gpio_exp_t* self);
 
- static esp_err_t mcp23s17_init(gpio_exp_t* self);
 
- static void      mcp23s17_set_pull_mode(gpio_exp_t* self);
 
- static void      mcp23s17_set_direction(gpio_exp_t* self);
 
- static uint32_t  mcp23s17_read(gpio_exp_t* self);
 
- static void      mcp23s17_write(gpio_exp_t* self);
 
- static void   service_handler(void *arg);
 
- static void   debounce_handler( TimerHandle_t xTimer );
 
- static esp_err_t i2c_write(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len);
 
- static uint32_t  i2c_read(uint8_t port, uint8_t addr, uint8_t reg, int len);
 
- static spi_device_handle_t spi_config(struct gpio_exp_phy_s *phy);
 
- static esp_err_t           spi_write(spi_device_handle_t handle, uint8_t addr, uint8_t reg, uint32_t data, int len);
 
- static uint32_t            spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg, int len);
 
- static const struct gpio_exp_model_s {
 
- 	char *model;
 
- 	gpio_int_type_t trigger;
 
- 	esp_err_t (*init)(gpio_exp_t* self);
 
- 	uint32_t  (*read)(gpio_exp_t* self);
 
- 	void      (*write)(gpio_exp_t* self);
 
- 	void      (*set_direction)(gpio_exp_t* self);
 
- 	void      (*set_pull_mode)(gpio_exp_t* self);
 
- } registered[] = {
 
- 	{ .model = "mpr121",
 
- 	  .trigger = GPIO_INTR_LOW_LEVEL,
 
- 	  .init = mpr121_init,
 
- 	  .read = mpr121_read,
 
- 	  .write = mpr121_write, },	
 
- 	{ .model = "pca9535",
 
- 	  .trigger = GPIO_INTR_LOW_LEVEL,
 
- 	  .set_direction = pca9535_set_direction,
 
- 	  .read = pca9535_read,
 
- 	  .write = pca9535_write, },
 
- 	{ .model = "pca85xx",
 
- 	  .trigger = GPIO_INTR_LOW_LEVEL,
 
- 	  .read = pca85xx_read,
 
- 	  .write = pca85xx_write, },
 
- 	{ .model = "mcp23017",
 
- 	  .trigger = GPIO_INTR_LOW_LEVEL,
 
- 	  .init = mcp23017_init,
 
- 	  .set_direction = mcp23017_set_direction,
 
- 	  .set_pull_mode = mcp23017_set_pull_mode,
 
- 	  .read = mcp23017_read,
 
- 	  .write = mcp23017_write, },
 
- 	{ .model = "mcp23s17",
 
- 	  .trigger = GPIO_INTR_LOW_LEVEL,
 
- 	  .init = mcp23s17_init,
 
- 	  .set_direction = mcp23s17_set_direction,
 
- 	  .set_pull_mode = mcp23s17_set_pull_mode,
 
- 	  .read = mcp23s17_read,
 
- 	  .write = mcp23s17_write, },
 
- };
 
- static EXT_RAM_ATTR uint8_t n_expanders;
 
- static EXT_RAM_ATTR QueueHandle_t message_queue;
 
- static EXT_RAM_ATTR gpio_exp_t expanders[4];
 
- static EXT_RAM_ATTR TaskHandle_t service_task;
 
- /******************************************************************************
 
-  * Retrieve base from an expander reference
 
-  */
 
- uint32_t gpio_exp_get_base(gpio_exp_t *expander) { 
 
- 	return expander->first; 
 
- }
 
- /******************************************************************************
 
-  * Retrieve reference from a GPIO
 
-  */
 
- gpio_exp_t *gpio_exp_get_expander(int gpio) { 
 
- 	int _gpio = gpio;
 
- 	return find_expander(NULL, &_gpio);
 
- }
 
- /******************************************************************************
 
-  * Create an I2C expander
 
-  */
 
- gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 
- 	gpio_exp_t *expander = expanders + n_expanders;
 
- 	
 
- 	if (config->base < GPIO_NUM_MAX || n_expanders == sizeof(expanders)/sizeof(gpio_exp_t)) {
 
- 		ESP_LOGE(TAG, "Base %d GPIO must be at least %d for %s or too many expanders %d", config->base, GPIO_NUM_MAX, config->model, n_expanders);
 
- 		return NULL;
 
- 	}
 
- 	// See if we know that model (expanders is zero-initialized)
 
- 	for (int i = 0; !expander->model && i < sizeof(registered)/sizeof(struct gpio_exp_model_s); i++) {
 
- 		if (strcasestr(config->model, registered[i].model)) expander->model = registered + i;
 
-     }
 
- 	// well... try again
 
- 	if (!expander->model) {
 
- 		ESP_LOGE(TAG, "Unknown GPIO expansion chip %s", config->model);
 
- 		return NULL;
 
- 	}
 
- 		
 
- 	memcpy(&expander->phy, &config->phy, sizeof(struct gpio_exp_phy_s));
 
- 	// try to initialize the expander if required
 
- 	if (expander->model->init && expander->model->init(expander) != ESP_OK) {
 
- 		ESP_LOGE(TAG, "Cannot create GPIO expander %s, check i2c/spi configuration", config->model);
 
- 		return NULL;
 
- 	}
 
- 	n_expanders++;
 
- 	expander->first = config->base;
 
- 	expander->last = config->base + config->count - 1;
 
- 	expander->intr = config->intr;
 
- 	expander->mutex = xSemaphoreCreateMutex();
 
- 	// create a task to handle asynchronous requests (only write at this time)
 
- 	if (!message_queue) {
 
- 		// we allocate TCB but stack is static to avoid SPIRAM fragmentation
 
- 		StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
 
- 		static EXT_RAM_ATTR StackType_t xStack[4*1024] __attribute__ ((aligned (4)));
 
- 		message_queue = xQueueCreate(4, sizeof(queue_request_t));
 
- 		service_task = xTaskCreateStatic(service_handler, "gpio_expander", sizeof(xStack), NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer);
 
- 	}
 
- 	// set interrupt if possible
 
- 	if (config->intr >= 0) {
 
- 		gpio_pad_select_gpio(config->intr);
 
- 		gpio_set_direction(config->intr, GPIO_MODE_INPUT);
 
- 		switch (expander->model->trigger) {
 
- 		case GPIO_INTR_NEGEDGE:
 
- 		case GPIO_INTR_LOW_LEVEL:
 
- 			gpio_set_pull_mode(config->intr, GPIO_PULLUP_ONLY);
 
- 			break;
 
- 		case GPIO_INTR_POSEDGE:
 
- 		case GPIO_INTR_HIGH_LEVEL:
 
- 			gpio_set_pull_mode(config->intr, GPIO_PULLDOWN_ONLY);
 
- 			break;
 
- 		default:	
 
- 			gpio_set_pull_mode(config->intr, GPIO_PULLUP_PULLDOWN);
 
- 			break;
 
- 		}	
 
- 		
 
- 		gpio_set_intr_type(config->intr, expander->model->trigger);		
 
- 		gpio_isr_handler_add(config->intr, intr_isr_handler, expander);
 
- 		gpio_intr_enable(config->intr);						
 
- 	}
 
- 	
 
- 	ESP_LOGI(TAG, "Create GPIO expander %s at base %u with intr %d at @%x on port/host %d/%d", config->model, config->base, config->intr, config->phy.addr, config->phy.port, config->phy.host);
 
- 	return expander;
 
- }
 
- /******************************************************************************
 
-  * Add ISR handler for a GPIO
 
-  */
 
- esp_err_t gpio_exp_isr_handler_add(int gpio, gpio_isr_t isr_handler, uint32_t debounce, void *arg, struct gpio_exp_s *expander) {
 
- 	if (gpio < GPIO_NUM_MAX && !expander) return gpio_isr_handler_add(gpio, isr_handler, arg);
 
- 	if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
 
- 	expander->isr[gpio].handler = isr_handler;
 
- 	expander->isr[gpio].arg = arg;
 
- 	if (debounce) expander->isr[gpio].timer = xTimerCreate("gpioExpDebounce", pdMS_TO_TICKS(debounce), 
 
- 	                                                       pdFALSE, expander->isr + gpio, debounce_handler );
 
- 	return ESP_OK;
 
- }
 
- /******************************************************************************
 
-  * Remove ISR handler for a GPIO
 
-  */
 
- esp_err_t gpio_exp_isr_handler_remove(int gpio, struct gpio_exp_s *expander) {
 
- 	if (gpio < GPIO_NUM_MAX && !expander) return gpio_isr_handler_remove(gpio);
 
- 	if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
 
- 	if (expander->isr[gpio].timer) xTimerDelete(expander->isr[gpio].timer, portMAX_DELAY);
 
- 	memset(expander->isr + gpio, 0, sizeof(struct gpio_exp_isr_s));
 
- 	return ESP_OK;
 
- }
 
- /******************************************************************************
 
-  * Set GPIO direction
 
-  */
 
- esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, gpio_exp_t *expander) {
 
- 	if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_direction(gpio, mode);
 
- 	if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
 
- 	xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
 
- 	if (mode == GPIO_MODE_INPUT) {
 
- 		expander->r_mask |= 1 << gpio;
 
- 		expander->shadow = expander->model->read(expander);
 
- 		expander->age = ~xTaskGetTickCount();
 
- 	} else {
 
- 		expander->w_mask |= 1 << gpio;
 
- 	}
 
- 	
 
- 	if (expander->r_mask & expander->w_mask) {
 
- 		xSemaphoreGive(expander->mutex);
 
- 		ESP_LOGE(TAG, "GPIO %d on expander base %u can't be r/w", gpio, expander->first);
 
- 		return ESP_ERR_INVALID_ARG;
 
- 	}
 
- 	// most expanders want unconfigured GPIO to be set to output
 
- 	if (expander->model->set_direction) expander->model->set_direction(expander);
 
- 	xSemaphoreGive(expander->mutex);
 
- 	return ESP_OK;
 
- }	
 
- /******************************************************************************
 
-  * Get GPIO level with cache
 
-  */
 
- int gpio_exp_get_level(int gpio, int age, gpio_exp_t *expander) {
 
- 	if (gpio < GPIO_NUM_MAX && !expander) return gpio_get_level(gpio);
 
- 	if ((expander = find_expander(expander, &gpio)) == NULL) return -1;
 
- 	uint32_t now = xTaskGetTickCount();
 
- 	// return last thing we had if we can't get the mutex
 
- 	if (xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)) == pdFALSE) {
 
- 		ESP_LOGW(TAG, "Can't get mutex for GPIO %d", expander->first + gpio);
 
- 		return (expander->shadow >> gpio) & 0x01;
 
- 	}
 
- 	// re-read the expander if data is too old
 
- 	if (age >= 0 && now - expander->age >= pdMS_TO_TICKS(age)) {
 
- 		uint32_t value = expander->model->read(expander);
 
- 		expander->pending |= (expander->shadow ^ value) & expander->r_mask;
 
- 		expander->shadow = value;
 
- 		expander->age = now;
 
- 	}
 
- 	// clear pending bit
 
- 	expander->pending &= ~(1 << gpio);
 
- 	xSemaphoreGive(expander->mutex);
 
- 	
 
- 	ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow);
 
- 	return (expander->shadow >> gpio) & 0x01;
 
- }
 
- /******************************************************************************
 
-  * Set GPIO level with cache
 
-  */
 
- esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, gpio_exp_t *expander) {
 
- 	if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_level(gpio, level);
 
- 	if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
 
- 	uint32_t mask = 1 << gpio;
 
- 	// very limited risk with lack of semaphore here
 
- 	if ((expander->w_mask & mask) == 0) {
 
- 		ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio);
 
- 		return ESP_ERR_INVALID_ARG;
 
- 	}
 
- 	if (direct) {
 
- 		xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
 
- 		level = level ? mask : 0;
 
- 		mask &= expander->shadow;
 
- 		// only write if shadow not up to date
 
- 		if ((mask ^ level) && expander->model->write) {
 
- 			expander->shadow = (expander->shadow & ~(mask | level)) | level;
 
- 			expander->model->write(expander);
 
- 		}
 
- 		xSemaphoreGive(expander->mutex);
 
- 		ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow);
 
- 	} else {
 
- 		queue_request_t request = { .gpio = gpio, .level = level, .type = ASYNC_WRITE, .expander = expander };
 
- 		if (xQueueSend(message_queue, &request, 0) == pdFALSE) return ESP_ERR_INVALID_RESPONSE;
 
- 		// notify service task that will write it when it can
 
- 		xTaskNotify(service_task, GPIO_EXP_WRITE, eSetValueWithoutOverwrite);
 
- 	} 
 
- 	return ESP_OK;
 
- }
 
- /******************************************************************************
 
-  * Set GPIO pullmode
 
-  */
 
- esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, gpio_exp_t *expander) {
 
- 	if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_pull_mode(gpio, mode);
 
- 	if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) {
 
- 		expander->pullup &= ~(1 << gpio);
 
- 		expander->pulldown &= ~(1 << gpio);
 
- 		if (mode == GPIO_PULLUP_ONLY  || mode == GPIO_PULLUP_PULLDOWN) expander->pullup |= 1 << gpio;
 
- 		if (mode == GPIO_PULLDOWN_ONLY || mode == GPIO_PULLUP_PULLDOWN) expander->pulldown |= 1 << gpio;
 
- 		expander->model->set_pull_mode(expander);
 
- 		return ESP_OK;
 
- 	}
 
- 	return ESP_ERR_INVALID_ARG;
 
- }
 
- /******************************************************************************
 
-  * Wrapper function
 
-  */
 
- esp_err_t gpio_set_pull_mode_x(int gpio, gpio_pull_mode_t mode) {
 
- 	if (gpio < GPIO_NUM_MAX) return gpio_set_pull_mode(gpio, mode);
 
- 	return gpio_exp_set_pull_mode(gpio, mode, NULL);
 
- }
 
- esp_err_t gpio_set_direction_x(int gpio, gpio_mode_t mode) {
 
- 	if (gpio < GPIO_NUM_MAX) return gpio_set_direction(gpio, mode);
 
- 	return gpio_exp_set_direction(gpio, mode, NULL);
 
- }
 
- int gpio_get_level_x(int gpio) {
 
- 	if (gpio < GPIO_NUM_MAX) return gpio_get_level(gpio);
 
- 	return gpio_exp_get_level(gpio, 10, NULL);
 
- }
 
- esp_err_t gpio_set_level_x(int gpio, int level) {
 
- 	if (gpio < GPIO_NUM_MAX) return gpio_set_level(gpio, level);
 
- 	return gpio_exp_set_level(gpio, level, false, NULL);
 
- }
 
- esp_err_t gpio_isr_handler_add_x(int gpio, gpio_isr_t isr_handler, void* args) {
 
- 	if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_add(gpio, isr_handler, args);
 
- 	return gpio_exp_isr_handler_add(gpio, isr_handler, 0, args, NULL);
 
- }
 
- esp_err_t gpio_isr_handler_remove_x(int gpio) {
 
- 	if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_remove(gpio);
 
- 	return gpio_exp_isr_handler_remove(gpio, NULL);
 
- }
 
- /****************************************************************************************
 
-  * INTR low-level handler
 
-  */
 
- static void IRAM_ATTR intr_isr_handler(void* arg) {
 
- 	gpio_exp_t *self = (gpio_exp_t*) arg;
 
- 	BaseType_t woken = pdFALSE;
 
- 	
 
- 	// edge interrupts do not work because of read/clear = potential short pulse
 
- 	gpio_intr_disable(self->intr);	
 
- 	
 
- 	// activate all, including ourselves
 
- 	for (int i = 0; i < n_expanders; i++) if (expanders[i].intr == self->intr) expanders[i].intr_pending = true; 
 
- 	
 
- 	xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken);
 
- 	if (woken) portYIELD_FROM_ISR();
 
- 	ESP_EARLY_LOGD(TAG, "INTR for expander base %d", gpio_exp_get_base(self));
 
- }
 
- /****************************************************************************************
 
-  * INTR debounce handler
 
-  */
 
- static void debounce_handler( TimerHandle_t xTimer ) {
 
- 	struct gpio_exp_isr_s *isr = (struct gpio_exp_isr_s*) pvTimerGetTimerID (xTimer);
 
- 	isr->handler(isr->arg);
 
- }
 
- /****************************************************************************************
 
-  * Service task
 
-  */
 
- void service_handler(void *arg) {
 
- 	while (1) {
 
- 		queue_request_t request;
 
- 		uint32_t notif = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
 
- 		// we have been notified of an interrupt
 
- 		if (notif == GPIO_EXP_INTR) {
 
- 			/* If we want a smarter bitmap of expanders with a pending interrupt
 
- 			   we'll have to disable interrupts while clearing that bitmap. For 
 
- 			   now, a loop will do */
 
- 			for (int i = 0; i < n_expanders; i++) {
 
- 				gpio_exp_t *expander = expanders + i;
 
- 				// no interrupt for that gpio or not pending (safe as interrupt is disabled)
 
- 				if (expander->intr < 0 || !expander->intr_pending) continue;
 
- 				xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50));
 
- 				// read GPIOs and clear all pending status
 
- 				uint32_t value = expander->model->read(expander);
 
- 				expander->age = xTaskGetTickCount();
 
- 				
 
- 				// re-enable interrupt now that it has been cleared
 
- 				expander->intr_pending = false;
 
- 				gpio_intr_enable(expander->intr);				
 
- 				
 
- 				uint32_t pending = expander->pending | ((expander->shadow ^ value) & expander->r_mask);
 
- 				expander->shadow = value;
 
- 				expander->pending = 0;
 
- 				xSemaphoreGive(expander->mutex);
 
- 				ESP_LOGD(TAG, "Handling GPIO %d reads 0x%04x and has 0x%04x pending", expander->first, expander->shadow, pending);
 
- 				
 
- 				for (int gpio = 31, clz = 0; pending && clz < 31; pending <<= (clz + 1)) {
 
- 					clz = __builtin_clz(pending);
 
- 					gpio -= clz;
 
- 					if (expander->isr[gpio].timer) xTimerReset(expander->isr[gpio].timer, 1);	// todo 0
 
- 					else if (expander->isr[gpio].handler) expander->isr[gpio].handler(expander->isr[gpio].arg);
 
- 				}	
 
- 			}
 
- 		}
 
- 		// check if we have some other pending requests
 
- 		while (xQueueReceive(message_queue, &request, 0) == pdTRUE) {
 
- 			esp_err_t err = gpio_exp_set_level(request.gpio, request.level, true, request.expander);
 
- 			if (err != ESP_OK) ESP_LOGW(TAG, "Can't execute async GPIO %d write request (%d)", request.gpio, err);  
 
- 		}
 
- 	}
 
- }
 
- /****************************************************************************************
 
-  * Find the expander related to base
 
-  */
 
- static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) {
 
- 	// a mutex would be better, but risk is so small...
 
- 	for (int i = 0; !expander && i < n_expanders; i++) {
 
- 		if (*gpio >= expanders[i].first && *gpio <= expanders[i].last) expander = expanders + i;
 
- 	}
 
- 	
 
- 	// normalize GPIO number
 
- 	if (expander && *gpio >= expander->first) *gpio -= expander->first;
 
- 	
 
- 	return expander;
 
- }
 
- /****************************************************************************************
 
-                                         DRIVERS                                       
 
- ****************************************************************************************/
 
- /****************************************************************************************
 
-  * MPR121 family : init, direction, read and write
 
-  */
 
- static esp_err_t mpr121_init(gpio_exp_t* self) {
 
- 	static const struct {
 
- 		uint8_t addr;
 
- 		uint8_t data;
 
- 	} mpr121_init_table[] = { 
 
- 		{ 0x80, 0x63 }, /* Soft reset */
 
- 		{ 0x2b, 0x01 }, { 0x2c, 0x01 }, { 0x2d, 0x10 }, { 0x2e, 0x20 },                /* MHDR, NHDR, NCLR, FDLR */
 
- 		{ 0x2f, 0x01 }, { 0x30, 0x01 }, { 0x31, 0x10 }, { 0x32, 0x20 },                /* MHDF, NHDF, NCLF, FDLF */
 
- 		{ 0x33, 0x01 }, { 0x34, 0x10 }, { 0x35, 0xff },                                /* NHDT, NCLT, FDLT */
 
- 		{ 0x5b, 0x11 }, { 0x5c, 0xff }, { 0x5d, 0x30 },                                /* DTR, AFE1, AFE2 */
 
- 		{ 0x7b, 0x00 }, { 0x7c, 0x00 }, { 0x7d, 0x00 }, { 0x7e, 0x00 }, { 0x7f, 0x00 },/* ACCR0, ACCR1, USL, LSL, TL */
 
- 		{ 0x41, 0x28 }, { 0x42, 0x14 }, /* ELE0: Touch Threshold, Release Threshold */
 
- 		{ 0x43, 0x28 }, { 0x44, 0x14 }, /* ELE1: Touch Threshold, Release Threshold */
 
- 		{ 0x45, 0x28 }, { 0x46, 0x14 }, /* ELE2: Touch Threshold, Release Threshold */
 
- 		{ 0x47, 0x28 }, { 0x48, 0x14 }, /* ELE3: Touch Threshold, Release Threshold */
 
- 		{ 0x49, 0x28 }, { 0x4a, 0x14 }, /* ELE4: Touch Threshold, Release Threshold */
 
- 		{ 0x4b, 0x28 }, { 0x4c, 0x14 }, /* ELE5: Touch Threshold, Release Threshold */
 
- 		{ 0x4d, 0x28 }, { 0x4e, 0x14 }, /* ELE6: Touch Threshold, Release Threshold */
 
- 		{ 0x4f, 0x28 }, { 0x50, 0x14 }, /* ELE7: Touch Threshold, Release Threshold */
 
- 		{ 0x51, 0x28 }, { 0x52, 0x14 }, /* ELE8: Touch Threshold, Release Threshold */
 
- 		{ 0x53, 0x28 }, { 0x54, 0x14 }, /* ELE9: Touch Threshold, Release Threshold */
 
- 		{ 0x55, 0x28 }, { 0x56, 0x14 }, /* ELE10: Touch Threshold, Release Threshold */
 
- 		{ 0x57, 0x28 }, { 0x58, 0x14 }, /* ELE11: Touch Threshold, Release Threshold */
 
- 		{ 0x5e, 0xcc }, /* ECR - must be set last */
 
- 		{ 0, 0 } 
 
- 	};
 
- 	esp_err_t err = 0;
 
- 	for (int i = 0; mpr121_init_table[i].addr; i++) {
 
- 		err |= i2c_write(self->phy.port, self->phy.addr, mpr121_init_table[i].addr, mpr121_init_table[i].data, 1);
 
- 	}
 
- 	return err;
 
- }
 
- static uint32_t mpr121_read(gpio_exp_t* self) {
 
- 	// only return the lower 12 bits of the pin status registers
 
- 	return i2c_read(self->phy.port, self->phy.addr, 0x00, 2) & 0x0fff;
 
- }
 
- static void mpr121_write(gpio_exp_t* self) {
 
- 	ESP_LOGE(TAG, "MPR121 GPIO write not implemented");
 
- }
 
- /****************************************************************************************
 
-  * PCA9535 family : direction, read and write
 
-  */
 
- static void pca9535_set_direction(gpio_exp_t* self) {
 
- 	i2c_write(self->phy.port, self->phy.addr, 0x06, self->r_mask, 2);
 
- }
 
- static uint32_t pca9535_read(gpio_exp_t* self) {
 
- 	return i2c_read(self->phy.port, self->phy.addr, 0x00, 2);
 
- }
 
- static void pca9535_write(gpio_exp_t* self) {
 
- 	i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
 
- }
 
- /****************************************************************************************
 
-  * PCA85xx family : read and write
 
-  */
 
- static uint32_t pca85xx_read(gpio_exp_t* self) {
 
- 	// must return the full set of pins, not just inputs
 
- 	uint32_t data = i2c_read(self->phy.port, self->phy.addr, 0xff, 2);
 
- 	return (data & self->r_mask) | (self->shadow & ~self->r_mask);
 
- }
 
- static void pca85xx_write(gpio_exp_t* self) {
 
- 	/* 
 
- 	 There is no good option with this chip: normally, unused pin should be set to input
 
- 	 to avoid any conflict but then they float and create tons of suprious. So option 1 is
 
- 	 to le tthem float and option 2 is to set them as output to 0.
 
- 	 In addition, setting an output pin to 1 equals is making it an input and if this is
 
- 	 use to short a led (e.g.) instead of being the sink, the it generates a spurious
 
- 	*/
 
- 	// option 1 
 
- 	// i2c_write(self->phy.port, self->phy.addr, 0xff, (self->shadow & self->w_mask) | ~self->w_mask, 2);
 
- 	// option 2
 
- 	i2c_write(self->phy.port, self->phy.addr, 0xff, (self->shadow & self->w_mask) | self->r_mask, 2);
 
- }
 
- /****************************************************************************************
 
-  * MCP23017 family : init, direction, read and write
 
-  */
 
- static esp_err_t mcp23017_init(gpio_exp_t* self) {
 
- 	/*
 
- 	0111 x10x = same bank, mirrot single int, no sequentµial, open drain, active low
 
- 	not sure about this funny change of mapping of the control register itself, really?
 
- 	*/
 
- 	esp_err_t err = i2c_write(self->phy.port, self->phy.addr, 0x05, 0x74, 1);
 
- 	err |= i2c_write(self->phy.port, self->phy.addr, 0x0a, 0x74, 1);
 
- 	// no interrupt on comparison or on change
 
- 	err |= i2c_write(self->phy.port, self->phy.addr, 0x04, 0x00, 2);
 
- 	err |= i2c_write(self->phy.port, self->phy.addr, 0x08, 0x00, 2);
 
- 	return err;
 
- }
 
- static void mcp23017_set_direction(gpio_exp_t* self) {
 
- 	// default to input and set real input to generate interrupt
 
- 	i2c_write(self->phy.port, self->phy.addr, 0x00, ~self->w_mask, 2);
 
- 	i2c_write(self->phy.port, self->phy.addr, 0x04, self->r_mask, 2);
 
- }
 
- static void mcp23017_set_pull_mode(gpio_exp_t* self) {
 
- 	i2c_write(self->phy.port, self->phy.addr, 0x0c, self->pullup, 2);
 
- }
 
- static uint32_t mcp23017_read(gpio_exp_t* self) {
 
- 	// read the pins value, not the stored one @interrupt
 
- 	return i2c_read(self->phy.port, self->phy.addr, 0x12, 2);
 
- }
 
- static void mcp23017_write(gpio_exp_t* self) {
 
- 	i2c_write(self->phy.port, self->phy.addr, 0x12, self->shadow, 2);
 
- }
 
- /****************************************************************************************
 
-  * MCP23s17 family : init, direction, read and write
 
-  */
 
- static esp_err_t mcp23s17_init(gpio_exp_t* self) {
 
- 	if ((self->spi_handle = spi_config(&self->phy)) == NULL) return ESP_ERR_INVALID_ARG;
 
- 	
 
- 	/*
 
- 	0111 x10x = same bank, mirrot single int, no sequentµial, open drain, active low
 
- 	not sure about this funny change of mapping of the control register itself, really?
 
- 	*/
 
- 	esp_err_t err = spi_write(self->spi_handle, self->phy.addr, 0x05, 0x74, 1);
 
- 	err |= spi_write(self->spi_handle, self->phy.addr, 0x0a, 0x74, 1);
 
- 	// no interrupt on comparison or on change
 
- 	err |= spi_write(self->spi_handle, self->phy.addr, 0x04, 0x00, 2);
 
- 	err |= spi_write(self->spi_handle, self->phy.addr, 0x08, 0x00, 2);
 
- 	return err;
 
- }
 
- static void mcp23s17_set_direction(gpio_exp_t* self) {
 
- 	// default to input and set real input to generate interrupt
 
- 	spi_write(self->spi_handle, self->phy.addr, 0x00, ~self->w_mask, 2);
 
- 	spi_write(self->spi_handle, self->phy.addr, 0x04, self->r_mask, 2);
 
- }
 
- static void mcp23s17_set_pull_mode(gpio_exp_t* self) {
 
- 	spi_write(self->spi_handle, self->phy.addr, 0x0c, self->pullup, 2);
 
- }
 
- static uint32_t mcp23s17_read(gpio_exp_t* self) {
 
- 	// read the pins value, not the stored one @interrupt
 
- 	return spi_read(self->spi_handle, self->phy.addr, 0x12, 2);
 
- }
 
- static void mcp23s17_write(gpio_exp_t* self) {
 
- 	spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2);
 
- }
 
- /***************************************************************************************
 
-                                      I2C low level                                   
 
- ***************************************************************************************/
 
- /****************************************************************************************
 
-  * I2C write up to 32 bits
 
-  */
 
- static esp_err_t i2c_write(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len) {
 
- 	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
 
-     i2c_master_start(cmd);
 
- 	
 
- 	i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
 
- 	if (reg != 0xff) i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
 
- 	// works with our endianness
 
- 	if (len > 1) i2c_master_write(cmd, (uint8_t*) &data, len, I2C_MASTER_NACK);
 
- 	else i2c_master_write_byte(cmd, data, I2C_MASTER_NACK);
 
-     
 
- 	i2c_master_stop(cmd);
 
- 	esp_err_t ret = i2c_master_cmd_begin(port, cmd, 100 / portTICK_RATE_MS);
 
-     i2c_cmd_link_delete(cmd);
 
- 	
 
- 	if (ret != ESP_OK) {		
 
- 		ESP_LOGW(TAG, "I2C write failed");
 
- 	}
 
- 	
 
-     return ret;
 
- }
 
- /****************************************************************************************
 
-  * I2C read up to 32 bits
 
-  */
 
- static uint32_t i2c_read(uint8_t port, uint8_t addr, uint8_t reg, int len) {
 
- 	uint32_t data = 0;
 
- 	
 
- 	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
 
-     i2c_master_start(cmd);
 
- 	// when using a register, write it's value then the device address again
 
- 	if (reg != 0xff) {
 
- 		i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
 
- 		i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
 
- 		i2c_master_start(cmd);
 
- 		i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
 
- 	} else {
 
- 		i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
 
- 	}
 
- 	
 
- 	// works with our endianness
 
- 	if (len > 1) i2c_master_read(cmd, (uint8_t*) &data, len, I2C_MASTER_LAST_NACK);
 
- 	else i2c_master_read_byte(cmd, (uint8_t*) &data, I2C_MASTER_NACK);
 
- 		
 
-     i2c_master_stop(cmd);
 
-     esp_err_t ret = i2c_master_cmd_begin(port, cmd, 100 / portTICK_RATE_MS);
 
-     i2c_cmd_link_delete(cmd);
 
- 	
 
- 	if (ret != ESP_OK) {
 
- 		ESP_LOGW(TAG, "I2C read failed");
 
- 	}
 
- 	return data;
 
- }
 
- /***************************************************************************************
 
-                                      SPI low level                                   
 
- ***************************************************************************************/
 
- /****************************************************************************************
 
-  * SPI device addition
 
-  */
 
- static spi_device_handle_t spi_config(struct gpio_exp_phy_s *phy) {
 
-     spi_device_interface_config_t config = { };
 
-     spi_device_handle_t handle = NULL;
 
- 	config.command_bits = config.address_bits = 8;
 
-     config.clock_speed_hz = phy->speed ? phy->speed : SPI_MASTER_FREQ_8M;
 
-     config.spics_io_num = phy->cs_pin;
 
-     config.queue_size = 1;
 
- 	config.flags = SPI_DEVICE_NO_DUMMY;
 
-     spi_bus_add_device( phy->host, &config, &handle );
 
- 	ESP_LOGI(TAG, "SPI expander initialized on host:%d with cs:%d and speed:%dHz", phy->host, phy->cs_pin, config.clock_speed_hz);
 
- 	return handle;
 
- }
 
- /****************************************************************************************
 
-  * SPI write up to 32 bits
 
-  */
 
- static esp_err_t spi_write(spi_device_handle_t handle, uint8_t addr, uint8_t reg, uint32_t data, int len) {
 
-     spi_transaction_t transaction = { };
 
- 	// rx_buffer is NULL, nothing to receive
 
- 	transaction.flags = SPI_TRANS_USE_TXDATA;
 
- 	transaction.cmd = addr << 1;
 
- 	transaction.addr = reg;
 
- 	transaction.tx_data[0] = data; transaction.tx_data[1] = data >> 8;
 
- 	transaction.length = len * 8;
 
- 	// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)		
 
- 	return spi_device_polling_transmit(handle, &transaction);
 
- }
 
- /****************************************************************************************
 
-  * SPI read up to 32 bits
 
-  */
 
- static uint32_t spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg, int len) {
 
- 	spi_transaction_t *transaction = heap_caps_calloc(1, sizeof(spi_transaction_t), MALLOC_CAP_DMA);
 
- 	// tx_buffer is NULL, nothing to transmit except cmd/addr
 
- 	transaction->flags = SPI_TRANS_USE_RXDATA;
 
- 	transaction->cmd = (addr << 1) | 0x01;
 
- 	transaction->addr = reg;
 
- 	transaction->length = len * 8;
 
- 	// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)		
 
- 	spi_device_polling_transmit(handle, transaction);
 
- 	uint32_t data = *(uint32_t*) transaction->rx_data;
 
- 	free(transaction);
 
- 	return data;
 
- }
 
 
  |