/*!****************************************************************************//**
 * @file   exif393.c
 * @brief  Drivers for Exif manipulation
 * @copyright Copyright (C) 2008-2016 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 3 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/>.
 */


//copied from cxi2c.c - TODO:remove unneeded

#include <linux/module.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/autoconf.h>
#include <linux/vmalloc.h>
#include <linux/platform_device.h> // dev_*


//#include <asm/system.h>
//#include <asm/svinto.h>
#include <asm/byteorder.h> // endians
//#include <asm/io.h>

#include <asm/irq.h>

#include <asm/delay.h>
#include <asm/uaccess.h>

#include <uapi/elphel/c313a.h>
#include <uapi/elphel/exifa.h>
#include <uapi/elphel/x393_devices.h>

//#include "fpgactrl.h"  // defines port_csp0_addr, port_csp4_addr
//
//#include "x3x3.h"
//#include "cc3x3.h"


#include "exif393.h"

#define D(x)
//#define D(x) printk(">>> %s:%d:",__FILE__,__LINE__);x


//Major
//#define X3X3_EXIF 136
//Minors
//#define X3X3_EXIF_EXIF    0 // read encoded Exif data (SEEK_END,
//#define X3X3_EXIF_META    1 // write metadata, concurently opened files. All writes atomic
// control/setup devices
//#define DEV393_MINOR(DEV393_EXIF_TEMPLATE) 2 // write Exif template
//#define DEV393_MINOR(DEV393_EXIF_METADIR)  3 // write metadata to Exif header translation (dir_table[MAX_EXIF_FIELDS])
// those 2 files will disable exif_enabled and exif_valid, truncate file size to file pointer on release.
//#define DEV393_MINOR(DEV393_EXIF_TIME)     4 // write today/tomorrow date (YYYY:MM:DD) and number of seconds at today/tomorrow
// midnight (00:00:00) in seconds from epoch (long, startting from LSB)



#define  X3X3_EXIF_DRIVER_DESCRIPTION "Elphel (R) model 393 Exif device driver"
/** @brief Global pointer to basic device structure. This pointer is used in debugfs output functions */
static struct device *g_devfp_ptr = NULL;

static DEFINE_SPINLOCK(lock);

//#define MAX_EXIF_FIELDS  256 // number of Exif tags in the header
//#define MAX_EXIF_SIZE   4096 // Exif data size 

static struct exif_dir_table_t dir_table[MAX_EXIF_FIELDS];
static int  exif_fields        = 0; // total number of the Exif fields in the header
static int  exif_template_size = 0; // size of Exif template
static char exif_template[MAX_EXIF_SIZE];

static int aexif_meta_size[SENSOR_PORTS] = {0,0,0,0}; // size of Exif meta data page (is it the same for all ports?) 393: set as individual
static int aexif_wp[SENSOR_PORTS]  =       {1,1,1,1}; // frame write pointer in the meta_buffer
static int aexif_enabled[SENSOR_PORTS] =   {0,0,0,0}; // enable storing of frame meta data, enable reading Exif data
static int aexif_valid[SENSOR_PORTS] =     {0,0,0,0}; // Exif tables and buffer are valid.
static char * ameta_buffer[SENSOR_PORTS]=  {NULL,NULL,NULL,NULL}; // dynamically allocated buffer to store frame meta data.
static char exif_tmp_buff[MAX_EXIF_SIZE];

//static char * meta_buffer=NULL; // dynamically allocated buffer to store frame meta data.
// page 0 - temporary storage, 1..MAX_EXIF_FRAMES - buffer


// Common for all sensor ports
struct exif_time_t {
	char          tomorrow_date[10]; //!"YYYY:MM:DD"
	unsigned long tomorrow_sec;      //!seconds from epoch tomorrow at 00:00
	char          today_date[10];    //!"YYYY:MM:DD"
	unsigned long today_sec;         //!seconds from epoch today at 00:00
} exif_time;

static struct exif_datetime_t {
	char          datetime[20];      //!"YYYY:MM:DD HH:MM:SS\0"
	char          subsec[7];         //!ASCII microseconds (0-padded), ."\0"
} now_datetime;




int        exif_open   (struct inode *inode, struct file *filp);
int        exif_release(struct inode *inode, struct file *filp);
loff_t     exif_lseek  (struct file * file, loff_t offset, int orig);
ssize_t    exif_write  (struct file * file, const char * buf, size_t count, loff_t *off);
ssize_t    exif_read   (struct file * file, char * buf, size_t count, loff_t *off);
static int __init exif_init(void);

