271 lines
8.9 KiB
C
271 lines
8.9 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "py/mperrno.h"
|
|
#include "py/mphal.h"
|
|
#include "shared-bindings/busio/I2C.h"
|
|
#include "py/runtime.h"
|
|
|
|
#include "shared-bindings/microcontroller/__init__.h"
|
|
#include "shared-bindings/microcontroller/Pin.h"
|
|
|
|
#include "peripherals/broadcom/cpu.h"
|
|
#include "peripherals/broadcom/vcmailbox.h"
|
|
|
|
#if BCM_VERSION == 2711
|
|
#define NUM_I2C (8)
|
|
STATIC BSC0_Type *i2c[NUM_I2C] = {BSC0, BSC1, NULL, BSC3, BSC4, BSC5, BSC6, NULL};
|
|
#else
|
|
#define NUM_I2C (3)
|
|
STATIC BSC0_Type *i2c[NUM_I2C] = {BSC0, BSC1, NULL};
|
|
#endif
|
|
|
|
STATIC bool never_reset_i2c[NUM_I2C];
|
|
STATIC bool i2c_in_use[NUM_I2C];
|
|
|
|
void reset_i2c(void) {
|
|
// BSC2 is dedicated to the first HDMI output.
|
|
never_reset_i2c[2] = true;
|
|
i2c_in_use[2] = true;
|
|
#if BCM_VERSION == 2711
|
|
// BSC7 is dedicated to the second HDMI output.
|
|
never_reset_i2c[7] = true;
|
|
i2c_in_use[7] = true;
|
|
#endif
|
|
for (size_t i = 0; i < NUM_I2C; i++) {
|
|
if (never_reset_i2c[i]) {
|
|
continue;
|
|
}
|
|
i2c_in_use[i] = false;
|
|
i2c[i]->C_b.I2CEN = false;
|
|
COMPLETE_MEMORY_READS;
|
|
}
|
|
}
|
|
|
|
void common_hal_busio_i2c_construct(busio_i2c_obj_t *self,
|
|
const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout) {
|
|
size_t instance_index = NUM_I2C;
|
|
uint8_t scl_alt = 0;
|
|
uint8_t sda_alt = 0;
|
|
for (scl_alt = 0; scl_alt < 6; scl_alt++) {
|
|
if (scl->functions[scl_alt].type != PIN_FUNCTION_I2C ||
|
|
i2c_in_use[scl->functions[scl_alt].index] ||
|
|
scl->functions[scl_alt].function != I2C_FUNCTION_SCL) {
|
|
continue;
|
|
}
|
|
for (sda_alt = 0; sda_alt < 6; sda_alt++) {
|
|
if (sda->functions[sda_alt].type != PIN_FUNCTION_I2C ||
|
|
scl->functions[scl_alt].index != sda->functions[sda_alt].index ||
|
|
sda->functions[sda_alt].function != I2C_FUNCTION_SDA) {
|
|
continue;
|
|
}
|
|
instance_index = scl->functions[scl_alt].index;
|
|
break;
|
|
}
|
|
if (instance_index != NUM_I2C) {
|
|
break;
|
|
}
|
|
}
|
|
if (instance_index == NUM_I2C) {
|
|
raise_ValueError_invalid_pins();
|
|
}
|
|
i2c_in_use[instance_index] = true;
|
|
self->index = instance_index;
|
|
self->peripheral = i2c[self->index];
|
|
self->sda_pin = sda;
|
|
self->scl_pin = scl;
|
|
|
|
uint32_t source_clock = vcmailbox_get_clock_rate_measured(VCMAILBOX_CLOCK_CORE);
|
|
uint16_t clock_divider = source_clock / frequency;
|
|
self->peripheral->DIV_b.CDIV = clock_divider;
|
|
|
|
gpio_set_function(sda->number, FSEL_VALUES[sda_alt]);
|
|
gpio_set_function(scl->number, FSEL_VALUES[scl_alt]);
|
|
}
|
|
|
|
bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) {
|
|
return self->sda_pin == NULL;
|
|
}
|
|
|
|
void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) {
|
|
if (common_hal_busio_i2c_deinited(self)) {
|
|
return;
|
|
}
|
|
never_reset_i2c[self->index] = false;
|
|
i2c_in_use[self->index] = false;
|
|
|
|
common_hal_reset_pin(self->sda_pin);
|
|
common_hal_reset_pin(self->scl_pin);
|
|
self->sda_pin = NULL;
|
|
self->scl_pin = NULL;
|
|
}
|
|
|
|
bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) {
|
|
uint8_t result = common_hal_busio_i2c_write(self, addr, NULL, 0);
|
|
return result == 0;
|
|
}
|
|
|
|
bool common_hal_busio_i2c_try_lock(busio_i2c_obj_t *self) {
|
|
bool grabbed_lock = false;
|
|
if (!self->has_lock) {
|
|
grabbed_lock = true;
|
|
self->has_lock = true;
|
|
}
|
|
return grabbed_lock;
|
|
}
|
|
|
|
bool common_hal_busio_i2c_has_lock(busio_i2c_obj_t *self) {
|
|
return self->has_lock;
|
|
}
|
|
|
|
void common_hal_busio_i2c_unlock(busio_i2c_obj_t *self) {
|
|
self->has_lock = false;
|
|
}
|
|
|
|
// Discussion of I2C implementation is here: https://github.com/raspberrypi/linux/issues/254
|
|
|
|
STATIC uint8_t _common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr,
|
|
const uint8_t *data, size_t len, bool transmit_stop_bit) {
|
|
COMPLETE_MEMORY_READS;
|
|
self->peripheral->S_b.DONE = true;
|
|
self->peripheral->A_b.ADDR = addr;
|
|
size_t loop_len = len;
|
|
// Prevent the stop bit by transmitting everything but the last byte. Doing
|
|
// so is left up to the subsequent read.
|
|
if (!transmit_stop_bit) {
|
|
loop_len -= 1;
|
|
}
|
|
self->peripheral->DLEN_b.DLEN = len;
|
|
self->peripheral->C = BSC0_C_ST_Msk | BSC0_C_I2CEN_Msk;
|
|
// Wait for the transaction to start.
|
|
while (self->peripheral->S_b.TA == 0) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
for (size_t i = 0; i < loop_len; i++) {
|
|
if (self->peripheral->S_b.ERR) {
|
|
break;
|
|
}
|
|
self->peripheral->FIFO_b.DATA = data[i];
|
|
// Wait for the FIFO to empty enough that we can write more data.
|
|
while (self->peripheral->S_b.TXE == 0) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
}
|
|
// Wait for the FIFO to empty completely, not DONE, because we may not complete the
|
|
// transaction with a write.
|
|
while (self->peripheral->S_b.ERR == 0 &&
|
|
((!transmit_stop_bit && self->peripheral->S_b.TXE == 0) ||
|
|
(transmit_stop_bit && self->peripheral->S_b.TA == 1))) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
self->finish_write = false;
|
|
if (self->peripheral->S_b.ERR) {
|
|
// Wait for the transfer to finish.
|
|
while (self->peripheral->S_b.TA == 1) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
// Clear the flag by writing it and wait for it to clear.
|
|
self->peripheral->S_b.ERR = true;
|
|
while (self->peripheral->S_b.ERR == 1) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
return MP_ENODEV;
|
|
}
|
|
|
|
if (loop_len < len) {
|
|
self->finish_write = true;
|
|
self->last_write_data = data[len - 1];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr,
|
|
const uint8_t *data, size_t len) {
|
|
return _common_hal_busio_i2c_write(self, addr, data, len, true);
|
|
}
|
|
|
|
uint8_t common_hal_busio_i2c_read(busio_i2c_obj_t *self, uint16_t addr,
|
|
uint8_t *data, size_t len) {
|
|
COMPLETE_MEMORY_READS;
|
|
self->peripheral->A_b.ADDR = addr;
|
|
if (self->finish_write) {
|
|
self->finish_write = false;
|
|
if (self->peripheral->S_b.ERR == 1) {
|
|
return MP_ENODEV;
|
|
}
|
|
self->peripheral->FIFO_b.DATA = self->last_write_data;
|
|
} else {
|
|
self->peripheral->S_b.DONE = true;
|
|
}
|
|
self->peripheral->DLEN_b.DLEN = len;
|
|
self->peripheral->C = BSC0_C_READ_Msk | BSC0_C_ST_Msk | BSC0_C_I2CEN_Msk;
|
|
// Wait for the transaction to start.
|
|
while (self->peripheral->S_b.TA == 0) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (self->peripheral->S_b.ERR) {
|
|
break;
|
|
}
|
|
// Wait for the FIFO to have enough data that we can read it. RXR is low
|
|
// once the transaction is done so we check the done bit too.
|
|
while (!self->peripheral->S_b.RXR && !self->peripheral->S_b.DONE) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
data[i] = self->peripheral->FIFO_b.DATA;
|
|
}
|
|
// Wait for the transaction to finish.
|
|
while (!self->peripheral->S_b.DONE && !self->peripheral->S_b.ERR) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
if (self->peripheral->S_b.ERR) {
|
|
self->peripheral->S_b.ERR = true;
|
|
while (self->peripheral->S_b.ERR == 1) {
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
return MP_ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t addr,
|
|
uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len) {
|
|
uint8_t result = _common_hal_busio_i2c_write(self, addr, out_data, out_len, false);
|
|
if (result != 0) {
|
|
return result;
|
|
}
|
|
|
|
return common_hal_busio_i2c_read(self, addr, in_data, in_len);
|
|
}
|
|
|
|
void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) {
|
|
never_reset_i2c[self->index] = true;
|
|
|
|
common_hal_never_reset_pin(self->scl_pin);
|
|
common_hal_never_reset_pin(self->sda_pin);
|
|
}
|