Add the gdium platform devices such as lm75 sensor and m41t83 rtc. Signed-off-by: yajin <yajin@xxxxxxxxxxxxx> --- arch/mips/loongson/Kconfig | 4 + arch/mips/loongson/gdium/Makefile | 2 +- arch/mips/loongson/gdium/platform.c | 133 ++++++++++ arch/mips/loongson/gdium/pwm-clock.c | 465 ++++++++++++++++++++++++++++++++++ 4 files changed, 603 insertions(+), 1 deletions(-) create mode 100644 arch/mips/loongson/gdium/platform.c create mode 100644 arch/mips/loongson/gdium/pwm-clock.c diff --git a/arch/mips/loongson/Kconfig b/arch/mips/loongson/Kconfig index 90a02b4..c264a84 100644 --- a/arch/mips/loongson/Kconfig +++ b/arch/mips/loongson/Kconfig @@ -80,10 +80,14 @@ config DEXXON_GDIUM select SYS_SUPPORTS_HIGHMEM select SYS_SUPPORTS_LITTLE_ENDIAN select ARCH_REQUIRE_GPIOLIB + select HAVE_PWM if MFD_SM501 help Dexxon gdium netbook based on Loongson 2F and SM502. endchoice +config HAVE_PWM + bool + config CS5536 bool diff --git a/arch/mips/loongson/gdium/Makefile b/arch/mips/loongson/gdium/Makefile index 31a8e57..4fb096d 100644 --- a/arch/mips/loongson/gdium/Makefile +++ b/arch/mips/loongson/gdium/Makefile @@ -1,3 +1,3 @@ # Makefile for gdium -obj-y += irq.o reset.o +obj-y += irq.o reset.o platform.o pwm-clock.o diff --git a/arch/mips/loongson/gdium/platform.c b/arch/mips/loongson/gdium/platform.c new file mode 100644 index 0000000..a6d5968 --- /dev/null +++ b/arch/mips/loongson/gdium/platform.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2009 Philippe Vachon <philippe@xxxxxxxxx> + * + * 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. + */ + + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/pwm_backlight.h> +#include <linux/i2c.h> +#include <linux/i2c-gpio.h> + +#include <loongson.h> + +#define GDIUM_GPIO_BASE 224 + +static struct i2c_board_info __initdata sm502dev_i2c_devices[] = { + { + I2C_BOARD_INFO("lm75", 0x48), + .type = "lm75", + }, + { + I2C_BOARD_INFO("rtc-m41t80", 0x68), + .type = "m41t83", + }, + { + I2C_BOARD_INFO("gdium-laptop", 0x40), + }, +}; + +static int sm502dev_backlight_init(struct device *dev) +{ + /* Add gpio request stuff here */ + return 0; +} + +static void sm502dev_backlight_exit(struct device *dev) +{ + /* Add gpio free stuff here */ +} + +static struct platform_pwm_backlight_data backlight_data = { + .pwm_id = 0, + .max_brightness = 100, + .dft_brightness = 50, + .pwm_period_ns = 50000, /* 20 kHz */ + .init = sm502dev_backlight_init, + .exit = sm502dev_backlight_exit, +}; + +static struct platform_device backlight = { + .name = "pwm-backlight", + .dev = { + .platform_data = &backlight_data, + }, + .id = -1, +}; + +/* + * Warning this stunt is very dangerous + * as the sm501 gpio have dynamic numbers... + */ +/* bus 0 is the one for the ST7, DS75 etc... */ +static struct i2c_gpio_platform_data i2c_gpio0_data = { + .sda_pin = GDIUM_GPIO_BASE + 13, + .scl_pin = GDIUM_GPIO_BASE + 6, + .udelay = 5, + .timeout = HZ / 10, + .sda_is_open_drain = 0, + .scl_is_open_drain = 0, +}; + +static struct platform_device i2c_gpio0_device = { + .name = "i2c-gpio", + .id = 0, + .dev = { + .platform_data = &i2c_gpio0_data, + }, +}; + +/* bus 1 is for the CRT/VGA external screen */ +static struct i2c_gpio_platform_data i2c_gpio1_data = { + .sda_pin = GDIUM_GPIO_BASE + 10, + .scl_pin = GDIUM_GPIO_BASE + 9, + .udelay = 5, + .timeout = HZ / 10, + .sda_is_open_drain = 0, + .scl_is_open_drain = 0, +}; + +static struct platform_device i2c_gpio1_device = { + .name = "i2c-gpio", + .id = 1, + .dev = { + .platform_data = &i2c_gpio1_data, + }, +}; + +static struct platform_device *devices[] __initdata = { + &i2c_gpio0_device, + &i2c_gpio1_device, + &backlight, +}; + +static int __init gdium_platform_devices_setup(void) +{ + int ret; + printk(KERN_INFO "Registering gdium platform devices\n"); + + platform_add_devices(devices, ARRAY_SIZE(devices)); + + ret = i2c_register_board_info(0, sm502dev_i2c_devices, + ARRAY_SIZE(sm502dev_i2c_devices)); + + if (ret != 0) { + printk(KERN_INFO "Error while registering gdium platform" + " devices: %d\n", ret); + return ret; + } + + return 0; +} + +/* + * some devices are on the pwm stuff which is behind the mfd which is + * behind the pci bus so arch_initcall can't work because too early + */ +late_initcall(gdium_platform_devices_setup); diff --git a/arch/mips/loongson/gdium/pwm-clock.c b/arch/mips/loongson/gdium/pwm-clock.c new file mode 100644 index 0000000..5af3b23 --- /dev/null +++ b/arch/mips/loongson/gdium/pwm-clock.c @@ -0,0 +1,465 @@ +/* + * SM501 PWM clock + * Copyright (C) 2009-2010 Arnaud Patard <apatard@xxxxxxxxxxxx> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/pwm.h> +#include <linux/sm501.h> +#include <linux/sm501-regs.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +static const char drv_name[] = "sm501-pwm"; + +#define INPUT_CLOCK 96 /* MHz */ +#define PWM_COUNT 3 + +#define SM501PWM_HIGH_COUNTER (1<<20) +#define SM501PWM_LOW_COUNTER (1<<8) +#define SM501PWM_CLOCK_DIVIDE (1>>4) +#define SM501PWM_IP (1<<3) +#define SM501PWM_I (1<<2) +#define SM501PWM_E (1<<0) + +struct pwm_device { + struct list_head node; + struct device *dev; + void __iomem *regs; + int duty_ns; + int period_ns; + char enabled; + void (*handler)(struct pwm_device *pwm); + + const char *label; + unsigned int use_count; + unsigned int pwm_id; +}; + +struct sm501pwm_info { + void __iomem *regs; + int irq; + struct resource *res; + struct device *dev; + struct dentry *debugfs; + + struct pwm_device pwm[3]; +}; + +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +{ + unsigned int high, low, divider; + int divider1, divider2; + unsigned long long delay; + + if (!pwm || !pwm->regs || period_ns == 0 || duty_ns > period_ns) + return -EINVAL; + + /* Get delay + * We're loosing some precision but multiplying then dividing + * will overflow + */ + if (period_ns > 1000) { + delay = period_ns / 1000; + delay *= INPUT_CLOCK; + } else { + delay = period_ns * 96; + delay /= 1000; + } + + /* Get the number of clock low and high */ + high = delay * duty_ns / period_ns; + low = delay - high; + + /* Get divider to make 'low' and 'high' fit into 12 bits */ + /* No need to say that the divider must be >= 0 */ + divider1 = fls(low)-12; + divider2 = fls(high)-12; + + if (divider1 < 0) + divider1 = 0; + if (divider2 < 0) + divider2 = 0; + + divider = max(divider1, divider2); + + low >>= divider; + high >>= divider; + + pwm->duty_ns = duty_ns; + pwm->period_ns = period_ns; + + writel((high<<20)|(low<<8)|(divider<<4), pwm->regs); + return 0; +} +EXPORT_SYMBOL(pwm_config); + +int pwm_enable(struct pwm_device *pwm) +{ + u32 reg; + + if (!pwm) + return -EINVAL; + + switch (pwm->pwm_id) { + case 0: + sm501_configure_gpio(pwm->dev->parent, 29, 1); + break; + case 1: + sm501_configure_gpio(pwm->dev->parent, 30, 1); + break; + case 2: + sm501_configure_gpio(pwm->dev->parent, 31, 1); + break; + default: + return -EINVAL; + } + + reg = readl(pwm->regs); + reg |= (SM501PWM_IP | SM501PWM_E); + writel(reg, pwm->regs); + pwm->enabled = 1; + + return 0; +} +EXPORT_SYMBOL(pwm_enable); + +void pwm_disable(struct pwm_device *pwm) +{ + u32 reg; + + if (!pwm) + return; + + reg = readl(pwm->regs); + reg &= ~(SM501PWM_IP | SM501PWM_E); + writel(reg, pwm->regs); + + switch (pwm->pwm_id) { + case 0: + sm501_configure_gpio(pwm->dev->parent, 29, 0); + break; + case 1: + sm501_configure_gpio(pwm->dev->parent, 30, 0); + break; + case 2: + sm501_configure_gpio(pwm->dev->parent, 31, 0); + break; + default: + break; + } + pwm->enabled = 0; +} +EXPORT_SYMBOL(pwm_disable); + +static DEFINE_MUTEX(pwm_lock); +static LIST_HEAD(pwm_list); + +struct pwm_device *pwm_request(int pwm_id, const char *label) +{ + struct pwm_device *pwm; + int found = 0; + + mutex_lock(&pwm_lock); + + list_for_each_entry(pwm, &pwm_list, node) { + if (pwm->pwm_id == pwm_id && pwm->use_count == 0) { + pwm->use_count++; + pwm->label = label; + found = 1; + break; + } + } + + mutex_unlock(&pwm_lock); + + return (found) ? pwm : NULL; +} +EXPORT_SYMBOL(pwm_request); + +void pwm_free(struct pwm_device *pwm) +{ + mutex_lock(&pwm_lock); + + if (pwm->use_count) { + pwm->use_count--; + pwm->label = NULL; + } else + dev_warn(pwm->dev, "PWM device already freed\n"); + + mutex_unlock(&pwm_lock); +} +EXPORT_SYMBOL(pwm_free); + +int pwm_int_enable(struct pwm_device *pwm) +{ + unsigned long conf; + + if (!pwm || !pwm->regs || !pwm->handler) + return -EINVAL; + + conf = readl(pwm->regs); + conf |= SM501PWM_I; + writel(conf, pwm->regs); + return 0; +} +EXPORT_SYMBOL(pwm_int_enable); + +int pwm_int_disable(struct pwm_device *pwm) +{ + unsigned long conf; + + if (!pwm || !pwm->regs || !pwm->handler) + return -EINVAL; + + conf = readl(pwm->regs); + conf &= ~SM501PWM_I; + writel(conf, pwm->regs); + return 0; +} +EXPORT_SYMBOL(pwm_int_disable); + +int pwm_set_handler(struct pwm_device *pwm, + void (*handler)(struct pwm_device *pwm)) +{ + if (!pwm || !handler) + return -EINVAL; + pwm->handler = handler; + return 0; +} +EXPORT_SYMBOL(pwm_set_handler); + +static irqreturn_t sm501pwm_irq(int irq, void *dev_id) +{ + unsigned long value; + struct sm501pwm_info *info = (struct sm501pwm_info *)dev_id; + struct pwm_device *pwm; + int i; + + value = sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 0); + + /* Check is the interrupt is for us */ + if (value & (1<<22)) { + for (i = 0 ; i < PWM_COUNT ; i++) { + /* + * Find which pwm triggered the interrupt + * and ack + */ + value = readl(info->regs + i*4); + if (value & SM501PWM_IP) + writel(value | SM501PWM_IP, info->regs + i*4); + + pwm = &info->pwm[i]; + if (pwm->handler) + pwm->handler(pwm); + } + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void add_pwm(int id, struct sm501pwm_info *info) +{ + struct pwm_device *pwm = &info->pwm[id]; + + pwm->use_count = 0; + pwm->pwm_id = id; + pwm->dev = info->dev; + pwm->regs = info->regs + id * 4; + + mutex_lock(&pwm_lock); + list_add_tail(&pwm->node, &pwm_list); + mutex_unlock(&pwm_lock); +} + +static void del_pwm(int id, struct sm501pwm_info *info) +{ + struct pwm_device *pwm = &info->pwm[id]; + + pwm->use_count = 0; + pwm->pwm_id = -1; + mutex_lock(&pwm_lock); + list_del(&pwm->node); + mutex_unlock(&pwm_lock); +} + +/* Debug fs */ +static int sm501pwm_show(struct seq_file *s, void *p) +{ + struct pwm_device *pwm; + + mutex_lock(&pwm_lock); + list_for_each_entry(pwm, &pwm_list, node) { + if (pwm->use_count) { + seq_printf(s, "pwm-%d (%12s) %d %d %s\n", + pwm->pwm_id, pwm->label, + pwm->duty_ns, pwm->period_ns, + pwm->enabled ? "on" : "off"); + seq_printf(s, " %08x\n", readl(pwm->regs)); + } + } + mutex_unlock(&pwm_lock); + + return 0; +} + +static int sm501pwm_open(struct inode *inode, struct file *file) +{ + return single_open(file, sm501pwm_show, inode->i_private); +} + +static const struct file_operations sm501pwm_fops = { + .open = sm501pwm_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int __init sm501pwm_probe(struct platform_device *pdev) +{ + struct sm501pwm_info *info; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + int res_len; + int i; + + info = kzalloc(sizeof(struct sm501pwm_info), GFP_KERNEL); + if (!info) { + dev_err(dev, "Allocation failure\n"); + ret = -ENOMEM; + goto err; + } + info->dev = dev; + platform_set_drvdata(pdev, info); + + /* Get irq number */ + info->irq = platform_get_irq(pdev, 0); + if (!info->irq) { + dev_err(dev, "no irq found\n"); + ret = -ENODEV; + goto err_alloc; + } + + /* Get regs address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(dev, "No memory resource found\n"); + ret = -ENODEV; + goto err_alloc; + } + info->res = res; + res_len = (res->end - res->start)+1; + + if (!request_mem_region(res->start, res_len, drv_name)) { + dev_err(dev, "Can't request iomem resource\n"); + ret = -EBUSY; + goto err_alloc; + } + + info->regs = ioremap(res->start, res_len); + if (!info->regs) { + dev_err(dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_mem; + } + + ret = request_irq(info->irq, sm501pwm_irq, IRQF_SHARED, drv_name, info); + if (ret != 0) { + dev_err(dev, "can't get irq\n"); + goto err_map; + } + + + sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 1); + + for (i = 0; i < 3; i++) + add_pwm(i, info); + + dev_info(dev, "SM501 PWM Found at %lx irq %d\n", + (unsigned long)info->res->start, info->irq); + + info->debugfs = debugfs_create_file("pwm", S_IFREG | S_IRUGO, + NULL, info, &sm501pwm_fops); + + + return 0; + +err_map: + iounmap(info->regs); + +err_mem: + release_mem_region(res->start, res_len); + +err_alloc: + kfree(info); + platform_set_drvdata(pdev, NULL); +err: + return ret; +} + +static int sm501pwm_remove(struct platform_device *pdev) +{ + struct sm501pwm_info *info = platform_get_drvdata(pdev); + int i; + + if (info->debugfs) + debugfs_remove(info->debugfs); + + for (i = 0; i < 3; i++) { + pwm_disable(&info->pwm[i]); + del_pwm(i, info); + } + + sm501_unit_power(info->dev->parent, SM501_GATE_GPIO, 0); + sm501_modify_reg(info->dev->parent, SM501_IRQ_STATUS, 0, 1<<22); + + free_irq(info->irq, info); + iounmap(info->regs); + release_mem_region(info->res->start, + (info->res->end - info->res->start)+1); + kfree(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver sm501pwm_driver = { + .probe = sm501pwm_probe, + .remove = sm501pwm_remove, + .driver = { + .name = drv_name, + .owner = THIS_MODULE, + }, +}; + +static int __devinit sm501pwm_init(void) +{ + return platform_driver_register(&sm501pwm_driver); +} + +static void __exit sm501pwm_cleanup(void) +{ + platform_driver_unregister(&sm501pwm_driver); +} + +module_init(sm501pwm_init); +module_exit(sm501pwm_cleanup); + +MODULE_AUTHOR("Arnaud Patard <apatard@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("SM501 PWM driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sm501-pwm"); -- 1.5.6.5