static struct file_operations exif_fops = {
		owner:    THIS_MODULE,
		open:     exif_open,
		release:  exif_release,
		read:     exif_read,
		write:    exif_write,
		llseek:   exif_lseek
};
ssize_t minor_file_size(int minor) { //return current file size for different minors
	int sensor_port;
	switch (minor) {
	case DEV393_MINOR(DEV393_EXIF_TEMPLATE):
		return exif_template_size;
	case DEV393_MINOR(DEV393_EXIF0):
	case DEV393_MINOR(DEV393_EXIF1):
	case DEV393_MINOR(DEV393_EXIF2):
	case DEV393_MINOR(DEV393_EXIF3):
		sensor_port = minor - DEV393_MINOR(DEV393_EXIF0);
		return aexif_enabled[sensor_port]? (exif_template_size * (MAX_EXIF_FRAMES+1)):0;
	case DEV393_MINOR(DEV393_EXIF_META0):
	case DEV393_MINOR(DEV393_EXIF_META1):
	case DEV393_MINOR(DEV393_EXIF_META2):
	case DEV393_MINOR(DEV393_EXIF_META3):
		sensor_port = minor - DEV393_MINOR(DEV393_EXIF_META0);
		return aexif_meta_size[sensor_port];
	case DEV393_MINOR(DEV393_EXIF_METADIR):
		return exif_fields * sizeof(struct exif_dir_table_t);
	case DEV393_MINOR(DEV393_EXIF_TIME):
		return sizeof(struct exif_time_t);
	default:return 0;
	}
}
ssize_t minor_max_size(int minor) { //return max file size for different minors
	switch (minor) {
	case DEV393_MINOR(DEV393_EXIF_TEMPLATE):
		return MAX_EXIF_SIZE;
	case DEV393_MINOR(DEV393_EXIF0):
	case DEV393_MINOR(DEV393_EXIF1):
	case DEV393_MINOR(DEV393_EXIF2):
	case DEV393_MINOR(DEV393_EXIF3):
		return MAX_EXIF_SIZE * (MAX_EXIF_FRAMES+1);
	case DEV393_MINOR(DEV393_EXIF_META0):
	case DEV393_MINOR(DEV393_EXIF_META1):
	case DEV393_MINOR(DEV393_EXIF_META2):
	case DEV393_MINOR(DEV393_EXIF_META3):
		return MAX_EXIF_SIZE;
	case DEV393_MINOR(DEV393_EXIF_METADIR):
		return MAX_EXIF_FIELDS * sizeof(struct exif_dir_table_t);
	case DEV393_MINOR(DEV393_EXIF_TIME):
		return sizeof(struct exif_time_t);
	default:
		return 0;
	}
}
void exif_invalidate(void) { // 393: OK, only invalidates all ayt once
	int sensor_port;
	for (sensor_port =0; sensor_port < SENSOR_PORTS; sensor_port++){
		aexif_enabled[sensor_port] = 0;
		aexif_valid[sensor_port]  = 0;
	}
}

//reallocate meta buffer to store per-frame meta data (later output as Exif)
// 393: Make both individual and all at once
int exif_rebuild(int frames) {
	int sensor_port,rslt;
	for (sensor_port =0; sensor_port < SENSOR_PORTS; sensor_port++){
		if ((rslt = exif_rebuild_chn(sensor_port, frames)) <0){
			return rslt;
		}
	}
	return 0;
}
int exif_rebuild_chn(int sensor_port, int frames) {
	int i,ml;
	char * meta_buffer = ameta_buffer[sensor_port];

	aexif_enabled[sensor_port] = 0;
	aexif_valid[sensor_port]  =  0;
	aexif_wp[sensor_port] =      1;
	// free buffer, if allocated
	if (meta_buffer) {
		vfree (meta_buffer);
		meta_buffer=NULL;
	}
	//  calculate page size
	if (exif_fields==0) return 0; // exif_valid==0;
	for (i=0; i < exif_fields; i++) {
		ml=dir_table[i].src+dir_table[i].len;
		if (ml > aexif_meta_size[sensor_port]) aexif_meta_size[sensor_port] = ml;
	}
	if (aexif_meta_size[sensor_port] > MAX_EXIF_SIZE) {
	    dev_warn(g_devfp_ptr,"%s:%d: Meta frame size (0x%x) is too big (>0x%x)\n",__FILE__,__LINE__, aexif_meta_size[sensor_port], MAX_EXIF_SIZE);
		return -1;
	}
	meta_buffer= vmalloc(aexif_meta_size[sensor_port] * (MAX_EXIF_FRAMES+1));
	if (!meta_buffer) {
	    dev_warn(g_devfp_ptr,"%s:%d: Failed to allocate memory (%d bytes)\n",__FILE__,__LINE__, aexif_meta_size[sensor_port] * (MAX_EXIF_FRAMES+1));
		return -1;
	}
	memset(meta_buffer, 0, aexif_meta_size[sensor_port] * (MAX_EXIF_FRAMES+1));
	ameta_buffer[sensor_port] = meta_buffer;
	aexif_valid[sensor_port]  = 1;
	return 0;
}

