#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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/clock10359.h 0000664 0000000 0000000 00000002116 12752235123 0026315 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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/command_sequencer.c 0000664 0000000 0000000 00000012015 12752235123 0030302 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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/command_sequencer.h 0000664 0000000 0000000 00000002313 12752235123 0030307 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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/cxi2c.c 0000664 0000000 0000000 00000141014 12752235123 0025624 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 X3X3_I2C_CTRL 0 // control/reset i2c
//#define X3X3_I2C_8_AINC 1 // 8bit registers, autoincement while read/write
//#define X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C_16_AINC :
bus=0;
break;
case X3X3_I2C1_RAW:
case X3X3_I2C1_8_AINC :
case X3X3_I2C1_16_AINC :
bus=1;
break;
case X3X3_I2C_ENABLE:
case X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C1_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C1_8_AINC :
inode->i_size=128*256;
burst_sizes[p]=1; //!fix for PHP read (always 8192) -> 1 byte/read
break;
case X3X3_I2C_16_AINC :
case X3X3_I2C1_16_AINC :
inode->i_size=256*256;
burst_sizes[p]=2; //!fix for PHP read (always 8192) -> 2 bytes/read
break;
case X3X3_I2C_CTRL :
inode->i_size=sizeof(bitdelays);
break;
case X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C_16_AINC :
bus=0;
break;
case X3X3_I2C1_RAW:
case X3X3_I2C1_8_AINC :
case X3X3_I2C1_16_AINC :
bus=1;
break;
case X3X3_I2C_ENABLE:
case X3X3_I2C_CTRL :
bus=-1;
break;
}
D(printk("xi2c_release, minor=%d\n",p));
if (bus>=0) inuse[bus]=0;
else if (p==X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C1_RAW:
(file->f_pos) &= ~(0x7f); // zero out 7 MSBs
break;
case X3X3_I2C_16_AINC :
case X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C_16_AINC :
bus=0;
i2cbuf = &i2cbuf_all[0];
break;
case X3X3_I2C1_RAW:
case X3X3_I2C1_8_AINC :
case X3X3_I2C1_16_AINC :
bus=1;
i2cbuf = &i2cbuf_all[1*I2CBUFSIZE];
break;
// case X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C1_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C1_8_AINC :
if (count == 8192) count=burst_sizes[(int)file->private_data]; //! PHP "feature" fix
break;
case X3X3_I2C_16_AINC :
case X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C1_RAW:
en_mask=(1 << X3X3_I2C_ENABLE_RD) | (1 <private_data) {
case X3X3_I2C_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C_16_AINC :
en_bits=i2c_enable[ slave_adr>>1 ];
break;
case X3X3_I2C1_RAW:
case X3X3_I2C1_8_AINC :
case X3X3_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 X3X3_I2C_RAW:
case X3X3_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 X3X3_I2C_8_AINC :
case X3X3_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 X3X3_I2C_16_AINC :
case X3X3_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 X3X3_I2C_CTRL :
userbuf=&bbitdelays[*off];
// memcpy(&i2cbuf[1],&bbitdelays[*off],count);
break;
case X3X3_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 X3X3_I2C_RAW:
case X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C_16_AINC :
bus=0;
i2cbuf = &i2cbuf_all[0];
break;
case X3X3_I2C1_RAW:
case X3X3_I2C1_8_AINC :
case X3X3_I2C1_16_AINC :
bus=1;
i2cbuf = &i2cbuf_all[1*I2CBUFSIZE];
break;
// case X3X3_I2C_CTRL :
// i2cbuf = &i2cbuf_all[X3X3_I2C_CHANNELS*I2CBUFSIZE];
}
userbuf=&i2cbuf[1];
slave_adr=(p >> 7) & 0xfe;
switch ((int)file->private_data) {
case X3X3_I2C_16_AINC :
case X3X3_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 X3X3_I2C_RAW:
case X3X3_I2C1_RAW:
en_mask=(1 << X3X3_I2C_ENABLE_WR) | (1 <private_data) {
case X3X3_I2C_RAW:
case X3X3_I2C_8_AINC :
case X3X3_I2C_16_AINC :
en_bits=i2c_enable[ slave_adr>>1 ];
break;
case X3X3_I2C1_RAW:
case X3X3_I2C1_8_AINC :
case X3X3_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 X3X3_I2C_CTRL :
userbuf=&bbitdelays[p];
break;
case X3X3_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 X3X3_I2C_RAW:
case X3X3_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 X3X3_I2C_8_AINC :
case X3X3_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 X3X3_I2C_16_AINC :
case X3X3_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 X3X3_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 X3X3_I2C_RAW:
case X3X3_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(X3X3_I2C_MAJOR, "fpga_xi2c", &xi2c_fops);
if(res < 0) {
printk(KERN_ERR "\nxi2c_init: couldn't get a major number %d.\n",X3X3_I2C_MAJOR);
return res;
}
printk(X3X3_I2C_DRIVER_NAME" - %d, %d channels\n",X3X3_I2C_MAJOR,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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/detect_sensors.c 0000664 0000000 0000000 00000047305 12752235123 0027650 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 "x393_detect_sensors.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 DETECT_SENSORS_DRIVER_NAME "detect_sensors"
#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;
}
/** Gert configured sensorport 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;
// struct x393_i2c_device_list * dl;
char names[4][80];
// int rslt;
// int num_devs, nd, ni, sa7, num_addr, num_data, khz;
struct device_node *node = pdev->dev.of_node;
// struct device_attribute *new_attr;
// struct device *dev =&pdev->dev;
int num_ports, port, num_sub, sub_chn;
if (node) {
if (num_ports > SENSOR_PORTS)
num_ports = SENSOR_PORTS;
config_string = of_get_property(node, OF_PREFIX_NAME",ports", NULL); // &len);
num_ports = sscanf(config_string,"%79s %79s %79s %79s", names[0], names[1], names[2], names[3]);
for (port = 0; port < num_ports; port++){
set_detected_mux_code(port, get_code_by_name(names[sub_chn], DETECT_MUX));
}
num_ports = of_property_count_strings(node,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();
}
num_sub = sscanf(config_string,"%79s %79s %79s %79s", names[0], names[1], names[2], names[3]);
for (sub_chn = 0; sub_chn < num_sub; sub_chn++){
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);
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 "DETECT_SENSORS_DRIVER_NAME);
// res = register_chrdev(DETECT_SENSORS_MAJOR, DETECT_SENSORS_DRIVER_NAME, &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, DETECT_SENSORS_DRIVER_NAME);
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 = DETECT_SENSORS_DRIVER_NAME,
.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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/detect_sensors.h 0000664 0000000 0000000 00000003465 12752235123 0027654 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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/elphel393-init.c 0000664 0000000 0000000 00000032412 12752235123 0027266 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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/elphel393-mem.c 0000664 0000000 0000000 00000053611 12752235123 0027105 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-cf4af6f5138c835ae694178e6bb47b8228302578/src/drivers/elphel/elphel393-pwr.c 0000664 0000000 0000000 00000143051 12752235123 0027135 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<