[PATCH] [POWERPC] 83xx: Support the MCU on the Thecus N1200 board

Byron Bradley byron.bbradley at gmail.com
Fri Jun 5 07:00:29 EST 2009


The Thecus N1200 has a board control PIC on the I2C bus. This controls
two of the LEDs, the buzzer and allows the board to poweroff.

Signed-off-by: Byron Bradley <byron.bbradley at gmail.com>
---
 arch/powerpc/configs/83xx/thecus_n1200_defconfig |    3 +-
 arch/powerpc/platforms/83xx/Makefile             |    1 +
 arch/powerpc/platforms/83xx/mcu_thecus_n1200.c   |  307 ++++++++++++++++++++++
 arch/powerpc/platforms/Kconfig                   |    9 +
 4 files changed, 319 insertions(+), 1 deletions(-)
 create mode 100644 arch/powerpc/platforms/83xx/mcu_thecus_n1200.c

diff --git a/arch/powerpc/configs/83xx/thecus_n1200_defconfig b/arch/powerpc/configs/83xx/thecus_n1200_defconfig
index b0c3a15..a0731e6 100644
--- a/arch/powerpc/configs/83xx/thecus_n1200_defconfig
+++ b/arch/powerpc/configs/83xx/thecus_n1200_defconfig
@@ -1,7 +1,7 @@
 #
 # Automatically generated make config: don't edit
 # Linux kernel version: 2.6.30-rc6
-# Fri May 22 18:36:51 2009
+# Fri May 22 19:01:15 2009
 #
 # CONFIG_PPC64 is not set
 
@@ -217,6 +217,7 @@ CONFIG_IPIC=y
 CONFIG_MPC8xxx_GPIO=y
 # CONFIG_SIMPLE_GPIO is not set
 # CONFIG_MCU_MPC8349EMITX is not set
+CONFIG_MCU_THECUS_N1200=m
 
 #
 # Kernel options
diff --git a/arch/powerpc/platforms/83xx/Makefile b/arch/powerpc/platforms/83xx/Makefile
index 2f2d522..682797c 100644
--- a/arch/powerpc/platforms/83xx/Makefile
+++ b/arch/powerpc/platforms/83xx/Makefile
@@ -15,4 +15,5 @@ obj-$(CONFIG_MPC837x_MDS)	+= mpc837x_mds.o
 obj-$(CONFIG_SBC834x)		+= sbc834x.o
 obj-$(CONFIG_MPC837x_RDB)	+= mpc837x_rdb.o
 obj-$(CONFIG_THECUS_N1200)	+= thecus_n1200.o
+obj-$(CONFIG_MCU_THECUS_N1200)	+= mcu_thecus_n1200.o
 obj-$(CONFIG_ASP834x)		+= asp834x.o