int exif_enable(int en) {
	int sensor_port,rslt;
	for (sensor_port =0; sensor_port < SENSOR_PORTS; sensor_port++){
		if ((rslt = exif_enable_chn(sensor_port, en)) <0){
			return rslt;
		}
	}
	return 0;
}

int exif_enable_chn(int sensor_port, int en) {
	int rslt;
	if (en) {
		if (!aexif_valid[sensor_port]) {
			if (((rslt=exif_rebuild_chn(sensor_port, MAX_EXIF_FRAMES))) <0) return rslt;
		}
		aexif_enabled[sensor_port] = 1;
	} else {
		aexif_enabled[sensor_port] = 0;
	}
	return 0;
}

int dir_find_tag (unsigned long tag) { //find location of the tag field in meta page 
	int indx;
	for (indx=0; indx < exif_fields; indx++) if (dir_table[indx].ltag==tag) return (int) dir_table[indx].src;
	return -1;
}

inline void write_meta_raw_irq(int sensor_port, char * data, int offset, int len) { //write data to meta, called from IRQ
	if (aexif_enabled[sensor_port])
		memcpy(&ameta_buffer[sensor_port][offset], data, len);
}

inline int write_meta_irq(int sensor_port, char * data, int * indx,  unsigned long ltag, int len) { //write data to meta, called from IRQ(len==0 => use field length)
	int i;
	if (!aexif_enabled[sensor_port]) return 0;
	if (indx && (dir_table[* indx].ltag==ltag)) i=*indx;
	else {
		for (i=0; i<exif_fields; i++) if (dir_table[i].ltag==ltag) break;
		if (i>=exif_fields) return -1; //ltag not found
	}
	if (len==0) len=dir_table[i].len;
	memcpy(&ameta_buffer[sensor_port][dir_table[i].src], data, len);
	if (indx)  * indx = i;
	return dir_table[i].src;
}

inline void putlong_meta_raw_irq(int sensor_port, unsigned long data, int offset) { //write data to meta (4 bytes, big endian), called from IRQ
	unsigned long bedata=__cpu_to_be32(data);
	if (aexif_enabled[sensor_port]) {
		memcpy(&ameta_buffer[sensor_port][ offset], &bedata, 4);
	}
}

inline int putlong_meta_irq(int sensor_port, unsigned long data, int * indx,  unsigned long ltag) { //write data to meta (4 bytes, big endian), from IRQ
	int i;
	unsigned long bedata=__cpu_to_be32(data);
	if (!aexif_enabled[sensor_port]) return -10;
	if (indx && (dir_table[* indx].ltag==ltag)) i=*indx;
	else {
		for (i=0; i<exif_fields; i++) if (dir_table[i].ltag==ltag) break;
		if (i>=exif_fields) return -1; //ltag not found
	}
	memcpy(&ameta_buffer[sensor_port][dir_table[i].src], &bedata, 4);
	if (indx)  * indx=i;
	return dir_table[i].src;
}



void write_meta_raw(int sensor_port, char * data, int offset, int len) { //write data to meta, called from outside IRQ (atomic)
	unsigned long flags;
	if (aexif_enabled[sensor_port]) {
		local_irq_save(flags);
		//local_irq_disable();
		memcpy(&ameta_buffer[sensor_port][ offset], data, len);
		local_irq_restore(flags);
	}
}
int write_meta(int sensor_port, char * data, int * indx,  unsigned long ltag, int len) { //write data to meta, from outside IRQ (atomic) (len==0 => use field length)
	int i;
	unsigned long flags;
	if (!aexif_enabled[sensor_port]) return 0;
	if (indx && (dir_table[* indx].ltag==ltag)) i=*indx;
	else {
		for (i=0; i<exif_fields; i++) if (dir_table[i].ltag==ltag) break;
		if (i>=exif_fields) return -1; //ltag not found
	}
	if (len==0) len=dir_table[i].len;
	local_irq_save(flags);
	memcpy(&ameta_buffer[sensor_port][dir_table[i].src], data, len);
	local_irq_restore(flags);
	if (indx)  * indx=i;
	return dir_table[i].src;
}

