diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c850aaa --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +KERNELDIR := /usr/src/linux-headers-6.1.21-v8+ +MODULE_NAME=ads1278 + +SRC := ads1278_core.c + +ifneq ($(KERNELRELEASE),) + $(MODULE_NAME)-objs = $(SRC:.c=.o) + obj-m := $(MODULE_NAME).o +else + PWD := $(shell pwd) + +default: +ifeq ($(strip $(KERNELDIR)),) + $(error "KERNELDIR is undefined!") +else + $(MAKE) -C $(KERNELDIR) scripts + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules +endif + +clean: + rm -rf *~ *.ko *.o *.mod.c modules.order Module.symvers .${MODULE_NAME}* .tmp_versions + +endif diff --git a/ads1278-fs.dts b/ads1278-fs.dts new file mode 100644 index 0000000..abdab95 --- /dev/null +++ b/ads1278-fs.dts @@ -0,0 +1,43 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + ads1278: ads1278@0 { + compatible = "ti,ads1278-fs"; + status = "okay"; + reg = <0>; + pinctrl-names = "default"; + }; + }; + }; +}; + diff --git a/ads1278_core.c b/ads1278_core.c new file mode 100644 index 0000000..e47f0c7 --- /dev/null +++ b/ads1278_core.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include + +#define FRAME_SIZE (24) +#define TRANSFER_SIZE (FRAME_SIZE * 10) +// 27'648'000 Hz = 144 kSPS +// 25'000'000 Hz = 130 kSPS +// 10'000'000 Hz = 52 kSPS +#define TRANSFER_HZ (27000000) + +// TODO: minimize 45us delay between xfers + +// 24-11-23: We're hitting 130 kSPS initially, but are eventually slowed down +// to 87 kSPS. Need to investigate high-priority workers, forcing a high SPI +// clock speed, and/or minimizing overall latency. + +struct ads1278 +{ + unsigned char *rx; + unsigned char *tx; + dma_addr_t rx_dma_handle; + dma_addr_t tx_dma_handle; + struct spi_message msg[2]; + struct spi_transfer tr[2]; + int stop_requested; + int running[2]; +}; + +static void ads1278_transfer_callback1(void *context) +{ + struct spi_device *spi = (struct spi_device *)context; + struct ads1278 *ads = spi_get_drvdata(spi); + + if (!ads->stop_requested) + spi_async(spi, &ads->msg[0]); + else + ads->running[0] = 0; +} + +static void ads1278_transfer_callback2(void *context) +{ + struct spi_device *spi = (struct spi_device *)context; + struct ads1278 *ads = spi_get_drvdata(spi); + + if (!ads->stop_requested) + spi_async(spi, &ads->msg[1]); + else + ads->running[1] = 0; +} + + +static int ads1278_build_transfers(struct spi_device *spi) +{ + struct ads1278 *ads = spi_get_drvdata(spi); + int i; + + dma_set_coherent_mask(&spi->dev, 0xFFFFFFFF); + + ads->tx = (unsigned char *)dma_alloc_coherent(&spi->dev, TRANSFER_SIZE * 2, + &ads->tx_dma_handle, GFP_KERNEL); + if (!ads->tx) + return -1; + + ads->rx = (unsigned char *)dma_alloc_coherent(&spi->dev, TRANSFER_SIZE * 2, + &ads->rx_dma_handle, GFP_KERNEL); + if (!ads->rx) + return -1; + + memset(ads->tr, 0, sizeof(ads->tr)); + memset(ads->tx, 0, TRANSFER_SIZE * 2); + memset(ads->rx, 0, TRANSFER_SIZE * 2); + + for (i = 0; i < TRANSFER_SIZE * 2; i += FRAME_SIZE) { + ads->tx[i] = 0xC0; + } + + ads->tr[0].tx_buf = ads->tx; + ads->tr[0].rx_buf = ads->rx; + ads->tr[0].len = TRANSFER_SIZE; + ads->tr[0].cs_off = 1; + ads->tr[0].bits_per_word = 8; + ads->tr[0].speed_hz = TRANSFER_HZ; + + ads->tr[1].tx_buf = ads->tx + TRANSFER_SIZE; + ads->tr[1].rx_buf = ads->rx + TRANSFER_SIZE; + ads->tr[1].len = TRANSFER_SIZE; + ads->tr[1].cs_off = 1; + ads->tr[1].bits_per_word = 8; + ads->tr[1].speed_hz = TRANSFER_HZ; + + return 0; +} + +static int ads1278_begin_transfer(struct spi_device *spi) +{ + struct ads1278 *ads = spi_get_drvdata(spi); + + if (!ads) + return -1; + + if (ads1278_build_transfers(spi)) { + dev_err(&spi->dev, "error with building transfer!"); + return -1; + } + + spi_message_init_with_transfers(&ads->msg[0], &ads->tr[0], 1); + spi_message_init_with_transfers(&ads->msg[1], &ads->tr[1], 1); + + ads->msg[0].complete = ads1278_transfer_callback1; + ads->msg[1].complete = ads1278_transfer_callback2; + ads->msg[0].context = spi; + ads->msg[1].context = spi; + //spi_optimize_message(spi, msg); + + ads->stop_requested = 0; + ads->running[0] = 1; + ads->running[1] = 1; + + /* TODO ret = */ spi_async(spi, &ads->msg[0]); + return spi_async(spi, &ads->msg[1]); +} + +static int ads1278_probe(struct spi_device *spi) +{ + struct ads1278 *ads; + + dev_info(&spi->dev, "probing..."); + + ads = kzalloc(sizeof(*ads), GFP_KERNEL); + if (!ads) + return -ENOMEM; + spi_set_drvdata(spi, ads); + + spi->max_speed_hz = TRANSFER_HZ; + spi->mode = 1; + spi->bits_per_word = 8; + spi->rt = 1; + spi->word_delay.value = 0; + spi->cs_setup.value = 0; + spi->cs_hold.value = 0; + spi->cs_inactive.value = 0; + if (spi_setup(spi)) { + dev_err(&spi->dev, "error with spi setup!"); + goto end_free_ads; + } + + dev_info(&spi->dev, "ready to go!"); + + if (ads1278_begin_transfer(spi)) { + dev_err(&spi->dev, "error with initial transfer!"); + goto end_free_ads; + } + + return 0; + +end_free_ads: + kfree(ads); + + return -1; +} + +static void ads1278_remove(struct spi_device *spi) +{ + struct ads1278 *ads = spi_get_drvdata(spi); + int i; + + dev_info(&spi->dev, "cleaning up..."); + + if (ads) { + ads->stop_requested = 1; + + for (i = 0; i < 100; i++) { + if (ads->running[0] <= 0 && ads->running[1] <= 0) + break; + + mdelay(10); + } + + //spi_unoptimize_message(&ads->msg); + + dma_free_coherent(&spi->dev, TRANSFER_SIZE * 2, ads->tx, ads->tx_dma_handle); + dma_free_coherent(&spi->dev, TRANSFER_SIZE * 2, ads->rx, ads->rx_dma_handle); + + kfree(ads); + } + + dev_info(&spi->dev, "done."); +} + +static struct of_device_id ads1278_id_table[] = { + { .compatible = "ti,ads1278-fs", }, + {} +}; +MODULE_DEVICE_TABLE(spi, ads1278_id_table); + +static struct spi_driver ads1278_spi_driver = +{ + .driver = { + .name = "ti,ads1278-fs", + .of_match_table = ads1278_id_table, + }, + + .probe = ads1278_probe, + .remove = ads1278_remove, +}; +module_spi_driver(ads1278_spi_driver); + +MODULE_DESCRIPTION("SPI/Frame-sync protocol driver for ADS1278"); +MODULE_AUTHOR("Clyne Sullivan