Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of mbed-dev by
targets/TARGET_ARM_SSG/TARGET_BEETLE/i2c_api.c
- Committer:
- <>
- Date:
- 2016-10-28
- Revision:
- 149:156823d33999
- Parent:
- targets/hal/TARGET_ARM_SSG/TARGET_BEETLE/i2c_api.c@ 144:ef7eb2e8f9f7
- Child:
- 160:d5399cc887bb
File content as of revision 149:156823d33999:
/* mbed Microcontroller Library * Copyright (c) 2015 ARM Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "i2c_api.h" #include "i2c_def.h" #include "cmsis.h" #include "pinmap.h" #include "mbed_error.h" #include "wait_api.h" /* States of a possibly combined I2C transfer */ typedef enum i2c_transfer_state_t { I2C_TRANSFER_SINGLE, /* Non combined transfer */ I2C_TRANSFER_COMBINED_FIRST_MESSAGE, /* * First message of a * combined transfer */ I2C_TRANSFER_COMBINED_INTERMEDIATE_MESSAGE, /* * Message in the middle * of a combined * transfer */ I2C_TRANSFER_COMBINED_LAST_MESSAGE, /* * Last message of a combined * transfer */ } i2c_transfer_state_t; /* * Driver private data structure that should not be shared by multiple * instances of the driver * (same driver for multiple instances of the IP) */ typedef struct private_i2c_t { /* State of a possibly combined ongoing i2c transfer */ i2c_transfer_state_t transfer_state; }private_i2c_t; /* * Retrieve the private data of the instance related to a given IP */ static private_i2c_t* get_i2c_private(i2c_t *obj) { static private_i2c_t data0, data1; /* * Select which instance to give using the base * address of registers */ switch((intptr_t)obj->i2c) { case I2C0_BASE: return &data0; case I2C1_BASE: return &data1; default: error("i2c driver private data structure not found for this registers base address"); return (void*)0; } } /* * Infer the current state of a possibly combined transfer * (repeated restart) from the current state and the "stop" parameter * of read and write functions * MUST be called ONCE AND ONLY ONCE at the beginning of i2c transfer * functions (read and write) */ static i2c_transfer_state_t update_transfer_state(i2c_t *obj, int stop) { private_i2c_t* private_data = get_i2c_private(obj); i2c_transfer_state_t *state = &private_data->transfer_state; /* * Choose the current and next state depending on the current state * This basically implements rising and falling edge detection on * "stop" variable */ switch(*state) { /* This is the default state for non restarted repeat transfer */ default: case I2C_TRANSFER_SINGLE: /* Not a combined transfer */ if (stop) { *state = I2C_TRANSFER_SINGLE; } else { *state = I2C_TRANSFER_COMBINED_FIRST_MESSAGE; } break; /* First message of a combined transfer */ case I2C_TRANSFER_COMBINED_FIRST_MESSAGE: /* Message in the middle of a combined transfer */ case I2C_TRANSFER_COMBINED_INTERMEDIATE_MESSAGE: if (stop) { *state = I2C_TRANSFER_COMBINED_LAST_MESSAGE; } else { *state = I2C_TRANSFER_COMBINED_INTERMEDIATE_MESSAGE; } break; /* Last message of a combined transfer */ case I2C_TRANSFER_COMBINED_LAST_MESSAGE: if (stop) { *state = I2C_TRANSFER_SINGLE; } else { *state = I2C_TRANSFER_COMBINED_FIRST_MESSAGE; } break; } return *state; } static const PinMap PinMap_I2C_SDA[] = { {SHIELD_SDA, I2C_0, 0}, {SENSOR_SDA, I2C_1, 0}, {NC, NC , 0} }; static const PinMap PinMap_I2C_SCL[] = { {SHIELD_SCL, I2C_0, 0}, {SENSOR_SCL, I2C_1, 0}, {NC, NC, 0} }; static void clear_isr(i2c_t *obj) { /* * Writing to the IRQ status register clears set bits. Therefore, to * clear indiscriminately, just read the register and write it back. */ uint32_t reg = obj->i2c->IRQ_STATUS; obj->i2c->IRQ_STATUS = reg; } void i2c_init(i2c_t *obj, PinName sda, PinName scl) { /* Determine the I2C to use */ I2CName i2c_sda = (I2CName)pinmap_peripheral(sda, PinMap_I2C_SDA); I2CName i2c_scl = (I2CName)pinmap_peripheral(scl, PinMap_I2C_SCL); obj->i2c = (I2C_TypeDef *)pinmap_merge(i2c_sda, i2c_scl); if ((int)obj->i2c == NC) { error("I2C pin mapping failed"); } pinmap_pinout(sda, PinMap_I2C_SDA); pinmap_pinout(scl, PinMap_I2C_SCL); /* * Default configuration: * - MS : Master mode * - NEA : Normal (7-bit) addressing * - ACKEN : Send ACKs when reading from slave * - CLR_FIFO : Not a configuration bit => clears the FIFO */ uint32_t reg = I2C_CTRL_MS | \ I2C_CTRL_NEA | \ I2C_CTRL_ACKEN | \ I2C_CTRL_CLR_FIFO; obj->i2c->CONTROL = reg; get_i2c_private(obj)->transfer_state = I2C_TRANSFER_SINGLE; i2c_frequency(obj, 100000); /* Default to 100kHz SCL frequency */ } int i2c_start(i2c_t *obj) { return 0; } int i2c_stop(i2c_t *obj) { /* Clear the hardware FIFO */ obj->i2c->CONTROL |= I2C_CTRL_CLR_FIFO; /* Clear the HOLD bit used for performing combined transfers */ obj->i2c->CONTROL &= ~I2C_CTRL_HOLD; /* Reset the transfer size (read and write) */ obj->i2c->TRANSFER_SIZE = 0; /* Clear interrupts */ clear_isr(obj); return 0; } void i2c_frequency(i2c_t *obj, int hz) { /* * Divider is split in two halfs : A and B * A is 2 bits wide and B is 6 bits wide * The Fscl frequency (SCL clock) is calculated with the following * equation: * Fscl=SystemCoreClock/(22*(A+1)*(B+1)) * Here, we only calculate the B divisor which already enables a * wide enough range of values */ uint32_t divisor_a = 0; /* Could be changed if a wider range of hz is needed */ uint32_t divisor_b = (SystemCoreClock / (22.0 * hz)) - 1; /* Clamp the divisors to their maximal value */ divisor_a = divisor_a > I2C_CTRL_DIVISOR_A_BIT_MASK ? I2C_CTRL_DIVISOR_A_BIT_MASK : divisor_a; divisor_b = divisor_b > I2C_CTRL_DIVISOR_B_BIT_MASK ? I2C_CTRL_DIVISOR_B_BIT_MASK : divisor_b; uint8_t divisor_combinded = (divisor_a & I2C_CTRL_DIVISOR_A_BIT_MASK) | (divisor_b & I2C_CTRL_DIVISOR_B_BIT_MASK); obj->i2c->CONTROL = (obj->i2c->CONTROL & ~I2C_CTRL_DIVISORS) | (divisor_combinded << I2C_CTRL_DIVISOR_OFFSET); } int i2c_read(i2c_t *obj, int address, char *data, int length, int stop) { int bytes_read = 0; int length_backup = length; char *data_backup = data; obj->last_xfer_address = address; i2c_transfer_state_t transfer_state = update_transfer_state(obj, stop); /* Try to write until it finally succeed or times out */ int main_timeout = 10; int retry = 0; do { main_timeout--; retry = 0; bytes_read = 0; length = length_backup; data = data_backup; uint32_t reg = obj->i2c->CONTROL & 0xff7f; reg |= I2C_CTRL_RW | \ I2C_CTRL_CLR_FIFO; /* * Only touch the HOLD bit at the beginning of * (possibly combined) transactions */ if(transfer_state == I2C_TRANSFER_COMBINED_FIRST_MESSAGE || transfer_state == I2C_TRANSFER_SINGLE) { reg |= I2C_CTRL_HOLD; } obj->i2c->CONTROL = reg; /* Set the expected number of bytes to be received */ if (length > I2C_TRANSFER_SIZE) { error("I2C transfer size too big for the FIFO"); } obj->i2c->TRANSFER_SIZE = length & I2C_TRANSFER_SIZE; clear_isr(obj); /* * Start the transaction by writing address. * Discard the lower bit as it is automatically set * by the controller based on I2C_CTRL_RW bit in CONTROL * register */ obj->i2c->ADDRESS = (address & 0xFF) >> 1; if(transfer_state == I2C_TRANSFER_COMBINED_LAST_MESSAGE || transfer_state == I2C_TRANSFER_SINGLE) { /* Clear the hold bit before reading the DATA register */ obj->i2c->CONTROL &= ~I2C_CTRL_HOLD; } /* Wait for completion of the address transfer */ int completion_timeout = 1000; while (completion_timeout) { completion_timeout--; uint32_t irq_status = obj->i2c->IRQ_STATUS; if (irq_status & I2C_IRQ_NACK || irq_status & I2C_IRQ_ARB_LOST) { retry = 1; break; } if(irq_status & I2C_IRQ_COMP) { break; } } /* If retry, jump to the beginning and try again */ if (retry || !completion_timeout) { retry = 1; continue; } clear_isr(obj); /* Read the data from the DATA register */ completion_timeout = 1000; while (length && completion_timeout) { completion_timeout--; uint32_t irq_status = obj->i2c->IRQ_STATUS; uint32_t status = obj->i2c->STATUS; if(irq_status & I2C_IRQ_NACK || irq_status & I2C_IRQ_ARB_LOST) { retry = 1; break; } /* * Just wait for RXDV because COMP is only risen at the end * of the transfer */ if (status & I2C_STATUS_RXDV) { *data++ = obj->i2c->DATA & 0xFF; length--; bytes_read++; } if (irq_status & I2C_IRQ_RX_UNF) { error("Reading more bytes than the I2C transfer size"); retry = 1; break; } } /* If retry, jump to the beginning and try again */ if (retry || !completion_timeout) { retry = 1; continue; } } while(retry && main_timeout); if (!main_timeout) { bytes_read = 0; data = data_backup; } obj->i2c->CONTROL |= I2C_CTRL_CLR_FIFO; clear_isr(obj); return bytes_read; } int i2c_write(i2c_t *obj, int address, const char *data, int length, int stop) { int bytes_written = 0; int length_backup = length; const char *data_backup = data; obj->last_xfer_address = address; i2c_transfer_state_t transfer_state = update_transfer_state(obj, stop); /* Try to write until it finally succeed or times out */ int main_timeout = 10; int retry = 0; do { main_timeout--; retry = 0; bytes_written = 0; length = length_backup; data = data_backup; /* Read the defined bits in the control register */ uint32_t reg = obj->i2c->CONTROL & 0xff7f; reg |= I2C_CTRL_CLR_FIFO; reg &= ~I2C_CTRL_RW; /* * Only touch the HOLD bit at the beginning of * (possibly combined) transactions */ if(transfer_state == I2C_TRANSFER_COMBINED_FIRST_MESSAGE || transfer_state == I2C_TRANSFER_SINGLE) { reg |= I2C_CTRL_HOLD; } obj->i2c->CONTROL = reg; clear_isr(obj); /* Set the expected number of bytes to be transmitted */ if (length > I2C_TRANSFER_SIZE) { error("I2C transfer size too big for the FIFO"); } /* Set the expected number of bytes to be transmitted */ obj->i2c->TRANSFER_SIZE = length & I2C_TRANSFER_SIZE; /* * Write the address, triggering the start of the transfer * Discard the lower bit as it is automatically set * by the controller based on I2C_CTRL_RW bit in CONTROL * register */ obj->i2c->ADDRESS = (address & 0xFF) >> 1; /* Send the data bytes */ int write_timeout = 1000 + length; while (length && write_timeout) { write_timeout--; uint32_t irq_status = obj->i2c->IRQ_STATUS; /* If overflow, undo last step */ if (irq_status & I2C_IRQ_TX_OVF) { *data--; length++; bytes_written--; /* Clear the bit by writing 1 to it */ obj->i2c->IRQ_STATUS |= I2C_IRQ_TX_OVF; } if (irq_status & I2C_IRQ_NACK || irq_status & I2C_IRQ_ARB_LOST) { retry = 1; break; } obj->i2c->DATA = *data++; length--; bytes_written++; } /* If retry, jump to the beginning and try again */ if (retry || !write_timeout) { retry = 1; continue; } if(transfer_state == I2C_TRANSFER_COMBINED_LAST_MESSAGE || transfer_state == I2C_TRANSFER_SINGLE) { /* * Clear the hold bit to signify the end * of the write sequence */ obj->i2c->CONTROL &= ~I2C_CTRL_HOLD; } /* Wait for transfer completion */ int completion_timeout = 1000; while (completion_timeout) { completion_timeout--; uint32_t irq_status = obj->i2c->IRQ_STATUS; if(irq_status & I2C_IRQ_NACK || irq_status & I2C_IRQ_ARB_LOST) { retry = 1; break; } if(irq_status & I2C_IRQ_COMP) { break; } } /* If retry, jump to the beginning and try again */ if (retry || !completion_timeout) { continue; } obj->i2c->CONTROL |= I2C_CTRL_CLR_FIFO; clear_isr(obj); } while(retry && main_timeout); return bytes_written; } void i2c_reset(i2c_t *obj) { i2c_stop(obj); } int i2c_byte_read(i2c_t *obj, int last) { char i2c_ret = 0; i2c_read(obj, obj->last_xfer_address, &i2c_ret, 1, last); return i2c_ret; } int i2c_byte_write(i2c_t *obj, int data) { /* Store the number of written bytes */ uint32_t wb = i2c_write(obj, obj->last_xfer_address, (char*)&data, 1, 0); if (wb == 1) return 1; else return 0; } void i2c_slave_mode(i2c_t *obj, int enable_slave) { } int i2c_slave_receive(i2c_t *obj) { return 0; } int i2c_slave_read(i2c_t *obj, char *data, int length) { return 0; } int i2c_slave_write(i2c_t *obj, const char *data, int length) { return 0; } void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask) { }