void putlong_meta_raw(int sensor_port, unsigned long data, int offset) { //write data to meta (4 bytes, big endian), called from outside IRQ (atomic)
	unsigned long flags;
	unsigned long bedata=__cpu_to_be32(data);
	if (aexif_enabled[sensor_port]) {
		local_irq_save(flags);
		//local_irq_disable();
		memcpy(&ameta_buffer[sensor_port][ offset], &bedata, 4);
		local_irq_restore(flags);
	}
}

int putlong_meta(int sensor_port, unsigned long data, int * indx,  unsigned long ltag) { //write data to meta (4 bytes, big endian), from outside IRQ (atomic)
	int i;
	unsigned long flags;
	unsigned long bedata=__cpu_to_be32(data);
	if (!aexif_enabled[sensor_port]) return -10;
	if (indx && (dir_table[* indx].ltag==ltag)) i=*indx;
	else {
		for (i=0; i<exif_fields; i++) if (dir_table[i].ltag==ltag) break;
		if (i>=exif_fields) return -1; //ltag not found
	}
	local_irq_save(flags);
	memcpy(&ameta_buffer[sensor_port][dir_table[i].src], &bedata, 4);
	local_irq_restore(flags);
	if (indx)  * indx=i;
	return dir_table[i].src;
}

// The next function is normally called from the interrupt service routine
// Encode time (epoch sec, usec) into static buffer, return pointer to the buffer 
// Uses struct exif_time that should be updated from the user space (once a day),
// calculates date/time ignoring leap seconds if not updated in time
/*
 * 393: Continue to use same static buffers for exif_time - common to all channels
 */
char * encode_time(char buf[27], unsigned long sec, unsigned long usec) {
	unsigned long s,d,m,y,y4,lp,h;
	unsigned long flags;
    spin_lock_irqsave(&lock,flags);

	if (((sec-exif_time.today_sec)>86400) || (sec < exif_time.today_sec)) {// today's time is not valid, try tomorrow:
		memcpy(&exif_time.today_date[0],&exif_time.tomorrow_date[0],sizeof(exif_time.today_date)+sizeof(exif_time.today_sec));
		if (((sec-exif_time.today_sec)>86400) || (sec < exif_time.today_sec)) {// today's time is _still_ not valid, has to do it itself :-(
			d=sec/86400;
			s=d*86400;
			y4=d/1461; // number of 4-year periods
			d-=1461*y4; //days after 1970, 1974, ...
			y=(d- ((d>=1095)?1:0))/365;
			d-=y*365+((y>2)?1:0);
			lp=(y==2);
			y+=4*y4+1970;
			if ((!lp) && (d>58)) d++;
			//      d+=(lp && (d>58))?1:0;
			D(printk("d=%ld, y=%ld, y4=%ld, lp=%ld\n",d, y, y4, lp));
			if (d>181) {
				if (d>273) {
					if (d>304) {
						if (d>334) {m=12;d-=334;} // December
						else       {m=11;d-=304;} // November
					} else       {m=10;d-=273;} // October
				} else {
					if (d>212) {
						if (d>243) {m= 9;d-=243;} // September
						else       {m= 8;d-=212;} // August
					} else       {m= 7;d-=181;} // July
				}
			} else {
				if (d>90) {
					if (d>120) {
						if (d>151) {m=6; d-=151;} // June
						else       {m=5; d-=120;} // May
					} else       {m=4; d-= 90;} // April
				} else {
					if (d>30) {
						if (d>59)  {m=3; d-= 59;} // March
						else       {m=2; d-= 30;} // February
					} else       {m=1; d++;   } // January
				}
			}
			D(printk("d=%ld, y=%ld, y4=%ld\n",d, y, y4));
			sprintf(exif_time.today_date,"%04ld:%02ld:%02ld",y,m,d);
			exif_time.today_sec=s;
		}
		memcpy (&now_datetime.datetime[0],exif_time.today_date,10);
		now_datetime.datetime[10]=' ';
		now_datetime.datetime[19]='\0';
		now_datetime.subsec[6]='\0';
	}
	// now we have valid exif_time.today_date, exif_time.today_sec;
	s=sec-exif_time.today_sec;
	h= s/3600;
	s-= 3600*h;
	m=  s/60;
	s-= 60*m;
	sprintf(&now_datetime.datetime[11],"%02ld:%02ld:%02ld",h,m,s);
	sprintf(&now_datetime.subsec[0],"%06ld",usec);
	memcpy(buf,&now_datetime.datetime[0],sizeof(now_datetime));
	//  return &now_datetime.datetime[0];
    spin_unlock_irqrestore(&lock,flags);
	return buf;
}

