xref: /linux/arch/powerpc/platforms/83xx/mcu_mpc8349emitx.c (revision da5f767ef66a4c71109fdb8577b36864abc3263c)
1ea0105eaSAnton Vorontsov /*
2ea0105eaSAnton Vorontsov  * Power Management and GPIO expander driver for MPC8349E-mITX-compatible MCU
3ea0105eaSAnton Vorontsov  *
4ea0105eaSAnton Vorontsov  * Copyright (c) 2008  MontaVista Software, Inc.
5ea0105eaSAnton Vorontsov  *
6ea0105eaSAnton Vorontsov  * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
7ea0105eaSAnton Vorontsov  *
8ea0105eaSAnton Vorontsov  * This program is free software; you can redistribute it and/or modify
9ea0105eaSAnton Vorontsov  * it under the terms of the GNU General Public License as published by
10ea0105eaSAnton Vorontsov  * the Free Software Foundation; either version 2 of the License, or
11ea0105eaSAnton Vorontsov  * (at your option) any later version.
12ea0105eaSAnton Vorontsov  */
13ea0105eaSAnton Vorontsov 
14ea0105eaSAnton Vorontsov #include <linux/kernel.h>
15ea0105eaSAnton Vorontsov #include <linux/module.h>
16ea0105eaSAnton Vorontsov #include <linux/device.h>
17ea0105eaSAnton Vorontsov #include <linux/mutex.h>
18ea0105eaSAnton Vorontsov #include <linux/i2c.h>
19*da5f767eSLinus Walleij #include <linux/gpio/driver.h>
20ea0105eaSAnton Vorontsov #include <linux/of.h>
21ea0105eaSAnton Vorontsov #include <linux/of_gpio.h>
225a0e3ad6STejun Heo #include <linux/slab.h>
236ca6ca5dSFabio Baltieri #include <linux/kthread.h>
246ca6ca5dSFabio Baltieri #include <linux/reboot.h>
25ea0105eaSAnton Vorontsov #include <asm/prom.h>
26ea0105eaSAnton Vorontsov #include <asm/machdep.h>
27ea0105eaSAnton Vorontsov 
28ea0105eaSAnton Vorontsov /*
29ea0105eaSAnton Vorontsov  * I don't have specifications for the MCU firmware, I found this register
30ea0105eaSAnton Vorontsov  * and bits positions by the trial&error method.
31ea0105eaSAnton Vorontsov  */
32ea0105eaSAnton Vorontsov #define MCU_REG_CTRL	0x20
33ea0105eaSAnton Vorontsov #define MCU_CTRL_POFF	0x40
346ca6ca5dSFabio Baltieri #define MCU_CTRL_BTN	0x80
35ea0105eaSAnton Vorontsov 
36ea0105eaSAnton Vorontsov #define MCU_NUM_GPIO	2
37ea0105eaSAnton Vorontsov 
38ea0105eaSAnton Vorontsov struct mcu {
39ea0105eaSAnton Vorontsov 	struct mutex lock;
40ea0105eaSAnton Vorontsov 	struct i2c_client *client;
41a19e3da5SAnton Vorontsov 	struct gpio_chip gc;
42ea0105eaSAnton Vorontsov 	u8 reg_ctrl;
43ea0105eaSAnton Vorontsov };
44ea0105eaSAnton Vorontsov 
45ea0105eaSAnton Vorontsov static struct mcu *glob_mcu;
46ea0105eaSAnton Vorontsov 
476ca6ca5dSFabio Baltieri struct task_struct *shutdown_thread;
486ca6ca5dSFabio Baltieri static int shutdown_thread_fn(void *data)
496ca6ca5dSFabio Baltieri {
506ca6ca5dSFabio Baltieri 	int ret;
516ca6ca5dSFabio Baltieri 	struct mcu *mcu = glob_mcu;
526ca6ca5dSFabio Baltieri 
536ca6ca5dSFabio Baltieri 	while (!kthread_should_stop()) {
546ca6ca5dSFabio Baltieri 		ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
556ca6ca5dSFabio Baltieri 		if (ret < 0)
566ca6ca5dSFabio Baltieri 			pr_err("MCU status reg read failed.\n");
576ca6ca5dSFabio Baltieri 		mcu->reg_ctrl = ret;
586ca6ca5dSFabio Baltieri 
596ca6ca5dSFabio Baltieri 
606ca6ca5dSFabio Baltieri 		if (mcu->reg_ctrl & MCU_CTRL_BTN) {
616ca6ca5dSFabio Baltieri 			i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,
626ca6ca5dSFabio Baltieri 						  mcu->reg_ctrl & ~MCU_CTRL_BTN);
636ca6ca5dSFabio Baltieri 
646ca6ca5dSFabio Baltieri 			ctrl_alt_del();
656ca6ca5dSFabio Baltieri 		}
666ca6ca5dSFabio Baltieri 
676ca6ca5dSFabio Baltieri 		set_current_state(TASK_INTERRUPTIBLE);
686ca6ca5dSFabio Baltieri 		schedule_timeout(HZ);
696ca6ca5dSFabio Baltieri 	}
706ca6ca5dSFabio Baltieri 
716ca6ca5dSFabio Baltieri 	return 0;
726ca6ca5dSFabio Baltieri }
736ca6ca5dSFabio Baltieri 
746ca6ca5dSFabio Baltieri static ssize_t show_status(struct device *d,
756ca6ca5dSFabio Baltieri 			   struct device_attribute *attr, char *buf)
766ca6ca5dSFabio Baltieri {
776ca6ca5dSFabio Baltieri 	int ret;
786ca6ca5dSFabio Baltieri 	struct mcu *mcu = glob_mcu;
796ca6ca5dSFabio Baltieri 
806ca6ca5dSFabio Baltieri 	ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
816ca6ca5dSFabio Baltieri 	if (ret < 0)
826ca6ca5dSFabio Baltieri 		return -ENODEV;
836ca6ca5dSFabio Baltieri 	mcu->reg_ctrl = ret;
846ca6ca5dSFabio Baltieri 
856ca6ca5dSFabio Baltieri 	return sprintf(buf, "%02x\n", ret);
866ca6ca5dSFabio Baltieri }
876ca6ca5dSFabio Baltieri static DEVICE_ATTR(status, S_IRUGO, show_status, NULL);
886ca6ca5dSFabio Baltieri 
89ea0105eaSAnton Vorontsov static void mcu_power_off(void)
90ea0105eaSAnton Vorontsov {
91ea0105eaSAnton Vorontsov 	struct mcu *mcu = glob_mcu;
92ea0105eaSAnton Vorontsov 
93ea0105eaSAnton Vorontsov 	pr_info("Sending power-off request to the MCU...\n");
94ea0105eaSAnton Vorontsov 	mutex_lock(&mcu->lock);
956ca6ca5dSFabio Baltieri 	i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL,
96ea0105eaSAnton Vorontsov 				  mcu->reg_ctrl | MCU_CTRL_POFF);
97ea0105eaSAnton Vorontsov 	mutex_unlock(&mcu->lock);
98ea0105eaSAnton Vorontsov }
99ea0105eaSAnton Vorontsov 
100ea0105eaSAnton Vorontsov static void mcu_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
101ea0105eaSAnton Vorontsov {
102*da5f767eSLinus Walleij 	struct mcu *mcu = gpiochip_get_data(gc);
103ea0105eaSAnton Vorontsov 	u8 bit = 1 << (4 + gpio);
104ea0105eaSAnton Vorontsov 
105ea0105eaSAnton Vorontsov 	mutex_lock(&mcu->lock);
106ea0105eaSAnton Vorontsov 	if (val)
107ea0105eaSAnton Vorontsov 		mcu->reg_ctrl &= ~bit;
108ea0105eaSAnton Vorontsov 	else
109ea0105eaSAnton Vorontsov 		mcu->reg_ctrl |= bit;
110ea0105eaSAnton Vorontsov 
111ea0105eaSAnton Vorontsov 	i2c_smbus_write_byte_data(mcu->client, MCU_REG_CTRL, mcu->reg_ctrl);
112ea0105eaSAnton Vorontsov 	mutex_unlock(&mcu->lock);
113ea0105eaSAnton Vorontsov }
114ea0105eaSAnton Vorontsov 
115ea0105eaSAnton Vorontsov static int mcu_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
116ea0105eaSAnton Vorontsov {
117ea0105eaSAnton Vorontsov 	mcu_gpio_set(gc, gpio, val);
118ea0105eaSAnton Vorontsov 	return 0;
119ea0105eaSAnton Vorontsov }
120ea0105eaSAnton Vorontsov 
121ea0105eaSAnton Vorontsov static int mcu_gpiochip_add(struct mcu *mcu)
122ea0105eaSAnton Vorontsov {
123ea0105eaSAnton Vorontsov 	struct device_node *np;
124a19e3da5SAnton Vorontsov 	struct gpio_chip *gc = &mcu->gc;
125ea0105eaSAnton Vorontsov 
126ea0105eaSAnton Vorontsov 	np = of_find_compatible_node(NULL, NULL, "fsl,mcu-mpc8349emitx");
127ea0105eaSAnton Vorontsov 	if (!np)
128ea0105eaSAnton Vorontsov 		return -ENODEV;
129ea0105eaSAnton Vorontsov 
130ea0105eaSAnton Vorontsov 	gc->owner = THIS_MODULE;
131ea0105eaSAnton Vorontsov 	gc->label = np->full_name;
132ea0105eaSAnton Vorontsov 	gc->can_sleep = 1;
133ea0105eaSAnton Vorontsov 	gc->ngpio = MCU_NUM_GPIO;
134ea0105eaSAnton Vorontsov 	gc->base = -1;
135ea0105eaSAnton Vorontsov 	gc->set = mcu_gpio_set;
136ea0105eaSAnton Vorontsov 	gc->direction_output = mcu_gpio_dir_out;
137594fa265SGrant Likely 	gc->of_node = np;
138ea0105eaSAnton Vorontsov 
139*da5f767eSLinus Walleij 	return gpiochip_add_data(gc, mcu);
140ea0105eaSAnton Vorontsov }
141ea0105eaSAnton Vorontsov 
142ea0105eaSAnton Vorontsov static int mcu_gpiochip_remove(struct mcu *mcu)
143ea0105eaSAnton Vorontsov {
14488d5e520Sabdoulaye berthe 	gpiochip_remove(&mcu->gc);
14588d5e520Sabdoulaye berthe 	return 0;
146ea0105eaSAnton Vorontsov }
147ea0105eaSAnton Vorontsov 
148cad5cef6SGreg Kroah-Hartman static int mcu_probe(struct i2c_client *client, const struct i2c_device_id *id)
149ea0105eaSAnton Vorontsov {
150ea0105eaSAnton Vorontsov 	struct mcu *mcu;
151ea0105eaSAnton Vorontsov 	int ret;
152ea0105eaSAnton Vorontsov 
153ea0105eaSAnton Vorontsov 	mcu = kzalloc(sizeof(*mcu), GFP_KERNEL);
154ea0105eaSAnton Vorontsov 	if (!mcu)
155ea0105eaSAnton Vorontsov 		return -ENOMEM;
156ea0105eaSAnton Vorontsov 
157ea0105eaSAnton Vorontsov 	mutex_init(&mcu->lock);
158ea0105eaSAnton Vorontsov 	mcu->client = client;
159ea0105eaSAnton Vorontsov 	i2c_set_clientdata(client, mcu);
160ea0105eaSAnton Vorontsov 
161ea0105eaSAnton Vorontsov 	ret = i2c_smbus_read_byte_data(mcu->client, MCU_REG_CTRL);
162ea0105eaSAnton Vorontsov 	if (ret < 0)
163ea0105eaSAnton Vorontsov 		goto err;
164ea0105eaSAnton Vorontsov 	mcu->reg_ctrl = ret;
165ea0105eaSAnton Vorontsov 
166ea0105eaSAnton Vorontsov 	ret = mcu_gpiochip_add(mcu);
167ea0105eaSAnton Vorontsov 	if (ret)
168ea0105eaSAnton Vorontsov 		goto err;
169ea0105eaSAnton Vorontsov 
1709178ba29SAlexander Graf 	/* XXX: this is potentially racy, but there is no lock for pm_power_off */
1719178ba29SAlexander Graf 	if (!pm_power_off) {
172ea0105eaSAnton Vorontsov 		glob_mcu = mcu;
1739178ba29SAlexander Graf 		pm_power_off = mcu_power_off;
174ea0105eaSAnton Vorontsov 		dev_info(&client->dev, "will provide power-off service\n");
175ea0105eaSAnton Vorontsov 	}
176ea0105eaSAnton Vorontsov 
1776ca6ca5dSFabio Baltieri 	if (device_create_file(&client->dev, &dev_attr_status))
1786ca6ca5dSFabio Baltieri 		dev_err(&client->dev,
1796ca6ca5dSFabio Baltieri 			"couldn't create device file for status\n");
1806ca6ca5dSFabio Baltieri 
1816ca6ca5dSFabio Baltieri 	shutdown_thread = kthread_run(shutdown_thread_fn, NULL,
1826ca6ca5dSFabio Baltieri 				      "mcu-i2c-shdn");
1836ca6ca5dSFabio Baltieri 
184ea0105eaSAnton Vorontsov 	return 0;
185ea0105eaSAnton Vorontsov err:
186ea0105eaSAnton Vorontsov 	kfree(mcu);
187ea0105eaSAnton Vorontsov 	return ret;
188ea0105eaSAnton Vorontsov }
189ea0105eaSAnton Vorontsov 
190cad5cef6SGreg Kroah-Hartman static int mcu_remove(struct i2c_client *client)
191ea0105eaSAnton Vorontsov {
192ea0105eaSAnton Vorontsov 	struct mcu *mcu = i2c_get_clientdata(client);
193ea0105eaSAnton Vorontsov 	int ret;
194ea0105eaSAnton Vorontsov 
1956ca6ca5dSFabio Baltieri 	kthread_stop(shutdown_thread);
1966ca6ca5dSFabio Baltieri 
1976ca6ca5dSFabio Baltieri 	device_remove_file(&client->dev, &dev_attr_status);
1986ca6ca5dSFabio Baltieri 
199ea0105eaSAnton Vorontsov 	if (glob_mcu == mcu) {
2009178ba29SAlexander Graf 		pm_power_off = NULL;
201ea0105eaSAnton Vorontsov 		glob_mcu = NULL;
202ea0105eaSAnton Vorontsov 	}
203ea0105eaSAnton Vorontsov 
204ea0105eaSAnton Vorontsov 	ret = mcu_gpiochip_remove(mcu);
205ea0105eaSAnton Vorontsov 	if (ret)
206ea0105eaSAnton Vorontsov 		return ret;
207ea0105eaSAnton Vorontsov 	kfree(mcu);
208ea0105eaSAnton Vorontsov 	return 0;
209ea0105eaSAnton Vorontsov }
210ea0105eaSAnton Vorontsov 
211ea0105eaSAnton Vorontsov static const struct i2c_device_id mcu_ids[] = {
212ea0105eaSAnton Vorontsov 	{ "mcu-mpc8349emitx", },
213ea0105eaSAnton Vorontsov 	{},
214ea0105eaSAnton Vorontsov };
215ea0105eaSAnton Vorontsov MODULE_DEVICE_TABLE(i2c, mcu_ids);
216ea0105eaSAnton Vorontsov 
217ce6d73c9SUwe Kleine-König static const struct of_device_id mcu_of_match_table[] = {
2182ffe8c5fSGrant Likely 	{ .compatible = "fsl,mcu-mpc8349emitx", },
2192ffe8c5fSGrant Likely 	{ },
2202ffe8c5fSGrant Likely };
2212ffe8c5fSGrant Likely 
222ea0105eaSAnton Vorontsov static struct i2c_driver mcu_driver = {
223ea0105eaSAnton Vorontsov 	.driver = {
224ea0105eaSAnton Vorontsov 		.name = "mcu-mpc8349emitx",
225ea0105eaSAnton Vorontsov 		.owner = THIS_MODULE,
2262ffe8c5fSGrant Likely 		.of_match_table = mcu_of_match_table,
227ea0105eaSAnton Vorontsov 	},
228ea0105eaSAnton Vorontsov 	.probe = mcu_probe,
229cad5cef6SGreg Kroah-Hartman 	.remove	= mcu_remove,
230ea0105eaSAnton Vorontsov 	.id_table = mcu_ids,
231ea0105eaSAnton Vorontsov };
232ea0105eaSAnton Vorontsov 
23398c7355fSWei Yongjun module_i2c_driver(mcu_driver);
234ea0105eaSAnton Vorontsov 
235ea0105eaSAnton Vorontsov MODULE_DESCRIPTION("Power Management and GPIO expander driver for "
236ea0105eaSAnton Vorontsov 		   "MPC8349E-mITX-compatible MCU");
237ea0105eaSAnton Vorontsov MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>");
238ea0105eaSAnton Vorontsov MODULE_LICENSE("GPL");
239