sdk-hwV1.3/lichee/linux-4.9/drivers/media/spi/spi-camera/spi_camera.c

687 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0+
#include "spi_camera.h"
#define SPI_SENSOR_NAME "gc032a"
static int spi_sensor_open(struct file *file)
{
struct spi_sensor *gc032a = video_drvdata(file);
pr_info("%s\n", __func__);
set_bit(DRIVER_BUSY, &gc032a->state);
return 0;
}
static int spi_sensor_close(struct file *file)
{
struct spi_sensor *gc032a = video_drvdata(file);
int ret = 0;
pr_info("%s\n", __func__);
if (!driver_busy(gc032a)) {
pr_err("%s: video have been closed!\n", __func__);
return 0;
}
if (driver_streaming(gc032a)) {
disable_irq(gc032a->cb_irq);
//wait all work done before streamoff
flush_work(&gc032a->spi_rx_work);
ret = vb2_ioctl_streamoff(file, NULL, V4L2_CAP_VIDEO_CAPTURE_MPLANE);
if (ret != 0)
pr_err("%s: vb2_ioctl_streamoff error\n", __func__);
clear_bit(DRIVER_STREAM, &gc032a->state);
}
v4l2_subdev_call(gc032a->sd, core, s_power, PWR_OFF);
clear_bit(DRIVER_BUSY, &gc032a->state);
return 0;
}
static int queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct spi_sensor *gc032a = vb2_get_drv_priv(vq);
struct spi_device *spi = (struct spi_device *)gc032a->spidev;
static u64 dma_mask = DMA_BIT_MASK(32);
int size;
*nplanes = 1;
//YUV422, head and tail
size = gc032a->height * gc032a->width * 2 +
(gc032a->frame_unit_size * 2) +
((gc032a->line_unit_size * 2) * gc032a->height);
//gc032a frame data contains YUV422 data/frmae head/tail/due and line head/tail
sizes[0] = size;
if (sizes[0] == 0) {
pr_info("%s size not right for YUV422\n", __func__);
//sizes[0] = 640 * 480 * 2;
sizes[0] = size;
}
spi->dev.dma_mask = &dma_mask;
spi->dev.coherent_dma_mask = DMA_BIT_MASK(32);
alloc_devs[0] = &spi->dev;
return 0;
}
static int buffer_prepare(struct vb2_buffer *vb)
{
struct spi_sensor *gc032a = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *vvb = container_of(vb, struct vb2_v4l2_buffer, vb2_buf);
struct spi_buffer *buf = container_of(vvb, struct spi_buffer, vb);
unsigned long size;
//gc032a frame data contains YUV422 data/frmae head/tail and line head/tail
size = gc032a->height * gc032a->width * 2 +
(gc032a->frame_unit_size * 2) +
((gc032a->line_unit_size * 2) * gc032a->height);
vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
vb->planes[0].m.offset = vb2_dma_contig_plane_dma_addr(vb, 0);
return 0;
}
static void buffer_queue(struct vb2_buffer *vb)
{
struct spi_sensor *gc032a = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *vvb = container_of(vb, struct vb2_v4l2_buffer, vb2_buf);
struct spi_buffer *buf = container_of(vvb, struct spi_buffer, vb);
struct spi_dmaqueue *vidq = &gc032a->vidq;
spin_lock(&gc032a->slock);
list_add_tail(&buf->list, &vidq->active);
spin_unlock(&gc032a->slock);
}
static void stop_streaming(struct vb2_queue *vq)
{
struct spi_sensor *gc032a = vb2_get_drv_priv(vq);
struct spi_dmaqueue *dma_q = &gc032a->vidq;
unsigned long flags = 0;
spin_lock_irqsave(&gc032a->slock, flags);
/* Release all active buffers*/
while (!list_empty(&dma_q->active)) {
struct spi_buffer *spi_buf;
spi_buf = list_entry(dma_q->active.next, struct spi_buffer, list);
list_del(&spi_buf->list);
vb2_buffer_done(&spi_buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
pr_info("stop streaming buffer %d stop\n", spi_buf->vb.vb2_buf.index);
}
spin_unlock_irqrestore(&gc032a->slock, flags);
}
static void strip_buffer(struct spi_sensor *gc032a, void *start, int *length)
{
char *front = (char *)start;
char *behind = (char *)start;
const int FRAME_UNIT = gc032a->frame_unit_size; //frame head and tail size
const int LINE_UNIT = gc032a->line_unit_size; //line head and tail size
const int WIDTH = gc032a->width;
const int HEIGHT = gc032a->height;
int LINE_DATA_LENGTH = WIDTH * 2; //YUV422
int data_length = 0;
int i;
//front prt skip frame head
front = front + FRAME_UNIT;
for (i = 0; i < HEIGHT; i++) {
front = front + LINE_UNIT; //skip line head
strncpy(behind, front, LINE_DATA_LENGTH);
front += LINE_DATA_LENGTH;
behind += LINE_DATA_LENGTH;
data_length += LINE_DATA_LENGTH;
front = front + LINE_UNIT; //skip line end
}
front = front + FRAME_UNIT;
*length = data_length;
}
static void buffer_finish(struct vb2_buffer *vb)
{
struct spi_sensor *gc032a = vb2_get_drv_priv(vb->vb2_queue);
void *buffer = vb2_plane_vaddr(vb, 0);
int length = 0;
if (gc032a->strip_buffer) {
strip_buffer(gc032a, buffer, &length);
vb2_set_plane_payload(vb, 0, length);
}
}
static struct vb2_ops spi_video_qops = {
.queue_setup = queue_setup,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.stop_streaming = stop_streaming,
.buf_finish = buffer_finish,
};
/*
* IOCTL vidioc handling
*/
static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
{
struct spi_sensor *gc032a = video_drvdata(file);
strlcpy(cap->driver, "spi-sensor", sizeof(cap->driver));
strlcpy(cap->card, "spi-sensor", sizeof(cap->card));
strlcpy(cap->bus_info, gc032a->v4l2_dev.name, sizeof(cap->bus_info));
cap->version = 1;
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE | V4L2_CAP_DEVICE_CAPS;
cap->device_caps |= V4L2_CAP_EXT_PIX_FORMAT;
return 0;
}
static int vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize)
{
struct spi_sensor *gc032a = video_drvdata(file);
struct v4l2_subdev_frame_size_enum fse;
int ret;
if (!gc032a || !gc032a->sd->ops->pad->enum_frame_size)
return -EINVAL;
fse.index = fsize->index;
ret = v4l2_subdev_call(gc032a->sd, pad, enum_frame_size, NULL, &fse);
if (ret >= 0) {
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
fsize->discrete.width = fse.max_height;
fsize->discrete.height = fse.max_width;
}
return ret;
}
static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
{
struct spi_sensor *gc032a = video_drvdata(file);
int ret = 0;
ret = v4l2_subdev_call(gc032a->sd, core, s_power, PWR_ON);
pr_info("%s: ################ sensor power_on______________________\n", __func__);
ret = v4l2_subdev_call(gc032a->sd, core, init, 0);
return ret;
}
static int vidioc_s_parm(struct file *file, void *priv, struct v4l2_streamparm *parms)
{
struct spi_sensor *gc032a = video_drvdata(file);
int ret = 0;
pr_err("enter %s\n", __func__);
if (parms->parm.capture.capturemode != V4L2_MODE_VIDEO &&
parms->parm.capture.capturemode != V4L2_MODE_IMAGE &&
parms->parm.capture.capturemode != V4L2_MODE_PREVIEW) {
parms->parm.capture.capturemode = V4L2_MODE_PREVIEW;
}
ret = v4l2_subdev_call(gc032a->sd, video, s_parm, parms);
if (ret < 0)
pr_err("v4l2 sub device s_parm error!\n");
return ret;
}
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
struct v4l2_pix_format_mplane *pixm;
pixm = &f->fmt.pix_mp;
pixm->field = V4L2_FIELD_NONE;
pixm->num_planes = 1;
return 0;
}
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
struct spi_sensor *gc032a = video_drvdata(file);
struct v4l2_subdev_format format;
int ret;
format.format.width = f->fmt.pix.width;
format.format.height = f->fmt.pix.height;
ret = v4l2_subdev_call(gc032a->sd, pad, set_fmt, NULL, &format);
gc032a->width = format.format.width;
gc032a->height = format.format.height;
gc032a->code = format.format.code;
f->fmt.pix.width = format.format.width;
f->fmt.pix.height = format.format.height;
pr_info("%s get width:%d, height:%d\n", __func__, gc032a->width, gc032a->height);
return ret;
}
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct spi_sensor *gc032a = video_drvdata(file);
struct spi_dmaqueue *dma_q = &gc032a->vidq;
int ret = 0;
vb2_ioctl_streamon(file, priv, i);
if (driver_streaming(gc032a)) {
pr_err("%s: video has already stream on\n", __func__);
return -EBUSY;
}
set_bit(DRIVER_STREAM, &gc032a->state);
if (!list_empty(&dma_q->active)) {
/* need to queue buffer first before stream on */
schedule_work(&gc032a->spi_rx_work); /* spi rx data */
usleep_range(10000, 12000);
ret = v4l2_subdev_call(gc032a->sd, video, s_stream, 1);
} else {
pr_info("enter %s, dma_q->active is empty\n", __func__);
}
//enable dma callback irq
enable_irq(gc032a->cb_irq);
return 0;
}
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct spi_sensor *gc032a = video_drvdata(file);
int ret = 0;
if (!driver_streaming(gc032a)) {
pr_err("%s: video already streamoff\n", __func__);
return -1;
}
disable_irq(gc032a->cb_irq);
//wait all work done before streamoff
flush_work(&gc032a->spi_rx_work);
//videobuf_streamoff(&gc032a->vb_vidq);
ret = vb2_ioctl_streamoff(file, priv, i);
if (ret != 0)
pr_err("%s vb2_ioctl_streamoff error\n", __func__);
clear_bit(DRIVER_STREAM, &gc032a->state);
return 0;
}
static const struct v4l2_file_operations spi_sensor_fops = {
.owner = THIS_MODULE,
.open = spi_sensor_open,
.release = spi_sensor_close,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
.poll = vb2_fop_poll,
};
static const struct v4l2_ioctl_ops spi_sensor_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_framesizes = vidioc_enum_framesizes,
.vidioc_s_input = vidioc_s_input,
.vidioc_s_parm = vidioc_s_parm,
.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
};
unsigned int os_gpio_request(struct gpio_config *pin_cfg)
{
unsigned int ret = 0;
if (pin_cfg->gpio == GPIO_INDEX_INVALID)
return 0;
ret = gpio_request(pin_cfg->gpio, NULL);
if (ret != 0) {
pr_err("%s failed, gpio=%d, ret=0x%x\n", __func__, pin_cfg->gpio, ret);
return 0;
}
return pin_cfg->gpio;
}
int os_gpio_release(unsigned int p_handler)
{
gpio_free(p_handler);
return 0;
}
int os_gpio_set(struct gpio_config *pin_cfg)
{
int ret = 0;
char pin_name[32];
__u32 config;
if (pin_cfg->gpio == GPIO_INDEX_INVALID)
return 0;
/* valid pin of sunxi-pinctrl,
* config pin attributes individually.
*/
sunxi_gpio_to_name(pin_cfg->gpio, pin_name);
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_FUNC, pin_cfg->mul_sel);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
if (pin_cfg->pull != GPIO_PULL_DEFAULT) {
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, pin_cfg->pull);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
}
if (pin_cfg->drv_level != GPIO_DRVLVL_DEFAULT) {
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV, pin_cfg->drv_level);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
}
if (pin_cfg->data != GPIO_DATA_DEFAULT) {
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT, pin_cfg->data);
pin_config_set(SUNXI_PINCTRL, pin_name, config);
}
return ret;
}
static irqreturn_t spi_sensor_isr(int irq, void *dev_id)
{
struct spi_sensor *gc032a = (struct spi_sensor *)dev_id;
struct spi_dmaqueue *dma_q = &gc032a->vidq;
if (!list_empty(&dma_q->active)) {
/* spi_sync */
schedule_work(&gc032a->spi_rx_work); /* spi rx data */
} else {
pr_err("dma_q->active is empty\n");
}
return IRQ_HANDLED;
}
static void spi_rx_work_handle(struct work_struct *work)
{
struct spi_sensor *gc032a = container_of(work, struct spi_sensor, spi_rx_work);
struct spi_device *spi = (struct spi_device *)gc032a->spidev;
struct spi_dmaqueue *dma_q = &gc032a->vidq;
struct spi_transfer transfer;
struct spi_message message;
struct spi_buffer *spi_buf;
int ret;
mutex_lock(&gc032a->spi_rx_mutex);
//fetch buffer
spi_buf = list_entry(dma_q->active.next, struct spi_buffer, list);
//configure message and transfer
memset(&transfer, 0, sizeof(transfer));
transfer.rx_buf = vb2_plane_vaddr(&spi_buf->vb.vb2_buf, 0);
transfer.rx_dma = vb2_dma_contig_plane_dma_addr(&spi_buf->vb.vb2_buf, 0);
transfer.len = spi_buf->vb.vb2_buf.planes[0].bytesused;
#if defined SPI_4_WIRE
transfer.rx_nbits = SPI_NBITS_QUAD;
#elif defined SPI_2_WIRE
transfer.rx_nbits = SPI_NBITS_DUAL;
#endif
memset(&message, 0, sizeof(message));
spi_message_init(&message);
message.is_dma_mapped = 1;
spi_message_add_tail(&transfer, &message);
ret = spi_sync(spi, &message);
if (ret < 0)
pr_err("%s: spi_sync return error %d\n", __func__, ret);
if (&dma_q->active != dma_q->active.next->next) {
spi_buf = list_entry(dma_q->active.next, struct spi_buffer, list);
list_del(&spi_buf->list);
vb2_buffer_done(&spi_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
} else {
pr_info("enter %s, only one buffer left for active\n", __func__);
}
mutex_unlock(&gc032a->spi_rx_mutex);
}
static int spi_sensor_probe(struct spi_device *spi)
{
struct device_node *np = spi->dev.of_node;
struct spi_sensor *gc032a;
struct i2c_adapter *i2c_adap;
struct v4l2_device *v4l2_dev;
struct video_device *vdev;
struct vb2_queue *q;
int ret = 0;
gc032a = kzalloc(sizeof(*gc032a), GFP_KERNEL);
memset(gc032a, 0, sizeof(*gc032a));
//i2c
ret = of_property_read_u32(np, "sensor_twi_id", &gc032a->twi_id);
if (ret) {
gc032a->twi_id = 0;
pr_err("fetch sensor_twi_id from device_tree failed\n");
}
ret = of_property_read_u32(np, "sensor_twi_addr", &gc032a->twi_addr);
if (ret) {
gc032a->twi_addr = 0;
pr_err("fetch sensor_twi_addr from device_tree failed\n");
}
//sensor_pwdn
gc032a->sensor_pwdn.gpio = of_get_named_gpio_flags(np, "sensor_pwdn", 0,
(enum of_gpio_flags *)(&gc032a->sensor_pwdn));
if (!gpio_is_valid(gc032a->sensor_pwdn.gpio))
pr_err("%s: sensor_pwdn is invalid.\n", __func__);
//sensor_cs
gc032a->sensor_cs.gpio = of_get_named_gpio_flags(np, "sensor_cs", 0,
(enum of_gpio_flags *)(&gc032a->sensor_cs));
if (!gpio_is_valid(gc032a->sensor_cs.gpio))
pr_err("%s: sensor_cs is invalid.\n", __func__);
//cb interrupt
gc032a->sensor_cb.gpio = of_get_named_gpio_flags(np, "sensor_cb", 0,
(enum of_gpio_flags *)(&gc032a->sensor_cb));
if (!gpio_is_valid(gc032a->sensor_cb.gpio))
pr_err("%s: sensor_cb is invalid.\n", __func__);
//to strip buffer or not
ret = of_property_read_u32(np, "sensor_strip_buffer", &gc032a->strip_buffer);
if (ret) {
gc032a->strip_buffer = 0;
pr_err("fetch strip_buffer config from device_tree failed\n");
}
//frame head and tail size
ret = of_property_read_u32(np, "sensor_frame_unit_size", &gc032a->frame_unit_size);
if (ret) {
gc032a->frame_unit_size = 0;
pr_err("fetch frame unit size from device_tree failed\n");
}
//line head and tail size
ret = of_property_read_u32(np, "sensor_line_unit_size", &gc032a->line_unit_size);
if (ret) {
gc032a->line_unit_size = 0;
pr_err("fetch line unit size from device_tree failed\n");
}
/* request gpio */
gc032a->sensor_pwdn_io = os_gpio_request(&gc032a->sensor_pwdn);
os_gpio_set(&gc032a->sensor_pwdn);
gc032a->sensor_cs_io = os_gpio_request(&gc032a->sensor_cs);
os_gpio_set(&gc032a->sensor_cs);
gc032a->sensor_cb_io = os_gpio_request(&gc032a->sensor_cb);
gpio_direction_input(gc032a->sensor_cb.gpio);
gc032a->cb_irq = gpio_to_irq(gc032a->sensor_cb.gpio);
/* request irq */
if (devm_request_irq(&spi->dev, gc032a->cb_irq, spi_sensor_isr,
IRQF_TRIGGER_FALLING, "spi-sensor-cb", gc032a)) {
pr_err("%s request irq %d failure\n", __func__, gc032a->cb_irq);
} else {
pr_info("%s request irq success, cb_irq:%d\n", __func__, gc032a->cb_irq);
}
disable_irq(gc032a->cb_irq);
spin_lock_init(&gc032a->slock);
mutex_init(&gc032a->buf_lock);
INIT_LIST_HEAD(&gc032a->vidq.active);
/* receive work task init */
INIT_WORK(&gc032a->spi_rx_work, spi_rx_work_handle);
/* v4l2 device register */
v4l2_dev = &gc032a->v4l2_dev;
strlcpy(v4l2_dev->name, "spi-sensor", sizeof(v4l2_dev->name));
ret = v4l2_device_register(&spi->dev, v4l2_dev);
if (ret)
pr_err("%s: error registering v4l2 device\n", __func__);
dev_set_drvdata(&spi->dev, gc032a);
i2c_adap = i2c_get_adapter(gc032a->twi_id);
gc032a->sensor_i2c_board.addr = (unsigned short)(gc032a->twi_addr >> 1);
strncpy(gc032a->sensor_i2c_board.type,
spi->modalias, sizeof(gc032a->sensor_i2c_board.type));
pr_info("%s: sub device register %s i2c_addr = 0x%x start\n",
__func__, gc032a->sensor_i2c_board.type, gc032a->twi_addr);
gc032a->sd = v4l2_i2c_new_subdev_board(&gc032a->v4l2_dev, i2c_adap,
&gc032a->sensor_i2c_board, NULL);
if (IS_ERR_OR_NULL(gc032a->sd)) {
i2c_put_adapter(i2c_adap);
pr_err("%s: error registering v4l2 subdevice No such device!\n", __func__);
}
/* subdev register is OK, check sensor init */
ret = v4l2_subdev_call(gc032a->sd, core, s_power, PWR_ON);
pr_info("%s: sensor power_on______________________\n", __func__);
ret = v4l2_subdev_call(gc032a->sd, core, init, 0);
v4l2_subdev_call(gc032a->sd, core, s_power, PWR_OFF);
pr_info("%s: sensor power_off______________________\n", __func__);
q = &gc032a->vb_vidq;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
q->drv_priv = gc032a;
q->buf_struct_size = sizeof(struct spi_buffer);
q->ops = &spi_video_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &gc032a->buf_lock;
ret = vb2_queue_init(q);
if (ret) {
pr_err("vb2_queue_init() failed\n");
return ret;
}
//video_device, put vb2_queue in
vdev = &gc032a->video;
vdev->v4l2_dev = &gc032a->v4l2_dev;
dev_set_name(&vdev->dev, "spi-sensor");
vdev->fops = &spi_sensor_fops;
vdev->ioctl_ops = &spi_sensor_ioctl_ops;
vdev->release = video_device_release;
vdev->queue = &gc032a->vb_vidq;
vdev->lock = &gc032a->buf_lock;
ret = video_register_device(vdev, VFL_TYPE_GRABBER, 0);
if (ret < 0)
pr_err("%s: video register device fail\n", __func__);
video_set_drvdata(vdev, gc032a);
mutex_init(&gc032a->spi_rx_mutex);
spi->bits_per_word = 8;
spi_setup(spi);
gc032a->spidev = spi;
gc032a->state = 0;
return ret;
}
static int spi_sensor_remove(struct spi_device *spi)
{
struct spi_sensor *gc032a = (struct spi_sensor *)dev_get_drvdata(&spi->dev);
os_gpio_release(gc032a->sensor_pwdn_io);
os_gpio_release(gc032a->sensor_cs_io);
os_gpio_release(gc032a->sensor_cb_io);
devm_free_irq(&spi->dev, gc032a->cb_irq, (void *)gc032a);
video_unregister_device(&gc032a->video);
v4l2_device_unregister(&gc032a->v4l2_dev);
//spin_destroy(gc032a->slock);
mutex_destroy(&gc032a->spi_rx_mutex);
mutex_destroy(&gc032a->buf_lock);
dev_set_drvdata(&spi->dev, NULL);
kfree(gc032a);
return 0;
}
static const struct spi_device_id spi_sensor_ids[] = {
{SPI_SENSOR_NAME, 0}
};
static const struct of_device_id of_match_spi_sensor[] = {
{.compatible = SPI_SENSOR_NAME,},
};
static struct spi_driver spi_sensor_driver = {
.probe = spi_sensor_probe,
.remove = spi_sensor_remove,
.driver = {
.name = SPI_SENSOR_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_spi_sensor,
},
.id_table = spi_sensor_ids,
};
static int spi_sensor_init(void)
{
pr_info("%s\n", __func__);
spi_register_driver(&spi_sensor_driver);
return 0;
}
static void spi_sensor_exit(void)
{
pr_info("%s\n", __func__);
spi_unregister_driver(&spi_sensor_driver);
}
module_init(spi_sensor_init);
module_exit(spi_sensor_exit);
MODULE_LICENSE("GPL");