#include "framepars.h"
#include "sensor_common.h"
#include "jpeghead.h"
//#include "circbuf.h"
#include "exif.h"
#include "x393_macro.h"
#include "x393_helpers.h"
/** @brief Driver name displayed in system logs */
//#define CIRCBUF_DRIVER_NAME "circbuf driver"
/** @brief Wait queue for the processes waiting for a new frame to appear in the circular buffer */
wait_queue_head_t circbuf_wait_queue;
struct circbuf_priv_t circbuf_priv[SENSOR_PORTS];
struct circbuf_priv_t *circbuf_priv_ptr = circbuf_priv;
/** @brief Global pointer to basic device structure. This pointer is used in debugfs output functions */
static struct device *g_dev_ptr;
/** @brief Structure used for matching a device from device tree */
static const struct of_device_id elphel393_circbuf_of_match[];
unsigned long *ccam_dma_buf_ptr[SENSOR_PORTS] = {NULL};
/** Get structure describing selected circbuf channel (start virtual and physical, buffer size) */
struct circbuf_priv_t *get_circbuf(int chn) ///< compressor channel
///< @return pointer to requested structure
{
return &circbuf_priv[chn];
}
/**
* @brief Set up pointers to memory where circular buffers are located. The memory
* for circular buffers is reserved using CMA mechanism.
* @param[in] pdev driver's platform device structure
* @return 0 if pointers were set up and negative error code otherwise
*/
int init_ccam_dma_buf_ptr(struct platform_device *pdev)
{
int i;
dma_addr_t dma_handle;
const size_t dma_size = (CCAM_DMA_SIZE + (PAGE_SIZE >> 2)) * sizeof(int);
struct device *dev = &pdev->dev;
unsigned long *dma_buf_ptr = NULL;
// use Elphel_buf if it was allocated
if (pElphel_buf != NULL) {
dma_buf_ptr = pElphel_buf->vaddr;
dma_handle = pElphel_buf->paddr;
dev_info(dev, "using %lu bytes of DMA memory from pElphel_buf at address 0x%08x", pElphel_buf->size * PAGE_SIZE, dma_handle);
} else {
dma_buf_ptr = dmam_alloc_coherent(dev, dma_size, &dma_handle, GFP_KERNEL);
if (!dma_buf_ptr) {
dev_err(dev, "unable to allocate DMA buffer\n");
return -ENOMEM;
} else {
dev_info(dev, "%d bytes of DMA memory allocated at address 0x%08x", dma_size , dma_handle);
}
}
for (i = 0; i < SENSOR_PORTS; i++) {
circbuf_priv[i].buf_ptr = dma_buf_ptr + BYTE2DW(CIRCBUF_START_OFFSET + i * CCAM_DMA_SIZE);
circbuf_priv[i].phys_addr = dma_handle + CIRCBUF_START_OFFSET + i * CCAM_DMA_SIZE;
circbuf_priv[i].buf_size = CCAM_DMA_SIZE;
circbuf_priv[i].buf_size32 = circbuf_priv[i].buf_size>>2; // used in many places
ccam_dma_buf_ptr[i] = circbuf_priv[i].buf_ptr;
// set circular buffer size in bytes
set_globalParam(i, G_CIRCBUFSIZE, circbuf_priv[i].buf_size);
}
return 0;
}
/**
* @brief Process circular buffer file opening and define further action in accordance
* with minor file number.
* @param[in] inode
* @param[in] filp
* @return 0 if file was opened successfully and negative error code otherwise
*/
int circbuf_all_open(struct inode *inode, struct file *filp)
{
int res;
unsigned int minor = MINOR(inode->i_rdev);
unsigned int dev_type;
dev_dbg(g_dev_ptr, "circbuf_all_open, minor = 0x%x\n", minor);
minor_to_chn(minor, &dev_type);
switch (dev_type) {
case DEV393_MINOR(DEV393_CIRCBUF0):
res = circbuf_open(inode, filp);
break;
case DEV393_MINOR(DEV393_JPEGHEAD0):
res = jpeghead_open(inode, filp);
break;
case DEV393_MINOR(DEV393_HUFFMAN0):
res = huffman_open(inode, filp);
break;
default:
return -EINVAL;
}
return res;
}
/**
* @brief Process circular buffer file closing and define further action in accordance
* with minor file number.
* @param[in] inode
* @param[in] filp
* @return 0 if file was opened successfully and negative error code otherwise
*/
int circbuf_all_release(struct inode *inode, struct file *filp)
{
int res=0;
unsigned int minor = MINOR(inode->i_rdev);
unsigned int dev_type;
dev_dbg(g_dev_ptr, "minor = 0x%x\n", minor);
minor_to_chn(minor, &dev_type);
switch (dev_type) {
case DEV393_MINOR(DEV393_CIRCBUF0):
// res=circbuf_release(inode,filp);
break;
case DEV393_MINOR(DEV393_JPEGHEAD0):
// res=jpeghead_release(inode,filp);
break;
case DEV393_MINOR(DEV393_HUFFMAN0):
// res=huffman_release(inode,filp);
break;
default:
return -EINVAL;
}
if (filp->private_data) kfree(filp->private_data);
return res;
}
/**
* @brief Process lseek operation on circular buffer file and define further
* action in accordance with minor file number.
* @param[in] file pointer to file structure corresponding to the circular buffer file
* @param[in] offset offset in bytes
* @param[in] orig where the @e offset should start
* @return The resulting offset location in bytes from the beginning of the file.
*/
loff_t circbuf_all_lseek(struct file *file, loff_t offset, int orig)
{
int rp;
struct interframe_params_t *fp = NULL;
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int dev_type;
unsigned int chn = minor_to_chn(minor, &dev_type);
dev_dbg(g_dev_ptr, "circbuf_all_lseek, minor = 0x%x\n", minor);
switch (dev_type) {
case DEV393_MINOR(DEV393_CIRCBUF0):
return circbuf_lseek(file, offset, orig);
case DEV393_MINOR(DEV393_JPEGHEAD0):
if (orig == SEEK_END && offset > 0) {
rp = BYTE2DW(X393_BUFFSUB_CHN(offset, CHUNK_SIZE, chn)) & (~7); // convert to index to long, align to 32-bytes
fp = (struct interframe_params_t *) &circbuf_priv[chn].buf_ptr[rp];
}
return jpeghead_lseek(file, offset, orig, fp);
case DEV393_MINOR(DEV393_HUFFMAN0):
return huffman_lseek(file, offset, orig);
default:
return -EINVAL;
}
}
/**
* @brief Process read operation on circular buffer file and define further
* action in accordance with minor file number.
* @param[in] file pointer to file structure corresponding to the circular buffer file
* @param[out] buf pointer to user buffer where the data should be placed
* @param[in] count the size of requested data transfer
* @param[in] off file position the user is accessing
* @return The number of bytes copied or negative error code.
*/
ssize_t circbuf_all_read(struct file *file, char *buf, size_t count, loff_t *off)
{
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int dev_type;
dev_dbg(g_dev_ptr, "minor = 0x%x\n", minor);
minor_to_chn(minor, &dev_type);
switch (dev_type) {
case DEV393_MINOR(DEV393_CIRCBUF0):
return circbuf_read(file, buf, count, off);
case DEV393_MINOR(DEV393_JPEGHEAD0):
return jpeghead_read(file, buf, count, off);
case DEV393_MINOR(DEV393_HUFFMAN0):
return huffman_read(file, buf, count, off);
default:
return -EINVAL;
}
}
/**
* @brief Process write operation on circular buffer file and define further
* action in accordance with minor file number.
* @param[in] file pointer to file structure corresponding to the circular buffer file
* @param[out] buf pointer to user buffer holding the data
* @param[in] count the size of requested data transfer
* @param[in] off file position the user is accessing
* @return The number of bytes copied or negative error code.
*/
ssize_t circbuf_all_write(struct file *file, const char *buf, size_t count, loff_t *off)
{
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int dev_type;
dev_dbg(g_dev_ptr, "minor = 0x%x, count = %d, off = %d\n", minor, (int)count, (int)*off);
minor_to_chn(minor, &dev_type);
switch (dev_type) {
case DEV393_MINOR(DEV393_CIRCBUF0):
return circbuf_write (file, buf, count, off);
case DEV393_MINOR(DEV393_JPEGHEAD0):
// no method for this module
return -EINVAL;
case DEV393_MINOR(DEV393_HUFFMAN0):
return huffman_write (file, buf, count, off);
default:
return -EINVAL;
}
}
/**
* @brief Process memory map operation on circular buffer file and define further
* action in accordance with minor file number. Only circular buffer itself supports
* memory mapped operation, all other files will return an error.
* @param[in] file pointer to file structure corresponding to the circular buffer file
* @param[in] vma contains the information about the virtual address range
* @return 0 if file was mapped successfully and negative error code otherwise
*/
int circbuf_all_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int dev_type;
dev_dbg(g_dev_ptr, "minor = 0x%x\n", minor);
minor_to_chn(minor, &dev_type);
switch (dev_type) {
case DEV393_MINOR(DEV393_CIRCBUF0):
return circbuf_mmap(file, vma);
default:
return -EINVAL;
}
}
/**
* @brief Process poll operation on circular buffer file and define further
* action in accordance with minor file number. Only circular buffer itself supports
* memory mapped operation, all other files will return an error.
* @param[in] file pointer to file structure corresponding to the circular buffer file
* @param[in] wait pointer to a wait queue
* @return POLLHUP if pointer is invalid, (POLLIN | POLLRDNORM) if frame is ready,
* 0 in case nothing is ready or incorrect minor number received.
*/
unsigned int circbuf_all_poll(struct file *file, poll_table *wait)
{
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int dev_type;
dev_dbg(g_dev_ptr, "minor = 0x%x\n", minor);
minor_to_chn(minor, &dev_type);
switch (dev_type) {
case DEV393_MINOR(DEV393_CIRCBUF0):
return circbuf_poll(file, wait);
default:
return 0;
}
}
/**
* @brief Process circular buffer file opening.
* @param[in] inode
* @param[in] filp
* @return Always 0.
*/
int circbuf_open(struct inode *inode, struct file *filp)
{
unsigned int minor = MINOR(inode->i_rdev);
inode->i_size = circbuf_priv[minor_to_chn(minor, NULL)].buf_size; // CCAM__DMA_SIZE;
dev_dbg(g_dev_ptr, "inode->i_size = 0x%llx\n", inode->i_size);
return 0;
}
/**
* @brief Debug function, prints the content of interframe parameters.
* @param[in] params pointer to #interframe_params_t which should be printed
* @param[in] offset offset in circular buffer where the @e params is located
* @param[in] chn channel number this @e params belongs to
* @return None
*/
void dump_interframe_params(struct interframe_params_t *params, int offset, unsigned int chn)
{
dev_dbg(g_dev_ptr, "Dump of interframe parameters at offset 0x%x [chn %u]:\n", offset, chn);
print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, params, sizeof(struct interframe_params_t) - 4);
}
/**
* @brief Get the length of an image before given pointer. The pointer @e byte_offset
* points to the beginning of new image data segment and this function calculates
* the length of the previous image data segment and returns a pointer to the end of
* this segment.
* @param[in] byte_offset the offset of image data in circular buffer
* @param[in] chn circular buffer channel
* @param[out] last_chunk_offset the offset where image data ends
*/
unsigned long get_image_length(int byte_offset, unsigned int chn, int *last_chunk_offset)
{
unsigned int offset;
unsigned long len32;
int last_image_chunk = X393_BUFFSUB_CHN(byte_offset, OFFSET_X40, chn);
offset = X393_BUFFADD_CHN(last_image_chunk, CHUNK_SIZE - CCAM_MMAP_META_LENGTH, chn);
len32 = circbuf_priv[chn].buf_ptr[BYTE2DW(offset)];
dev_dbg(g_dev_ptr, "[chn %d] byte_offset = 0x%x, calculated offset = 0x%x, len32 = 0x%lx\n", chn, byte_offset, offset, len32);
if ((len32 & MARKER_FF) != MARKER_FF) {
dev_dbg(g_dev_ptr, "[chn %u] failed to get 0xff marker at offset 0x%x\n", chn, offset);
last_image_chunk = X393_BUFFSUB_CHN(byte_offset, OFFSET_X40 + CHUNK_SIZE, chn);
offset = X393_BUFFADD_CHN(last_image_chunk, CHUNK_SIZE - CCAM_MMAP_META_LENGTH, chn);
len32 = circbuf_priv[chn].buf_ptr[BYTE2DW(offset)];
if ((len32 & MARKER_FF) != MARKER_FF) {
dev_dbg(g_dev_ptr, "[chn %u] failed to get 0xff marker at CORRECTED offset 0x%x\n", chn, offset);
return 0;
}
}
dev_dbg(g_dev_ptr, "[chn %u] got len32 = 0x%lx at 0x%x\n", chn, len32, offset);
if (last_chunk_offset != NULL)
*last_chunk_offset = last_image_chunk;
return len32;
}
/**
* @brief Check that read pointer is valid
* @param[in] rp_offset read pointer to be checked; this pointer is in bytes
* @param[out] fpp pointer to #interframe_params_t structure, this pointer will be set to
* frame header before @e rp and will point to its parameters
* @param[in] chn specify compressor channel number which pointer should be checked
* @return 0 if the pointer is for the frame yet to be acquired, 1 if there is a valid frame at this address,
* 2 if file pointer should be advanced by 32 bytes,
* -1 if there is no frame at this index, -2 if the pointer is not 32-bytes aligned
* sets *fpp to the frame header, including signature and length
*/
#ifdef PRE_FRAMEPARS
int circbuf_valid_ptr(loff_t *rp_offset, struct interframe_params_t **fpp, unsigned int chn)
{
int rp = *rp_offset;
int last_image_chunk;
// unsigned int sec;
// unsigned int usec;
int wp = camseq_get_jpeg_wp(chn);
// unsigned int len32 = get_image_length(DW2BYTE(wp), chn, &last_image_chunk);
struct interframe_params_t *fp, *fp_off;
if (rp & 0x1f) {
// rp is not 32-bytes aligned
dev_dbg(g_dev_ptr, "misaligned pointer rp = 0x%x for channel %d\n", rp, chn);
return -2;
}
fp = (struct interframe_params_t *) &circbuf_priv[chn].buf_ptr[BYTE2DW(X393_BUFFSUB_CHN(rp, sizeof(struct interframe_params_t) - 4, chn))];
*fpp = fp;
dump_interframe_params(fp, X393_BUFFSUB_CHN(rp, sizeof(struct interframe_params_t) - 4, chn), chn);
if (BYTE2DW(rp) == wp)
// read pointer and write pointer coincide, frame not yet acquired
return 0;
if (fp->signffff != MARKER_FFFF) {
dev_dbg(g_dev_ptr, "[chn %u] interframe signature is overwritten, signffff = 0x%x\n", chn, fp->signffff);
fp_off = (struct interframe_params_t *) &circbuf_priv[chn].buf_ptr[BYTE2DW(rp)];
if (fp_off->signffff != MARKER_FFFF) {
dev_dbg(g_dev_ptr, "[chn %u] interframe signature is overwritten at CORRECTED offset, signffff = 0x%x\n", chn, fp_off->signffff);
return -1;
} else {
dev_dbg(g_dev_ptr, "[chn %u] interframe pointer and file ponter is advanced by 0x20\n", chn);
*fpp = fp_off;
*rp_offset = X393_BUFFADD_CHN(*rp_offset, CHUNK_SIZE, chn);
dump_interframe_params(fp_off, rp, chn);
return 2;
}
}
return 1;
}
#endif
#define X313_PADDED_FRAME(x)((((x)+67+CCAM_MMAP_META ) >>2) & 0xfffffff8)
/** Verify that the image pointer in the buffer is valid */
int circbufValidPointer(int rp, ///< rp_offset read pointer to be checked; this pointer is in bytes
struct interframe_params_t ** fpp, ///< pointer to #interframe_params_t structure, this pointer will be set to
///< the frame header before @e rp and will point to its parameters
unsigned int chn) ///< sensor port number
///< @return
/// * 0 if the pointer is for the frame yet to be acquired,
///< * 1 if there is a valid frame at this address,
///< * 2 if file pointer should be advanced by 32 bytes,
///< * -1 if there is no frame at this index,
///< * -2 if the pointer is not 32-bytes aligned
///< Sets *fpp to the frame header, including signature and length
{
struct interframe_params_t * fp;
int wp, p;
if (rp & 0x1f) { //!rp is not 32-bytes aligned
dev_dbg(g_dev_ptr, "circbufValidPointer: misaligned pointer\n");
return -2;
}
wp=camSeqGetJPEG_wp(chn);
p= rp >> 2; // index in ccam_dma_buf
fp = (struct interframe_params_t *) &circbuf_priv[chn].buf_ptr[X393_BUFFSUB32(p, 8, chn)]; // 32 bytes before the frame pointer, may roll-over to the end of ccam_dma_buf
dev_dbg(g_dev_ptr, "rp=0x%x (0x%x), offset=0x%x\n",
rp, p, (int) &circbuf_priv[chn].buf_ptr[p]-(int)fp);
// dumpFrameParams(fp, "in circbufValidPointer:");
*fpp=fp;
dev_dbg(g_dev_ptr, "p=0x%x , wp==0x%x\n",p,wp);
if (p == wp) {
dev_dbg(g_dev_ptr, "circbufValidPointer: frame not yet acquired, fp - not valid\n");
return 0; // frame not yet acquired, fp - not valid
}
if (fp->signffff != 0xffff) { //! signature is overwritten
int i;
dev_dbg(g_dev_ptr, "circbufValidPointer: signature overwritten\n");
dev_dbg(g_dev_ptr, "wp = 0x%x, p=0x%x\n",wp,p);
for (i=p-16; itimestamp_sec) & X313_LENGTH_MASK) {
dev_dbg(g_dev_ptr, "Should not get here - fp->timestamp_sec=0x%x\n",(int) fp->timestamp_sec);
return 0; //! should not get here - should be caught by (p==wp)
}
return 1;
}
/**
* @brief Reposition read/write file offset
*
* This function is overloaded with additional functionality in order to avoid ioctls.
* In case user-space program set orig == SEEK_END, @e lseek will treat (offset > 0) as a command
* to manipulate frame pointer(s) or wait for the image to be ready using these commands:
* command | description
* ------------------|------------------------------------------------------------------------------------
* LSEEK_CIRC_TORP | set file pointer to global (shared) read pointer
* LSEEK_CIRC_TOWP | set file pointer to FPGA write pointer (next frame to be acquired)
* LSEEK_CIRC_PREV | move pointer to the previous frame, return @e -EOVERFLOW if there are none
* LSEEK_CIRC_NEXT | advance pointer to the next frame, return @e -EOVERFLOW if it was at the last frame
* LSEEK_CIRC_LAST | move pointer to the last acquired frame (default after open), this is a combination of LSEEK_CIRC_TOWP and LSEEK_CIRC_PREV
* LSEEK_CIRC_FIRST | move pointer to the first acquired frame. It s not safe to rely on this pointer if more frames are expected - next incoming frame can overwrite this one
* LSEEK_CIRC_SCND | move pointer to the second oldest acquired frame. A slightly safer to use instead of LSEEK_CIRC_FIRST when constant acquisition is on and sensor provides new frames - this frame will likely survive longer
* LSEEK_CIRC_SETP | save current pointer to global read pointer
* LSEEK_CIRC_VALID | verify that the frame at current location is valid (not overrun in the buffer). Returns file pointer if it is valid and -1 otherwise
* LSEEK_CIRC_READY | verify frame at current location is available (valid and acquired). Returns file pointer if it is ready or -1 otherwise
* LSEEK_CIRC_WAIT | sleep until next frame is acquired
* All commands but (LSEEK_CIRC_TOWP, LSEEK_CIRC_LAST, LSEEK_CIRC_FIRST) will return -EINVAL if read
* pointer is not valid (i.e buffer was overrun and data pointed is lost). In case of success, they return
* current (byte *) to the start of the frame data (parameters are at offset - 32 from it).
* (0, SEEK_CUR) also verifies that the header is not overwritten. It can be used after buffering frame data to
* verify you got it all correctly.
*
* SEEK_CUR also supports the circular nature of the buffer and rolls over if needed.
*
* Additional commands for SEEK_END (they _DO_ modify the current file pointer):
* command | description
* ------------------|------------------------------------------------------------------------------------
* LSEEK_CIRC_FREE | returns remaining memory in circbuf from the current file pointer, or -EINVAL if the pointer is invalid. As this command uses the buffer write pointer that is updated only when the complete frame is in the buffer, the actual free memory may be less by a whole frame if compressor is running
* LSEEK_CIRC_USED | returns memory used in the in circbuf from the current file pointer, or -EINVAL if the pointer is invalid
* The following command is used for profiling from user space applications. It does not change file pointer:
* LSEEK_CIRC_UTIME return current value of microsecond counter.
* @param[in] file pointer to @e file structure
* @param[in] offset offset inside buffer in bytes
* @param[in] orig where the @e offset should start
* @return Current file pointer position if operation was successful and error code otherwise
*/
loff_t circbuf_lseek(struct file * file, loff_t offset, int orig) {
// orig 0: position from begning
// orig 1: relative from current
// orig 2: position from last address
// int l = CCAM_DMA_SIZE;
int fl=0;// frame length
struct interframe_params_t * fp;
int fvld=-1;
int rp=0, bp, prev_p, preprev_p; //, p;
int nf; //! number of frames;
int nz=1; //! number of crossing of start of the circular buffer (counter to prevent looping forever)
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int chn = minor_to_chn(minor, NULL);
//MD12(int dbg_i);
// int pf; // previous frame
dev_dbg(g_dev_ptr, "circbuf_lseek, offset=0x%x, orig=0x%x\n",(int) offset, (int) orig);
switch(orig) {
case SEEK_SET:
file->f_pos = offset;
break;
case SEEK_CUR:
if (offset) file->f_pos += offset;
else if (circbufValidPointer(file->f_pos, &fp, chn) <0 ) return -EINVAL; //!no frames at the specified location or pointer is not 32-byte aligned
break;
case SEEK_END:
if (offset <= 0) {
file->f_pos = circbuf_priv_ptr[chn].buf_size + offset;
} else { //! New functionality
//!verify the frame pointer
switch (offset) {
case LSEEK_CIRC_TORP:
file->f_pos=camSeqGetJPEG_rp(chn)<<2; //! set file pointer to global read pointer, and precoeed
case LSEEK_CIRC_PREV:
case LSEEK_CIRC_NEXT:
case LSEEK_CIRC_SETP:
case LSEEK_CIRC_VALID:
case LSEEK_CIRC_READY:
case LSEEK_CIRC_FREE:
case LSEEK_CIRC_USED:
if (((fvld=circbufValidPointer(file->f_pos, &fp, chn))) <0 )
return -EINVAL; //!no frames at the specified location
}
switch (offset) {
case LSEEK_CIRC_GETFRAME:
return get_compressor_frame(chn); // New in NC393
case LSEEK_CIRC_FREE:
bp=(file->f_pos - (camSeqGetJPEG_wp(chn)<<2));
// return (bp>0)?bp:(bp+l); //!will return full buffer size if current pointer is a write pointer (waiting for the next frame)
return (file->f_pos=(bp>0)?bp:(bp + circbuf_priv_ptr[chn].buf_size)); //!Has a side effect of moving a file pointer!
case LSEEK_CIRC_USED:
bp=((camSeqGetJPEG_wp(chn)<<2) - file->f_pos);
// return (bp>=0)?bp:(bp+l); //!will return 0 if current pointer is a write pointer (waiting for the next frame)
return (file->f_pos=(bp>0)?bp:(bp + circbuf_priv_ptr[chn].buf_size)); //!Has a side effect of moving a file pointer!
case LSEEK_CIRC_TORP:
break;
case LSEEK_CIRC_TOWP:
file->f_pos=camSeqGetJPEG_wp(chn)<<2; // no checking if it is valid
break;
case LSEEK_CIRC_LAST:
file->f_pos=camSeqGetJPEG_wp(chn)<<2;
fvld=circbufValidPointer(file->f_pos, &fp, chn); //!set fp
if (fvld <0 ){
dev_dbg(g_dev_ptr, "*** LSEEK_CIRC_LAST: invalid pointer rp=0x%x, fvld=%d, fl=0x%x\n",
(int) (file->f_pos >> 2), (int)fvld,(int)circbuf_priv[chn].buf_ptr[X393_BUFFSUB32(rp, 9, chn)] ^ X313_LENGTH_MASK);
}
case LSEEK_CIRC_PREV:
rp= file->f_pos >> 2;
fl=circbuf_priv[chn].buf_ptr[X393_BUFFSUB32(rp, 9, chn)] ^ X313_LENGTH_MASK;
dev_dbg(g_dev_ptr, "LSEEK_CIRC_PREV: rp=0x%x, fvld=%d, fl=0x%x\n", rp, fvld,fl);
if (fl & X313_LENGTH_MASK) {
if (offset==LSEEK_CIRC_LAST) break; // just don't move pointer, leave it at write pointer and return no error
return -EOVERFLOW; //! no frames before current
}
bp = (X393_BUFFSUB32(rp, X313_PADDED_FRAME(fl),chn)<<2); // in bytes
dev_dbg(g_dev_ptr, "LSEEK_CIRC_PREV: bp=0x%x (0x%x), circbufValidPointer=%d\n", bp, bp>>2,circbufValidPointer(rp>>2, &fp,chn));
if (circbufValidPointer(bp, &fp, chn) < 0 ) { //! no valid frames before current
if (offset==LSEEK_CIRC_LAST) break; // just don't move pointer, leave it at write pointer and return no error
return -EOVERFLOW; //! no frames before current
}
//! move frame pointer only if there is a valid frame there
file->f_pos=bp;
break;
case LSEEK_CIRC_NEXT:
dev_dbg(g_dev_ptr, "LSEEK_CIRC_NEXT: rp=0x%llx, fvld=%d, fp->timestamp_sec=0x%lx\n", file->f_pos >> 2, fvld, fp->timestamp_sec);
if (fvld <=0)
return -EOVERFLOW; //! no frames after current
file->f_pos = X393_BUFFADD32(file->f_pos >> 2, X313_PADDED_FRAME(fp->timestamp_sec),chn) <<2 ;// do it even if the next frame does not yet exist
break;
case LSEEK_CIRC_FIRST:
case LSEEK_CIRC_SCND:
//! Starting from the write pointer do be able to count all the frames in the buffer
rp=camSeqGetJPEG_wp(chn);
prev_p=rp;
preprev_p=prev_p; // for second
nf=0;
nz=1;
file->f_pos=rp<<2;
while ((((fvld=circbufValidPointer(rp<<2, &fp, chn))) >= 0) & (nz>=0)) {
nf++;
// file->f_pos=rp<<2;
preprev_p=prev_p; //! second known good (at least first one)
prev_p=rp; //!now - current, known good
fl=circbuf_priv[chn].buf_ptr[X393_BUFFSUB32(rp, 9, chn)] ^ X313_LENGTH_MASK;
dev_dbg(g_dev_ptr, "\nf=%d, rp=0x%x, fvld=%d, fl=0x%x\n",nf, rp, fvld, fl);
if (fl & X313_LENGTH_MASK) break; //! no frames before rp (==prev_p)
//! move rp to the previous frame
rp= X393_BUFFSUB32(rp, X313_PADDED_FRAME(fl),chn);
if (rp > prev_p) nz-- ; // rolled through zero - make sure we'll not stuck in this loop forever
}
dev_dbg(g_dev_ptr, "after while{}: nf=%d, rp=0x%x, fvld=%d, fl=0x%x\n",nf, rp, fvld, fl);
file->f_pos=((offset==LSEEK_CIRC_SCND)?preprev_p:prev_p) << 2;
break;
case LSEEK_CIRC_SETP:
camSeqSetJPEG_rp(chn, file->f_pos>>2);
break;
case LSEEK_CIRC_VALID:
break;
case LSEEK_CIRC_READY:
if (fvld <=0) return -EINVAL; //! no frame is available better code?
break;
case LSEEK_CIRC_WAIT:
while (((fvld=circbufValidPointer(file->f_pos, &fp, chn)))==0) { //! only while not ready, ready or BAD - return
wait_event_interruptible (circbuf_wait_queue,(camSeqGetJPEG_wp(chn)<<2)!=file->f_pos);
}
if (fvld < 0) return -ESPIPE; //!invalid seek - have better code?
return file->f_pos ; //! data already available, return file pointer
default:
if ((offset & ~0x1f)==LSEEK_DAEMON_CIRCBUF) {
wait_event_interruptible (circbuf_wait_queue, get_imageParamsFrame(chn, P_DAEMON_EN, camSeqGetJPEG_frame(chn)) & (1<<(offset & 0x1f)));
}
}
dev_dbg(g_dev_ptr, "return SEEK_END file->f_pos =0x%08llx\n",file->f_pos);
return ( file->f_pos ); //! file position >=0
}
break;
default:
return -EINVAL;
}
// roll-over position
while (file->f_pos < 0) file->f_pos+= circbuf_priv_ptr[chn].buf_size;
while (file->f_pos > circbuf_priv_ptr[chn].buf_size) file->f_pos-= circbuf_priv_ptr[chn].buf_size;
if ((orig !=SEEK_END) && (file->f_pos == circbuf_priv_ptr[chn].buf_size)) file->f_pos=0; //!only for lseek (fd,0,SEEK_END) the result will be file size, others will roll to 0
dev_dbg(g_dev_ptr, "return file->f_pos =0x%08llx\n",file->f_pos);
return file->f_pos ;
}
/**
* @brief This function handles write operations for circbuf files.
* @note Never use @e file->f_pos in this function.
* @param[in] file pointer to struct file
* @param[in] buf pointer to buffer containing data
* @param[in] count number of bytes in buffer
* @param[in] off offset
* @return number of bytes read form @e buf
*/
ssize_t circbuf_write(struct file *file, const char *buf, size_t count, loff_t *off)
{
unsigned long p;
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int chn = minor_to_chn(minor, NULL);
dev_dbg(g_dev_ptr, "minor = 0x%x, count = 0x%x, off = 0x%x", minor, count,(int) off);
p = *off;
if (p >= circbuf_priv[chn].buf_size) //CCAM__DMA_SIZE)
p = circbuf_priv[chn].buf_size; // CCAM__DMA_SIZE;
if ((p + count) > circbuf_priv[chn].buf_size) // CCAM_-MA_SIZE)
count = circbuf_priv[chn].buf_size-p; // CCAM__DMA_SIZE - p;
if (count) {
if (copy_from_user(&circbuf_priv[chn].buf_ptr[BYTE2DW(p)], buf, count))
return -EFAULT;
*off += count;
}
return count;
}
/**
* @brief This function handles read operations for circbuf files.
* @note Never use @e file->f_pos in this function.
* @param[in] file pointer to struct file
* @param[in] buf pointer to buffer where data will be written to
* @param[in] count number of bytes written to @e buf
* @param[in] off offset
* @return Number of bytes written to @e buf
*/
ssize_t circbuf_read(struct file *file, char *buf, size_t count, loff_t *off)
{
unsigned long p;
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int chn = minor_to_chn(minor, NULL);
dev_dbg(g_dev_ptr, "minor = 0x%x, count = 0x%x, off = 0x%llx", minor, count, *off);
p = *off;
if (p >= circbuf_priv[chn].buf_size) // CCAM__DMA_SIZE)
p = circbuf_priv[chn].buf_size; // CCAM__DMA_SIZE;
if ((p + count) > circbuf_priv[chn].buf_size) // CCAM__DMA_SIZE)
count = circbuf_priv[chn].buf_size - p; // CCAM__DMA_SIZE - p;
if (count) {
if (copy_to_user(buf, &circbuf_priv[chn].buf_ptr[BYTE2DW(p)], count))
return -EFAULT;
*off+=count;
}
return count;
}
/**
* @brief Process memory map operation on circular buffer file.
* @param[in] file pointer to file structure corresponding to the circular buffer file
* @param[in] vma contains the information about the virtual address range
* @return 0 if file was mapped successfully and negative error code otherwise
*/
int circbuf_mmap(struct file *file, struct vm_area_struct *vma)
{
int ret;
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int chn = minor_to_chn(minor, NULL);
dev_dbg(g_dev_ptr, "vm_start = 0x%lx\n", vma->vm_start);
dev_dbg(g_dev_ptr, "vm_end = 0x%lx\n", vma->vm_end);
dev_dbg(g_dev_ptr, "vm_pgoff = 0x%lx\n", vma->vm_pgoff);
dev_dbg(g_dev_ptr, "vm_file = 0x%lx\n", (unsigned long)vma->vm_file);
dev_dbg(g_dev_ptr, "ccam_dma_buf = 0x%lx\n", (unsigned long)circbuf_priv[chn].phys_addr);
/* remap_pfn_range will mark the range VM_IO and VM_RESERVED */
ret = remap_pfn_range(vma,
vma->vm_start,
circbuf_priv[chn].phys_addr >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
dev_dbg(g_dev_ptr, "remap_pfn_range returned 0x%x\n", ret);
if (ret) return -EAGAIN;
return 0;
}
/**
* @brief This driver method is called when user-space program performs poll, select or
* @e epoll system call.
*
* If the current read pointer is invalid, circbuf_poll returns POLLHUP
* as no data will be ever available until file pointer is reset.
* If it is valid, wait is setup and blocking condition occurs in case
* current file pointer is equal to the FPGA write pointer.
* @param[in] file pointer to struct file structure
* @param[in] wait pointer to struct poll_table structure
* @return POLLHUP if pointer is invalid, (POLLIN | POLLRDNORM) if frame is ready,
* 0 in case nothing is ready.
*/
unsigned int circbuf_poll (struct file *file, poll_table *wait)
{
int w_ptr;
unsigned int minor = MINOR(file->f_inode->i_rdev);
unsigned int chn = minor_to_chn(minor, NULL);
struct interframe_params_t * fp;
int rslt;
dev_dbg(g_dev_ptr, "minor = 0x%x\n", minor);
rslt = circbufValidPointer(&file->f_pos, &fp, chn);
if (rslt < 0) {
// not a valid read pointer, probable buffer overrun
dev_dbg(g_dev_ptr, "invalid pointer file->f_pos = 0x%llx\n", file->f_pos);
return POLLHUP ;
} else if (rslt > 0) {
return POLLIN | POLLRDNORM; // there was frame already available
} else {
// pointer valid, no frame yet
poll_wait(file, &circbuf_wait_queue, wait);
// Frame might become available during call to poll_wait so nobody will wake us up,
// let's see if there is still no frame.
w_ptr = camseq_get_jpeg_wp(chn) << 2;
if (w_ptr != file->f_pos)
return POLLIN | POLLRDNORM; // we are lucky - got it
}
return 0; // nothing ready
}
static struct file_operations circbuf_fops = {
.owner = THIS_MODULE,
.llseek = circbuf_all_lseek,
.read = circbuf_all_read,
.write = circbuf_all_write,
.open = circbuf_all_open,
.mmap = circbuf_all_mmap,
.poll = circbuf_all_poll,
.release = circbuf_all_release
};
/**
* @brief cirbuf driver probing function
* @param[in] pdev pointer to @e platform_device structure
* @return 0 on success or negative error code otherwise
*/
static int circbuf_all_init(struct platform_device *pdev)
{
int res;
struct device *dev = &pdev->dev;
const struct of_device_id *match;
/* sanity check */
match = of_match_device(elphel393_circbuf_of_match, dev);
if (!match)
return -EINVAL;
dev_dbg(dev, "registering character device with name 'circbuf_operations'");
res = register_chrdev(DEV393_MAJOR(DEV393_CIRCBUF0), "circbuf_operations", &circbuf_fops);
if(res < 0) {
dev_err(dev, "couldn't get a major number %d.\n", DEV393_MAJOR(DEV393_CIRCBUF0));
return res;
}
dev_info(dev, "registered MAJOR: %d\n", DEV393_MAJOR(DEV393_CIRCBUF0));
res = jpeghead_init(pdev);
if (res < 0) {
dev_err(dev, "unable to initialize jpeghead module\n");
return res;
}
res = image_acq_init(pdev);
if (res < 0) {
dev_err(dev, "unable to initialize sensor_common module\n");
return res;
}
res = init_ccam_dma_buf_ptr(pdev);
if (res < 0) {
dev_err(dev, "ERROR allocating coherent DMA buffer\n");
return -ENOMEM;
}
dev_dbg(dev, "initialize circbuf wait queue\n");
init_waitqueue_head(&circbuf_wait_queue);
dev_dbg(dev, "initialize Huffman tables with default data\n");
g_dev_ptr = dev;
return 0;
}
/**
* @brief cirbuf driver remove function
* @param[in] pdev pointer to @e platform_device structure
* @return Always 0.
*/
static int circbuf_remove(struct platform_device *pdev)
{
unregister_chrdev(DEV393_MAJOR(DEV393_CIRCBUF0), "circbuf_operations");
return 0;
}
static const struct of_device_id elphel393_circbuf_of_match[] = {
{ .compatible = "elphel,elphel393-circbuf-1.00" },
{ /* end of list */ }
};
MODULE_DEVICE_TABLE(of, elphel393_circbuf_of_match);
static struct platform_driver elphel393_circbuf = {
.probe = circbuf_all_init,
.remove = circbuf_remove,
.driver = {
.name = DEV393_NAME(DEV393_CIRCBUF0),
.of_match_table = elphel393_circbuf_of_match,
},
};
module_platform_driver(elphel393_circbuf);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andrey Filippov .");
MODULE_DESCRIPTION(DEV393_NAME(DEV393_CIRCBUF0));
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/circbuf.h 0000664 0000000 0000000 00000007303 12766141417 0026154 0 ustar 00root root 0000000 0000000 /** @file circbuf.h
*
* @brief Drivers to manipulate large circular buffer that holds compressed
* images/video. Buffer frame data is filled in by the FPGA, frame pointers and
* essential frames metadata filled during servicing of the interrupts.
*
* @copyright Copyright (C) 2016 Elphel, Inc
*
* @par License
* 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 .
*/
#ifndef _CIRCBUF_H
#define _CIRCBUF_H
#include
/** @brief Circular buffer private data */
struct circbuf_priv_t {
int minor; ///< device file minor number
unsigned long *buf_ptr; ///< pointer to circular buffer memory region
unsigned long buf_size; ///< circular region size in bytes
unsigned long buf_size32; ///< circular region size in dwords
dma_addr_t phys_addr; ///< physical address of memory region reported by memory driver
};
struct circbuf_priv_t *get_circbuf(int chn); // alternative to use of extern struct circbuf_priv_ptr;
extern struct circbuf_priv_t *circbuf_priv_ptr;
extern wait_queue_head_t circbuf_wait_queue;
int circbuf_all_open (struct inode *inode, struct file *filp); // set file size
int circbuf_all_release(struct inode *inode, struct file *filp);
loff_t circbuf_all_lseek (struct file * file, loff_t offset, int orig);
ssize_t circbuf_all_write (struct file * file, const char * buf, size_t count, loff_t *off);
ssize_t circbuf_all_read (struct file * file, char * buf, size_t count, loff_t *off);
int circbuf_all_mmap (struct file *file, struct vm_area_struct *vma);
unsigned int circbuf_all_poll (struct file *file, poll_table *wait);
int circbuf_open (struct inode *inode, struct file *filp); // set file size
loff_t circbuf_lseek (struct file * file, loff_t offset, int orig);
ssize_t circbuf_write (struct file * file, const char * buf, size_t count, loff_t *off);
ssize_t circbuf_read (struct file * file, char * buf, size_t count, loff_t *off);
int circbuf_mmap (struct file *file, struct vm_area_struct *vma);
unsigned int circbuf_poll (struct file *file, poll_table *wait);
#ifdef USE_OLD_CODE
//int init_ccam_dma_buf_ptr(void);
/*!======================================================================================
* Wait queue for the processes waiting for a new frame to appear in the circular buffer
*======================================================================================*/
extern wait_queue_head_t circbuf_wait_queue;
// private data
struct circbuf_priv_t {
int minor;
unsigned long *buf_ptr;
dma_addr_t phys_addr;
};
extern struct circbuf_priv_t *circbuf_priv_ptr;
#endif
/* debug code follows */
#ifdef PRE_FRAMEPARS
extern unsigned short circbuf_quality;
extern unsigned short circbuf_height;
extern unsigned short circbuf_width;
extern unsigned char circbuf_byrshift;
#endif
/* end of debug code */
#endif /* _CIRCBUF_H */
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/clock10359.c 0000664 0000000 0000000 00000041053 12766141417 0026227 0 ustar 00root root 0000000 0000000 /***************************************************************************//**
* @file clock10359.c
* @brief Control of the CY22393 clock on the 10359 multiplexer connected
* to the sensor port
* @copyright Copyright 2002-2016 (C) Elphel, Inc.
* @par License
* 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 .
*******************************************************************************/
/****************** INCLUDE FILES SECTION ***********************************/
//#define DEBUG /* should be before linux/module.h - enables dev_dbg at boot in this file */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "x393.h"
#include "sensor_i2c.h"
#include "clock10359.h"
#define SYSFS_PERMISSIONS 0644 /* default permissions for sysfs files */
#define SYSFS_READONLY 0444
#define SYSFS_WRITEONLY 0222
#define DRV_NAME "elphel_clock10359"
static int clock_frequency[16]; // in Hz per port, per clock
static struct device *sdev = NULL; // store this device here
const char CLOCK_NAME[] = "cy22393";
typedef struct {
unsigned int p : 11; // p - 16..1600
unsigned int q : 8; // q - 0.. 255
unsigned int dv: 7; // dv - 1.. 127
unsigned int corr: 3; // 0/1/2/3/4
unsigned int rslt: 3; // 0 - OK, 1 - frequency low, 2 - frequency high, 3 - other
} t_pll_params;
// in Hz
#define CY22393_SCALE 100 // precision will be 100Hz
#define CY22393_PLLMIN (100000000/CY22393_SCALE)
#define CY22393_PLLMAX (400000000/CY22393_SCALE)
#define CY22393_XTAL ( 12000000/CY22393_SCALE)
#define CY22393_OUTMAX (166000000/CY22393_SCALE)
#define CY22393_PMIN 16
#define CY22393_PMAX 1600
int calc_pll_params (unsigned int f, t_pll_params * pars); // f -in Hz
int setCYField (int sensor_port, int addr, int mask, int value); /// bus==1 - FPGA (sensor bus through 10359), 0 - CPU bus
int calc_pll_params (unsigned int f, t_pll_params * pars) { // f -in Hz
// t_pll_params pars;
unsigned int f0= CY22393_XTAL;
unsigned int f0_2= CY22393_XTAL/2;
unsigned int fpmn= CY22393_XTAL * (CY22393_PMIN + 6);
unsigned int fpmx= CY22393_XTAL * (CY22393_PMAX + 6);
int divmn, divmx, err1,err, div,q,qmn,qmx,qmx1,fdv,p, e,fdvq;
pars->rslt=3; // other error
dev_dbg(sdev, "f0=%d,f=%d, CY22393_OUTMAX=%d\r\n",f0,f,CY22393_OUTMAX);
f/=CY22393_SCALE; // to fit into 32-bit calculations
dev_dbg(sdev, "f0=%d,f=%d, CY22393_OUTMAX=%d\r\n",f0,f,CY22393_OUTMAX);
if (f>CY22393_OUTMAX) {
pars->rslt=2;
dev_dbg(sdev, "f0=%d,f=%d, CY22393_OUTMAX=%d\r\n",f0,f,CY22393_OUTMAX);
return pars->rslt;
}
if (f <=0 ) {
pars->rslt=1;
dev_dbg(sdev, "f0=%d,f=%d, CY22393_OUTMAX=%d\r\n",f0,f,CY22393_OUTMAX);
return pars->rslt;
}
divmx=CY22393_PLLMAX/f; if (divmx > 127) divmx=127; // could not be <1
divmn=CY22393_PLLMIN/f; if (divmn < 1) divmn=1;
if (divmn >127) {
pars->rslt=1;
dev_dbg(sdev, "f0=%d,f=%d, CY22393_OUTMAX=%d, divmn=%d\r\n",f0,f,CY22393_OUTMAX,divmn);
return pars->rslt;
}
err1=f;
qmx1=0;
for (div=divmn;div<=divmx;div++) {
err=err1*div;
fdv=f*div;
qmn=fpmn/fdv-2; if (qmn < 0) qmn=0;
qmx=fpmx/fdv-2; if (qmx >255) qmx=255;
// recalculate qmn to avoid same div*qmn as already tried with lover div
dev_dbg(sdev, "div=%d,qmn=%d, qmx=%d\r\n",div,qmn,qmx);
if (div==1) qmx1=qmx;
else if ((qmn*div) < qmx1) qmn=qmx1/div;
for (q=qmn+2;q<=qmx+2; q++) {
fdvq=fdv*q;
p= (fdvq+f0_2)/f0; // can p be out of range here?
e= fdvq-f0*p; if (e<0) e=-e;
if (e< (err*q)) { // better solution found
pars->rslt=0;
pars->p=p-6;
pars->q=q-2;
pars->dv=div;
err1=e/q/div;
err=err1*div;
dev_dbg(sdev, "f=%d, div=%d, p=%d,q=%d, err1=%d\r\n", (f0*p)/q/div, div,p, q, err1);
if (err1==0) {
pars->corr=(pars->p<226)?0:((pars->p<621)?1:((pars->p<829)?2:((pars->p<1038)?3:4)));
dev_dbg(sdev, "f=%d, div=%d, p=%d,q=%d, err1=%d, rslt=%d\r\n",
(f0*(pars->p+6))/(pars->q+2)/pars->dv,
pars->dv,
(pars->p+6),
(pars->q+2),
err1,
pars->rslt);
return pars->rslt;
}
}
}
}
dev_dbg(sdev, "f=%d, div=%d, p=%d,q=%d, err1=%d, rslt=%d\r\n",
(f0*(pars->p+6))/(pars->q+2)/pars->dv,
pars->dv,
(pars->p+6),
(pars->q+2),
err1,
pars->rslt);
pars->corr=(pars->p<226)?0:((pars->p<621)?1:((pars->p<829)?2:((pars->p<1038)?3:4)));
return pars->rslt;
}
int setCYField (int sensor_port, int reg_addr, int mask, int value) {
int error;
int reg_data;
if ((error = x393_xi2c_read_reg(CLOCK_NAME, // device class name
sensor_port, // sensor port number
0, // slave address (7-bit) offset from the class defined slave address
reg_addr, // register address (width is defined by class)
®_data)) <0) { // pointer to a data receiver (read data width is defined by class)
dev_err(sdev,"setCYField(%d, 0x%x, 0x%x,0x%x) failed reading i2c register\n",
sensor_port, reg_addr, mask, value);
return error;
}
reg_data ^= (reg_data ^ value) & mask;
if ((error = x393_xi2c_write_reg(CLOCK_NAME, // device class name
sensor_port, // sensor port number
0, // slave address (7-bit) offset from the class defined slave address
reg_addr, // register address (width is defined by class)
reg_data)) <0) { // data to write (width is defined by class)
dev_err(sdev,"setCYField(%d, 0x%x, 0x%x, 0x%x) failed writing 0x%x to i2c register\n",
sensor_port, reg_addr, mask, value,reg_data);
return error;
}
return 0;
}
int x393_getClockFreq(int sensor_port, int nclock) {
if ((sensor_port < 0) || (sensor_port > 3) || (nclock < 0) || (nclock > 3))return -EINVAL; // bad clock number
else {
return clock_frequency[(sensor_port << 2) || nclock];
}
}
EXPORT_SYMBOL_GPL(x393_getClockFreq);
int x393_setClockFreq(int sensor_port, int nclock, int freq)
{ // freq now in Hz
int err=0;
sensor_port &= 3;
nclock &= 3;
t_pll_params pll_params;
int i,bp,bq,bdiv,pllc,fact;
bp=0; bq=0; bdiv=0; pllc= 0; // just to make gcc happy
fact=0;
dev_dbg(sdev, "setClockFreq(%d,%d,%d)\r\n",sensor_port,nclock,freq);
if ((freq!=0) && (nclock!=3) ){
if ( (i=calc_pll_params (freq, &pll_params)) !=0) {
dev_err(sdev, "bad frequency for clock %d - %d Hz, err=%d\n",nclock,freq,i);
return -EINVAL;
}
fact=CY22393_SCALE*(CY22393_XTAL*(pll_params.p+6)/(pll_params.q+2)/pll_params.dv);
bp= pll_params.p; // (freqtab[freq]>>20)&0x7ff;
bq= pll_params.q; // (freqtab[freq]>>12)&0xff;
bdiv=pll_params.dv; // (freqtab[freq]>> 4)&0xff;
pllc=pll_params.corr; // freqtab[freq] &0x0f;
}
switch (nclock) {
case 0:
if (freq==0) {
err |= setCYField (sensor_port,0x16, 0x40, 0x00); // turn off pll3
err |= setCYField (sensor_port,0x09, 0x7f, 0x00); // turn off divider- A
err |= setCYField (sensor_port,0x08, 0x7f, 0x00); // turn off divider- A
} else {
err |= setCYField (sensor_port,0x16, 0x7f, 0x40+(pllc<<3)+((bp & 1)<<2)+((bp & 0x600)>>9) );
err |= setCYField (sensor_port,0x15, 0xff, ((bp & 0x1fe)>>1) );
err |= setCYField (sensor_port,0x14, 0xff, bq );
err |= setCYField (sensor_port,0x09, 0x7f, bdiv); // set divider- A
err |= setCYField (sensor_port,0x08, 0x7f, bdiv); // set divider- A
err |= setCYField (sensor_port,0x0e, 0x03, 0x03); // set PLL3 as source for ClkA
}
// fpga_state |= FPGA_STATE_CLOCKS;
break;
case 1:
if (freq==0) {
err |= setCYField (sensor_port,0x0b, 0x7f, 0x00); // turn off divider- B
err |= setCYField (sensor_port,0x0a, 0x7f, 0x00); // turn off divider- B
for (i=0;i<24;i+=3) err |= setCYField (sensor_port,0x42+i, 0x40, 0x00); // turn off pll1
} else {
// progam all variants
for (i=0;i<24;i+=3) {
err |= setCYField (sensor_port,0x42+i, 0x7f, 0x40+(pllc<<3)+((bp & 1)<<2)+((bp & 0x600)>>9) );
err |= setCYField (sensor_port,0x41+i, 0xff, ((bp & 0x1fe)>>1) );
err |= setCYField (sensor_port,0x40+i, 0xff, bq );
}
err |= setCYField (sensor_port,0x0b, 0x7f, bdiv); // set divider- B
err |= setCYField (sensor_port,0x0a, 0x7f, bdiv); // set divider- B
err |= setCYField (sensor_port,0x0e, 0x0c, 0x04); // set PLL1 as source for ClkB
}
break;
case 2:
if (freq==0) {
err |= setCYField (sensor_port,0x13, 0x40, 0x00); // turn off pll2
err |= setCYField (sensor_port,0x0d, 0x7f, 0x00); // turn off divider- D
} else {
err |= setCYField (sensor_port,0x13, 0x7f, 0x40+(pllc<<3)+((bp & 1)<<2)+((bp & 0x600)>>9) );
err |= setCYField (sensor_port,0x12, 0xff, ((bp & 0x1fe)>>1) );
err |= setCYField (sensor_port,0x11, 0xff, bq );
err |= setCYField (sensor_port,0x0d, 0x7f, bdiv); // set divider- D
err |= setCYField (sensor_port,0x0e, 0xc0, 0x80); // set PLL2 as source for ClkD
}
break;
case 3:
if ((freq!=0) && (freq!=CY22393_SCALE*CY22393_XTAL)) {
dev_err(sdev, "Only frequency 0 (off) and %d Hz (xtal) are allowed for channel 3\r\n",CY22393_SCALE*CY22393_XTAL);
return -EINVAL;
} else {
// int setCYField (sensor_port,int devfd, int addr, int mask, int value) O_RDWR
if (freq==0) {
err |= setCYField (sensor_port,0x0f, 0x04, 0x00);
} else {
err |= setCYField (sensor_port,0x0f, 0x04, 0x04);
fact= CY22393_SCALE*CY22393_XTAL;
}
}
break;
default: return -1; // wrong clock number
}
dev_dbg(sdev, "nclock=%d fact=%d\n",nclock,fact);
if (err != 0) {
dev_err(sdev, "Error programming clock %d fact=%d, err=0x%x\n",nclock,fact, err);
return err;
}
clock_frequency[(sensor_port << 2) + nclock] = fact;
return fact;
}
EXPORT_SYMBOL_GPL(x393_setClockFreq);
// dev_warn(dev,"i2c_member_store(): not implemented\n");
/* =========================== sysfs functionality ============================== */
/* Get channel port and channel number from the last 2 character of the attribute name*/
static int get_port_channel_from_name(struct device_attribute *attr){
int reg = 0;
int sport,nclock;
sscanf(attr->attr.name + (strlen(attr->attr.name)-2), "%du", ®);
sport = reg / 10;
nclock = reg - 10 * sport;
return (nclock & 3) + ((sport &3) <<2);
}
static ssize_t clock_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn = get_port_channel_from_name(attr) ;
int sensor_port = chn >> 2;
int nclock = chn & 3;
int ni, freq, err;
ni = sscanf(buf,"%i", &freq);
if (ni < 1) {
dev_err(dev, "Requires clock frequency in Hz");
return -EINVAL;
}
if ((err = x393_setClockFreq(sensor_port, nclock, freq)))
return err;
return count;
}
static ssize_t clock_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn = get_port_channel_from_name(attr);
int sensor_port = chn >> 2;
int nclock = chn & 3;
int freq = x393_getClockFreq(sensor_port, nclock);
if (freq < 0)
return freq;
return sprintf(buf,"%d\n",freq);
}
// Get i2c read data from fifo
static ssize_t clock_help_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Numeric suffix in file names selects sensor port/clock number\n"
"clock: read - get programmed clock frequency in Hz, write: set clock frequency\n"
);
}
//==================================
static DEVICE_ATTR(clock00 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock01 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock02 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock03 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock10 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock11 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock12 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock13 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock20 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock21 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock22 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock23 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock30 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock31 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock32 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(clock33 , SYSFS_PERMISSIONS , clock_show , clock_store);
static DEVICE_ATTR(help, SYSFS_PERMISSIONS & SYSFS_READONLY, clock_help_show, NULL);
static struct attribute *root_dev_attrs[] = {
&dev_attr_clock00.attr,
&dev_attr_clock01.attr,
&dev_attr_clock02.attr,
&dev_attr_clock03.attr,
&dev_attr_clock10.attr,
&dev_attr_clock11.attr,
&dev_attr_clock12.attr,
&dev_attr_clock13.attr,
&dev_attr_clock20.attr,
&dev_attr_clock21.attr,
&dev_attr_clock22.attr,
&dev_attr_clock23.attr,
&dev_attr_clock30.attr,
&dev_attr_clock31.attr,
&dev_attr_clock32.attr,
&dev_attr_clock33.attr,
&dev_attr_help.attr,
NULL
};
static const struct attribute_group dev_attr_root_group = {
.attrs = root_dev_attrs,
.name = NULL,
};
static int elphel393_clock10359_sysfs_register(struct platform_device *pdev)
{
int retval=0;
struct device *dev = &pdev->dev;
if (&dev->kobj) {
if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval;
dev_dbg(dev,"sysfs_create_group(dev_attr_root_group) done \n");
}
return retval;
}
// =======================================
static void elphel393_clock10359_init_of(struct platform_device *pdev)
{
pr_info("elphel393_clock10359_init_of()\n");
}
static int elphel393_clock10359_probe(struct platform_device *pdev)
{
sdev =&pdev->dev;
dev_dbg(&pdev->dev,"Probing elphel_clock10359\n");
elphel393_clock10359_sysfs_register(pdev);
dev_dbg(&pdev->dev,"elphel393_clock10359_sysfs_register() done\n");
elphel393_clock10359_init_of(pdev);
dev_dbg(&pdev->dev,"done probing elphel393_clock10359_probe\n");
return 0;
}
static int elphel393_clock10359_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev,"Removing elphel393_clock10359");
return 0;
}
static struct of_device_id elphel393_clock10359_of_match[] = {
{ .compatible = "elphel,elphel393_clock10359-1.00", },
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, elphel393_clock10359_of_match);
static struct platform_driver elphel393_clock10359 = {
.probe = elphel393_clock10359_probe,
.remove = elphel393_clock10359_remove,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = elphel393_clock10359_of_match,
.pm = NULL, /* power management */
},
};
module_platform_driver(elphel393_clock10359);
MODULE_AUTHOR("Andrey Filippov ");
MODULE_DESCRIPTION("Elphel 10393 10359");
MODULE_LICENSE("GPL");
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/clock10359.h 0000664 0000000 0000000 00000002116 12766141417 0026231 0 ustar 00root root 0000000 0000000 /***************************************************************************//**
* @file clock10359.h
* @brief Control of the CY22393 clock on the 10359 multiplexer connected
* to the sensor port
* Copyright 2002-2016 (C) Elphel, Inc.
* @par License
* 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 .
*******************************************************************************/
int x393_setClockFreq(int sensor_port, int nclock, int freq);
int x393_getClockFreq(int sensor_port, int nclock);
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/command_sequencer.c 0000664 0000000 0000000 00000012022 12766141417 0030214 0 ustar 00root root 0000000 0000000 /***************************************************************************//**
* @file command_sequencer.c
* @brief Interface to FPGA-based command sequencer sequencer
* @copyright Copyright 2016 (C) Elphel, Inc.
* @par License
* 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 .
*******************************************************************************/
#include
#include
#include // PARS_FRAMES_MASK
#include "x393.h"
static DEFINE_SPINLOCK(lock);
/** Write command to the 16-frame sequencer, relative to the current frame */
int write_seq_rel (int chn, ///< sensor port
int frame, ///< relative frame number modulo 16 (0 - ASAP)
u32 addr, ///< command (register) address in bytes (2 LSBs are will be discarded)
u32 data) ///< command data.
///< @return 0 on success, negative error if table shadow is not initialized to data
{
if (unlikely(frame < 0)) frame = 0;
else if (unlikely(frame >= PARS_FRAMES_MASK)){
return -EINVAL; // too far in the future
}
spin_lock(&lock);
x393_cmdframeseq_rel(addr>>2, chn, frame);
x393_cmdframeseq_rel(data, chn, frame);
spin_unlock(&lock);
return 0;
}
EXPORT_SYMBOL_GPL(write_seq_rel);
/* Write command to the 16-frame sequencer, absolute frame */
int write_seq_abs (int chn, ///< sensor port
int frame, ///< absolute frame number modulo 16
u32 addr, ///< command (register) address in bytes (2 LSBs are will be discarded)
u32 * data) ///< data array. Length written is defined by the pre-configured table entry.
///< MSB (data is sent MSB first) of the first entry is index in the table.
///< @return 0 on success, negative error if table shadow is not initialized to data
{
frame &= PARS_FRAMES_MASK;
spin_lock(&lock);
x393_cmdframeseq_rel(addr>>2, chn, frame);
x393_cmdframeseq_rel(data, chn, frame);
spin_unlock(&lock);
return 0;
}
EXPORT_SYMBOL_GPL(write_seq_abs);
// TODO: Add other sequencer control here
/*
// Command sequencer control
// Controller is programmed through 32 locations. Each register but the control requires two writes:
// First write - register address (AXI_WR_ADDR_BITS bits), second - register data (32 bits)
// Writing to the contol register (0x1f) resets the first/second counter so the next write will be "first"
// 0x0..0xf write directly to the frame number [3:0] modulo 16, except if you write to the frame
// "just missed" - in that case data will go to the current frame.
// 0x10 - write seq commands to be sent ASAP
// 0x11 - write seq commands to be sent after the next frame starts
//
// 0x1e - write seq commands to be sent after the next 14 frame start pulses
// 0x1f - control register:
// [14] - reset all FIFO (takes 32 clock pulses), also - stops seq until run command
// [13:12] - 3 - run seq, 2 - stop seq , 1,0 - no change to run state
// [1:0] - 0: NOP, 1: clear IRQ, 2 - Clear IE, 3: set IE
void x393_cmdframeseq_ctrl (x393_cmdframeseq_mode_t d, int sens_chn){writel(d.d32, mmio_ptr + (0x1e7c + 0x80 * sens_chn));} // CMDFRAMESEQ control register
void x393_cmdframeseq_abs (u32 d, int sens_chn, int offset){writel(d, mmio_ptr + (0x1e00 + 0x80 * sens_chn + 0x4 * offset));} // CMDFRAMESEQ absolute frame address/command
void x393_cmdframeseq_rel (u32 d, int sens_chn, int offset){writel(d, mmio_ptr + (0x1e40 + 0x80 * sens_chn + 0x4 * offset));} // CMDFRAMESEQ relative frame address/command
// Command sequencer multiplexer, provides current frame number for each sensor channel and interrupt status/interrupt masks for them.
// Interrupts and interrupt masks are controlled through channel CMDFRAMESEQ module
void set_x393_cmdseqmux_status_ctrl (x393_status_ctrl_t d){writel(d.d32, mmio_ptr + 0x1c08);} // CMDSEQMUX status control mode (status provides current frame numbers)
x393_status_ctrl_t get_x393_cmdseqmux_status_ctrl (void) { x393_status_ctrl_t d; d.d32 = readl(mmio_ptr + 0x1c08); return d; }
x393_cmdseqmux_status_t x393_cmdseqmux_status (void) { x393_cmdseqmux_status_t d; d.d32 = readl(mmio_ptr + 0x20e0); return d; } // CMDSEQMUX status data (frame numbers and interrupts
*/
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/command_sequencer.h 0000664 0000000 0000000 00000002313 12766141417 0030223 0 ustar 00root root 0000000 0000000 /***************************************************************************//**
* @file command_sequencer.h
* @brief Interface to FPGA-based command sequencer sequencer
* @copyright Copyright 2016 (C) Elphel, Inc.
* @par License
* 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 .
*******************************************************************************/
/* Write command to the 16-frame sequencer, relative to the current frame */
int write_seq_rel (int chn, int frame, u32 addr, u32 data);
/* Write command to the 16-frame sequencer, absolute frame */
int write_seq_abs (int chn, int frame, u32 addr, u32 * data);
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/cxi2c.c 0000664 0000000 0000000 00000144442 12766141417 0025550 0 ustar 00root root 0000000 0000000 /** @file cxi2c.c
*
* @brief Pre-393 I2c driver for FPGA communicating to sensors, software implementation
* Porting to get GPS communication, sesnors in NC393 are handled by sensor_i2c.c driver
*
* @copyright Copyright (C) 2002-2016 Elphel, Inc
*
* @par License
* 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 .
*/
/*********************************************************************************
*! -----------------------------------------------------------------------------**
*! $Log: cxi2c.c,v $
*! Revision 1.7 2012/01/16 01:44:53 elphel
*! added comments, and release of SDA after reading byte
*!
*! Revision 1.6 2011/12/22 05:37:09 elphel
*! added more known devices
*!
*! Revision 1.5 2011/08/13 00:53:10 elphel
*! defined addresses for "granddaughter" ID eeprom and 8-bit ports
*!
*! Revision 1.4 2010/08/10 21:09:23 elphel
*! improve i2c access to sensor board eeprom - turning off hardware i2c and disabling interrupts during software r/w operations
*!
*! Revision 1.3 2010/04/06 20:35:42 elphel
*! 8.0.7.5 - made the fpgaclock driver program 10359 clock in addition to the system one
*!
*! Revision 1.2 2008/12/24 13:27:24 spectr_rain
*! removed io board pin ioctl
*!
*! Revision 1.1.1.1 2008/11/27 20:04:00 elphel
*!
*!
*! Revision 1.7 2008/09/20 00:29:49 elphel
*! moved driver major/minor numbers to a single file - include/asm-cris/elphel/driver_numbers.h
*!
*! Revision 1.6 2008/09/12 00:23:58 elphel
*! removed cc353.c, cc353.h
*!
*! Revision 1.5 2008/09/05 23:20:26 elphel
*! just a snapshot
*!
*! Revision 1.4 2008/08/25 19:07:23 elphel
*! just a snapshot
*!
*! Revision 1.3 2008/07/27 04:27:49 elphel
*! next snapshot
*!
*! Revision 1.2 2008/06/19 02:17:36 elphel
*! continuing work - just a snapshot
*!
*! Revision 1.7 2008/04/11 23:16:51 elphel
*! removed unneeded local_irq_disable() after local_irq_save_flags()
*!
*! Revision 1.6 2008/03/16 01:25:15 elphel
*! increased default delays fro I2c bus 1 (EEPROM was not fast enough)
*!
*! Revision 1.5 2008/02/18 20:02:34 elphel
*! added option to specify number of bytes to be actually written from device per read command (PHP feature fix)
*!
*! Revision 1.4 2008/02/12 21:53:20 elphel
*! Modified I2c to support multiple buses, added raw access (no address registers) and per-slave protection bitmasks
*!
*! Revision 1.3 2008/02/11 04:52:18 elphel
*! Modified I2C operations, added second bus to work with the 10369 (and future) board(s)
*!
*! Revision 1.2 2007/10/16 23:18:31 elphel
*! added filtering I2C lines, r/w control of the I2C bit delays
*!
*/
/****************** INCLUDE FILES SECTION ***********************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "x393.h"
//#include "fpgactrl.h" // defines port_csp0_addr, port_csp4_addr
//#include "x3x3.h"
#include "cci2c.h"
#define D(x)
//#define D(x) printk("%s:%d:",__FILE__,__LINE__);x
//#define PASSIVE_PULLUP y // Enable to remove active pullup on SCL SDA (just use pullup resistors)
// implementing read/write/lseek for i2c - using a new major and several minors:
// for 8 and 16 registers with and w/o autoincrement.
// next minors - for less commomn i2c devices.
// address space is considered to include 1 byte of register address and 7MSBs of slave address
/****************** I2C DEFINITION SECTION *************************/
#define CLOCK_LOW_TIME 8
#define CLOCK_HIGH_TIME 8
#define START_CONDITION_HOLD_TIME 8
#define STOP_CONDITION_HOLD_TIME 8
#define ENABLE_OUTPUT 0x01
#define ENABLE_INPUT 0x00
#define I2C_CLOCK_HIGH 1
#define I2C_CLOCK_LOW 0
#define I2C_DATA_HIGH 1
#define I2C_DATA_LOW 0
/* use the kernels delay routine */
//#define i2c_delay(usecs) udelay(usecs)
//#define i2c_delay(usecs) {}
//Just removing delays is absolutely wrong (does not work with other sensors) what can be done - replacing constants with some run-time value(s) and set them individually for different sensors. For now - I'll use the standard values again.
#define i2c_delay(usecs) udelay(usecs)
#define I2C_DELAY_SCALE 1
//#define X3X3_I2C_MAXMINOR 4 //
//#define X3X3_I2C_CHANNELS 2 // number of i2c channels
//volatile unsigned long ccam_cr_shadow=0;
//extern volatile unsigned long ccam_cr_shadow;
// currently delays are approximately 0.4usec+0.2usec*n and 0x01010000 works (~8usec/byte)
// with deafult 20MHz pixel clock - 0x01010202, after clock is set to 48MHz 0x01010000 is enough
//TODO: Make delays independent for 2 channels?
static struct i2c_timing_t bitdelays[X3X3_I2C_CHANNELS];
#ifdef NC353
static int i2c_hardware_on=0; // shadow register fro FPFA I@C controller
#endif
//void i2c_disable(int n);
//void i2c_dir_out(int n);
//void i2c_dir_in (int n);
void i2c_scl_0 (int n);
void i2c_scl_1 (int n);
//void i2c_sda (int n, int d); /* d is checked against zero only, it does not need to be "1" */
int i2c_getbit (int n);
int i2c_getscl(int n); /* just for i2c testing */
//int i2c_diagnose(int n);
int i2c_start(int n);
int i2c_restart(int n);
int i2c_stop(int n);
int i2c_outbyte(int n, unsigned char d);
unsigned char i2c_inbyte(int n, int more);
//void i2c_sendack(int n, int ackn); // ackn= 1 - send ackn (low level), 0 - no ackn.
// the following functions should be called with IRQ off. Maybe will replace with FPGA register read
#ifdef NC353
void i2c_reset_wait(void) {X3X3_I2C_RESET_WAIT;i2c_hardware_on=0;}
void i2c_stop_wait(void) {X3X3_I2C_STOP_WAIT; i2c_hardware_on=0;}
void i2c_run(void) {X3X3_I2C_RUN; i2c_hardware_on=1;}
int i2s_running(void) {return i2c_hardware_on;}
#endif
void i2c_ldelay(int dly){
while (dly--) {
__asm__ __volatile__("");
}
}
// Low level i2c pin functions
int i2c_delays (unsigned long delays) { // will only change bus 0 this way
unsigned long * bitdelays_long=(unsigned long *) bitdelays;
if (delays !=0) bitdelays_long[0]=delays;
return (int) bitdelays_long[0];
}
/* I2C functions */
// redesign of i2c functions optimized for faster communications,
// including FPGA <->FPGA transfers.
// To make communications faster there are the following steps:
// 1 - SCL is always driven by master - no waiting for the slave
// 2 - SDA is actively driven high by the master (and expecting the same from the slave)
// SDA is driven high during SCL=0 when other party is known not to drive it low.
// 3 - separate bus turn-over delays are added in addition to (shorter) bit delays
// 4 - 4 delays are programmed as a single 32-bit word that can be changed to communicate with
// different slaves (i.e. FPGA and image sensor)
/*
#define x3x3_DELAY(x) {int iiii; for (iiii=0; iiii < (x); iiii++) X3X3_AFTERWRITE ; }
I2C_DELAY_SCALE
#define X313_WA_IOPINS 0x70 // bits [13:12] selecte of the source of the control word:
SCL=EXT[0]
SDA=EXT[1]
/// software control of SDA0, SCL0 lines (when hardware i2c is off)
#define X3X3_I2C_SCL_0_BITS 0x10000
#define X3X3_I2C_SCL_1_BITS 0x20000
#define X3X3_I2C_SCL_Z_BITS 0x30000
#define X3X3_I2C_SDA_0_BITS 0x40000
#define X3X3_I2C_SDA_1_BITS 0x80000
#define X3X3_I2C_SDA_Z_BITS 0xc0000
X313_I2C_CMD
*/
#define SCL1_0 0x1
#define SCL1_OFF 0x3
#define SDA1_0 0x4
#define SDA1_OFF 0xc
#ifdef PASSIVE_PULLUP
#define SCL1_1 0x3 // same as off
#define SDA1_1 0xc // same as off
#else
#define SCL1_1 0x2
#define SDA1_1 0x8
#endif
#define X313_PIOR__SCL1 0
#define X313_PIOR__SDA1 1
#ifdef NC353
#define X313_PIOR(x) ((port_csp0_addr[X313__RA__IOPINS] >> X313_PIOR__##x ) & 1)
#else
#define X313_PIOR(x) ((x393_gpio_status().d32 >> X313_PIOR__##x ) & 1)
#endif
/*
struct i2c_timing_t {
0 unsigned char scl_high; //0x02, //! SCL high:
1 unsigned char scl_low; //0x02, //! SCL low:
2 unsigned char slave2master; //0x01, //! slave -> master
3 unsigned char master2slave; //0x01, //! master -> slave
4 unsigned char filter_sda; //0x07, //! filter SDA read data by testing multiple times - currently just zero/non zero
5 unsigned char filter_scl; //0x07};//! filter SCL read data by testing multiple times - currently just zero/non zero
}
*/
// filtering noise/interference by repeating measurement 7 times and using the majority
int i2c_getbit(int n) {
#ifdef NC353
if (bitdelays[n].filter_sda)
return n? (((X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1))) >> 2) :
(((X313_SR(SDA0)) + (X313_SR(SDA0)) + (X313_SR(SDA0)) + (X313_SR(SDA0)) + (X313_SR(SDA0)) + (X313_SR(SDA0)) + (X313_SR(SDA0))) >> 2) ;
else return n? X313_PIOR(SDA1) : X313_SR(SDA0);
#else
if (bitdelays[n].filter_sda)
return (((X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1)) + (X313_PIOR(SDA1))) >> 2);
else return X313_PIOR(SDA1);
#endif
}
int i2c_getscl(int n) {
#ifdef NC353
if (bitdelays[n].filter_scl)
return n? (((X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1))) >> 2) :
(((X313_SR(SCL0)) + (X313_SR(SCL0)) + (X313_SR(SCL0)) + (X313_SR(SCL0)) + (X313_SR(SCL0)) + (X313_SR(SCL0)) + (X313_SR(SCL0))) >> 2) ;
else return n? X313_PIOR(SCL1) : X313_SR(SCL0);
#else
if (bitdelays[n].filter_scl)
return (((X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1)) + (X313_PIOR(SCL1))) >> 2) ;
else return X313_PIOR(SCL1);
#endif
}
void i2c_scl_0(int n) {
#ifdef NC353
if (n) port_csp0_addr[X313_WA_IOPINS]=SCL1_0; // EXT[0] enabled, 0
else port_csp0_addr[X313_I2C_CMD]=X3X3_I2C_SCL_0_BITS; // SCL= 0
#else
x393_gpio_set_pins_t gpio_set_pins = {.d32 = SCL1_0};
x393_gpio_set_pins (gpio_set_pins);
#endif
}
void i2c_scl_1(int n) {
#ifdef NC353
if (n) port_csp0_addr[X313_WA_IOPINS]=SCL1_1; // modified by ifdef PASSIVE_PULLUP
else port_csp0_addr[X313_I2C_CMD]=X3X3_I2C_SCL_1_BITS;; // SCL=1
#else
x393_gpio_set_pins_t gpio_set_pins = {.d32 = SCL1_1};
x393_gpio_set_pins (gpio_set_pins);
#endif
}
void i2c_sda_weak (int n, int d) { // will also force sda enable
#ifdef NC353
if (n) {
if (d) port_csp0_addr[X313_WA_IOPINS]=SDA1_OFF; // turn off SDA
else port_csp0_addr[X313_WA_IOPINS]=SDA1_0; // turn on SDA ==0
} else {
if (d) port_csp0_addr[X313_I2C_CMD]=X3X3_I2C_SDA_Z_BITS; // turn off SDA ==1 (just in case)
else port_csp0_addr[X313_I2C_CMD]=X3X3_I2C_SDA_0_BITS; // turn on SDA ==0
}
#else
const x393_gpio_set_pins_t gpio_sda1_off = {.d32 = SDA1_OFF};
const x393_gpio_set_pins_t gpio_sda1_0 = {.d32 = SDA1_0};
if (d) x393_gpio_set_pins(gpio_sda1_off); // turn off SDA
else x393_gpio_set_pins(gpio_sda1_0); // turn on SDA ==0
#endif
}
void i2c_sda_strong (int n, int d) { // will also force sda enable
#ifdef NC353
if (n) {
if (d) port_csp0_addr[X313_WA_IOPINS]=SDA1_1; // modified by ifdef PASSIVE_PULLUP
else port_csp0_addr[X313_WA_IOPINS]=SDA1_0; // turn on SDA ==0
} else {
if (d) port_csp0_addr[X313_I2C_CMD]=X3X3_I2C_SDA_1_BITS; // turn off SDA ==1 (just in case)
else port_csp0_addr[X313_I2C_CMD]=X3X3_I2C_SDA_0_BITS; // turn on SDA ==0
}
#else
const x393_gpio_set_pins_t gpio_sda1_0 = {.d32 = SDA1_0};
const x393_gpio_set_pins_t gpio_sda1_1 = {.d32 = SDA1_1};
if (d) x393_gpio_set_pins(gpio_sda1_1); // turn off SDA ==1
else x393_gpio_set_pins(gpio_sda1_0); // turn on SDA ==0
#endif
}
// generate i2c start condition and test bus
int i2c_start(int n) {
int i;
unsigned long flags;
local_irq_save(flags);
//local_irq_disable();
D(printk("i2c_start: bus=%x\r\n", n));
// both SCL and SDA are supposed to be high - no waiting is needed
// set SCL=1, release SDA, wait SCL high time and verify.
i2c_scl_1(n);
i2c_sda_weak (n, 1);
i2c_ldelay(bitdelays[n].scl_high*I2C_DELAY_SCALE);
// verify SDA is high
if (!i2c_getbit(n)) { // try recovering by clocking SCL (8x slower)
for (i=0; i<9; i++) {
i2c_scl_0(n);
i2c_ldelay(bitdelays[n].scl_low * I2C_DELAY_SCALE*8);
i2c_scl_1(n);
i2c_ldelay(bitdelays[n].scl_high * I2C_DELAY_SCALE*8);
if (!i2c_getbit(n)) break;
}
if (!i2c_getbit(n)) {
local_irq_restore(flags);
return ERR_I2C_SDA_ST0; // error
}
}
i2c_sda_weak (n, 0);
i2c_ldelay(bitdelays[n].scl_low*I2C_DELAY_SCALE); // technically it is SCL==1
i2c_scl_0(n);
local_irq_restore(flags);
return 0;
}
// generate i2c stop condition
int i2c_stop(int n) {
unsigned long flags;
local_irq_save(flags);
//local_irq_disable();
D(printk("i2c_stop: bus=%x\r\n", n));
// SCL=0, SDA - unknown. Wait for bus turnover
i2c_sda_weak (n, 0);
i2c_ldelay(bitdelays[n].slave2master*I2C_DELAY_SCALE); // maybe not needed as it is 1->0 transition
i2c_ldelay(bitdelays[n].scl_low*I2C_DELAY_SCALE); // regular SCL=0 delay
i2c_scl_1(n);
i2c_ldelay(bitdelays[n].scl_high*I2C_DELAY_SCALE); // regular SCL=1 delay
i2c_sda_strong (n, 1);
i2c_ldelay(bitdelays[n].scl_high*I2C_DELAY_SCALE); // regular SCL=1 delay
i2c_sda_weak (n, 1);
local_irq_restore(flags);
return 0;
}
// generate i2c repeated start condition
int i2c_restart(int n) {
unsigned long flags;
local_irq_save(flags);
//local_irq_disable();
D(printk("i2c_restart: bus=%x\r\n", n));
// SCL=0, SDA - unknown. Wait for bus turnover
i2c_sda_weak (n, 1);
i2c_ldelay(bitdelays[n].slave2master*I2C_DELAY_SCALE); // time for slave to release the bus
i2c_sda_strong (n, 1);
i2c_ldelay(bitdelays[n].scl_low*I2C_DELAY_SCALE); // regular SCL=0 delay
i2c_scl_1(n);
i2c_sda_weak (n, 1);
i2c_ldelay(bitdelays[n].scl_high*I2C_DELAY_SCALE); // regular SCL=1 delay
i2c_sda_weak (n, 0);
i2c_ldelay(bitdelays[n].scl_low*I2C_DELAY_SCALE); // technically it is SCL==1
i2c_scl_0(n);
local_irq_restore(flags);
return 0;
}
// write a byte to the i2c interface , return acknowledge (active high, inverted from SDA line)
int i2c_outbyte(int n, unsigned char d) {
int i;
unsigned char x=d; // make it non-destructive
unsigned long flags;
local_irq_save(flags);
//local_irq_disable();
D(printk("i2c_outbyte: bus=%x byte=%x\r\n", n, x));
i2c_sda_weak (n, 1);
i2c_ldelay(bitdelays[n].slave2master * I2C_DELAY_SCALE); // time for slave to release the bus
for (i = 0; i < 8; i++) { // assumed to be with SCL=0;
i2c_sda_strong (n,(x & 0x80)); // checks only on non-zero, so no need to '>>'
i2c_ldelay(bitdelays[n].scl_low * I2C_DELAY_SCALE); // SCL=0 delay
i2c_scl_1(n);
i2c_sda_weak (n,(x & 0x80)); // checks only on non-zero, so no need to '>>'
i2c_ldelay(bitdelays[n].scl_high * I2C_DELAY_SCALE); // regular SCL=1 delay
i2c_scl_0(n);
x <<= 1;
}
// prepare to read ACKN
i2c_sda_weak (n, 1);
// TODO: Need to wait here long (maybe only if last bit was 0? - (x & 0x100)!=0 )
i2c_ldelay(bitdelays[n].master2slave * I2C_DELAY_SCALE); // master -> slave delay
i2c_ldelay(bitdelays[n].scl_low * I2C_DELAY_SCALE); // regular SCL=0 delay
i2c_scl_1(n);
i2c_ldelay(bitdelays[n].scl_high * I2C_DELAY_SCALE); // regular SCL=1 delay
i= (1-i2c_getbit(n));
i2c_scl_0(n);
D(printk("i2c_outbyte: ACK=%x\r\n", i));
local_irq_restore(flags);
return i;
}
// read a byte from the i2c interface, send "more" bit (SDA=0 - more, SDA=1 - that's enough)
unsigned char i2c_inbyte(int n, int more) { // assumed SCL=0, SDA=X
unsigned char aBitByte = 0;
int i;
unsigned long flags;
local_irq_save(flags);
//local_irq_disable();
D(printk("i2c_inbyte: bus=%x\r\n", n));
// prepare to read ACKN
i2c_sda_weak (n, 1);
i2c_ldelay(bitdelays[n].master2slave * I2C_DELAY_SCALE); // master -> slave delay
// read bits
for (i = 0; i < 8; i++) {
i2c_ldelay(bitdelays[n].scl_low * I2C_DELAY_SCALE); // regular SCL=0 delay
i2c_scl_1(n);
i2c_ldelay(bitdelays[n].scl_high * I2C_DELAY_SCALE); // regular SCL=1 delay
aBitByte = (aBitByte << 1) | i2c_getbit(n);
i2c_scl_0(n);
}
// send "more"
i2c_sda_weak (n, more ? 0 : 1);
i2c_ldelay(bitdelays[n].slave2master * I2C_DELAY_SCALE); // time for slave to release the bus
i2c_sda_strong (n, more ? 0 : 1);
i2c_ldelay(bitdelays[n].scl_low * I2C_DELAY_SCALE); // SCL=0 delay
i2c_scl_1(n);
i2c_sda_weak (n, more ? 0 : 1);
i2c_ldelay(bitdelays[n].scl_high * I2C_DELAY_SCALE); // regular SCL=1 delay
i2c_scl_0(n);
//TODO: (test next is OK - 2012-01-15)
i2c_sda_weak (n, 1); // release SDA byte
D(printk("i2c_inbyte: data=%x\r\n", aBitByte));
local_irq_restore(flags);
return aBitByte; // returns with SCL=0, SDA - OFF
}
/*#---------------------------------------------------------------------------
*#
*# FUNCTION NAME: i2c_writeData
*#
*# DESCRIPTION : Writes a sequence of bytes to an I2C device
*# removed retries, will add test-ready later as a separate function
*# removed all "dummy stuff" - I never needed that
*# added "stop" argument - don't send stop before read
*#--------------------------------------------------------------------------*/
int i2c_writeData(int n, unsigned char theSlave, unsigned char *theData, int size, int stop) {
int i,error=0;
D(printk("i2c_writeData: bus=%x theSlave=%x data=%x %x size=%x\r\n", n, theSlave, theData[0], theData[1], size));
// generate start condition, test bus
if ((error=i2c_start(n))) return error;
// send slave address, wait for ack
if(!i2c_outbyte(n,theSlave)) {
i2c_stop(n);
return ERR_I2C_BSY; // device does not exist or not ready
}
// OK, now send theData verifying ACK goes after each byte
for (i=0;iprivate_data)[0]= %x\n\r",((int *)file->private_data)[0]));
// D(printk("i2c_ioctl: ((int )file->private_data)= %x\n\r",(int)file->private_data));
if(_IOC_TYPE(cmd) != CMOSCAM_IOCTYPE) {
return -EINVAL;
}
// #define I2C_DELAYS 0x0 /* read/write bit deleys for I2C */
//int i2c_delays (unsigned long delays) {
switch (_IOC_NR(cmd)) {
case I2C_DELAYS:
return i2c_delays (arg);
case I2C_WRITEREG:
/* write to an i2c slave */
D(printk("i2cw bus=%d, slave=%d, reg=%d, value=%d\n",
(int) I2C_ARGBUS(arg),
(int) I2C_ARGSLAVE(arg),
(int) I2C_ARGREG(arg),
(int) I2C_ARGVALUE(arg)));
data[0]=I2C_ARGREG(arg);
data[1]=I2C_ARGVALUE(arg);
return -i2c_writeData(I2C_ARGBUS(arg), I2C_ARGSLAVE(arg) & 0xfe, &data[0], 2, 1); // send stop
case I2C_READREG:
/* read from an i2c slave */
D(printk("i2cr bus=%d, slave=%d, reg=%d ",
(int) I2C_ARGBUS(arg),
(int) I2C_ARGSLAVE(arg),
(int) I2C_ARGREG(arg)));
data[0]=I2C_ARGREG(arg);
error=i2c_writeData(I2C_ARGBUS(arg), I2C_ARGSLAVE(arg) & 0xfe, &data[0], 1, 0); // no stop
if (error) return -error;
error=i2c_readData(I2C_ARGBUS(arg), I2C_ARGSLAVE(arg) | 0x01, &data[1], 1, 0); // will start with restart, not start
if (error) return -error;
D(printk("returned %d\n", data[1]));
return data[1];
case I2C_16_WRITEREG:
/* write to an i2c slave */
D(printk("i2c16w slave=%d, reg=%d, value=%d\n",
(int) I2C_16_ARGSLAVE(arg),
(int) I2C_16_ARGREG(arg),
(int) I2C_16_ARGVALUE(arg)));
data[0]=I2C_16_ARGREG(arg);
data[1]=I2C_16_ARGVALUE_H(arg);
data[2]=I2C_16_ARGVALUE_L(arg);
return -i2c_writeData(0, I2C_16_ARGSLAVE(arg) & 0xfe, &data[0], 3, 1); // send stop
case I2C_16_READREG:
/* read from an i2c slave */
D(printk("i2c16r slave=%d, reg=%d ",
(int) I2C_16_ARGSLAVE(arg),
(int) I2C_16_ARGREG(arg)));
data[0]=I2C_16_ARGREG(arg);
error=i2c_writeData(0, I2C_16_ARGSLAVE(arg) & 0xfe, &data[0], 1, 0); //no stop
if (error) return -error;
error=i2c_readData(0, I2C_16_ARGSLAVE(arg) | 0x01, &data[1], 2, 0);
if (error) return -error;
D(printk("returned %d\n", (data[1]<<8)+data[2]));
return (data[1]<<8)+data[2];
default:
return -EINVAL;
}
return 0;
}
/*
* I2C as character devices
*
*/
//#define X3X3_I2C 134
// minors (add more later - maybe different minors for different speed - set speed when opening)
//#define DEV393_MINOR(DEV393_I2C_CTRL) 0 // control/reset i2c
//#define DEV393_MINOR(DEV393_I2C_8_AINC) 1 // 8bit registers, autoincement while read/write
//#define DEV393_MINOR(DEV393_I2C_16_AINC) 2 // 16bit registers, autoincement while read/write
#define X3X3_I2C_DRIVER_NAME "Elphel (R) model 353 i2c character device driver"
//#define X3X3_I2C_CHANNELS 2
//#define X3X3_I2C_MAXMINOR 4 //
#define I2CBUFSIZE 8196
//static unsigned char i2cbuf[256*256+1]; //actually usually much less
static unsigned char i2c_enable[X3X3_I2C_CHANNELS*128]; //128 devices in a bus
//static unsigned char i2cbuf_all[( X3X3_I2C_CHANNELS + 1 ) * I2CBUFSIZE];
static unsigned char i2cbuf_all[ X3X3_I2C_CHANNELS + 1 * I2CBUFSIZE]; //! no buffers for control files
static loff_t sizes[X3X3_I2C_MAXMINOR + 1];
static int burst_sizes[X3X3_I2C_MAXMINOR + 1]; //!fix for PHP read (always 8192)
static loff_t inuse[X3X3_I2C_CHANNELS];
//static int thisminor =0;
//static int thissize =0;
static int xi2c_open (struct inode *inode, struct file *filp);
static int xi2c_release(struct inode *inode, struct file *filp);
static loff_t xi2c_lseek (struct file * file, loff_t offset, int orig);
static ssize_t xi2c_write (struct file * file, const char * buf, size_t count, loff_t *off);
static ssize_t xi2c_read (struct file * file, char * buf, size_t count, loff_t *off);
static int __init xi2c_init(void);
static struct file_operations xi2c_fops = {
owner: THIS_MODULE,
open: xi2c_open,
release: xi2c_release,
read: xi2c_read,
write: xi2c_write,
llseek: xi2c_lseek
};
//!++++++++++++++++++++++++++++++++++++ open() ++++++++++++++++++++++++++++++++++++++++++++++++++++++
int xi2c_open(struct inode *inode, struct file *filp) {
int p = MINOR(inode->i_rdev);
int bus=-1;
int * pd= (int *) &(filp->private_data);
switch (p) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C_16_AINC) :
bus=0;
break;
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
bus=1;
break;
case DEV393_MINOR(DEV393_I2C_ENABLE):
case DEV393_MINOR(DEV393_I2C_CTRL) :
bus=-1;
break;
}
D(printk("xi2c_open, minor=%d\n",p));
if ((bus>=0) && (inuse[bus] !=0)) return -EACCES;
D(printk("xi2c_open, minor=%d\n",p));
inode->i_size=sizes[p];
if (bus>=0) inuse[bus] =1;
switch ( p ) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
inode->i_size=128*256;
burst_sizes[p]=1; //!fix for PHP read (always 8192) -> 1 byte/read
break;
case DEV393_MINOR(DEV393_I2C_16_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
inode->i_size=256*256;
burst_sizes[p]=2; //!fix for PHP read (always 8192) -> 2 bytes/read
break;
case DEV393_MINOR(DEV393_I2C_CTRL) :
inode->i_size=sizeof(bitdelays);
break;
case DEV393_MINOR(DEV393_I2C_ENABLE):
inode->i_size=sizeof(i2c_enable);
break;
default:return -EINVAL;
}
// thisminor=p;
// thissize=inode->i_size;
// filp->private_data = &thisminor;
// filp->private_data=p; // just a minor number
pd[0]=p; // just a minor number
return 0;
}
//!++++++++++++++++++++++++++++++++++++ release() ++++++++++++++++++++++++++++++++++++++++++++++++++++++
//static loff_t sizes[X3X3_I2C_MAXMINOR + 1];
//static loff_t inuse[X3X3_I2C_CHANNELS];
static int xi2c_release(struct inode *inode, struct file *filp){
int p = MINOR(inode->i_rdev);
int bus=-1;
switch (p) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C_16_AINC) :
bus=0;
break;
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
bus=1;
break;
case DEV393_MINOR(DEV393_I2C_ENABLE):
case DEV393_MINOR(DEV393_I2C_CTRL) :
bus=-1;
break;
}
D(printk("xi2c_release, minor=%d\n",p));
if (bus>=0) inuse[bus]=0;
else if (p==DEV393_MINOR(DEV393_I2C_CTRL)) for (bus=0; bus < X3X3_I2C_CHANNELS; bus++) inuse[bus]=0;
// thisminor =0;
return 0;
}
//!++++++++++++++++++++++++++++++++++++ lseek() ++++++++++++++++++++++++++++++++++++++++++++++++++++++
static loff_t xi2c_lseek(struct file * file, loff_t offset, int orig) {
/*
* orig 0: position from begning of eeprom
* orig 1: relative from current position
* orig 2: position from last address
*/
int p=(int)file->private_data;
int thissize=sizes[p];
switch (orig) {
case SEEK_SET:
file->f_pos = offset;
break;
case SEEK_CUR:
file->f_pos += offset;
break;
case SEEK_END:
// burst_sizes[p]=2; //!fix for PHP read (always 8192) -> 2 bytes/read
//!overload
if (offset<=0) {
file->f_pos = thissize + offset;
} else {
burst_sizes[p]=offset; //!Number of bytes to read in a single (read) if 8192 is passed (odd for 16-bits will be fixed during read itself
}
break;
default:
return -EINVAL;
}
// switch (((int *)file->private_data)[0]) {
switch (p) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
(file->f_pos) &= ~(0x7f); // zero out 7 MSBs
break;
case DEV393_MINOR(DEV393_I2C_16_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) : {
if ((file->f_pos) & 1) (file->f_pos)++; // increment to the next (if odd) for 16-bit accesses
break;
}
}
/* truncate position */
if (file->f_pos < 0) {
file->f_pos = 0;
return (-EOVERFLOW);
}
if (file->f_pos > thissize) {
file->f_pos = thissize;
return (-EOVERFLOW);
}
return (file->f_pos);
}
//!++++++++++++++++++++++++++++++++++++ read() ++++++++++++++++++++++++++++++++++++++++++++++++++++++
ssize_t xi2c_read(struct file * file, char * buf, size_t count, loff_t *off) {
int hardware_i2c_running;
unsigned long flags;
unsigned long p;
int error;
p = *off;
char * bbitdelays= (char*) bitdelays;
int bus=0;
unsigned char * i2cbuf=&i2cbuf_all[0]; // initialize to keep compiler happy
unsigned char * userbuf=&i2cbuf[1];
int thissize=sizes[(int)file->private_data];
int slave_adr;
int en_mask=0;
int en_bits=0;
// switch (((int *)file->private_data)[0]) {
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C_16_AINC) :
bus=0;
i2cbuf = &i2cbuf_all[0];
break;
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
bus=1;
i2cbuf = &i2cbuf_all[1*I2CBUFSIZE];
break;
// case DEV393_MINOR(DEV393_I2C_CTRL) :
// i2cbuf = &i2cbuf_all[X3X3_I2C_CHANNELS*I2CBUFSIZE];
}
userbuf=&i2cbuf[1];
// switch (((int *)file->private_data)[0]) {
slave_adr=(p >> 7) & 0xfe;
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
if (count == 8192) count=burst_sizes[(int)file->private_data]; //! PHP "feature" fix
break;
case DEV393_MINOR(DEV393_I2C_16_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
p&=0xfffffffe;
if (count == 8192) count=burst_sizes[(int)file->private_data]; //! PHP "feature" fix
if (count & 1) count++;
slave_adr=(p >> 8) & 0xfe;
break;
}
D(printk("xi2c_read (bus=%d) from 0x%x, count=%d\n", bus, (int) *off, (int) count));
//! Verify if this slave is enabled for the I2C operation requested
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
en_mask=(1 << X3X3_I2C_ENABLE_RD) | (1 <private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C_16_AINC) :
en_bits=i2c_enable[ slave_adr>>1 ];
break;
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
en_bits=i2c_enable[(slave_adr>>1) + 128];
break;
}
if ((en_bits & en_mask) ^ en_mask) {
printk("tried disabled xi2c_read (bus=%d, slave=0x%x)\n", bus, slave_adr);
D(printk("en_bits=0x%x, en_mask=0x%x (minor=%d)\n", (int) en_bits, (int) en_mask, (int)file->private_data));
return -ENXIO;
}
if (p >= thissize) return -EINVAL; // bigger than
if( (p + count) > thissize) { // truncate count
count = thissize - p;
}
if( count > (I2CBUFSIZE-1)) { // should not happen i2cbuf is larger than maximal thissize
count = I2CBUFSIZE-1;
}
if (count==0) return 0;
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
i2cbuf[0]=p & 0xff;
error=i2c_readData (bus, slave_adr | 0x01, &i2cbuf[1], count, 1); // will start with start, not restart
if (error) return -EINVAL;
break;
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
i2cbuf[0]=p & 0xff;
if (bus==0) {
#ifdef NC353
local_irq_save(flags); /// IRQ Off
hardware_i2c_running=i2s_running();
if (hardware_i2c_running) i2c_stop_wait();
error= i2c_writeData(bus, slave_adr, &i2cbuf[0], 1, 0); // no stop
if (error==0) error=i2c_readData (bus, slave_adr | 0x01, &i2cbuf[1], count,0); // will start with restart, not start
if (hardware_i2c_running) i2c_run(); /// turn hardware i2c back on
local_irq_restore(flags); /// IRQ restore
#endif
} else {
error= i2c_writeData(bus, slave_adr, &i2cbuf[0], 1, 0); // no stop
if (error==0) error=i2c_readData (bus, slave_adr | 0x01, &i2cbuf[1], count,0); // will start with restart, not start
}
if (error) return -EINVAL;
break;
case DEV393_MINOR(DEV393_I2C_16_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
i2cbuf[0]=(p>>1) & 0xff;
if (bus==0) {
#ifdef NC353
local_irq_save(flags); /// IRQ Off
hardware_i2c_running=i2s_running();
if (hardware_i2c_running) i2c_stop_wait();
error= i2c_writeData(bus, slave_adr, &i2cbuf[0], 1, 0); // no stop
if (error==0) error=i2c_readData (bus, slave_adr | 0x01, &i2cbuf[1], count,0); // will start with restart, not start
if (hardware_i2c_running) i2c_run(); /// turn hardware i2c back on
local_irq_restore(flags); /// IRQ restore
#endif
} else {
error= i2c_writeData(bus, slave_adr, &i2cbuf[0], 1, 0); // no stop
if (error==0) error=i2c_readData (bus, slave_adr | 0x01, &i2cbuf[1], count,0); // will start with restart, not start
}
if (error) return -EINVAL;
break;
case DEV393_MINOR(DEV393_I2C_CTRL) :
userbuf=&bbitdelays[*off];
// memcpy(&i2cbuf[1],&bbitdelays[*off],count);
break;
case DEV393_MINOR(DEV393_I2C_ENABLE):
userbuf=&i2c_enable[*off];
break;
default:return -EINVAL;
}
// if (copy_to_user(buf, &i2cbuf[1], count)) return -EFAULT;
if (copy_to_user(buf, userbuf, count)) return -EFAULT;
//! do not increment pointer for raw accesses
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
*off &= ~(0x7f); // zero out 7 MSBs
break;
default:
*off+=count;
}
D(printk("count= 0x%x, pos= 0x%x\n", (int) count, (int)*off));
return count;
}
//!++++++++++++++++++++++++++++++++++++ write() ++++++++++++++++++++++++++++++++++++++++++++++++++++++
static ssize_t xi2c_write(struct file * file, const char * buf, size_t count, loff_t *off) {
int hardware_i2c_running;
unsigned long flags;
unsigned long p;
int error;
p = *off;
int bus=0;
char * bbitdelays= (char*) bitdelays;
unsigned char * i2cbuf=&i2cbuf_all[0]; // initialize to keep compiler happy
unsigned char * userbuf=&i2cbuf[1];
int thissize=sizes[(int)file->private_data];
int slave_adr;
int en_mask=0;
int en_bits=0;
// switch (((int *)file->private_data)[0]) {
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C_16_AINC) :
bus=0;
i2cbuf = &i2cbuf_all[0];
break;
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
bus=1;
i2cbuf = &i2cbuf_all[1*I2CBUFSIZE];
break;
// case DEV393_MINOR(DEV393_I2C_CTRL) :
// i2cbuf = &i2cbuf_all[X3X3_I2C_CHANNELS*I2CBUFSIZE];
}
userbuf=&i2cbuf[1];
slave_adr=(p >> 7) & 0xfe;
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_16_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
p&=0xfffffffe;
if (count & 1) count++;
slave_adr=(p >> 8) & 0xfe;
break;
}
D(printk("xi2c_write (bus=%d) to 0x%x, count=%x\n", bus, (int) *off, (int) count));
//! Verify if this slave is enabled for the I2C operation requested
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
en_mask=(1 << X3X3_I2C_ENABLE_WR) | (1 <private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C_16_AINC) :
en_bits=i2c_enable[ slave_adr>>1 ];
break;
case DEV393_MINOR(DEV393_I2C1_RAW):
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
en_bits=i2c_enable[(slave_adr>>1) + 128];
break;
}
if ((en_bits & en_mask) ^ en_mask) {
printk("tried disabed xi2c_write (bus=%d, slave=0x%x)\n", bus, slave_adr);
D(printk("en_bits=0x%x, en_mask=0x%x (minor=%d)\n", (int) en_bits, (int) en_mask, (int)file->private_data));
return -ENXIO;
}
if (p >= thissize) return -EINVAL;
if( (p + count) > thissize) { // truncate count
count = thissize - p;
}
if( count > (I2CBUFSIZE-1)) { // should not happen i2cbuf is larger than maximal thissize
count = I2CBUFSIZE-1;
}
if (count==0) return 0;
//!only for control files that write directly to static arrays, no buffering
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_CTRL) :
userbuf=&bbitdelays[p];
break;
case DEV393_MINOR(DEV393_I2C_ENABLE):
userbuf=&i2c_enable[p];
break;
}
// if (copy_from_user( &i2cbuf[1], buf, count)) return -EFAULT;
if (copy_from_user( userbuf, buf, count)) return -EFAULT;
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
if (bus==0) {
#ifdef NC353
local_irq_save(flags); /// IRQ Off
hardware_i2c_running=i2s_running();
if (hardware_i2c_running) i2c_stop_wait();
error=i2c_writeData(bus, slave_adr, &i2cbuf[1],count, 1); // send stop
if (hardware_i2c_running) i2c_run(); /// turn hardware i2c back on
local_irq_restore(flags); /// IRQ restore
#endif
} else {
error=i2c_writeData(bus, slave_adr, &i2cbuf[1],count, 1); // send stop
}
if (error) return -EINVAL;
break;
case DEV393_MINOR(DEV393_I2C_8_AINC) :
case DEV393_MINOR(DEV393_I2C1_8_AINC) :
i2cbuf[0]=p & 0xff;
if (bus==0) {
#ifdef NC353
local_irq_save(flags); /// IRQ Off
hardware_i2c_running=i2s_running();
if (hardware_i2c_running) i2c_stop_wait();
error=i2c_writeData(bus, slave_adr, &i2cbuf[0],count+1, 1); // send stop
if (hardware_i2c_running) i2c_run(); /// turn hardware i2c back on
local_irq_restore(flags); /// IRQ restore
#endif
} else {
error=i2c_writeData(bus, slave_adr, &i2cbuf[0],count+1, 1); // send stop
}
if (error) return -EINVAL;
break;
case DEV393_MINOR(DEV393_I2C_16_AINC) :
case DEV393_MINOR(DEV393_I2C1_16_AINC) :
i2cbuf[0]=(p>>1) & 0xff;
if (bus==0) {
#ifdef NC353
local_irq_save(flags); /// IRQ Off
hardware_i2c_running=i2s_running();
if (hardware_i2c_running) i2c_stop_wait();
error=i2c_writeData(bus, slave_adr, &i2cbuf[0],count+1, 1); // send stop
if (hardware_i2c_running) i2c_run(); /// turn hardware i2c back on
local_irq_restore(flags); /// IRQ restore
#endif
} else {
error=i2c_writeData(bus, slave_adr, &i2cbuf[0],count+1, 1); // send stop
}
if (error) return -EINVAL;
break;
// case DEV393_MINOR(DEV393_I2C_CTRL) :
// memcpy(&bbitdelays[*off],&i2cbuf[1],count);
// break;
default:return -EINVAL;
}
// *off+=count;
//! do not increment pointer for raw accesses
switch ((int)file->private_data) {
case DEV393_MINOR(DEV393_I2C_RAW):
case DEV393_MINOR(DEV393_I2C1_RAW):
*off &= ~(0x7f); // zero out 7 MSBs
break;
default:
*off+=count;
}
D(printk("count= 0x%x, pos= 0x%x\n", (int) count, (int)*off));
return count;
}
static int __init xi2c_init(void) {
int i,res;
res = register_chrdev(DEV393_MAJOR(DEV393_I2C_CTRL), DEV393_NAME(DEV393_I2C_CTRL), &xi2c_fops);
if(res < 0) {
printk(KERN_ERR "\nxi2c_init: couldn't get a major number %d.\n",DEV393_MAJOR(DEV393_I2C_CTRL));
return res;
}
printk(X3X3_I2C_DRIVER_NAME" - %d, %d channels\n",DEV393_MAJOR(DEV393_I2C_CTRL),X3X3_I2C_CHANNELS);
// thisminor =0;
bitdelays[0].scl_high=2; //! SCL high:
bitdelays[0].scl_low=2; //! SCL low:
bitdelays[0].slave2master=1; //! slave -> master
bitdelays[0].master2slave=1; //! master -> slave
bitdelays[0].filter_sda=0x07; //! filter SDA read data by testing multiple times - currently just zero/non zero
bitdelays[0].filter_scl=0x07; //! filter SCL read data by testing multiple times - currently just zero/non zero
//! bus 1 - increased by 1 measured for EEPROM
bitdelays[1].scl_high=3; //! SCL high:
bitdelays[1].scl_low=4; //! SCL low: with 2 -
bitdelays[1].slave2master=2; //! slave -> master
bitdelays[1].master2slave=2; //! master -> slave
bitdelays[1].filter_sda=0x07; //! filter SDA read data by testing multiple times - currently just zero/non zero
bitdelays[1].filter_scl=0x07; //! filter SCL read data by testing multiple times - currently just zero/non zero
for (i=0; i> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 0 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <> 1)]= (1 << X3X3_I2C_ENABLE_RD) | ( 1 << X3X3_I2C_ENABLE_WR) | (1 <.");
MODULE_DESCRIPTION(X3X3_I2C_DRIVER_NAME);
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/debug393.h 0000664 0000000 0000000 00000006065 12766141417 0026070 0 ustar 00root root 0000000 0000000 /***************************************************************************//**
* @file debug393.h
* @brief Macros for low-overhad logging messages to a large memory buffer
* using klogger_393
* @copyright Copyright 2016 (C) Elphel, Inc.
* @par License
* 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 .
*******************************************************************************/
// Used debug bits:
// framepars.c
// 0
#define DBGB_FSFA 1 ///< setFrameParsAtomic (kthread)
#define DBGB_FASAP 2 ///< _processParsASAP (kthread, tasklet) (first N only)
#define DBGB_FSEQ 3 ///< _processParsSeq (kthread, tasklet) (first N only)
#define DBGB_FPPI 4 ///< _processPars (kthread, tasklet) (first N only)
#define DBGB_FPPT 5 ///< processPars (from tasklets) (first N only)
#define DBGB_FSFP 6 ///< setFramePar(), setFramePars() (tasklet)
#define DBGB_FSFV 7 ///< setFramePar(), setFramePars() - adiitional(verbose) (tasklet)
#define DBGB_FSCF 8 ///< schedule_pgm_func,schedule_this_pgm_func
#define DBGB_FFOP 9 ///< file operations
// pgm_functions.c
#define DBGB_PSFN 10 ///< start of each function
#define DBGB_PADD 11 ///< additional details
// x393_vidoemem.c
#define DBGB_VM 12 ///< vidoemem all debug
#define DBGB_SCRST 13 ///< vidoemem all debug
// 13
// 14
// 15
// 16
// 17
// 18
// 19
// 20
// 21
// 22
// 23
// 24
// 25
// 26
// 27
// 28
// 29
// 30
// 31
#define DBGB_SFA 1 ///< setFrameParsAtomic (kthread)
#include "klogger_393.h"
#include "framepars.h" // for aglobals
#include "sensor_common.h"// for int getHardFrameNumber(int sensor_port, int use_compressor);
#ifndef ELPHEL_DEBUG393
#define ELPHEL_DEBUG393 1
#if ELPHEL_DEBUG393
static int klog_mode = 0xff; ///< bits specify attributes to log
/// log unconditionally, for any channel
#define MDG(...) print_klog393(klog_mode, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__);
/// log only if specified bit in G_DEBUG global parameter for the specified sensor port is set
// #define MDP(bit,port,fmt,...) { if (GLOBALPARS(port,G_DEBUG) & (1 << bit)) print_klog393(klog_mode, __FILE__, __FUNCTION__, __LINE__,"%d: "fmt,port,__VA_ARGS__); }
#define MDP(bit,port,fmt,...) { if (GLOBALPARS(port,G_DEBUG) & (1 << bit)) print_klog393(klog_mode, __FILE__, __FUNCTION__, __LINE__,"%d:%d "fmt,port,getHardFrameNumber(port, 0),__VA_ARGS__); }
#else
#define MDF(x)
#define MDP(bit,port,fmt,...)
#endif
#endif
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/detect_sensors.c 0000664 0000000 0000000 00000050561 12766141417 0027562 0 ustar 00root root 0000000 0000000 /***************************************************************************//**
* @file detect_sensors.c
* @brief Determine sensor boards attached to each of the ports. Use
* Device Tree, sysfs to set sensor types per port. Add autodetection
* (using pullup/pull downs) later
* @copyright Copyright 2016 (C) Elphel, Inc.
* @par License
* 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 .
*******************************************************************************/
//#include
#include
#include
#include
#include
#include
//#include // TODO: Implement cache operations for the membridge !!!!
//#include
#include
#include
#include "x393.h"
#include
#include
#include "mt9x001.h"
#include "multi10359.h"
#include "detect_sensors.h"
#define DETECT_SENSORS_MODULE_DESCRIPTION "Detect sensor type(s) attached to each of the ports"
#define OF_PREFIX_NAME "elphel393-detect_sensors"
struct sensor_port_config_t
{
u32 mux; ///< sensor multiplexer, currently 0 (SENSOR_DETECT, SENSOR_MUX_10359 or SENSOR_NONE)
u32 sensor[MAX_SENSORS]; ///< Without mux only [0] is used, with 10359 - 0..2 are used (i2c addressing is shifted so 0 is broadcast)
};
static struct sensor_port_config_t sensorPortConfig[] = {
{.mux=SENSOR_NONE,.sensor={SENSOR_NONE,SENSOR_NONE,SENSOR_NONE,SENSOR_NONE}},
{.mux=SENSOR_NONE,.sensor={SENSOR_NONE,SENSOR_NONE,SENSOR_NONE,SENSOR_NONE}},
{.mux=SENSOR_NONE,.sensor={SENSOR_NONE,SENSOR_NONE,SENSOR_NONE,SENSOR_NONE}},
{.mux=SENSOR_NONE,.sensor={SENSOR_NONE,SENSOR_NONE,SENSOR_NONE,SENSOR_NONE}}
};
//struct sensor_port_config_t *pSensorPortConfig;
static const struct of_device_id elphel393_detect_sensors_of_match[];
static struct device *g_dev_ptr; ///< Global pointer to basic device structure. This pointer is used in debugfs output functions
struct sensor_name_t {
const char * name;
u32 code;
int type; ///< +1 - applicable to sensors, +2 - applicable to multiplexers
sens_iface_t iface;
};
//typedef enum {NONE,PARALLEL12,HISPI} sens_iface_t; ///< Sensor port interface type
const struct sensor_name_t sensor_names[] ={
{.name="detect", .type=3, .iface=NONE, .code = 0}, // to be automatically detected
{.name="none", .type=3, .iface=NONE, .code = SENSOR_NONE}, // no device attached
{.name="mux10359", .type=2, .iface=PARALLEL12, .code = SENSOR_MUX_10359}, // no device attached
{.name="zr32112", .type=1, .iface=PARALLEL12, .code = SENSOR_ZR32112}, // Zoran ZR32112
{.name="zr32212", .type=1, .iface=PARALLEL12, .code = SENSOR_ZR32212}, // Zoran ZR32212
{.name="kac1310", .type=1, .iface=PARALLEL12, .code = SENSOR_KAC1310}, // Kodak KAC1310
{.name="kac5000", .type=1, .iface=PARALLEL12, .code = SENSOR_KAC5000}, // Kodak KAC5000
{.name="mi1300", .type=1, .iface=PARALLEL12, .code = SENSOR_MI1300}, // Micron MI1300
{.name="mt9m001", .type=1, .iface=PARALLEL12, .code = SENSOR_MT9M001}, // MT9M001
{.name="mt9d001", .type=1, .iface=PARALLEL12, .code = SENSOR_MT9D001}, // MT9D001
{.name="mt9t001", .type=1, .iface=PARALLEL12, .code = SENSOR_MT9T001}, // MT9T001
{.name="mt9p006", .type=1, .iface=PARALLEL12, .code = SENSOR_MT9P006}, // MT9P006
{.name="mt9f002", .type=1, .iface=HISPI4, .code = SENSOR_MT9F002}, // MT9F002
{.name="ibis51300", .type=1, .iface=PARALLEL12, .code = SENSOR_IBIS51300}, // FillFactory IBIS51300
{.name="kai11002", .type=1, .iface=PARALLEL12, .code = SENSOR_KAI11000}, // Kodak KAI11002
{.name=NULL, .type=0, .iface=NONE, .code = 0} // end of list
};
static sens_iface_t port_iface[SENSOR_PORTS];
//#define DETECT_SENSOR 1 ///< Include sensors, May be OR-ed when looking for sensor/multiplexer code/name
//#define DETECT_MUX 2 ///< Include multiplexers, May be OR-ed when looking for sensor/multiplexer code/name
/** Get sensor/multiplexer code (SENSOR_*) by name */
int get_code_by_name(const char * name, ///< sensor name
int type) ///< valid type [DETECT_SENSOR]|[DETECT_MUX]
///< @return sensor code or -EINVAL for invalid name
{
int i;
if (name) for (i = 0; sensor_names[i].name; i++){
if ((sensor_names[i].type & type) && (!strncmp(sensor_names[i].name,name,80))){
return sensor_names[i].code;
}
}
return -EINVAL;
}
/** Get sensor/multiplexer name (SENSOR_*) by code */
const char * get_name_by_code(int code, ///< sensor code
int type) ///< valid type [DETECT_SENSOR]|[DETECT_MUX]
///< @return sensor name or NULL for invalid code
{
int i;
for (i = 0; sensor_names[i].name; i++){
if ((sensor_names[i].type & type) && (sensor_names[i].code == code)){
return sensor_names[i].name;
}
}
return NULL;
}
/** Get sensor/multiplexer interface type by code */
sens_iface_t get_iface_by_code(int code, ///< sensor code
int type) ///< valid type [DETECT_SENSOR]|[DETECT_MUX]
///< @return sensor name or NULL for invalid code
{
int i;
for (i = 0; sensor_names[i].name; i++){
if ((sensor_names[i].type & type) && (sensor_names[i].code == code)){
return sensor_names[i].iface;
}
}
return NONE;
}
/** Get sensor port multiplexer type */
int get_detected_mux_code(int port) ///< Sensor port number (0..3)
///< @return port multiplexer code (SENSOR_DETECT, SENSOR_MUX_10359 or SENSOR_NONE)
{
return sensorPortConfig[port & 3].mux;
}
/** Get sensor type */
int get_detected_sensor_code(int port, ///< Sensor port number (0..3)
int sub_chn) ///< Sensor subchannel (0..3), -1 - use first defined sub channel
///< @return sensor code (SENSOR_DETECT, SENSOR_NONE, or SENSOR_*)
{
int nchn,code;
port &= 3;
if (sub_chn >= 0)
return sensorPortConfig[port].sensor[sub_chn & 3];
// Negative sensor - find first defined
nchn = (get_detected_mux_code(port) == SENSOR_NONE)? 1: MAX_SENSORS;
for (sub_chn = 0; sub_chn < nchn; sub_chn++){
code = sensorPortConfig[port].sensor[sub_chn];
if ((code != SENSOR_DETECT) && (code != SENSOR_NONE))
return code;
}
return SENSOR_NONE;
}
/** Get configured sensor port subchannels */
int get_subchannels(int port) ///< Sensor port
///< @return bitmask of available channels
{
int sub_chn, chn_mask = 0;
int nchn = (get_detected_mux_code(port) == SENSOR_NONE)? 1: MAX_SENSORS;
for (sub_chn = 0; sub_chn < nchn; sub_chn++){
if ((sensorPortConfig[port].sensor[sub_chn]!= SENSOR_DETECT) && (sensorPortConfig[port].sensor[sub_chn] != SENSOR_NONE)) {
chn_mask |= 1 << sub_chn;
}
}
return chn_mask;
}
/** Update per-port interface type after changing sensor/multiplexer */
void update_port_iface(int port) ///< Sensor port number (0..3)
{
sens_iface_t iface = get_iface_by_code(get_detected_mux_code(port), DETECT_MUX);
if (iface != NONE) {
port_iface[port] = iface;
return;
}
port_iface[port] = get_iface_by_code(get_detected_sensor_code(port,-1), DETECT_MUX); // '-1' - any subchannel
}
/** Get per-port interface type */
sens_iface_t get_port_interface(int port) ///< Sensor port number (0..3)
///< @ return interface type (none, parallel12, hispi4
{
return port_iface[port];
}
/** Set sensor port multiplexer type */
int set_detected_mux_code(int port, ///< Sensor port number (0..3)
int mux_type) ///< Sensor multiplexer type (SENSOR_DETECT, SENSOR_MUX_10359 or SENSOR_NONE)
///< @return 0 - OK, -EINVAL if mux_type is invalid
{
if (mux_type < 0)
return mux_type;
if (!get_name_by_code(mux_type, DETECT_MUX)){
pr_err("%s: Invalid port multiplexer code for port %d: 0x%x\n", __func__, port, mux_type);
return -EINVAL;
}
sensorPortConfig[port & 3].mux = mux_type;
update_port_iface(port);
return 0;
}
/** Set sensor port multiplexer type */
int set_detected_sensor_code(int port, ///< Sensor port number (0..3)
int sub_chn, ///< Sensor subchannel (0..3)
int sens_type) ///< Sensor code (SENSOR_DETECT, SENSOR_NONE, or SENSOR_*)
///< @return 0 - OK, -EINVAL if mux_type is invalid)
{
if (sens_type < 0)
return sens_type;
if (!get_name_by_code(sens_type, DETECT_SENSOR)){
pr_err("%s: Invalid sensor code for port %d, subchannel %d: 0x%x\n", __func__, port, sens_type);
return -EINVAL;
}
sensorPortConfig[port & 3].sensor[sub_chn] = sens_type;
update_port_iface(port);
return 0;
}
// SysFS interface to read/modify video memory map
#define SYSFS_PERMISSIONS 0644 /* default permissions for sysfs files */
#define SYSFS_READONLY 0444
#define SYSFS_WRITEONLY 0222
/** Sysfs helper function - get channel number from the last character of the attribute name*/
static int get_channel_from_name(struct device_attribute *attr) ///< Linux kernel interface for exporting device attributes
///< @return channel number
{
int reg = 0;
sscanf(attr->attr.name + (strlen(attr->attr.name)-1), "%du", ®);
return reg;
}
/** Sysfs helper function - get channel and sub-channel numbers from the last 2 characters of the attribute name*/
static int get_channel_sub_from_name(struct device_attribute *attr) ///< Linux kernel interface for exporting device attributes
///< @return channel * 16 + sub_channel
{
int reg = 0;
sscanf(attr->attr.name + (strlen(attr->attr.name)-2), "%du", ®);
reg += (reg/10) * 6;
return reg;
}
static ssize_t show_port_mux(struct device *dev, struct device_attribute *attr, char *buf)
{
int i;
const char * name = get_name_by_code(get_detected_mux_code(get_channel_from_name(attr)), DETECT_MUX);
if (name) return sprintf(buf,"%s\n", name);
// Should never get here
return sprintf(buf,"0x%x\n", sensorPortConfig[get_channel_from_name(attr)].mux);
}
static ssize_t show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
{
int i;
int psch = get_channel_sub_from_name(attr);
int port = (psch>>4) &3;
int sub_chn = psch &3;
const char * name = get_name_by_code(get_detected_sensor_code(port,sub_chn), DETECT_SENSOR);
if (name) return sprintf(buf,"%s\n", name);
// Should never get here
return sprintf(buf,"0x%x\n", sensorPortConfig[(psch>>4) & 3].sensor[psch & 3]);
}
static ssize_t store_port_mux(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
size_t len;
char name[80];
int port = get_channel_from_name(attr);
int i, code, rslt;
if (sscanf(buf, "%79s", name, &len)){
if ((rslt = set_detected_mux_code( port, get_code_by_name(name, DETECT_MUX)))<0)
return rslt;
}
return count;
}
static ssize_t store_sensor(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
size_t len;
char name[80];
int psch = get_channel_sub_from_name(attr);
int port = (psch>>4) &3;
int sub_chn = psch &3;
int i, rslt;
if (sscanf(buf, "%79s", name, &len)){
if ((rslt = set_detected_sensor_code(port, sub_chn, get_code_by_name(name, DETECT_SENSOR)))<0)
return rslt;
}
return count;
}
static DEVICE_ATTR(port_mux0, SYSFS_PERMISSIONS, show_port_mux, store_port_mux);
static DEVICE_ATTR(port_mux1, SYSFS_PERMISSIONS, show_port_mux, store_port_mux);
static DEVICE_ATTR(port_mux2, SYSFS_PERMISSIONS, show_port_mux, store_port_mux);
static DEVICE_ATTR(port_mux3, SYSFS_PERMISSIONS, show_port_mux, store_port_mux);
static DEVICE_ATTR(sensor00, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor01, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor02, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor03, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor10, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor11, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor12, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor13, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor20, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor21, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor22, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor23, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor30, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor31, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor32, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static DEVICE_ATTR(sensor33, SYSFS_PERMISSIONS, show_sensor, store_sensor);
static struct attribute *root_dev_attrs[] = {
&dev_attr_port_mux0.attr,
&dev_attr_port_mux1.attr,
&dev_attr_port_mux2.attr,
&dev_attr_port_mux3.attr,
&dev_attr_sensor00.attr,
&dev_attr_sensor01.attr,
&dev_attr_sensor02.attr,
&dev_attr_sensor03.attr,
&dev_attr_sensor10.attr,
&dev_attr_sensor11.attr,
&dev_attr_sensor12.attr,
&dev_attr_sensor13.attr,
&dev_attr_sensor20.attr,
&dev_attr_sensor21.attr,
&dev_attr_sensor22.attr,
&dev_attr_sensor23.attr,
&dev_attr_sensor30.attr,
&dev_attr_sensor31.attr,
&dev_attr_sensor32.attr,
&dev_attr_sensor33.attr,
NULL
};
static const struct attribute_group dev_attr_root_group = {
.attrs = root_dev_attrs,
.name = NULL,
};
static int elphel393_detect_sensors_sysfs_register(struct platform_device *pdev)
{
int retval=0;
struct device *dev = &pdev->dev;
if (&dev->kobj) {
if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval;
}
return retval;
}
/** Initialize this driver from the Device Tree.
* Read per-port multiplexers and sensors. */
static void detect_sensors_init_of(struct platform_device *pdev) ///< Platform device structure for this driver
///< @return 0 on success, or negative error.
{
const char * config_string;
char names[4][80];
struct device_node *node = pdev->dev.of_node;
int num_ports, port, num_sub, sub_chn;
if (node) {
config_string = of_get_property(node, OF_PREFIX_NAME",port-mux", NULL); // &len);
pr_info ("Mux config_string = %s (was looking for '%s')\n",config_string, OF_PREFIX_NAME",port-mux");
if (config_string) {
num_ports = sscanf(config_string,"%79s %79s %79s %79s", names[0], names[1], names[2], names[3]);
if (num_ports > SENSOR_PORTS)
num_ports = SENSOR_PORTS;
pr_info ("num_ports= %d\n",num_ports);
for (port = 0; port < num_ports; port++){
pr_info ("Setting port %d mux '%s' (0x%x)\n",port, names[port], get_code_by_name(names[port], DETECT_MUX));
set_detected_mux_code(port, get_code_by_name(names[port], DETECT_MUX));
}
}
num_ports = of_property_count_strings(node,OF_PREFIX_NAME",sensors");
if (num_ports > SENSOR_PORTS)
num_ports = SENSOR_PORTS;
pr_info ("num_ports = %d (was looking for '%s')\n",num_ports, OF_PREFIX_NAME",sensors");
for (port = 0; port < num_ports; port++){
if (of_property_read_string_index(node, OF_PREFIX_NAME",sensors", port, &config_string)) {
pr_err("%s: No data for selected port\n", __func__);
BUG();
}
pr_info ("Sensor config_string = %s\n",config_string);
if (config_string) {
num_sub = sscanf(config_string,"%79s %79s %79s %79s", names[0], names[1], names[2], names[3]);
pr_info ("port %d : %d subchannels\n",port, num_sub);
for (sub_chn = 0; sub_chn < num_sub; sub_chn++){
pr_info ("Setting sensor %d:%d '%s' (0x%x)\n",port, sub_chn, names[sub_chn], get_code_by_name(names[sub_chn], DETECT_SENSOR));
set_detected_sensor_code(port, sub_chn, get_code_by_name(names[sub_chn], DETECT_SENSOR));
}
}
}
}
}
static int detect_sensors_probe(struct platform_device *pdev)
{
unsigned int irq;
int res;
struct device *dev = &pdev->dev;
const struct of_device_id *match;
const __be32 *bufsize_be;
struct device_node *node;
// pSensorPortConfig = sensorPortConfig;
elphel393_detect_sensors_sysfs_register(pdev);
pr_info ("Registered sysfs for detect_sensors");
match = of_match_device(elphel393_detect_sensors_of_match, dev);
if (!match) {
pr_err("Detect sensors ERROR: No device tree for '%s' node found\n",elphel393_detect_sensors_of_match[0].compatible);
return -EINVAL;
}
detect_sensors_init_of(pdev);
// dev_dbg(dev, "Registering character device with name "DEV393_NAME(DEV393_DETECT_SENSORS));
// res = register_chrdev(DETECT_SENSORS_MAJOR, DEV393_NAME(DEV393_DETECT_SENSORS), &detect_sensors_fops);
// if(res < 0) {
// dev_err(dev, "\nlogger_init: couldn't get a major number %d.\n ",DETECT_SENSORS_MAJOR);
// return res;
// }
g_dev_ptr = dev; // for debugfs
return 0;
}
/** IMU/GPS logger driver remove function */
static int detect_sensors_remove(struct platform_device *pdev) ///< [in] pointer to @e platform_device structure
///< @return always 0
{
// unregister_chrdev(DETECT_SENSORS_MAJOR, DEV393_NAME(DEV393_DETECT_SENSORS));
return 0;
}
static const struct of_device_id elphel393_detect_sensors_of_match[] = {
{ .compatible = "elphel,elphel393-detect_sensors-1.00" },
{ /* end of list */ }
};
MODULE_DEVICE_TABLE(of, elphel393_detect_sensors_of_match);
static struct platform_driver elphel393_detect_sensors = {
.probe = detect_sensors_probe,
.remove = detect_sensors_remove,
.driver = {
.name = DEV393_NAME(DEV393_DETECT_SENSORS),
.of_match_table = elphel393_detect_sensors_of_match,
},
};
module_platform_driver(elphel393_detect_sensors);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andrey Filippov .");
MODULE_DESCRIPTION(DETECT_SENSORS_MODULE_DESCRIPTION);
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/detect_sensors.h 0000664 0000000 0000000 00000003465 12766141417 0027570 0 ustar 00root root 0000000 0000000 /***************************************************************************//**
* @file detect_sensors.h
* @brief Determine sensor boards attached to each of the ports. Use
* Device Tree, sysfs to set sensor types per port. Add autodetection
* (using pullup/pull downs) later
* @copyright Copyright 2016 (C) Elphel, Inc.
* @par License
* 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 .
*******************************************************************************/
#define DETECT_SENSOR 1 ///< Include sensors, May be OR-ed when looking for sensor/multiplexer code/name
#define DETECT_MUX 2 ///< Include multiplexers, May be OR-ed when looking for sensor/multiplexer code/name
typedef enum {NONE,PARALLEL12,HISPI4} sens_iface_t; ///< Sensor port interface type
int get_code_by_name(const char * name, int type);
const char * get_name_by_code(int code, int type);
sens_iface_t get_iface_by_code(int code, int type);
int get_detected_mux_code(int port);
int get_detected_sensor_code(int port, int sub_chn);
int get_subchannels(int port);
int set_detected_mux_code(int port, int mux_type);
int set_detected_sensor_code(int port, int sub_chn, int mux_type);
sens_iface_t get_port_interface(int port);
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/elphel393-init.c 0000664 0000000 0000000 00000032412 12766141417 0027202 0 ustar 00root root 0000000 0000000 /*!***************************************************************************
* @file elphel393-init.c
* @brief * Unlock rootfs NAND flash partition
* * Read MAC and other useful info from NAND flash OTP area
* and put to sysfs
*
* E-mail: oleg@elphel.com, support-list@elphel.com
*
* @copyright Copyright (C) 2016 Elphel, Inc.
*
* 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 .
*****************************************************************************/
#define DRV_NAME "elphel393-init"
#define pr_fmt(fmt) DRV_NAME": " fmt
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* from elphel393-mem.c */
#define SYSFS_PERMISSIONS 0644 /* default permissions for sysfs files */
#define SYSFS_READONLY 0444
#define SYSFS_WRITEONLY 0222
#define NAND_FLASH_OTP_PAGE_OFFSET 0*2048
/*
* Read and parse bootargs parameter in the device tree
* This driver is run in the last place - at least after NAND driver is probed
*/
static struct mtd_info *mtd;
//surprise size
static char *bootargs;
//known size, all should be zero filled
static char boardinfo[2048];
static char serial[13];
static char revision[8];
static int setup_mio_pin_and_reset_usb(void);
static int __init elphel393_early_initialize(void){
pr_info("Set up the mio pin 49 and reset USB\n");
setup_mio_pin_and_reset_usb();
return 0;
}
/*
static int __init elphel393_early_initialize(void){
struct device_node *node;
struct property *newproperty;
u8 *macaddr;
pr_info("VERY EARLY CALL TO UPDATE DEVICE TREE");
node = of_find_node_by_name(NULL, "ps7-ethernet");
if (!node){
pr_err("Device tree node 'ps7-ethernet' not found.");
return -ENODEV;
}
newproperty = kzalloc(sizeof(*newproperty) + 6, GFP_KERNEL);
if (!newproperty)
return -ENOMEM;
newproperty->value = newproperty + 1;
newproperty->length = 6;
newproperty->name = kstrdup("local-mac-address", GFP_KERNEL);
if (!newproperty->name) {
kfree(newproperty);
return -ENOMEM;
}
macaddr = newproperty->value;
macaddr[0] = 0x02;
macaddr[1] = 0x03;
macaddr[2] = 0x04;
macaddr[3] = 0x05;
macaddr[4] = 0x06;
macaddr[5] = 0x07;
of_update_property(node,newproperty);
return 0;
}
*/
static int __init elphel393_init_init (void)
{
struct device_node *node;
node = of_find_node_by_name(NULL, "chosen");
//just throw an error
if (!node){
pr_err("Device tree node 'chosen' not found.");
return -ENODEV;
}
of_property_read_string(node, "bootargs", &bootargs);
if (bootargs!=NULL) {
pr_debug("bootargs line from device tree is %s",bootargs);
}
return 0;
}
static void __exit elphel393_init_exit(void)
{
printk("Exit\n");
}
// SYSFS
static ssize_t get_bootargs(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%s\n",bootargs);
}
static ssize_t get_boardinfo(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%s\n",boardinfo);
}
static ssize_t get_revision(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%s\n",revision);
}
static ssize_t get_serial(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%s\n",serial);
}
static int get_factory_info(void);
static ssize_t set_boardinfo(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int ret;
size_t retlen;
char wbuf[2048];
if (IS_ERR(mtd)){
pr_err("Get MTD device error, code:%d\n",-(u32)mtd);
return -ENODEV;
}
//need all 0xff bytes
memset(wbuf,0xff,2048);
//too much of a trouble to read from flash again
if(!strnstr(boardinfo,"",sizeof(boardinfo))){
pr_info("Factory Info record is clean.\n");
pr_info("Data to be written: %s\n",buf);
// I got some buf, unknown size- should be limited to 2048? ok
if (strlen(buf)>2047) {
pr_err("Data > 2KiB. Abort.\n");
return -EFBIG;
}
// Not strict check, just look for opening tags.
if(!strstr(buf,"")||!strstr(buf,"")||!strstr(buf,"")){
pr_err("Bad data format\n");
return -EINVAL;
}
//copy to wbuf, buf is null terminated and is <=2047
strcpy(wbuf,buf);
//pr_info("BUFFER: %s\n",wbuf);
ret = mtd_write_user_prot_reg(mtd, NAND_FLASH_OTP_PAGE_OFFSET, 2048, &retlen, wbuf);
if (ret){
pr_err("Flash page write, code %d\n",ret);
return ret;
}
pr_info("Data is successfully written and cannot be overwritten anymore\n");
get_factory_info();
}else{
pr_err("Factory Info record (serial='%s' revision='%s') can not be overwritten\n",serial,revision);
return -EPERM;
}
return count;
}
static ssize_t perform_usbreset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){
pr_info("EMIO pin 49: USB reset\n");
setup_mio_pin_and_reset_usb();
return count;
}
static DEVICE_ATTR(bootargs , SYSFS_PERMISSIONS & SYSFS_READONLY, get_bootargs , NULL);
static DEVICE_ATTR(boardinfo , SYSFS_PERMISSIONS , get_boardinfo, set_boardinfo);
static DEVICE_ATTR(revision , SYSFS_PERMISSIONS & SYSFS_READONLY, get_revision , NULL);
static DEVICE_ATTR(serial , SYSFS_PERMISSIONS & SYSFS_READONLY, get_serial , NULL);
static DEVICE_ATTR(usbreset , SYSFS_PERMISSIONS , NULL, perform_usbreset);
static struct attribute *root_dev_attrs[] = {
&dev_attr_bootargs.attr,
&dev_attr_boardinfo.attr,
&dev_attr_revision.attr,
&dev_attr_serial.attr,
&dev_attr_usbreset.attr,
NULL
};
static const struct attribute_group dev_attr_root_group = {
.attrs = root_dev_attrs,
.name = NULL,
};
static int elphel393_init_sysfs_register(struct platform_device *pdev)
{
int retval=0;
struct device *dev = &pdev->dev;
if (&dev->kobj) {
if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval;
}
return retval;
}
static int elphel393_init_probe(struct platform_device *pdev)
{
char *bootargs_copy;
char *token;
char *token_copy;
char *param;
char *value;
//mtd device number to unlock
u8 devnum= 0;
u8 unlock= 0;
pr_debug("Probing\n");
//copy bootargs string
bootargs_copy = kstrdup(bootargs,GFP_KERNEL);
//find out which partition to unlock if any
//parse bootargs
do {
token = strsep(&bootargs_copy," ");
if (token) {
//if '=' is found - split by '='
token_copy = token;
if (strchr(token_copy,'=')){
if (!strcmp(token_copy,"rootfstype=ubifs")){
unlock=1;
}
param = strsep(&token_copy,"=");
//if "ubi.mtd" then get the partition number and unlock /dev/mtdN - if not found then don't care
if (!strcmp(param,"ubi.mtd")){
value = strsep(&token_copy,",");
if (kstrtou8(value,10,&devnum)){
pr_err("Partition number str to u8 conversion.");
}
}
}
}
} while (token);
kfree(bootargs_copy);
// unlock /dev/mtdN partition
// if there's no need to unlock, get /dev/mtd0
if (devnum>=0){
mtd = get_mtd_device(NULL,devnum);
if (IS_ERR(mtd)){
pr_err("Get MTD device error, code:%d\n",-(u32)mtd);
return -ENODEV;
}
if (unlock){
mtd_unlock(mtd,0,mtd->size);
pr_info("/dev/mtd%d: unlocked",devnum);
}
}
// read boardinfo record
// * device number is not important
// * no need to unlock
// page size
BUG_ON(mtd->writesize > 2048);
get_factory_info();
elphel393_init_sysfs_register(pdev);
return 0;
}
static int get_factory_info(void){
char regvalh[]="0000";
char regvall[]="00000000";
u16 hwaddrh0, hwaddrh1;
u32 hwaddrl0, hwaddrl1;
size_t retlen;
//size of nand flash page
char kbuf[2048];
size_t size = mtd->writesize;
size_t min_size;
int ret;
char *ps,*pe;
struct device_node *node;
struct property *newproperty;
u32 *mac32; // = (u32*) mac_address;
u8 *macaddr;
u8 block_factory_mac = 0;
node = of_find_node_by_name(NULL, "ps7-ethernet");
if (!node){
pr_err("Device tree node 'ps7-ethernet' not found.");
return -ENODEV;
}
// check if MAC address was overridden in u-boot
mac32 = (u32 *) of_get_mac_address(node);
hwaddrl0 = cpu_to_le32(mac32[0]);
hwaddrh0 = cpu_to_le16(mac32[1]);
if ((hwaddrh0!=0x0000)||(hwaddrl0!=0x10640E00)){
block_factory_mac = 1;
}
ret = mtd_read_user_prot_reg(mtd, NAND_FLASH_OTP_PAGE_OFFSET, size, &retlen, kbuf);
if (ret){
pr_err("Flash page read, code %d",ret);
return ret;
}
pr_debug("buf: %s\n",kbuf);
// do whatever we like with the kbuf
// search for ""
// expecting to find it somewhere...
if(strnstr(kbuf,"",size)){
//...right in the beginning or error
ps = strnstr(kbuf,"",size);
pe = strnstr(kbuf,"",size);
min_size = min(pe-ps-(sizeof("")-1),sizeof(serial)-1);
strncpy(serial,ps+sizeof("")-1,min_size);
strncpy(regvalh,serial,4);
strncpy(regvall,serial+4,8);
//there is kstrtou64 but it doesn't work?
kstrtou16(regvalh,16,&hwaddrh1);
kstrtou32(regvall,16,&hwaddrl1);
pr_debug("MAC from flash: %02x:%02x:%02x:%02x:%02x:%02x\n",
(hwaddrl1 & 0xff), ((hwaddrl1 >> 8) & 0xff),
((hwaddrl1 >> 16) & 0xff), (hwaddrl1 >> 24),
(hwaddrh1 & 0xff), (hwaddrh1 >> 8));
newproperty = kzalloc(sizeof(*newproperty) + 6, GFP_KERNEL);
if (!newproperty)
return -ENOMEM;
newproperty->value = newproperty + 1;
newproperty->length = 6;
newproperty->name = kstrdup("local-mac-address", GFP_KERNEL);
if (!newproperty->name) {
kfree(newproperty);
return -ENOMEM;
}
macaddr = newproperty->value;
macaddr[0] = (hwaddrh1 >> 8) & 0xff;
macaddr[1] = hwaddrh1 & 0xff;
macaddr[2] = (hwaddrl1 >> 24) & 0xff;
macaddr[3] = (hwaddrl1 >> 16) & 0xff;
macaddr[4] = (hwaddrl1 >> 8) & 0xff;
macaddr[5] = hwaddrl1 & 0xff;
if (!block_factory_mac)
of_update_property(node,newproperty);
else
pr_info("Factory MAC: %02x:%02x:%02x:%02x:%02x:%02x, u-boot overridden MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
(hwaddrl1 & 0xff),((hwaddrl1 >> 8) & 0xff),((hwaddrl1 >> 16) & 0xff),(hwaddrl1 >> 24),(hwaddrh1 & 0xff),(hwaddrh1 >> 8),
(hwaddrl0 & 0xff),((hwaddrl0 >> 8) & 0xff),((hwaddrl0 >> 16) & 0xff),(hwaddrl0 >> 24),(hwaddrh0 & 0xff),(hwaddrh0 >> 8));
mac32 = (u32 *) of_get_mac_address(node);
hwaddrl1 = cpu_to_le32(mac32[0]);
hwaddrh1 = cpu_to_le16(mac32[1]);
pr_debug("new MAC from device tree: %02x:%02x:%02x:%02x:%02x:%02x\n",
(hwaddrl1 & 0xff), ((hwaddrl1 >> 8) & 0xff),
((hwaddrl1 >> 16) & 0xff), (hwaddrl1 >> 24),
(hwaddrh1 & 0xff), (hwaddrh1 >> 8));
//write hwaddr to zynq reg
//kstrtou16(serial,16,®valh);
//serial
ps = strnstr(kbuf,"",size);
pe = strnstr(kbuf,"",size);
min_size = min(pe-ps-(sizeof("")-1),sizeof(revision)-1);
strncpy(revision,ps+sizeof("")-1,min_size);
ps = strnstr(kbuf,"",size);
pe = strnstr(kbuf,"",size);
min_size = min(pe-ps+(sizeof("")-1),sizeof(boardinfo)-1);
strncpy(boardinfo,ps,min_size);
}
return 0;
}
static int setup_mio_pin_and_reset_usb(void){
u32 reg_value;
void __iomem* emio_ptr = ioremap(0xe000a000, 0x00000300);
if (!emio_ptr) {
pr_err("Failed to get a pointer to the EMIO registers");
return -1;
}
const u32 newvalue = 0x1<<17;
reg_value = readl(emio_ptr+0x244);
pr_debug("read DIR reg: 0x%08x\n",reg_value);
writel(reg_value|newvalue,emio_ptr+0x244);
udelay(10);
reg_value = readl(emio_ptr+0x248);
pr_debug("read EN reg: 0x%08x\n",reg_value);
writel(reg_value|newvalue,emio_ptr+0x248);
udelay(10);
reg_value = readl(emio_ptr+0x44);
pr_debug("read OUT reg: 0x%08x\n",reg_value);
writel(reg_value&(~newvalue),emio_ptr+0x44);
udelay(10);
writel(reg_value|newvalue,emio_ptr+0x44);
reg_value = readl(emio_ptr+0x64);
pr_debug("read IN reg (correct value: 0x00020000): 0x%08x\n",reg_value);
iounmap(emio_ptr);
return 0;
}
static int elphel393_init_remove(struct platform_device *pdev)
{
pr_info("Remove");
return 0;
}
static struct of_device_id elphel393_init_of_match[] = {
{ .compatible = "elphel,elphel393-init-1.00", },
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, elphel393_init_of_match);
static struct platform_driver elphel393_initialize = {
.probe = elphel393_init_probe,
.remove = elphel393_init_remove,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = elphel393_init_of_match,
.pm = NULL, /* power management */
},
};
early_initcall(elphel393_early_initialize);
module_platform_driver(elphel393_initialize);
module_init(elphel393_init_init);
module_exit(elphel393_init_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Elphel, Inc.");
MODULE_DESCRIPTION("Unlock rootfs flash partition and read/write board info: serial and revision");
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/elphel393-mem.c 0000664 0000000 0000000 00000053611 12766141417 0027021 0 ustar 00root root 0000000 0000000 /*!***********************************************************************//**
* @file elphel393-mem.c
* @brief Reserve large memory range at boot time (when it is available)
* to use as a circular video buffer
* @copyright Copyright (C) 2015 Elphel, Inc.
* @par License
* 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 .
*****************************************************************************/
#define DRV_NAME "elphel393-mem"
#define pr_fmt(fmt) DRV_NAME": " fmt
#include
#include
#include
#include
#include // kmalloc
#include // vmalloc
#include // __get_free_pages
#include
#include
#include
#include
#include
#include
#include
#include
#define SYSFS_PERMISSIONS 0644 /* default permissions for sysfs files */
#define SYSFS_READONLY 0444
#define SYSFS_WRITEONLY 0222
static ssize_t get_paddr(struct device *dev, struct device_attribute *attr, char *buf);
static struct elphel_buf_t _elphel_buf = {
// Coherent DMA buffer
.vaddr = NULL,
.paddr = 0,
.size = 0,
// Host to device stream DMA buffer
.h2d_vaddr = NULL,
.h2d_paddr = 0,
.h2d_size = 1024, // TODO: add to DT, learn how to allocate more
// Device to host stream DMA buffer
.d2h_vaddr = NULL,
.d2h_paddr = 0,
.d2h_size = 1024,
// Bidirectional stream DMA buffer
.bidir_vaddr = NULL,
.bidir_paddr = 0,
.bidir_size = 1024,
// Device to host stream DMA buffer for histograms
.histograms_vaddr = NULL,
.histograms_paddr = 0,
.histograms_size = 1024,
// Device to host stream DMA buffer for the logger
.logger_vaddr = NULL,
.logger_paddr = 0,
.logger_size = 1024 // should be 2**n !
};
struct elphel_buf_t *pElphel_buf; // static can not be extern
EXPORT_SYMBOL_GPL(pElphel_buf);
static int __init elphelmem_init(void)
{
struct device_node *node;
const __be32 *bufsize_be;
pElphel_buf = &_elphel_buf;
node = of_find_node_by_name(NULL, "elphel393-mem");
if (!node)
{
pr_err("DMA buffer allocation ERROR: No device tree node found\n");
return -ENODEV;
}
bufsize_be = (__be32 *)of_get_property(node, "memsize", NULL);
_elphel_buf.size = be32_to_cpup(bufsize_be);
_elphel_buf.vaddr = dma_alloc_coherent(NULL,(_elphel_buf.size*PAGE_SIZE),&(_elphel_buf.paddr),GFP_KERNEL);
if(_elphel_buf.vaddr) {
pr_info("Allocated %u pages for DMA at address 0x%x\n", (u32)_elphel_buf.size, (u32)_elphel_buf.paddr);
} else {
pr_err("ERROR allocating coherent DMA memory buffer\n");
}
_elphel_buf.h2d_vaddr = kzalloc((_elphel_buf.h2d_size*PAGE_SIZE) ,GFP_KERNEL);
if (!_elphel_buf.h2d_vaddr){
_elphel_buf.h2d_size = 0;
pr_err("ERROR allocating H2D DMA memory buffer\n");
}
_elphel_buf.d2h_vaddr = kzalloc((_elphel_buf.d2h_size*PAGE_SIZE) ,GFP_KERNEL);
if (!_elphel_buf.d2h_vaddr){
_elphel_buf.d2h_size = 0;
pr_err("ERROR allocating D2H DMA memory buffer\n");
}
_elphel_buf.histograms_vaddr = kzalloc((_elphel_buf.histograms_size*PAGE_SIZE) ,GFP_KERNEL);
if (!_elphel_buf.histograms_vaddr){
_elphel_buf.histograms_size = 0;
pr_err("ERROR allocating HISTOGRAMS memory buffer\n");
}
_elphel_buf.logger_vaddr = kzalloc((_elphel_buf.logger_size*PAGE_SIZE) ,GFP_KERNEL);
if (!_elphel_buf.logger_vaddr){
_elphel_buf.logger_size = 0;
pr_err("ERROR allocating LOGGER memory buffer\n");
}
_elphel_buf.bidir_vaddr = kzalloc((_elphel_buf.bidir_size*PAGE_SIZE) ,GFP_KERNEL);
if (!_elphel_buf.bidir_vaddr){
_elphel_buf.bidir_size = 0;
pr_err("ERROR allocating Bidirectional DMA memory buffer\n");
}
pr_info("Coherent buffer vaddr: 0x%08X\n",(u32) pElphel_buf -> vaddr);
pr_info("Coherent buffer paddr: 0x%08X\n",(u32) pElphel_buf -> paddr);
pr_info("Coherent buffer length: 0x%08X\n",(u32) pElphel_buf -> size * PAGE_SIZE);
return 0;
}
static void __exit elphelmem_exit(void)
{
pr_info("DMA buffer disabled\n");
}
static ssize_t get_paddr(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"0x%x\n", (u32)_elphel_buf.paddr);
}
static ssize_t get_size(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%u\n", _elphel_buf.size);
}
static ssize_t get_paddr_h2d(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"0x%x\n", (u32)_elphel_buf.h2d_paddr);
}
static ssize_t get_size_h2d(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%u\n", _elphel_buf.h2d_size);
}
static ssize_t get_paddr_d2h(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"0x%x\n", (u32)_elphel_buf.d2h_paddr);
}
static ssize_t get_size_d2h(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%u\n", _elphel_buf.d2h_size);
}
static ssize_t get_paddr_bidir(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"0x%x\n", (u32)_elphel_buf.bidir_paddr);
}
static ssize_t get_size_bidir(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%u\n", _elphel_buf.bidir_size);
}
static ssize_t get_paddr_histograms(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"0x%x\n", (u32)_elphel_buf.histograms_paddr);
}
static ssize_t get_size_histograms(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%u\n", _elphel_buf.histograms_size);
}
static ssize_t get_paddr_logger(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"0x%x\n", (u32)_elphel_buf.logger_paddr);
}
static ssize_t get_size_logger(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"%u\n", _elphel_buf.logger_size);
}
static ssize_t sync_for_cpu_h2d(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.h2d_paddr;
len = _elphel_buf.h2d_size * PAGE_SIZE;
}
printk("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_cpu(dev, paddr, len, DMA_TO_DEVICE);
return count;
}
static ssize_t sync_for_device_h2d(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.h2d_paddr;
len = _elphel_buf.h2d_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_device(dev, paddr, len, DMA_TO_DEVICE);
return count;
}
static ssize_t sync_for_cpu_d2h(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.d2h_paddr;
len = _elphel_buf.d2h_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_cpu(dev, paddr, len, DMA_FROM_DEVICE);
return count;
}
static ssize_t sync_for_device_d2h(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.d2h_paddr;
len = _elphel_buf.d2h_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_device(dev, paddr, len, DMA_FROM_DEVICE);
return count;
}
static ssize_t sync_for_cpu_bidir(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.bidir_paddr;
len = _elphel_buf.bidir_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_cpu(dev, paddr, len, DMA_BIDIRECTIONAL);
return count;
}
static ssize_t sync_for_device_bidir(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.bidir_paddr;
len = _elphel_buf.bidir_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_device(dev, paddr, len, DMA_BIDIRECTIONAL);
return count;
}
static ssize_t sync_for_cpu_histograms(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.histograms_paddr;
len = _elphel_buf.histograms_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_cpu(dev, paddr, len, DMA_FROM_DEVICE);
return count;
}
static ssize_t sync_for_device_histograms(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.histograms_paddr;
len = _elphel_buf.histograms_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_device(dev, paddr, len, DMA_FROM_DEVICE);
return count;
}
static ssize_t sync_for_cpu_logger(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.logger_paddr;
len = _elphel_buf.logger_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_cpu(dev, paddr, len, DMA_FROM_DEVICE);
return count;
}
static ssize_t sync_for_device_logger(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
dma_addr_t paddr;
size_t len;
int num_items;
num_items=sscanf(buf, "%u %u", &paddr, &len);
if (num_items<2) {
paddr = _elphel_buf.logger_paddr;
len = _elphel_buf.logger_size * PAGE_SIZE;
}
pr_info("\naddr=0x%08x, size = 0x%08x\n", paddr, len);
dma_sync_single_for_device(dev, paddr, len, DMA_FROM_DEVICE);
return count;
}
static ssize_t get_sync_for_device_h2d(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the host to device DMA buffer to device (after CPU writes).\n");
}
static ssize_t get_sync_for_cpu_h2d(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the host to device DMA buffer to CPU (before CPU reads).\n");
}
static ssize_t get_sync_for_device_d2h(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the device to host DMA buffer to device (after CPU writes).\n");
}
static ssize_t get_sync_for_cpu_d2h(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the device to host DMA buffer to CPU (before CPU reads).\n");
}
static ssize_t get_sync_for_device_bidir(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the bidirectional DMA buffer to device (after CPU writes).\n");
}
static ssize_t get_sync_for_cpu_bidir(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the bidirectional DMA buffer to CPU (before CPU reads).\n");
}
static ssize_t get_sync_for_device_histograms(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the histograms buffer to device (after CPU writes).\n");
}
static ssize_t get_sync_for_cpu_histograms(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the histograms buffer to CPU (before CPU reads).\n");
}
static ssize_t get_sync_for_device_logger(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the logger buffer to device (after CPU writes).\n");
}
static ssize_t get_sync_for_cpu_logger(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Write address/length pair into this file to hand this region of the logger buffer to CPU (before CPU reads).\n");
}
static DEVICE_ATTR(buffer_address, SYSFS_PERMISSIONS & SYSFS_READONLY, get_paddr, NULL);
static DEVICE_ATTR(buffer_pages, SYSFS_PERMISSIONS & SYSFS_READONLY, get_size, NULL);
static DEVICE_ATTR(buffer_address_h2d, SYSFS_PERMISSIONS & SYSFS_READONLY, get_paddr_h2d, NULL);
static DEVICE_ATTR(buffer_pages_h2d, SYSFS_PERMISSIONS & SYSFS_READONLY, get_size_h2d, NULL);
static DEVICE_ATTR(buffer_address_d2h, SYSFS_PERMISSIONS & SYSFS_READONLY, get_paddr_d2h, NULL);
static DEVICE_ATTR(buffer_pages_d2h, SYSFS_PERMISSIONS & SYSFS_READONLY, get_size_d2h, NULL);
static DEVICE_ATTR(buffer_address_bidir, SYSFS_PERMISSIONS & SYSFS_READONLY, get_paddr_bidir, NULL);
static DEVICE_ATTR(buffer_pages_bidir, SYSFS_PERMISSIONS & SYSFS_READONLY, get_size_bidir, NULL);
static DEVICE_ATTR(buffer_address_histograms, SYSFS_PERMISSIONS & SYSFS_READONLY, get_paddr_histograms, NULL);
static DEVICE_ATTR(buffer_pages_histograms, SYSFS_PERMISSIONS & SYSFS_READONLY, get_size_histograms, NULL);
static DEVICE_ATTR(buffer_address_logger, SYSFS_PERMISSIONS & SYSFS_READONLY, get_paddr_logger, NULL);
static DEVICE_ATTR(buffer_pages_logger, SYSFS_PERMISSIONS & SYSFS_READONLY, get_size_logger, NULL);
static DEVICE_ATTR(sync_for_cpu_h2d, SYSFS_PERMISSIONS, get_sync_for_cpu_h2d, sync_for_cpu_h2d);
static DEVICE_ATTR(sync_for_device_h2d, SYSFS_PERMISSIONS, get_sync_for_device_h2d, sync_for_device_h2d);
static DEVICE_ATTR(sync_for_cpu_d2h, SYSFS_PERMISSIONS, get_sync_for_cpu_d2h, sync_for_cpu_d2h);
static DEVICE_ATTR(sync_for_device_d2h, SYSFS_PERMISSIONS, get_sync_for_device_d2h, sync_for_device_d2h);
static DEVICE_ATTR(sync_for_cpu_bidir, SYSFS_PERMISSIONS, get_sync_for_cpu_bidir, sync_for_cpu_bidir);
static DEVICE_ATTR(sync_for_device_bidir, SYSFS_PERMISSIONS, get_sync_for_device_bidir, sync_for_device_bidir);
static DEVICE_ATTR(sync_for_cpu_histograms, SYSFS_PERMISSIONS, get_sync_for_cpu_histograms, sync_for_cpu_histograms);
static DEVICE_ATTR(sync_for_device_histograms,SYSFS_PERMISSIONS, get_sync_for_device_histograms, sync_for_device_histograms);
static DEVICE_ATTR(sync_for_cpu_logger, SYSFS_PERMISSIONS, get_sync_for_cpu_logger, sync_for_cpu_logger);
static DEVICE_ATTR(sync_for_device_logger, SYSFS_PERMISSIONS, get_sync_for_device_logger, sync_for_device_logger);
static struct attribute *root_dev_attrs[] = {
&dev_attr_buffer_address.attr,
&dev_attr_buffer_pages.attr,
&dev_attr_buffer_address_h2d.attr,
&dev_attr_buffer_pages_h2d.attr,
&dev_attr_buffer_address_d2h.attr,
&dev_attr_buffer_pages_d2h.attr,
&dev_attr_buffer_address_bidir.attr,
&dev_attr_buffer_pages_bidir.attr,
&dev_attr_buffer_address_histograms.attr,
&dev_attr_buffer_pages_histograms.attr,
&dev_attr_buffer_address_logger.attr,
&dev_attr_buffer_pages_logger.attr,
&dev_attr_sync_for_cpu_h2d.attr,
&dev_attr_sync_for_device_h2d.attr,
&dev_attr_sync_for_cpu_d2h.attr,
&dev_attr_sync_for_device_d2h.attr,
&dev_attr_sync_for_cpu_bidir.attr,
&dev_attr_sync_for_device_bidir.attr,
&dev_attr_sync_for_cpu_histograms.attr,
&dev_attr_sync_for_device_histograms.attr,
&dev_attr_sync_for_cpu_logger.attr,
&dev_attr_sync_for_device_logger.attr,
NULL
};
static const struct attribute_group dev_attr_root_group = {
.attrs = root_dev_attrs,
.name = NULL,
};
static int elphel393_mem_sysfs_register(struct platform_device *pdev)
{
int retval=0;
struct device *dev = &pdev->dev;
if (&dev->kobj) {
if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval;
}
return retval;
}
static int elphel393_mem_probe(struct platform_device *pdev)
{
elphel393_mem_sysfs_register(pdev);
pr_info("Probing elphel393-mem\n");
if (_elphel_buf.h2d_vaddr){
// mapped as DMA_BIDIRECTIONAL, each time will be synchronized when passing control from soft to hard and back
pElphel_buf->h2d_paddr = dma_map_single(&pdev->dev, _elphel_buf.h2d_vaddr, (_elphel_buf.h2d_size*PAGE_SIZE), DMA_TO_DEVICE);
if (!pElphel_buf->h2d_paddr){
pr_err("ERROR in dma_map_single() for h2d buffer\n");
return 0;
}
// printk("H2D DMA buffer location:\t\t0x%08X\n", pElphel_buf->h2d_paddr);
}
if (_elphel_buf.d2h_vaddr){
// mapped as DMA_BIDIRECTIONAL, each time will be synchronized when passing control from soft to hard and back
pElphel_buf->d2h_paddr = dma_map_single(&pdev->dev, _elphel_buf.d2h_vaddr, (_elphel_buf.d2h_size*PAGE_SIZE), DMA_FROM_DEVICE);
if (!pElphel_buf->d2h_paddr){
pr_err("ERROR in dma_map_single() for d2h buffer\n");
return 0;
}
}
if (_elphel_buf.histograms_vaddr){
pElphel_buf->histograms_paddr = dma_map_single(&pdev->dev, _elphel_buf.histograms_vaddr, (_elphel_buf.histograms_size*PAGE_SIZE), DMA_FROM_DEVICE);
if (!pElphel_buf->histograms_paddr){
pr_err("ERROR in dma_map_single() for histograms buffer\n");
return 0;
}
}
if (_elphel_buf.logger_vaddr){
pElphel_buf->logger_paddr = dma_map_single(&pdev->dev, _elphel_buf.logger_vaddr, (_elphel_buf.logger_size*PAGE_SIZE), DMA_FROM_DEVICE);
if (!pElphel_buf->logger_paddr){
pr_err("ERROR in dma_map_single() for the logger buffer\n");
return 0;
}
}
if (_elphel_buf.bidir_vaddr){
pElphel_buf->bidir_paddr = dma_map_single(&pdev->dev, _elphel_buf.bidir_vaddr, (_elphel_buf.bidir_size*PAGE_SIZE), DMA_BIDIRECTIONAL);
if (!pElphel_buf->bidir_paddr){
pr_err("ERROR in dma_map_single() for bidirectional buffer\n");
return 0;
}
// printk("Bidirectional DMA buffer location:\t0x%08X\n", pElphel_buf->bidir_paddr);
}
printk("H2D stream buffer vaddr: 0x%08X\n",(u32) pElphel_buf -> h2d_vaddr);
printk("H2D stream buffer paddr: 0x%08X\n",(u32) pElphel_buf -> h2d_paddr);
printk("H2D stream buffer length: 0x%08lX\n",(u32) pElphel_buf -> h2d_size * PAGE_SIZE);
printk("D2H stream buffer vaddr: 0x%08X\n",(u32) pElphel_buf -> d2h_vaddr);
printk("D2H stream buffer paddr: 0x%08X\n",(u32) pElphel_buf -> d2h_paddr);
printk("D2H stream buffer length: 0x%08lX\n",(u32) pElphel_buf -> d2h_size * PAGE_SIZE);
printk("Bidirectional stream buffer vaddr: 0x%08X\n",(u32) pElphel_buf -> bidir_vaddr);
printk("Bidirectional stream buffer paddr: 0x%08X\n",(u32) pElphel_buf -> bidir_paddr);
printk("Bidirectional stream buffer length: 0x%08lX\n",(u32) pElphel_buf -> bidir_size * PAGE_SIZE);
printk("HISTOGRAMS stream buffer vaddr: 0x%08X\n",(u32) pElphel_buf -> histograms_vaddr);
printk("HISTOGRAMS stream buffer paddr: 0x%08X\n",(u32) pElphel_buf -> histograms_paddr);
printk("HISTOGRAMS stream buffer length: 0x%08lX\n",(u32) pElphel_buf -> histograms_size * PAGE_SIZE);
printk("LOGGER stream buffer vaddr: 0x%08X\n",(u32) pElphel_buf -> logger_vaddr);
printk("LOGGER stream buffer paddr: 0x%08X\n",(u32) pElphel_buf -> logger_paddr);
printk("LOGGER stream buffer length: 0x%08lX\n",(u32) pElphel_buf -> logger_size * PAGE_SIZE);
return 0;
}
static int elphel393_mem_remove(struct platform_device *pdev)
{
pr_info("Removing elphel393-mem");
return 0;
}
static struct of_device_id elphel393_mem_of_match[] = {
{ .compatible = "elphel,elphel393-mem-1.00", },
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, elphel393_mem_of_match);
static struct platform_driver elphel393_mem = {
.probe = elphel393_mem_probe,
.remove = elphel393_mem_remove,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = elphel393_mem_of_match,
.pm = NULL, /* power management */
},
};
module_platform_driver(elphel393_mem);
module_init(elphelmem_init);
module_exit(elphelmem_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Elphel, Inc.");
MODULE_DESCRIPTION("Reserve a large chunk of contiguous memory at boot");
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/elphel393-pwr.c 0000664 0000000 0000000 00000143051 12766141417 0027051 0 ustar 00root root 0000000 0000000 /*!***********************************************************************//**
* @file elphel393-pwr.c
* @brief power supplies control on Elphel 10393 board
* @copyright Copyright (C) 2013-2016 Elphel, Inc.
* @par License
* 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 .
*/
#undef DEBUG /* should be before linux/module.h - enables dev_dbg at boot in this file */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DRIVER_DESCRIPTION "Elphel 10393 power supply control"
#define DRIVER_VERSION "1.00"
#define SYSFS_PERMISSIONS 0644 /* default permissions for sysfs files */
#define SYSFS_READONLY 0444
#define SYSFS_WRITEONLY 0222
/* PCA6408As */
#define GPIO_CHIP1_ADDR 0x20
#define GPIO_CHIP2_ADDR 0x21
/* PCA9571 on 10389, high 4 pins are NC */
#define GPIO_10389_U4_ADDR 0x25
#define LTC3589_ADDR 0x34
/* TODO: set resistors in device tree to accommodate different revisions ( elphel393_pwr,vp15_r1 = <357000>)*/
#define VP15_R1 357000
#define VP15_R2 287000
#define VCC_SENS01_R1 787000
#define VCC_SENS01_R2 287000
#define VCC_SENS23_R1 787000
#define VCC_SENS23_R2 287000
#define VP5_R1 523000
#define VP5_R2 100000
#define VLDO18_R1 357000
#define VLDO18_R2 287000
#define PINSTRAPPED_OVEN 1
#define REF_FIXED_TENTH_MV 8000
#define REF_VAR_0_TENTH_MV 3625
#define REF_VAR_STEP_TENTH_MV 125
#define DEAFULT_TIMEOUT 300 /* number of retries testing pgood before giving up */
static DEFINE_MUTEX(gpio_10389_lock);
struct pwr_gpio_t {
const char * label;
int pin;
int dir; /* direction: 0 - in, 1 - out*/
int out_val; /* output value */
};
struct elphel393_pwr_data_t {
int chip_i2c_addr[4];
struct device * ltc3489_dev;
struct pwr_gpio_t pwr_gpio [24];
int simulate; /* do not perform actual i2c writes */
struct mutex lock;
int pgoot_timeout;
int pinstrapped_oven;
};
struct voltage_reg_t {
const char * name;
int r1; /* resistor in ohms, if <=0 - r2 is voltage in mv */
int r2; /* resistor in ohms, if r1<=0 - voltage in mv */
int awe_ref; /* 0 - no control, -1 - margining VP10, -2 - margining VP18 */
int awe_en; /* 0 - no control, negative - -1-gpio_index */
int awe_pgood; /* 0 - no status , negative - -1-gpio_index */
int mask_pgood; /* 1 - temporarily disable pgood when turning on/changing voltage */
int awe_slew;
};
static struct voltage_reg_t voltage_reg[]={
{
.name="vp15",
.r1=VP15_R1,
.r2=VP15_R2,
.awe_ref=LTC3589_AWE_B1DTV1_REF,
.awe_en=0,
.awe_pgood=LTC3589_AWE_PGSTAT_SD1,
.mask_pgood=1,
.awe_slew=LTC3589_AWE_VCCR_SLEW_SD1
},
{
.name="vcc_sens01",
.r1=VCC_SENS01_R1,
.r2=VCC_SENS01_R2,
.awe_ref=LTC3589_AWE_B2DTV1_REF,
.awe_en=LTC3589_AWE_OVEN_EN_SD2,
.awe_pgood=LTC3589_AWE_PGSTAT_SD2,
.mask_pgood=1,
.awe_slew=LTC3589_AWE_VCCR_SLEW_SD2
},
{
.name="vcc_sens23",
.r1=VCC_SENS23_R1,
.r2=VCC_SENS23_R2,
.awe_ref=LTC3589_AWE_B3DTV1_REF,
.awe_en=LTC3589_AWE_OVEN_EN_SD3,
.awe_pgood=LTC3589_AWE_PGSTAT_SD3,
.mask_pgood=1,
.awe_slew=LTC3589_AWE_VCCR_SLEW_SD3
},
{
.name="vp5",
.r1=VP5_R1,
.r2=VP5_R2,
.awe_ref=0,
.awe_en=LTC3589_AWE_OVEN_EN_BB,
.awe_pgood=LTC3589_AWE_PGSTAT_BB,
.mask_pgood=1,
.awe_slew=0
},
{
.name="vldo18",
.r1=VLDO18_R1,
.r2=VLDO18_R2,
.awe_ref=0,
.awe_en= 0,
.awe_pgood=LTC3589_AWE_PGSTAT_LDO1,
.mask_pgood=1,
.awe_slew=0
},
{
.name="vp33sens01",
.r1=-1,
.r2=33000,
.awe_ref=0,
.awe_en= -7, /* SENSPWREN0 */
.awe_pgood=0,
.mask_pgood=1,
.awe_slew=0
},
{
.name="vp33sens23",
.r1=-1,
.r2=33000,
.awe_ref=0,
.awe_en= -8, /* SENSPWREN1 */
.awe_pgood=0,
.mask_pgood=1,
.awe_slew=0
},
{
.name="mmtavcc10",
.r1=-1,
.r2=10000,
.awe_ref=0,
.awe_en= 0,
.awe_pgood=-15, /* MGTAVTTGOOD */
.mask_pgood=1,
.awe_slew=0
},
{
.name="mmtavtt12",
.r1=-1,
.r2=12000,
.awe_ref=0,
.awe_en= 0,
.awe_pgood=-15, /* MGTAVTTGOOD */
.mask_pgood=1,
.awe_slew=0
},
{
.name="vp10",
.r1=-1,
.r2=10000,
.awe_ref=-1,
.awe_en= 0,
.awe_pgood=-16, /* PGOOD18 */
.mask_pgood=1,
.awe_slew=0
},
{
.name="vp18",
.r1=-1,
.r2=18000,
.awe_ref=-2,
.awe_en= 0,
.awe_pgood=-16, /* PGOOD18 */
.mask_pgood=1,
.awe_slew=0
},
};
static struct pwr_gpio_t pwr_gpio[24]={
/* 0x20: */
{"PWR_MGB1", 0, 0, 0}, /* 1.8V margining magnitude (0 - 5%, 1 - 10%, float - 15%) */
{"PWR_MG1", 1, 0, 0}, /* 1.8V margining enable 0 - negative margining, 1 - positive margining, float - no margining */
{"PWR_MGB0", 2, 0, 0}, /* 1.0V margining magnitude (0 - 5%, 1 - 10%, float - 15%) */
{"PWR_MG0", 3, 0, 0}, /* 1.0V margining enable 0 - negative margining, 1 - positive margining, float - no margining */
{"PWR_FQ0", 4, 0, 0}, /* float - nominal frequency (should float for SS), 0 - 0.67 nominal frequency, 1 - 1.5 nominal frequency */
{"PWR_SS", 5, 0, 0}, /* Spread spectrum, 0 or float - spread spectrum disabled */
{"SENSPWREN0", 6, 0, 0}, /* 1 - enable 3.3 power to sensor connectors J6 and J7 (0 or float - disable) */
{"SENSPWREN1", 7, 0, 0}, /* 1 - enable 3.3 power to sensor connectors J8 and J9 (0 or float - disable) */
/* 0x21: */
{"NSHUTDOWN", 8, 0, 0}, /* (pulled up). 0 - shutdown, 1 normal */
{"DIS_POR", 9, 0, 0}, /* (pulled down). 0 - normal, 1 - disable POR generation on PGOOD deassertion (needed whil changing voltages) */
{ NULL, 10, 0, 0}, /* Not connected */
{ NULL, 11, 0, 0}, /* Not connected */
{ NULL, 12, 0, 0}, /* Not connected */
{ NULL, 13, 0, 0}, /* Not connected */
{"MGTAVTTGOOD",14, 0, 0}, /* (input) 1.2V linear regulator status (generated from 1.8V) */
{"PGOOD18", 15, 0, 0}, /* (input). Combines other voltages, can be monitored when DIS_POR is activated */
/* 0x25: */
{ "FAN_CTL", 16, 1, 0}, /* Fan Control, 1 - on, 0 - off */
{ "DAS_DSS", 17, 1, 0}, /* ? */
{ "DEVSLP", 18, 1, 0}, /* ? */
{ "EPGMA", 19, 1, 0}, /* ? */
{ NULL, 20, 1, 0}, /* Not connected */
{ NULL, 21, 1, 0}, /* Not connected */
{ NULL, 22, 1, 0}, /* Not connected */
{ NULL, 23, 1, 0} /* Not connected */
};
static struct device * shutdown_dev;
static int make_group (struct device *dev, const char * name,
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf),
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count));
static ssize_t simulate_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t simulate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static ssize_t outputs_all_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t configs_all_show(struct device *dev, struct device_attribute *attr, char *buf);
#if 0
static ssize_t output_state_show(struct device *dev, struct device_attribute *attr, char *buf);
#endif
static ssize_t output_en_output_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t output_en_output_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static ssize_t outputs_pgood_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t channels_en_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t channels_en_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static ssize_t channels_dis_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t channels_dis_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static ssize_t output_ref_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t output_ref_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static ssize_t pgood_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t pbad_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t enable_por_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t enable_por_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
static ssize_t gpio_10389_get(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t gpio_10389_set(struct device *dev, struct device_attribute *attr, char *buf, size_t count);
static ssize_t gpio_poweroff(struct device *dev, struct device_attribute *attr, char *buf, size_t count);
static int gpio_shutdown(struct device *dev);
static int por_ctrl(struct device *dev, int disable_por);
static int get_and_disable_por(struct device *dev, int chn_bits, int * old_dis_por);
static int reenable_por(struct device *dev);
static int wait_all_pgood(struct device *dev);
static int list_chn_bits(char * buf, int chn_bits);
static int parse_chn_bits(const char * buf);
static int get_enabled_mask(struct device *dev);
static int set_enabled_by_mask(struct device *dev, int chn_bits, int enable);
static int slew_by_mask(struct device *dev, int chn_bits);
static int get_voltage_channel(const char * name);
static int get_gpio_index_by_name(const char * name);
static int gpio_conf_by_index(struct device *dev,int gpio_index, int dir, int val);
static int get_gpio_pwr_mgx_indices(int chn, int * indices); /* chn = 0 (VP10) or 1 (VP18) */
static int get_volt_mv(struct device *dev, int chn);
static int set_volt_mv(struct device *dev, int chn, int v_mv);
static int get_enable(struct device *dev, int chn);
static int set_enable(struct device *dev, int chn, int enable);
static int get_pgood(struct device *dev, int chn);
int gpio_10389_ctrl(struct device *dev, int value);
/*
Voltages:
VP10 (on at power up, nominal 1.0V)
VP18 (on at power up, nomianl 1.8V)
VP15 (SW1, on by pinstrap, nominal 1.5V - may be reduced to 1.35 later)
VCC_SENS01 (SW2, nominal 1.8V, max 2.8V)
VCC_SENS23 (SW3, nominal 1.8V, max 2.8V)
VP5 (nominal 5.0V, not software programmed)
VLDO18 (LDO1 - always on)
VP33SENS0 - 3.3V to sensors J6,J7
VP33SESN1 - 3.3V to sensors J8,J9
MGTAVCC10 - 1.0 V, linear from VP18 (pgood controls MGTAVTT12)
MGTAVTT12 - 1.2 V, linear from VP18 (pgood available, means both)
LTC3589 used channels : LDO1, SW1, SW2, SW3, BB
TODO: Change VCC_SENS01_R1, VCC_SENS23_R1 to 787K (now 487)
*/
/* root directory */
static DEVICE_ATTR(simulate, SYSFS_PERMISSIONS, simulate_show, simulate_store);
static DEVICE_ATTR(output_state, SYSFS_PERMISSIONS & SYSFS_READONLY, outputs_all_show, NULL);
static DEVICE_ATTR(configs, SYSFS_PERMISSIONS & SYSFS_READONLY, configs_all_show, NULL);
static DEVICE_ATTR(channels_en, SYSFS_PERMISSIONS, channels_en_show, channels_en_store);
static DEVICE_ATTR(channels_dis, SYSFS_PERMISSIONS, channels_dis_show, channels_dis_store);
static DEVICE_ATTR(power_good, SYSFS_PERMISSIONS & SYSFS_READONLY, pgood_show, NULL);
static DEVICE_ATTR(power_bad, SYSFS_PERMISSIONS & SYSFS_READONLY, pbad_show, NULL);
static DEVICE_ATTR(enable_por, SYSFS_PERMISSIONS, enable_por_show, enable_por_store);
/*
* Performs power off whtough CHIP2 P0
* examples:
* 1. echo "anything" > power_off - immediate shutdown
* 2. shutdown -hP now - civilized shutdown
*/
static DEVICE_ATTR(power_off, SYSFS_PERMISSIONS , NULL, gpio_poweroff);
/*
* input is hex, set all outputs at once with a mask, 8xMSB - enable mask, 8xLSB - pin values.
* examples:
* 1. echo 0x101 > gpio_10389 - set P0 high, P1-P3 keep
* 2. echo 0xf01 > gpio_10389 - set P0 high, P1-P3 - low
* 3. echo 0x605 > gpio_10389 - P0 keep, P1 low, P2 high, P3 keep
* P7-P4 - NC, 0xX0X0 - not supported even on this driver level.
*/
static DEVICE_ATTR(gpio_10389, SYSFS_PERMISSIONS, gpio_10389_get, gpio_10389_set);
static struct attribute *root_dev_attrs[] = {
&dev_attr_simulate.attr,
&dev_attr_output_state.attr,
&dev_attr_configs.attr,
&dev_attr_channels_en.attr,
&dev_attr_channels_dis.attr,
&dev_attr_power_good.attr,
&dev_attr_power_bad.attr,
&dev_attr_enable_por.attr,
&dev_attr_power_off.attr,
&dev_attr_gpio_10389.attr,
NULL
};
static const struct attribute_group dev_attr_root_group = {
.attrs = root_dev_attrs,
.name = NULL,
};
static int make_group (struct device *dev, const char * name,
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf),
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count))
{
int retval=-1;
int index;
struct attribute **pattrs; /* array of pointers to attibutes */
struct device_attribute *dev_attrs;
struct attribute_group *attr_group;
pattrs = devm_kzalloc(dev,(ARRAY_SIZE(voltage_reg)+1)*sizeof(pattrs[0]), GFP_KERNEL);
if (!pattrs) return -ENOMEM;
dev_attrs = devm_kzalloc(dev, ARRAY_SIZE(voltage_reg)*sizeof(dev_attrs[0]), GFP_KERNEL);
if (!dev_attrs) return -ENOMEM;
attr_group = devm_kzalloc(dev, sizeof(*attr_group), GFP_KERNEL);
if (!attr_group) return -ENOMEM;
memset(dev_attrs, 0, ARRAY_SIZE(voltage_reg)*sizeof(dev_attrs[0]));
memset(attr_group, 0, sizeof(*attr_group));
for (index=0;indexname = name;
attr_group->attrs =pattrs;
dev_dbg(dev,"name=%s, &dev->kobj=0x%08x\n",attr_group->name, (int) (&dev->kobj));
if (&dev->kobj) {
retval = sysfs_create_group(&dev->kobj, attr_group);
}
return retval;
}
static ssize_t simulate_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
return sprintf(buf, "%d\n",clientdata->simulate);
}
static ssize_t simulate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
struct i2c_client *ltc3589_client= to_i2c_client(clientdata->ltc3489_dev);
sscanf(buf, "%du", &clientdata->simulate);
ltc3589_set_simulate(ltc3589_client, clientdata->simulate);
return count;
}
static ssize_t outputs_all_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn, pg;
char * cp = buf;
for (chn=0;chn0)?get_pgood(dev, chn):-1;
buf+=sprintf(buf,"%s: %s %d mV%s\n",
voltage_reg[chn].name,
get_enable(dev, chn)?"ON":"OFF",
get_volt_mv(dev, chn),
(pg==1)?", power good":((pg==0)?", power is NOT good":"")
);
}
return buf-cp;
}
static ssize_t configs_all_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn, pg;
char * cp = buf;
for (chn=0;chnattr.name);
if (chn<0) return chn;
pg=get_pgood(dev, chn);
return sprintf(buf,"%s: %s %d mV, %s\n",
voltage_reg[chn].name,
get_enable(dev, chn)?"ON":"OFF",
get_volt_mv(dev, chn),
(pg=1)?"power good":((pg==0)?"power is NOT good":"")
);
}
#endif
static ssize_t output_en_output_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn;
chn=get_voltage_channel(attr->attr.name);
if (chn<0) return chn;
return sprintf(buf,"%d\n", get_enable(dev, chn));
}
static ssize_t output_en_output_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn, enable;
chn=get_voltage_channel(attr->attr.name);
if (chn<0) return chn;
sscanf(buf, "%du", &enable);
return count;
}
static ssize_t outputs_pgood_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn;
chn=get_voltage_channel(attr->attr.name);
if (chn<0) return chn;
return sprintf(buf,"%d\n", get_pgood(dev, chn));
}
static ssize_t channels_en_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn_bits;
char * cp=buf;
chn_bits=get_enabled_mask(dev);
if (chn_bits<0) return chn_bits;
buf+=list_chn_bits(buf, chn_bits);
buf+=sprintf(buf,"\n");
return buf-cp;
}
/* also slews DAC(s) if applilcable. Call after changing voltage on enabled channels */
static ssize_t channels_en_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn_bits,rc,old_dis_por,pre_disabled;
chn_bits=parse_chn_bits(buf);
pre_disabled=get_and_disable_por(dev, chn_bits, &old_dis_por);
if (pre_disabled<0) return pre_disabled;
rc=slew_by_mask(dev, chn_bits); /* slew if needed - before enabling, waits for slew over */
if (rc<0) return rc;
rc=set_enabled_by_mask(dev, chn_bits, 1);
if (rc<0) return rc;
if (pre_disabled && (old_dis_por==0)){
rc=reenable_por(dev); /* will wait pgood */
if (rc<0) return rc;
}
return count;
}
static ssize_t channels_dis_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn_bits;
char * cp=buf;
chn_bits=get_enabled_mask(dev);
if (chn_bits<0) return chn_bits;
chn_bits=~chn_bits;
buf+=list_chn_bits(buf, chn_bits);
buf+=sprintf(buf,"\n");
return buf-cp;
}
static ssize_t channels_dis_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn_bits,rc;
chn_bits=parse_chn_bits(buf);
rc=set_enabled_by_mask(dev, chn_bits, 0);
if (rc<0) return rc;
return count;
}
static ssize_t output_ref_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn;
chn=get_voltage_channel(attr->attr.name);
if (chn<0) return chn;
return sprintf(buf,"%d\n",get_volt_mv(dev, chn));
}
static ssize_t output_ref_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn, v_mv;
int rc,old_dis_por,pre_disabled;
chn=get_voltage_channel(attr->attr.name);
if (chn<0) return chn;
/* if output was enabled, and pgood negation may cause POR, disable POR (later restore) */
if (get_enable(dev,chn)) pre_disabled=get_and_disable_por(dev, 1<0) pgood_bits |= (1<pwr_gpio[gpio_disable_por_index].out_val)?0:1);
}
/* When enable_por is set to 1, it first waits for PGOOD and does not enable POR on error */
static ssize_t enable_por_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int en_por,rc;
sscanf(buf, "%du", &en_por);
if (en_por) rc=reenable_por(dev); /* will wait pgood, then enable POR */
else rc=por_ctrl(dev, 1); /* disable POR */
if (rc<0) return rc;
return count;
}
static ssize_t gpio_10389_set(struct device *dev, struct device_attribute *attr, char *buf, size_t count)
{
int result;
int value;
sscanf(buf, "%i", &value);
result = gpio_10389_ctrl(dev,value);
if (result<0) return result;
return count;
}
/* hardcoded to be [19:16] in pwr_gpio */
static ssize_t gpio_10389_get(struct device *dev, struct device_attribute *attr, char *buf)
{
int i;
unsigned int res=0;
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
// just 4 of them
for (i=16;i<20;i++){
if (pwr_gpio[i].label){
res += ((clientdata->pwr_gpio[i].out_val)?1:0)<<(i-16);
}
}
return sprintf(buf,"%02x\n",res);
}
static ssize_t gpio_poweroff(struct device *dev, struct device_attribute *attr, char *buf, size_t count)
{
int rc=gpio_shutdown(dev);
if (rc<0) return rc;
return count;
}
int gpio_shutdown(struct device *dev)
{
int gpio_shutdown_index=get_gpio_index_by_name("NSHUTDOWN");
if (gpio_shutdown_index<0) return gpio_shutdown_index;
return gpio_conf_by_index(dev, gpio_shutdown_index, 1, 0);
}
//TODO: test mutex_lock/unlock works
int gpio_10389_ctrl(struct device *dev, int value){
int i, res;
int val = 0;
mutex_lock(&gpio_10389_lock);
for(i=16;i<20;i++){
if ((value>>(i-8))&0x1){
val = (value>>(i-16))&0x1;
//res = gpio_conf_by_index(dev, i, 1, ~val);
//if (res<0) return res;
res = gpio_conf_by_index(dev, i, 1, val);
if (res<0) return res;
}
}
mutex_unlock(&gpio_10389_lock);
return 0;
}
int gpio_10389_control(int value){
gpio_10389_ctrl(shutdown_dev,value);
return 0;
}
EXPORT_SYMBOL_GPL(gpio_10389_control);
int por_ctrl(struct device *dev, int disable_por)
{
int gpio_disable_por_index=get_gpio_index_by_name("DIS_POR");
if (gpio_disable_por_index<0) return gpio_disable_por_index;
return gpio_conf_by_index(dev, gpio_disable_por_index, 1, disable_por);
}
/*
* disable POR (if needed) before changing value or enabling one of the voltages
* chn_bits - 1 bit per channel
*/
static int get_and_disable_por(struct device *dev, int chn_bits, int * old_dis_por)
{
int rc,chn;
int gpio_disable_por_index;
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
gpio_disable_por_index=get_gpio_index_by_name("DIS_POR");
if (gpio_disable_por_index<0) return gpio_disable_por_index;
old_dis_por[0]=clientdata->pwr_gpio[gpio_disable_por_index].out_val;
for (chn=0;chn=ARRAY_SIZE(voltage_reg)) return 0; /* POR was not required to be disabled */
rc = gpio_conf_by_index(dev, gpio_disable_por_index, 1, 1); /* out turn on "disable_por" */
if (rc<0) return rc;
return 1; /* pgood-based POR was disabled (could already be disabled)*/
}
/* call if POR was diasabled before changing voltage (value or enabling), after waiting for pgood*/
static int reenable_por(struct device *dev)
{
int gpio_disable_por_index, rc;
gpio_disable_por_index=get_gpio_index_by_name("DIS_POR");
if (gpio_disable_por_index<0) return gpio_disable_por_index;
if (((rc=wait_all_pgood(dev)))<0) return rc;
return gpio_conf_by_index(dev, gpio_disable_por_index, 1, 0); /* out turn off "disable_por" */
}
static int wait_all_pgood(struct device *dev)
{
int ntry,chn,all_good=0;
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
for (ntry=0;ntrypgoot_timeout;ntry++){
all_good=1;
for (chn=0;chn0) && (get_pgood(dev,chn)!=1)){ /* enabled or always enabled */
all_good=0;
break;
}
}
if (all_good) break; /* all enabled channels that have pgood control are good */
}
if (!all_good) return -EAGAIN;
return 0;
}
static int list_chn_bits(char * buf, int chn_bits)
{
int chn;
char * cp=buf;
for (chn=0;chn0) en_mask|= (1<0){
awe |= voltage_reg[chn].awe_en;
}
}
awe &= 0xff; /* just WE mask */
if (awe){
dev_dbg(dev,"set_enabled_by_mask(), cumulative awe=0x%x\n",awe);
ltc3589_client = to_i2c_client(clientdata->ltc3489_dev);
oven=ltc3589_read_field (ltc3589_client, LTC3589_AWE_OVEN);
if (oven<0) return oven;
if (enable) oven |= awe;
else oven &= ~awe;
return ltc3589_write_field (ltc3589_client, oven, LTC3589_AWE_OVEN);
}
return 0;
}
static int slew_by_mask(struct device *dev, int chn_bits)
{
/* assuming all slew bits in LTC3589 to be in a single register (LTC3589_AWE_OVEN) */
int chn, slew=0,rc,ntry;
u32 adwe;
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
struct i2c_client *ltc3589_client;
ltc3589_client = to_i2c_client(clientdata->ltc3489_dev);
dev_dbg(dev,"slew_by_mask(dev,0x%x)\n",chn_bits);
for (chn=0;chn%d (slew = 0x%x)\n",adwe,rc,slew);
if (rc<0) return rc;
/* wait slew over */
for (ntry=0;ntrypgoot_timeout;ntry++){
rc=ltc3589_read_field(ltc3589_client, LTC3589_AWE_VCCR);
dev_dbg(dev,"slew_by_mask():ltc3589_read_field(ltc3589_client, 0x%x)->0x%x(%d)\n",LTC3589_AWE_VCCR,rc,rc);
if (rc<0) return rc;
if ((rc & slew) ==0 ) break;
}
if (ntry>=clientdata->pgoot_timeout) return -EAGAIN;
}
return 0;
}
/* name should either completely match, or have "_*" suffix */
static int get_voltage_channel(const char * name)
{
int i;
for (i=0;i=ARRAY_SIZE(clientdata->pwr_gpio))) return -EINVAL;
if ((clientdata->pwr_gpio[gpio_index].dir==dir) && ((clientdata->pwr_gpio[gpio_index].out_val==val) || (dir==0))){
dev_dbg(dev,"GPIO#%d(index=%d) did not change: old dir=%d, new dir=%d, old val = %d, new val=%d\n",
clientdata->pwr_gpio[gpio_index].pin,
gpio_index,
clientdata->pwr_gpio[gpio_index].dir,
dir,
clientdata->pwr_gpio[gpio_index].out_val,
val);
return 0;
}
clientdata->pwr_gpio[gpio_index].dir=dir?1:0;
clientdata->pwr_gpio[gpio_index].out_val=val?1:0;
if (clientdata->pwr_gpio[gpio_index].dir){
if (!clientdata->simulate) rc=gpio_direction_output(clientdata->pwr_gpio[gpio_index].pin, clientdata->pwr_gpio[gpio_index].out_val);
dev_dbg(dev,"gpio_direction_output(%d,%d)->%d\n",clientdata->pwr_gpio[gpio_index].pin, clientdata->pwr_gpio[gpio_index].out_val,rc);
} else {
if (!clientdata->simulate) rc=gpio_direction_input(clientdata->pwr_gpio[gpio_index].pin);
dev_dbg(dev,"gpio_direction_input(%d)->%d\n",clientdata->pwr_gpio[gpio_index].pin,rc);
}
return rc;
}
static int get_gpio_pwr_mgx_indices(int chn, int * indices) /* chn = 0 (VP10) or 1 (VP18) */
{
indices[0]=get_gpio_index_by_name(chn?"PWR_MG1": "PWR_MG0");
indices[1]=get_gpio_index_by_name(chn?"PWR_MGB1":"PWR_MGB0");
return ((indices[0]>=0) && (indices[1]>=0))?0:-EINVAL;
}
/* calculate output voltage in mV */
static int get_volt_mv(struct device *dev, int chn)
{
int v_mv,ref,rc;
int pwr_mg_indices[2];
s64 num;
struct i2c_client *ltc3589_client;
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
if ((chn<0) || (chn>=ARRAY_SIZE(voltage_reg))) return -EINVAL;
if (voltage_reg[chn].r1<=0) {
if (voltage_reg[chn].awe_ref<0) { /* vp10, vp18*/
rc= get_gpio_pwr_mgx_indices(-1-voltage_reg[chn].awe_ref,pwr_mg_indices); /* chn = 0 (VP10) or 1 (VP18) */
if (rc<0) return rc;
if (clientdata->pwr_gpio[pwr_mg_indices[0]].dir==0) ref=0;
else if (clientdata->pwr_gpio[pwr_mg_indices[0]].out_val) ref=1;
else ref=-1;
if (ref) {
if (clientdata->pwr_gpio[pwr_mg_indices[1]].dir==0) ref*=15;
else if (clientdata->pwr_gpio[pwr_mg_indices[1]].out_val) ref*=10;
else ref*= 5;
}
v_mv=(voltage_reg[chn].r2*(100+ref)*2+10)/2000;
} else { /* vp33sens01, vp33sens23, mmtavcc10, mmtavtt12 */
v_mv=(voltage_reg[chn].r2+5)/10;
}
} else if (voltage_reg[chn].awe_ref==0){ /* VP5, vldo18 */
#if 0
v_mv=(REF_FIXED_TENTH_MV*(voltage_reg[chn].r1+voltage_reg[chn].r2)+ 5*voltage_reg[chn].r2)/(10*voltage_reg[chn].r2);
#endif
num=((u64) REF_FIXED_TENTH_MV)* (voltage_reg[chn].r1+voltage_reg[chn].r2)+ 5*voltage_reg[chn].r2;
v_mv=(int) div64_u64(num, 10*voltage_reg[chn].r2);
dev_dbg(dev,"chn=%d REF_FIXED_TENTH_MV=%d .r1=%d .r2=%d v_mv=%d\n",chn, REF_FIXED_TENTH_MV,voltage_reg[chn].r1,voltage_reg[chn].r2,v_mv);
} else { /* vp15, vcc_sens01,vcc_sens23 */
ltc3589_client = to_i2c_client(clientdata->ltc3489_dev);
ref=ltc3589_read_field(ltc3589_client, voltage_reg[chn].awe_ref);
if (ref<0) return ref;
num=(REF_VAR_0_TENTH_MV+ REF_VAR_STEP_TENTH_MV* ref);
num=num*(voltage_reg[chn].r1+voltage_reg[chn].r2)+ 5*voltage_reg[chn].r2;
v_mv=div64_u64(num, 10*voltage_reg[chn].r2);
dev_dbg(dev,"chn=%d ref=%d .r1=%d .r2=%d v_mv=%d\n",chn, ref,voltage_reg[chn].r1,voltage_reg[chn].r2,v_mv);
}
return v_mv;
}
/* 0 - OK, <0 - error */
/* does not iclude disabling/re-enabling PoR */
static int set_volt_mv(struct device *dev, int chn, int v_mv)
{
int rc,index,d;
s64 num;
int pwr_mg_indices[2];
struct i2c_client *ltc3589_client;
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
if ((chn<0) || (chn>=ARRAY_SIZE(voltage_reg))) return -EINVAL;
dev_dbg(dev,"set_volt_mv(dev,%d,%d),.r1=%d\n",chn,v_mv,voltage_reg[chn].r1);
if (voltage_reg[chn].r1<=0) {
if (voltage_reg[chn].awe_ref<0) { /* vp10, vp18*/
index=(400*v_mv+voltage_reg[chn].r2)/(2*voltage_reg[chn].r2);
dev_dbg(dev,"chn=%d v_mv=%d index=%d .r1=%d .r2=%d\n",chn, v_mv, index,voltage_reg[chn].r1,voltage_reg[chn].r2);
if ((index<17) || (index>23)) {
dev_err(dev,"specified voltage for %s is not in the range %dmV to %d mV\n", voltage_reg[chn].name,
(17*voltage_reg[chn].r2)/200,(23*voltage_reg[chn].r2)/200);
return -EINVAL;
}
/* disable -> chnage -> enable (if needed) */
rc= get_gpio_pwr_mgx_indices(-1-voltage_reg[chn].awe_ref,pwr_mg_indices); /* chn = 0 (VP10) or 1 (VP18) */
if (rc<0) return rc;
rc = gpio_conf_by_index(dev,pwr_mg_indices[0], 0, 0); /* disable margining */
if (rc < 0)return rc;
if (index !=20){
/* set margining absolute value */
switch (index) {
case 17:
case 23:
rc = gpio_conf_by_index(dev,pwr_mg_indices[1], 0, 0); /* float: +/- 15% */
break;
case 18:
case 22:
rc = gpio_conf_by_index(dev,pwr_mg_indices[1], 1, 1); /* out 1: +/- 10% */
break;
case 19:
case 21:
rc = gpio_conf_by_index(dev,pwr_mg_indices[1], 1, 0); /* out 0: +/- 5% */
break;
}
if (rc < 0)return rc;
/* set margining sign */
if (index >20) rc = gpio_conf_by_index(dev,pwr_mg_indices[0], 1, 1); /* out 1: positive margining */
else rc = gpio_conf_by_index(dev,pwr_mg_indices[0], 1, 0); /* out 0: negative margining */
if (rc < 0)return rc;
}
} else { /* vp33sens01, vp33sens23, mmtavcc10, mmtavtt12 */
return -EINVAL; /* voltage not regulated */
}
} else if (voltage_reg[chn].awe_ref==0){ /* VP5, vldo18 */
return -EINVAL; /* voltage not regulated */
} else { /* vp15, vcc_sens01,vcc_sens23 */
ltc3589_client = to_i2c_client(clientdata->ltc3489_dev);
#if 0
index=((10*v_mv*voltage_reg[chn].r2) -(REF_VAR_0_TENTH_MV-REF_VAR_STEP_TENTH_MV/2)*(voltage_reg[chn].r1+voltage_reg[chn].r2))/
((voltage_reg[chn].r1+voltage_reg[chn].r2)*REF_VAR_STEP_TENTH_MV);
num=(10*v_mv*voltage_reg[chn].r2) -(REF_VAR_0_TENTH_MV-REF_VAR_STEP_TENTH_MV/2);
num*=(voltage_reg[chn].r1+voltage_reg[chn].r2);
index=div64_u64(num, (voltage_reg[chn].r1+voltage_reg[chn].r2)*REF_VAR_STEP_TENTH_MV);
#endif
num= (10LL*v_mv*voltage_reg[chn].r2) - ((s64) (voltage_reg[chn].r1+voltage_reg[chn].r2))*REF_VAR_0_TENTH_MV;
d= REF_VAR_STEP_TENTH_MV*(voltage_reg[chn].r1+voltage_reg[chn].r2);
index=div64_u64(num +(d>>1), d);
dev_dbg(dev,"chn=%d v_mv=%d index=%d .r1=%d .r2=%d\n",chn, v_mv, index,voltage_reg[chn].r1,voltage_reg[chn].r2);
dev_dbg(dev,"index=%d\n",index);
if ((index<0) || (index>31)){
dev_err(dev,"chn=%d v_mv=%d index=%d .r1=%d .r2=%d\n",chn, v_mv, index,voltage_reg[chn].r1,voltage_reg[chn].r2);
dev_err(dev,"REF_VAR_0_TENTH_MV=%d REF_VAR_STEP_TENTH_MV=%d\n",REF_VAR_0_TENTH_MV,REF_VAR_STEP_TENTH_MV);
dev_err(dev,"specified voltage for %s is not in the range %dmV to %d mV\n", voltage_reg[chn].name,
(int) div64_u64((((u64)(REF_VAR_0_TENTH_MV+REF_VAR_STEP_TENTH_MV* 0))*(voltage_reg[chn].r1+voltage_reg[chn].r2)+5*voltage_reg[chn].r2),
10*voltage_reg[chn].r2),
(int) div64_u64((((u64)(REF_VAR_0_TENTH_MV+REF_VAR_STEP_TENTH_MV*31))*(voltage_reg[chn].r1+voltage_reg[chn].r2)+5*voltage_reg[chn].r2),
10*voltage_reg[chn].r2));
return -EINVAL;
}
dev_dbg(dev,"ltc3589_client->name= %s\n", ltc3589_client->name);
rc=ltc3589_write_field(ltc3589_client, index,voltage_reg[chn].awe_ref);
if (rc<0) return rc;
}
return 0;
}
/* get output enable state */
static int get_enable(struct device *dev, int chn)
{
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
struct i2c_client *ltc3589_client= to_i2c_client(clientdata->ltc3489_dev);
if ((chn<0) || (chn>=ARRAY_SIZE(voltage_reg))) return -EINVAL;
if (voltage_reg[chn].awe_en==0) {
return 2; /* always on */
} else if (voltage_reg[chn].awe_en>0){
if (clientdata->pinstrapped_oven & voltage_reg[chn].awe_en) return 1; /* pin-strapped on bit */
return ltc3589_read_field(ltc3589_client, voltage_reg[chn].awe_en);
} else {
return (clientdata->pwr_gpio[-1-voltage_reg[chn].awe_en].dir && clientdata->pwr_gpio[-1-voltage_reg[chn].awe_en].out_val)?1:0;
}
}
/* set output enable state */
static int set_enable(struct device *dev, int chn, int enable)
{
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
struct i2c_client *ltc3589_client= to_i2c_client(clientdata->ltc3489_dev);
if ((chn<0) || (chn>=ARRAY_SIZE(voltage_reg))) return -EINVAL;
if (voltage_reg[chn].awe_en==0) {
return -EINVAL; /* always on, not controlled */
} else if (voltage_reg[chn].awe_en>0){
return ltc3589_write_field(ltc3589_client, enable, voltage_reg[chn].awe_en);
} else {
return gpio_conf_by_index(dev,-1-voltage_reg[chn].awe_en, 1, enable);
}
}
/* get power good state */
static int get_pgood(struct device *dev, int chn)
{
int rc;
struct elphel393_pwr_data_t *clientdata=platform_get_drvdata(to_platform_device(dev));
struct i2c_client *ltc3589_client= to_i2c_client(clientdata->ltc3489_dev);
if ((chn<0) || (chn>=ARRAY_SIZE(voltage_reg))) return -EINVAL;
if (voltage_reg[chn].awe_pgood==0) {
if (((rc=get_enable(dev,chn)))<0) return rc; /* 0 - disabled */
return 2; /* no status available */
} else if (voltage_reg[chn].awe_pgood>0){
return ltc3589_read_field(ltc3589_client, voltage_reg[chn].awe_pgood);
} else {
/* return gpio_get_value(clientdata->pwr_gpio[-1-voltage_reg[chn].awe_pgood].pin); */
return gpio_get_value_cansleep(clientdata->pwr_gpio[-1-voltage_reg[chn].awe_pgood].pin);
}
}
static int elphel393_pwr_sysfs_register(struct platform_device *pdev)
{
int retval=0;
struct device *dev = &pdev->dev;
if (&dev->kobj) {
if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval;
if (((retval = make_group (dev, "voltages_mv", output_ref_show, output_ref_store)))<0) return retval;
if (((retval = make_group (dev, "outputs_en", output_en_output_show, output_en_output_store)))<0) return retval;
if (((retval = make_group (dev, "outputs_pgood", outputs_pgood_show, NULL)))<0) return retval;
}
return retval;
}
static void elphel393_pwr_init_of_i2caddr(struct platform_device *pdev)
{
const __be32 * config_data;
int len,i;
struct device_node *node = pdev->dev.of_node;
struct elphel393_pwr_data_t *clientdata = platform_get_drvdata(pdev);
if (node) {
config_data = of_get_property(node, "elphel393_pwr,i2c_chips", &len);
if (config_data){
len /= sizeof(*config_data);
dev_dbg(&pdev->dev,"Found %d items in 'elphel393_pwr,i2c_chips' in the Device Tree\n",len);
if (len!= ARRAY_SIZE(clientdata->chip_i2c_addr)){
dev_err(&pdev->dev,"Got %d items in 'elphel393_pwr,i2c_chips', expected %d\n",len,ARRAY_SIZE(clientdata->chip_i2c_addr));
return;
}
for (i=0;ichip_i2c_addr[i]=be32_to_cpup(&config_data[i]);
}
}
}
static void elphel393_pwr_init_of(struct platform_device *pdev)
{
const __be32 * config_data;
const char * config_string;
char str[40];
int len,chn,pre_disabled,old_dis_por,rc,chn_bits;
struct device_node *node = pdev->dev.of_node;
struct elphel393_pwr_data_t *clientdata = platform_get_drvdata(pdev);
struct i2c_client *ltc3589_client= to_i2c_client(clientdata->ltc3489_dev);
if (node) {
/* find resistor values */
for (chn=0;chn0)){
dev_dbg(&pdev->dev,"Found %s=<%d>\n",str,be32_to_cpup(&config_data[0]));
voltage_reg[chn].r1=be32_to_cpup(&config_data[0]);
}
sprintf(str,"elphel393_pwr,%s.r2",voltage_reg[chn].name);
config_data = of_get_property(node, str, &len);
if (config_data && (len>0)){
dev_dbg(&pdev->dev,"Found %s=<%d>\n",str,be32_to_cpup(&config_data[0]));
voltage_reg[chn].r2=be32_to_cpup(&config_data[0]);
}
}
/* which channels are enabled by pin-strapping */
config_data = of_get_property(node, "elphel393_pwr,pinstrapped_oven", &len);
if (config_data && (len>0)){
dev_dbg(&pdev->dev,"Found elphel393_pwr,pinstrapped_oven=<%d>\n",be32_to_cpup(&config_data[0]));
clientdata->pinstrapped_oven=be32_to_cpup(&config_data[0]);
}
/* debug mode - simulate only, no actual power supply control */
config_data = of_get_property(node, "elphel393_pwr,simulate", &len);
if (config_data && (len>0)){
dev_dbg(&pdev->dev,"Found elphel393_pwr,simulate=<%d>\n",be32_to_cpup(&config_data[0]));
clientdata->simulate=config_data[0]?1:0;
ltc3589_set_simulate(ltc3589_client, clientdata->simulate);
}
/* disable output voltages (not likely to be needed - maybe for warm reboot) */
config_string = of_get_property(node, "elphel393_pwr,channels_disable", &len);
if (config_string){
dev_dbg(&pdev->dev,"Found elphel393_pwr,channels_disable=\"%s\"\n",config_string);
chn_bits=parse_chn_bits(config_string);
rc=set_enabled_by_mask(&pdev->dev, chn_bits, 0);
if (rc<0) return;
}
/* set output voltages (target voltages, in mV) */
for (chn=0;chn0)){
dev_dbg(&pdev->dev,"Found %s=<%d>\n",str,be32_to_cpup(&config_data[0]));
if (get_enable(&pdev->dev,chn)) pre_disabled=get_and_disable_por(&pdev->dev, 1<dev,"pre_disabled=%d\n",pre_disabled);
rc=set_volt_mv(&pdev->dev, chn,be32_to_cpup(&config_data[0]));
dev_dbg(&pdev->dev,"set_volt_mv()->%d\n",rc);
if (rc<0) return;
if (pre_disabled && (old_dis_por==0)){
rc=reenable_por(&pdev->dev); /* will wait pgood */
if (rc<0){
dev_err(&pdev->dev,"Timeout during wait for power good after chnging voltage for %s before re-enabling POR on power loss\n",\
voltage_reg[chn].name);
return;
}
}
}
}
/* enable output voltages */
config_string = of_get_property(node, "elphel393_pwr,channels_enable", &len);
if (config_string){
dev_dbg(&pdev->dev,"Found elphel393_pwr,channels_enable=\"%s\"\n",config_string);
chn_bits=parse_chn_bits(config_string);
pre_disabled=get_and_disable_por(&pdev->dev, chn_bits, &old_dis_por);
if (pre_disabled<0) return;
rc=slew_by_mask(&pdev->dev, chn_bits); /* slew if needed - before enabling, waits for slew over */
if (rc<0) {
dev_err(&pdev->dev,"Timeout during wait for slew over\n");
return;
}
rc=set_enabled_by_mask(&pdev->dev, chn_bits, 1);
if (rc<0) return;
if (pre_disabled && (old_dis_por==0)){
rc=reenable_por(&pdev->dev); /* will wait pgood */
if (rc<0) {
dev_err(&pdev->dev,"Timeout during wait for power good before re-enabling POR on power loss\n");
return;
}
}
}
}
dev_info(&pdev->dev,"elphel393_pwr configuration done\n");
}
static int device_by_i2c_addr_match(struct device *dev, void *data)
{
struct i2c_client *client = to_i2c_client(dev);
int *addr = (int *)data;
dev_dbg(dev,"addr_given=0x%02x, addr found=0x%02x\n",addr[0],(int) client->addr);
return i2c_verify_client(dev) && (client->addr==addr[0]);
}
static struct device * find_device_by_i2c_addr(int address)
{
return bus_find_device(&i2c_bus_type, NULL, &address, device_by_i2c_addr_match);
}
static int i2c_addr_gpiochip_match(struct gpio_chip *chip, void *data)
{
struct i2c_client *client = to_i2c_client(chip->dev);
int *addr = (int *)data;
dev_dbg(chip->dev,"addr_given=0x%02x, addr found=0x%02x\n",addr[0],(int) client->addr);
return i2c_verify_client(chip->dev) && (client->addr==addr[0]);
}
static void shutdown(void){
gpio_shutdown(shutdown_dev);
}
static int elphel393_pwr_probe(struct platform_device *pdev)
{
struct gpio_chip *chip;
int i,rc;
struct device *tmp_dev;
struct i2c_client *tmp_i2c_client;
int base[3],dir[3],out_val[3];
struct i2c_client *ltc3589_client;
struct elphel393_pwr_data_t *clientdata = NULL;
struct gpio_desc *desc;
shutdown_dev = &pdev->dev;
dev_info(&pdev->dev,"Probing elphel393-pwr\n");
clientdata = devm_kzalloc(&pdev->dev, sizeof(*clientdata), GFP_KERNEL);
clientdata->pgoot_timeout=DEAFULT_TIMEOUT;
clientdata->pinstrapped_oven=PINSTRAPPED_OVEN;
clientdata->chip_i2c_addr[0]=GPIO_CHIP1_ADDR;
clientdata->chip_i2c_addr[1]=GPIO_CHIP2_ADDR;
clientdata->chip_i2c_addr[2]=GPIO_10389_U4_ADDR;
clientdata->chip_i2c_addr[3]=LTC3589_ADDR;
platform_set_drvdata(pdev, clientdata);
elphel393_pwr_sysfs_register(pdev);
// elphel393_pwr_init_of(pdev);
elphel393_pwr_init_of_i2caddr(pdev);
mutex_init(&clientdata->lock);
/* locate GPIO chips by i2c address */
for (i=0;i<3;i++){
chip = gpiochip_find(&clientdata->chip_i2c_addr[i], i2c_addr_gpiochip_match);
if (chip!=NULL) {
base[i]=chip->base;
dev_dbg(&pdev->dev,"Found gpio_chip with i2c_addr=0x%02x, label=%s, base=0x%x\n",clientdata->chip_i2c_addr[i],chip->label,base[i]);
tmp_dev=find_device_by_i2c_addr(clientdata->chip_i2c_addr[i]);
tmp_i2c_client = to_i2c_client(tmp_dev);
//chip0 and chip1 have registers, chip2 - no regs, only outputs
if (i<2){
//need to invert direction register value
dir[i]=i2c_smbus_read_byte_data(tmp_i2c_client, 0x3)^0xff;
out_val[i]=i2c_smbus_read_byte_data(tmp_i2c_client, 0x1)&0xff;
//dir[i]=0x0;
//out_val[i]=0x0;
pr_debug("chip %d: dir=%d val=%d\n",i,dir[i],out_val[i]);
}else{
dir[i]=0xff;
out_val[i]=i2c_smbus_read_byte(tmp_i2c_client)&0xff;
out_val[i]=0x0;
}
}else{
base[i]=NULL;
}
}
if (base[2]==NULL){
device_remove_file(&pdev->dev, &dev_attr_gpio_10389);
}
for (i=0;i>3]!=NULL) if (pwr_gpio[i].label){
clientdata->pwr_gpio[i].label=pwr_gpio[i].label;
clientdata->pwr_gpio[i].pin=base[i>>3]+(i & 7);
pr_debug("setting gpio %d struct to dir=%d val=%d\n",i,(dir[i>>3]>>(i&7))&0x1,(out_val[i>>3]>>(i&7))&0x1);
clientdata->pwr_gpio[i].dir = (dir[i>>3]>>(i&7))&0x1;
clientdata->pwr_gpio[i].out_val = (out_val[i>>3]>>(i&7))&0x1;
//if (i<16) clientdata->pwr_gpio[i].dir=0; /* input */
//else clientdata->pwr_gpio[i].dir=1; /* output */
//if (i<16) clientdata->pwr_gpio[i].out_val=0;
//else clientdata->pwr_gpio[i].out_val=1;
//clientdata->pwr_gpio[i].out_val=0;
rc=gpio_request(clientdata->pwr_gpio[i].pin, clientdata->pwr_gpio[i].label);
if (rc<0){
dev_err(&pdev->dev," Failed to get GPIO[%d] with label %s\n",clientdata->pwr_gpio[i].pin,clientdata->pwr_gpio[i].label);
return rc;
} else {
dev_dbg(&pdev->dev,"Confirmed request GPIO[%d] with label %s\n",clientdata->pwr_gpio[i].pin,clientdata->pwr_gpio[i].label);
}
}
}
/* find ltc3589 */
clientdata->ltc3489_dev=find_device_by_i2c_addr(LTC3589_ADDR);
if (!clientdata->ltc3489_dev){
dev_err(&pdev->dev," Failed to find LTC3489 with i2c address 0x%02x\n",LTC3589_ADDR);
return -EIO;
}
ltc3589_client = to_i2c_client(clientdata->ltc3489_dev);
dev_dbg(&pdev->dev,"Located %s with i2c address 0x%02x\n",ltc3589_client->name,LTC3589_ADDR);
dev_dbg(&pdev->dev,"LTC3589 status= 0x%02x\n",ltc3589_read_field(ltc3589_client, LTC3589_AWE_PGSTAT));
elphel393_pwr_init_of(pdev);
/*
* 1. pm_power_off - arch/arm/kernel/process.c - called in the end of halt if power off requested
* 2. To perform a proper system shutdown with power off ("shutdown -hP now") this function is set here.
*/
pm_power_off = shutdown;
/*
if (base[2]!=NULL){
//turn off PCA9571
gpio_10389_ctrl(&pdev->dev, 0xf0f);
gpio_10389_ctrl(&pdev->dev, 0xf00);
}
*/
return 0;
}
static int elphel393_pwr_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev,"Removing elphel393-pwr");
return 0;
}
static struct of_device_id elphel393_pwr_of_match[] = {
{ .compatible = "elphel,elphel393-pwr-1.00", },
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, elphel393_pwr_of_match);
static struct platform_driver elphel393_pwr = {
.probe = elphel393_pwr_probe,
.remove = elphel393_pwr_remove,
.driver = {
.name = "elphel393-pwr",
.owner = THIS_MODULE,
.of_match_table = elphel393_pwr_of_match,
.pm = NULL, /* power management */
},
};
module_platform_driver(elphel393_pwr);
MODULE_AUTHOR("Andrey Filippov ");
MODULE_DESCRIPTION("Elphel 10393 power supply control");
MODULE_LICENSE("GPL");
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/exif.h 0000664 0000000 0000000 00000001776 12766141417 0025502 0 ustar 00root root 0000000 0000000 #ifndef __F_EXIF__H_
#define __F_EXIF__H_
extern unsigned char exif_header[];
int exif_header_length(void);
#define EXIF_OFFSET 4
#define EXIF_FIRMWARE 0xC4
#define EXIF_FIRMWARE_LEN 27
//#define EXIF_DATE_TIME 0x7A
#define EXIF_DATE_TIME 0xE0
#define EXIF_DATE_TIME_LEN 20
//#define EXIF_ARTIST 0x8E
#define EXIF_ARTIST 0xF4
#define EXIF_ARTIST_LEN 18
//#define EXIF_DATE_TIME_OR 0xCA
#define EXIF_DATE_TIME_OR 0x0138
#define EXIF_DATE_TIME_OR_LEN 20
//#define EXIF_SUBSEC_OR 0xDE
#define EXIF_SUBSEC_OR 0x014C
#define EXIF_SUBSEC_OR_LEN 7
//#define EXIF_EXP 0xE6
#define EXIF_EXP 0x0130
#define EXIF_EXP_LEN 8
#define EXIF_IMAGE_ID 0x6E
#define EXIF_IMAGE_ID_LEN 64
struct exif_desc_t {
unsigned char date_time[EXIF_DATE_TIME_LEN];
unsigned char date_time_or[EXIF_DATE_TIME_OR_LEN];
unsigned char subsec[EXIF_SUBSEC_OR_LEN];
unsigned char artist[EXIF_ARTIST_LEN];
unsigned char firmware[EXIF_FIRMWARE_LEN];
unsigned long exp[2];
};
extern struct exif_desc_t exif_desc;
#endif //__F_EXIF__H_
linux-elphel-27b66fd7510873204ee94b725c25d7ea0f590915/src/drivers/elphel/exif393.c 0000664 0000000 0000000 00000063113 12766141417 0025725 0 ustar 00root root 0000000 0000000 /*!****************************************************************************//**
* @file exif393.c
* @brief Drivers for Exif manipulation
* @copyright Copyright (C) 2008-2016 Elphel, Inc.
* @par License
* 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 .
*/
//copied from cxi2c.c - TODO:remove unneeded
#include
#include
#include
#include
#include
#include
#include
#include
//#include
#include
#include // dev_*
//#include
//#include
#include // endians
//#include
#include
#include
#include
#include