adxl345_driver/adxl345.c

372 lines
11 KiB
C

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#define FIFO_SIZE 64
// TODO: Déclarer toutes les variables en début de code pour faire plaisir à GCC90.
// TODO: Changer la déclaration des variables utiles à l'i2c
// Prototypes +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ssize_t adxl345_read(struct file *, char __user *, size_t, loff_t *);
// Structures +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
struct Sample {
int16_t x;
int16_t y;
int16_t z;
};
struct Fifo {
struct Sample content[FIFO_SIZE];
int read_idx;
int write_idx;
};
struct adxl345_device{
struct Fifo fifo;
struct miscdevice miscdev;
};
struct file_operations adxl345_fops = {
.owner = THIS_MODULE,
.read = adxl345_read,
};
// FIFO management functions ++++++++++++++++++++++++++++++++++++++++++
static void fifo_init(struct Fifo *f) {
f->read_idx = 0;
f->write_idx = 0;
}
static int fifo_len(struct Fifo *f) {
if (f->write_idx >= f->read_idx) {
return f->write_idx - f->read_idx;
} else {
return FIFO_SIZE - f->read_idx + f->write_idx + 1;
}
}
static void fifo_push(struct Fifo *f, struct Sample *s) {
f->content[f->write_idx] = *s;
f->write_idx = (f->write_idx + 1) % FIFO_SIZE;
// Push read index because we keep only the {FIFO_SIZE} last samples
if(f->write_idx == f->read_idx){
f->read_idx = (f->read_idx + 1) % FIFO_SIZE;
}
}
static struct Sample fifo_pop(struct Fifo *f) {
struct Sample res = f->content[f->read_idx];
f->read_idx = (f->read_idx + 1) % FIFO_SIZE;
return res;
}
// Fonctions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ssize_t adxl345_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos){
char nsample;
char rest;
unsigned char res[FIFO_SIZE*6]; // Not res[count] to avoid C99 warning
int i=0;
int16_t direction_sample;
struct Sample sample;
int ret;
// TODO: permetre de choisir l'axe de lecture
//pr_info("In adxl345_read function\n");
// Recuperation de la structure adxl345
struct adxl345_device *d = container_of(file->private_data,struct adxl345_device,miscdev);
//pr_info("Got pointer to adxl345_device structure\n");
// Compute the number of complete samples (6 bytes) to get
nsample = count/6;
rest = count%6;
// Adjust what to get depending on the number of sample in the fifo
if(fifo_len(&(d->fifo))<=nsample){
nsample = fifo_len(&(d->fifo));
rest = 0;
}
//pr_info("%i sample in fifo\nGetting %i full sample and %i more bytes\n",fifo_len(&(d->fifo)),nsample,rest);
// Get the full samples
for(i=0;i<nsample;i++){
//pr_info("Getting sample %i\n",i);
sample = fifo_pop(&(d->fifo));
res[i*6+1] = (unsigned char) (sample.x>>8);
res[i*6+0] = (unsigned char) (sample.x & 0x00ff);
res[i*6+3] = (unsigned char) (sample.y>>8);
res[i*6+2] = (unsigned char) (sample.y & 0x00ff);
res[i*6+5] = (unsigned char) (sample.z>>8);
res[i*6+4] = (unsigned char) (sample.z & 0x00ff);
//pr_info("%i %i %i\n",sample.x,sample.y,sample.z);
}
// Get the rest
if(rest>0){
sample = fifo_pop(&(d->fifo));
}
for(i=nsample*6;i<nsample*6+rest;i++){
//pr_info("Getting Byte %i/%i\n",i,nsample*6+rest);
// Get the correct direction
if(i-nsample*6<2){direction_sample = sample.x;} // X
else if(i-nsample*6<4){direction_sample = sample.y;} // Y
else{direction_sample = sample.z;} // Z
// Extract the correct byte from the direction sample
res[i] = direction_sample & (0xff<<(i%2)*2);
}
// Ecriture du resultat dans le buffer de retour
//pr_info("%x %x %x %x\n",res[3], res[2], res[1],res[0]);
if((ret = copy_to_user(buf, res, nsample*6+rest)) > 0){
pr_info("%i bytes not copied to user\n",ret);
return(-1);
}
return (ssize_t)nsample*6+rest;
}
irq_handler_t adxl345_int(int irq, struct adxl345_device * d){
struct Sample sample;
char question = 0x39;
unsigned char response[6];
char ret_code[2];
char nentries;
char nsample;
char i;
struct miscdevice miscdev = d->miscdev;
struct device *dev = miscdev.parent;
struct i2c_client *client = container_of(dev,struct i2c_client,dev);
// Get number of entries in Hard FIFO
ret_code[0] = i2c_master_send(client,&question,1);
ret_code[1] = i2c_master_recv(client,response,1);
if(ret_code[0] < 0 || ret_code[1] < 0){
pr_info("Error in reading FIFO STATUS: %i %i\n",ret_code[0], ret_code[1]);
}
nentries = response[0] & 0x3f;
//pr_info("Getting %i sample from the Hard FIFO:\n",nentries);
for(i=nentries; i>0; i--){
// Get entries from Hard FIFO
question = 0x32;
ret_code[0] = i2c_master_send(client,&question,1);
ret_code[1] = i2c_master_recv(client,response,6);
sample.x = (int16_t)(response[1]<<8 | response[0]);
sample.y = (int16_t)(response[3]<<8 | response[2]);
sample.z = (int16_t)(response[5]<<8 | response[4]);
//pr_info(" Nouveau Sample: X=%i Y=%i Z=%i\n",sample.x,sample.y,sample.z);
fifo_push(&(d->fifo), &sample);
}
nsample = fifo_len(&(d->fifo));
//pr_info("%i samples in Soft FIFO\n",nsample);
return (void *)IRQ_HANDLED;
}
static int adxl345_probe(struct i2c_client *client,const struct i2c_device_id *id){
// i2c variables
char question[2];
char response[1];
char ret_code;
// Interuption variables
void *int_ptr = &adxl345_int;
const char *device_name = "adxl345";
int code;
// Init adxl345_device
struct adxl345_device *d;
d = (struct adxl345_device *) kzalloc(sizeof(struct adxl345_device), GFP_KERNEL);
pr_info("adxl_345_device and file_operation struct allocated\n");
// Init FIFO
fifo_init(&(d->fifo));
pr_info("Fifo Initialized\n");
// Init i2c_client
i2c_set_clientdata(client,d);
pr_info("i2c_clientdata set\n");
// Populate miscdev fields
d->miscdev.parent = &(client->dev);
d->miscdev.minor = MISC_DYNAMIC_MINOR;
d->miscdev.name = "adxl345";
d->miscdev.fops = &adxl345_fops;
//pr_info("miscdev fields set\n");
// Registering to misc
misc_register(&(d->miscdev));
//pr_info("misc_register set\n");
//pr_info("Misc minor is: %i\n",d->miscdev.minor);
// Verify device ID
question[0] = 0x00;
question[1] = 0x00;
i2c_master_send(client,question,1);
ret_code = i2c_master_recv(client,response,1);
if(ret_code < 0){
pr_info("i2c return code: %i\n",ret_code);
}
//pr_info("DEVID: %x\n",*response);
// Set device parameters
// Power control measure mode
question[0] = 0x2D;
question[1] = 0x08;
ret_code = i2c_master_send(client,question,2);
if(ret_code < 0){
pr_info("Error when setting Power Control parameter.\nReturn code: %i\n", ret_code);
}
// BW_RATE = 100 Hz
question[0] = 0x2C;
question[1] = 0x0A;
ret_code = i2c_master_send(client,question,2);
if(ret_code < 0){
pr_info("Error when setting Data Rate parameter.\nReturn code: %i\n", ret_code);
}
// Data Format
question[0] = 0x31;
question[1] = 0x00;
ret_code = i2c_master_send(client,question,2);
if(ret_code < 0){
pr_info("Error when setting Data Format parameter.\nReturn code: %i\n", ret_code);
}
// Set FIFO to Stream mode with 20 sample
question[0] = 0x38;
question[1] = 0x94;
ret_code = i2c_master_send(client,question,2);
if(ret_code < 0){
pr_info("Error when setting FIFO mode parameter.\nReturn code: %i\n", ret_code);
}
// Watermark Interruption Enabled
question[0] = 0x2e;
question[1] = 0x02;
ret_code = i2c_master_send(client,question,2);
if(ret_code < 0){
pr_info("Error when setting Interuptions parameter.\nReturn code: %i\n", ret_code);
}
pr_info("Device Setting done.\n");
// Interruption Handler:
code = devm_request_threaded_irq(&(client->dev),client->irq, NULL, int_ptr, IRQF_ONESHOT, device_name, d);
//pr_info("irq declaration done. Irq number: %i\n",client->irq);
//pr_info("irq request return code: %i\n",code);
pr_info("ADXL345 device setup done.\n");
return(0);
}
static int adxl345_remove(struct i2c_client *client)
{
// Allocate question(address), response, and return code. All Char
struct adxl345_device *d;
char question[2];
char ret_code;
// Power control measure mode
question[0] = 0x2D;
question[1] = 0x00;
ret_code = i2c_master_send(client,question,2);
if(ret_code < 0){
pr_info("Error when setting device in standby mode.\nReturn code: %i\n", ret_code);
return(1);
}
else{
pr_info("ADXL345 Device put to standby mode.");
}
// récupération de l'instance de adxl345_device
d = i2c_get_clientdata(client);
// Désenregistrement de misc
misc_deregister(&(d->miscdev));
pr_info("ADXL345 Device unregistered.\n");
return(0);
}
/* La liste suivante permet l'association entre un périphérique et son
pilote dans le cas d'une initialisation statique sans utilisation de
device tree.
Chaque entrée contient une chaîne de caractère utilisée pour
faire l'association et un entier qui peut être utilisé par le
pilote pour effectuer des traitements différents en fonction
du périphérique physique détecté (cas d'un pilote pouvant gérer
différents modèles de périphérique).
*/
static struct i2c_device_id adxl345_idtable[] = {
{ "adxl345", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adxl345_idtable);
#ifdef CONFIG_OF
/* Si le support des device trees est disponible, la liste suivante
permet de faire l'association à l'aide du device tree.
Chaque entrée contient une structure de type of_device_id. Le champ
compatible est une chaîne qui est utilisée pour faire l'association
avec les champs compatible dans le device tree. Le champ data est
un pointeur void* qui peut être utilisé par le pilote pour
effectuer des traitements différents en fonction du périphérique
physique détecté.
*/
static const struct of_device_id adxl345_of_match[] = {
{ .compatible = "tp,testi2cdev",
.data = NULL },
{}
};
MODULE_DEVICE_TABLE(of, adxl345_of_match);
#endif
static struct i2c_driver adxl345_driver = {
.driver = {
/* Le champ name doit correspondre au nom du module
et ne doit pas contenir d'espace */
.name = "adxl345",
.of_match_table = of_match_ptr(adxl345_of_match),
},
.id_table = adxl345_idtable,
.probe = adxl345_probe,
.remove = adxl345_remove,
};
module_i2c_driver(adxl345_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ADXL345 driver");
MODULE_AUTHOR("Arthur Grisel-Davy");