int store_meta(int sensor_port) { //called from IRQ service - put current metadata to meta_buffer, return page index
    int meta_index;
	if (!aexif_enabled[sensor_port]) return 0;
	meta_index=aexif_wp[sensor_port];
	memcpy(&ameta_buffer[sensor_port][meta_index * aexif_meta_size[sensor_port]], ameta_buffer[sensor_port], aexif_meta_size[sensor_port]);
	aexif_wp[sensor_port]++;
	if (aexif_wp[sensor_port] > MAX_EXIF_FRAMES) aexif_wp[sensor_port] = 1;
	return meta_index;
}
/**
 * @brief Go through mapping table and invalidate all tags that will be updated
 * @param curr_table current mapping table
 * @param curr_num   the number of entries in current table
 * @param new_table  new mapping table of just a part of it
 * @param new_num    the number of entries in new table
 */
static void tags_invalidate(struct exif_dir_table_t *curr_table, int curr_num,
		struct exif_dir_table_t *new_table, int new_num)
{
	int i, j;

	for (i = 0; i < curr_num; i++) {
		for (j = 0; j < new_num; j++) {
			if (curr_table[i].ltag == new_table[j].ltag) {
				memset(&curr_table[i], 0, sizeof(struct exif_dir_table_t));
			}
		}
	}

}

//!++++++++++++++++++++++++++++++++++++ open() ++++++++++++++++++++++++++++++++++++++++++++++++++++++

//static
int exif_open(struct inode *inode, struct file *filp) {
	int p = MINOR(inode->i_rdev);
	int * pd= (int *) &(filp->private_data);
	switch (p) {
	case DEV393_MINOR(DEV393_EXIF0):
	case DEV393_MINOR(DEV393_EXIF1):
	case DEV393_MINOR(DEV393_EXIF2):
	case DEV393_MINOR(DEV393_EXIF3):
	case DEV393_MINOR(DEV393_EXIF_META0):
	case DEV393_MINOR(DEV393_EXIF_META1):
	case DEV393_MINOR(DEV393_EXIF_META2):
	case DEV393_MINOR(DEV393_EXIF_META3):
	case DEV393_MINOR(DEV393_EXIF_TEMPLATE):
	case DEV393_MINOR(DEV393_EXIF_METADIR):
	case DEV393_MINOR(DEV393_EXIF_TIME):
		break;
	default:return -EINVAL;
	}

	dev_dbg(g_devfp_ptr,"exif_open, minor=%d\n",p);
	inode->i_size=minor_file_size(p);
	pd[0]=p; // just a minor number
	return 0;
}

//!++++++++++++++++++++++++++++++++++++ release() ++++++++++++++++++++++++++++++++++++++++++++++++++++++

//static
int exif_release(struct inode *inode, struct file *filp){
	int p = MINOR(inode->i_rdev);
	int * pd= (int *) &(filp->private_data);
	switch (p) {
	case DEV393_MINOR(DEV393_EXIF0):
	case DEV393_MINOR(DEV393_EXIF1):
	case DEV393_MINOR(DEV393_EXIF2):
	case DEV393_MINOR(DEV393_EXIF3):
		break;
	case DEV393_MINOR(DEV393_EXIF_META0):
	case DEV393_MINOR(DEV393_EXIF_META1):
	case DEV393_MINOR(DEV393_EXIF_META2):
	case DEV393_MINOR(DEV393_EXIF_META3):
		break;
	case DEV393_MINOR(DEV393_EXIF_TEMPLATE):
		break;
	case DEV393_MINOR(DEV393_EXIF_METADIR):
		break;
	case DEV393_MINOR(DEV393_EXIF_TIME):
		break;
	default:return -EINVAL;
	}

	dev_dbg(g_devfp_ptr,"exif_open, minor=%d\n",p);
	inode->i_size=minor_file_size(p);
	pd[0]=p; // just a minor number
	return 0;
}

//!++++++++++++++++++++++++++++++++++++ lseek() ++++++++++++++++++++++++++++++++++++++++++++++++++++++

