Commit 0a316dae authored by Andrey Filippov's avatar Andrey Filippov

worked on i2c

parent 2c032e80
......@@ -122,4 +122,9 @@
<resource resourceType="PROJECT" workspacePath="/linux-elphel"/>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings">
<doc-comment-owner id="org.eclipse.cdt.ui.doxygen">
<path value=""/>
</doc-comment-owner>
</storageModule>
</cproject>
......@@ -2,7 +2,7 @@
<project>
<configuration id="cdt.managedbuild.toolchain.gnu.base.1636449201" name="Default">
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
<provider class="org.eclipse.cdt.core.language.settings.providers.LanguageSettingsGenericProvider" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider" name="CDT User Setting Entries" prefer-non-shared="true"/>
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
</extension>
</configuration>
......
......@@ -9,5 +9,6 @@ obj-$(CONFIG_ELPHEL393_INIT) += elphel393-init.o
obj-$(CONFIG_ELPHEL393) += x393.o
obj-$(CONFIG_ELPHEL393) += sensor_i2c.o
obj-$(CONFIG_ELPHEL393) += fpgajtag353.o
obj-$(CONFIG_ELPHEL393) += clock10359.o
#fpgajtag-y := fpgajtag353.o x393.o
#obj-$(CONFIG_ELPHEL393_EXTERNAL) += fpgajtag.o
\ No newline at end of file
/*******************************************************************************
* FILE NAME : clock10359.c
* DESCRIPTION: Control of the CY22393 clock on the 10359 multiplexer connected
* to the sensor port
* Copyright 2002-2016 (C) 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 <http://www.gnu.org/licenses/>.
*******************************************************************************/
/****************** INCLUDE FILES SECTION ***********************************/
#define DEBUG /* should be before linux/module.h - enables dev_dbg at boot in this file */
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/page.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/of_net.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
#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 setClockFreq(int nclock, int freq); // freq now in Hz
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)
&reg_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;
}
}
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", &reg);
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<p><c>: 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-sensor-i2c");
return 0;
}
static struct of_device_id elphel393_clock10359_of_match[] = {
{ .compatible = "elphel,elphel393-sensor-i2c-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 = "elphel393-sensor-i2c",
.owner = THIS_MODULE,
.of_match_table = elphel393_clock10359_of_match,
.pm = NULL, /* power management */
},
};
module_platform_driver(elphel393_clock10359);
MODULE_AUTHOR("Andrey Filippov <andrey@elphel.com>");
MODULE_DESCRIPTION("Elphel 10393 sensor ports i2c");
MODULE_LICENSE("GPL");
/*******************************************************************************
* FILE NAME : clock10359.h
* DESCRIPTION: Control of the CY22393 clock on the 10359 multiplexer connected
* to the sensor port
* Copyright 2002-2016 (C) 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 <http://www.gnu.org/licenses/>.
*******************************************************************************/
int x393_setClockFreq(int sensor_port, int nclock, int freq);
int x393_getClockFreq(int sensor_port, int nclock);
......@@ -11,7 +11,7 @@
*!
*! 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
*! 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,
......
......@@ -7,7 +7,7 @@
*!
*! 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
*! 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,
......
/*!***************************************************************************
*! FILE NAME : elphel393-pwr.c
*! DESCRIPTION: power supplies control on Elphel 10393 board
*! Copyright (C) 2013 Elphel, Inc.
*! Copyright (C) 2013-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 3 of the License, or
*! 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,
......
/*!***************************************************************************
*! FILE NAME : fpgajtag353.c
*! DESCRIPTION: TBD
*! Copyright 2002-2007 (C) Elphel, Inc.
*! Copyright 2002-20016 (C) 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 3 of the License, or
*! 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,
......@@ -17,58 +17,6 @@
*! You should have received a copy of the GNU General Public License
*! along with this program. If not, see <http://www.gnu.org/licenses/>.
*! -----------------------------------------------------------------------------**
*! $Log: fpgajtag353.c,v $
*! Revision 1.2 2011/05/20 21:36:52 elphel
*! typo fix
*!
*! Revision 1.1.1.1 2008/11/27 20:04:01 elphel
*!
*!
*! Revision 1.4 2008/09/22 22:55:48 elphel
*! snapshot
*!
*! Revision 1.3 2008/09/20 00:29:50 elphel
*! moved driver major/minor numbers to a single file - include/asm-cris/elphel/driver_numbers.h
*!
*! Revision 1.2 2008/09/16 00:49:31 elphel
*! snapshot
*!
*! Revision 1.2 2008/04/11 23:16:51 elphel
*! removed unneeded local_irq_disable() after local_irq_save_flags()
*!
*! Revision 1.1.1.1 2007/08/17 10:23:18 elphel
*! This is a fresh tree based on elphel353-2.10
*!
*! Revision 1.7 2007/08/17 10:23:18 spectr_rain
*! switch to GPL3 license
*!
*! Revision 1.6 2007/07/20 10:17:46 spectr_rain
*! *** empty log message ***
*!
*! Revision 1.5 2007/06/28 02:20:39 elphel
*! Slowed down sensor FPGA programming while working with long cables. Problem was different, so maybe that change may be undone.
*!
*! Revision 1.4 2007/05/21 21:23:50 elphel
*! remove compile-time warning
*!
*! Revision 1.3 2007/05/21 17:45:11 elphel
*! boundary scan support, added 359/347 detection
*!
*! Revision 1.2 2007/03/25 10:14:23 elphel
*! Accommodating 10359 board
*!
*! Revision 1.1.1.1 2007/02/23 10:11:48 elphel
*! initial import into CVS
*!
*! Revision 1.2 2005/05/10 21:08:49 elphel
*! *** empty log message ***
*!
TODO: replace static buffer (what a waste!)
I suspect "somebody" is is playing with portA during JTAG configuration.
To test that I'll use 256K static buffer, copy all the bitstream there,
disable interrupts and do the programming.
No debug with printk ...
*/
#undef DEBUG
/****************** INCLUDE FILES SECTION ***********************************/
......@@ -111,9 +59,9 @@ No debug with printk ...
//#define JTAG_DISABLE_IRQ y
//#define D(x)
#define D(x) printk("%s:%d:",__FILE__,__LINE__);x
#define D(x)
//#define D(x) printk("%s:%d:",__FILE__,__LINE__);x
#define PARALLEL_JTAG
/*
port C 353:
0 - TDO (in)
......@@ -495,6 +443,7 @@ static int fpga_jtag_release(struct inode *inode, struct file *filp) {
JTAG_channels[chn].mode=JTAG_MODE_CLOSED;
//D(printk("fpga_jtag_release: done\r\n"));
dev_dbg(NULL, "fpga_jtag_release: done\r\n");
dev_info(NULL, "fpga_jtag_release: done, res= %d\n",res);
return (res<0)?res:0;
}
......@@ -776,6 +725,26 @@ inline u32 read_tdo(int sens_num)
return stat.xfpgatdo;
}
// read last 8 TDO bits, shifted at rising edge of TCL
inline u32 read_tdo_byte(int sens_num)
{
x393_status_sens_io_t stat;
x393_status_ctrl_t stat_ctrl;
int i;
stat_ctrl.d32 = 0;
stat = x393_sensio_status(sens_num);
stat_ctrl.seq_num = stat.seq_num + 1;
stat_ctrl.mode = 1;
set_x393_sensio_status_cntrl(stat_ctrl, sens_num);
for (i = 0; i < 10; i++) {
stat = x393_sensio_status(sens_num & 3); // sens_num);
if (likely(stat.seq_num == stat_ctrl.seq_num)) {
return stat.xfpgatdo_byte;
}
}
dev_err(NULL,"read_tdo_byte(%d): failed to get expected seq_num in 10 cycles, expected = 0x%x, got 0x%x\n",sens_num,stat_ctrl.seq_num, stat.seq_num);
return stat.xfpgatdo_byte;
}
......@@ -858,8 +827,8 @@ int jtag_send (int chn, int tms, int len, int d) {
int sens_num = chn & 3;
x393_sensio_jtag_t data;
x393_status_sens_io_t stat;
u32 seq_num;
int i; //,m;
// u32 seq_num;
int i, bm = 0; //,m;
int r=0;
int d0;
i = len & 7;
......@@ -919,8 +888,11 @@ int jtag_send (int chn, int tms, int len, int d) {
// x393_sensio_jtag(data, sens_num);
/* read TDO before TCK pulse */
r = (r << 1) + read_tdo(sens_num); // may to need to read twice to increase delay?
#ifndef PARALLEL_JTAG
r = (r << 1) + read_tdo(sens_num); // may need to read twice to increase delay?
#else
bm = (bm <<1 ) | 1;
#endif
data.tck = 1;
x393_sensio_jtag(data, sens_num); // keep other signals, set TCK == 1
// x393_sensio_jtag(data, sens_num); // repeat if delay will be needed to increase length of the TCK signal
......@@ -929,6 +901,10 @@ int jtag_send (int chn, int tms, int len, int d) {
// x393_sensio_jtag(data, sens_num);
}
x393_sensio_jtag(data, sens_num);
#ifdef PARALLEL_JTAG
r = read_tdo_byte(sens_num) & bm;
#endif
// x393_sensio_jtag(data, sens_num);
dev_dbg(NULL, " ---> %02x\n", r);
break;
......@@ -947,14 +923,15 @@ int jtag_send (int chn, int tms, int len, int d) {
int jtag_write_bits (int chn,
unsigned char *buf, // data to write
int len, // number of bytes to write
int len, // number of bits to write
int check, // compare readback data with previously written, abort on mismatch
int last, // output last bit with TMS=1
int prev[2]) // if null - don't use
{
int sens_num = chn & 3;
int i,j;
int r=0;
int r = 0;
int bm = 0;
int d,d0;
// u32 seq_num;
x393_status_sens_io_t stat;
......@@ -1046,26 +1023,34 @@ int jtag_write_bits (int chn,
for (i = 0; len > 0; i++) {
d0 = (d = buf[i]);
dev_dbg(NULL,"jtag_write_bits(), i=0x%x ", i);
bm = 0;
for (j = 0; j < 8; j++) {
if (len > 0) {
data.tms = (len == 1 && last)? 1:0 ;
data.tdi = ((d <<= 1) >> 8) & 1;
data.tck = 0;
x393_sensio_jtag(data, sens_num);
// x393_sensio_jtag(data, sens_num); // repeat writel() if needed for delay
#ifndef PARALLEL_JTAG
r = (r << 1) + read_tdo(sens_num);
#else
bm = (bm <<1 ) | 1;
#endif
data.tck = 1;
x393_sensio_jtag(data, sens_num);
// x393_sensio_jtag(data, sens_num); // remove if no delay is needed
data.tck = 0;
x393_sensio_jtag(data, sens_num);
// x393_sensio_jtag(data, sens_num);
} else {
r <<= 1;
}
len--;
}
#ifdef PARALLEL_JTAG
r = read_tdo_byte(sens_num) & bm;
if (unlikely(len < 0)){
r <<= -len;
}
#endif
buf[i] = r;
dev_dbg(NULL," ===> %02x\n", r);
if (check && ((r ^ (prev[1]>>24)) & 0xff)) {
......@@ -1110,14 +1095,14 @@ int JTAG_configure (int chn, unsigned char * buf, int len) {
}
}
if (datastart<0) {
printk("Bitstream not found - bad file\r\n");
return -EFAULT;
dev_err(NULL,"Bitstream not found - bad file\r\n");
return -EFAULT;
}
// check for right bitstream length
if ((len-datastart)!=(XC3S1200E_BITSIZE>>3)) {
printk("Wrong bitstream size - XC3S1200E has bitstream of %d bits (%d bytes)\n",XC3S1200E_BITSIZE,XC3S1200E_BITSIZE>>3);
printk ("header size - %d, data size - %d\r\n",datastart, len-datastart);
return -EFAULT;
dev_err(NULL,"Wrong bitstream size - XC3S1200E has bitstream of %d bits (%d bytes)\n",XC3S1200E_BITSIZE,XC3S1200E_BITSIZE>>3);
dev_err(NULL,"header size - %d, data size - %d\r\n",datastart, len-datastart);
return -EFAULT;
}
// enable programmimg mode (nop for the 10353 FPGA)
set_pgm_mode(chn, 1);
......@@ -1169,8 +1154,8 @@ int JTAG_configure (int chn, unsigned char * buf, int len) {
set_pgm (chn, 0);
// disable programmimg mode (nop for the 10353 FPGA)
set_pgm_mode(chn, 0);
printk ("**** Configuration failed at byte # %d (%x)****\n", (i-datastart),(i-datastart));
printk ("**** r= %x, prev64=%x prev32=%x****\n", r,prev[1], prev[0]);
dev_err(NULL,"**** Configuration failed at byte # %d (%x)****\n", (i-datastart),(i-datastart));
dev_err(NULL,"**** r= %x, prev64=%x prev32=%x****\n", r,prev[1], prev[0]);
return -EFAULT;
}
jtag_send(chn, 1, 1, 0 ); //step 11 - set UPDATE-DR state
......@@ -1195,12 +1180,13 @@ int JTAG_configure (int chn, unsigned char * buf, int len) {
set_pgm_mode(chn, 0);
if (r==0) {
printk("*** FPGA did not start after configuration ***\r\n");
return -EFAULT;
dev_err(NULL,"*** FPGA did not start after configuration ***\r\n");
return -EFAULT;
}
//D( udelay (100000);printk("\nJTAG_configure() OK!\r\n"));
D( mdelay (100);printk("\nJTAG_configure() OK!\r\n"));
dev_info(NULL,"JTAG_configure() OK!\n");
return 0;
} //int JTAG_configure
......@@ -1398,10 +1384,10 @@ static int __init fpga_jtag_init(void) {
int i,res;
res = register_chrdev(FPGA_JTAG_MAJOR, fpga_jtag_name, &fpga_jtag_fops);
if(res < 0) {
printk(KERN_ERR "\nfpga_jtag_init: couldn't get a major number %d.\n",FPGA_JTAG_MAJOR);
dev_err(NULL,"\nfpga_jtag_init: couldn't get a major number %d.\n",FPGA_JTAG_MAJOR);
return res;
}
printk(FPGA_JTAG_DRIVER_NAME" - %d\n",FPGA_JTAG_MAJOR);
dev_dbg(NULL,FPGA_JTAG_DRIVER_NAME" - %d\n",FPGA_JTAG_MAJOR);
for (i=0;i<=FPGA_JTAG_MAXMINOR;i++) minors[i]=0;
initPortC();
......
......@@ -6,7 +6,7 @@
*
* 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
* 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,
......@@ -18,6 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
/****************** INCLUDE FILES SECTION ***********************************/
#define DEBUG /* should be before linux/module.h - enables dev_dbg at boot in this file */
#include <linux/module.h>
#include <linux/sched.h>
......@@ -30,10 +31,9 @@
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
//#include <linux/spinlock_types.h>
#include <linux/jiffies.h>
#include <asm/io.h>
//#include <asm/system.h>
#include <asm/irq.h>
......@@ -54,6 +54,7 @@
#include <linux/of_net.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
#include <linux/list.h>
#define SYSFS_PERMISSIONS 0644 /* default permissions for sysfs files */
#define SYSFS_READONLY 0444
......@@ -61,40 +62,31 @@
#define DRV_NAME "elphel_sensor_i2c"
//------------------
#if 0
#include <linux/fs.h>
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
struct x393_i2c_device_list {
x393_i2c_device_t i2c_dev;
struct list_head list;
};
const char of_prop_name[] = "elphel393-sensor-i2c,i2c_devices";
const char group_name[] = "i2c_classes";
const int max_buf_len = 4096-256; // stop output when only 256 bytes are left in the caller's buffer
const int max_i2c_classes = 256;
const int mclk_mhz = 200; // mclk frequency in MHz
//const int tenth_sec = max(2, HZ/10); // make it not less than 2 jiffies for some very slow clock
//const int tenth_sec = (HZ >=20) ? (HZ/10) : 2; // make it not less than 2 jiffies for some very slow clock
const int tenth_sec = 2;
#endif
static LIST_HEAD(i2c_class_list);
// Number of channels is hard-wired to 4
static u32 free_i2c_groups[4];
static u32 free_i2c_pages [32];
static DEFINE_SPINLOCK(lock);
static u32 i2c_pages_shadow[1024]; // Mostly for debugging to analyze i2c pages allocation
static int sysfs_page[4]; // when positive - page locked for exclusive access
static struct device *sdev = NULL; // store this device here
static u32 i2c_read_data[4]; // last data read from i2c device
/* Mark all i2c pages for each channel as free */
void i2c_page_alloc_init(void){
int i;
......@@ -102,7 +94,6 @@ void i2c_page_alloc_init(void){
for (i = 0; i < sizeof(free_i2c_pages)/ sizeof(free_i2c_pages[0]); i++) free_i2c_pages[i] = 0xffffffff;
for (i = 0; i < sizeof(i2c_pages_shadow)/sizeof(i2c_pages_shadow[0]); i++) i2c_pages_shadow[i] = 0;
for (i = 0; i < sizeof(sysfs_page)/sizeof(sysfs_page[0]); i++) sysfs_page[i] = -1;
}
/* Reserve i2c page (1 of 256 for a sensor port)*/
......@@ -124,6 +115,7 @@ int i2c_page_alloc(int chn){
spin_unlock(&lock);
return (g << 5) + b;
}
EXPORT_SYMBOL_GPL(i2c_page_alloc);
/* Free i2c page */
void i2c_page_free(int chn, int page){
......@@ -135,10 +127,23 @@ void i2c_page_free(int chn, int page){
*fb |= 1 << (31-b);
spin_unlock(&lock);
}
EXPORT_SYMBOL_GPL(i2c_page_free);
/* calculate value of SCLK 1/4 period delay from maximal SCL frequency*/
int get_bit_delay(int khz){
int dly;
if (!khz)
return 0;
dly = (1000 * mclk_mhz / 4)/khz;
if (dly > 255)
dly = 255;
return dly;
}
/* Set i2c table entry to raw data (will just overwrite tbl_mode = 2)*/
void set_sensor_i2c_raw(int chn,
int page, // index in lookup table
u32 data) { // Bit delay - number of mclk periods in 1/4 of the SCL period
void set_xi2c_raw(int chn,
int page, // index in lookup table
u32 data) { // Bit delay - number of mclk periods in 1/4 of the SCL period
x393_i2c_ctltbl_t tb_data, tb_addr;
tb_addr.d32 = 0;
tb_addr.tbl_mode = 3;
......@@ -151,21 +156,81 @@ void set_sensor_i2c_raw(int chn,
spin_unlock(&lock);
i2c_pages_shadow[(chn << 8) + page] =tb_data.d32;
}
EXPORT_SYMBOL_GPL(set_xi2c_raw);
/*
* Set i2c table entry for write operation using known devices
* Get device with xi2c_dev_get(), copy and modify, if needed to
* offset slave address or change number of bytes to write, SCL frequency
*/
void set_xi2c_wrc( x393_i2c_device_t * dc, // device class
int chn, // sensor port
int page, // index in lookup table
int rah){ // High byte of the i2c register address
x393_i2c_ctltbl_t tb_data, tb_addr;
tb_addr.d32 = 0;
tb_addr.tbl_mode = 3;
tb_addr.tbl_addr = page;
tb_data.d32 = 0;
tb_data.rah = rah;
tb_data.rnw = 0;
tb_data.sa = dc -> slave7;
tb_data.nbwr = dc -> address_bytes + dc -> data_bytes;
tb_data.dly = get_bit_delay(dc -> scl_khz);
tb_data.tbl_mode = 2;
/* Table address and data should not interleave with others */
spin_lock(&lock);
x393_sensi2c_ctrl (tb_addr, chn);
x393_sensi2c_ctrl (tb_data, chn);
spin_unlock(&lock);
i2c_pages_shadow[(chn << 8) + page] =tb_data.d32;
}
EXPORT_SYMBOL_GPL(set_xi2c_wrc);
/*
* Set i2c table entry for read operation using known devices
* Get device with xi2c_dev_get(), copy and modify, if needed to
* offset slave address or change number of bytes to write, SCL frequency
*/
void set_xi2c_rdc(x393_i2c_device_t * dc, // device class
int chn, // sensor port
int page) { // index in lookup table
x393_i2c_ctltbl_t tb_data, tb_addr;
tb_addr.d32 = 0;
tb_addr.tbl_mode = 3;
tb_addr.tbl_addr = page;
tb_data.d32 = 0;
tb_data.rnw = 1;
tb_data.nabrd = (dc -> address_bytes > 1)? 1:0;
tb_data.nbrd = dc -> data_bytes;
tb_data.dly = get_bit_delay(dc -> scl_khz);
tb_data.tbl_mode = 2;
/* Table address and data should not interleave with others */
dev_dbg(sdev, "set_xi2c_rdc(*, %d, %d), tb_addr.d32=0x%08x, tb_data.d32 = 0x%08x\n",chn,page,tb_addr.d32,tb_data.d32);
spin_lock(&lock);
x393_sensi2c_ctrl (tb_addr, chn);
x393_sensi2c_ctrl (tb_data, chn);
spin_unlock(&lock);
i2c_pages_shadow[(chn << 8) + page] = tb_data.d32;
}
EXPORT_SYMBOL_GPL(set_xi2c_rdc);
/* Set i2c table entry for write operation */
void set_sensor_i2c_wr(int chn,
int page, // index in lookup table
int sa, // slave address (7 bit)
int rah, // High byte of the i2c register address
int num_bytes, //Number of bytes to write (1..10)
int bit_delay) { // Bit delay - number of mclk periods in 1/4 of the SCL period
void set_xi2c_wr(int chn,
int page, // index in lookup table
int sa7, // slave address (7 bit)
int rah, // High byte of the i2c register address
int num_bytes, // Number of bytes to write (1..10)
int bit_delay) { // Bit delay - number of mclk periods in 1/4 of the SCL period
x393_i2c_ctltbl_t tb_data, tb_addr;
tb_addr.d32 = 0;
tb_addr.tbl_mode = 3;
tb_addr.tbl_addr = page;
tb_data.d32 = 0;
tb_data.rah = rah;
tb_data.rnw = 0;
tb_data.sa = sa;
tb_data.sa = sa7;
tb_data.nbwr = num_bytes;
tb_data.dly = bit_delay;
tb_data.tbl_mode = 2;
......@@ -176,16 +241,19 @@ void set_sensor_i2c_wr(int chn,
spin_unlock(&lock);
i2c_pages_shadow[(chn << 8) + page] =tb_data.d32;
}
EXPORT_SYMBOL_GPL(set_xi2c_wr);
/* Set i2c table entry for read operation */
void set_sensor_i2c_rd(int chn,
int page, // index in lookup table
int two_byte_addr, // Number of address bytes (0 - one byte, 1 - two bytes)
int num_bytes, // Number of bytes to read (1..8, 0 means 8)
int bit_delay) { // Bit delay - number of mclk periods in 1/4 of the SCL period
void set_xi2c_rd(int chn,
int page, // index in lookup table
int two_byte_addr, // Number of address bytes (0 - one byte, 1 - two bytes)
int num_bytes, // Number of bytes to read (1..8, 0 means 8)
int bit_delay) { // Bit delay - number of mclk periods in 1/4 of the SCL period
x393_i2c_ctltbl_t tb_data, tb_addr;
tb_addr.d32 = 0;
tb_addr.tbl_mode = 3;
tb_addr.tbl_addr = page;
tb_data.d32 = 0;
tb_data.rnw = 1;
tb_data.nabrd = two_byte_addr;
......@@ -199,6 +267,7 @@ void set_sensor_i2c_rd(int chn,
spin_unlock(&lock);
i2c_pages_shadow[(chn << 8) + page] =tb_data.d32;
}
EXPORT_SYMBOL_GPL(set_xi2c_rd);
/*
// Write i2c command to the i2c command sequencer
......@@ -220,14 +289,14 @@ void set_sensor_i2c_rd(int chn,
/* Write one or multiple DWORDs to i2c relative (modulo16) address. Use offs = 0 for immediate (ASAP) command */
/* Length of data is determined by the page data already preset */
int write_sensor_i2c_rel (int chn,
int offs, // 4 bits
u32 * data){
int write_xi2c_rel (int chn,
int offs, // 4 bits
u32 * data){
x393_i2c_ctltbl_t tb_data;
int len;
int i;
tb_data.d32 = i2c_pages_shadow[(chn <<8) + (data[0] >> 24)];
if (tb_data.tbl_mode !=2) return -1;
if (tb_data.tbl_mode !=2) return -EBADR;
len = (tb_data.rnw )? 1:((tb_data.nbwr + 5) >> 2); // read mode - always 1 DWORD, write - 1..3
if (len > 1) {
spin_lock(&lock);
......@@ -240,15 +309,16 @@ int write_sensor_i2c_rel (int chn,
spin_unlock(&lock);
return 0;
}
EXPORT_SYMBOL_GPL(write_xi2c_rel);
int write_sensor_i2c_abs (int chn,
int offs, // 4 bits
u32 * data){
int write_xi2c_abs (int chn,
int offs, // 4 bits
u32 * data){
x393_i2c_ctltbl_t tb_data;
int len;
int i;
tb_data.d32 = i2c_pages_shadow[(chn <<8) + (data[0] >> 24)];
if (tb_data.tbl_mode !=2) return -1;
if (tb_data.tbl_mode !=2) return -EBADR;
len = (tb_data.rnw )? 1:((tb_data.nbwr + 5) >> 2); // read mode - always 1 DWORD, write - 1..3
if (len > 1) {
spin_lock(&lock);
......@@ -261,55 +331,332 @@ int write_sensor_i2c_abs (int chn,
spin_unlock(&lock);
return 0;
}
EXPORT_SYMBOL_GPL(write_xi2c_abs);
/* Write sensor 16 bit (or 8 bit as programmed in the table) data in immediate mode */
void write_sensor_reg16 (int chn,
int page, // page (8 bits)
int addr, // low 8 bits
u32 data){ // 16 or 8-bit data (LSB aligned)
void write_xi2c_reg16 (int chn,
int page, // page (8 bits)
int addr, // low 8 bits
u32 data){ // 16 or 8-bit data (LSB aligned)
u32 dw = ((page & 0xff) << 24) | ((addr & 0xff) << 16) | (data & 0xffff);
x393_sensi2c_rel (dw, chn, 0);
}
EXPORT_SYMBOL_GPL(write_xi2c_reg16);
/* Initiate sensor i2c read in immediate mode (data itself has to be read from FIFO with read_sensor_i2c_fifo)*/
void read_sensor_i2c (int chn,
int page, // page (8 bits)
int sa7, // 7-bit i2c slave address
int addr){ // 8/16 bit address
/*
* Initiate sensor i2c read in immediate mode (data itself has to be read from FIFO with read_xi2c_fifo)
* Use slave address from provided class structure.
*/
void read_xi2c (x393_i2c_device_t * dc, // device class
int chn,
int page, // page (8 bits)
int addr){ // 8/16 bit address
u32 dw = ((page & 0xff) << 24) | (dc -> slave7 << 17) | (addr & 0xffff);
x393_sensi2c_rel (dw, chn, 0);
}
EXPORT_SYMBOL_GPL(read_xi2c);
/* Initiate sensor i2c read in immediate mode (data itself has to be read from FIFO with read_xi2c_fifo)*/
void read_xi2c_sa7 (int chn,
int page, // page (8 bits)
int sa7, // 7-bit i2c slave address
int addr){ // 8/16 bit address
u32 dw = ((page & 0xff) << 24) | (sa7 << 17) | (addr & 0xffff);
dev_dbg(sdev, "read_xi2c_sa7(%d,0x%x,0x%x,0x%x): 0x%08x\n",chn,page,sa7,addr,(int) dw);
x393_sensi2c_rel (dw, chn, 0);
}
EXPORT_SYMBOL_GPL(read_xi2c_sa7);
//void x393_sensi2c_rel (u32 d, int sens_num, int offset){writel(d, mmio_ptr + (0x1080 + 0x40 * sens_num + 0x1 * offset));} // Write sensor i2c sequencer
/* Read next byte from the channel i2c FIFO. Return byte or -1 if no data available */
/* Sensor channel status should be in auto update mode (3) */
int read_sensor_i2c_fifo(int chn){
/* Read next byte from the channel i2c FIFO. Return byte or -1 if no data available, -EIO - error */
/* Sensor channel status will be set to auto update mode (3) if it was in different mode */
int read_xi2c_fifo(int chn){
int fifo_lsb, rslt,i;
x393_i2c_ctltbl_t i2c_cmd;
x393_status_sens_i2c_t status = x393_sensi2c_status (chn);
x393_status_sens_i2c_t status;
x393_status_ctrl_t status_ctrl = get_x393_sensi2c_status_ctrl(chn); /* last written data to status_cntrl */
if (status_ctrl.mode != 3){
status = x393_sensi2c_status (chn);
status_ctrl.mode = 3;
status_ctrl.seq_num = status.seq_num ^ 0x20;
set_x393_sensi2c_status_ctrl(status_ctrl, chn);
for (i = 0; i < 10; i++) {
status = x393_sensi2c_status(chn);
if (likely(status.seq_num = status_ctrl.seq_num)) break;
}
dev_dbg(sdev, "read_xi2c_fifo(%d): mode set to 3, status updated to 0x%08x\n",chn, (int) status.d32);
}
status = x393_sensi2c_status (chn);
// dev_dbg(sdev, "read_xi2c_fifo(%d): status = 0x%08x\n",chn, (int) status.d32);
if (!status.i2c_fifo_nempty) return -1; // No data available
fifo_lsb = status.i2c_fifo_lsb;
rslt = status.i2c_fifo_dout;
// Advance FIFO readout pointer
/* Advance FIFO readout pointer */
i2c_cmd.d32 = 0;
i2c_cmd.next_fifo_rd = 1; // tbl_mode is 0 already
x393_sensi2c_ctrl (i2c_cmd, chn);
/* Make sure status matches next FIFO entry (or empty)*/
for (i = 0; i < 10; i++) {
status = x393_sensi2c_status(chn);
if (likely(status.i2c_fifo_lsb != fifo_lsb)) break;
}
dev_dbg(sdev, "read_xi2c_fifo(%d): new status = 0x%08x\n",chn, (int) status.d32);
return rslt;
}
// ======================================
// SYSFS
/* Get channelo number from the last character of the attribute name*/
EXPORT_SYMBOL_GPL(read_xi2c_fifo);
/* Single-command i2c write/read register using pre-defined device classes */
int x393_xi2c_write_reg(const char * cname, // device class name
int chn, // sensor port number
int sa7_offs, // slave address (7-bit) offset from the class defined slave address
int reg_addr, // register address (width is defined by class)
int data){ // data to write (width is defined by class)
x393_i2c_device_t * dc;
x393_i2c_device_t ds;
int page;
/* Get i2c class */
dc = xi2c_dev_get(cname);
if (!dc) {
dev_err(sdev, "I2c class name %s is not defined\n",cname);
return -ENOENT;
}
/* Reserve table page */
page = i2c_page_alloc(chn);
if (page < 0) {
dev_err(sdev, "Failed to reserve page, returned %d\n",page);
return page;
}
/* Set table entry for writing */
memcpy(&ds, dc, sizeof(ds));
ds.slave7 = dc->slave7 + sa7_offs;
set_xi2c_wrc(&ds, // device class
chn, // sensor port
page, // index in lookup table
(reg_addr >> 8) & 0xff); // High byte of the i2c register address
/* Write i2c in immediate mode */
write_xi2c_reg16 (chn, // sensor port
page, // page (8 bits)
reg_addr & 0xff, // low 8 bits
(u32) data); // 16 or 8-bit data (LSB aligned)
/* Free table page */
i2c_page_free(chn, page);
return 0;
}
EXPORT_SYMBOL_GPL(x393_xi2c_write_reg);
int x393_xi2c_read_reg( const char * cname, // device class name
int chn, // sensor port number
int sa7_offs, // slave address (7-bit) offset from the class defined slave address
int reg_addr, // register address (width is defined by class)
int * datap){ // pointer to a data receiver (read data width is defined by class)
x393_i2c_device_t * dc;
int page, i, db=-1;
unsigned long timeout_end;
/* Get i2c class */
dev_dbg(sdev, "x393_xi2c_read_reg(%s, %d, 0x%x, 0x%x)\n",cname,chn,sa7_offs,reg_addr);
dc = xi2c_dev_get(cname);
if (!dc) {
dev_err(sdev, "I2c class name %s is not defined\n",cname);
return -ENOENT;
}
dev_dbg(sdev, "got class %s:slave7=0x%x,address_bytes = %d, data_bytes = %d, scl_khz = %d)\n",
dc->name, dc->slave7, dc->address_bytes, dc->data_bytes, dc->scl_khz);
/* Reserve table page */
page = i2c_page_alloc(chn);
if (page < 0) {
dev_err(sdev, "Failed to reserve page, returned %d\n",page);
return page;
}
dev_dbg(sdev, "got page= 0x%x\n",page);
/* Set table entry for reading */
// ds.slave7 = dc->slave7 + sa7_offs;
set_xi2c_rdc(dc, // device class
chn, // sensor port
page); // index in lookup table
/* Flush channel i2c read FIFO to make sure it is empty, status mode will be set, if needed */
while (read_xi2c_fifo(chn) >= 0) ; // includes waiting for status propagate
dev_dbg(sdev, "Flushed i2c read fifo for channel %d\n",chn);
/* Initiate i2c read */
read_xi2c_sa7 (chn,
page & 0xff, // page (8 bits)
(dc->slave7 + sa7_offs) & 0x7f, // 7-bit i2c slave address
reg_addr & 0xffff); // 8/16 bit address
/* Now read required number of bytes with timeout */
*datap = 0;
dev_dbg(sdev, "Trying to get FIFO data for channel %d\n",chn);
for (i = 0; i< dc->data_bytes; i++) {
// timeout_end = jiffies + 20*tenth_sec;
timeout_end = jiffies + tenth_sec;
while (jiffies < timeout_end){
db = read_xi2c_fifo(chn);
if (db >=0)
break;
}
if (db < 0) {
/* release used page */
dev_dbg(sdev, "Timeout waiting for i2c fifo read data for channel %d, freeing page %d\n",chn,page);
i2c_page_free(chn, page);
return -ETIMEDOUT;
}
*datap = (*datap <<8) | (db & 0xff);
}
/* Free table page */
dev_dbg(sdev, "Freeing i2c page %d\n",page);
i2c_page_free(chn, page);
return 0;
}
EXPORT_SYMBOL_GPL(x393_xi2c_read_reg);
/* Handling classes of i2c devices */
struct x393_i2c_device_list * i2c_dev_get(const char * name){
struct list_head *p;
struct x393_i2c_device_list * sp;
list_for_each(p, &i2c_class_list) {
sp = list_entry(p, struct x393_i2c_device_list, list);
dev_dbg(sdev,"i2c_dev_get(%s): sp->i2c_dev.name=%s\n",name,sp->i2c_dev.name);
if (!strncmp(name, sp->i2c_dev.name, sizeof(sp->i2c_dev.name)))
return sp;
}
return NULL;
}
x393_i2c_device_t * xi2c_dev_get(const char * name){
struct x393_i2c_device_list * dl = i2c_dev_get(name);
if (dl)
return &dl->i2c_dev;
else
return NULL;
}
EXPORT_SYMBOL_GPL(xi2c_dev_get);
struct x393_i2c_device_list * i2c_dev_add(const char * name){
struct x393_i2c_device_list * sp = i2c_dev_get(name);
if (sp) return sp; /* already exists */
/*Allocate new structure */
dev_dbg(sdev,"allocating space for %s\n",name);
sp = (struct x393_i2c_device_list *) devm_kzalloc(sdev, sizeof(struct x393_i2c_device_list), GFP_KERNEL);
dev_dbg(sdev,"Copying name\n");
// INIT_LIST_HEAD(&sp->list);
strncpy(sp->i2c_dev.name, name, sizeof(sp->i2c_dev.name));
dev_dbg(sdev,"Done\n");
INIT_LIST_HEAD(&sp->list);
list_add(&sp->list, &i2c_class_list);
dev_dbg(sdev,"list_add() OK\n");
/* create sysfs entry here? */
return sp;
}
int i2c_dev_remove(const char * name){
struct x393_i2c_device_list * sp = i2c_dev_get(name);
if (!sp) return - ENOENT;
/* remove sysfs entry */
list_del(&sp->list);
devm_kfree(sdev, sp);
return 0;
}
//TODO: Add Write/read <name> <reg addr> <reg data>, with separate register for read data
/* =========================== sysfs functionality ============================== */
/* Get channel number from the last character of the attribute name*/
static int get_channel_from_name(struct device_attribute *attr){
int reg = 0;
sscanf(attr->attr.name + (strlen(attr->attr.name)-1), "%du", &reg);
return reg;
}
static ssize_t i2c_member_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){
dev_warn(dev,"i2c_member_store(): not implemented\n");
return count;
}
static ssize_t i2c_member_show(struct device *dev, struct device_attribute *attr, char *buf){
struct x393_i2c_device_list * sp;
sp = i2c_dev_get(attr->attr.name);
if (!sp)
return sprintf(buf,"i2c device class %s not found (should not get here)\n",attr->attr.name);
return sprintf(buf,"%d %d %d %d\n",
sp->i2c_dev.slave7,
sp->i2c_dev.address_bytes,
sp->i2c_dev.data_bytes,
sp->i2c_dev.scl_khz);
}
static ssize_t i2c_class_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
struct x393_i2c_device_list * dl;
char name[32];
int ni, sa7, num_addr, num_data, khz;
struct device_attribute *new_attr;
int rslt;
ni = sscanf(buf,"%31s %i %i %i %i", name, &sa7, &num_addr, &num_data, &khz);
if (ni < 5) {
dev_err(dev, "Requires 5 parameters: name, slave addr (7 bit), address width (bytes), data width (bytes), max SCL frequency (kHz)\n");
return -EINVAL;
}
dl = i2c_dev_get(name);
if (!dl){
dl = i2c_dev_add (name);
// create sysfs group member
//group_name
new_attr = devm_kzalloc(dev, sizeof(new_attr[0]), GFP_KERNEL);
if (!new_attr)
return -ENOMEM;
new_attr->attr.name = devm_kzalloc(dev, strlen(name)+1, GFP_KERNEL);
strcpy(new_attr->attr.name, (const char *) name);
new_attr->attr.mode = SYSFS_PERMISSIONS;
new_attr->show = i2c_member_show;
new_attr->store = i2c_member_store;
if (&dev->kobj) {
if ((rslt = sysfs_add_file_to_group (
&dev->kobj,
&new_attr -> attr, // const struct attribute * attr,
group_name))) // const char * group);
{
dev_err(dev,"i2c_class_store() failed to add %s to group %s\n",name, group_name);
return rslt;
}
}
}
if (dl) {
dl-> i2c_dev.slave7 = sa7;
dl-> i2c_dev.address_bytes = num_addr;
dl-> i2c_dev.data_bytes = num_data;
dl-> i2c_dev.scl_khz = khz;
}
return count;
}
static ssize_t i2c_class_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct list_head *p;
struct x393_i2c_device_list * sp;
char * cp = buf;
list_for_each(p, &i2c_class_list) {
sp = list_entry(p, struct x393_i2c_device_list, list);
buf += sprintf(buf,"%s: 0x%x %d %d %d kHz\n",
sp->i2c_dev.name,
sp->i2c_dev.slave7,
sp->i2c_dev.address_bytes,
sp->i2c_dev.data_bytes,
sp->i2c_dev.scl_khz);
if ((buf-cp) > max_buf_len) {
buf += sprintf(buf,"--- truncated ---\n");
break;
}
}
if (buf == cp){
return sprintf(buf,"No I2C classes defined\n");
}
return buf-cp;
}
static ssize_t get_i2c_page_alloc(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn = get_channel_from_name(attr) ;
......@@ -368,13 +715,13 @@ static ssize_t set_i2c_tbl_raw(struct device *dev, struct device_attribute *attr
int chn = get_channel_from_name(attr) ;
int ni, page, data;
ni = sscanf(buf, "%i %i", &page, &data);
if (ni < 2)
if (ni < 2) {
dev_err(dev, "Requires 2 parameters: page, data\n");
return -EINVAL;
set_sensor_i2c_raw(chn,
page & 0xff, // index in lookup table
(u32) data); // Bit delay - number of mclk periods in 1/4 of the SCL period
}
set_xi2c_raw(chn,
page & 0xff, // index in lookup table
(u32) data); // Bit delay - number of mclk periods in 1/4 of the SCL period
return count;
}
......@@ -389,7 +736,7 @@ static ssize_t get_i2c_tbl_human(struct device *dev, struct device_attribute *at
tb_data.d32 = i2c_pages_shadow[(chn << 8) + (page &0xff)];
if (tb_data.rnw){
return sprintf(buf,"Read entry: chn=%d page=%d(0x%x) two_byte_addr=%d number bytes to read=%d bit_duration=%d\n",
chn, page,page, tb_data.nabrd,tb_data.nabrd,tb_data.nbrd, tb_data.dly);
chn, page,page, tb_data.nabrd,tb_data.nbrd, tb_data.dly);
} else {
return sprintf(buf,"Write entry: chn=%d page=%d(0x%x) sa=0x%02x rah=0x%02x nbw=%d bit_duration=%d\n",
chn, page,page,tb_data.sa,tb_data.rah,tb_data.nbwr, tb_data.dly);
......@@ -399,78 +746,163 @@ static ssize_t get_i2c_tbl_human(struct device *dev, struct device_attribute *at
static ssize_t set_i2c_tbl_wr_human(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn = get_channel_from_name(attr) ;
int ni, page, rah,sa7,nbwr,dly;
ni = sscanf(buf, "%i %i %i %i %i", &page, &sa7, &rah, &nbwr, &dly);
if (ni < 2)
dev_err(dev, "Requires 5 parameters: page, slave address (7 bit), high reg address byte, bytes to write (1..10), 1/4 scl period in mclk\n");
return -EINVAL;
set_sensor_i2c_wr(chn,
page & 0xff, // index in lookup table
sa7 & 0x7f, // slave address (7 bit)
rah & 0xff, // High byte of the i2c register address
nbwr & 0xf, // Number of bytes to write (1..10)
dly & 0xff); // Bit delay - number of mclk periods in 1/4 of the SCL period
int ni, page, rah,sa7,nbwr,dly,khz;
char name[32];
x393_i2c_device_t * dc;
x393_i2c_device_t ds;
// check if it starts from a number
if (sscanf(buf, "%i", &page)) {
ni = sscanf(buf, "%i %i %i %i %i", &page, &sa7, &rah, &nbwr, &dly);
if (ni < 2) {
dev_err(dev, "Requires 5 parameters: page, slave address (7 bit), high reg address byte, bytes to write (1..10), 1/4 scl period in mclk\n");
return -EINVAL;
}
set_xi2c_wr(chn,
page & 0xff, // index in lookup table
sa7 & 0x7f, // slave address (7 bit)
rah & 0xff, // High byte of the i2c register address
nbwr & 0xf, // Number of bytes to write (1..10)
dly & 0xff); // Bit delay - number of mclk periods in 1/4 of the SCL period
} else { // try alternative (class name based format)
// set defaults for optional parameters
sa7 = 0;
rah = 0;
nbwr = 0;
dly = 0;
ni = sscanf(buf, "%31s %i %i %i %i %i", name, &page, &sa7, &rah, &nbwr, &khz);
if (ni < 2) {
dev_err(dev, "Requires at least 2 parameters: class name, page. Optional: slave address (7 bit), high reg address byte, bytes to write (1..10), SCL freq. (kHz)\n");
return -EINVAL;
}
dc = xi2c_dev_get(name);
if (!dc) {
dev_err(dev, "I2c class name %s is not defined\n",name);
return -ENOENT;
}
memcpy(&ds, dc, sizeof(ds));
ds.slave7 = dc->slave7 + sa7;
if (nbwr) ds.data_bytes = nbwr;
if (khz) ds.scl_khz = khz;
set_xi2c_wrc(&ds, // device class
chn, // sensor port
page, // index in lookup table
rah); // High byte of the i2c register address
}
return count;
}
static ssize_t set_i2c_tbl_rd_human(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn = get_channel_from_name(attr) ;
int ni, page, two_byte_addr, num_bytes,bit_delay;
ni = sscanf(buf, "%i %i %i %i %i", &page, &two_byte_addr, &num_bytes, &bit_delay);
if (ni < 2)
dev_err(dev, "Requires 4 parameters: page, two byte addr (0 - 8bit,1 - 16 bit reg. addr), number of bytes to read, bytes to write (1..10), 1/4 scl period in mclk\n");
return -EINVAL;
set_sensor_i2c_rd( chn,
page & 0xff, // index in lookup table
two_byte_addr & 1, // Number of address bytes (0 - one byte, 1 - two bytes)
num_bytes & 7, // Number of bytes to read (1..8, 0 means 8)
bit_delay & 0xff); // Bit delay - number of mclk periods in 1/4 of the SCL period
int ni, page, two_byte_addr, num_bytes, bit_delay, khz;
char name[32];
x393_i2c_device_t * dc;
x393_i2c_device_t ds;
// check if it starts from a number
if (sscanf(buf, "%i", &page)) {
ni = sscanf(buf, "%i %i %i %i", &page, &two_byte_addr, &num_bytes, &bit_delay);
if (ni < 2) {
dev_err(dev, "Requires 4 parameters: page, two byte addr (0 - 8bit,1 - 16 bit reg. addr), number of bytes to read (1..8), 1/4 scl period in mclk\n");
return -EINVAL;
}
set_xi2c_rd(chn,
page & 0xff, // index in lookup table
two_byte_addr & 1, // Number of address bytes (0 - one byte, 1 - two bytes)
num_bytes & 7, // Number of bytes to read (1..8, 0 means 8)
bit_delay & 0xff); // Bit delay - number of mclk periods in 1/4 of the SCL period
} else { // try alternative (class name based format)
// set defaults for optional parameters
num_bytes = 0;
khz = 0;
ni = sscanf(buf, "%31s %i %i %i", name, &page, &num_bytes, &khz);
if (ni < 2) {
dev_err(dev, "Requires at least 2 parameters: class name, page. Optional: number of bytes to read, SCL freq. (kHz)\n");
return -EINVAL;
}
dc = xi2c_dev_get(name);
if (!dc) {
dev_err(dev, "I2c class name %s is not defined\n",name);
return -ENOENT;
}
memcpy(&ds, dc, sizeof(ds));
if (num_bytes) ds.data_bytes = num_bytes;
if (khz) ds.scl_khz = khz;
set_xi2c_rdc(&ds, // device class name
chn, // sensor port
page); // index in lookup table
}
return count;
}
static ssize_t set_i2c_read(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
static ssize_t i2c_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int chn = get_channel_from_name(attr) ;
int ni, page, sa7, addr;
ni = sscanf(buf, "%i %i %i", &page, &sa7, addr);
if (ni <3)
dev_err(dev, "Requires 3 parameters: page, sa7, reg_addr\n");
char cname[32];
int ni, sa7_offset, reg_addr, reg_data, rslt;
dev_dbg(sdev, "i2c_store(), chn=%d\n",chn);
ni = sscanf(buf, "%31s %i %i %i", cname, &sa7_offset, &reg_addr, &reg_data);
dev_dbg(sdev, "i2c_store(), ni = %d, chn=%d, cname = %s, sa7_offset = 0x%x, reg_addr=0x%x, reg_data= 0x%x)\n",
ni, chn, cname, sa7_offset, reg_addr, reg_data);
if (ni < 3) {
dev_err(dev, "Requires at least 3 parameters: cname, sa7_offset, reg_addr. Optional reg_data (for register write operation)\n");
return -EINVAL;
page &= 0xff;
read_sensor_i2c (chn,
page & 0xff, // page (8 bits)
sa7 & 0x7f, // 7-bit i2c slave address
addr & 0xffff); // 8/16 bit address return count;
}
if (ni == 3){ // register read
dev_dbg(sdev, "i2c_store(), calling x393_xi2c_read_reg()\n");
rslt = x393_xi2c_read_reg(cname, // device class name
chn, // sensor port number
sa7_offset, // slave address (7-bit) offset from the class defined slave address
reg_addr, // register address (width is defined by class)
&reg_data); // pointer to a data receiver (read data width is defined by class)
if (rslt < 0) return rslt;
i2c_read_data[chn] = (u32) reg_data;
} else { // register write
dev_dbg(sdev, "i2c_store(), calling x393_xi2c_write_reg()\n");
rslt = x393_xi2c_write_reg(cname, // device class name
chn, // sensor port number
sa7_offset, // slave address (7-bit) offset from the class defined slave address
reg_addr, // register address (width is defined by class)
reg_data); // data to write (width is defined by class)
}
return count;
}
// Get i2c read data from fifo
static ssize_t get_i2c_read(struct device *dev, struct device_attribute *attr, char *buf)
static ssize_t i2c_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int chn = get_channel_from_name(attr) ;
int page = sysfs_page[chn]; // currently selected page for sysfs reads
if (page < 0)
return -ENXIO; /* No such device or address */
return sprintf(buf,"%d\n",read_sensor_i2c_fifo(chn)); // <0 - not ready, 0..255 - data
dev_dbg(sdev, "i2c_show(), chn=%d\n",chn);
return sprintf(buf,"%d\n",i2c_read_data[chn]);
}
// Get i2c read data from fifo
static ssize_t get_i2c_help(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf,"Numeric suffix in file names selects sensor port\n"
"i2c_all: read - list classes of i2c devices, write - add new (name, sa7, address bytes, data bytes, SCL kHz)\n"
"i2c_classes - directory with register i2c classes, when read provide sa7, address bytes, data bytes, SCL kHz\n"
"alloc*: read - allocate and return page, write <page> (any data) - free page\n"
"rd_page*: read/write page number used in read operations (-1 if none)\n"
"tbl_raw*: read - raw hex table value (for current rd_page), write <page> <data32> set page\n"
"tbl_wr*: read - decoded table entry for current rd_page, write <page> <sa7> <high_addr_byte> <bytes_to_write> <dly>\n"
"tbl_wr*: alt: <class-name> <page> <sa7-offset> <high_addr_byte> <bytes_to_write or 0 (use class)> <dly or 0(use class)>\n"
"tbl_rd*: read - decoded table entry for current rd_page, write <page> <2-byte addr> <bytes_to_read> <dly>\n"
"tbl_rd* and tbl_wr* return same result when read. Delay is 8 bit, 250 - 200KHz SCL\n");
"tbl_rd*: alt: <class-name> <page> <bytes_to_read or 0 (use class)> <dly or 0 (use class)>\n"
"tbl_rd* and tbl_wr* return same result when read. Delay is 8 bit, 250 - 200KHz SCL\n"
"Read/write i2c register (for the pre-configured device classes), sa7_affset is added to class device slave address\n"
"[<data>] is used for register write, without - register read. Reading i2c* returns result of last i2c read operation\n"
"i2c*: read - last read data, write: <class_name> <sa7_offset> <reg_addr> [<data>]\n"
);
}
// Sysfs top
/* alloc*: read - allocate and return page, write (any data) - free page */
static DEVICE_ATTR(i2c_all , SYSFS_PERMISSIONS , i2c_class_show , i2c_class_store);
static DEVICE_ATTR(alloc0 , SYSFS_PERMISSIONS , get_i2c_page_alloc , free_i2c_page);
static DEVICE_ATTR(alloc1 , SYSFS_PERMISSIONS , get_i2c_page_alloc , free_i2c_page);
static DEVICE_ATTR(alloc2 , SYSFS_PERMISSIONS , get_i2c_page_alloc , free_i2c_page);
......@@ -492,14 +924,19 @@ static DEVICE_ATTR(tbl_rd0 , SYSFS_PERMISSIONS , get_i2
static DEVICE_ATTR(tbl_rd1 , SYSFS_PERMISSIONS , get_i2c_tbl_human, set_i2c_tbl_rd_human);
static DEVICE_ATTR(tbl_rd2 , SYSFS_PERMISSIONS , get_i2c_tbl_human, set_i2c_tbl_rd_human);
static DEVICE_ATTR(tbl_rd3 , SYSFS_PERMISSIONS , get_i2c_tbl_human, set_i2c_tbl_rd_human);
static DEVICE_ATTR(i2c_rd0 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
static DEVICE_ATTR(i2c_rd1 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
static DEVICE_ATTR(i2c_rd2 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
static DEVICE_ATTR(i2c_rd3 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
//static DEVICE_ATTR(i2c_rd0 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
//static DEVICE_ATTR(i2c_rd1 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
//static DEVICE_ATTR(i2c_rd2 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
//static DEVICE_ATTR(i2c_rd3 , SYSFS_PERMISSIONS , get_i2c_read, set_i2c_read);
static DEVICE_ATTR(i2c0 , SYSFS_PERMISSIONS , i2c_show, i2c_store);
static DEVICE_ATTR(i2c1 , SYSFS_PERMISSIONS , i2c_show, i2c_store);
static DEVICE_ATTR(i2c2 , SYSFS_PERMISSIONS , i2c_show, i2c_store);
static DEVICE_ATTR(i2c3 , SYSFS_PERMISSIONS , i2c_show, i2c_store);
static DEVICE_ATTR(help, SYSFS_PERMISSIONS & SYSFS_READONLY, get_i2c_help, NULL);
static struct attribute *root_dev_attrs[] = {
&dev_attr_alloc0.attr,
&dev_attr_i2c_all.attr,
&dev_attr_alloc0.attr,
&dev_attr_alloc1.attr,
&dev_attr_alloc2.attr,
&dev_attr_alloc3.attr,
......@@ -519,10 +956,14 @@ static struct attribute *root_dev_attrs[] = {
&dev_attr_tbl_rd1.attr,
&dev_attr_tbl_rd2.attr,
&dev_attr_tbl_rd3.attr,
&dev_attr_i2c_rd0.attr,
&dev_attr_i2c_rd1.attr,
&dev_attr_i2c_rd2.attr,
&dev_attr_i2c_rd3.attr,
// &dev_attr_i2c_rd0.attr,
// &dev_attr_i2c_rd1.attr,
// &dev_attr_i2c_rd2.attr,
// &dev_attr_i2c_rd3.attr,
&dev_attr_i2c0.attr,
&dev_attr_i2c1.attr,
&dev_attr_i2c2.attr,
&dev_attr_i2c3.attr,
&dev_attr_help.attr,
NULL
};
......@@ -531,6 +972,32 @@ 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[max_i2c_classes]; /* array of pointers to attibutes */
// struct device_attribute *dev_attrs;
struct attribute_group *attr_group;
attr_group = devm_kzalloc(dev, sizeof(*attr_group), GFP_KERNEL);
if (!attr_group) return -ENOMEM;
// memset(attr_group, 0, sizeof(*attr_group));
pattrs[0] = NULL;
attr_group->name = name;
attr_group->attrs =pattrs;
// dev_dbg(dev,"name=%s, &dev->kobj=0x%08x\n",attr_group->name, (int) (&dev->kobj));
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 int elphel393_sens_i2c_sysfs_register(struct platform_device *pdev)
{
......@@ -538,47 +1005,88 @@ static int elphel393_sens_i2c_sysfs_register(struct platform_device *pdev)
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");
if (((retval = make_group (dev, group_name)))<0) return retval;
dev_dbg(dev,"sysfs_create_group(%s) done \n",group_name);
}
return retval;
}
// =======================================
static void elphel393_sensor_i2c_init_of(struct platform_device *pdev)
{
const __be32 * config_data;
// const __be32 * config_data;
const char * config_string;
char str[40];
int len,chn,pre_disabled,old_dis_por,rc,chn_bits;
struct x393_i2c_device_list * dl;
char name[32];
// int len,chn,pre_disabled,old_dis_por,rc,chn_bits
int rslt;
int num_devs, nd, ni, sa7, num_addr, num_data, khz;
struct device_node *node = pdev->dev.of_node;
// struct elphel393_pwr_data_t *clientdata = platform_get_drvdata(pdev);
// struct i2c_client *ltc3589_client= to_i2c_client(clientdata->ltc3489_dev);
struct device_attribute *new_attr;
struct device *dev =&pdev->dev;
if (node) {
/*TODO: Configure some i2c devices here (slaves, formats, speeds) to be used by names*/
num_devs = of_property_count_strings(node,of_prop_name);
for (nd=0; nd <num_devs; nd++){
if (of_property_read_string_index(node, of_prop_name, nd, &config_string)) {
pr_err("%s: No data for selected i2c device\n", __func__);
BUG();
}
ni = sscanf(config_string,"%31s %i %i %i %i", name, &sa7, &num_addr, &num_data, &khz);
if (ni <5){
pr_err("%s: wrong number of items (%d) in %s, 5 expected\n", __func__, ni, config_string);
BUG();
}
dl = i2c_dev_add (name);
// create sysfs group member
//group_name
new_attr = devm_kzalloc(dev, sizeof(new_attr[0]), GFP_KERNEL);
if (!new_attr) {
pr_err("%s: failed to allocate memory for %s\n", __func__,name);
return;
}
new_attr->attr.name = devm_kzalloc(dev, strlen(name)+1, GFP_KERNEL);
strcpy(new_attr->attr.name, (const char *)name);
new_attr->attr.mode = SYSFS_PERMISSIONS;
new_attr->show = i2c_member_show;
new_attr->store = i2c_member_store;
if (&dev->kobj) {
if ((rslt = sysfs_add_file_to_group (
&dev->kobj,
&new_attr -> attr, // const struct attribute * attr,
group_name))) // const char * group);
{
dev_err(dev,"i2c_class_store() failed to add %s to group %s, returned %d\n",name, group_name, rslt);
return;
}
}
if (dl) {
dl-> i2c_dev.slave7 = sa7;
dl-> i2c_dev.address_bytes = num_addr;
dl-> i2c_dev.data_bytes = num_data;
dl-> i2c_dev.scl_khz = khz;
}
}
}
dev_info(&pdev->dev,"elphel393_sensor_i2c configuration done\n");
pr_info("elphel393_sensor_i2c: registered %d i2c device classes\n", num_devs);
}
static int elphel393_sensor_i2c_probe(struct platform_device *pdev)
{
/*
struct gpio_chip *chip;
// struct device * ltc3489_dev;
int i,rc;
int base[2];
struct i2c_client *ltc3589_client;
struct elphel393_pwr_data_t *clientdata = NULL;
*/
pr_info("Probing elphel393-sensor-i2c\n");
elphel393_sens_i2c_sysfs_register(pdev);
sdev =&pdev->dev;
dev_dbg(&pdev->dev,"Probing elphel393-sensor-i2c\n");
i2c_page_alloc_init();
#if 0
clientdata = devm_kzalloc(&pdev->dev, sizeof(*clientdata), GFP_KERNEL);
#endif
dev_dbg(&pdev->dev,"i2c_page_alloc_init() done\n");
elphel393_sens_i2c_sysfs_register(pdev);
dev_dbg(&pdev->dev,"elphel393_sens_i2c_sysfs_register() done\n");
elphel393_sensor_i2c_init_of(pdev);
pr_info("done probing elphel393-sensor-i2c\n");
dev_dbg(&pdev->dev,"done probing elphel393-sensor-i2c\n");
return 0;
}
......
......@@ -6,7 +6,7 @@
*
* 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
* 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,
......@@ -19,16 +19,13 @@
*******************************************************************************/
// I2C device description to be used with i2c sequencer
/*
typedef struct x393_i2c_device_tag{
char * name;
typedef struct{
char name[32];
u8 slave7; // slave address (7-bit)
u8 address_bytes;
u8 data_bytes;
int scl_khz; // maximal SCL frequency in KHz (currently limited by 200KHz slowest)
struct x393_i2c_device_tag * next;
} x393_i2c_device_t;
*/
/* Reserve i2c page (1 of 256 for a sensor port)*/
int i2c_page_alloc(int chn);
......@@ -36,25 +33,37 @@ int i2c_page_alloc(int chn);
/* Free i2c page */
void i2c_page_free(int chn, int page);
/* Set i2c table entry to raw data (will just overwrite tbl_mode = 2) */
void set_sensor_i2c_raw(int chn,
int page, // index in lookup table
u32 data); // Bit delay - number of mclk periods in 1/4 of the SCL period
/* Set i2c table entry to raw data (will just overwrite tbl_mode = 2)*/
void set_xi2c_raw(int chn,
int page, // index in lookup table
u32 data); // Bit delay - number of mclk periods in 1/4 of the SCL period
/*
* Set i2c table entry for write operation using known devices
* Get device with xi2c_dev_get(), copy and modify, if needed to
* offset slave address or change number of bytes to write, SCL frequency
*/
void set_xi2c_wrc( x393_i2c_device_t * dc, // device class
int chn, // sensor port
int page, // index in lookup table
int rah); // High byte of the i2c register address
/* Set i2c table entry for write operation */
void set_sensor_i2c_wr(int chn,
int page, // index in lookup table
int sa, // slave address (7 bit)
int rah, // High byte of the i2c register address
int num_bytes, //Number of bytes to write (1..10)
int bit_delay); // Bit delay - number of mclk periods in 1/4 of the SCL period
/*
* Set i2c table entry for read operation using known devices
* Get device with xi2c_dev_get(), copy and modify, if needed to
* offset slave address or change number of bytes to write, SCL frequency
*/
void set_xi2c_rdc(x393_i2c_device_t * dc, // device class
int chn, // sensor port
int page); // index in lookup table
/* Set i2c table entry for read operation */
void set_sensor_i2c_rd(int chn,
int page, // index in lookup table
int two_byte_addr, // Number of address bytes (0 - one byte, 1 - two bytes)
int num_bytes, // Number of bytes to read (1..8, 0 means 8)
int bit_delay);// Bit delay - number of mclk periods in 1/4 of the SCL period
void set_xi2c_rd(int chn,
int page, // index in lookup table
int two_byte_addr, // Number of address bytes (0 - one byte, 1 - two bytes)
int num_bytes, // Number of bytes to read (1..8, 0 means 8)
int bit_delay); // Bit delay - number of mclk periods in 1/4 of the SCL period
/*
// Write i2c command to the i2c command sequencer
// I2C command sequencer, block of 16 DWORD slots for absolute frame numbers (modulo 16) and 15 slots for relative ones
......@@ -75,27 +84,52 @@ void set_sensor_i2c_rd(int chn,
/* Write one or multiple DWORDs to i2c relative (modulo16) address. Use offs = 0 for immediate (ASAP) command */
/* Length of data is determined by the page data already preset */
int write_sensor_i2c_rel (int chn,
int offs, // 4 bits
u32 * data);
/* Same to absolute (modulo16) address */
int write_sensor_i2c_abs (int chn,
int offs, // 4 bits
u32 * data);
int write_xi2c_rel (int chn,
int offs, // 4 bits
u32 * data);
int write_xi2c_abs (int chn,
int offs, // 4 bits
u32 * data);
/* Write sensor 16 bit (or 8 bit as programmed in the table) data in immediate mode */
void write_sensor_reg16 (int chn,
int page, // page (8 bits)
int addr, // low 8 bits
u32 data); // 16 or 8-bit data (LSB aligned)
void write_xi2c_reg16 (int chn,
int page, // page (8 bits)
int addr, // low 8 bits
u32 data); // 16 or 8-bit data (LSB aligned)
/* Initiate sensor i2c read in immediate mode (data itself has to be read from FIFO with read_sensor_i2c_fifo)*/
void read_sensor_i2c (int chn,
int page, // page (8 bits)
int sa7, // 7-bit i2c slave address
int addr); // 8/16 bit address
/*
* Initiate sensor i2c read in immediate mode (data itself has to be read from FIFO with read_xi2c_fifo)
* Use slave address from provided class structure.
*/
void read_xi2c (x393_i2c_device_t * dc, // device class
int chn,
int page, // page (8 bits)
int addr); // 8/16 bit address
/* Initiate sensor i2c read in immediate mode (data itself has to be read from FIFO with read_xi2c_fifo)*/
void read_xi2c_sa7 (int chn,
int page, // page (8 bits)
int sa7, // 7-bit i2c slave address
int addr); // 8/16 bit address
/* Read next byte from the channel i2c FIFO. Return byte or -1 if no data available */
/* Sensor channel status should be in auto update mode (3) */
int read_sensor_i2c_fifo(int chn);
int read_xi2c_fifo(int chn);
/* Handling classes of i2c devices */
x393_i2c_device_t * xi2c_dev_get(const char * name);
/* Single-command i2c write/read register using pre-defined device classes */
int x393_xi2c_write_reg(const char * cname, // device class name
int chn, // sensor port number
int sa7_offs, // slave address (7-bit) offset from the class defined slave address
int reg_addr, // register address (width is defined by class)
int data); // data to write (width is defined by class)
int x393_xi2c_read_reg( const char * cname, // device class name
int chn, // sensor port number
int sa7_offs, // slave address (7-bit) offset from the class defined slave address
int reg_addr, // register address (width is defined by class)
int * datap); // pointer to a data receiver (read data width is defined by class)
/*******************************************************************************
* File: x393.c
* Date: 2016-04-06
* Date: 2016-04-19
* Author: auto-generated file, see x393_export_c.py
* Description: Functions definitions to access x393 hardware registers
*******************************************************************************/
......@@ -201,8 +201,8 @@ x393_sensio_tim3_t get_x393_sensio_tim3 (int sens_num)
// in the table. Slave address is always in byte 2 (bits 23:16), byte1 (high register address) is skipped if
// read address in the table is programmed to be a single-byte one
void x393_sensi2c_abs (u32 d, int sens_num, int offset){writel(d, mmio_ptr + (0x1040 + 0x40 * sens_num + 0x1 * offset));} // Write sensor i2c sequencer
void x393_sensi2c_rel (u32 d, int sens_num, int offset){writel(d, mmio_ptr + (0x1080 + 0x40 * sens_num + 0x1 * offset));} // Write sensor i2c sequencer
void x393_sensi2c_abs (u32 d, int sens_num, int offset){writel(d, mmio_ptr + (0x1040 + 0x100 * sens_num + 0x4 * offset));} // Write sensor i2c sequencer
void x393_sensi2c_rel (u32 d, int sens_num, int offset){writel(d, mmio_ptr + (0x1080 + 0x100 * sens_num + 0x4 * offset));} // Write sensor i2c sequencer
// Lens vignetting correction (for each sub-frame separately)
......@@ -394,8 +394,8 @@ u32 get_x393_camsync_trig_delay (int sens_chn)
// [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 + 0x20 * sens_chn + 0x1 * offset));} // CMDFRAMESEQ absolute frame address/command
void x393_cmdframeseq_rel (u32 d, int sens_chn, int offset){writel(d, mmio_ptr + (0x1e40 + 0x20 * sens_chn + 0x1 * offset));} // CMDFRAMESEQ relative frame address/command
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)
......
/*******************************************************************************
* File: x393.h
* Date: 2016-04-06
* Date: 2016-04-19
* Author: auto-generated file, see x393_export_c.py
* Description: Constants definitions and functions declarations to access x393 hardware registers
*******************************************************************************/
......
/*******************************************************************************
* File: x393_defs.h
* Date: 2016-04-06
* Date: 2016-04-19
* Author: auto-generated file, see x393_export_c.py
* Description: Constants and hardware addresses definitions to access x393 hardware registers
*******************************************************************************/
......@@ -158,8 +158,8 @@
// in the table. Slave address is always in byte 2 (bits 23:16), byte1 (high register address) is skipped if
// read address in the table is programmed to be a single-byte one
#define X393_SENSI2C_ABS(sens_num,offset) (0x40001040)+ 0x40 * (sens_num)+ 0x1 * (offset)) // Write sensor i2c sequencer, sens_num = 0..3, offset = 0..15, data type: u32 (wo)
#define X393_SENSI2C_REL(sens_num,offset) (0x40001080)+ 0x40 * (sens_num)+ 0x1 * (offset)) // Write sensor i2c sequencer, sens_num = 0..3, offset = 0..15, data type: u32 (wo)
#define X393_SENSI2C_ABS(sens_num,offset) (0x40001040)+ 0x100 * (sens_num)+ 0x4 * (offset)) // Write sensor i2c sequencer, sens_num = 0..3, offset = 0..15, data type: u32 (wo)
#define X393_SENSI2C_REL(sens_num,offset) (0x40001080)+ 0x100 * (sens_num)+ 0x4 * (offset)) // Write sensor i2c sequencer, sens_num = 0..3, offset = 0..15, data type: u32 (wo)
// Lens vignetting correction (for each sub-frame separately)
......@@ -361,8 +361,8 @@
// [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
#define X393_CMDFRAMESEQ_CTRL(sens_chn) (0x40001e7c + 0x80 * (sens_chn)) // CMDFRAMESEQ control register, sens_chn = 0..3, data type: x393_cmdframeseq_mode_t (wo)
#define X393_CMDFRAMESEQ_ABS(sens_chn,offset) (0x40001e00)+ 0x20 * (sens_chn)+ 0x1 * (offset)) // CMDFRAMESEQ absolute frame address/command, sens_chn = 0..3, offset = 0..15, data type: u32 (wo)
#define X393_CMDFRAMESEQ_REL(sens_chn,offset) (0x40001e40)+ 0x20 * (sens_chn)+ 0x1 * (offset)) // CMDFRAMESEQ relative frame address/command, sens_chn = 0..3, offset = 0..14, data type: u32 (wo)
#define X393_CMDFRAMESEQ_ABS(sens_chn,offset) (0x40001e00)+ 0x80 * (sens_chn)+ 0x4 * (offset)) // CMDFRAMESEQ absolute frame address/command, sens_chn = 0..3, offset = 0..15, data type: u32 (wo)
#define X393_CMDFRAMESEQ_REL(sens_chn,offset) (0x40001e40)+ 0x80 * (sens_chn)+ 0x4 * (offset)) // CMDFRAMESEQ relative frame address/command, sens_chn = 0..3, offset = 0..14, data type: u32 (wo)
// 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
#define X393_CMDSEQMUX_STATUS_CTRL 0x40001c08 // CMDSEQMUX status control mode (status provides current frame numbers), data type: x393_status_ctrl_t (rw)
......
/*******************************************************************************
* File: x393_map.h
* Date: 2016-04-06
* Date: 2016-04-19
* Author: auto-generated file, see x393_export_c.py
* Description: Sorted hardware addresses map
*******************************************************************************/
......
/*******************************************************************************
* File: x393_types.h
* Date: 2016-04-06
* Date: 2016-04-19
* Author: auto-generated file, see x393_export_c.py
* Description: typedef definitions for the x393 hardware registers
*******************************************************************************/
......@@ -294,7 +294,7 @@ typedef union {
u32 hact_alive: 1; // [ 13] (0) HACT signal from the sensor (or internal) is toggling (N/A for HiSPI
u32 hact_ext_alive: 1; // [ 14] (0) HACT signal from the sensor is toggling (N/A for HiSPI)
u32 vact_alive: 1; // [ 15] (0) VACT signal from the sensor is toggling (N/A for HiSPI)
u32 : 8;
u32 xfpgatdo_byte: 8; // [23:16] (0) Multiplexer FPGA TDO output
u32 senspgmin: 1; // [ 24] (0) senspgm pin state
u32 xfpgatdo: 1; // [ 25] (0) Multiplexer FPGA TDO output
u32 seq_num: 6; // [31:26] (0) Sequence number
......@@ -382,7 +382,7 @@ typedef union {
typedef union {
struct {
u32 tbl_addr: 8; // [ 7: 0] (0) Address/length in 64-bit words (<<3 to get byte address)
u32 tbl_addr: 8; // [ 7: 0] (0) I2C table index
u32 :20;
u32 tbl_mode: 2; // [29:28] (3) Should be 3 to select table address write mode
u32 : 2;
......@@ -400,7 +400,7 @@ typedef union {
u32 /*rah*/: 8; // [ 7: 0] (0) High byte of the i2c register address
u32 /*rnw*/: 1; // [ 8] (0) Read/not write i2c register, should be 1 here
u32 : 7;
u32 nbrd: 3; // [18:16] (0) Number of bytes to read (1..18, 0 means '8')
u32 nbrd: 3; // [18:16] (0) Number of bytes to read (1..8, 0 means '8')
u32 nabrd: 1; // [ 19] (0) Number of address bytes for read (0 - one byte, 1 - two bytes)
u32 /*dly*/: 8; // [27:20] (0) Bit delay - number of mclk periods in 1/4 of the SCL period
u32 /*tbl_mode*/: 2; // [29:28] (2) Should be 2 to select table data write mode
......
......@@ -6,7 +6,7 @@
*!
*! 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
*! 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,
......
/*!***************************************************************************
*! FILE NAME : vsc330x.c
*! DESCRIPTION: control of the VSC3304 4x4 crosspoint switch
*! Copyright (C) 2013 Elphel, Inc.
*! Copyright (C) 2013-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 3 of the License, or
*! 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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment