diff --git a/drivers/i2s/CMakeLists.txt b/drivers/i2s/CMakeLists.txt index a37c8c6c7a2..55dfbe20ef9 100644 --- a/drivers/i2s/CMakeLists.txt +++ b/drivers/i2s/CMakeLists.txt @@ -1,4 +1,6 @@ zephyr_library() +zephyr_library_sources(i2s_common.c) zephyr_library_sources_ifdef(CONFIG_I2S_SAM_SSC i2s_sam_ssc.c) zephyr_library_sources_ifdef(CONFIG_I2S_CAVS i2s_cavs.c) +zephyr_library_sources_ifdef(CONFIG_USERSPACE i2s_handlers.c) diff --git a/drivers/i2s/i2s_common.c b/drivers/i2s/i2s_common.c new file mode 100644 index 00000000000..2c2fe884b02 --- /dev/null +++ b/drivers/i2s/i2s_common.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +int _impl_i2s_buf_read(struct device *dev, void *buf, size_t *size) +{ + void *mem_block; + int ret; + + ret = i2s_read((struct device *)dev, &mem_block, size); + + if (!ret) { + struct i2s_config *rx_cfg = + i2s_config_get((struct device *)dev, I2S_DIR_RX); + + memcpy(buf, mem_block, *size); + k_mem_slab_free(rx_cfg->mem_slab, mem_block); + } + + return ret; +} + +int _impl_i2s_buf_write(struct device *dev, void *buf, size_t size) +{ + int ret; + struct i2s_config *tx_cfg; + void *mem_block; + + tx_cfg = i2s_config_get((struct device *)dev, I2S_DIR_TX); + if (!tx_cfg) { + return -EIO; + } + + if (size > tx_cfg->block_size) { + return -EINVAL; + } + + ret = k_mem_slab_alloc(tx_cfg->mem_slab, &mem_block, K_FOREVER); + if (ret < 0) { + return -ENOMEM; + } + + memcpy(mem_block, (void *)buf, size); + + return i2s_write((struct device *)dev, mem_block, size); +} diff --git a/drivers/i2s/i2s_handlers.c b/drivers/i2s/i2s_handlers.c new file mode 100644 index 00000000000..53fabdaf942 --- /dev/null +++ b/drivers/i2s/i2s_handlers.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +Z_SYSCALL_HANDLER(i2s_buf_read, dev, buf, size) +{ + void *mem_block; + size_t data_size; + int ret; + + Z_OOPS(Z_SYSCALL_DRIVER_I2S(dev, read)); + + ret = i2s_read((struct device *)dev, &mem_block, &data_size); + + if (!ret) { + struct i2s_config *rx_cfg; + int copy_success; + + /* Presumed to be configured otherwise the i2s_read() call + * would have failed. + */ + rx_cfg = i2s_config_get((struct device *)dev, I2S_DIR_RX); + + copy_success = z_user_to_copy((void *)buf, mem_block, + data_size); + + k_mem_slab_free(rx_cfg->mem_slab, mem_block); + Z_OOPS(copy_success); + Z_OOPS(z_user_to_copy((void *)size, &data_size, + sizeof(data_size))); + } + + return ret; +} + +Z_SYSCALL_HANDLER(i2s_buf_write, dev, buf, size) +{ + int ret; + struct i2s_config *tx_cfg; + void *mem_block; + + Z_OOPS(Z_SYSCALL_DRIVER_I2S(dev, write)); + tx_cfg = i2s_config_get((struct device *)dev, I2S_DIR_TX); + if (!tx_cfg) { + return -EIO; + } + + if (size > tx_cfg->block_size) { + return -EINVAL; + } + + ret = k_mem_slab_alloc(tx_cfg->mem_slab, &mem_block, K_FOREVER); + if (ret < 0) { + return -ENOMEM; + } + + ret = z_user_from_copy(mem_block, (void *)buf, size); + if (ret) { + k_mem_slab_free(tx_cfg->mem_slab, mem_block); + Z_OOPS(ret); + } + + return i2s_write((struct device *)dev, mem_block, size); +} + +Z_SYSCALL_HANDLER(i2s_trigger, dev, dir, cmd) +{ + Z_OOPS(Z_SYSCALL_DRIVER_I2S(dev, trigger)); + + return _impl_i2s_trigger((struct device *)dev, dir, cmd); +} diff --git a/drivers/i2s/i2s_sam_ssc.c b/drivers/i2s/i2s_sam_ssc.c index 0407765cea9..58910f380d6 100644 --- a/drivers/i2s/i2s_sam_ssc.c +++ b/drivers/i2s/i2s_sam_ssc.c @@ -508,6 +508,25 @@ static int bit_clock_set(Ssc *const ssc, u32_t bit_clk_freq) return 0; } +static struct i2s_config *i2s_sam_config_get(struct device *dev, + enum i2s_dir dir) +{ + struct i2s_sam_dev_data *const dev_data = DEV_DATA(dev); + struct stream *stream; + + if (dir == I2S_DIR_RX) { + stream = &dev_data->rx; + } else { + stream = &dev_data->tx; + } + + if (stream->state == I2S_STATE_NOT_READY) { + return NULL; + } + + return &stream->cfg; +} + static int i2s_sam_configure(struct device *dev, enum i2s_dir dir, struct i2s_config *i2s_cfg) { @@ -925,6 +944,7 @@ static int i2s_sam_initialize(struct device *dev) static const struct i2s_driver_api i2s_sam_driver_api = { .configure = i2s_sam_configure, + .config_get = i2s_sam_config_get, .read = i2s_sam_read, .write = i2s_sam_write, .trigger = i2s_sam_trigger, diff --git a/include/i2s.h b/include/i2s.h index cdc2d40e7ff..bb587a2422f 100644 --- a/include/i2s.h +++ b/include/i2s.h @@ -316,6 +316,8 @@ struct i2s_config { struct i2s_driver_api { int (*configure)(struct device *dev, enum i2s_dir dir, struct i2s_config *cfg); + struct i2s_config *(*config_get)(struct device *dev, + enum i2s_dir dir); int (*read)(struct device *dev, void **mem_block, size_t *size); int (*write)(struct device *dev, void *mem_block, size_t size); int (*trigger)(struct device *dev, enum i2s_dir dir, @@ -352,6 +354,22 @@ static inline int i2s_configure(struct device *dev, enum i2s_dir dir, return api->configure(dev, dir, cfg); } +/** + * @brief Fetch configuration information of a host I2S controller + * + * @param dev Pointer to the device structure for the driver instance + * @param dir Stream direction: RX or TX as defined by I2S_DIR_* + * @retval Pointer to the structure containing configuration parameters, + * or NULL if un-configured + */ +static inline struct i2s_config *i2s_config_get(struct device *dev, + enum i2s_dir dir) +{ + const struct i2s_driver_api *api = dev->driver_api; + + return api->config_get(dev, dir); +} + /** * @brief Read data from the RX queue. * @@ -383,13 +401,40 @@ static inline int i2s_configure(struct device *dev, enum i2s_dir dir, * @retval -EBUSY Returned without waiting. * @retval -EAGAIN Waiting period timed out. */ -static inline int i2s_read(struct device *dev, void **mem_block, size_t *size) +static inline int i2s_read(struct device *dev, void **mem_block, + size_t *size) { const struct i2s_driver_api *api = dev->driver_api; return api->read(dev, mem_block, size); } +/** + * @brief Read data from the RX queue into a provided buffer + * + * Data received by the I2S interface is stored in the RX queue consisting of + * memory blocks preallocated by this function from rx_mem_slab (as defined by + * i2s_configure). Calling this function removes one block from the queue + * which is copied into the provided buffer and then freed. + * + * The provided buffer must be large enough to contain a full memory block + * of data, which is parameterized for the channel via i2s_configure(). + * + * This function is otherwise equivalent to i2s_read(). + * + * @param dev Pointer to the device structure for the driver instance. + * @param buf Destination buffer for read data, which must be at least the + * as large as the configured memory block size for the RX channel. + * @param size Pointer to the variable storing the number of bytes read. + * + * @retval 0 If successful. + * @retval -EIO The interface is in NOT_READY or ERROR state and there are no + * more data blocks in the RX queue. + * @retval -EBUSY Returned without waiting. + * @retval -EAGAIN Waiting period timed out. + */ +__syscall int i2s_buf_read(struct device *dev, void *buf, size_t *size); + /** * @brief Write data to the TX queue. * @@ -423,6 +468,27 @@ static inline int i2s_write(struct device *dev, void *mem_block, size_t size) return api->write(dev, mem_block, size); } +/** + * @brief Write data to the TX queue from a provided buffer + * + * This function acquires a memory block from the I2S channel TX queue + * and copies the provided data buffer into it. It is otherwise equivalent + * to i2s_write(). + * + * @param dev Pointer to the device structure for the driver instance. + * @param buf Pointer to a buffer containing the data to transmit. + * @param size Number of bytes to write. This value has to be equal or smaller + * than the size of the channel's TX memory block configuration. + * + * @retval 0 If successful. + * @retval -EIO The interface is not in READY or RUNNING state. + * @retval -EBUSY Returned without waiting. + * @retval -EAGAIN Waiting period timed out. + * @retval -ENOMEM No memory in TX slab queue. + * @retval -EINVAL Size parameter larger than TX queue memory block. + */ +__syscall int i2s_buf_write(struct device *dev, void *buf, size_t size); + /** * @brief Send a trigger command. * @@ -436,14 +502,19 @@ static inline int i2s_write(struct device *dev, void *mem_block, size_t size) * channel cannot be allocated. * @retval -ENOMEM RX/TX memory block not available. */ -static inline int i2s_trigger(struct device *dev, enum i2s_dir dir, - enum i2s_trigger_cmd cmd) +__syscall int i2s_trigger(struct device *dev, enum i2s_dir dir, + enum i2s_trigger_cmd cmd); + +static inline int _impl_i2s_trigger(struct device *dev, enum i2s_dir dir, + enum i2s_trigger_cmd cmd) { const struct i2s_driver_api *api = dev->driver_api; return api->trigger(dev, dir, cmd); } +#include + #ifdef __cplusplus } #endif