//static
loff_t exif_lseek  (struct file * file, loff_t offset, int orig) {
	int p=(int)file->private_data;
	int thissize=minor_file_size(p);
	int maxsize=minor_max_size(p);
    int fp;
    dev_dbg(g_devfp_ptr,"exif_lseek, minor=%d, offset = 0x%llx, orig=%d\n",p,offset,orig);
	//   int sensor_port;
	switch (orig) {
	case SEEK_SET:
		file->f_pos = offset;
		break;
	case SEEK_CUR:
		file->f_pos += offset;
		break;
	case SEEK_END:
		//!overload
		if (offset<=0) {
			file->f_pos = thissize + offset;
		} else {

			switch (p) {
			case DEV393_MINOR(DEV393_EXIF_TEMPLATE): //enable/disable
				switch (offset) {
				case EXIF_LSEEK_DISABLE:
					exif_enable(0);
					break;
				case EXIF_LSEEK_ENABLE:
					if (exif_enable(1)<0) return -EOVERFLOW; //TODO: change code
					break;
				case EXIF_LSEEK_INVALIDATE:
					exif_invalidate();
					break;
				case EXIF_LSEEK_REBUILD:
					if (exif_rebuild(MAX_EXIF_FRAMES)<0) return -EOVERFLOW; //TODO: change code
					break;
				default:return -EINVAL;
				}
				break;
			case DEV393_MINOR(DEV393_EXIF0):
			case DEV393_MINOR(DEV393_EXIF1):
			case DEV393_MINOR(DEV393_EXIF2):
			case DEV393_MINOR(DEV393_EXIF3):
				//            sensor_port = p - DEV393_MINOR(DEV393_EXIF0);
				if (offset > MAX_EXIF_FRAMES) return -EOVERFLOW; //larger than buffer
				//            file->f_pos=exif_meta_size * offset;
				file->f_pos=exif_template_size * offset;
				break;
			case DEV393_MINOR(DEV393_EXIF_META0):
			case DEV393_MINOR(DEV393_EXIF_META1):
			case DEV393_MINOR(DEV393_EXIF_META2):
			case DEV393_MINOR(DEV393_EXIF_META3):
                fp= dir_find_tag (offset);
                if (fp < 0) return -EOVERFLOW; // tag is not in the directory
                file->f_pos=fp;
				break;
            case DEV393_MINOR(DEV393_EXIF_METADIR):
                file->f_pos=offset*sizeof(struct exif_dir_table_t);
                break;
			case DEV393_MINOR(DEV393_EXIF_TIME):
				switch (offset) {
				case EXIF_LSEEK_TOMORROW_DATE:
					file->f_pos=exif_time.tomorrow_date - ((char *) &exif_time);
					break;
				case EXIF_LSEEK_TOMORROW_SEC:
					file->f_pos=((char *) &exif_time.tomorrow_sec) - ((char *) &exif_time);
					break;
				case EXIF_LSEEK_TODAY_DATE:
					file->f_pos=exif_time.today_date - ((char *) &exif_time);
					break;
				case EXIF_LSEEK_TODAY_SEC:
					file->f_pos=((char *) &exif_time.today_sec) - ((char *) &exif_time);
					break;
				default:return -EINVAL;
				}
				break;
				default:return -EINVAL;
			}
		}
		break;
	default:
		return -EINVAL;
	}
	/* truncate position */
	if (file->f_pos < 0) {
		file->f_pos = 0;
		return (-EOVERFLOW);
	}

	if (file->f_pos > maxsize) {
		file->f_pos = maxsize;
		return (-EOVERFLOW);
	}
	return (file->f_pos);
}

//!++++++++++++++++++++++++++++++++++++ write() ++++++++++++++++++++++++++++++++++++++++++++++++++++++

