spi客户端驱动
spi子系统类似i2c子系统,驱动注册方式和i2c类似。没有什么可说的,主要区别在于SPI总线会多两个信号线。
驱动注册
使用 module_spi_driver()
进行注册即可。在注册之前,需要定义号设备树匹配的结构体。
static struct of_device_id bmp280_of_match[] = {
{.compatible = "bosch,bmp280-spi"},
{ },
};
MODULE_DEVICE_TABLE(of, bmp280_of_match);
static struct spi_driver bmp280_driver = {
.probe = bmp280_probe,
.remove = bmp280_remove,
.driver = {
.owner = THIS_MODULE,
.name = "bmp280",
.of_match_table = of_match_ptr(bmp280_of_match),
},
};
module_spi_driver(bmp280_driver);
注意这里的compatible属性,格式应该是 vendor,model
。node 将成为设备别名,如果设备树中的 node 名称和 model 字符串相等,那么也能匹配成功。或者 node 和 driver->name 相等,也将匹配成功。优先级顺序按照如下:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
spi 也支持 id_table 方式进行匹配。但是在 probe 中不会传入 id_table 参数。
在 probe 中,我们需要去设置 SPI 主机的属性,以便适配我们得SPI设备。这里,bmp280 支持的模式是:0x00
和 0x11
,最大 5MHz 速率。
static int bmp280_probe(struct spi_device *spi)
{
struct bmp280_dev *bmp280;
int ret = 0;
bmp280 = devm_kzalloc(&spi->dev, sizeof(*bmp280), GFP_KERNEL);
if (IS_ERR(bmp280)) {
dev_err(&spi->dev, "kzalloc fail: 0x%lx\n", PTR_ERR(bmp280));
return PTR_ERR(bmp280);
}
spi_set_drvdata(spi, bmp280);
bmp280->spi = spi;
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 5000000;
ret = spi_setup(spi);
if (ret) {
return -EIO;
}
if (bmp280_init(bmp280)) {
dev_err(&spi->dev, "bmp280 init error\n");
return -EIO;
}
device_create_file(&spi->dev, &dev_attr_temperature);
device_create_file(&spi->dev, &dev_attr_pressure);
dev_info(&spi->dev, "bmp280 probe success\n");
return 0;
}
设备树
除了驱动的设备树匹配外,SPI客户端驱动也是有地址信息的。如果有多个CS引脚,那么不同的CS引脚外接的设备对应不同的地址。CS-0 为 reg = <0x0> ,CS-1 为 reg = <0x01>,依次类推。
这里使用的开发板是rockchip rk3399:
rockchip_spi_probe
spi_register_master
of_register_spi_devices
of_register_spi_device
在 of_register_spi_device
中,会去识别spi需要的设备树信息。如:
rc = of_property_read_u32(nc, "reg", &value);
if (rc) {
dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",
nc->full_name, rc);
goto err_out;
}
spi->chip_select = value;
/* Mode (clock phase/polarity/etc.) */
if (of_find_property(nc, "spi-cpha", NULL))
spi->mode |= SPI_CPHA;
if (of_find_property(nc, "spi-cpol", NULL))
spi->mode |= SPI_CPOL;
if (of_find_property(nc, "spi-cs-high", NULL))
spi->mode |= SPI_CS_HIGH;
if (of_find_property(nc, "spi-3wire", NULL))
spi->mode |= SPI_3WIRE;
if (of_find_property(nc, "spi-lsb-first", NULL))
spi->mode |= SPI_LSB_FIRST;
/* Device speed */
......
rc = of_property_read_u32(nc, "spi-max-frequency", &value);
if (rc) {
dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
nc->full_name, rc);
goto err_out;
}
spi->max_speed_hz = value;
对于spi主机的配置,我们都可以在设备树中完成。另外对于CS pin的定义,在:
rockchip_spi_probe
spi_register_master
of_spi_register_master
static int of_spi_register_master(struct spi_master *master)
{
int nb, i, *cs;
struct device_node *np = master->dev.of_node;
if (!np)
return 0;
nb = of_gpio_named_count(np, "cs-gpios");
master->num_chipselect = max_t(int, nb, master->num_chipselect);
/* Return error only for an incorrectly formed cs-gpios property */
if (nb == 0 || nb == -ENOENT)
return 0;
else if (nb < 0)
return nb;
cs = devm_kzalloc(&master->dev,
sizeof(int) * master->num_chipselect,
GFP_KERNEL);
master->cs_gpios = cs;
if (!master->cs_gpios)
return -ENOMEM;
for (i = 0; i < master->num_chipselect; i++)
cs[i] = -ENOENT;
for (i = 0; i < nb; i++)
cs[i] = of_get_named_gpio(np, "cs-gpios", i);
return 0;
}
这里会从 cs-gpios
这个属性中获取对于CS引脚的定义信息。这里有个奇怪的现象,rockchip平台,并没有在设备树中定义 cs-gpios
属性,但是CS引脚在SPI的使用过程中仍然可用。当然这里的CS引脚是 SPI对应的默认CS引脚:
spi1 {
spi1_clk: spi1-clk {
rockchip,pins =
<1 9 RK_FUNC_2 &pcfg_pull_up>;
};
spi1_cs0: spi1-cs0 {
rockchip,pins =
<1 10 RK_FUNC_2 &pcfg_pull_up>;
};
spi1_rx: spi1-rx {
rockchip,pins =
<1 7 RK_FUNC_2 &pcfg_pull_up>;
};
spi1_tx: spi1-tx {
rockchip,pins =
<1 8 RK_FUNC_2 &pcfg_pull_up>;
};
};
网上的说法是,有的平台SPI硬件会自动使用CS引脚。这里看代码:
static void spi_set_cs(struct spi_device *spi, bool enable)
{
if (spi->mode & SPI_CS_HIGH)
enable = !enable;
if (gpio_is_valid(spi->cs_gpio)) {
gpio_set_value(spi->cs_gpio, !enable);
spi->master->set_cs(spi, !enable);
} else {
spi->master->set_cs(spi, !enable);
}
}
应该是在自己设置了 cs-gpios
属性后,还是会调用rockchip平台的cs_gpio的设置函数。我猜测这里会对默认的CS_GPIO进行使用。
static void rockchip_spi_set_cs(struct spi_device *spi, bool enable)
{
struct spi_master *master = spi->master;
struct rockchip_spi *rs = spi_master_get_devdata(master);
bool cs_asserted = spi->mode & SPI_CS_HIGH ? enable : !enable;
/* Return immediately for no-op */
if (cs_asserted == rs->cs_asserted[spi->chip_select])
return;
if (cs_asserted) {
/* Keep things powered as long as CS is asserted */
pm_runtime_get_sync(rs->dev);
if (gpio_is_valid(spi->cs_gpio))
ROCKCHIP_SPI_SET_BITS(rs->regs + ROCKCHIP_SPI_SER, 1);
else
ROCKCHIP_SPI_SET_BITS(rs->regs + ROCKCHIP_SPI_SER, BIT(spi->chip_select));
} else {
if (gpio_is_valid(spi->cs_gpio))
ROCKCHIP_SPI_CLR_BITS(rs->regs + ROCKCHIP_SPI_SER, 1);
else
ROCKCHIP_SPI_CLR_BITS(rs->regs + ROCKCHIP_SPI_SER, BIT(spi->chip_select));
/* Drop reference from when we first asserted CS */
pm_runtime_put(rs->dev);
}
rs->cs_asserted[spi->chip_select] = cs_asserted;
}
经过测试验证,当我们不定义 cs-gpios
时,默认的CS引脚确实是能正常工作的。当我们定义CS引脚后,默认的CS引脚还是正常工作的。
&spi1 {
status = "okay";
cs-gpios = <&gpio4 29 GPIO_ACTIVE_LOW>;
bmp280-spi@0{
status = "okay";
compatible = "bosch,bmp280-spi";
reg = <0x00>;
spi-max-frequency = <10000000>;
//spi-cpha; /* SPI mode: CPHA=1 */
//spi-cpol; /* SPI mode: CPOL=1 */
//spi-cs-high;
};
};
也就是说,上述dts中,定义了设备地址为0的设备的CS引脚,当SPI工作时,默认的CS引脚和定义的另外一个CS引脚是同时工作的。因此当,只有一个CS引脚时,在rockchip平台,就不需要去定义CS引脚了,硬件上最好就使用默认的CS引脚即可。
当我们一个SPI总线挂了两个设备时,就需要定义 cs-gpios
引脚了。将第一个CS引脚定义为默认的CS引脚,第二个引脚定义为我们第二设备的CS引脚。这样,控制对应地址的设备就能设置对应CS引脚了。
&spi1 {
status = "okay";
cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>,
<&gpio4 29 GPIO_ACTIVE_LOW>;
bmp280-spi@1{
status = "okay";
compatible = "bosch,bmp280-spi";
reg = <0x01>;
spi-max-frequency = <10000000>;
//spi-cpha; /* SPI mode: CPHA=1 */
//spi-cpol; /* SPI mode: CPOL=1 */
//spi-cs-high;
};
};
这里,当我们将设备CS引脚接入到第一个CS引脚时,设置设备地址和reg为 0x0,那么控制设备时,就只有第一个CS引脚会有电平变化。当将CS引脚接入到第二个CS引脚时,设置设备地址和reg 为 0x01,那么控制设备时,就只有第二个CS引脚有电平变化。此时能发现,CS是根据设备地址来自行选择设置的。
of_spi_register_master()
函数会解析所有设置的 CS 引脚。在 spi_add_device
添加设备时,会根据 reg
地址设置相应的 CS 引脚给相应的设备地址。
int spi_add_device(struct spi_device *spi)
{
......
if (master->cs_gpios)
spi->cs_gpio = master->cs_gpios[spi->chip_select];
......
}
根据上述提到的 spi_set_cs
的定义,当reg 为0时,会同时设置SPI的默认CS引脚和 cs-gpios
中定义第0个CS引脚。
在调试时遇到一个问题,在使用 GPIO1_B4
作为CS引脚时,SPI通信时死活无法控制这个引脚,最后发现是这个引脚一直是高电平。暂不清楚是GPIO有问题,还是被其他外设占用了。当导出时,设置 out 为高或者低,测量出来的电平都是3.0V。更换其它引脚后,SPI通信正常。
bmp280 spi驱动
除了要在 probe 中设置设备支持的SPI主机模式,还要注意,bmp280使用SPI时,写寄存器地址时,不支持地址自增,因此,写一个寄存器,需要重新设置寄存器地址。读寄存器数据时,支持寄存器地址自增。
另外,更重要的是,SPI模式下,bmp280的寄存器地址只支持7bit,最高位为读写标志位。当SPI读写调不通时,多半是没有注意到这个特殊要求。
static int bmp280_spi_write(struct spi_device *spi, spi_w_ctrl *wc, size_t len)
{
struct spi_transfer xfer = {0};
u8 *data = kzalloc(sizeof(u8), 2 * len);
int i = 0;
struct spi_message msg = {0};
int ret = 0;
for (i = 0; i < len; i++) {
data[2 * i] = wc[i].reg & (~(1 << 7));
data[2 * i + 1] = wc[i].val;
}
xfer.tx_buf = data;
xfer.len = 2 * len;
// xfer.cs_change = 1;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
ret = spi_sync(spi, &msg);
kfree(data);
return ret;
}
static int bmp280_spi_read(struct spi_device *spi, u8 reg, u8 *data, size_t len)
{
#if 0
return spi_write_then_read(spi, ®, 1, data, len);
#else
struct spi_transfer xfer = {0};
u8 *tx_buf = kzalloc(sizeof(u8), len);
u8 *rx_buf = kzalloc(sizeof(u8), len);
struct spi_message msg = {0};
int ret = 0;
tx_buf[0] = reg;
xfer.tx_buf = tx_buf;
xfer.rx_buf = rx_buf;
xfer.len = len + 1;
// xfer.cs_change = 1;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
ret = spi_sync(spi, &msg);
memcpy(data, rx_buf + 1, len);
kfree(tx_buf);
kfree(rx_buf);
return ret;
#endif
}
代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/errno.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
struct bmp280_compensation {
unsigned short dig_T1;
signed short dig_T2;
signed short dig_T3;
unsigned short dig_P1;
signed short dig_P2;
signed short dig_P3;
signed short dig_P4;
signed short dig_P5;
signed short dig_P6;
signed short dig_P7;
signed short dig_P8;
signed short dig_P9;
s32 t_fine;
};
struct bmp280_dev {
struct spi_device *spi;
struct bmp280_compensation compensation;
s32 adc_T;
s32 adc_P;
s32 temperature;
u32 pressure;
};
typedef struct {
u8 reg;
u8 val;
} spi_w_ctrl;
typedef u32 BMP280_U32_t;
typedef s32 BMP280_S32_t;
typedef s64 BMP280_S64_t;
// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123”
// equals 51.23 DegC. t_fine carries fine temperature as global value
static BMP280_S32_t bmp280_compensate_T_int32(BMP280_S32_t adc_T,
struct bmp280_compensation *com)
{
BMP280_S32_t var1, var2, T;
var1 = ((((adc_T >> 3) - ((BMP280_S32_t)com->dig_T1 << 1))) *
((BMP280_S32_t)com->dig_T2)) >>
11;
var2 = (((((adc_T >> 4) - ((BMP280_S32_t)com->dig_T1)) *
((adc_T >> 4) - ((BMP280_S32_t)com->dig_T1))) >>
12) *
((BMP280_S32_t)com->dig_T3)) >>
14;
com->t_fine = var1 + var2;
T = (com->t_fine * 5 + 128) >> 8;
return T;
}
// Returns pressure in Pa as unsigned 32 bit integer. Output value of “96386”
// equals 96386 Pa = 963.86 hPa
static BMP280_U32_t bmp280_compensate_P_int32(BMP280_S32_t adc_P,
struct bmp280_compensation *com)
{
BMP280_S32_t var1, var2;
BMP280_U32_t p;
var1 = (((BMP280_S32_t)com->t_fine) >> 1) - (BMP280_S32_t)64000;
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((BMP280_S32_t)com->dig_P6);
var2 = var2 + ((var1 * ((BMP280_S32_t)com->dig_P5)) << 1);
var2 = (var2 >> 2) + (((BMP280_S32_t)com->dig_P4) << 16);
var1 = (((com->dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) +
((((BMP280_S32_t)com->dig_P2) * var1) >> 1)) >>
18;
var1 = ((((32768 + var1)) * ((BMP280_S32_t)com->dig_P1)) >> 15);
if (var1 == 0) {
return 0; // avoid exception caused by division by zero
}
p = (((BMP280_U32_t)(((BMP280_S32_t)1048576) - adc_P) - (var2 >> 12))) *
3125;
if (p < 0x80000000) {
p = (p << 1) / ((BMP280_U32_t)var1);
} else {
p = (p / (BMP280_U32_t)var1) * 2;
}
var1 = (((BMP280_S32_t)com->dig_P9) *
((BMP280_S32_t)(((p >> 3) * (p >> 3)) >> 13))) >>
12;
var2 = (((BMP280_S32_t)(p >> 2)) * ((BMP280_S32_t)com->dig_P8)) >> 13;
p = (BMP280_U32_t)((BMP280_S32_t)p + ((var1 + var2 + com->dig_P7) >> 4));
return p;
}
static int bmp280_spi_read(struct spi_device *spi, u8 reg, u8 *data, size_t len)
{
#if 0
return spi_write_then_read(spi, ®, 1, data, len);
#else
struct spi_transfer xfer = {0};
u8 *tx_buf = kzalloc(sizeof(u8), len);
u8 *rx_buf = kzalloc(sizeof(u8), len);
struct spi_message msg = {0};
int ret = 0;
tx_buf[0] = reg;
xfer.tx_buf = tx_buf;
xfer.rx_buf = rx_buf;
xfer.len = len + 1;
// xfer.cs_change = 1;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
ret = spi_sync(spi, &msg);
memcpy(data, rx_buf + 1, len);
kfree(tx_buf);
kfree(rx_buf);
return ret;
#endif
}
static int bmp280_spi_write(struct spi_device *spi, spi_w_ctrl *wc, size_t len)
{
struct spi_transfer xfer = {0};
u8 *data = kzalloc(sizeof(u8), 2 * len);
int i = 0;
struct spi_message msg = {0};
int ret = 0;
for (i = 0; i < len; i++) {
data[2 * i] = wc[i].reg & (~(1 << 7));
data[2 * i + 1] = wc[i].val;
}
xfer.tx_buf = data;
xfer.len = 2 * len;
// xfer.cs_change = 1;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
ret = spi_sync(spi, &msg);
kfree(data);
return ret;
}
static int bmp280_compensation_load(struct bmp280_dev *bmp280)
{
u8 data[24] = {0};
struct spi_device *spi = bmp280->spi;
u8 reg = 0x88;
int ret = 0;
struct bmp280_compensation *com = &bmp280->compensation;
ret = bmp280_spi_read(spi, reg, data, sizeof(data));
if (ret) {
return ret;
}
com->dig_T1 = data[0] | (data[1] << 8);
com->dig_T2 = data[2] | (data[3] << 8);
com->dig_T3 = data[4] | (data[5] << 8);
com->dig_P1 = data[6] | (data[7] << 8);
com->dig_P2 = data[8] | (data[9] << 8);
com->dig_P3 = data[10] | (data[11] << 8);
com->dig_P4 = data[12] | (data[13] << 8);
com->dig_P5 = data[14] | (data[15] << 8);
com->dig_P6 = data[16] | (data[17] << 8);
com->dig_P7 = data[18] | (data[19] << 8);
com->dig_P8 = data[20] | (data[21] << 8);
com->dig_P9 = data[22] | (data[23] << 8);
dev_info(&bmp280->spi->dev,
"dig_T1=0x%x\n"
"dig_T2=0x%x\n"
"dig_T3=0x%x\n"
"dig_P1=0x%x\n"
"dig_P2=0x%x\n"
"dig_P3=0x%x\n"
"dig_P4=0x%x\n"
"dig_P5=0x%x\n"
"dig_P6=0x%x\n"
"dig_P7=0x%x\n"
"dig_P8=0x%x\n"
"dig_P9=0x%x\n",
com->dig_T1, com->dig_T2, com->dig_T3,
com->dig_P1, com->dig_P2, com->dig_P3,
com->dig_P4, com->dig_P5, com->dig_P6,
com->dig_P7, com->dig_P8, com->dig_P9);
return 0;
}
static int bmp280_read_adc(struct bmp280_dev *bmp280)
{
u8 data[6] = {0};
int ret = 0;
struct spi_device *spi = bmp280->spi;
spi_w_ctrl w_ctrl = {
.reg = 0xF4,
.val = (0x07 << 5) | (0x07 << 2) | 0x01,
};
u8 reg = 0xF7;
/* write config for sample, oversampling x1, force mode */
bmp280_spi_write(spi, &w_ctrl, 1);
/* measure time Max 6.4ms: T and P oversampling x1 */
msleep(7);
ret = bmp280_spi_read(spi, reg, data, sizeof(data));
if (ret) {
return ret;
}
bmp280->adc_P = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
bmp280->adc_T = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
dev_info(&bmp280->spi->dev, "adc_P: 0x%x, adc_T: 0x%x\n", bmp280->adc_P,
bmp280->adc_T);
return 0;
}
static int bmp280_read_temp_hum_press(struct bmp280_dev *bmp280)
{
int ret = 0;
ret = bmp280_read_adc(bmp280);
if (ret) {
return ret;
}
bmp280->temperature = bmp280_compensate_T_int32(bmp280->adc_T,
&bmp280->compensation);
bmp280->pressure = bmp280_compensate_P_int32(bmp280->adc_P,
&bmp280->compensation);
dev_info(&bmp280->spi->dev, "temperature: %d, pressure: %u\n",
bmp280->temperature, bmp280->pressure);
return 0;
}
static ssize_t bmp280_temperature_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct spi_device *spi = container_of(dev, struct spi_device, dev);
struct bmp280_dev *bmp280 = spi_get_drvdata(spi);
if (IS_ERR(bmp280)) {
dev_err(dev, "i2c get cleintdata error: 0x%lx\n", PTR_ERR(bmp280));
}
bmp280_read_temp_hum_press(bmp280);
return sprintf(buf, "%d\n", bmp280->temperature);
}
static ssize_t bmp280_temperature_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return 0;
}
static DEVICE_ATTR(temperature, 0644, bmp280_temperature_show,
bmp280_temperature_store);
static ssize_t bmp280_pressure_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct spi_device *spi = container_of(dev, struct spi_device, dev);
struct bmp280_dev *bmp280 = spi_get_drvdata(spi);
if (IS_ERR(bmp280)) {
dev_err(dev, "i2c get cleintdata error: 0x%lx\n", PTR_ERR(bmp280));
}
bmp280_read_temp_hum_press(bmp280);
return sprintf(buf, "%u\n", bmp280->pressure);
}
static ssize_t bmp280_pressure_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return 0;
}
static DEVICE_ATTR(pressure, 0644, bmp280_pressure_show, bmp280_pressure_store);
static int bmp280_init(struct bmp280_dev *bmp280)
{
struct spi_device *spi = bmp280->spi;
u8 id = 0;
int ret = 0;
u8 reg = 0xD0;
spi_w_ctrl w_ctrl = {
.reg = 0xE0,
.val = 0xB6,
};
ret = bmp280_spi_read(spi, reg, &id, 1);
if (ret < 0) {
return -EREMOTEIO;
}
dev_info(&spi->dev, "Read BMP280 Device ID: 0x%02x\n", id);
/* Reset chip */
ret = bmp280_spi_write(spi, &w_ctrl, 1);
if (ret) {
return ret;
}
/* startup time: 2ms */
msleep(2);
ret = bmp280_compensation_load(bmp280);
if (ret) {
return ret;
}
return 0;
}
static int bmp280_probe(struct spi_device *spi)
{
struct bmp280_dev *bmp280;
int ret = 0;
bmp280 = devm_kzalloc(&spi->dev, sizeof(*bmp280), GFP_KERNEL);
if (IS_ERR(bmp280)) {
dev_err(&spi->dev, "kzalloc fail: 0x%lx\n", PTR_ERR(bmp280));
return PTR_ERR(bmp280);
}
spi_set_drvdata(spi, bmp280);
bmp280->spi = spi;
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 5000000;
ret = spi_setup(spi);
if (ret) {
return -EIO;
}
if (bmp280_init(bmp280)) {
dev_err(&spi->dev, "bmp280 init error\n");
return -EIO;
}
device_create_file(&spi->dev, &dev_attr_temperature);
device_create_file(&spi->dev, &dev_attr_pressure);
dev_info(&spi->dev, "bmp280 probe success\n");
return 0;
}
static int bmp280_remove(struct spi_device *spi)
{
spi_set_drvdata(spi, NULL);
device_remove_file(&spi->dev, &dev_attr_temperature);
device_remove_file(&spi->dev, &dev_attr_pressure);
dev_info(&spi->dev, "bmp280 remove");
return 0;
}
static struct of_device_id bmp280_of_match[] = {
{.compatible = "bosch,bmp280-spi"},
{ },
};
MODULE_DEVICE_TABLE(of, bmp280_of_match);
static struct spi_driver bmp280_driver = {
.probe = bmp280_probe,
.remove = bmp280_remove,
.driver = {
.owner = THIS_MODULE,
.name = "bmp280",
.of_match_table = of_match_ptr(bmp280_of_match),
},
};
module_spi_driver(bmp280_driver);
MODULE_AUTHOR("duapple <duapple2@gmail.com>");
MODULE_LICENSE("GPL");