static void i2c_init(uint32_t bus, SercomI2cm *si) { static uint8_t have_run_init; if (have_run_init & (1<<bus)) return; have_run_init |= 1<<bus; // Configure i2c si->CTRLA.reg = 0; uint32_t areg = (SERCOM_I2CM_CTRLA_LOWTOUTEN | SERCOM_I2CM_CTRLA_INACTOUT(3) | SERCOM_I2CM_STATUS_SEXTTOUT | SERCOM_I2CM_STATUS_MEXTTOUT | SERCOM_I2CM_CTRLA_MODE(5)); si->CTRLA.reg = areg; uint32_t freq = sercom_get_pclock_frequency(bus); uint32_t baud = (freq/I2C_FREQ - 10 - freq*TIME_RISE/1000000000) / 2; si->BAUD.reg = baud; si->CTRLA.reg = areg | SERCOM_I2CM_CTRLA_ENABLE; while (si->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_ENABLE) ; // Go into idle mode si->STATUS.reg = SERCOM_I2CM_STATUS_BUSSTATE(1); while (si->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_SYSOP) ; }
/** * \internal * Starts blocking write operation. * * \param[in,out] module Pointer to software module struct. * \param[in,out] packet Pointer to I<SUP>2</SUP>C packet to transfer. * * \return Status of reading packet. * \retval STATUS_OK The packet was read successfully * \retval STATUS_ERR_TIMEOUT If no response was given within * specified timeout period * \retval STATUS_ERR_DENIED If error on bus * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave * acknowledged the address */ static enum status_code _i2c_master_write_packet( struct i2c_master_module *const module, struct i2c_master_packet *const packet) { SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Return value. */ enum status_code tmp_status; uint16_t tmp_data_length = packet->data_length; _i2c_master_wait_for_sync(module); /* Set address and direction bit. Will send start command on bus. */ i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_WRITE; /* Wait for response on bus. */ tmp_status = _i2c_master_wait_for_bus(module); /* Check for address response error unless previous error is * detected. */ if (tmp_status == STATUS_OK) { tmp_status = _i2c_master_address_response(module); } /* Check that no error has occurred. */ if (tmp_status == STATUS_OK) { /* Buffer counter. */ uint16_t buffer_counter = 0; /* Write data buffer. */ while (tmp_data_length--) { /* Check that bus ownership is not lost. */ if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { return STATUS_ERR_PACKET_COLLISION; } /* Write byte to slave. */ _i2c_master_wait_for_sync(module); i2c_module->DATA.reg = packet->data[buffer_counter++]; /* Wait for response. */ tmp_status = _i2c_master_wait_for_bus(module); /* Check for error. */ if (tmp_status != STATUS_OK) { break; } /* Check for NACK from slave. */ if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { /* Return bad data value. */ tmp_status = STATUS_ERR_OVERFLOW; break; } } if (module->send_stop) { /* Stop command */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } } return tmp_status; }
/** * \internal * Starts blocking read operation. * * \param[in,out] module Pointer to software module struct. * \param[in,out] packet Pointer to I<SUP>2</SUP>C packet to transfer. * * \return Status of reading packet. * \retval STATUS_OK The packet was read successfully * \retval STATUS_ERR_TIMEOUT If no response was given within * specified timeout period * \retval STATUS_ERR_DENIED If error on bus * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave * acknowledged the address * */ static enum status_code _i2c_master_read_packet( struct i2c_master_module *const module, struct i2c_master_packet *const packet) { /* Sanity check arguments */ Assert(module); Assert(module->hw); Assert(packet); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Return value. */ enum status_code tmp_status; uint16_t tmp_data_length = packet->data_length; /* Written buffer counter. */ uint16_t counter = 0; /* Set address and direction bit. Will send start command on bus. */ i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_READ; /* Wait for response on bus. */ tmp_status = _i2c_master_wait_for_bus(module); /* Set action to ack. */ i2c_module->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; /* Check for address response error unless previous error is * detected. */ if (tmp_status == STATUS_OK) { tmp_status = _i2c_master_address_response(module); } /* Check that no error has occurred. */ if (tmp_status == STATUS_OK) { /* Read data buffer. */ while (tmp_data_length--) { /* Check that bus ownership is not lost. */ if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { return STATUS_ERR_PACKET_COLLISION; } if (tmp_data_length == 0) { /* Set action to NACK */ i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; } else { /* Save data to buffer. */ _i2c_master_wait_for_sync(module); packet->data[counter++] = i2c_module->DATA.reg; /* Wait for response. */ tmp_status = _i2c_master_wait_for_bus(module); } /* Check for error. */ if (tmp_status != STATUS_OK) { break; } } if (module->send_stop) { /* Send stop command unless arbitration is lost. */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } /* Save last data to buffer. */ _i2c_master_wait_for_sync(module); packet->data[counter] = i2c_module->DATA.reg; } return tmp_status; }
/** * \internal * Interrupt handler for I<SUP>2</SUP>C master. * * \param[in] instance SERCOM instance that triggered the interrupt */ void _i2c_master_interrupt_handler( uint8_t instance) { /* Get software module for callback handling */ struct i2c_master_module *module = (struct i2c_master_module*)_sercom_instances[instance]; Assert(module); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Combine callback registered and enabled masks */ uint8_t callback_mask = module->enabled_callback & module->registered_callback; /* Check if the module should respond to address ack */ if ((module->buffer_length <= 0) && (module->buffer_remaining > 0)) { /* Call function for address response */ _i2c_master_async_address_response(module); /* Check if buffer write is done */ } else if ((module->buffer_length > 0) && (module->buffer_remaining <= 0) && (module->status == STATUS_BUSY) && (module->transfer_direction == I2C_TRANSFER_WRITE)) { /* Stop packet operation */ i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB; module->buffer_length = 0; module->status = STATUS_OK; if (module->send_stop) { /* Send stop condition */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } else { /* Clear write interrupt flag */ i2c_module->INTFLAG.reg = SERCOM_I2CM_INTENCLR_MB; } if (callback_mask & (1 << I2C_MASTER_CALLBACK_WRITE_COMPLETE)) { module->callbacks[I2C_MASTER_CALLBACK_WRITE_COMPLETE](module); } /* Continue buffer write/read */ } else if ((module->buffer_length > 0) && (module->buffer_remaining > 0)){ /* Check that bus ownership is not lost */ if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { module->status = STATUS_ERR_PACKET_COLLISION; } else if (module->transfer_direction == I2C_TRANSFER_WRITE) { _i2c_master_write(module); } else { _i2c_master_read(module); } } /* Check if read buffer transfer is complete */ if ((module->buffer_length > 0) && (module->buffer_remaining <= 0) && (module->status == STATUS_BUSY) && (module->transfer_direction == I2C_TRANSFER_READ)) { /* Stop packet operation */ i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB; module->buffer_length = 0; module->status = STATUS_OK; /* Call appropriate callback if enabled and registered */ if ((callback_mask & (1 << I2C_MASTER_CALLBACK_READ_COMPLETE)) && (module->transfer_direction == I2C_TRANSFER_READ)) { module->callbacks[I2C_MASTER_CALLBACK_READ_COMPLETE](module); } else if ((callback_mask & (1 << I2C_MASTER_CALLBACK_WRITE_COMPLETE)) && (module->transfer_direction == I2C_TRANSFER_WRITE)) { module->callbacks[I2C_MASTER_CALLBACK_WRITE_COMPLETE](module); } } /* Check for error */ if ((module->status != STATUS_BUSY) && (module->status != STATUS_OK)) { /* Stop packet operation */ i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB; module->buffer_length = 0; module->buffer_remaining = 0; /* Send nack and stop command unless arbitration is lost */ if ((module->status != STATUS_ERR_PACKET_COLLISION) && module->send_stop) { _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(3); } /* Call error callback if enabled and registered */ if (callback_mask & (1 << I2C_MASTER_CALLBACK_ERROR)) { module->callbacks[I2C_MASTER_CALLBACK_ERROR](module); } } }