AT91SAM9x25EK システム オン チップの SPI バス上のチップからカウントを読み取る Linux カーネル モジュールに取り組んでいます。しかし、私はいくつかの異常な動作を見つけています。チップは、次のような数値のセットを返す必要があります。
170、172、172、172、170、173、173、173、170、174、174、174、170、175、175、175など
基本的に、170 の後に 3 バイトが表示され、読み取りごとに増加します。
これは PIO では問題なく機能しますが、実用的ではありません。200us ごとに 4 バイトを読み取る必要があり、CPU 全体が拘束されます。そのため、DMA が最適です。
ただし、DMA には 2 つの問題があります。最初は atmel_spi.c から来ます。ファイルの上部には次のように書かれています。
#if defined(CONFIG_SPI_ATMEL_DMA)
/* use PIO for small transfers, avoiding DMA setup/teardown overhead and
* cache operations; better heuristics consider wordsize and bitrate.
*/
#define DMA_MIN_BYTES 16
明らかに、必要な 4 バイトは DMA の最小要件を下回っています。これは、チップから一度に 16 バイトを読み取るか、この #define を 4 に変更する必要があることを意味します。どちらの場合も、結果は同じです。DMA コントローラをアクティブにすると、CPU 使用率が半分以下に削減されますが、結果が台無しになります。さて、私のチップは次のようなものを吐き出します:
170, 0, 0, 0, 170, 0, 0, 0, 170, 0, 0, 0, 170, 0, 0, 0
私の 170 マーカーはまだそこにありますが、他の読み取りはすべて 0 を返します。正しいバイト数を読み取ったかどうかは問題ではないようです.DMAコントローラーを使用するたびに、すべての値が0になり、完全に困惑しています.
何がうまくいかなかったのか、または DMA 全体の問題を完全に回避する方法を知っている人がいれば、ぜひ試してみてください。200us は難しい要件ですが、実装の残りの部分で余裕があるかもしれません。
これは、カーネル バッファーと DMA を設定する私の init 関数です。
static int __init quicklogic_init_spi(void)
{
int error;
quicklogic_ctl.tx_buff = kmalloc(4, GFP_KERNEL | GFP_DMA);
if (!quicklogic_ctl.tx_buff) {
error = -ENOMEM;
goto quicklogic_init_error;
}
memset(quicklogic_ctl.tx_buff, 0, 4);
quicklogic_ctl.rx_buff = kmalloc(SPI_BUFF_SIZE, GFP_KERNEL | GFP_DMA);
if (!quicklogic_ctl.rx_buff) {
error = -ENOMEM;
goto quicklogic_init_error;
}
memset(quicklogic_ctl.rx_buff, 9, SPI_BUFF_SIZE);
/* configure DMA recieve buffer */
quicklogic_ctl.rx_dma = dma_map_single(&quicklogic_dev.spi_device->dev,
(void*)quicklogic_ctl.rx_buff, SPI_BUFF_SIZE, DMA_FROM_DEVICE);
quicklogic_ctl.tx_dma = dma_map_single(&quicklogic_dev.spi_device->dev,
(void*)quicklogic_ctl.tx_buff, 4, DMA_TO_DEVICE);
/*Tell the driver we want DMA */
quicklogic_ctl.msg.is_dma_mapped = 1;
error = spi_register_driver(&quicklogic_driver);
if (error < 0) {
printk(KERN_ALERT "spi_register_driver() failed %d\n", error);
goto quicklogic_init_error;
}
error = add_quicklogic_device_to_bus();
if (error < 0) {
printk(KERN_ALERT "add_quicklogic_to_bus() failed\n");
spi_unregister_driver(&quicklogic_driver);
goto quicklogic_init_error;
}
quicklogic_prepare_spi_message();
/*my messages are always the same, so set this up only once*/
return 0;
quicklogic_init_error:
if (quicklogic_ctl.tx_buff) {
kfree(quicklogic_ctl.tx_buff);
quicklogic_ctl.tx_buff = 0;
}
if (quicklogic_ctl.rx_buff) {
kfree(quicklogic_ctl.rx_buff);
quicklogic_ctl.rx_buff = 0;
}
return error;
}
また、これは SPI 転送を設定する私の関数です。基本的に、大量の転送セットを 1 つのメッセージにキューイングします。
#define SPI_TRANSFERS 150528
#define SPI_MSG_LEN 4
#define SPI_MSG_DELAY 200 /*in microseconds*/
#define SPI_BUFF_SIZE 602112 /*can hold 150528 four byte transfers (30 seconds)*/
#define USER_BUFF_SIZE 602112
const char this_driver_name[] = "quicklogic";
struct quicklogic_control {
struct spi_message msg;
struct spi_transfer transfer[SPI_TRANSFERS];
u8 *tx_buff;
u8 *rx_buff;
dma_addr_t tx_dma;
dma_addr_t rx_dma;
};
static struct quicklogic_control quicklogic_ctl;
struct quicklogic_dev {
struct semaphore spi_sem;
struct semaphore fop_sem;
dev_t devt;
struct cdev cdev;
struct class *class;
struct spi_device *spi_device;
char *user_buff;
u8 test_data;
};
static struct quicklogic_dev quicklogic_dev;
static void quicklogic_prepare_spi_message(void)
{
int i;
spi_message_init(&quicklogic_ctl.msg);
for (i=0; i<SPI_TRANSFERS; i++) {
quicklogic_ctl.transfer[i].tx_buf = quicklogic_ctl.tx_buff;
quicklogic_ctl.transfer[i].rx_buf = quicklogic_ctl.rx_buff + (i * SPI_MSG_LEN);
quicklogic_ctl.transfer[i].len = SPI_MSG_LEN;
quicklogic_ctl.transfer[i].delay_usecs = SPI_MSG_DELAY;
spi_message_add_tail(&quicklogic_ctl.transfer[i], &quicklogic_ctl.msg);
}
}