diff --git a/arch/powerpc/platforms/83xx/mcu_thecus_n1200.c b/arch/powerpc/platforms/83xx/mcu_thecus_n1200.c
new file mode 100644
index 0000000..03384ce
--- /dev/null
+++ b/arch/powerpc/platforms/83xx/mcu_thecus_n1200.c
@@ -0,0 +1,307 @@
+/*
+ * Driver for the MCU on Thecus N1200 boards.
+ *
+ * Copyright (c) 2009 Byron Bradley
+ *
+ * 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.
+ */
+
+/*
+ * The Thecus N1200 boards have a board control PIC on the I2C bus. This
+ * provides control over two of the LEDs, buzzer and the power-off function.
+ * The behaviour of the power button changes depending on the power state so we
+ * tell the PIC we have booted when the module loads and tell it when we are
+ * rebooting.
+ */
+
+#include <asm/machdep.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+
+/* N1200 registers */
+#define N1200_REG_PWR_CTRL	0x0
+#define N1200_REG_LED_SATA	0x1
+#define N1200_REG_LED_SYSTEM	0x2
+#define N1200_REG_LED_BUSY	0x3
+#define N1200_REG_BUZZER	0x4
+#define N1200_REG_PWR_STATUS	0x5
+#define N1200_REG_VERSION	0x6
+
+/* for N1200_REG_PWR_CTRL */
+#define N1200_PWR_OFF		0x0
+#define N1200_PWR_RESET		0x1
+
+/* for N1200_REG_PWR_STATUS */
+#define N1200_PWR_OFF		0x0
+#define N1200_PWR_BOOTED	0x2
+
+/* for N1200_REG_LED_* */
+#define N1200_LED_OFF		0x0
+#define N1200_LED_ON		0x1
+#define N1200_LED_BLINK		0x2
+
+/* We need to store a pointer to the i2c_client for use during a poweroff */
+static struct i2c_client *n1200_i2c_client;
+
+struct mcu_n1200 {
+	struct led_classdev		sata_led;
+	struct led_classdev		system_led;
+	struct led_classdev		busy_led;
+	struct input_dev		*input_dev;
+};
+
+static inline int n1200_read(struct i2c_client *client, u8 reg)
+{
+	return i2c_smbus_read_byte_data(client, reg);
+}
+
+static inline void n1200_write(struct i2c_client *client, u8 reg, u8 value)
+{
+	i2c_smbus_write_byte_data(client, reg, value);
+}
+
+void n1200_power_off(void)
+{
+	if (n1200_i2c_client == NULL)
+		return;
+
+	n1200_write(n1200_i2c_client, N1200_REG_PWR_CTRL, N1200_PWR_OFF);
+}
+
+static int n1200_reboot_notify(struct notifier_block *nb, unsigned long event,
+			       void *p)
+{
+	if (event != SYS_RESTART || n1200_i2c_client == NULL)
+		return NOTIFY_DONE;
+
+	n1200_write(n1200_i2c_client, N1200_REG_PWR_CTRL, N1200_PWR_RESET);
+
+	return NOTIFY_DONE;
+}
+
+struct notifier_block n1200_notifier = {
+	.notifier_call = n1200_reboot_notify,
+};
+
+#define N1200_NEW_LED(__name, __reg, __blinkable)			\
+static void n1200_##__name##_set(struct led_classdev *led_cdev,		\
+		enum led_brightness value)				\
+{									\
+	if (value == LED_OFF)						\
+		n1200_write(n1200_i2c_client, __reg, N1200_LED_OFF);	\
+	else								\
+		n1200_write(n1200_i2c_client, __reg, N1200_LED_ON);	\
+}									\
+static int n1200_##__name##_blink(struct led_classdev *led_cdev,	\
+		unsigned long *delay_on, unsigned long *delay_off)	\
+{									\
+	if (__blinkable == 1) {						\
+		n1200_write(n1200_i2c_client, __reg, N1200_LED_BLINK);	\
+		return 0;						\
+	} else								\
+		return -EINVAL;						\
+}
+
+N1200_NEW_LED(sata_red, N1200_REG_LED_SATA, 1);
+N1200_NEW_LED(system_red, N1200_REG_LED_SYSTEM, 1);
+N1200_NEW_LED(busy_orange, N1200_REG_LED_BUSY, 1);
+
+static int n1200_spkr_event(struct input_dev *dev, unsigned int type,
+			    unsigned int code, int value)
+{
+	if (type != EV_SND)
+		return -1;
+
+	switch (code) {
+	case SND_BELL:
+		if (value)
+			value = 1000;
+	case SND_TONE:
+		break;
+	default:
+		return -1;
+	}
+
+	if (value > 20 && value < 32767) {
+		n1200_write(n1200_i2c_client, N1200_REG_BUZZER, N1200_LED_ON);
+		msleep(value);
+		n1200_write(n1200_i2c_client, N1200_REG_BUZZER, N1200_LED_OFF);
+	} else {
+		n1200_write(n1200_i2c_client, N1200_REG_BUZZER, N1200_LED_OFF);
+	}
+
+	return 0;
+}
+
+static int n1200_probe(struct i2c_client *client,
+		       const struct i2c_device_id *id)
+{
+	int version, err = -ENOMEM;
+	struct mcu_n1200 *mcu;
+	struct i2c_adapter *adapter = client->adapter;
+	extern struct machdep_calls ppc_md;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+
+	version = n1200_read(client, N1200_REG_VERSION);
+	if (version < 0)
+		return -ENODEV;
+
+	mcu = kzalloc(sizeof(struct mcu_n1200), GFP_KERNEL);
+	if (!mcu) {
+		dev_err(&client->dev, "failed to allocate driver data\n");
+		return -ENOMEM;
+	}
+	i2c_set_clientdata(client, mcu);
+
+	mcu->input_dev = input_allocate_device();
+	if (!mcu->input_dev)
+		goto err_alloc;
+
+	dev_info(&adapter->dev, "found mcu_thecus_n1200: v%d\n", version);
+
+	/* Store the client as a static, we'll need it for a poweroff */
+	n1200_i2c_client = client;
+
+	mcu->sata_led.name = "n1200:red:sata";
+	mcu->sata_led.brightness = LED_OFF;
+	mcu->sata_led.brightness_set = n1200_sata_red_set;
+	mcu->sata_led.blink_set = n1200_sata_red_blink;
+	err = led_classdev_register(&client->dev, &mcu->sata_led);
+	if (err < 0) {
+		dev_err(&client->dev, "Failed to register LED sata_red");
+		goto err_sata;
+	}
+
+	mcu->system_led.name = "n1200:red:status";
+	mcu->system_led.brightness = LED_OFF;
+	mcu->system_led.brightness_set = n1200_system_red_set;
+	mcu->system_led.blink_set = n1200_system_red_blink;
+	err = led_classdev_register(&client->dev, &mcu->system_led);
+	if (err < 0) {
+		dev_err(&client->dev, "Failed to register LED system_red");
+		goto err_system;
+	}
+
+	mcu->busy_led.name = "n1200:orange:status";
+	mcu->busy_led.brightness = LED_FULL;
+	mcu->busy_led.brightness_set = n1200_busy_orange_set;
+	mcu->busy_led.blink_set = n1200_busy_orange_blink;
+	err = led_classdev_register(&client->dev, &mcu->busy_led);
+	if (err < 0) {
+		dev_err(&client->dev, "Failed to register LED busy_orange");
+		goto err_busy;
+	}
+
+	input_set_drvdata(mcu->input_dev, (void *) mcu);
+	mcu->input_dev->name = "n1200 beeper",
+	mcu->input_dev->phys = "n1200/gpio";
+	mcu->input_dev->id.bustype = BUS_I2C;
+	mcu->input_dev->id.vendor  = 0x0001;
+	mcu->input_dev->id.product = 0x0001;
+	mcu->input_dev->id.version = 0x0100;
+	mcu->input_dev->dev.parent = &client->dev;
+
+	mcu->input_dev->evbit[0] = BIT_MASK(EV_SND);
+	mcu->input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
+	mcu->input_dev->event = n1200_spkr_event;
+
+	err = input_register_device(mcu->input_dev);
+	if (err) {
+		dev_err(&client->dev, "Failed to register input device");
+		goto err_buzzer;
+	}
+
+	err = register_reboot_notifier(&n1200_notifier);
+	if (err != 0) {
+		dev_err(&client->dev, "Failed to register reboot notifier");
+		goto err_notifier;
+	}
+
+	n1200_write(client, N1200_REG_PWR_STATUS, N1200_PWR_BOOTED);
+
+	ppc_md.power_off = &n1200_power_off;
+
+	return 0;
+
+err_notifier:
+	input_unregister_device(mcu->input_dev);
+	mcu->input_dev = NULL;
+err_buzzer:
+	led_classdev_unregister(&mcu->busy_led);
+err_busy:
+	led_classdev_unregister(&mcu->system_led);
+err_system:
+	led_classdev_unregister(&mcu->sata_led);
+err_sata:
+	input_free_device(mcu->input_dev);
+err_alloc:
+	kfree(mcu);
+	return err;
+}
+
+static int n1200_remove(struct i2c_client *client)
+{
+	int err;
+	extern struct machdep_calls ppc_md;
+	struct mcu_n1200 *mcu = i2c_get_clientdata(client);
+
+	/* Tell the PIC we are going away, needed if we wanted to reload */
+	n1200_write(client, N1200_REG_PWR_STATUS, N1200_PWR_OFF);
+
+	err = unregister_reboot_notifier(&n1200_notifier);
+	ppc_md.power_off = NULL;
+
+	input_unregister_device(mcu->input_dev);
+	mcu->input_dev = NULL;
+
+	led_classdev_unregister(&mcu->sata_led);
+	led_classdev_unregister(&mcu->system_led);
+	led_classdev_unregister(&mcu->busy_led);
+
+	i2c_set_clientdata(client, NULL);
+	kfree(mcu);
+	n1200_i2c_client = NULL;
+
+	return err;
+}
+
+static const struct i2c_device_id n1200_id[] = {
+	{ "mcu_thecus_n1200", 0 },
+	{ }
+};
+
+static struct i2c_driver n1200_driver = {
+	.driver = {
+		.name   = "mcu_thecus_n1200",
+	},
+	.probe          = n1200_probe,
+	.remove         = n1200_remove,
+	.id_table       = n1200_id,
+};
+
+static int __init n1200_init(void)
+{
+	return i2c_add_driver(&n1200_driver);
+}
+
+static void __exit n1200_exit(void)
+{
+	i2c_del_driver(&n1200_driver);
+}
+
+MODULE_AUTHOR("Byron Bradley <byron.bbradley at gmail.com>");
+MODULE_DESCRIPTION("Thecus N1200 Board Controller");
+MODULE_LICENSE("GPL");
+
+module_init(n1200_init);
+module_exit(n1200_exit);
diff --git a/arch/powerpc/platforms/Kconfig b/arch/powerpc/platforms/Kconfig
index e3e8707..699ee83 100644
--- a/arch/powerpc/platforms/Kconfig
+++ b/arch/powerpc/platforms/Kconfig
@@ -329,4 +329,13 @@ config MCU_MPC8349EMITX
 	  also register MCU GPIOs with the generic GPIO API, so you'll able
 	  to use MCU pins as GPIOs.
 
+config MCU_THECUS_N1200
+	tristate "Thecus N1200 MCU driver"
+	depends on I2C && THECUS_N1200
+	select GENERIC_GPIO
+	select ARCH_REQUIRE_GPIOLIB
+	help
+	  Say Y here to enable power-off functionality on Thecus N1200 board.
+	  Also provides control over some of the LEDs and the buzzer.
+
 endmenu
-- 
1.6.3




More information about the Linuxppc-dev mailing list