|  | @@ -16,6 +16,7 @@
 | 
	
		
			
				|  |  |  #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
 | 
	
	
		
			
				|  | @@ -28,7 +29,10 @@
 | 
	
		
			
				|  |  |   
 | 
	
		
			
				|  |  |  typedef struct gpio_exp_s {
 | 
	
		
			
				|  |  |  	uint32_t first, last;
 | 
	
		
			
				|  |  | -	union gpio_exp_phy_u phy;
 | 
	
		
			
				|  |  | +	struct  {
 | 
	
		
			
				|  |  | +		struct gpio_exp_phy_s phy;
 | 
	
		
			
				|  |  | +		spi_device_handle_t spi_handle;
 | 
	
		
			
				|  |  | +	};
 | 
	
		
			
				|  |  |  	uint32_t shadow, pending;
 | 
	
		
			
				|  |  |  	TickType_t age;
 | 
	
		
			
				|  |  |  	SemaphoreHandle_t mutex;
 | 
	
	
		
			
				|  | @@ -54,35 +58,44 @@ 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 void   pca9535_set_direction(gpio_exp_t* self);
 | 
	
		
			
				|  |  | -static int    pca9535_read(gpio_exp_t* self);
 | 
	
		
			
				|  |  | -static void   pca9535_write(gpio_exp_t* self);
 | 
	
		
			
				|  |  | +static void pca9535_set_direction(gpio_exp_t* self);
 | 
	
		
			
				|  |  | +static int  pca9535_read(gpio_exp_t* self);
 | 
	
		
			
				|  |  | +static void pca9535_write(gpio_exp_t* self);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static void   pca85xx_set_direction(gpio_exp_t* self);
 | 
	
		
			
				|  |  | -static int    pca85xx_read(gpio_exp_t* self);
 | 
	
		
			
				|  |  | -static void   pca85xx_write(gpio_exp_t* self);
 | 
	
		
			
				|  |  | +static void pca85xx_set_direction(gpio_exp_t* self);
 | 
	
		
			
				|  |  | +static int  pca85xx_read(gpio_exp_t* self);
 | 
	
		
			
				|  |  | +static void pca85xx_write(gpio_exp_t* self);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static void   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 int    mcp23017_read(gpio_exp_t* self);
 | 
	
		
			
				|  |  | -static void   mcp23017_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 int       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 int       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_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val);
 | 
	
		
			
				|  |  | -static uint16_t  i2c_read(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, bool word);
 | 
	
		
			
				|  |  | -static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data);
 | 
	
		
			
				|  |  | +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;
 | 
	
		
			
				|  |  | -	void (*init)(gpio_exp_t* self);
 | 
	
		
			
				|  |  | -	int  (*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);
 | 
	
		
			
				|  |  | +	esp_err_t (*init)(gpio_exp_t* self);
 | 
	
		
			
				|  |  | +	int       (*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 = "pca9535",
 | 
	
		
			
				|  |  |  	  .trigger = GPIO_INTR_NEGEDGE, 
 | 
	
	
		
			
				|  | @@ -101,6 +114,13 @@ static const struct gpio_exp_model_s {
 | 
	
		
			
				|  |  |  	  .set_pull_mode = mcp23017_set_pull_mode,
 | 
	
		
			
				|  |  |  	  .read = mcp23017_read,
 | 
	
		
			
				|  |  |  	  .write = mcp23017_write, },
 | 
	
		
			
				|  |  | +	{ .model = "mcp23s17",
 | 
	
		
			
				|  |  | +	  .trigger = GPIO_INTR_NEGEDGE, 
 | 
	
		
			
				|  |  | +	  .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;
 | 
	
	
		
			
				|  | @@ -150,7 +170,7 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 | 
	
		
			
				|  |  |  	expander->last = config->base + config->count - 1;
 | 
	
		
			
				|  |  |  	expander->mutex = xSemaphoreCreateMutex();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	memcpy(&expander->phy, &config->phy, sizeof(union gpio_exp_phy_u));
 | 
	
		
			
				|  |  | +	memcpy(&expander->phy, &config->phy, sizeof(struct gpio_exp_phy_s));
 | 
	
		
			
				|  |  |  	if (expander->model->init) expander->model->init(expander);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	// create a task to handle asynchronous requests (only write at this time)
 | 
	
	
		
			
				|  | @@ -372,6 +392,72 @@ esp_err_t gpio_isr_handler_remove_x(int gpio) {
 | 
	
		
			
				|  |  |  	return gpio_exp_isr_handler_remove(gpio, NULL);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/****************************************************************************************
 | 
	
		
			
				|  |  | + * INTR low-level handler
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +static void IRAM_ATTR intr_isr_handler(void* arg) {
 | 
	
		
			
				|  |  | +	BaseType_t woken = pdFALSE;
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	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(arg));
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/****************************************************************************************
 | 
	
		
			
				|  |  | + * 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;
 | 
	
		
			
				|  |  | +				
 | 
	
		
			
				|  |  | +				xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// read GPIOs and clear all pending status
 | 
	
		
			
				|  |  | +				uint32_t value = expander->model->read(expander);
 | 
	
		
			
				|  |  | +				uint32_t pending = expander->pending | ((expander->shadow ^ value) & expander->r_mask);
 | 
	
		
			
				|  |  | +				expander->shadow = value;
 | 
	
		
			
				|  |  | +				expander->pending = 0;
 | 
	
		
			
				|  |  | +				expander->age = xTaskGetTickCount();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				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; pending; 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
 | 
	
		
			
				|  |  | +		if (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
 | 
	
		
			
				|  |  |   */
 | 
	
	
		
			
				|  | @@ -387,19 +473,23 @@ static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) {
 | 
	
		
			
				|  |  |  	return expander;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/****************************************************************************************
 | 
	
		
			
				|  |  | +                                        DRIVERS                                       
 | 
	
		
			
				|  |  | +****************************************************************************************/
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /****************************************************************************************
 | 
	
		
			
				|  |  |   * PCA9535 family : direction, read and write
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  static void pca9535_set_direction(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | -	i2c_write_word(self->phy.port, self->phy.addr, 0x06, self->r_mask);
 | 
	
		
			
				|  |  | +	i2c_write(self->phy.port, self->phy.addr, 0x06, self->r_mask, 2);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int pca9535_read(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | -	return i2c_read(self->phy.port, self->phy.addr, 0x00, true);
 | 
	
		
			
				|  |  | +	return i2c_read(self->phy.port, self->phy.addr, 0x00, 2);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void pca9535_write(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | -	i2c_write_word(self->phy.port, self->phy.addr, 0x02, self->shadow);
 | 
	
		
			
				|  |  | +	i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /****************************************************************************************
 | 
	
	
		
			
				|  | @@ -407,134 +497,117 @@ static void pca9535_write(gpio_exp_t* self) {
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  static void pca85xx_set_direction(gpio_exp_t* self) {
 | 
	
		
			
				|  |  |  	// all inputs must be set to 1 (open drain) and output are left open as well
 | 
	
		
			
				|  |  | -	i2c_write_word(self->phy.port, self->phy.addr, 0xff, self->r_mask | self->w_mask);
 | 
	
		
			
				|  |  | +	i2c_write(self->phy.port, self->phy.addr, 0xff, self->r_mask | self->w_mask, true);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int pca85xx_read(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | -	return i2c_read(self->phy.port, self->phy.addr, 0xff, true);
 | 
	
		
			
				|  |  | +	return i2c_read(self->phy.port, self->phy.addr, 0xff, 2);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void pca85xx_write(gpio_exp_t* self) {
 | 
	
		
			
				|  |  |  	// all input must be set to 1 (open drain)
 | 
	
		
			
				|  |  | -	i2c_write_word(self->phy.port, self->phy.addr, 0xff, self->shadow | self->r_mask);
 | 
	
		
			
				|  |  | +	i2c_write(self->phy.port, self->phy.addr, 0xff, self->shadow | self->r_mask, true);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /****************************************************************************************
 | 
	
		
			
				|  |  |   * MCP23017 family : init, direction, read and write
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -static void mcp23017_init(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | +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?
 | 
	
		
			
				|  |  |  	*/
 | 
	
		
			
				|  |  | -	i2c_write_byte(self->phy.port, self->phy.addr, 0x05, 0x74);
 | 
	
		
			
				|  |  | -	i2c_write_byte(self->phy.port, self->phy.addr, 0x0a, 0x74);
 | 
	
		
			
				|  |  | +	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
 | 
	
		
			
				|  |  | -	i2c_write_word(self->phy.port, self->phy.addr, 0x04, 0x00);
 | 
	
		
			
				|  |  | -	i2c_write_word(self->phy.port, self->phy.addr, 0x08, 0x00);
 | 
	
		
			
				|  |  | +	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_word(self->phy.port, self->phy.addr, 0x00, ~self->w_mask);
 | 
	
		
			
				|  |  | -	i2c_write_word(self->phy.port, self->phy.addr, 0x04, self->r_mask);
 | 
	
		
			
				|  |  | +	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_word(self->phy.port, self->phy.addr, 0x0c, self->pullup);
 | 
	
		
			
				|  |  | +	i2c_write(self->phy.port, self->phy.addr, 0x0c, self->pullup, 2);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int mcp23017_read(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | -	// read the pin value, not the stored one @interrupt
 | 
	
		
			
				|  |  | -	return i2c_read(self->phy.port, self->phy.addr, 0x12, true);
 | 
	
		
			
				|  |  | +	// 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_word(self->phy.port, self->phy.addr, 0x12, self->shadow);
 | 
	
		
			
				|  |  | +	i2c_write(self->phy.port, self->phy.addr, 0x12, self->shadow, 2);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /****************************************************************************************
 | 
	
		
			
				|  |  | - * INTR low-level handler
 | 
	
		
			
				|  |  | + * MCP23s17 family : init, direction, read and write
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -static void IRAM_ATTR intr_isr_handler(void* arg) {
 | 
	
		
			
				|  |  | -	BaseType_t woken = pdFALSE;
 | 
	
		
			
				|  |  | +static esp_err_t mcp23s17_init(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | +	self->spi_handle = spi_config(&self->phy);
 | 
	
		
			
				|  |  |  	
 | 
	
		
			
				|  |  | -	xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken);
 | 
	
		
			
				|  |  | -	if (woken) portYIELD_FROM_ISR();
 | 
	
		
			
				|  |  | +	/*
 | 
	
		
			
				|  |  | +	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);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	ESP_EARLY_LOGD(TAG, "INTR for expander base %d", gpio_exp_get_base(arg));
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +	// 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);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/****************************************************************************************
 | 
	
		
			
				|  |  | - * 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);
 | 
	
		
			
				|  |  | +	return err;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/****************************************************************************************
 | 
	
		
			
				|  |  | - * 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;
 | 
	
		
			
				|  |  | -				
 | 
	
		
			
				|  |  | -				xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50));
 | 
	
		
			
				|  |  | +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);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				// read GPIOs and clear all pending status
 | 
	
		
			
				|  |  | -				uint32_t value = expander->model->read(expander);
 | 
	
		
			
				|  |  | -				uint32_t pending = expander->pending | ((expander->shadow ^ value) & expander->r_mask);
 | 
	
		
			
				|  |  | -				expander->shadow = value;
 | 
	
		
			
				|  |  | -				expander->pending = 0;
 | 
	
		
			
				|  |  | -				expander->age = xTaskGetTickCount();
 | 
	
		
			
				|  |  | +static void mcp23s17_set_pull_mode(gpio_exp_t* self) {
 | 
	
		
			
				|  |  | +	spi_write(self->spi_handle, self->phy.addr, 0x0c, self->pullup, 2);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				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; pending; 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);
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +static int 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);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		// check if we have some other pending requests
 | 
	
		
			
				|  |  | -		if (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);  
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | +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_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val) {
 | 
	
		
			
				|  |  | -    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
 | 
	
		
			
				|  |  | +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, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -	i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -	i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -	
 | 
	
		
			
				|  |  | +	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 out 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(i2c_port, cmd, 100 / portTICK_RATE_MS);
 | 
	
		
			
				|  |  | +	esp_err_t ret = i2c_master_cmd_begin(port, cmd, 100 / portTICK_RATE_MS);
 | 
	
		
			
				|  |  |      i2c_cmd_link_delete(cmd);
 | 
	
		
			
				|  |  |  	
 | 
	
		
			
				|  |  | -	if (ret != ESP_OK) {
 | 
	
		
			
				|  |  | +	if (ret != ESP_OK) {		
 | 
	
		
			
				|  |  |  		ESP_LOGW(TAG, "I2C write failed");
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	
 | 
	
	
		
			
				|  | @@ -542,59 +615,96 @@ static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg,
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /****************************************************************************************
 | 
	
		
			
				|  |  | - * I2C read 8 or 16 bits word
 | 
	
		
			
				|  |  | + * I2C read up to 32 bits
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -static uint16_t i2c_read(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, bool word) {
 | 
	
		
			
				|  |  | -	uint8_t data[2];
 | 
	
		
			
				|  |  | +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);
 | 
	
		
			
				|  |  | -    i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | +    i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	// when using a register, write it's value then the device address again
 | 
	
		
			
				|  |  |  	if (reg != 0xff) {
 | 
	
		
			
				|  |  |  		i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  |  		i2c_master_start(cmd);
 | 
	
		
			
				|  |  | -		i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (word) {
 | 
	
		
			
				|  |  | -		i2c_master_read_byte(cmd, data, I2C_MASTER_ACK);
 | 
	
		
			
				|  |  | -		i2c_master_read_byte(cmd, data + 1, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -	} else {
 | 
	
		
			
				|  |  | -		i2c_master_read_byte(cmd, data, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | +		i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	
 | 
	
		
			
				|  |  | +	// works with out 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(i2c_port, cmd, 100 / portTICK_RATE_MS);
 | 
	
		
			
				|  |  | +    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 *(uint16_t*) data;
 | 
	
		
			
				|  |  | +	return data;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/***************************************************************************************
 | 
	
		
			
				|  |  | +                                     SPI low level                                   
 | 
	
		
			
				|  |  | +***************************************************************************************/
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /****************************************************************************************
 | 
	
		
			
				|  |  | - * I2C write 16 bits word
 | 
	
		
			
				|  |  | + * SPI device addition
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data) {
 | 
	
		
			
				|  |  | -	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
 | 
	
		
			
				|  |  | -    i2c_master_start(cmd);
 | 
	
		
			
				|  |  | -	
 | 
	
		
			
				|  |  | -	i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -	if (reg != 0xff) i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -	i2c_master_write(cmd, (uint8_t*) &data, 2, I2C_MASTER_NACK);
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -	i2c_master_stop(cmd);
 | 
	
		
			
				|  |  | -	esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
 | 
	
		
			
				|  |  | -    i2c_cmd_link_delete(cmd);
 | 
	
		
			
				|  |  | -	
 | 
	
		
			
				|  |  | -	if (ret != ESP_OK) {		
 | 
	
		
			
				|  |  | -		ESP_LOGW(TAG, "I2C write failed");
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | +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;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// initialize ChipSelect (CS)
 | 
	
		
			
				|  |  | +	gpio_set_direction(phy->cs_pin, GPIO_MODE_OUTPUT );
 | 
	
		
			
				|  |  | +	gpio_set_level(phy->cs_pin, 0 );
 | 
	
		
			
				|  |  |  	
 | 
	
		
			
				|  |  | -    return ret;
 | 
	
		
			
				|  |  | +    memset( &config, 0, sizeof( spi_device_interface_config_t ) );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	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 );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	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[1] = data; transaction.tx_data[2] = 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 = { };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// tx_buffer is NULL, nothing to transmit except cmd/addr
 | 
	
		
			
				|  |  | +	transaction.flags = SPI_TRANS_USE_RXDATA;
 | 
	
		
			
				|  |  | +	transaction.cmd = (addr << 1) | 1;
 | 
	
		
			
				|  |  | +	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);
 | 
	
		
			
				|  |  | +	return *(uint32_t*) transaction.rx_data;
 | 
	
		
			
				|  |  |  }
 |