//static
ssize_t    exif_write  (struct file * file, const char * buf, size_t count, loff_t *off) {
	int p=(int)file->private_data;
	int sensor_port;
	//  int thissize=minor_file_size(p);
	int maxsize=minor_max_size(p);
	int fields_num;
	char * cp, *new_table;
	char tmp[MAX_EXIF_SIZE]; //! Or is it possible to disable IRQ while copy_from_user()?
	unsigned long flags;
	int disabled_err=0;
    dev_dbg(g_devfp_ptr,"minor=0x%x\n", p);

	if ((*off+count)>maxsize) {
//	    dev_warn(g_devfp_ptr,"%s:%d: Data (0x%x) does not fit into 0x%x bytes\n",__FILE__,__LINE__, (int) (*off+count), maxsize);
        dev_dbg(g_devfp_ptr,"Data (0x%x) does not fit into 0x%x bytes, minor = 0x%x\n",(int) (*off+count), maxsize, p);
		return -EOVERFLOW;
	}
	switch (p) {
	case DEV393_MINOR(DEV393_EXIF_TEMPLATE):
		exif_invalidate();
		if (copy_from_user(&exif_template[*off], buf, count)) return -EFAULT;
		exif_template_size=*off+count;
		break;
	case DEV393_MINOR(DEV393_EXIF_METADIR):
		exif_invalidate();
		cp= (char *) &dir_table;
		new_table = kmalloc(MAX_EXIF_FIELDS * sizeof(struct exif_dir_table_t), GFP_KERNEL);
		if (!new_table) {
			return -ENOMEM;
		}
		if (copy_from_user(new_table, buf, count)) {
			kfree(new_table);
			return -EFAULT;
		}
		fields_num = exif_fields;
		exif_fields=(*off+count)/sizeof(struct exif_dir_table_t);
		tags_invalidate(dir_table, fields_num, (struct exif_dir_table_t *)new_table, exif_fields);
		memcpy(&cp[*off], new_table, count);
		kfree(new_table);
		break;
	case DEV393_MINOR(DEV393_EXIF_TIME): //write date/time first, then - midnight seconds
		cp= (char *) &exif_time;
		if (copy_from_user(&cp[*off], buf, count)) return -EFAULT;
		break;
	case DEV393_MINOR(DEV393_EXIF_META0):
	case DEV393_MINOR(DEV393_EXIF_META1):
	case DEV393_MINOR(DEV393_EXIF_META2):
	case DEV393_MINOR(DEV393_EXIF_META3):
		sensor_port = p - DEV393_MINOR(DEV393_EXIF_META0);
		if (copy_from_user(tmp, buf, count)) return -EFAULT;
		local_irq_save(flags);
		//local_irq_disable();
		if (aexif_enabled[sensor_port]) memcpy(&ameta_buffer[sensor_port][*off], tmp, count);
		else disabled_err=1;
		local_irq_restore(flags);
		if (disabled_err) {
		    dev_warn(g_devfp_ptr,"tried to write meta channel %d while disabled\n",sensor_port);
			count=0;
		}
		break;
	case DEV393_MINOR(DEV393_EXIF0):
	case DEV393_MINOR(DEV393_EXIF1):
	case DEV393_MINOR(DEV393_EXIF2):
	case DEV393_MINOR(DEV393_EXIF3):
		return -EINVAL; // no writing - read only
		break;
	default:return -EINVAL;
	}
	*off+=count;
	 dev_dbg(g_devfp_ptr,"count= 0x%x, pos= 0x%x\n", (int) count, (int)*off);
	return count;
}

//!++++++++++++++++++++++++++++++++++++ read() ++++++++++++++++++++++++++++++++++++++++++++++++++++++

//static
ssize_t    exif_read   (struct file * file, char * buf, size_t count, loff_t *off) {
	int p=(int)file->private_data;
	int thissize=minor_file_size(p);
	char * cp, * metap;
	int start_p, page_p,i;
	int sensor_port;
	char tmp[MAX_EXIF_SIZE]; //! Or is it possible to disable IRQ while copy_from_user()?
	/*
  Does not work with PHP - always read 8192 bytes
  if ((*off+count)>maxsize) {
    printk ("%s:%d: Data (0x%x) does not fit into 0x%x bytes\n",__FILE__,__LINE__, (int) (*off+count), maxsize);
    return -EOVERFLOW;
  }
	 */
	if (*off > thissize) {
		return 0; // nothing to read
	}
	if ((*off + count) > thissize) {
		count=thissize-*off;
	}

	switch (p) {
	case DEV393_MINOR(DEV393_EXIF_TEMPLATE):
		if (copy_to_user(buf,  &exif_template[*off], count)) return -EFAULT;
		break;
	case DEV393_MINOR(DEV393_EXIF_METADIR):
		cp= (char *) &dir_table;
		if (copy_to_user(buf,  &cp[*off], count)) return -EFAULT;
		break;
	case DEV393_MINOR(DEV393_EXIF_TIME):
		cp= (char *) &exif_time;
		if (copy_to_user(buf,  &cp[*off], count)) return -EFAULT;
		break;
	case DEV393_MINOR(DEV393_EXIF_META0):
	case DEV393_MINOR(DEV393_EXIF_META1):
	case DEV393_MINOR(DEV393_EXIF_META2):
	case DEV393_MINOR(DEV393_EXIF_META3):
		sensor_port = p - DEV393_MINOR(DEV393_EXIF_META0);
		if (!aexif_enabled[sensor_port]) return 0;
		if (copy_to_user(buf,  &ameta_buffer[sensor_port][*off], count)) return -EFAULT;
		break;
	case DEV393_MINOR(DEV393_EXIF0):// generates exif data by merging exif_template with the selected meta_buffer page
	case DEV393_MINOR(DEV393_EXIF1):
	case DEV393_MINOR(DEV393_EXIF2):
	case DEV393_MINOR(DEV393_EXIF3):
		sensor_port = p - DEV393_MINOR(DEV393_EXIF0);
		//will truncate by the end of current page
		if (!aexif_enabled[sensor_port]) return 0;
		i=((int) *off) / exif_template_size;
		 dev_dbg(g_devfp_ptr,"count= 0x%x, *off= 0x%x, i=0x%x, exif_template_size=0x%x\n", (int) count, (int) *off, (int) i, (int) exif_template_size);
		//arch/cris/arch-v32/drivers/elphel/exif353.c:590:count= 0x2000, *off= 0x410, i=0x82, exif_template_size=0x208
		start_p=i*exif_template_size;
		page_p= *off - start_p;
		 dev_dbg(g_devfp_ptr,"count= 0x%x, pos= 0x%x, start_p=0x%x, page_p=0x%x, i=0x%x, exif_template_size=0x%x\n", (int) count, (int) *off, (int)start_p, (int)page_p,(int) i, (int) exif_template_size);
		//arch/cris/arch-v32/drivers/elphel/exif353.c:591:count= 0x2000, pos= 0x410, start_p=0x10810, page_p=0xfffefc00, i=0x82, exif_template_size=0x208
		metap= &ameta_buffer[sensor_port][i*aexif_meta_size[sensor_port]]; // pointer to the start of the selected page in frame meta_buffer
		if ((page_p+count) > exif_template_size) count=exif_template_size-page_p;
		memcpy(tmp,exif_template, exif_template_size);
		 dev_dbg(g_devfp_ptr,"count= 0x%x, pos= 0x%x, start_p=0x%x, page_p=0x%x\n", (int) count, (int) *off, (int)start_p, (int)page_p);
		for (i=0;i<exif_fields;i++){
			memcpy(&tmp[dir_table[i].dst],&metap[dir_table[i].src], dir_table[i].len);
		}
		if (copy_to_user(buf,  &tmp[page_p], count)) return -EFAULT;
		break;
	default:return -EINVAL;
	}
	*off+=count;
	 dev_dbg(g_devfp_ptr,"count= 0x%x, pos= 0x%x\n", (int) count, (int)*off);
	return count;
}

