/** * @file sdrv_i2c_drv.c * @brief sdrv i2c driver function file. * * @Copyright (c) 2022 Semidrive Semiconductor. * @All rights reserved. **/ #include #include #include #include "irq.h" #include "sdrv_i2c_reg.h" #define i2c_min(a, b) ((a) < (b) ? a : b) /** * @brief I2c check error type * * @param[in] base i2c controller instance pointer * @param[in] errcode err code storage variable pointer * @param[in] id i2c bus num * @param[in] int0_stat int0 register value * @param[in] idx local err flag num * * @return SDRV_I2CDRV_TRANS_NAK/SDRV_I2CDRV_TRANS_ERR */ static status_t sdrv_i2c_check_err(paddr_t base, uint32_t *errcode, uint32_t id, uint32_t int0_stat, uint32_t idx) { status_t ret; uint32_t psr0_stat; psr0_stat = sdrv_i2c_get_psr_stat(base, I2C_PSR0); if ((psr0_stat & 0xff) == 0x27) ret = SDRV_I2CDRV_TRANS_NAK; else { ssdk_printf(SSDK_CRIT, "i2c%u err%u int0=%x, psr0=%x\r\n", id, idx, int0_stat, psr0_stat); *errcode = psr0_stat; sdrv_i2c_reset(base); ret = SDRV_I2CDRV_TRANS_ERR; } return ret; } /** * @brief I2c write data to slave in polling mode * * @param[in] base i2c controller instance pointer * @param[in] id i2c controller instance num * @param[in] mctrl i2c controller instance pointer * @param[in] timeout timeout in milliseconds to wait (0 = wait forever) * * @return SDRV_I2CDRV_TRANS_OK * SDRV_I2CDRV_TRANS_GO * SDRV_I2CDRV_TRANS_ERR * SDRV_I2CDRV_TRANS_NAK * SDRV_I2CDRV_TRANS_TIMEOUT */ static status_t sdrv_i2c_pwrite(paddr_t base, uint32_t id, sdrv_i2cdrv_mctrl_t *mctrl, uint32_t timeout) { uint8_t len = 0; uint8_t flen = 0; const uint32_t retry_delay = 10; /* us */ uint64_t retry_times = (uint64_t)timeout * 1000 / retry_delay; uint64_t scnt = 100000UL / retry_delay; uint32_t int0_stat = 0; uint8_t *wbuf = mctrl->buf; uint16_t wlen = mctrl->len; while (wlen) { int0_stat = sdrv_i2c_get_int_stat(base, I2C_INTR0); if (int0_stat & SDRV_I2C_INT0_ERR_STAT) return sdrv_i2c_check_err(base, &mctrl->trans_errcode, id, int0_stat, 1); flen = (uint8_t)sdrv_i2c_get_wspace(base); if (flen > 0) { len = i2c_min(flen, wlen); wlen -= len; while (len--) sdrv_i2c_send_data(base, *wbuf++); } else if (timeout > 0) { if (retry_times--) { udelay(retry_delay); continue; } else { ssdk_printf(SSDK_CRIT, "i2c%u write retry time runs out\r\n", id); return SDRV_I2CDRV_TRANS_TIMEOUT; } } } if (timeout > 0) { if (retry_times > 0) scnt = retry_times; else scnt = 1; } while (scnt--) { int0_stat = sdrv_i2c_get_int_stat(base, I2C_INTR0); if (int0_stat & SDRV_I2C_INT0_APBCMDDONE) { return SDRV_I2CDRV_TRANS_OK; } else if (int0_stat & SDRV_I2C_INT0_ERR_STAT) { return sdrv_i2c_check_err(base, &mctrl->trans_errcode, id, int0_stat, 2); } else if (scnt <= 1) { ssdk_printf(SSDK_CRIT, "i2c%u pwerr may hold by slave\r\n", id); return SDRV_I2CDRV_TRANS_TIMEOUT; } else udelay(retry_delay); } return SDRV_I2CDRV_TRANS_GO; } /** * @brief I2c read data from slave in polling mode * * @param[in] base i2c controller instance pointer * @param[in] id i2c controller instance num * @param[in] mctrl i2c controller instance pointer * @param[in] timeout timeout in milliseconds to wait (0 = wait forever) * * @return SDRV_I2CDRV_TRANS_OK * SDRV_I2CDRV_TRANS_GO * SDRV_I2CDRV_TRANS_ERR * SDRV_I2CDRV_TRANS_NAK * SDRV_I2CDRV_TRANS_TIMEOUT */ static status_t sdrv_i2c_pread(paddr_t base, uint32_t id, sdrv_i2cdrv_mctrl_t *mctrl, uint32_t timeout) { uint8_t len = 0; uint8_t flen = 0; const uint32_t retry_delay = 10; /* us */ uint64_t retry_times = (uint64_t)timeout * 1000 / retry_delay; uint64_t scnt = 100000UL / retry_delay; uint32_t int0_stat = 0; uint8_t *rbuf = mctrl->buf; uint16_t rlen = mctrl->len; while (rlen) { int0_stat = sdrv_i2c_get_int_stat(base, I2C_INTR0); if (int0_stat & SDRV_I2C_INT0_ERR_STAT) return sdrv_i2c_check_err(base, &mctrl->trans_errcode, id, int0_stat, 3); flen = (uint8_t)sdrv_i2c_get_rspace(base); if (flen > 0) { len = i2c_min(flen, rlen); rlen -= len; while (len--) *rbuf++ = sdrv_i2c_recv_data(base); } else if (timeout > 0) { if (retry_times--) { udelay(retry_delay); continue; } else { ssdk_printf(SSDK_CRIT, "i2c%u read retry time runs out\r\n", id); return SDRV_I2CDRV_TRANS_TIMEOUT; } } } if (timeout > 0) { if (retry_times > 0) scnt = retry_times; else scnt = 1; } while (scnt--) { int0_stat = sdrv_i2c_get_int_stat(base, I2C_INTR0); if (int0_stat & SDRV_I2C_INT0_APBCMDDONE) { return SDRV_I2CDRV_TRANS_OK; } else if (int0_stat & SDRV_I2C_INT0_ERR_STAT) { return sdrv_i2c_check_err(base, &mctrl->trans_errcode, id, int0_stat, 4); } else if (scnt <= 1) { ssdk_printf(SSDK_CRIT, "i2c%u prerr may hold by slave\r\n", id); return SDRV_I2CDRV_TRANS_TIMEOUT; } else udelay(retry_delay); } return SDRV_I2CDRV_TRANS_GO; } /** * @brief I2c transfer init * * @param[in] base i2c controller address * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ static status_t sdrv_i2c_xfer_init(paddr_t base) { int cnt = 100; sdrv_i2c_set_cmdcsr(base, I2C_CMDCSR0, 0xA5C3); sdrv_i2c_clear_fifo(base); do { if (sdrv_i2c_get_fifo_empty_stat(base)) return SDRV_STATUS_OK; udelay(10); } while (cnt--); return SDRV_STATUS_FAIL; } /** * @brief I2c driver real transfer function entry * init the controller with client configer info(speed/addr/len/need_stop...) * * @param[in] ctrl i2c controller instance pointer * @param[in] timeout timeout in milliseconds to wait (0 = wait forever) * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ static status_t sdrv_i2c_xfer(sdrv_i2c_t *ctrl, uint32_t timeout) { status_t ret; uint32_t int0_mask; sdrv_i2c_cfg_t *cfg = ctrl->cfg; sdrv_i2cdrv_mctrl_t *mctrl = &ctrl->mctrl; mctrl->trans_stat = SDRV_I2CDRV_TRANS_GO; mctrl->trans_errcode = 0; if (!ctrl->cfg->inited) { ssdk_printf(SSDK_CRIT, "i2c%u not init\r\n", ctrl->cfg->id); mctrl->trans_stat = SDRV_I2CDRV_TRANS_ABORT; return SDRV_STATUS_FAIL; } sdrv_i2c_disable_int(cfg->base); ret = sdrv_i2c_xfer_init(cfg->base); if (ret == SDRV_STATUS_FAIL) { ssdk_printf(SSDK_CRIT, "i2c%u xfer init fail\r\n", cfg->id); mctrl->trans_stat = SDRV_I2CDRV_TRANS_ABORT; return ret; } sdrv_i2c_clear_int(cfg->base); sdrv_i2c_set_cmdcsr(cfg->base, I2C_CMDCSR2, mctrl->slave_addr | (mctrl->read_flag << 11) | (mctrl->len << 16)); if (mctrl->need_stop) sdrv_i2c_set_cmdcsr(cfg->base, I2C_CMDCSR0, 0xA53C | (0x1 << 30)); else sdrv_i2c_set_cmdcsr(cfg->base, I2C_CMDCSR0, 0xA53C | (0x0 << 30)); if (cfg->tsmode == SDRV_I2CDRV_POLL) { if (mctrl->read_flag) mctrl->trans_stat = sdrv_i2c_pread(cfg->base, cfg->id, mctrl, timeout); else mctrl->trans_stat = sdrv_i2c_pwrite(cfg->base, cfg->id, mctrl, timeout); } else { if (mctrl->read_flag) int0_mask = SDRV_I2C_INT0_DEFAULT_MASK | SDRV_I2C_INT0_RXFWF; else int0_mask = SDRV_I2C_INT0_DEFAULT_MASK | SDRV_I2C_INT0_TXFWE; sdrv_i2c_unmask_int(cfg->base, I2C_INTEN0, int0_mask); } return SDRV_STATUS_OK; } /** * @brief I2c master mode interrupt handler for data send/receive and * transfer status check * * @param[in] irq i2c controller irq num * @param[in] arg i2c ctrl instance get from irq register * * @return 0 */ static int sdrv_i2c_master_irq_handler(uint32_t irq, void *arg) { uint16_t len = 0; uint16_t flen = 0; uint8_t *buf = NULL; uint32_t int0_stat = 0; sdrv_i2c_t *ctrl = arg; sdrv_i2c_cfg_t *cfg = ctrl->cfg; sdrv_i2cdrv_mctrl_t *mctrl = &ctrl->mctrl; int0_stat = sdrv_i2c_get_int_stat(cfg->base, I2C_INTR0); sdrv_i2c_clear_int_bits(cfg->base, I2C_INTR0, int0_stat); if (int0_stat & SDRV_I2C_INT0_APBCMDDONE) { mctrl->trans_stat = SDRV_I2CDRV_TRANS_OK; if (mctrl->cb) mctrl->cb(ctrl, mctrl->cb_param, mctrl->trans_stat); } else if (int0_stat & SDRV_I2C_INT0_ERR_STAT) { mctrl->trans_stat = sdrv_i2c_check_err(cfg->base, &mctrl->trans_errcode, cfg->id, int0_stat, 0); if (mctrl->cb) mctrl->cb(ctrl, mctrl->cb_param, mctrl->trans_stat); sdrv_i2c_disable_int(cfg->base); return 0; } buf = mctrl->buf + mctrl->cur_len; if (mctrl->read_flag) { flen = sdrv_i2c_get_rspace(cfg->base); len = i2c_min(flen, mctrl->len - mctrl->cur_len); mctrl->cur_len += len; while (len--) *buf++ = sdrv_i2c_recv_data(cfg->base); } else { flen = sdrv_i2c_get_wspace(cfg->base); len = i2c_min(flen, mctrl->len - mctrl->cur_len); mctrl->cur_len += len; while (len--) sdrv_i2c_send_data(cfg->base, *buf++); } if (mctrl->cur_len >= mctrl->len) sdrv_i2c_unmask_int(cfg->base, I2C_INTEN0, SDRV_I2C_INT0_DEFAULT_MASK); if (int0_stat & SDRV_I2C_INT0_APBCMDDONE) sdrv_i2c_disable_int(cfg->base); return 0; } /** * @brief Set bus stat(only applicable for master). * @this api will abandon,please use * sdrv_i2c_lock_bus/sdrv_i2c_unlock_bus instead * * @param[in] ctrl i2c controller instance pointer * @param[in] bus_stat bus stat to be set * * @return SDRV_STATUS_OK */ status_t sdrv_i2c_set_bus_stat(sdrv_i2c_t *ctrl, sdrv_i2cdrv_bus_stat_t bus_stat) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); ctrl->cfg->bus_stat = bus_stat; return SDRV_STATUS_OK; } /** * @brief Get bus stat(only applicable for master). * @this api will abandon,please use * sdrv_i2c_lock_bus/sdrv_i2c_unlock_bus instead * * @param[in] ctrl i2c controller instance pointer * * @return SDRV_I2CDRV_BUS_IDLE/SDRV_I2CDRV_BUS_BUSY */ sdrv_i2cdrv_bus_stat_t sdrv_i2c_get_bus_stat(sdrv_i2c_t *ctrl) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); return ctrl->cfg->bus_stat; } /** * @brief Lock bus stat(only applicable for master). * * @param[in] ctrl i2c controller instance pointer * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ status_t sdrv_i2c_lock_bus(sdrv_i2c_t *ctrl) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); irq_state_t irq_stat; status_t ret = SDRV_STATUS_FAIL; irq_stat = arch_irq_save(); if (ctrl->cfg->bus_stat == SDRV_I2CDRV_BUS_IDLE) { ctrl->cfg->bus_stat = SDRV_I2CDRV_BUS_BUSY; ret = SDRV_STATUS_OK; } arch_irq_restore(irq_stat); return ret; } /** * @brief Unlock bus stat(only applicable for master). * * @param[in] ctrl i2c controller instance pointer * * @return SDRV_STATUS_OK */ status_t sdrv_i2c_unlock_bus(sdrv_i2c_t *ctrl) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); irq_state_t irq_stat; irq_stat = arch_irq_save(); ctrl->cfg->bus_stat = SDRV_I2CDRV_BUS_IDLE; arch_irq_restore(irq_stat); return SDRV_STATUS_OK; } /** * @brief Init the i2c controller. * * @param[in] ctrl i2c controller instance pointer * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ status_t sdrv_i2c_init_ctrl(sdrv_i2c_t *ctrl) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); if (ctrl->cfg->inited == 1) return SDRV_STATUS_OK; sdrv_i2c_disable_int(ctrl->cfg->base); sdrv_i2c_clear_fifo(ctrl->cfg->base); sdrv_i2c_clear_int(ctrl->cfg->base); sdrv_i2c_reset(ctrl->cfg->base); sdrv_i2c_set_watermark(ctrl->cfg->base, SDRV_I2C_RXTX_WML); if (ctrl->cfg->speed == SDRV_I2CDRV_SSPEED) sdrv_i2c_set_timing(ctrl->cfg->base, ctrl->cfg->clk, 0); else if (ctrl->cfg->speed == SDRV_I2CDRV_FSPEED) sdrv_i2c_set_timing(ctrl->cfg->base, ctrl->cfg->clk, 1); else if (ctrl->cfg->speed == SDRV_I2CDRV_PSPEED) sdrv_i2c_set_timing(ctrl->cfg->base, ctrl->cfg->clk, 2); else if (ctrl->cfg->speed == SDRV_I2CDRV_HSPEED) sdrv_i2c_set_timing(ctrl->cfg->base, ctrl->cfg->clk, 3); else if (ctrl->cfg->speed == SDRV_I2CDRV_USPEED) sdrv_i2c_set_timing(ctrl->cfg->base, ctrl->cfg->clk, 4); if (ctrl->cfg->speed == SDRV_I2CDRV_HSPEED) sdrv_i2c_set_speed(ctrl->cfg->base, ctrl->cfg->clk, SDRV_I2C_HSPEED); else if (ctrl->cfg->speed == SDRV_I2CDRV_USPEED) sdrv_i2c_set_speed(ctrl->cfg->base, ctrl->cfg->clk, SDRV_I2C_USPEED); else sdrv_i2c_set_speed(ctrl->cfg->base, ctrl->cfg->clk, SDRV_I2C_NSPEED); if (ctrl->cfg->opmode == SDRV_I2CDRV_MASTER) { sdrv_i2c_set_opmode(ctrl->cfg->base, SDRV_I2C_MASTER); irq_attach(ctrl->cfg->irq, sdrv_i2c_master_irq_handler, ctrl); irq_enable(ctrl->cfg->irq); } else { ssdk_printf(SSDK_CRIT, "i2c%u opmode err\r\n", ctrl->cfg->id); return SDRV_STATUS_FAIL; } ctrl->cfg->inited = 1; return SDRV_STATUS_OK; } /** * @brief Write data to slave device use poll blocking format * * @param[in] ctrl i2c controller instance pointer * @param[in] addr slave device 7bit addr not include LSB read/write bit * @param[in] wbuf point to data buf for send * @param[in] wlen data buf len * @param[in] timeout timeout in milliseconds to wait (0 = wait forever) * @param[in] need_stop whether send stop signal or hold sda after finish write * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ status_t sdrv_i2c_write(sdrv_i2c_t *ctrl, uint16_t addr, uint8_t *wbuf, uint16_t wlen, uint32_t timeout, bool need_stop) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(wbuf != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); ctrl->mctrl.slave_addr = addr; ctrl->mctrl.buf = wbuf; ctrl->mctrl.len = wlen; ctrl->mctrl.read_flag = 0x0; ctrl->mctrl.need_stop = need_stop; ctrl->mctrl.cb = NULL; ctrl->mctrl.cur_len = 0; ctrl->cfg->tsmode = SDRV_I2CDRV_POLL; return sdrv_i2c_xfer(ctrl, timeout); } /** * @brief Read data from slave device use poll blocking format * * @param[in] ctrl i2c controller instance pointer * @param[in] addr slave device 7bit addr not include LSB read/write bit * @param[out] rbuf point to data buf for receive * @param[in] rlen data buf len * @param[in] timeout timeout in milliseconds to wait (0 = wait forever) * @param[in] need_stop whether send stop signal or hold sda after finish read * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ status_t sdrv_i2c_read(sdrv_i2c_t *ctrl, uint16_t addr, uint8_t *rbuf, uint16_t rlen, uint32_t timeout, bool need_stop) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(rbuf != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); ctrl->mctrl.slave_addr = addr; ctrl->mctrl.buf = rbuf; ctrl->mctrl.len = rlen; ctrl->mctrl.read_flag = 0x1; ctrl->mctrl.need_stop = need_stop; ctrl->mctrl.cb = NULL; ctrl->mctrl.cur_len = 0; ctrl->cfg->tsmode = SDRV_I2CDRV_POLL; return sdrv_i2c_xfer(ctrl, timeout); } /** * @brief Write data to slave device use interrupt non-blocking format * * @param[in] ctrl i2c controller instance pointer * @param[in] addr slave device 7bit addr not include LSB read/write bit * @param[in] wbuf point to data buf for send * @param[in] wlen data buf len * @param[in] need_stop whether send stop signal or hold sda after finish write * @param[in] cb callback function pointer * @param[in] cb_param callback user parameter * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ status_t sdrv_i2c_write_async(sdrv_i2c_t *ctrl, uint16_t addr, uint8_t *wbuf, uint16_t wlen, bool need_stop, sdrv_i2c_cb_t cb, void *cb_param) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(wbuf != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); ctrl->mctrl.slave_addr = addr; ctrl->mctrl.buf = wbuf; ctrl->mctrl.len = wlen; ctrl->mctrl.read_flag = 0x0; ctrl->mctrl.need_stop = need_stop; ctrl->mctrl.cb = cb; ctrl->mctrl.cb_param = cb_param; ctrl->mctrl.cur_len = 0; ctrl->cfg->tsmode = SDRV_I2CDRV_INTP; return sdrv_i2c_xfer(ctrl, 0); } /** * @brief Read data from slave device use interrupt non-blocking format * * @param[in] ctrl i2c controller instance pointer * @param[in] addr slave device 7bit addr not include LSB read/write bit * @param[out] rbuf point to data buf for receive * @param[in] rlen data buf len * @param[in] need_stop whether send stop signal or hold sda after complete read * @param[in] cb callback function pointer * @param[in] cb_param callback user parameter * * @return SDRV_STATUS_OK/SDRV_STATUS_FAIL */ status_t sdrv_i2c_read_async(sdrv_i2c_t *ctrl, uint16_t addr, uint8_t *rbuf, uint16_t rlen, bool need_stop, sdrv_i2c_cb_t cb, void *cb_param) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(rbuf != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); ctrl->mctrl.slave_addr = addr; ctrl->mctrl.buf = rbuf; ctrl->mctrl.len = rlen; ctrl->mctrl.read_flag = 0x1; ctrl->mctrl.need_stop = need_stop; ctrl->mctrl.cb = cb; ctrl->mctrl.cb_param = cb_param; ctrl->mctrl.cur_len = 0; ctrl->cfg->tsmode = SDRV_I2CDRV_INTP; return sdrv_i2c_xfer(ctrl, 0); } /** * @brief Get transfer stat(only applicable for master). * * @param[in] ctrl i2c controller instance pointer * * @return SDRV_I2CDRV_TRANS_OK * SDRV_I2CDRV_TRANS_GO * SDRV_I2CDRV_TRANS_ERR * SDRV_I2CDRV_TRANS_NAK * SDRV_I2CDRV_TRANS_ABORT * SDRV_I2CDRV_TRANS_TIMEOUT */ status_t sdrv_i2c_get_trans_stat(sdrv_i2c_t *ctrl) { ASSERT(ctrl != NULL); ASSERT(ctrl->cfg != NULL); ASSERT(ctrl->cfg->opmode == SDRV_I2CDRV_MASTER); return ctrl->mctrl.trans_stat; }