SPI接続のIOエキスパンダ(MCP23S09)を使う+ドライバ作成練習として
7セグ制御を行うドライバを書いてみた
0.前提
・カスタムファーム入りFON2405E
http://continue-to-challenge.blogspot.com/2018/02/fon2405eledeopenwrtspi.html
http://continue-to-challenge.blogspot.com/2018/02/fon2405eledeopenwrtspi144.html
・MCP23S09
3.3Vで動くSPI-IOエキスパンダ
https://www.microchip.com/wwwproducts/jp/MCP23S09
1.手順
(1)eclipceで空のプロジェクト作成
Makefile project → Empty Project
(2)ソースファイル・Makefile作成
ソースは末尾のgist参照、Makefileは
(3)ビルド
(4)ターゲットボードに転送
scpとかで
(5)動作確認
insmod /tmp/mcp23s09_7seg.ko
2.ソース
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* mcp23s09_7seg_drv.c | |
* | |
* Created on: 2018/06/03 | |
* Author: adeno | |
*/ | |
#include <linux/module.h> | |
#include <linux/moduleparam.h> | |
#include <linux/kernel.h> | |
#include <linux/cdev.h> | |
#include <linux/delay.h> | |
#include <linux/uaccess.h> //copy_from_user | |
#include <linux/gpio.h> | |
#include <linux/spi/spi.h> | |
MODULE_AUTHOR("Adeno"); | |
MODULE_LICENSE("GPL v2"); | |
//======================================================================================= | |
/* ドライバ */ | |
#define DRV_NUM_DEVS 1 /* このドライバが制御するデバイスの数 */ | |
#define DRV_DEVNAME "mcp7dDriver" /* このデバイスドライバの名称 */ | |
#define DRV_MAJOR 0 /* メジャー番号だが自動設定なので0 */ | |
#define DRV_MINOR 0 /* マイナー番号のベース番号 */ | |
#define DRV_GPIO_MAPNAME "mcp7d_gpio_map" | |
/* SPI MCP23S09設定 */ | |
#define MCP_PACKET_SIZE 3 | |
const uint8_t MCP23S09_OPCODE = 0b01000000; //dev:0100000 RW | |
const uint8_t MCP23S09_OPCODE_WRITE = 0b0; | |
const uint8_t MCP23S09_OPCODE_READ = 0b1; | |
const uint8_t MCP23S09_IODIR_REG = 0x00; | |
const uint8_t MCP23S09_GPPU_REG = 0x06; | |
const uint8_t MCP23S09_GPIO_REG = 0x09; | |
const uint8_t MCP23S09_OLAT_REG = 0x0A; | |
/* LED パターン */ | |
// DP A | |
#define LED7SEG_OFF 0b11111111 | |
#define LED7SEG_0 0b11000000 | |
#define LED7SEG_1 0b11111001 | |
#define LED7SEG_2 0b10100100 | |
#define LED7SEG_3 0b10110000 | |
#define LED7SEG_4 0b10011001 | |
#define LED7SEG_5 0b10010010 | |
#define LED7SEG_6 0b10000010 | |
#define LED7SEG_7 0b11111000 | |
#define LED7SEG_8 0b10000000 | |
#define LED7SEG_9 0b10010000 | |
#define LED7SEG_A 0b10001000 | |
#define LED7SEG_b 0b10000011 | |
#define LED7SEG_C 0b11000110 | |
#define LED7SEG_d 0b10100001 | |
#define LED7SEG_E 0b10000110 | |
#define LED7SEG_F 0b10001110 | |
#define LED7SEG_dot 0b01111111 | |
// hgfedcba | |
//const uint8_t LED10PTN[10] = {LED7SEG_0,LED7SEG_1,LED7SEG_2,LED7SEG_3,LED7SEG_4,LED7SEG_5, | |
// LED7SEG_6,LED7SEG_7,LED7SEG_8,LED7SEG_9}; | |
const uint8_t LED16PTN[16] = {LED7SEG_0,LED7SEG_1,LED7SEG_2,LED7SEG_3,LED7SEG_4,LED7SEG_5, | |
LED7SEG_6,LED7SEG_7,LED7SEG_8,LED7SEG_9, | |
LED7SEG_A,LED7SEG_b,LED7SEG_C,LED7SEG_d,LED7SEG_E,LED7SEG_F}; | |
//======================================================================================= | |
/* ドライバ */ | |
static int _mcp7d_major = DRV_MAJOR; | |
static int _mcp7d_minor = DRV_MINOR; | |
/* SPI */ | |
struct drvdata { | |
struct spi_device *spi; | |
struct mutex lock; | |
unsigned char tx[MCP_PACKET_SIZE]; | |
unsigned char rx[MCP_PACKET_SIZE]; | |
struct spi_transfer xfer; | |
struct spi_message msg; | |
dev_t md_dev; | |
struct class *drv_class; | |
struct cdev *drv_cdev_array; | |
} ____cacheline_aligned; | |
static struct drvdata mcp7d; | |
static struct spi_board_info mcp7d_info = { | |
.modalias = "mcp", | |
.max_speed_hz = 1000000, | |
.bus_num = 0, | |
.chip_select = 0, | |
.mode = SPI_MODE_3, | |
}; | |
static struct spi_device_id mcp_id[] = { | |
{ "mcp", 0 }, | |
{ }, | |
}; | |
MODULE_DEVICE_TABLE(spi, mcp_id); | |
/* Parameters Setting */ | |
static int gpiono = 9; | |
static int show_debug = 0; | |
static int hexmode = 0; | |
//======================================================================================= | |
/* プロトタイプ宣言 */ | |
static unsigned int mcp7d_set_value( struct drvdata *data,unsigned int v); | |
static void spi_remove_device(struct spi_master *master, unsigned int cs); | |
static int mcp7d_probe(struct spi_device *spi); | |
static int mcp7d_remove(struct spi_device *spi); | |
static int mcp7d_open(struct inode *inode, struct file *filep); | |
static int mcp7d_release(struct inode *inode, struct file *filep); | |
static ssize_t mcp7d_write(struct file *filep, const char __user *buf, size_t count, loff_t *f_pos); | |
static int mcp7d_register_dev(void); | |
static int mcp7d_init(void); | |
static void mcp7d_exit(void); | |
//======================================================================================= | |
/* SPIデバイス */ | |
static struct spi_driver mcp_driver = { | |
.driver = { | |
.name = DRV_DEVNAME, | |
.owner = THIS_MODULE, | |
}, | |
.id_table = mcp_id, | |
.probe = mcp7d_probe, | |
.remove = mcp7d_remove, | |
}; | |
/* キャラクタデバイス */ | |
struct file_operations mcp7d_fops = { | |
.open = mcp7d_open, | |
.release = mcp7d_release, | |
.write = mcp7d_write, | |
}; | |
//======================================================================================= | |
static unsigned int mcp7d_set_value( struct drvdata *data,unsigned int v) | |
{ | |
unsigned int r = 0; | |
mutex_lock( &data->lock ); | |
data->tx[0] = MCP23S09_OPCODE; | |
data->tx[1] = MCP23S09_OLAT_REG; | |
if((v >= 0) && (v < 16)) data->tx[2] = LED16PTN[v];//0xFF; //全て1 | |
else data->tx[2] = LED7SEG_OFF; | |
if( spi_sync( data->spi, &data->msg) ) { | |
if(show_debug) printk(KERN_INFO "spi_sync_transfer returned non zero\n" ); | |
} | |
mutex_unlock(&data->lock); | |
return r; | |
} | |
/** | |
* SPIデバイスの削除 | |
*/ | |
static void spi_remove_device(struct spi_master *master, unsigned int cs) | |
{ | |
struct device *dev; | |
char str[128]; | |
snprintf(str, sizeof(str), "%s.%u", dev_name(&master->dev), cs); | |
if(show_debug) printk(KERN_INFO "[spi_remove_device] %s\n",str); | |
// SPIデバイスを探す | |
dev = bus_find_device_by_name(&spi_bus_type, NULL, str); | |
// あったら削除 | |
if( dev ){ | |
if(show_debug) printk(KERN_INFO "Delete %s\n", str); | |
device_del(dev); | |
} | |
} | |
/** | |
* SPI通信初期化 | |
*/ | |
static int mcp7d_probe(struct spi_device *spi) | |
{ | |
struct mcp_drvdata *data; | |
int retval; | |
if(show_debug) printk(KERN_INFO "mcp7d spi probe\n"); | |
/* SPIを設定する */ | |
spi->max_speed_hz = mcp7d_info.max_speed_hz; | |
spi->mode = mcp7d_info.mode; | |
spi->bits_per_word = 8; | |
if( spi_setup( spi ) ) { | |
printk(KERN_ERR "spi_setup returned error\n"); | |
return -ENODEV; | |
} | |
mcp7d.spi = spi; | |
mutex_init( &mcp7d.lock ); | |
mcp7d.xfer.tx_buf = mcp7d.tx; | |
mcp7d.xfer.rx_buf = 0;//data->rx; | |
mcp7d.xfer.bits_per_word = 8; | |
mcp7d.xfer.len = MCP_PACKET_SIZE; | |
mcp7d.xfer.cs_change = 0; | |
mcp7d.xfer.delay_usecs = 0; | |
mcp7d.xfer.speed_hz = 1000000; | |
spi_message_init_with_transfers( &mcp7d.msg, &mcp7d.xfer, 1 ); | |
spi_set_drvdata( spi, &mcp7d ); | |
mutex_lock( &mcp7d.lock ); | |
mcp7d.tx[0] = MCP23S09_OPCODE; | |
mcp7d.tx[1] = MCP23S09_IODIR_REG; | |
mcp7d.tx[2] = 0x00; //全て出力 | |
if( spi_sync( mcp7d.spi, &mcp7d.msg) ) { | |
printk(KERN_INFO "spi_sync_transfer returned non zero\n" ); | |
} | |
mcp7d.tx[0] = MCP23S09_OPCODE; | |
mcp7d.tx[1] = MCP23S09_OLAT_REG; | |
mcp7d.tx[2] = LED7SEG_dot;//LED7SEG_OFF; //全て1 | |
if( spi_sync( mcp7d.spi, &mcp7d.msg) ) { | |
printk(KERN_INFO "spi_sync_transfer returned non zero\n" ); | |
} | |
mutex_unlock(&mcp7d.lock); | |
return 0; | |
} | |
/** | |
* SPI通信終了 | |
*/ | |
static int mcp7d_remove(struct spi_device *spi) | |
{ | |
if(show_debug) printk(KERN_INFO "[mcp7d spi_remove]\n"); | |
struct drvdata *drd = spi_get_drvdata(spi); | |
mutex_lock( &drd->lock ); | |
drd->tx[0] = MCP23S09_OPCODE; | |
drd->tx[1] = MCP23S09_IODIR_REG; | |
drd->tx[2] = 0x00; //全て出力 | |
if( spi_sync( drd->spi, &drd->msg) ) { | |
printk(KERN_INFO "spi_sync_transfer returned non zero\n" ); | |
} | |
drd->tx[0] = MCP23S09_OPCODE; | |
drd->tx[1] = MCP23S09_OLAT_REG; | |
drd->tx[2] = LED7SEG_OFF; //全て1 | |
if( spi_sync( drd->spi, &drd->msg) ) { | |
printk(KERN_INFO "spi_sync_transfer returned non zero\n" ); | |
} | |
mutex_unlock(&drd->lock); | |
spi_set_drvdata(drd->spi, NULL); | |
drd->spi = NULL; | |
mutex_destroy(&mcp7d.lock); | |
if(gpiono != 0) gpio_free(gpiono); | |
return 0; | |
} | |
/** | |
* デバイスオープン時の処理 | |
*/ | |
static int mcp7d_open(struct inode *inode, struct file *filep) | |
{ | |
if(show_debug) printk(KERN_INFO "[mcp7d_open]\n"); | |
filep->private_data = &mcp7d; | |
return 0; | |
} | |
/** | |
* デバイスクローズ時の処理 | |
*/ | |
static int mcp7d_release(struct inode *inode, struct file *filep) | |
{ | |
if(show_debug) printk(KERN_INFO "[mcp7d_release]\n"); | |
struct drvdata *drd = filep->private_data; | |
if ((drd == NULL) || (drd->spi == NULL)) | |
return -ENODEV; | |
filep->private_data = NULL; | |
return 0; | |
} | |
/** | |
* デバイス書き込み処理 | |
*/ | |
static ssize_t mcp7d_write(struct file *filep, const char __user *buf, size_t count, loff_t *f_pos) | |
{ | |
char cvalue; | |
int i; | |
struct drvdata *drd = filep->private_data; | |
if ((drd == NULL) || (drd->spi == NULL)) | |
return -ENODEV; | |
// struct mcp_drvdata *data = (struct mcp_drvdata *)dev_get_drvdata(filep->private_data); | |
if(count > 0) { | |
if(gpiono != 0) gpio_set_value(gpiono,1); | |
if(copy_from_user( &cvalue, buf, sizeof(char) )) { | |
return -EFAULT; | |
} | |
if((cvalue >=0) && (cvalue <= 9)) mcp7d_set_value(drd,cvalue); | |
else if(('0' <= cvalue) && (cvalue <= '9')) mcp7d_set_value(drd,cvalue - '0'); | |
else{ | |
if(hexmode){ | |
if(('A' <= cvalue) && (cvalue <= 'F')) mcp7d_set_value(drd,cvalue - 'A' + 10); | |
else if(('a' <= cvalue) && (cvalue <= 'f')) mcp7d_set_value(drd,cvalue - 'a' + 10); | |
} | |
else if(show_debug) printk(KERN_ALERT "Can not display %d\n", cvalue ); | |
} | |
if(gpiono != 0) gpio_set_value(gpiono,0); | |
return sizeof(char); | |
} | |
return 0; | |
} | |
/** | |
* デバイスドライバをカーネルに登録 | |
*/ | |
static int mcp7d_register_dev(void) | |
{ | |
int retval; | |
// dev_t dev; | |
size_t size; | |
int i; | |
/* 空いているメジャー番号を使ってメジャー& | |
マイナー番号をカーネルに登録する */ | |
retval = alloc_chrdev_region( | |
&mcp7d.md_dev, /* 結果を格納するdev_t構造体 */ | |
DRV_MINOR, /* ベースマイナー番号 */ | |
DRV_NUM_DEVS, /* デバイスの数 */ | |
DRV_DEVNAME /* デバイスドライバの名前 */ | |
); | |
if( retval < 0 ) { | |
printk(KERN_ERR "alloc_chrdev_region failed.\n" ); | |
return retval; | |
} | |
_mcp7d_major = MAJOR(mcp7d.md_dev); | |
/* デバイスクラスを作成する */ | |
mcp7d.drv_class = class_create(THIS_MODULE,DRV_DEVNAME); | |
if(IS_ERR(mcp7d.drv_class)) | |
return PTR_ERR(mcp7d.drv_class); | |
/* cdev構造体の用意 */ | |
size = sizeof(struct cdev) * DRV_NUM_DEVS; | |
mcp7d.drv_cdev_array = (struct cdev*)kmalloc(size, GFP_KERNEL); | |
/* デバイスの数だけキャラクタデバイスを登録する */ | |
/* ただし7セグLEDは1個しかない */ | |
for( i = 0; i < DRV_NUM_DEVS; i++ ) { | |
dev_t devno = MKDEV(_mcp7d_major, _mcp7d_minor+i); | |
/* キャラクタデバイスとしてこのモジュールをカーネルに登録する */ | |
cdev_init(&(mcp7d.drv_cdev_array[i]), &mcp7d_fops); | |
mcp7d.drv_cdev_array[i].owner = THIS_MODULE; | |
if( cdev_add( &(mcp7d.drv_cdev_array[i]), devno, 1) < 0 ) { | |
/* 登録に失敗した */ | |
printk(KERN_ERR "cdev_add failed minor = %d\n", _mcp7d_minor+i ); | |
} | |
else { | |
/* デバイスノードの作成 */ | |
device_create( | |
mcp7d.drv_class, | |
NULL, | |
devno, | |
NULL, | |
DRV_DEVNAME"%u",_mcp7d_minor+i | |
); | |
} | |
} | |
return 0; | |
} | |
/** | |
* モジュールの初期化処理 | |
*/ | |
static int mcp7d_init(void) | |
{ | |
int retval; | |
int i; | |
/* 開始のメッセージ */ | |
printk(KERN_INFO "%s loading...\n", DRV_DEVNAME ); | |
if(gpiono != 0){ | |
/* GPIOレジスタがマップ可能か調べる */ | |
retval = gpio_request_one(gpiono, GPIOF_OUT_INIT_LOW, DRV_GPIO_MAPNAME); | |
if (retval) { | |
printk(KERN_ERR "Unable to request GPIOs: %d\n", retval); | |
return retval; | |
} | |
for( i=0; i<10; i++ ) { | |
gpio_set_value(gpiono,1); | |
msleep(10); | |
gpio_set_value(gpiono,0); | |
msleep(10); | |
} | |
} | |
/* デバイスドライバをカーネルに登録 */ | |
retval = mcp7d_register_dev(); | |
if( retval != 0 ) { | |
printk( KERN_ALERT "mcp7d driver register failed.\n"); | |
return retval; | |
} | |
if(show_debug) | |
printk( KERN_INFO "mcp7d driver register sccessed.\n"); | |
struct spi_master *master; | |
struct spi_device *spi_device; | |
spi_register_driver(&mcp_driver); | |
mcp7d_info.bus_num = 0; | |
mcp7d_info.chip_select = 1; | |
master = spi_busnum_to_master(mcp7d_info.bus_num); | |
if( ! master ) { | |
printk( KERN_ERR "spi_busnum_to_master returned NULL\n"); | |
spi_unregister_driver(&mcp_driver); | |
return -ENODEV; | |
} | |
spi_remove_device(master, mcp7d_info.chip_select); | |
spi_device = spi_new_device( master, &mcp7d_info ); | |
if( !spi_device ) { | |
printk(KERN_ERR "spi_new_device returned NULL\n" ); | |
spi_unregister_driver(&mcp_driver); | |
return -ENODEV; | |
} | |
return 0; | |
} | |
/** | |
* モジュールの終了処理 | |
*/ | |
static void mcp7d_exit(void) | |
{ | |
int i; | |
dev_t devno; | |
if(show_debug) printk(KERN_INFO "[mcp7d_exit]\n"); | |
if(show_debug) printk(KERN_INFO "[cdev_del]\n"); | |
/* キャラクタデバイスの登録解除 */ | |
for( i = 0; i < DRV_NUM_DEVS; i++ ) { | |
cdev_del(&(mcp7d.drv_cdev_array[i])); | |
devno = MKDEV(_mcp7d_major, _mcp7d_minor+i); | |
device_destroy(mcp7d.drv_class, devno); | |
} | |
/* メジャー番号/マイナー番号を取り除く */ | |
devno = MKDEV(_mcp7d_major,_mcp7d_minor); | |
unregister_chrdev_region(devno, DRV_NUM_DEVS); | |
/* デバイスノードを取り除く */ | |
class_destroy( mcp7d.drv_class ); | |
kfree(mcp7d.drv_cdev_array); | |
if(show_debug) printk(KERN_INFO "[mcp_del]\n"); | |
spi_unregister_driver(&mcp_driver); | |
} | |
module_init(mcp7d_init); | |
module_exit(mcp7d_exit); | |
module_param( show_debug, int, S_IRUSR | S_IRGRP | S_IROTH ); | |
module_param( gpiono, int, S_IRUSR | S_IRGRP | S_IROTH ); | |
module_param( hexmode, int, S_IRUSR | S_IRGRP | S_IROTH ); | |
3.参考
・Raspberry Piで学ぶARMデバイスドライバープログラミング
・LINUXデバイスドライバ 第3版
・ci-bridge-spi
https://android.googlesource.com/kernel/msm/+/android-msm-hammerhead-3.4-kk-r1/drivers/misc/ci-bridge-spi.c
・spike
https://github.com/scottellis/spike/blob/master/spike.c