/* This code is copied from exif_read, consider replacing it with this function invocation */
size_t exif_get_data(int sensor_port, unsigned short meta_index, void *buff, size_t buff_sz)
{
	size_t count = exif_template_size;
	loff_t off;
	int start_p, page_p, i;
	char *metap;

	//will truncate by the end of current page
	if (!aexif_enabled[sensor_port])
		return 0;
	off = meta_index * exif_template_size;
	D(printk("%s: count= 0x%x, *off= 0x%x, i=0x%x, exif_template_size=0x%x\n", __func__, (int) count, (int) off, (int) meta_index, (int) exif_template_size));
	start_p = meta_index * exif_template_size;
	page_p = off - start_p;
	D(printk("%s: count= 0x%x, pos= 0x%x, start_p=0x%x, page_p=0x%x, i=0x%x, exif_template_size=0x%x\n", __func__, (int) count, (int) off, (int)start_p, (int)page_p,(int) meta_index, (int) exif_template_size));
	metap = &ameta_buffer[sensor_port][meta_index * aexif_meta_size[sensor_port]]; // pointer to the start of the selected page in frame meta_buffer
	if ((page_p + count) > exif_template_size)
		count = exif_template_size - page_p;
	memcpy(exif_tmp_buff, exif_template, exif_template_size);
	D(printk("%s: count= 0x%x, pos= 0x%x, start_p=0x%x, page_p=0x%x\n", __func__, (int) count, (int) off, (int)start_p, (int)page_p));
	for (i = 0; i < exif_fields; i++) {
		memcpy(&exif_tmp_buff[dir_table[i].dst], &metap[dir_table[i].src], dir_table[i].len);
	}
	memcpy(buff, &exif_tmp_buff[page_p], count);
	return count;
}
EXPORT_SYMBOL_GPL(exif_get_data);

//!++++++++++++++++++++++++++++++++++++ _init() ++++++++++++++++++++++++++++++++++++++++++++++++++++++

static int __init exif_init(void) {
	int res;
	res = register_chrdev(DEV393_MAJOR(DEV393_EXIF0), "Exif", &exif_fops);
	if(res < 0) {
	    dev_err(g_devfp_ptr,"\nexif_init: couldn't get a major number  %d.\n",DEV393_MAJOR(DEV393_EXIF0));
		return res;
	}
	 dev_dbg(g_devfp_ptr,DEV393_NAME(DEV393_EXIF0)" - %d\n",DEV393_MAJOR(DEV393_EXIF0));
	return 0;
}

module_init(exif_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andrey Filippov <andrey@elphel.com>.");
MODULE_DESCRIPTION(X3X3_EXIF_DRIVER_DESCRIPTION);