“This is the 21st day of my participation in the First Challenge 2022. For details: First Challenge 2022”

1. Introduction to MMA7660 chip

The MMA7660FC is a ± 1.5 g three-axis digital output, ultra-low power, compact capacitive micro-motor three-axis accelerometer, is a very low power, small capacitive MEMS sensor. Features a low-pass filter for offset and gain error compensation, as well as user-configurable conversion to 6-bit resolution, user-configurable output rate, and more. The MMA7660 chip can notify the sensor of data change, direction, attitude recognition and other information through the interrupt pin (INT). Analog operating voltage range is 2.4V to 3.6V, digital operating voltage range is 1.71V to 3.6V. Commonly used in mobile phones, handheld computers, vehicle-mounted navigation, portable computer anti-theft, automatic bicycle brake lights, motion detection bracelets, digital machines, automatic wake-up alarm clocks and so on.

In particular, the function of step counting is now the most common, whether it is smart bracelet, or mobile phone with three axis accelerometer, can record the number of steps every day, calculate the amount of exercise, etc.. Now a lot of tumblers, drones, camera head, many common products can see the figure of the three-axis accelerometer.

Many projects can be made through MMA7660: for example, anti-fall bracelets for the elderly, automatic brake lights for bicycles, intelligent alarm clocks, automatic power off for oven falls, sports bracelets and so on.

This paper introduces how to write the driver of MMA7660 three axis acceleration chip under Linux, read the direction and attitude of the current chip, and get the data of X,Y and Z three axes. MMA7660 is an IIC interface. The current driver uses the standard IIC subsystem to write the driver, and uses the character device framework to upload the data obtained to the application layer.

2. Connect hardware cables

The development board currently used is The Friendly Arm Tiny4412 development board, using Samsung EXYNOS4412 chip, the board itself has a MMA7660 chip, the schematic diagram of the chip is as follows:

The kernel itself has the MMA7660 driver, the following is the source path:

If you load a driver written by yourself, you need to remove the driver from the original kernel; otherwise, the match cannot be matched.

Device Drivers --> <*> Hardware Monitoring Support --> <*> Freescale MMA7660 AccelerometerCopy the code

3. The source code

3.1 MMA7660 device side code: IIC subsystem

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>static struct i2c_client *i2cClient = NULL;
​
static unsigned short  i2c_addr_list[]= {0x4c, I2C_CLIENT_END};/* Address queue *//* 1. Obtain controller (bus) 2. Detect whether the device exists 3. Define a name for finding the driver */
static int __init mma7660_dev_init(void)
{
    /*mach-tiny4412.c*/
    struct i2c_adapter *i2c_adap=NULL;  /* The obtained bus is stored in this structure */
    struct i2c_board_info i2c_info;     /* Device description structure, which stores the device name and address *//*1. Obtain the IIC controller */
    i2c_adap = i2c_get_adapter(3);     /* Use IIC_3 bus */
    if(! i2c_adap) {printk("Failed to obtain IIC controller information! \n");
        return - 1;
    }
    
    memset(&i2c_info,0.sizeof(struct i2c_board_info));          /* Empty the structure */
    strlcpy(i2c_info.type,"mma7660_drv",I2C_NAME_SIZE);    /* Name assignment */
    i2c_info.irq=EXYNOS4_GPX3(1); /* Interrupts the IO port *//*2. Create an IIC device client */
    i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL);
    if(! i2cClient) {printk("Mma7660_ probe address error!! \n");
        return - 1;
    }
​
    i2c_put_adapter(i2c_adap);/* Sets the module usage count */
    
    printk("mma7660_dev_init!! \n");
    return 0;
}
​
​
static void __exit mma7660_dev_exit(void)// Platform device exit function
{
    printk(" mma7660_dev_exit ok!! \n");
​
    /* Unregister the device */
    i2c_unregister_device(i2cClient);
​
    / * * / release
    i2c_release_client(i2cClient);
}
​
​
module_init(mma7660_dev_init);
module_exit(mma7660_dev_exit);
MODULE_LICENSE("GPL");
Copy the code

3.2 MMA7660 driver code: IIC subsystem

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/workqueue.h>
#include <linux/delay.h>

/* MMA7760 Registers */
#define MMA7660_XOUT			0x00	// 6-bit output value X
#define MMA7660_YOUT			0x01	// 6-bit output value Y
#define MMA7660_ZOUT			0x02	// 6-bit output value Z
#define MMA7660_TILT			0x03	// Tilt status
#define MMA7660_SRST			0x04	// Sampling Rate Status
#define MMA7660_SPCNT			0x05	// Sleep Count
#define MMA7660_INTSU			0x06	// Interrupt Setup
#define MMA7660_MODE			0x07	// Mode
#define MMA7660_SR				0x08	// Auto-Wake/Sleep and Debounce Filter
#define MMA7660_PDET			0x09	// Tap Detection
#define MMA7660_PD				0x0a	// Tap Debounce Count

static const struct i2c_device_id mma7660_id[] ={{"mma7660_drv".0}, /* The name of the device,0 means that no private data is required */{}};static u32 mma7660_irq; /* Touch screen interrupt number */
static struct i2c_client *mma7660_client=NULL;
static int	last_tilt = 0;

#define __need_retry(__v)	(__v & (1 << 6))
#define __is_negative(__v)	(__v & (1 << 5))

