/***************************************************************************//** * @file histograms.c * @brief Handles histograms storage, access and percentile calculation * @copyright Copyright 2008-2016 (C) Elphel, Inc. * @par <b>License</b> * 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. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ /* -----------------------------------------------------------------------------** *! $Log: histograms.c,v $ *! Revision 1.3 2009/02/18 06:25:59 elphel *! typo in format *! *! Revision 1.2 2008/11/30 21:56:39 elphel *! Added enforcing limit on the overall gains in the color channels, storage of exposure and gains in the histograms cache (to be used with autoexposure/white balance) *! *! Revision 1.1.1.1 2008/11/27 20:04:00 elphel *! *! *! Revision 1.17 2008/11/14 07:08:11 elphel *! no request for the histogram if past histogram is needed and some exist in the past *! *! Revision 1.16 2008/11/13 05:40:45 elphel *! 8.0.alpha16 - modified histogram storage, profiling *! *! Revision 1.15 2008/10/29 04:18:28 elphel *! v.8.0.alpha10 made a separate structure for global parameters (not related to particular frames in a frame queue) *! *! Revision 1.14 2008/10/28 07:04:28 elphel *! driver returns histogram frame number (it lags one frame from the real frame number) *! *! Revision 1.13 2008/10/25 19:59:48 elphel *! added lseek() calls to enable/disable daemons at events (compressed frame available, any frame available, histogram-Y and histograms-C available) *! *! Revision 1.12 2008/10/23 18:26:14 elphel *! Fixed percentile calculations in histograms *! *! Revision 1.11 2008/10/23 08:05:56 elphel *! added 2 wait queues for histogram data - separately for G1 (used as Y for autoexposure) and other color components (for color balancing and histogram display) *! *! Revision 1.10 2008/10/12 16:46:22 elphel *! snapshot *! *! Revision 1.9 2008/10/04 16:10:12 elphel *! snapshot *! *! Revision 1.8 2008/09/20 00:29:50 elphel *! moved driver major/minor numbers to a single file - include/asm-cris/elphel/driver_numbers.h *! *! Revision 1.7 2008/09/12 00:23:59 elphel *! removed cc353.c, cc353.h *! *! Revision 1.6 2008/09/07 19:48:09 elphel *! snapshot *! *! Revision 1.5 2008/09/05 23:20:26 elphel *! just a snapshot *! *! Revision 1.4 2008/07/27 23:25:07 elphel *! next snapshot *! *! Revision 1.3 2008/06/16 06:51:21 elphel *! work in progress, intermediate commit *! *! Revision 1.2 2008/06/10 00:02:42 elphel *! fast calculation of 8-bit reverse functions for "gamma" tables and histograms *! *! Revision 1.1 2008/06/08 23:46:45 elphel *! added drivers files for handling quantization tables, gamma tables and the histograms */ //copied from cxi2c.c - TODO:remove unneeded //#define DEBUG // should be before linux/module.h - enables dev_dbg at boot in this file (needs "debug" in bootarg) #include <linux/module.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/string.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/spinlock.h> //#include <linux/autoconf.h> #include <linux/vmalloc.h> //#include <asm/system.h> #include <asm/byteorder.h> // endians #include <asm/io.h> #include <asm/irq.h> #include <asm/delay.h> #include <asm/uaccess.h> #include <asm/outercache.h> #include <asm/cacheflush.h> #include <linux/dma-mapping.h> #include <linux/dma-direction.h> // ##include <asm/dma-mapping.h> #include <uapi/elphel/x393_devices.h> //#include <uapi/elphel/c313a.h> #include <uapi/elphel/exifa.h> //#include "fpgactrl.h" // defines port_csp0_addr, port_csp4_addr //#include "fpga_io.h"//fpga_table_write_nice #include "framepars.h" // for debug mask #include <elphel/elphel393-mem.h> #include "x393.h" #include "histograms.h" #include "detect_sensors.h" #include "x393_fpga_functions.h" // to check bitsteram /** * \def MDF21(x) optional debug output */ #if ELPHEL_DEBUG // only when commands are issued #define MDF21(x) { if (GLOBALPARS(G_DEBUG) & (1 <<21)) {printk("%s:%d:%s ",__FILE__,__LINE__,__FUNCTION__);x ;} } // includes tasklets #define MDF22(x) { if (GLOBALPARS(G_DEBUG) & (1 <<22)) {printk("%s:%d:%s ",__FILE__,__LINE__,__FUNCTION__);x ;} } #else #define MDF21(x) #define MDF22(x) #endif //u32 (*fpga_hist_data)[SENSOR_PORTS][MAX_SENSORS][PARS_FRAMES][4][256]; ///< Array of histogram data, mapped to the memory wheer FPGA sends data //u32 *fpga_hist_data[SENSOR_PORTS][MAX_SENSORS][PARS_FRAMES][4][256]; ///< Array of histogram data, mapped to the memory where FPGA sends data //u32 (*fpga_hist_data)[SENSOR_PORTS][MAX_SENSORS][PARS_FRAMES][4][256]; ///< Array of histogram data, mapped to the memory where FPGA sends data //typedef u32 fpga_hist_t [SENSOR_PORTS][MAX_SENSORS][PARS_FRAMES][4][256]; //typedef u32 fpga_hist_t [][MAX_SENSORS][PARS_FRAMES][4][256]; typedef u32 (*fpga_hist_t)[MAX_SENSORS][PARS_FRAMES][4][256]; fpga_hist_t fpga_hist_data; dma_addr_t fpga_hist_phys; // physical address of the start of the received histogram data #define X3X3_HISTOGRAMS_DRIVER_DESCRIPTION "Elphel (R) Model 353 Histograms device driver" /** for each port and possible sensor subchannel provides index in combine histogram data */ int histograms_map[SENSOR_PORTS][MAX_SENSORS]; static DEFINE_SPINLOCK(histograms_init_lock); ///< do not start multiple threads of histogram structures initialization /** total number of sensors (on all ports) used */ static int numHistChn = 0; /** Variable-length array (length is the total number of active sensors <=16), each being the same as in 353: * consisting of SENSOR_PORTS histogram_stuct_t structures */ struct histogram_stuct_t (*histograms)[HISTOGRAM_CACHE_NUMBER] = NULL; //struct histogram_stuct_t *histograms; dma_addr_t histograms_phys; ///< likely not needed, saved during allocation struct histogram_stuct_t * histograms_p; ///< alias of histogram_stuct_t /** @brief Global pointer to basic device structure. This pointer is used in debugfs output functions */ static struct device *g_dev_ptr; wait_queue_head_t ahist_y_wait_queue[SENSOR_PORTS]; ///< wait queue for the G1 histogram (used as Y) wait_queue_head_t ahist_c_wait_queue[SENSOR_PORTS]; ///< wait queue for all the other (R,G2,B) histograms (color) void init_histograms(int chn_mask); ///< combined subchannels and ports Save mask to global P-variable int histograms_init_hardware(void); static volatile int histograms_initialized = 0; ///< 0 - not initialized, 1 - structures only, 2 structures and hardware NC393: initialize when first used? /** Check if histograms structures are initialized, initialize if not */ int histograms_check_init(void) { int sensor_port, chn_mask=0; if (histograms_initialized > 1) return histograms_initialized; spin_lock(&histograms_init_lock); if (histograms_initialized > 1) { spin_unlock(&histograms_init_lock); return histograms_initialized; } if (!histograms_initialized){ dev_dbg(g_dev_ptr, "need to initialize histograms structures"); for (sensor_port = 0; sensor_port < SENSOR_PORTS; sensor_port++){ chn_mask |= get_subchannels(sensor_port) << (MAX_SENSORS * sensor_port); dev_dbg(g_dev_ptr, "sensor_port=%d, chn_mask updated to 0x%x", sensor_port, chn_mask); } init_histograms(chn_mask); histograms_initialized = 1; // structures initialized } if (is_fpga_programmed() >0 ){ // do not try to access FPGA if it is not programmed dev_dbg(g_dev_ptr, "need to initialize histograms hardware"); if (!histograms_init_hardware()) histograms_initialized = 2; // fully initialized } dev_dbg(g_dev_ptr, "histograms_check_init() -> %d", histograms_initialized); spin_unlock(&histograms_init_lock); return histograms_initialized; } /** File operations private data */ struct histograms_pd { int minor; unsigned long frame; ///< absolute frame number requested int frame_index; ///< histogram frame index (in cache and when accessing through mmap), -1 if invalid, int needed; ///< bits specify what histograms (color, type) are requested ///< each group of 4 bits covers 4 colors of the same type: ///< - bits 0..3 - read raw histograms from the FPGA - normally called from IRQ/tasklet (use just 1 color for autoexposure to speed up?) ///< - bits 4..7 - calculate cumulative histograms (sum of raw ones) - normally called from applications ///< - bits 8..11 - calculate percentiles (reverse cumulative histograms) - normally called from applications ///< "needed" for raw histograms should be specified explicitly (can not be read from FPGA later), ///< "needed" for cumul_hist will be added automatically if percentiles are requested int wait_mode; ///< 0 - wait just for G1 histogram, 1 - wait for all histograms int request_en; ///< enable requesting histogram for the specified frame (0 - rely on the available ones) int port; ///< selected sensor port (0..3) int subchannel; ///< selected sensor sub_channel(0..3) struct wait_queue *hist_y_wait_queue; ///< wait queue for the G1 histogram (used as Y) ///NOTE: not used at all? struct wait_queue *hist_c_wait_queue; ///< wait queue for all the other (R,G2,B) histograms (color) ///NOTE: not used at all? // something else to be added here? }; int histograms_open (struct inode *inode, struct file *file); int histograms_release(struct inode *inode, struct file *file); loff_t histograms_lseek (struct file * file, loff_t offset, int orig); int histograms_mmap (struct file *file, struct vm_area_struct *vma); inline unsigned long histogram_calc_cumul ( unsigned long * hist, unsigned long * cumul_hist ); inline void histogram_calc_percentiles ( unsigned long * cumul_hist, unsigned char * percentile); /** Initialize FPGA DMA engine for histograms. Obviously requires bitstream to be loaded. * Histograms will be initialized for all possible ports/channels, only some will be enabled */ int histograms_init_hardware(void) { int port, chn, hist_chn; x393_hist_saxi_addr_t saxi_addr = {.d32=0}; // fpga_hist_data = (u32 *) pElphel_buf->histograms_vaddr; // d2h_vaddr; // must be page-aligned! fpga_hist_data = (fpga_hist_t) pElphel_buf->histograms_vaddr; // d2h_vaddr; // must be page-aligned! fpga_hist_phys = pElphel_buf->histograms_paddr; //d2h_paddr; for (port=0; port<SENSOR_PORTS; port++) for (chn=0; chn < MAX_SENSORS; chn++) { hist_chn = chn + MAX_SENSORS *port; saxi_addr.page=(fpga_hist_phys >> PAGE_SHIFT)+ PARS_FRAMES * hist_chn;// table for 4 colors is exactly 1 page; set_x393_hist_saxi_addr (saxi_addr, hist_chn); // Histogram DMA addresses (in 4096 byte pages) } // Hand all data to device dma_sync_single_for_device(NULL, fpga_hist_phys, SENSOR_PORTS*MAX_SENSORS*PARS_FRAMES*4*256*4, DMA_FROM_DEVICE); histograms_dma_ctrl(2); return 0; } /** Reset/Enable/disable histograms DMA engine */ void histograms_dma_ctrl(int mode) ///< 0 - reset, 1 - disable, 2 - enable { x393_hist_saxi_mode_t saxi_mode; saxi_mode.d32= 0; saxi_mode.cache= 3; saxi_mode.confirm=1; saxi_mode.nrst= (mode)? 1:0; saxi_mode.en= (mode>1)?1:0; set_x393_hist_saxi_mode(saxi_mode); } /** Initialize histograms data structures, should be called when active subchannels are known (maybe use DT)? */ //#define HISTOGRAMS_DISABLE_IRQ void init_histograms(int chn_mask) ///< combined subchannels and ports Save mask to global P-variable { #ifdef HISTOGRAMS_DISABLE_IRQ unsigned long flags; #endif int p,s,i, sz,pages; numHistChn = 0; //__builtin_popcount (chn_mask & 0xffff); for (p=0; p< SENSOR_PORTS; p++) for (s=0;s <MAX_SENSORS;s++) { i = p * SENSOR_PORTS + s; if (chn_mask & (1 << i)){ histograms_map[p][s] = numHistChn++; GLOBALPARS(p, G_HIST_LAST_INDEX + s) =0; // mark as valid GLOBALPARS(p, G_SUBCHANNELS) |= 1 << s; } else { histograms_map[p][s] = -1; GLOBALPARS(p, G_HIST_LAST_INDEX + s) =0xffffffff; // mark as invalid GLOBALPARS(p, G_SUBCHANNELS) &= ~(1 << s); } } dev_dbg(g_dev_ptr, "Histograms structures, channel mask = 0x%x, numHistChn = 0x%x",chn_mask, numHistChn); //G_SUBCHANNELS sz = numHistChn * HISTOGRAM_CACHE_NUMBER * sizeof(struct histogram_stuct_t); pages = ((sz -1) >> PAGE_SHIFT)+1; // if (sz & (PAGE_SIZE-1)) pages++; // When device == NULL, dma_alloc_coherent just allocates notmal memory, page aligned, CMA if available // histograms = (struct histogram_stuct_t* [HISTOGRAM_CACHE_NUMBER]) dma_alloc_coherent(NULL,(sz * PAGE_SIZE),&histograms_phys,GFP_KERNEL); // histograms = (struct histogram_stuct_t[HISTOGRAM_CACHE_NUMBER] * ) dma_alloc_coherent(NULL,(sz * PAGE_SIZE),&histograms_phys,GFP_KERNEL); // histograms = (struct histogram_stuct_t[HISTOGRAM_CACHE_NUMBER]) * dma_alloc_coherent(NULL,(sz * PAGE_SIZE),&histograms_phys,GFP_KERNEL); // histograms = dma_alloc_coherent(NULL,(pages * PAGE_SIZE),&histograms_phys,GFP_KERNEL); // OK // dev_warn(g_dev_ptr, "dma_alloc_coherent(NULL, 0x%x, 0x%x,GFP_KERNEL)",pages * PAGE_SIZE, (int) histograms_phys); // This code is spin-locked above to prevent simultaneous allocation from several php instances, so GFP_KERNEL may not be used // histograms = dma_alloc_coherent(NULL,(pages * PAGE_SIZE),&histograms_phys,GFP_NOWAIT); // OK // dev_warn(g_dev_ptr, "dma_alloc_coherent(NULL, 0x%x, 0x%x,GFP_NOWAIT)",pages * PAGE_SIZE, (int) histograms_phys); // histograms = dma_alloc_coherent(NULL,(pages * PAGE_SIZE),&histograms_phys,GFP_ATOMIC); // OK dev_dbg(g_dev_ptr, "dma_alloc_coherent should be done before (@probe), needed are 0x%lx bytes)",pages * PAGE_SIZE); BUG_ON(!histograms); histograms_p= (struct histogram_stuct_t *) histograms; // MDF21(printk("\n")); #ifdef HISTOGRAMS_DISABLE_IRQ local_ irq_save(flags); #endif dev_dbg(g_dev_ptr, "Histograms structures, channel mask = 0x%x",chn_mask); for (s=0; s<numHistChn; s++) { for (i=0; i < HISTOGRAM_CACHE_NUMBER; i++) { histograms[s][i].frame=0xffffffff; histograms[s][i].valid=0; } } #ifdef HISTOGRAMS_DISABLE_IRQ local_ irq_restore(flags); #endif dev_dbg(g_dev_ptr, "Histograms structures initialized"); } /** Get histogram index for sensor port/channel, skipping unused ones */ int get_hist_index (int sensor_port, ///< sensor port number (0..3) int sensor_chn) ///< sensor subchannel (0..3, 0 w/o multiplexer) ///< @return index of used (active) histogram set (just skipping unused ports/subchannels { return histograms_map[sensor_port][sensor_chn]; } /** * @brief Get histograms from the FPGA (called as tasklet - not anymore?) * TODO: should it be one frame behind current? * each group of 4 bits cover 4 colors of the same type */ int set_histograms (int sensor_port, ///< sensor port number (0..3) int sensor_chn, ///< sensor subchannel (0 w/o multiplexer) unsigned long frame, ///< absolute frame number (Caller should match it to the hardware frame) int needed, ///< bits specify what histograms (color, type) are requested ///< each group of 4 bits covers 4 colors of the same type: ///< - bits 0..3 - read raw histograms from the FPGA - normally called from IRQ/tasklet (use just 1 color for autoexposure to speed up?) ///< - bits 4..7 - calculate cumulative histograms (sum of raw ones) - normally called from applications ///< - bits 8..11 - calculate percentiles (reverse cumulative histograms) - normally called from applications ///< "needed" for raw histograms should be specified explicitly (can not be read from FPGA later), ///< "needed" for cumul_hist will be added automatically if percentiles are requested unsigned long * gammaHash,///< array of 4 hash32 values to be saved with the histograms (same gamma for all sub-channels), NULL OK unsigned long * framep) ///< array of 8 values to copy (frame, gains,expos,vexpos, focus), NULL OK ///< @return 0 OK, -EINVAL unused port/channel { const int hist_frames_available = PARS_FRAMES - 3; // ? int i, color_start, hist_indx, hist_frame, hist_frame_index; unsigned long thisFrameNumber=getThisFrameNumber(sensor_port); dev_dbg(g_dev_ptr, "Setting histograms for frame=%ld(0x%lx), thisFrameNumber=%ld (0x%lx), needed=0x%x\n", frame,frame,thisFrameNumber,thisFrameNumber, needed); hist_indx=get_hist_index(sensor_port,sensor_chn); // channel/subchannel combination, removed unused if (hist_indx <0 ) return -EINVAL; if (frame >= thisFrameNumber) { dev_dbg(g_dev_ptr, "frame=%ld(0x%lx) >= thisFrameNumber=%ld (0x%lx) (not yet available)\n",frame,frame,thisFrameNumber,thisFrameNumber); return -EINVAL; // not yet available } // verify frame still not overwritten if (frame < (thisFrameNumber - hist_frames_available)) { dev_dbg(g_dev_ptr, "frame=%ld(0x%lx) < thisFrameNumber=%ld (0x%lx) - %d (already gone)\n",frame,frame,thisFrameNumber,thisFrameNumber,hist_frames_available); return -EINVAL; // not yet available } dev_dbg(g_dev_ptr, "Setting histograms for frame=%ld(0x%lx), thisFrameNumber=%ld (0x%lx), needed=0x%x, hist_indx=0x%x \n", frame,frame,thisFrameNumber,thisFrameNumber, needed, hist_indx); hist_frame_index = (int) GLOBALPARS(sensor_port,G_HIST_LAST_INDEX+sensor_chn); dev_dbg(g_dev_ptr, "histograms[%d][%d].frame = 0x%lx\n", hist_indx, hist_frame_index, histograms[hist_indx][hist_frame_index].frame); if (histograms[hist_indx][hist_frame_index].frame!=frame) { hist_frame_index = (hist_frame_index+1) & (HISTOGRAM_CACHE_NUMBER-1); GLOBALPARS(sensor_port, G_HIST_LAST_INDEX+sensor_chn)=hist_frame_index; histograms[hist_indx][hist_frame_index].valid=0; // overwrite all histograms[hist_indx][hist_frame_index].frame=frame; // add to existent if (framep) memcpy (&(histograms[hist_indx][hist_frame_index].frame), framep, 32); // copy provided frame, gains,expos,vexpos, focus if (gammaHash) memcpy (&(histograms[hist_indx][hist_frame_index].gtab_r), gammaHash, 16); // copy provided 4 hash32 values dev_dbg(g_dev_ptr, "histograms[%d][%d].frame = 0x%lx\n", hist_indx, hist_frame_index, histograms[hist_indx][hist_frame_index].frame); } else { needed &= ~histograms[hist_indx][hist_frame_index].valid; // remove those that are already available from the request } dev_dbg(g_dev_ptr, "needed=0x%x\n", needed); // TODO: handle valid and needed for multichannel? if (!(needed & 0xf)) // nothing to do with FPGA return 0; // Copying received data to histograms structure, maybe later we can skip that step and use data in place // hist_frame=(frame-1) & PARS_FRAMES_MASK; // TODO: Verify // hist_frame=frame & PARS_FRAMES_MASK; // TODO: Verify hist_frame=(frame + (GLOBALPARS(sensor_port, G_HIST_SHIFT))) & PARS_FRAMES_MASK; // TODO: Verify for (i=0; i<4; i++) if (needed & ( 1 << i )) { u32 phys_addr= fpga_hist_phys + PAGE_SIZE*(hist_frame + PARS_FRAMES * (sensor_chn + MAX_SENSORS *sensor_port)) + i*256*sizeof(u32); // start of selected color u32 * dma_data = &fpga_hist_data[sensor_port][sensor_chn][hist_frame][i][0]; // invalidate both caches outer_inv_range(phys_addr, phys_addr + (256*sizeof(u32) - 1)); __cpuc_flush_dcache_area(dma_data, 256*sizeof(u32)); color_start= i<<8 ; memcpy(&histograms[hist_indx][hist_frame_index].hist[256*i], dma_data, 256*sizeof(u32)); // old in 353: fpga_hist_read_nice (color_start, 256, (unsigned long *) &histograms[GLOBALPARS(G_HIST_LAST_INDEX)].hist[color_start]); histograms[hist_indx][hist_frame_index].valid |= 1 << i; dev_dbg(g_dev_ptr, "histograms[%d][%d].valid=0x%lx\n", hist_indx, hist_frame_index,histograms[hist_indx][hist_frame_index].valid ); } return 0; /* for (i=0; i<4; i++) if (needed & ( 1 << i )) { color_start= i<<8 ; fpga_hist_read_nice (color_start, 256, (unsigned long *) &histograms[GLOBALPARS(G_HIST_LAST_INDEX)].hist[color_start]); histograms[GLOBALPARS(G_HIST_LAST_INDEX)].valid |= 1 << i; } */ } /** * @brief Get derivative histograms (raw FPGA should be already there read by a tasklet needed) * Will look for requested (or earlier) frame that has the "needed" raw histograms * TODO: should it be one frame behind current? - yes, exactly */ ///TODO: Make color (rare) histograms survive longer? - Challenge - Y goes first, we do not know if it will be followed by color int get_histograms(int sensor_port, ///< sensor port number (0..3) int sensor_chn, ///< sensor subchannel (0 w/o multiplexer) unsigned long frame, ///< absolute frame number (Caller should match it to the hardware frame) int needed) ///< bits specify what histograms (color, type) are requested ///< each group of 4 bits covers 4 colors of the same type: ///< - bits 0..3 - read raw histograms from the FPGA - normally called from IRQ/tasklet (use just 1 color for autoexposure to speed up?) ///< - bits 4..7 - calculate cumulative histograms (sum of raw ones) - normally called from applications ///< - bits 8..11 - calculate percentiles (reverse cumulative histograms) - normally called from applications ///< "needed" for raw histograms should be specified explicitly (can not be read from FPGA later), ///< "needed" for cumul_hist will be added automatically if percentiles are requested ///< @return index of the histogram (>=0) if OK, otherwise: ///< - -EFAULT not reading FPGA and frame number stored is different from the requested (too late - histogram buffer overrun?) ///< - -EINVAL unused port/channel { int i, color_start, index; int hist_indx=get_hist_index(sensor_port,sensor_chn); int raw_needed; unsigned long thisFrameNumber=getThisFrameNumber(sensor_port); unsigned long * gammaHash; unsigned long * framep; unsigned long dbg_sum; if (hist_indx <0 ) return -EINVAL; raw_needed=(needed | (needed>>4) | needed>>8) & 0xf; dev_dbg(g_dev_ptr, "sensor_port=%d, sensor_chn=%d, frame = %ld (0x%lx), thisFrameNumber=%ld(0x%lx), needed = 0x%x, raw_needed=0x%x\n", sensor_port, sensor_chn, frame, frame, thisFrameNumber, thisFrameNumber, needed, raw_needed); if (raw_needed){ // get parameters - decide from current or mpastpars if (frame == (thisFrameNumber-1)) { // use pars gammaHash = get_imageParamsFramePtr(sensor_port, P_GTAB_R, frame); framep = get_imageParamsFramePtr(sensor_port, P_FRAME, frame); } else { gammaHash = get_imageParamsPastPtr (sensor_port, P_GTAB_R, frame); framep = get_imageParamsPastPtr (sensor_port, P_FRAME, frame); } if ((i= set_histograms (sensor_port, sensor_chn, frame, raw_needed, gammaHash, framep))){ dev_dbg(g_dev_ptr, "Failed to set up histograms for frame= %ld(0x%lx), thisFrameNumber=%ld(0x%lx), returned %d\n", frame,frame, thisFrameNumber,thisFrameNumber,i); return i; } } index=GLOBALPARS(sensor_port, G_HIST_LAST_INDEX+sensor_chn); // set_histograms may increment G_HIST_LAST_INDEX+sensor_chn for (i=0;i<HISTOGRAM_CACHE_NUMBER;i++) { dev_dbg(g_dev_ptr, "hist_indx= %d, index=%d, needed=0x%x\n",hist_indx, index,needed); if ((histograms[hist_indx][index].frame <= frame) && ((histograms[hist_indx][index].valid & raw_needed)==raw_needed)) break; index = (index-1) & (HISTOGRAM_CACHE_NUMBER-1); } if (i>=HISTOGRAM_CACHE_NUMBER) { dev_err(g_dev_ptr, "no histograms exist for requested colors (0x%x), requested 0x%x\n",raw_needed,needed); // here we need to try to locate and copy raw histogram return -EFAULT; // if Y - never calculated, if C - maybe all the cache is used by Y } /// needed &= ~0x0f; // mask out FPGA read requests -= they are not handled here anymore (use set_histograms()) dev_dbg(g_dev_ptr, "needed=0x%x\n",needed); needed |= ((needed >>4) & 0xf0); // cumulative histograms are needed for percentile calculations needed &= ~histograms[hist_indx][index].valid; dev_dbg(g_dev_ptr, "needed=0x%x\n",needed); if (needed & 0xf0) { // Calculating cumulative histograms for (i=0; i<4; i++) if (needed & ( 0x10 << i )) { color_start= i<<8 ; dbg_sum = histogram_calc_cumul ( (unsigned long *) &histograms[hist_indx][index].hist[color_start], (unsigned long *) &histograms[hist_indx][index].cumul_hist[color_start] ); dev_dbg(g_dev_ptr, "frame: 0x%lx (now 0x%lx) color:%d, pixel sum=0x%08lx\n",frame, thisFrameNumber, i, dbg_sum); histograms[hist_indx][index].valid |= 0x10 << i; } dev_dbg(g_dev_ptr, "needed=0x%x, valid=0x%lx\n",needed,histograms[hist_indx][index].valid); } if (needed & 0xf00) { // Calculating percentiles for (i=0; i<4; i++) if (needed & ( 0x100 << i )) { color_start= i<<8 ; histogram_calc_percentiles ( (unsigned long *) &histograms[hist_indx][index].cumul_hist[color_start], (unsigned char *) &histograms[hist_indx][index].percentile[color_start] ); histograms[hist_indx][index].valid |= 0x100 << i; } dev_dbg(g_dev_ptr, "needed=0x%x, valid=0x%lx\n",needed, histograms[hist_indx][index].valid); } return index; } /** Calculate cumulative histogram (one color component) from the corresponding raw histogram */ inline unsigned long histogram_calc_cumul ( unsigned long * hist, ///< input raw histogram array of unsigned long, single color (256) unsigned long * cumul_hist) ///< output cumulative histogram array of unsigned long, single color (256) ///< @return sum of all pixels (last value) { int i; cumul_hist[0]=hist[0]; for (i=1; i<256;i++) cumul_hist[i]=cumul_hist[i-1]+hist[i]; return cumul_hist[255]; } /** * Calculate reverse cumulative histograms (~percentiles) * The reverse cumulative histogram (~percentiles) works as the following: * for the given 1 byte input X (0 - 1/256 of all pixels, * ..., 255 - all pixels) * it returns threshold value P (0..255), so that number of pixels with value less than x is * less or equal to (P/256)*total_number_of_pixels, and number of pixels with value less than (x+1) is * greater than (P/256)*total_number_of_pixels, P(0)=0, P(256)=256 (not included in the table). * * Percentiles arrays are calculated without division for each element, interpolation (involving division) * will be done only for the value of interest on demand, in the user space. * * NOTE: - argument is FPGA pixel output-to-videoRAM value (after gamma-correction), reverse gamma table * is needed to relate percentiles to amount of light (proportional to exposure) * * Current algorithm is limited to 16 MPix/color_component (64 MPix total) */ inline void histogram_calc_percentiles (unsigned long * cumul_hist, ///< [IN] Pointer to the start of u32[256] cumulative histogram array unsigned char * percentile) ///< [OUT]Pointer to the start of u32[256] calculated percentile array { unsigned long v256=0; // running value to be compared against cumulative histogram (it is 256 larger than cumul_hist) unsigned long inc_v256=cumul_hist[255]; // step of v256 increment int shiftl=8; int p=0; // current value of percentile int x=0; // current percentile index while (inc_v256>0xffffff) { // to protect from unlikely overflow at 16MPix - in the future) inc_v256 >>= 1; shiftl--; } while ((p<256) && (x<256)) { percentile[x]=p; if ((p<255) && ( (cumul_hist[p] << shiftl) <= v256)) { p++; } else { x++; v256+=inc_v256; } } } //====================================== // use G_SUBCHANNELS in userspace to re-calculate full histogram index // File operations: // open, release - nop // read - none // write - none // lseek // mmap (should be used read only) /** HISTOGRAMS_FILE_SIZE histograms file size in frames (total nu,ber in all channels), not bytes) */ #define HISTOGRAMS_FILE_SIZE (HISTOGRAM_CACHE_NUMBER*numHistChn) static struct file_operations histograms_fops = { owner: THIS_MODULE, llseek: histograms_lseek, open: histograms_open, mmap: histograms_mmap, release: histograms_release }; /** Histograms driver OPEN method */ int histograms_open(struct inode *inode, ///< inode struct file *file) ///< file pointer ///< @return OK - 0, -EINVAL for wrong minor { int res; struct histograms_pd * privData; privData= (struct histograms_pd *) kmalloc(sizeof(struct histograms_pd),GFP_KERNEL); if (!privData) return -ENOMEM; file->private_data = privData; privData-> minor=MINOR(inode->i_rdev); dev_dbg(g_dev_ptr, "histograms_open: minor=0x%x\n",privData-> minor); switch (privData-> minor) { case DEV393_MINOR(DEV393_HISTOGRAM) : inode->i_size = HISTOGRAMS_FILE_SIZE; privData->frame=0xffffffff; privData->frame_index=-1; privData->needed= 0; privData->wait_mode=0; // 0 - wait just for G1 histogram, 1 - wait for all histograms privData->request_en=1; // enable requesting histogram for the specified frame (0 - rely on the available ones) privData->port=0; privData->subchannel=0; if (histograms_check_init() < 2) return -ENODEV; // Bitstream not loaded? return 0; default: kfree(file->private_data); // already allocated return -EINVAL; } file->f_pos = 0; return res; } /** Histograms driver RELEASE method */ int histograms_release (struct inode *inode, ///< inode struct file *file) ///< file pointer ///< @return OK - 0, -EINVAL for wrong minor { int res=0; int p = MINOR(inode->i_rdev); dev_dbg(g_dev_ptr, "histograms_release minor=0x%x\n",p); switch ( p ) { case DEV393_MINOR(DEV393_HISTOGRAM) : break; default: return -EINVAL; //! do not need to free anything - "wrong number" } kfree(file->private_data); return res; } /** Histograms driver LSEEK method (and execute commands)<ul> * <li>lseek <b>(SEEK_SET, value)</b> wait for histogram of the absolute frame 'value' (G1 or all depending on wait_mode * locate frame number value and set frame_index (and file pointer) to the result. * Return error if frame can not be found, otherwise - histogram index (to use with mmap) * Calculate missing tables according to "needed" variable * <li>lseek <b>(SEEK_CUR, value)</b> wait for histogram of the frame 'value' from the current one (G1 or all depending on wait_mode * locate frame number value and set frame_index (and file pointer) to the result. * Return error if frame can not be found, otherwise - histogram index (to use with mmap) * Calculate missing tables according to "needed" variable * lseek (SEEK_CUR, 0) will wait for the histogram(s) for current frame * <li> lseek <b>(SEEK_CUR, value)</b> - ignore value, return frame_index (may be negative if error) * <li> lseek <b>(SEEK_END, value < 0)</b> - do nothing?, do not modify file pointer, return error * <li> lseek <b>(SEEK_END, value = 0)</b> - return HISTOGRAMS_FILE_SIZE * <li> lseek <b>(SEEK_END, LSEEK_HIST_WAIT_Y)</b> - set histogram waiting for the Y (actually G1) histogram (default after open) * <li> lseek <b>(SEEK_END, LSEEK_HIST_WAIT_C)</b> - set histogram waiting for the C (actually R, G2, B) histograms to become available - implies G1 too * <li> lseek <b>(SEEK_END, LSEEK_HIST_SET_CHN +4*port+ subchannel)</b> - select sensor port and subchannel. Returns -ENXIO if port/subchannel does not * match any active sensor. * <li> lseek <b>(SEEK_END, LSEEK_HIST_NEEDED)</b> set histogram "needed" bits * <li> lseek <b>(SEEK_END, LSEEK_HIST_REQ_EN)</b> - (default)enable histogram request when reading histogram (safer, but may be not desirable in HDR mode) - default after opening * <li> lseek <b>(SEEK_END, LSEEK_HIST_REQ_DIS</b>) - disable histogram request when reading histogram - will read latest available relying it is available </ul> * @param file * @param offset * @param orig SEEK_SET, SEEK_CUR or SEEK_SET END * @return file position (histogram frame index (combined frame index and channel)) */ // TODO: NC393 - use int camSeqGetJPEG_frame(unsigned int chn); and // get_imageParamsFrame(..., camSeqGetJPEG_frame(chn)) instead of get_imageParamsThis(...) // TODO: add flag that will allow driver to wakeup processes before the specified frame comes ? loff_t histograms_lseek (struct file * file, loff_t offset, int orig) { int p,s,index; struct histograms_pd * privData = (struct histograms_pd *) file->private_data; unsigned long reqAddr,reqFrame; dev_dbg(g_dev_ptr, "histograms_lseek: offset=%d(0x%x), orig=0x%x, getThisFrameNumber(%d)=0x%x\n", (int) offset, (int) offset, (int) orig, privData->port, (int)getThisFrameNumber(privData->port)); switch (privData->minor) { case DEV393_MINOR(DEV393_HISTOGRAM) : switch(orig) { case SEEK_CUR: // ignore offset - in NC353 it was get latest? // offset = -1; // for now - just make "latest" #if 0 offset+=(privData-> wait_mode)? GLOBALPARS(privData->port,G_HIST_C_FRAME+privData->subchannel): GLOBALPARS(privData->port,G_HIST_Y_FRAME+privData->subchannel); #endif offset+=getThisFrameNumber(privData->port); // get relative to current frame (-1 - latest) //no break (CDT understands this) case SEEK_SET: // negative - make relative to current (-1 - latest, -2 - before latest - up to 15 before) if (offset <0){ dev_dbg(g_dev_ptr, "offset= %lld (0x%llx) changing to previous frame \n",offset,offset); offset += getThisFrameNumber(privData->port); } privData->frame=offset; // Try to make some precautions to avoid waiting forever - if the past frame is requested - request histogram for the current frame, // if the "immediate" future (fits into the array of frames) one - request that frame's histogram // if in the far future (unsafe) do nothing -NOTE: far future should be avoided if the histograms are set request-only // NOTE: there could be another wrong condition - request written w/o "JUST_THIS" modifier - then it will turn to always on until cleared. // TODO: Save time on always enabled histograms? Don't request them additionally? if (privData->request_en) { reqAddr=(privData-> wait_mode)?P_HISTRQ_C:P_HISTRQ_Y; reqFrame=getThisFrameNumber(privData->port); dev_dbg(g_dev_ptr, "offset= %d (0x%x), reqFrame=%d (0x%x) \n",(int) offset,(int) offset,(int) reqFrame,(int) reqFrame); if (offset > reqFrame) { if (offset > (reqFrame+5)) reqFrame+=5; // What is this 5? else reqFrame=offset; dev_dbg(g_dev_ptr, "offset= %d (0x%x), modified reqFrame for future request =%d (0x%x) \n",(int) offset,(int) offset,(int) reqFrame,(int) reqFrame); } if (offset < reqFrame) { // just debugging dev_dbg(g_dev_ptr, "offset < reqFrame, will run get_histograms (%d, %d, 0x%x, 0x%x) \n", privData->port, privData->subchannel, (int) offset, (int) privData->needed); } if ((offset < reqFrame) && // if the requested frame is in the past - try to get it first before requesting a new (((privData->frame_index = get_histograms (privData->port, privData->subchannel, offset, privData->needed))) >=0)) { // file->f_pos=privData->frame_index; if (((index = get_hist_index(privData->port, privData->subchannel))) <0) return -ENODEV; // requested combination of port and subchannel does not exist file->f_pos=privData->frame_index + HISTOGRAM_CACHE_NUMBER * get_hist_index(privData->port, privData->subchannel); dev_dbg(g_dev_ptr, "Returning %d (0x%x)\n", (int) file->f_pos, (int) file->f_pos); return file->f_pos; } // request histogram(s) // setFramePar(&framepars[reqFrame & PARS_FRAMES_MASK], reqAddr, 1); dev_dbg(g_dev_ptr, "setFrameParLocked(%d, &aframepars[%d][0x%x], 0x%lx, 1)\n", privData->port, privData->port, (int) (reqFrame & PARS_FRAMES_MASK), reqAddr); setFrameParLocked(privData->port, &aframepars[privData->port][reqFrame & PARS_FRAMES_MASK], reqAddr, 1); // make sure (harmful) interrupt did not happen since getThisFrameNumber() if (reqFrame < getThisFrameNumber(privData->port)) { dev_dbg(g_dev_ptr, "setFrameParLocked(%d, &aframepars[%d][0x%lx], 0x%lx, 1)\n", privData->port, privData->port, getThisFrameNumber(privData->port) & PARS_FRAMES_MASK, reqAddr); setFrameParLocked(privData->port, &aframepars[privData->port][getThisFrameNumber(privData->port) & PARS_FRAMES_MASK], reqAddr, 1); } } else { // debug dev_dbg(g_dev_ptr, "privData->request_en=0\n"); } #if 0 if (privData-> wait_mode) wait_event_interruptible (ahist_c_wait_queue[privData->port],GLOBALPARS(privData->port,G_HIST_C_FRAME + privData->subchannel)>=offset); else wait_event_interruptible (ahist_y_wait_queue[privData->port],GLOBALPARS(privData->port,G_HIST_Y_FRAME + privData->subchannel)>=offset); #endif dev_dbg(g_dev_ptr, "Before waiting: frame = 0x%x, offset=0x%x privData-> wait_mode=%d\n", (int) getThisFrameNumber(privData->port), (int) offset, privData-> wait_mode); // neded next frame after requested (modify php too?) if (privData-> wait_mode) wait_event_interruptible (ahist_c_wait_queue[privData->port],getThisFrameNumber(privData->port)>offset); else wait_event_interruptible (ahist_y_wait_queue[privData->port],getThisFrameNumber(privData->port)>offset); dev_dbg(g_dev_ptr, "After waiting: frame = 0x%x\n", (int) getThisFrameNumber(privData->port)); privData->frame_index = get_histograms (privData->port, privData->subchannel, offset, privData->needed); if (privData->frame_index <0) { return -EFAULT; } else { // file->f_pos=privData->frame_index; file->f_pos=privData->frame_index + HISTOGRAM_CACHE_NUMBER * get_hist_index(privData->port, privData->subchannel); dev_dbg(g_dev_ptr, "file->f_pos (full histogram number - cache and port/channel combined) = 0x%x\n", (int) file->f_pos); return file->f_pos; } break; // just in case case SEEK_END: if (offset < 0) { return -EINVAL; } else { if (offset < LSEEK_HIST_NEEDED) { //#define LSEEK_HIST_SET_CHN 0x30 ///< ..2F Select channel to wait for (4*port+subchannel) if ((offset & ~0xf) == LSEEK_HIST_SET_CHN){ p = (offset >> 2) & 3; s = (offset >> 0) & 3; if (get_hist_index(p,s)<0) return -ENXIO; // invalid port/channel combination privData->port = p; privData->subchannel = s; file->f_pos=privData->frame_index + HISTOGRAM_CACHE_NUMBER * get_hist_index(privData->port, privData->subchannel); } else switch (offset) { case 0: break; case LSEEK_HIST_REQ_EN: // enable requesting histogram for the specified frame (0 - rely on the available ones) privData->request_en=1; ///default after open break; case LSEEK_HIST_REQ_DIS: // disable requesting histogram for the specified frame, rely on the available ones privData->request_en=0; break; case LSEEK_HIST_WAIT_Y: // set histogram waiting for the Y (actually G1) histogram (default after open) privData-> wait_mode=0; break; case LSEEK_HIST_WAIT_C: // set histogram waiting for the C (actually R, G2, B) histograms to become available - implies G1 too privData-> wait_mode=1; break; default: switch (offset & ~0x1f) { case LSEEK_DAEMON_HIST_Y: // wait for daemon enabled and histograms Y ready dev_dbg(g_dev_ptr, "wait_event_interruptible (ahist_y_wait_queue[%d],0x%x & 0x%x)\n",privData->port, (int) get_imageParamsThis(privData->port, P_DAEMON_EN), (int) (1<<(offset & 0x1f))); wait_event_interruptible (ahist_y_wait_queue[privData->port], get_imageParamsThis(privData->port, P_DAEMON_EN) & (1<<(offset & 0x1f))); break; case LSEEK_DAEMON_HIST_C: // wait for daemon enabled and histograms C ready dev_dbg(g_dev_ptr, "wait_event_interruptible (ahist_c_wait_queue[%d],0x%x & 0x%x)\n",privData->port, (int) get_imageParamsThis(privData->port, P_DAEMON_EN), (int) (1<<(offset & 0x1f))); wait_event_interruptible (ahist_c_wait_queue[privData->port], get_imageParamsThis(privData->port, P_DAEMON_EN) & (1<<(offset & 0x1f))); break; default: return -EINVAL; } } } else if (offset < (LSEEK_HIST_NEEDED + 0x10000)) { privData->needed= (offset & 0xffff); } else { return -EINVAL; } file->f_pos= HISTOGRAMS_FILE_SIZE; dev_dbg(g_dev_ptr, "file->f_pos = HISTOGRAMS_FILE_SIZE = 0x%x\n", (int) file->f_pos); return file->f_pos; } break; default: // not SEEK_SET/SEEK_CUR/SEEK_END return -EINVAL; } // switch (orig) dev_dbg(g_dev_ptr, "file->f_pos = 0x%x\n", (int) file->f_pos); return file->f_pos ; default: // other minors return -EINVAL; } } /** * @brief Histograms driver MMAP method to read out the histogram data (raw and calculated) * @param file * @param vma * @return OK - 0, negative - errors */ int histograms_mmap (struct file *file, struct vm_area_struct *vma) { int result; struct histograms_pd * privData = (struct histograms_pd *) file->private_data; dev_dbg(g_dev_ptr, "histograms_mmap minor=0x%x\n",privData-> minor); switch (privData->minor) { case DEV393_MINOR(DEV393_HISTOGRAM) : result=remap_pfn_range(vma, vma->vm_start, ((unsigned long) virt_to_phys(histograms_p)) >> PAGE_SHIFT, // Should be page-aligned vma->vm_end-vma->vm_start, vma->vm_page_prot); dev_dbg(g_dev_ptr, "remap_pfn_range returned=%x\n",result); if (result) return -EAGAIN; return 0; default: return -EINVAL; } } /** * @brief Histograms driver init * @return 0 */ int histograms_init(struct platform_device *pdev) { int res,i; int sz, pages; struct device *dev = &pdev->dev; // const struct of_device_id *match; // not yet used // init_histograms(); // Not now??? Need to have list of channels // Do it later, from the user space res = register_chrdev(DEV393_MAJOR(DEV393_HISTOGRAM), DEV393_NAME(DEV393_HISTOGRAM), &histograms_fops); if(res < 0) { dev_err(dev, "histograms_init: couldn't get a major number %d.\n", DEV393_MAJOR(DEV393_HISTOGRAM)); return res; } // init_waitqueue_head(&histograms_wait_queue); for (i = 0; i < SENSOR_PORTS; i++) { init_waitqueue_head(&ahist_y_wait_queue[i]); // wait queue for the G1 histogram (used as Y) init_waitqueue_head(&ahist_c_wait_queue[i]); // wait queue for all the other (R,G2,B) histograms (color) } dev_info(dev, DEV393_NAME(DEV393_HISTOGRAM)": registered MAJOR: %d\n", DEV393_MAJOR(DEV393_HISTOGRAM)); histograms_initialized = 0; // NC393: Did not find a way to get memory when histograms a first needed: // GFP_KERNEL can nolt work inside spinlock (needed to prevent simultaneous initializations // from multiple php-cli instances, GFP_ATOMIC just fails. // So here we // allocate for all 16 (ports*subchannels) ~1.1MB and then use portion of it (usually 1/4) sz = SENSOR_PORTS * MAX_SENSORS * HISTOGRAM_CACHE_NUMBER * sizeof(struct histogram_stuct_t); pages = ((sz -1 ) >> PAGE_SHIFT) + 1; histograms = dma_alloc_coherent(NULL,(pages * PAGE_SIZE),&histograms_phys,GFP_KERNEL); dev_info(dev, "dma_alloc_coherent(NULL, 0x%lx, 0x%x,GFP_KERNEL)", pages * PAGE_SIZE, (int) histograms_phys); g_dev_ptr = dev; // to use for debug print return 0; } int histograms_remove(struct platform_device *pdev) { unregister_chrdev(DEV393_MAJOR(DEV393_HISTOGRAM), DEV393_NAME(DEV393_HISTOGRAM)); return 0; } static const struct of_device_id elphel393_histograms_of_match[] = { { .compatible = "elphel,elphel393-histograms-1.00" }, { /* end of list */ } }; MODULE_DEVICE_TABLE(of, elphel393_histograms_of_match); static struct platform_driver elphel393_histograms = { .probe = histograms_init, .remove = histograms_remove, .driver = { .name = DEV393_NAME(DEV393_HISTOGRAM), .of_match_table = elphel393_histograms_of_match, }, }; //module_init(histograms_init); module_platform_driver(elphel393_histograms); MODULE_LICENSE("GPLv3.0"); MODULE_AUTHOR("Andrey Filippov <andrey@elphel.com>."); MODULE_DESCRIPTION(X3X3_HISTOGRAMS_DRIVER_DESCRIPTION);