| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * drivers/amlogic/media/video_processor/v4lvideo/v4lvideo.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| */ |
| |
| #include <linux/amlogic/major.h> |
| #include <linux/anon_inodes.h> |
| #include <linux/file.h> |
| //#include <linux/amlogic/media/utils/am_com.h> |
| #include <linux/mm.h> |
| #include <linux/module.h> |
| #include <linux/errno.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/compat.h> |
| #include <linux/mutex.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/of_fdt.h> |
| #include <linux/dma-contiguous.h> |
| #include <linux/of_reserved_mem.h> |
| #include <linux/uaccess.h> |
| #include <linux/amlogic/media/vfm/vframe.h> |
| //#include <linux/amlogic/media/registers/cpu_version.h> |
| #include "di_v4l.h" |
| #include <linux/platform_device.h> |
| #include <linux/amlogic/major.h> |
| #include <linux/amlogic/media/codec_mm/codec_mm.h> |
| #include <linux/amlogic/media/utils/amstream.h> |
| |
| #define V4L_DI_DEVICE_NAME "di_v4l" |
| |
| static struct device *devp; |
| |
| static u32 print_flag; |
| static u32 dump_out; |
| static int instance_no; |
| static u32 empty_done_count; |
| |
| loff_t pos; |
| static struct di_buffer di_outputcrc[4]; |
| static struct vframe_s vfcrc[4]; |
| |
| #define PRINT_ERROR 0X0 |
| #define PRINT_INFO 0X1 |
| #define PRINT_QUEUE_STATUS 0X0002 |
| #define PRINT_OTHER 0X0004 |
| #define DUMP_NAME_SIZE 64 |
| |
| struct crc_info_t crc_info[11] = {0}; |
| |
| int di_v4l_print(int index, int debug_flag, const char *fmt, ...) |
| { |
| if ((print_flag & debug_flag) || |
| debug_flag == PRINT_ERROR) { |
| unsigned char buf[256]; |
| int len = 0; |
| va_list args; |
| |
| va_start(args, fmt); |
| len = sprintf(buf, "di_v4l:[%d]", index); |
| vsnprintf(buf + len, 256 - len, fmt, args); |
| pr_info("%s", buf); |
| va_end(args); |
| } |
| return 0; |
| } |
| |
| static u8 __iomem *map_virt_from_phys(phys_addr_t phys, |
| unsigned long total_size) |
| { |
| u32 offset, npages; |
| struct page **pages = NULL; |
| pgprot_t pgprot; |
| u8 __iomem *vaddr; |
| int i; |
| |
| npages = PAGE_ALIGN(total_size) / PAGE_SIZE; |
| offset = phys & (~PAGE_MASK); |
| if (offset) |
| npages++; |
| pages = vmalloc(sizeof(struct page *) * npages); |
| if (!pages) |
| return NULL; |
| for (i = 0; i < npages; i++) { |
| pages[i] = phys_to_page(phys); |
| phys += PAGE_SIZE; |
| } |
| /*nocache*/ |
| pgprot = pgprot_noncached(PAGE_KERNEL); |
| |
| vaddr = vmap(pages, npages, VM_MAP, pgprot); |
| if (!vaddr) { |
| pr_err("vmaped fail, size: %d\n", |
| npages << PAGE_SHIFT); |
| vfree(pages); |
| return NULL; |
| } |
| vfree(pages); |
| |
| return vaddr; |
| } |
| |
| static void unmap_virt_from_phys(u8 __iomem *vaddr) |
| { |
| if (vaddr) { |
| vunmap(vaddr); |
| vaddr = NULL; |
| } |
| } |
| |
| static void codec_dma_flush(char *buf_start, u32 buf_size) |
| { |
| codec_mm_dma_flush((void *)buf_start, |
| buf_size, |
| DMA_FROM_DEVICE); |
| } |
| |
| uint32_t empty_input_done(struct di_buffer *buf) |
| { |
| struct di_v4l_dev *dev = NULL; |
| unsigned int tmp; |
| |
| if (!buf) { |
| pr_err("di_v4l:empty di_buffer is NULL\n"); |
| return 0; |
| } |
| |
| if (empty_done_count > 5) { |
| //pr_err("vf is over\n"); |
| return 0; |
| } |
| |
| dev = (struct di_v4l_dev *)(buf->caller_data); |
| |
| if (buf->flag & DI_FLAG_EOS) |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "eos\n"); |
| |
| if (!buf->vf) { |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "vf is NULL\n"); |
| return 0; |
| } |
| |
| dev->empty_done_count++; |
| empty_done_count++; |
| |
| di_v4l_print(dev->index, PRINT_INFO, |
| "index=%d\n", dev->empty_done_count); |
| |
| tmp = kfifo_len(&dev->di_input_free_q) / sizeof(unsigned int); |
| |
| if ((buf->flag & DI_FLAG_BUF_BY_PASS) == 0) |
| if (!kfifo_put(&dev->di_input_free_q, buf)) |
| di_v4l_print(dev->index, PRINT_INFO, |
| "put di_input_free_q fail=%d,count=%d", tmp, |
| dev->empty_done_count); |
| |
| return 0; |
| } |
| |
| static void dump_yuv_data(struct di_v4l_dev *dev, |
| struct vframe_s *vf, u32 index) |
| { |
| struct file *fp; |
| mm_segment_t fs; |
| char name_buf[DUMP_NAME_SIZE]; |
| u32 write_size; |
| u32 phy_addr_y; |
| u32 phy_addr_uv; |
| u32 size_y; |
| u32 size_uv; |
| u32 size_total; |
| void __iomem *buffer_start; |
| char *p; |
| u32 w; |
| u32 h; |
| |
| w = vf->canvas0_config[0].width; |
| h = vf->canvas0_config[0].height; |
| di_v4l_print(dev->index, PRINT_INFO, |
| "w=%d, h=%d, vf_w=%d, vf_h=%d, block_mode=%x, endian=%x, block_mode1=%x, endian1=%x, bitdepth=%d, type=%d\n", |
| w, h, |
| vf->width, |
| vf->height, |
| vf->canvas0_config[0].block_mode, |
| vf->canvas0_config[0].endian, |
| vf->canvas0_config[1].block_mode, |
| vf->canvas0_config[1].endian, |
| vf->bitdepth, |
| vf->type); |
| |
| phy_addr_y = vf->canvas0_config[0].phy_addr; |
| phy_addr_uv = vf->canvas0_config[1].phy_addr; |
| size_y = w * h; |
| size_uv = size_y / 2; |
| size_total = size_y + size_uv; |
| |
| snprintf(name_buf, DUMP_NAME_SIZE, "/data/tmp/diout%d-%d-%d.raw", |
| vf->width, vf->height, index); |
| |
| fs = get_fs(); |
| set_fs(KERNEL_DS); |
| fp = filp_open(name_buf, O_WRONLY | O_CREAT, 0666); |
| if (IS_ERR(fp)) { |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "create %s fail.\n", name_buf); |
| } else { |
| write_size = size_total; |
| buffer_start = map_virt_from_phys(phy_addr_y, size_total); |
| p = (char *)buffer_start; |
| vfs_write(fp, p, size_total, &pos); |
| unmap_virt_from_phys(buffer_start); |
| filp_close(fp, NULL); |
| } |
| set_fs(fs); |
| } |
| |
| uint32_t fill_output_done(struct di_buffer *buf) |
| { |
| struct di_v4l_dev *dev = NULL; |
| |
| if (!buf) { |
| pr_err("di_v4l: di_buffer is NULL\n"); |
| return 0; |
| } |
| dev = (struct di_v4l_dev *)(buf->caller_data); |
| |
| if (buf->flag & DI_FLAG_EOS) { |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "%s: eos\n", __func__); |
| } |
| |
| if (!buf->vf) { |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "%s: vf is NULL\n", __func__); |
| return 0; |
| } |
| if (dev->fill_done_count > 5) |
| crc_info->crcdata[dev->fill_done_count] = buf->nrcrcout; |
| else |
| crc_info->crcdata[dev->fill_done_count] = buf->crcout; |
| |
| crc_info->done_flag[dev->fill_done_count] = true; |
| |
| di_v4l_print(dev->index, PRINT_OTHER, |
| "%s count=%d, crc =%x\n", |
| __func__, |
| dev->fill_done_count, |
| crc_info->crcdata[dev->fill_done_count]); |
| |
| dev->fill_done_count++; |
| |
| di_v4l_print(dev->index, PRINT_OTHER, |
| "%s: index=%d\n", __func__, buf->vf->omx_index); |
| |
| if (dump_out) |
| dump_yuv_data(dev, buf->vf, dev->fill_done_count); |
| |
| if (!kfifo_put(&dev->di_output_di_q, buf)) |
| di_v4l_print(dev->index, PRINT_ERROR, "%s:putfail\n", __func__); |
| return 0; |
| } |
| |
| static void di_buf_init(struct di_v4l_dev *dev, struct yuv_info_t *yuv_info) |
| { |
| int i; |
| int buf_size; |
| int flags = CODEC_MM_FLAGS_DMA | CODEC_MM_FLAGS_CMA_CLEAR; |
| struct vframe_s *vf = NULL; |
| struct di_buffer *di_buf; |
| int w; |
| int h; |
| int buf_w; |
| int buf_h; |
| |
| dev->yuv_info = *yuv_info; |
| w = yuv_info->width; |
| h = yuv_info->height; |
| buf_w = yuv_info->buffer_w; |
| buf_h = yuv_info->buffer_h; |
| |
| di_v4l_print(dev->index, PRINT_INFO, |
| "w = %d, h = %d\n", w, h); |
| buf_size = buf_w * buf_h * 3 / 2; |
| dev->in_buf_ready = true; |
| |
| INIT_KFIFO(dev->di_input_free_q); |
| kfifo_reset(&dev->di_input_free_q); |
| |
| INIT_KFIFO(dev->di_output_di_q); |
| kfifo_reset(&dev->di_output_di_q); |
| |
| for (i = 0; i < DI_INPUT_SIZE; i++) { |
| if (dev->in_buf[i].phy_addr == 0) |
| dev->in_buf[i].phy_addr = |
| codec_mm_alloc_for_dma("di_v4l", |
| buf_size / PAGE_SIZE, |
| 0, flags); |
| di_v4l_print(dev->index, PRINT_OTHER, |
| "cma memory is %x , size is %x\n", |
| (unsigned int)dev->in_buf[i].phy_addr, |
| (unsigned int)buf_size); |
| |
| if (dev->in_buf[i].phy_addr == 0) { |
| dev->in_buf_ready = false; |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "cma memory config fail\n"); |
| return; |
| } |
| dev->in_buf[i].index = i; |
| dev->in_buf[i].buf_w = buf_w; |
| dev->in_buf[i].buf_h = buf_h; |
| dev->in_buf[i].buf_size = buf_size; |
| vf = &dev->in_buf[i].frame; |
| memset(vf, 0, sizeof(struct vframe_s)); |
| vf->canvas0Addr = -1; |
| vf->canvas0_config[0].phy_addr = dev->in_buf[i].phy_addr; |
| vf->canvas0_config[0].width = buf_w; |
| vf->canvas0_config[0].height = buf_h; |
| vf->canvas0_config[0].block_mode = 1; |
| vf->canvas0_config[0].endian = 0; |
| |
| vf->canvas1Addr = -1; |
| vf->canvas0_config[1].phy_addr = dev->in_buf[i].phy_addr |
| + vf->canvas0_config[0].width |
| * vf->canvas0_config[0].height; |
| vf->canvas0_config[1].width = buf_w; |
| vf->canvas0_config[1].height = buf_h; |
| vf->canvas0_config[1].block_mode = 1; |
| vf->canvas0_config[1].endian = 0; |
| |
| vf->width = w; |
| vf->height = h; |
| vf->plane_num = 2; |
| vf->type = VIDTYPE_VIU_NV21 |
| | VIDTYPE_INTERLACE_FIRST |
| | VIDTYPE_INTERLACE_TOP; |
| vf->bitdepth = 0; |
| //BITDEPTH_Y8 | BITDEPTH_U8 | BITDEPTH_V8; |
| vf->flag = 0x10; |
| |
| di_buf = &dev->in_buf[i].di_buf; |
| di_buf->vf = vf; |
| di_buf->caller_data = (void *)dev; |
| di_buf->flag = 0; |
| di_buf->phy_addr = dev->in_buf[i].phy_addr; |
| if (!kfifo_put(&dev->di_input_free_q, di_buf)) |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "init buffer free_q is full\n"); |
| } |
| } |
| |
| static void di_buf_uninit(struct di_v4l_dev *dev) |
| { |
| int i; |
| |
| for (i = 0; i < DI_INPUT_SIZE; i++) { |
| if (dev->in_buf[i].phy_addr != 0) { |
| di_v4l_print(dev->index, PRINT_INFO, |
| "cma free addr is %x\n", |
| (unsigned int)dev->in_buf[i].phy_addr); |
| codec_mm_free_for_dma("di_v4l", |
| dev->in_buf[i].phy_addr); |
| dev->in_buf[i].phy_addr = 0; |
| } |
| } |
| } |
| |
| static void di_init(struct di_v4l_dev *dev) |
| { |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "begin %s\n", __func__); |
| |
| dev->di_index = -1; |
| |
| dev->di_parm.work_mode = WORK_MODE_PRE_POST; |
| dev->di_parm.buffer_mode = BUFFER_MODE_ALLOC_BUF; |
| dev->di_parm.output_format = 0; |
| dev->di_parm.ops.empty_input_done = empty_input_done; |
| dev->di_parm.ops.fill_output_done = fill_output_done; |
| dev->di_parm.caller_data = (void *)dev; |
| dev->di_index = di_create_instance(dev->di_parm); |
| if (dev->di_index < 0) { |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "creat di fail, di_index=%d\n", |
| dev->di_index); |
| return; |
| } |
| di_v4l_print(dev->index, PRINT_OTHER, |
| "di_index = %d\n", dev->di_index); |
| |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "end %s\n", __func__); |
| dev->empty_done_count = 0; |
| dev->fill_done_count = 0; |
| dev->fill_frame_count = 0; |
| dev->fill_crc_count = 0; |
| empty_done_count = 0; |
| } |
| |
| static void di_uninit(struct di_v4l_dev *dev) |
| { |
| int ret; |
| |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "begin %s\n", __func__); |
| |
| if (dev->di_index >= 0) { |
| ret = di_destroy_instance(dev->di_index); |
| if (ret != 0) |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "destroy di fail, di_index=%d\n", |
| dev->di_index); |
| dev->di_index = -1; |
| } |
| |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "end %s\n", __func__); |
| } |
| |
| static int di_v4l_open(struct inode *inode, struct file *file) |
| { |
| struct di_v4l_dev *dev; |
| |
| dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| file->private_data = dev; |
| di_init(dev); |
| instance_no++; |
| pos = 0; |
| |
| return 0; |
| } |
| |
| static int di_v4l_release(struct inode *inode, struct file *file) |
| { |
| struct di_v4l_dev *dev = file->private_data; |
| struct di_buffer *di_buf = NULL; |
| |
| while (kfifo_get(&dev->di_output_di_q, &di_buf)) { |
| di_fill_output_buffer(dev->di_index, di_buf); |
| dev->fill_count++; |
| } |
| |
| di_uninit(dev); |
| di_buf_uninit(dev); |
| while (kfifo_get(&dev->di_output_di_q, &di_buf)) { |
| di_release_keep_buf(di_buf); |
| dev->fill_count++; |
| } |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "fill_done_count %d, fill_count=%d\n", |
| dev->fill_done_count, |
| dev->fill_count); |
| |
| kfree(dev); |
| return 0; |
| } |
| |
| static ssize_t di_v4l_write(struct file *file, const char *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct di_v4l_dev *dev = file->private_data; |
| char *dst; |
| unsigned long phy_addr; |
| int ret; |
| struct di_buffer *di_buf = NULL; |
| struct di_buffer *di_bufcrc = &di_outputcrc[0]; |
| struct vframe_s *pvfcrc = &vfcrc[0]; |
| int i; |
| |
| di_v4l_print(dev->index, PRINT_INFO, |
| "di_v4l_write__1\n"); |
| if (dev->fill_frame_count < 3) { |
| for (i = 0; i < 2; i++) { |
| if (kfifo_get(&dev->di_input_free_q, &di_buf)) { |
| phy_addr = di_buf->phy_addr; |
| dst = map_virt_from_phys(phy_addr, count); |
| if (copy_from_user(dst, buf, count)) { |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "copy_from_user err\n"); |
| unmap_virt_from_phys(dst); |
| return -EFAULT; |
| } |
| //dma_flush(phy_addr, count); |
| codec_dma_flush(dst, count); |
| unmap_virt_from_phys(dst); |
| if (i == 0) |
| di_buf->vf->type = VIDTYPE_VIU_NV21 |
| | VIDTYPE_INTERLACE_FIRST |
| | VIDTYPE_INTERLACE_TOP; |
| else |
| di_buf->vf->type = VIDTYPE_VIU_NV21 |
| | VIDTYPE_INTERLACE_FIRST |
| | VIDTYPE_INTERLACE_BOTTOM; |
| ret = di_empty_input_buffer(dev->di_index, |
| di_buf); |
| di_v4l_print(dev->index, PRINT_INFO, |
| "canvaswidth=%d,height=%d\n", |
| di_buf->vf->canvas0_config[0].width, |
| di_buf->vf->canvas0_config[0].height); |
| di_v4l_print(dev->index, PRINT_INFO, |
| "bufWidth=%d, width=%d,height=%d\n", |
| di_buf->vf->bufWidth, di_buf->vf->width, |
| di_buf->vf->height); |
| di_v4l_print(dev->index, PRINT_INFO, |
| "type=%d, plane_num=%d\n", |
| di_buf->vf->type, |
| di_buf->vf->plane_num); |
| if (ret != 0) |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "empty_input err:ret=%d, di_index=%d\n", |
| ret, dev->di_index); |
| } else { |
| return -ENOMEM; |
| } |
| } |
| dev->fill_frame_count++; |
| di_v4l_print(dev->index, PRINT_INFO, |
| "di_v4l_write__2\n"); |
| } |
| |
| if (dev->fill_frame_count == 3) { |
| while (1) { |
| if (dev->fill_frame_count > 7) |
| break; |
| di_v4l_print(dev->index, PRINT_INFO, |
| "di_v4l_writecrc__5 start\n"); |
| if (kfifo_get(&dev->di_output_di_q, &di_buf)) { |
| memcpy(pvfcrc, di_buf->vf, |
| sizeof(struct vframe_s)); |
| pvfcrc->private_data = NULL; |
| //di_bufcrc->private_data = NULL; |
| di_bufcrc->flag = di_buf->flag; |
| di_bufcrc->vf = pvfcrc; |
| if (dump_out) |
| dump_yuv_data(dev, di_bufcrc->vf, |
| dev->fill_frame_count); |
| ret = di_empty_input_buffer |
| (dev->di_index, di_bufcrc); |
| if (ret != 0) |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "empty_input err:ret=%d, di_index=%d\n", |
| ret, dev->di_index); |
| di_fill_output_buffer(dev->di_index, |
| di_buf); |
| dev->fill_frame_count++; |
| di_bufcrc++; |
| pvfcrc++; |
| di_v4l_print(dev->index, PRINT_INFO, |
| "di_v4l_writecrc__6 end\n"); |
| } else { |
| di_v4l_print(dev->index, PRINT_INFO, |
| "output_di_q\n"); |
| //usleep(1000 * 5); |
| } |
| } |
| } |
| return count; |
| } |
| |
| static long di_v4l_ioctl(struct file *file, |
| unsigned int cmd, |
| ulong arg) |
| { |
| long ret = 0; |
| void __user *argp = (void __user *)arg; |
| struct di_v4l_dev *dev = file->private_data; |
| |
| switch (cmd) { |
| case DI_V4L_IOCTL_GET_CRC:{ |
| struct di_buffer *di_buf = NULL; |
| |
| if (crc_info->done_flag[dev->fill_crc_count] && |
| dev->fill_frame_count > 7) { |
| di_v4l_print(dev->index, PRINT_INFO, |
| "get crc ok\n"); |
| put_user(crc_info->crcdata[dev->fill_crc_count], |
| (u32 __user *)argp); |
| if (kfifo_get(&dev->di_output_di_q, &di_buf)) |
| di_fill_output_buffer(dev->di_index, |
| di_buf); |
| dev->fill_count++; |
| dev->fill_crc_count++; |
| } else { |
| di_v4l_print(dev->index, PRINT_INFO, |
| "get crc ng\n"); |
| return -EAGAIN; |
| } |
| } |
| break; |
| case DI_V4L_IOCTL_INIT:{ |
| struct yuv_info_t yuv_info; |
| |
| di_v4l_print(dev->index, PRINT_ERROR, |
| "DI_V4L_IOCTL_INIT\n"); |
| if (copy_from_user((void *)&yuv_info, (void *)arg, |
| sizeof(struct yuv_info_t))) |
| return -EFAULT; |
| di_buf_init(dev, &yuv_info); |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long di_v4l_compat_ioctl(struct file *file, |
| unsigned int cmd, |
| ulong arg) |
| { |
| long ret = 0; |
| |
| ret = di_v4l_ioctl(file, cmd, (ulong)compat_ptr(arg)); |
| return ret; |
| } |
| #endif |
| |
| static const struct file_operations di_v4l_fops = { |
| .owner = THIS_MODULE, |
| .open = di_v4l_open, |
| .release = di_v4l_release, |
| .write = di_v4l_write, |
| .unlocked_ioctl = di_v4l_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = di_v4l_compat_ioctl, |
| #endif |
| .poll = NULL, |
| }; |
| |
| static ssize_t print_flag_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%d\n", print_flag); |
| } |
| |
| static ssize_t print_flag_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| ssize_t r; |
| int val; |
| |
| r = kstrtoint(buf, 0, &val); |
| if (r < 0) |
| return -EINVAL; |
| |
| print_flag = val; |
| pr_info("set print_flag:%d\n", print_flag); |
| return count; |
| } |
| |
| static ssize_t dump_out_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%d\n", dump_out); |
| } |
| |
| static ssize_t dump_out_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| ssize_t r; |
| int val; |
| |
| r = kstrtoint(buf, 0, &val); |
| if (r < 0) |
| return -EINVAL; |
| |
| dump_out = val; |
| pr_info("set dump_out:%d\n", dump_out); |
| return count; |
| } |
| |
| static CLASS_ATTR_RW(print_flag); |
| static CLASS_ATTR_RW(dump_out); |
| |
| static struct attribute *di_v4l_class_attrs[] = { |
| &class_attr_print_flag.attr, |
| &class_attr_dump_out.attr, |
| NULL |
| }; |
| |
| ATTRIBUTE_GROUPS(di_v4l_class); |
| |
| static struct class di_v4l_class = { |
| .name = "di_v4l", |
| .class_groups = di_v4l_class_groups, |
| }; |
| |
| int __init di_v4l_init(void) |
| { |
| int ret = -1; |
| int r; |
| |
| pr_info("%s init\n", __func__); |
| ret = class_register(&di_v4l_class); |
| if (ret < 0) |
| return ret; |
| |
| ret = register_chrdev(DI_V4L_MAJOR, "di_v4l", &di_v4l_fops); |
| if (ret < 0) { |
| pr_err("Can't allocate major for di_v4l device\n"); |
| goto error1; |
| } |
| |
| devp = device_create(&di_v4l_class, |
| NULL, |
| MKDEV(DI_V4L_MAJOR, 0), |
| NULL, |
| V4L_DI_DEVICE_NAME); |
| if (IS_ERR(devp)) { |
| pr_err("failed to create di_v4l device node\n"); |
| ret = PTR_ERR(devp); |
| return ret; |
| } |
| |
| r = of_reserved_mem_device_init(devp); |
| if (r < 0) |
| pr_err("%s reserved mem is not used.\n", __func__); |
| pr_info("%s out\n", __func__); |
| |
| return 0; |
| error1: |
| pr_info("%s err\n", __func__); |
| unregister_chrdev(DI_V4L_MAJOR, "di_v4l"); |
| class_unregister(&di_v4l_class); |
| return ret; |
| } |
| |
| void __exit di_v4l_exit(void) |
| { |
| device_destroy(&di_v4l_class, MKDEV(DI_V4L_MAJOR, 0)); |
| unregister_chrdev(DI_V4L_MAJOR, V4L_DI_DEVICE_NAME); |
| class_unregister(&di_v4l_class); |
| } |
| |
| //#ifndef MODULE |
| //module_init(di_v4l_init); |
| //module_exit(di_v4l_exit); |
| //#endif |
| |