AM335X——hwmon和input子系统

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/01/18/AM335X——hwmon和input子系统/#more

记录两个SPI设备分别采用hwmon子系统和input子系统。

刚开始学Linux驱动的时候,就看了input子系统,现在都忘得差不多了,不过回忆起来也还快,这里再记录一下。

为什么要用各种子系统框架,就目前的理解,一是为了向应用层提供统一的接口,二是简化了编写驱动的流程
各种子系统它们是通过一层一层的函数传递与封装,实现了设备驱动的注册,定义了file_operations结构体里面的各种函数操作,不需要在单独的设备驱动代码中进行注册、定义,直接调用相应的的子系统即可。

1. hwmon子系统简介

hwmon即硬件监控(Hardware monitor),它是用于检测设备状态的一类传感器设备接口,比如CPU温度、风扇转速、模数转换等。

Hwmon子系统的核心代码是drivers/hwmon/hwmon.c
通过同路径下的Kconfig文件,可以得知它在make menuconfig中的配置名字为Hardware Monitoring support
通过hwmon.c中的EXPORT_SYMBOL_GPL()符号,可知对外提供如下几组接口函数:

hwmon_device_register_with_groups() / hwmon_device_register()
hwmon_device_unregister()

devm_hwmon_device_register_with_groups()
devm_hwmon_device_unregister()

1.1 AD7705简介

AD7705是十六位分辨率的A/D转换器,2个通道全差分模拟输入。
内部有8个寄存器,常用的就Communication RegisterSetup registerClock registerData register
在每次做任何操作前,都要写Communication Register来设置即将操作的是哪一个寄存器、是读还是写操作、操作哪一个通道。
其操作流程如下:

  1. 拉低复位引脚,硬件复位;
  1. 在至少32个时钟周期里连续发送高脉冲,以同步时钟;
  2. 配置AD7705时钟(时钟源、分频系数等);
  3. 自校准,并等待就绪引脚拉低;
  4. 从数据寄存器里读取数据;

1.2 完整代码及效果

  • 设备树:
    {% codeblock lang:dts [am335x-evm.dts] %}
    spidev@0 {
    compatible = “titan,ad7705”;
    spi-max-frequency = <2500000>;
    reg = <2>;
    };
    {% endcodeblock %}

  • 驱动代码:
    {% codeblock lang:c [ad7705_drv.c] %}
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/gpio.h>
    #include <linux/spi/spi.h>
    #include <linux/hwmon.h>
    #include <linux/hwmon-sysfs.h>

#define CHANNEL_NUM (2) //AD7705 channel number
#define DRDY_PIN (12) //AD7705 DRDY Pin(GPIO0_12)
#define RESET_PIN (13) //AD7705 RESET Pin(GPIO0_13)

//Communication Register
enum
{
REG_COMM = (0 << 4), //Communication Register RS2:RS1:RS0 = [0:0:0]
REG_SETUP = (1 << 4), //Setup register RS2:RS1:RS0 = [0:0:1]
REG_CLOCK = (2 << 4), //Clock register RS2:RS1:RS0 = [0:1:0]
REG_DATA = (3 << 4), //Data register RS2:RS1:RS0 = [0:1:1]
REG_TEST = (4 << 4), //Test register RS2:RS1:RS0 = [1:0:0]
REG_RESE = (5 << 4), //No operation RS2:RS1:RS0 = [1:0:1]
REG_OFFSET = (6 << 4), //Offset register RS2:RS1:RS0 = [1:1:0]
REG_GAIN = (7 << 4), //Gain register RS2:RS1:RS0 = [1:1:1]

CMD_WRITE        = (0 << 3), //write operation
CMD_READ         = (1 << 3), //read  operation

CH_1             = 0,        //Register Pair 0 (AIN1+  AIN1-)
CH_2             = 1,        //Register Pair 1 (AIN2+  AIN2-)
CH_3             = 2,        //Register Pair 0 (AIN1-  AIN1-)
CH_4             = 3         //Register Pair 2 (AIN1-  AIN2-)

};