static const char *mma7660_bafro[] = {
	"Unknown"."Front"."On the back"
};

static const char *mma7660_pola[] = {
	"Unknown"."The left"."Right"."Reserved"."Reserved"."Down"."向上"."Reserved"};/* Reads one byte of data */
static int mma7660_read_tilt(struct i2c_client *client, int *tilt)
{
	int val;
	do {
		val = i2c_smbus_read_byte_data(client, MMA7660_TILT);
	} while (__need_retry(val));
	*tilt = (val & 0xff);
	return 0;
}


/* Function: read XYZ coordinate data */
static int mma7660_read_xyz(struct i2c_client *client, int idx, int *xyz)
{
	int val;
	do {
		val = i2c_smbus_read_byte_data(client, idx + MMA7660_XOUT);
	} while (__need_retry(val));
	*xyz = __is_negative(val) ? (val | ~0x3f) : (val & 0x3f);
	return 0;
}


/* Work queue handler function */
static void mma7660_worker(struct work_struct *work)
{
	int bafro, pola, shake, tap;
	int val = 0;

	mma7660_read_tilt(mma7660_client,&val);

	/ *TODO:report it ? * /
	bafro = val & 0x03;
	if(bafro ! = (last_tilt &0x03)) {
		printk("%s\n", mma7660_bafro[bafro]);
	}

	pola = (val >> 2) & 0x07;
	if(pola ! = ((last_tilt >>2) & 0x07)) {
		printk("%s\n", mma7660_pola[pola]);
	}

	shake = (val >> 5) & 0x01;
	if(shake && shake ! = ((last_tilt >>5) & 0x01)) {
		printk("Shake\n");
	}

	tap = (val >> 7) & 0x01;
	if(tap && tap ! = ((last_tilt >>7) & 0x01)) {
		printk("Tap\n");
	}

	/* Save current status */
	last_tilt = val;
	
	int axis[3];
	int i;
	for (i = 0; i < 3; i++)
	{
		mma7660_read_xyz(mma7660_client, i, &axis[i]);
	}
	printk("ABS_X=%d\n",axis[0]);
	printk("ABS_Y=%d\n",axis[1]);
	printk("ABS_Z=%d\n",axis[2]);
}


/* Mma7660 initialization */
static int mma7660_initialize(struct i2c_client *client)
{
	int val;

	/* Using test mode to probe chip */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
	mdelay(10);
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04);
	mdelay(10);
	i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f);
	i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x01);
	i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x15);
	val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT);
	if(val ! =0x15) {
		dev_err(&client->dev, "no device\n");
		return -ENODEV;
	}

	/* Goto standby mode for configuration */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
	mdelay(10);

	/* Sample rate: 64Hz / 16Hz; Filt: 3 samples */
	i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1));

	/* Sleep count */
	i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0);

	/* Tap detect and debounce ~4ms */
	i2c_smbus_write_byte_data(client, MMA7660_PDET, 4);
	i2c_smbus_write_byte_data(client, MMA7660_PD, 15);

	/* Enable interrupt except exiting Auto-Sleep */
	i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7);

	/* IPP, Auto-wake, auto-sleep and standby */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59);
	mdelay(10);

	/* Save current tilt status */
	mma7660_read_tilt(client, &last_tilt);

	mma7660_client = client;
	return 0;
}


/* Statically initializes the work queue */
DECLARE_WORK(mma7660_work,mma7660_worker);
static irqreturn_t mma7660_interrupt(int irq, void *dev_id)
{
	/* Schedule a shared work queue */
	schedule_work(&mma7660_work);
	return IRQ_HANDLED;
}


*/ is called when the match is successful
static int mma7660_probe(struct i2c_client *client, const struct i2c_device_id *device_id)
{
	printk("mma7660_probe!!! \n");
	printk("Driver IIC matching address =0x%x\n",client->addr);
	
	mma7660_client=client;
	
	/*1. Registration is interrupted */
	mma7660_irq=gpio_to_irq(client->irq);/* Get the interrupt number */
    if(request_irq(mma7660_irq,mma7660_interrupt,IRQF_TRIGGER_FALLING,"mma7660_irq".NULL)! =0)
    {
		printk("Mma7660_ failed to interrupt registration! \n");
	}
	
	/*2. Initialize mma7660*/
	if(mma7660_initialize(client) < 0)
	{
		printk("Failed to initialize mMA7660! \n");
	}
    return 0;
}


static int mma7660_remove(struct i2c_client *client)
{
	free_irq(mma7660_irq,NULL);
	printk("mma7660_remove!!! \n");
	return 0;
}


struct i2c_driver i2c_drv =
{
	.driver =
	{
		.name = "mma7660",
		.owner = THIS_MODULE,
	},	
	.probe = mma7660_probe,   // Probe function
	.remove = mma7660_remove, // Unmount resources
	.id_table = mma7660_id,   // There is an argument with a name to match the device name
};


static int __init mma7660_drv_init(void)
{
	/* Register a driver with the IIC bus */
	i2c_add_driver(&i2c_drv);
	return 0;
}

static void __exit mma7660_drv_exit(void)
{
	/* Unregister a driver from the IIC bus */
	i2c_del_driver(&i2c_drv);
}

module_init(mma7660_drv_init);
module_exit(mma7660_drv_exit);
MODULE_LICENSE("GPL");
Copy the code