| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * 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 <errno.h> |
| #include <heap.h> |
| #include <string.h> |
| |
| #include <cpu.h> |
| #include <spi.h> |
| #include <spi_priv.h> |
| #include <timer.h> |
| |
| #define INFO_PRINT(fmt, ...) do { \ |
| osLog(LOG_INFO, "%s " fmt, "[spi]", ##__VA_ARGS__); \ |
| } while (0); |
| |
| #define ERROR_PRINT(fmt, ...) do { \ |
| osLog(LOG_ERROR, "%s " fmt, "[spi] ERROR:", ##__VA_ARGS__); \ |
| } while (0); |
| |
| struct SpiDeviceState { |
| struct SpiDevice dev; |
| |
| const struct SpiPacket *packets; |
| size_t n; |
| size_t currentBuf; |
| struct SpiMode mode; |
| uint16_t tid; |
| |
| SpiCbkF rxTxCallback; |
| void *rxTxCookie; |
| |
| SpiCbkF finishCallback; |
| void *finishCookie; |
| |
| int err; |
| }; |
| #define SPI_DEVICE_TO_STATE(p) ((struct SpiDeviceState *)p) |
| |
| static void spiMasterNext(struct SpiDeviceState *state); |
| static void spiMasterStop(struct SpiDeviceState *state); |
| static void spiMasterDone(struct SpiDeviceState *state, int err); |
| |
| static void spiSlaveNext(struct SpiDeviceState *state); |
| static void spiSlaveIdle(struct SpiDeviceState *state, int err); |
| static void spiSlaveDone(struct SpiDeviceState *state); |
| |
| static int spiMasterStart(struct SpiDeviceState *state, |
| spi_cs_t cs, const struct SpiMode *mode) |
| { |
| struct SpiDevice *dev = &state->dev; |
| |
| if (dev->ops->masterStartAsync) |
| return dev->ops->masterStartAsync(dev, cs, mode); |
| |
| if (dev->ops->masterStartSync) { |
| int err = dev->ops->masterStartSync(dev, cs, mode); |
| if (err < 0) |
| return err; |
| } |
| |
| return dev->ops->masterRxTx(dev, state->packets[0].rxBuf, |
| state->packets[0].txBuf, state->packets[0].size, mode); |
| } |
| |
| void spi_masterStartAsync_done(struct SpiDevice *dev, int err) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| if (err) |
| spiMasterDone(state, err); |
| else |
| spiMasterNext(state); |
| } |
| |
| static void spiDelayCallback(uint32_t timerId, void *data) |
| { |
| spiMasterNext((struct SpiDeviceState *)data); |
| } |
| |
| static void spiMasterNext(struct SpiDeviceState *state) |
| { |
| struct SpiDevice *dev = &state->dev; |
| |
| if (state->currentBuf == state->n) { |
| spiMasterStop(state); |
| return; |
| } |
| |
| size_t i = state->currentBuf; |
| void *rxBuf = state->packets[i].rxBuf; |
| const void *txBuf = state->packets[i].txBuf; |
| size_t size = state->packets[i].size; |
| const struct SpiMode *mode = &state->mode; |
| |
| int err = dev->ops->masterRxTx(dev, rxBuf, txBuf, size, mode); |
| if (err) |
| spiMasterDone(state, err); |
| } |
| |
| void spiMasterRxTxDone(struct SpiDevice *dev, int err) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| if (err) { |
| spiMasterDone(state, err); |
| } else { |
| size_t i = state->currentBuf++; |
| |
| if (state->packets[i].delay > 0) { |
| if (!timTimerSet(state->packets[i].delay, 0, 50, spiDelayCallback, state, true)) { |
| ERROR_PRINT("Cannot do delayed spi, timer depleted\n"); |
| spiMasterDone(state, -ENOMEM); // should be out of timer; out of mem is close enough |
| } |
| } else { |
| spiMasterNext(state); |
| } |
| } |
| } |
| |
| static void spiMasterStop(struct SpiDeviceState *state) |
| { |
| struct SpiDevice *dev = &state->dev; |
| |
| if (dev->ops->masterStopSync) { |
| int err = dev->ops->masterStopSync(dev); |
| spiMasterDone(state, err); |
| } else if (dev->ops->masterStopAsync) { |
| int err = dev->ops->masterStopAsync(dev); |
| if (err < 0) |
| spiMasterDone(state, err); |
| } else { |
| spiMasterDone(state, 0); |
| } |
| } |
| |
| void spiMasterStopAsyncDone(struct SpiDevice *dev, int err) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| spiMasterDone(state, err); |
| } |
| |
| static void spiMasterDone(struct SpiDeviceState *state, int err) |
| { |
| SpiCbkF callback = state->rxTxCallback; |
| void *cookie = state->rxTxCookie; |
| |
| uint16_t oldTid = osSetCurrentTid(state->tid); |
| callback(cookie, err); |
| osSetCurrentTid(oldTid); |
| } |
| |
| static int spiSlaveStart(struct SpiDeviceState *state, |
| const struct SpiMode *mode) |
| { |
| struct SpiDevice *dev = &state->dev; |
| |
| if (dev->ops->slaveStartAsync) |
| return dev->ops->slaveStartAsync(dev, mode); |
| |
| if (dev->ops->slaveStartSync) { |
| int err = dev->ops->slaveStartSync(dev, mode); |
| if (err < 0) |
| return err; |
| } |
| |
| return dev->ops->slaveIdle(dev, mode); |
| } |
| |
| void spiSlaveStartAsyncDone(struct SpiDevice *dev, int err) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| |
| if (err) |
| state->err = err; |
| else |
| state->err = dev->ops->slaveIdle(dev, &state->mode); |
| } |
| |
| void spiSlaveRxTxDone(struct SpiDevice *dev, int err) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| |
| if (err) { |
| spiSlaveIdle(state, err); |
| } else { |
| state->currentBuf++; |
| spiSlaveNext(state); |
| } |
| } |
| |
| void spiSlaveCsInactive(struct SpiDevice *dev) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| |
| dev->ops->slaveSetCsInterrupt(dev, false); |
| |
| if (!state->finishCallback) { |
| osLog(LOG_WARN, "%s called without callback\n", __func__); |
| return; |
| } |
| |
| SpiCbkF callback = state->finishCallback; |
| void *cookie = state->finishCookie; |
| state->finishCallback = NULL; |
| state->finishCookie = NULL; |
| |
| uint16_t oldTid = osSetCurrentTid(state->tid); |
| callback(cookie, 0); |
| osSetCurrentTid(oldTid); |
| } |
| |
| static void spiSlaveNext(struct SpiDeviceState *state) |
| { |
| struct SpiDevice *dev = &state->dev; |
| |
| if (state->currentBuf == state->n) { |
| spiSlaveIdle(state, 0); |
| return; |
| } |
| |
| size_t i = state->currentBuf; |
| void *rxBuf = state->packets[i].rxBuf; |
| const void *txBuf = state->packets[i].txBuf; |
| size_t size = state->packets[i].size; |
| const struct SpiMode *mode = &state->mode; |
| |
| int err = dev->ops->slaveRxTx(dev, rxBuf, txBuf, size, mode); |
| if (err) |
| spiSlaveIdle(state, err); |
| } |
| |
| static void spiSlaveIdle(struct SpiDeviceState *state, int err) |
| { |
| struct SpiDevice *dev = &state->dev; |
| SpiCbkF callback = state->rxTxCallback; |
| void *cookie = state->rxTxCookie; |
| |
| if (!err) |
| err = dev->ops->slaveIdle(dev, &state->mode); |
| |
| uint16_t oldTid = osSetCurrentTid(state->tid); |
| callback(cookie, err); |
| osSetCurrentTid(oldTid); |
| } |
| |
| void spiSlaveStopAsyncDone(struct SpiDevice *dev, int err) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| spiSlaveDone(state); |
| } |
| |
| static void spiSlaveDone(struct SpiDeviceState *state) |
| { |
| struct SpiDevice *dev = &state->dev; |
| |
| if (dev->ops->release) |
| dev->ops->release(dev); |
| heapFree(state); |
| } |
| |
| static int spiSetupRxTx(struct SpiDeviceState *state, |
| const struct SpiPacket packets[], size_t n, |
| SpiCbkF callback, void *cookie) |
| { |
| state->packets = packets; |
| state->n = n; |
| state->currentBuf = 0; |
| state->rxTxCallback = callback; |
| state->rxTxCookie = cookie; |
| state->tid = osGetCurrentTid(); |
| |
| return 0; |
| } |
| |
| int spiMasterRequest(uint8_t busId, struct SpiDevice **dev_out) |
| { |
| int ret = 0; |
| |
| struct SpiDeviceState *state = heapAlloc(sizeof(*state)); |
| if (!state) |
| return -ENOMEM; |
| struct SpiDevice *dev = &state->dev; |
| |
| ret = spiRequest(dev, busId); |
| if (ret < 0) |
| goto err_request; |
| |
| if (!dev->ops->masterRxTx) { |
| ret = -EOPNOTSUPP; |
| goto err_opsupp; |
| } |
| |
| *dev_out = dev; |
| return 0; |
| |
| err_opsupp: |
| if (dev->ops->release) |
| dev->ops->release(dev); |
| err_request: |
| heapFree(state); |
| return ret; |
| } |
| |
| int spiMasterRxTx(struct SpiDevice *dev, spi_cs_t cs, |
| const struct SpiPacket packets[], size_t n, |
| const struct SpiMode *mode, SpiCbkF callback, |
| void *cookie) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| int ret = 0; |
| |
| if (!n) |
| return -EINVAL; |
| |
| ret = spiSetupRxTx(state, packets, n, callback, cookie); |
| if (ret < 0) |
| return ret; |
| |
| state->mode = *mode; |
| |
| return spiMasterStart(state, cs, mode); |
| } |
| |
| int spiMasterRelease(struct SpiDevice *dev) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| |
| if (dev->ops->release) { |
| int ret = dev->ops->release(dev); |
| if (ret < 0) |
| return ret; |
| } |
| |
| heapFree(state); |
| return 0; |
| } |
| |
| int spiSlaveRequest(uint8_t busId, const struct SpiMode *mode, |
| struct SpiDevice **dev_out) |
| { |
| int ret = 0; |
| |
| struct SpiDeviceState *state = heapAlloc(sizeof(*state)); |
| if (!state) |
| return -ENOMEM; |
| struct SpiDevice *dev = &state->dev; |
| |
| ret = spiRequest(dev, busId); |
| if (ret < 0) |
| goto err_request; |
| |
| if (!dev->ops->slaveIdle || !dev->ops->slaveRxTx) { |
| ret = -EOPNOTSUPP; |
| goto err_opsupp; |
| } |
| |
| state->mode = *mode; |
| state->err = 0; |
| |
| ret = spiSlaveStart(state, mode); |
| if (ret < 0) |
| goto err_opsupp; |
| |
| *dev_out = dev; |
| return 0; |
| |
| err_opsupp: |
| if (dev->ops->release) |
| dev->ops->release(dev); |
| err_request: |
| heapFree(state); |
| return ret; |
| } |
| |
| int spiSlaveRxTx(struct SpiDevice *dev, |
| const struct SpiPacket packets[], size_t n, |
| SpiCbkF callback, void *cookie) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| |
| if (!n) |
| return -EINVAL; |
| |
| if (state->err) |
| return state->err; |
| |
| int ret = spiSetupRxTx(state, packets, n, callback, cookie); |
| if (ret < 0) |
| return ret; |
| |
| return dev->ops->slaveRxTx(dev, state->packets[0].rxBuf, |
| state->packets[0].txBuf, state->packets[0].size, &state->mode); |
| } |
| |
| int spiSlaveWaitForInactive(struct SpiDevice *dev, SpiCbkF callback, |
| void *cookie) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| |
| if (!dev->ops->slaveSetCsInterrupt || !dev->ops->slaveCsIsActive) |
| return -EOPNOTSUPP; |
| |
| state->finishCallback = callback; |
| state->finishCookie = cookie; |
| |
| uint64_t flags = cpuIntsOff(); |
| dev->ops->slaveSetCsInterrupt(dev, true); |
| |
| /* CS may already be inactive before enabling the interrupt. In this case |
| * roll back and fire the callback immediately. |
| * |
| * Interrupts must be off while checking for this. Otherwise there is a |
| * (very unlikely) race where the CS interrupt fires between calling |
| * slaveSetCsInterrupt(true) and the rollback |
| * slaveSetCsInterrupt(false), causing the event to be handled twice. |
| * |
| * Likewise the check must come after enabling the interrupt. Otherwise |
| * there is an (also unlikely) race where CS goes inactive between reading |
| * CS and enabling the interrupt, causing the event to be lost. |
| */ |
| |
| bool cs = dev->ops->slaveCsIsActive(dev); |
| if (!cs) { |
| dev->ops->slaveSetCsInterrupt(dev, false); |
| cpuIntsRestore(flags); |
| |
| state->finishCallback = NULL; |
| state->finishCookie = NULL; |
| callback(cookie, 0); |
| return 0; |
| } |
| |
| cpuIntsRestore(flags); |
| return 0; |
| } |
| |
| int spiSlaveRelease(struct SpiDevice *dev) |
| { |
| struct SpiDeviceState *state = SPI_DEVICE_TO_STATE(dev); |
| int ret; |
| |
| if (dev->ops->slaveStopSync) { |
| ret = dev->ops->slaveStopSync(dev); |
| if (ret < 0) |
| return ret; |
| } else if (dev->ops->slaveStopAsync) { |
| return dev->ops->slaveStopAsync(dev); |
| } |
| |
| spiSlaveDone(state); |
| return 0; |
| } |