//Setup register
enum
{
MD_NORMAL = (0 << 6), //Normal Mode.
MD_CAL_SELF = (1 << 6), //Self-Calibration
MD_CAL_ZERO = (2 << 6), //Zero-Scale System Calibration
MD_CAL_FULL = (3 << 6), //Full-Scale System Calibration

GAIN_1           = (0 << 3), //Gain 1  
GAIN_2           = (1 << 3), //Gain 2  
GAIN_4           = (2 << 3), //Gain 4  
GAIN_8           = (3 << 3), //Gain 8  
GAIN_16          = (4 << 3), //Gain 16 
GAIN_32          = (5 << 3), //Gain 32 
GAIN_64          = (6 << 3), //Gain 64 
GAIN_128         = (7 << 3), //Gain 128

BIPOLAR          = (0 << 2), //Bipolar Operation  
UNIPOLAR         = (1 << 2), //Unipolar Operation

BUF_NO           = (0 << 1), //Buffer Control Off
BUF_EN           = (1 << 1), //Buffer Control On

FSYNC_0          = (0 << 0), //Filter Synchronization Normal
FSYNC_1          = (1 << 0)  //Filter Synchronization Disable

};

//Clock register
enum
{
CLKDIS_0 = (0 << 4), //Master Clock Enable (Use crystal clock source)
CLKDIS_1 = (1 << 4), //Master Clock Disable(Use an external clock source)

CLKDIV_0         = (0 << 3), //Clock Divider 0
CLKDIV_1         = (1 << 3), //Clock Divider 2 (4.9152Mhz/2=2.4576Mhz)

CLK_0            = (0 << 2), //Clock Bit(If master clock 1MHz(CLKDIV = 0) or 2MHz(CLKDIV = 1)) 
CLK_1            = (1 << 2), //Clock Bit(If master clock 2.4576MHz(CLKDIV = 0) or 4.9152MHz(CLKDIV = 1)) 
//If Clock Bit = 0
UPDATE_20        = (0 << 0), //Filter Selection Bits
UPDATE_25        = (1 << 0),
UPDATE_100       = (2 << 0),
UPDATE_200       = (3 << 0),
//If Clock Bit = 1      
UPDATE_50        = (0 << 0), //Filter Selection Bits
UPDATE_60        = (1 << 0),
UPDATE_250       = (2 << 0),
UPDATE_500       = (3 << 0)

};

struct ad7705 {
struct device *hwmon_dev;
struct mutex lock;
};

//Reset
static void ad7705_reset(void)
{
gpio_direction_output(RESET_PIN, 1);
msleep(1);
gpio_direction_output(RESET_PIN, 0);
msleep(2);
gpio_direction_output(RESET_PIN, 1);
msleep(1);
}

//Synchronous SPI timing
static void ad7705_sync_spi(struct spi_device *spi)
{
u8 tx_buf[6]; //Write logic “1” to DIN for at least 32 clocks
memset(tx_buf, 0xFF, sizeof(tx_buf));
spi_write(spi, tx_buf, sizeof(tx_buf));

msleep(1);

}

//Waiting for DRDY pin signal
static int ad7705_wait_DRDY(void)
{
int i = 0;
int time_cnt = 500*1000;

for (i=0; i<time_cnt; i++)
{
    if (0 == gpio_get_value(DRDY_PIN))
        break;
    udelay(1);
}

if (i >= time_cnt)
    return -1; 
else
    return 0;

}

//Self-Calibration
static void ad7705_calib_self(struct spi_device *spi, u8 channel)
{
u8 tx_buf[2] = {0};

tx_buf[0] = REG_SETUP | CMD_WRITE | channel;
tx_buf[1] = MD_CAL_SELF | GAIN_1 | UNIPOLAR | BUF_EN | FSYNC_0;
spi_write(spi, tx_buf, sizeof(tx_buf));

ad7705_wait_DRDY(); //Waiting for the internal operation to complete, the time is long, about 180ms

msleep(50);

}

//Initialize the specified channel
static void ad7705_config_channel(struct spi_device *spi, u8 channel)
{
u8 tx_buf[2] = {0};

tx_buf[0] = REG_CLOCK | CMD_WRITE | channel;
tx_buf[1] = CLKDIS_0 | CLKDIV_1 | CLK_1 | UPDATE_50;
spi_write(spi, tx_buf, sizeof(tx_buf));

ad7705_calib_self(spi, channel); 

}

//Reset and initialize the specified channel
static void ad7705_reset_and_reconfig(struct spi_device *spi)
{
ad7705_reset();

msleep(5);
ad7705_sync_spi(spi);
msleep(5);

ad7705_config_channel(spi, CH_1);
ad7705_config_channel(spi, CH_2);

}

