2018年6月3日日曜日

MCP23S09制御用のFON2405E(LEDE・Openwrt)向けドライバ作成

気づけば、毎年同じようなことをやっているような気がするけど。。。
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.ソース

/*
* 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 );
view raw mcp23s09_7seg.c hosted with ❤ by GitHub

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