//Read the specified channel value
static int ad7705_read_channel(struct device *dev, u8 channel, u16 *data)
{
struct spi_device *spi = to_spi_device(dev);
struct ad7705 *adc = spi_get_drvdata(spi);
int ret = -1;
u16 value = 0;
u8 tx_buf[1] = {0};
u8 rx_buf[2] = {0};

if (mutex_lock_interruptible(&adc->lock))
    return -ERESTARTSYS;

if (ad7705_wait_DRDY() < 0)
{
    printk(KERN_ERR "[%s] ad7705_wait_DRDY() time out.\n", __FUNCTION__);
    goto fail;
}

tx_buf[0] = REG_DATA | CMD_READ | channel;
ret = spi_write_then_read(spi, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf));
//printk("channel:%d rx_buf[0]=%d  rx_buf[1]=%d \n", channel, rx_buf[0], rx_buf[1]);
if (0 > ret)
{
    printk(KERN_ERR "[%s] ad7705_read_channel() fail. ret=%d\n", __FUNCTION__, ret);
    goto fail;
}
value = (rx_buf[0] << 8) + rx_buf[1];

#if 0
if (0xFFFF == value)
{
ret = -1;
goto fail;
}
#endif
*data = value;

fail:
mutex_unlock(&adc->lock);

return ret;

}

//sysfs hook function
static ssize_t ad7705_get_sensor_value(struct device *dev, struct device_attribute *devattr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
u16 ad = 0;
int i, ret = -1;
int vol1, vol2;

//After switching channels, the value of the other channel is read for the first time.
for (i=0; i<2; i++) 
{
    ret = ad7705_read_channel(dev, attr->index, &ad);
        continue;
    if (0 > ret)
    {
        ad7705_reset_and_reconfig(spi);
        return ret;
    }
}

#if 0
ret = sprintf(buf, “%u\n”, ad);
#else
vol1 = ad5/65535; //Voltage integral part
vol2 = (ad
51000/65535) - (vol11000);//Voltage fraction part
ret = sprintf(buf, “vol = %d.%dV\n”,vol1, vol2);
#endif

return ret;

}

static struct sensor_device_attribute ad_input[] = {
SENSOR_ATTR(ad7705_ch1, S_IRUGO, ad7705_get_sensor_value, NULL, CH_1),
SENSOR_ATTR(ad7705_ch2, S_IRUGO, ad7705_get_sensor_value, NULL, CH_2),
};

static int ad7705_probe(struct spi_device *spi)
{
struct ad7705 *adc = NULL;
int i, status;

adc = kzalloc(sizeof *adc, GFP_KERNEL);
if (!adc)
    return -ENOMEM;

mutex_init(&adc->lock);
mutex_lock(&adc->lock);

spi_set_drvdata(spi, adc);
for (i=0; i<CHANNEL_NUM; i++)
{
    status = device_create_file(&spi->dev, &ad_input[i].dev_attr);
    if (status)
    {
        dev_err(&spi->dev, "device_create_file() failed.\n");
        goto fail_crete_file;
    }
}

adc->hwmon_dev = hwmon_device_register(&spi->dev);
if (IS_ERR(adc->hwmon_dev))
{
    dev_err(&spi->dev, "hwmon_device_register() fail.\n");
    status = PTR_ERR(adc->hwmon_dev);
    goto fail_crete_file;
}

status = gpio_request(DRDY_PIN, "ad7705_drdy");   //ad7705 DRDY Pin
if (status)
{
    dev_err(&spi->dev, "gpio_request(AD705_DRDY_PIN) fail.\n");
    goto fail_device_register;
}
gpio_direction_input(DRDY_PIN);

status = gpio_request(RESET_PIN, "ad7705_reset");  //ad7705 RESET Pin
if (status)
{
    dev_err(&spi->dev, "gpio_request(RESET_PIN) fail.\n");
    goto fail_request_drdy_pin;
}
gpio_direction_output(RESET_PIN, 1);

ad7705_reset_and_reconfig(spi);

mutex_unlock(&adc->lock);
return 0;

fail_request_drdy_pin:
gpio_free(DRDY_PIN);
fail_device_register:
hwmon_device_unregister(adc->hwmon_dev);
fail_crete_file:
for (i–; i>=0; i–)
device_remove_file(&spi->dev, &ad_input[i].dev_attr);

spi_set_drvdata(spi, NULL);
mutex_unlock(&adc->lock);
kfree(a
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值