diff options
Diffstat (limited to 'drivers/char/aspeed')
-rw-r--r-- | drivers/char/aspeed/Kconfig | 54 | ||||
-rw-r--r-- | drivers/char/aspeed/Makefile | 9 | ||||
-rw-r--r-- | drivers/char/aspeed/ast_kcs.c | 446 | ||||
-rw-r--r-- | drivers/char/aspeed/ast_peci.c | 508 |
4 files changed, 1017 insertions, 0 deletions
diff --git a/drivers/char/aspeed/Kconfig b/drivers/char/aspeed/Kconfig new file mode 100644 index 0000000..0d339a1 --- /dev/null +++ b/drivers/char/aspeed/Kconfig @@ -0,0 +1,54 @@ +# +# MISC configuration for ASPEED SOCs +# + +if ARCH_ASPEED +menuconfig AST_MISC + tristate 'MISC drivers for ASPEED SOCs' + help + We can select misc drivers for ASPEED SOC in this sub-function. + +if AST_MISC +config AST_VIDEO + tristate "ASPEED Video Engine driver" + default n + help + Driver for AST Video Engine + +config ADC_CAT9883 + tristate "CAT 9883 ADC driver" + default n + help + Driver for CAT 9883 + +config AST_SPI_BIOS + tristate "ASPEED SPI BIOS flash register" + default n + help + Driver for SPI BIOS flash register + +config AST_PECI + tristate "ASPEED PECI Controller" + default n + help + Driver for PECI Controller + +if KCS_GENERIC +config AST_KCS + tristate 'ASPEED KCS support' + help + Support for the KCS channels on the ASPEED chips, + providing /dev/kcs0, 1 and 2 (note, some machines may not + provide all of these ports, depending on how the serial port + pins are configured. +endif # CONFIG_KCS_GENERIC + +config AST_GPIO + tristate "ASPEED GPIO Controller" + default n + help + Driver for GPIO Controller included in ASPEED SOCs. + +endif # CONFIG_AST_MISC +endif # CONFIG_AST + diff --git a/drivers/char/aspeed/Makefile b/drivers/char/aspeed/Makefile new file mode 100644 index 0000000..517b2b7 --- /dev/null +++ b/drivers/char/aspeed/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the ASPEED drivers. +# + +obj-$(CONFIG_AST_VIDEO) += ast_video.o +obj-$(CONFIG_ADC_CAT9883) += adc_cat9883.o +obj-$(CONFIG_AST_KCS) += ast_kcs.o +obj-$(CONFIG_AST_GPIO) += ast_gpio.o +obj-$(CONFIG_AST_PECI) += ast_peci.o diff --git a/drivers/char/aspeed/ast_kcs.c b/drivers/char/aspeed/ast_kcs.c new file mode 100644 index 0000000..93c079f --- /dev/null +++ b/drivers/char/aspeed/ast_kcs.c @@ -0,0 +1,446 @@ +/* + * ASPEED AST2100/2050/2200/2150/2300 KCS controller driver + * + * (C) Copyright 2017 Raptor Engineering, LLC + * (C) Copyright 2006-2009, American Megatrends Inc. + * + * Author : Timothy Pearson <tpearson@raptorengineering.com> + * Jothiram Selvam <jothirams@ami.com> + * Vinay Tandon <vinayt@ami.com> + */ + +// #define DEBUG + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/errno.h> +#include <linux/ioport.h> + +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#include <char/kcs/kcs.h> +#include <mach/ast_kcs.h> +#include <plat/regs-intr.h> + +#define AST_KCS_DRIVER_NAME "ast_kcs" + +static void *ast_kcs_virt_base; +bool kcs_enabled_by_user = 0; + +inline uint32_t ast_kcs_read_reg(uint32_t reg) +{ + return ioread32(ast_kcs_virt_base + reg); +} + +inline void ast_kcs_write_reg(uint32_t data, uint32_t reg) +{ + iowrite32(data, ast_kcs_virt_base + reg); +} + +static void ast_kcs_enable_channel(void) +{ + uint32_t reg; + + reg = ast_kcs_read_reg(AST_LPC_HICR0); + reg |= (AST_LPC_HICR0_LPC3E | AST_LPC_HICR0_LPC2E | AST_LPC_HICR0_LPC1E); + ast_kcs_write_reg(reg, AST_LPC_HICR0); + +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + reg = ast_kcs_read_reg(AST_LPC_HICRB); + reg |= AST_LPC_HICRB0_KCS4E; + ast_kcs_write_reg(reg, AST_LPC_HICRB); +#endif +} + +static void ast_kcs_disable_channel(void) +{ + uint32_t reg; + + reg = ast_kcs_read_reg(AST_LPC_HICR0); + reg &= ~(AST_LPC_HICR0_LPC3E | AST_LPC_HICR0_LPC2E | AST_LPC_HICR0_LPC1E); + ast_kcs_write_reg(reg, AST_LPC_HICR0); + +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + reg = ast_kcs_read_reg(AST_LPC_HICRB); + reg &= ~(AST_LPC_HICRB0_KCS4E ); + ast_kcs_write_reg(reg, AST_LPC_HICRB); +#endif +} + +static void ast_kcs_configure_io_port_addr(void) +{ + uint32_t reg; + + /* switch the access of LADR12 to LADR1 */ + reg = ast_kcs_read_reg(AST_LPC_HICR4); + reg &= ~AST_LPC_HICR4_LADR12SEL; + ast_kcs_write_reg(reg, AST_LPC_HICR4); + + mb(); + + /* set I/O port address of channel 1*/ + ast_kcs_write_reg(AST_KCS_ADR1_HI, AST_LPC_LADR12H); + ast_kcs_write_reg(AST_KCS_ADR1_LO, AST_LPC_LADR12L); + + mb(); + + /* switch the access of LADR12 to LADR2 */ + reg = ast_kcs_read_reg(AST_LPC_HICR4); + reg |= AST_LPC_HICR4_LADR12SEL; + ast_kcs_write_reg(reg, AST_LPC_HICR4); + + mb(); + + /* set I/O port address 2 */ + ast_kcs_write_reg(AST_KCS_ADR2_HI, AST_LPC_LADR12H); + ast_kcs_write_reg(AST_KCS_ADR2_LO, AST_LPC_LADR12L); + + mb(); + + /* enable KCS in channel 3 */ + reg = ast_kcs_read_reg(AST_LPC_HICR4); + reg |= AST_LPC_HICR4_KCSENBL; + ast_kcs_write_reg(reg, AST_LPC_HICR4); + + mb(); + + /* set I/O port address of channel 3 */ + ast_kcs_write_reg(AST_KCS_ADR3_HI, AST_LPC_LADR3H); + ast_kcs_write_reg(AST_KCS_ADR3_LO, AST_LPC_LADR3L); + + mb(); + +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + /* enable KCS in channel 4 */ + reg = ast_kcs_read_reg(AST_LPC_HICRB); + reg |= AST_LPC_HICRB0_KCS4E; + ast_kcs_write_reg(reg, AST_LPC_HICRB); + + mb(); + + /* set I/O port address of channel 4 */ + ast_kcs_write_reg(AST_KCS_ADR4, AST_LPC_LADR4); +#endif + +} + +static unsigned char ast_kcs_num_ch(void) +{ + return AST_KCS_CHANNEL_NUM; +} + +static void ast_kcs_enable_interrupt(void) +{ + uint32_t reg; + + reg = ast_kcs_read_reg(AST_LPC_HICR2); + reg |= (AST_LPC_HICR2_IBFIE1 | AST_LPC_HICR2_IBFIE2 | AST_LPC_HICR2_IBFIE3); + ast_kcs_write_reg(reg, AST_LPC_HICR2); + +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + reg = ast_kcs_read_reg(AST_LPC_HICRB); + reg |= (AST_LPC_HICRB0_KCS4INTE); + ast_kcs_write_reg(reg, AST_LPC_HICRB); +#endif +} + +static void ast_kcs_disable_interrupt(void) +{ + uint32_t reg; + + reg = ast_kcs_read_reg(AST_LPC_HICR2); + reg &= ~(AST_LPC_HICR2_IBFIE1 | AST_LPC_HICR2_IBFIE2 | AST_LPC_HICR2_IBFIE3); + ast_kcs_write_reg(reg, AST_LPC_HICR2); + +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + reg = ast_kcs_read_reg(AST_LPC_HICRB); + reg &= ~(AST_LPC_HICRB0_KCS4INTE); + ast_kcs_write_reg(reg, AST_LPC_HICRB); +#endif +} + +static void ast_kcs_read_status(u8 channel, u8 *status) +{ + if (channel == 3) + { +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + *status = (uint8_t) ast_kcs_read_reg(AST_LPC_STR4); +#endif + } + else + *status = (uint8_t) ast_kcs_read_reg(AST_LPC_STR_CH(channel)); +} + +static void ast_kcs_write_status(u8 channel, u8 status) +{ + if (channel == 3) + { +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + ast_kcs_write_reg(status, AST_LPC_STR4); +#endif + } + else + ast_kcs_write_reg(status, AST_LPC_STR_CH(channel)); + +} + +static void ast_kcs_read_command(u8 channel, u8 *command) +{ + if (channel == 3) + { +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + *command = (uint8_t) ast_kcs_read_reg(AST_LPC_IDR4); +#endif + } + else + *command = (uint8_t) ast_kcs_read_reg(AST_LPC_IDR_CH(channel)); +} + +static void ast_kcs_read_data(u8 channel, u8 *data) +{ + if(channel == 3) + { +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + *data = (uint8_t) ast_kcs_read_reg(AST_LPC_IDR4); +#endif + } + else + *data = (uint8_t) ast_kcs_read_reg(AST_LPC_IDR_CH(channel)); +} + +static void ast_kcs_write_data(u8 channel, u8 data) +{ + if (channel == 3) + { +#if defined(CONFIG_ARCH_AST2300) || defined(CONFIG_ARCH_AST2400) + ast_kcs_write_reg(data, AST_LPC_ODR4); +#endif + } + else + ast_kcs_write_reg(data, AST_LPC_ODR_CH(channel)); +} + +void +ast_kcs_interrupt_enable_user (void) +{ + kcs_enabled_by_user = 1; + ast_kcs_enable_interrupt(); +} + +void +ast_kcs_interrupt_disable_user (void) +{ + kcs_enabled_by_user = 0; + ast_kcs_disable_interrupt(); +} + +static kcs_driver_operations_t ast_kcs_hw_ops = { + .num_kcs_ch = ast_kcs_num_ch, + .enable_kcs_interrupt = ast_kcs_enable_interrupt, + .disable_kcs_interrupt = ast_kcs_disable_interrupt, + .read_kcs_status = ast_kcs_read_status, + .write_kcs_status = ast_kcs_write_status, + .read_kcs_command = ast_kcs_read_command, + .read_kcs_data_in = ast_kcs_read_data, + .write_kcs_data_out = ast_kcs_write_data, + .kcs_interrupt_enable_user = ast_kcs_interrupt_enable_user, + .kcs_interrupt_disable_user = ast_kcs_interrupt_disable_user +}; + +static irqreturn_t ast_kcs_irq_handler(int irq, void *dev_id) +{ + uint32_t reg; + int ch; + int ret; + uint8_t status; + int handled; + + reg = ast_kcs_read_reg(AST_LPC_HICR6); + + if (reg & (AST_LPC_HICR6_SNP0_STR | AST_LPC_HICR6_SNP1_STR)) { /* snoop interrupt is occured */ + return IRQ_NONE; /* handled by snoop driver */ + } + + reg = ast_kcs_read_reg(AST_LPC_HICR2); + + if (reg & (AST_LPC_HICR2_LRST | AST_LPC_HICR2_SDWN | AST_LPC_HICR2_ABRT)) { /* LRESET | SDWN | ABRT interrupt is occured */ + return IRQ_NONE; /* handled by LPC-reset driver */ + } + for (ch = 0; ch < AST_KCS_CHANNEL_NUM; ch ++) { + ast_kcs_read_status(ch, &status); + if (status & 0x02) { /* Command or Data_In register has been written by system-side software */ + handled = 1; + ret = process_kcs_intr(ch); + if ((ret != 0) && (ret != -ENXIO)) { + printk(KERN_WARNING "KCS core IRQ handler failed\n"); + } + } + /* when the KCS core IRQ handler reads IDR, IDF is cleared automatically */ + } + + return (handled == 1) ? IRQ_HANDLED : IRQ_NONE; +} + +static void ast_kcs_init_hw(void) +{ + uint32_t reg; + int ch; + u8 ch_num, status; + +#ifdef SOC_AST2300 + reg = ast_kcs_read_reg(AST_LPC_HICR5); + reg &= ~(0x00000500); /* clear bit 10 & 8 to disable LPC flash cycle */ + ast_kcs_write_reg(reg, AST_LPC_HICR5); +#endif + + ast_kcs_disable_interrupt(); + ast_kcs_disable_channel(); + + ast_kcs_configure_io_port_addr(); + + /* clear OBF */ + for (ch = 0; ch < AST_KCS_CHANNEL_NUM; ch ++) { + if(ch == 3) + { +#ifdef SOC_AST2300 + reg = ast_kcs_read_reg(AST_LPC_STR4); + reg &= ~AST_LPC_STR_OBFA; + ast_kcs_write_reg(reg, AST_LPC_STR4); +#endif + } + else + { + reg = ast_kcs_read_reg(AST_LPC_STR_CH(ch)); + reg &= ~AST_LPC_STR_OBFA; + ast_kcs_write_reg(reg, AST_LPC_STR_CH(ch)); + } + } + for (ch_num = 0; ch_num < AST_KCS_CHANNEL_NUM; ++ch_num) + { + ast_kcs_read_status (ch_num, &status); + status = status | ERROR_STATE; + ast_kcs_write_status (ch_num, status); + } + + ast_kcs_enable_channel(); + // ast_kcs_enable_interrupt(); +} + +static int ast_kcs_probe(struct platform_device *pdev) { + int ret = 0; + struct resource *res0; + + dev_dbg(&pdev->dev, "ast_kcs_probe() \n\n\n"); + + ret = kcs_init(&ast_kcs_hw_ops, pdev->id, THIS_MODULE); + if (ret) { + printk(KERN_WARNING "%s: initialization of KCS library failed\n", AST_KCS_DRIVER_NAME); + return ret; + } + + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res0) { + dev_err(&pdev->dev, "cannot get IORESOURCE_MEM 0\n"); + goto err_no_io_res; + } + + ast_kcs_virt_base = ioremap(res0->start, resource_size(res0)); + if (!ast_kcs_virt_base) { + printk(KERN_WARNING "%s: ioremap failed\n", AST_KCS_DRIVER_NAME); + ret = -ENOMEM; + goto err_no_io_res; + } + + IRQ_SET_LEVEL_TRIGGER(0, AST_KCS_IRQ); + IRQ_SET_HIGH_LEVEL(0, AST_KCS_IRQ); + + ret = request_irq(AST_KCS_IRQ, ast_kcs_irq_handler, IRQF_SHARED, AST_KCS_DRIVER_NAME, ast_kcs_virt_base); + if (ret) { + printk(KERN_WARNING "%s: request irq failed\n", AST_KCS_DRIVER_NAME); + goto out_iomap; + } + + ast_kcs_init_hw(); + + return 0; + +out_iomap: + iounmap(ast_kcs_virt_base); +err_no_io_res: + kcs_exit(); + + return ret; +} + +static int +ast_kcs_remove(struct platform_device *pdev) +{ + struct resource *res0; + + dev_dbg(&pdev->dev, "ast_kcs_remove()\n"); + + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res0->start, res0->end - res0->start + 1); + iounmap(ast_kcs_virt_base); + + platform_set_drvdata(pdev, NULL); + kcs_exit(); + return 0; +} + + +#ifdef CONFIG_PM +static int +ast_kcs_suspend(struct platform_device *pdev, pm_message_t msg) +{ + return 0; +} + +static int +ast_kcs_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define ast_kcs_suspend NULL +#define ast_kcs_resume NULL +#endif + + +static struct platform_driver ast_kcs_driver = { + .probe = ast_kcs_probe, + .remove = ast_kcs_remove, + .suspend = ast_kcs_suspend, + .resume = ast_kcs_resume, + .driver = { + .name = "ast-kcs", + .owner = THIS_MODULE, + }, +}; + +static int ast_kcs_module_init(void) +{ + platform_driver_register(&ast_kcs_driver); + + return 0; +} + +static void ast_kcs_module_exit(void) +{ + free_irq(AST_KCS_IRQ, ast_kcs_virt_base); + iounmap(ast_kcs_virt_base); + platform_driver_unregister(&ast_kcs_driver); + kcs_exit(); +} + +module_init(ast_kcs_module_init); +module_exit(ast_kcs_module_exit); + +MODULE_AUTHOR("American Megatrends Inc."); +MODULE_DESCRIPTION("AST2100/2050/2200/2150/2300 KCS controller driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/char/aspeed/ast_peci.c b/drivers/char/aspeed/ast_peci.c new file mode 100644 index 0000000..1f7cae3 --- /dev/null +++ b/drivers/char/aspeed/ast_peci.c @@ -0,0 +1,508 @@ +/******************************************************************************** +* File Name : ast_peci.c +* Author : Ryan Chen +* Description : AST PECI Controller +* +* Copyright (C) 2012-2020 ASPEED Technology Inc. +* 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. +* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* +* Version : 1.0 +* History : +* 1. 2013/01/30 Ryan Chen create this file +* +********************************************************************************/ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <asm/uaccess.h> + +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/miscdevice.h> +#ifdef CONFIG_COLDFIRE +#include <asm/arch/regs-peci.h> +#else +#include <plat/regs-peci.h> +#endif + +//#define CONFIG_AST_PECI_DEBUG + +#ifdef CONFIG_AST_PECI_DEBUG + #define PECI_DBG(fmt, args...) printk("%s(): " fmt, __FUNCTION__, ## args) +#else + #define PECI_DBG(fmt, args...) +#endif + +/***********************************************************************/ +struct timing_negotiation { + u8 msg_timing; + u8 addr_timing; +}; + +struct xfer_msg { + u8 client_addr; + u8 tx_len; + u8 rx_len; + u8 tx_fcs; + u8 rx_fcs; + u8 fcs_en; + u8 sw_fcs; + u8 *tx_buf; + u8 *rx_buf; + u32 sts; +}; + +#define PECI_DEVICE "/dev/ast-peci" + +//IOCTL .. +#define PECIIOC_BASE 'P' + +#define AST_PECI_IOCRTIMING _IOR(PECIIOC_BASE, 0, struct timing_negotiation*) +#define AST_PECI_IOCWTIMING _IOW(PECIIOC_BASE, 1, struct timing_negotiation*) +#define AST_PECI_IOCXFER _IOWR(PECIIOC_BASE, 2, struct xfer_msg*) + + +/***********************************************************************/ + +static struct ast_peci_data { + struct device *misc_dev; + void __iomem *reg_base; /* virtual */ + int irq; //PECI IRQ number + int open_count; + struct completion xfer_complete; + u32 sts; + struct mutex lock; +} ast_peci; + +static inline void +ast_peci_write(u32 val, u32 reg) +{ + PECI_DBG("write offset: %x, val: %x \n",reg,val); + writel(val, ast_peci.reg_base + reg); +} + +static inline u32 +ast_peci_read(u32 reg) +{ + u32 val = readl(ast_peci.reg_base + reg); + PECI_DBG("read offset: %x, val: %x \n",reg,val); + return val; +} + +static long ast_peci_ioctl(struct file *fp, + unsigned int cmd, unsigned long arg) +{ + long ret = 0; + void __user *argp = (void __user *)arg; + struct xfer_msg msg; + struct timing_negotiation tim_ng; + u32 peci_head; + int i=0; + u32 *tx_buf0 = (u32 *) (ast_peci.reg_base + AST_PECI_W_DATA0); + u32 *tx_buf1 = (u32 *) (ast_peci.reg_base + AST_PECI_W_DATA4); + u32 *rx_buf0 = (u32 *) (ast_peci.reg_base + AST_PECI_R_DATA0); + u32 *rx_buf1 = (u32 *) (ast_peci.reg_base + AST_PECI_R_DATA4); + u32 rx_data; + + PECI_DBG("ast_peci_ioctl cmd %x \n", cmd); + + switch(cmd) { + case AST_PECI_IOCRTIMING: + tim_ng.msg_timing = PECI_TIMING_MESSAGE_GET(ast_peci_read(AST_PECI_TIMING)); + tim_ng.addr_timing = PECI_TIMING_ADDRESS_GET(ast_peci_read(AST_PECI_TIMING)); + if (copy_to_user(argp, &tim_ng, sizeof(struct timing_negotiation))) + ret = -EFAULT; + break; + + case AST_PECI_IOCWTIMING: + if (copy_from_user(&tim_ng, argp, sizeof(struct timing_negotiation))) { + ret = -EFAULT; + } else { + ast_peci_write(PECI_TIMING_MESSAGE(tim_ng.msg_timing) | + PECI_TIMING_ADDRESS(tim_ng.addr_timing), AST_PECI_TIMING); + } + break; + + case AST_PECI_IOCXFER: + //Check cmd operation sts + while(ast_peci_read(AST_PECI_CMD) & PECI_CMD_FIRE) { + printk("wait for free \n"); + }; + + if (copy_from_user(&msg, argp, sizeof(struct xfer_msg))) { + ret = -EFAULT; + break; + } + +#ifdef CONFIG_AST_PECI_DEBUG + printk("fcs_en %d, client_addr %x, tx_len %d, rx_len %d",msg.fcs_en ,msg.client_addr, msg.tx_len, msg.rx_len); + printk("\ntx_buf : "); + for(i = 0;i< msg.tx_len; i++) + printk(" %x ",msg.tx_buf[i]); + printk("\n"); +#endif + + if(msg.fcs_en) + peci_head = PECI_TAGET_ADDR(msg.client_addr) | + PECI_WRITE_LEN(msg.tx_len) | + PECI_READ_LEN(msg.rx_len) | PECI_AW_FCS_EN; + else + peci_head = PECI_TAGET_ADDR(msg.client_addr) | + PECI_WRITE_LEN(msg.tx_len) | + PECI_READ_LEN(msg.rx_len); + + + ast_peci_write(peci_head, AST_PECI_CMD_CTRL); + + for(i = 0; i < msg.tx_len; i++) { + if(i < 16) { + if(i%4 == 0) + tx_buf0[i/4] = 0; + tx_buf0[i/4] |= (msg.tx_buf[i] << ((i%4)*8)) ; + } else { + if(i%4 == 0) + tx_buf1[i/4] = 0; + tx_buf1[i/4] |= (msg.tx_buf[i] << ((i%4)*8)) ; + } + } + +#ifdef CONFIG_AST_PECI_DEBUG + printk("\nWD \n "); + ast_peci_read(AST_PECI_W_DATA0); + ast_peci_read(AST_PECI_W_DATA1); + ast_peci_read(AST_PECI_W_DATA2); + ast_peci_read(AST_PECI_W_DATA3); + ast_peci_read(AST_PECI_W_DATA4); + ast_peci_read(AST_PECI_W_DATA5); + ast_peci_read(AST_PECI_W_DATA6); + ast_peci_read(AST_PECI_W_DATA7); +#endif + init_completion(&ast_peci.xfer_complete); + //Fire Command + ast_peci_write(PECI_CMD_FIRE, AST_PECI_CMD); + + + ret = wait_for_completion_interruptible_timeout(&ast_peci.xfer_complete, 30*HZ); + + if (ret == 0) + printk("peci controller timed out\n"); + + for(i = 0; i < msg.rx_len; i++) { + if(i < 16) { + switch(i%4) { + case 0: + rx_data = rx_buf0[i/4]; + + msg.rx_buf[i] = rx_data & 0xff; + break; + case 1: + msg.rx_buf[i] = (rx_data & 0xff00) >> 8; + break; + case 2: + msg.rx_buf[i] = (rx_data & 0xff0000) >> 16; + break; + case 3: + msg.rx_buf[i] = (rx_data & 0xff000000) >> 24; + break; + + } + } else { + switch(i%4) { + case 0: + rx_data = rx_buf1[i/4]; + msg.rx_buf[i] = rx_data & 0xff; + break; + case 1: + msg.rx_buf[i] = (rx_data & 0xff00) >> 8; + break; + case 2: + msg.rx_buf[i] = (rx_data & 0xff0000) >> 16; + break; + case 3: + msg.rx_buf[i] = (rx_data & 0xff000000) >> 24; + break; + + } + } + } +#ifdef CONFIG_AST_PECI_DEBUG + printk("\nRD \n"); + ast_peci_read(AST_PECI_R_DATA0); + ast_peci_read(AST_PECI_R_DATA1); + ast_peci_read(AST_PECI_R_DATA2); + ast_peci_read(AST_PECI_R_DATA3); + ast_peci_read(AST_PECI_R_DATA4); + ast_peci_read(AST_PECI_R_DATA5); + ast_peci_read(AST_PECI_R_DATA6); + ast_peci_read(AST_PECI_R_DATA7); + + printk("rx_buf : "); + for(i = 0;i< msg.rx_len; i++) + printk("%x ",msg.rx_buf[i]); + printk("\n"); +#endif + msg.sts = ast_peci.sts; + msg.rx_fcs = PECI_CAPTURE_READ_FCS(ast_peci_read(AST_PECI_CAP_FCS)); + if (copy_to_user(argp, &msg, sizeof(struct xfer_msg))) + ret = -EFAULT; + + break; + default: + printk("ast_peci_ioctl command fail\n"); + ret = -ENOTTY; + break; + } + + return ret; +} + +static int ast_peci_open(struct inode *inode, struct file *file) +{ + PECI_DBG("ast_peci_open\n"); + + + /* Flush input queue on first open */ + if (ast_peci.open_count) + return -1; + + ast_peci.open_count++; + + + return 0; +} + +static int ast_peci_release(struct inode *inode, struct file *file) +{ + PECI_DBG("ast_peci_release\n"); + ast_peci.open_count--; + + return 0; +} + +static irqreturn_t ast_peci_handler(int this_irq, void *dev_id) +{ + ast_peci.sts = (0x1f & ast_peci_read(AST_PECI_INT_STS)); + + switch(ast_peci.sts) { + case PECI_INT_TIMEOUT: + printk("PECI_INT_TIMEOUT \n"); + ast_peci_write(PECI_INT_TIMEOUT, AST_PECI_INT_STS); + break; + case PECI_INT_CONNECT: + printk("PECI_INT_CONNECT \n"); + ast_peci_write(PECI_INT_CONNECT, AST_PECI_INT_STS); + break; + case PECI_INT_W_FCS_BAD: + printk("PECI_INT_W_FCS_BAD \n"); + ast_peci_write(PECI_INT_W_FCS_BAD, AST_PECI_INT_STS); + break; + case PECI_INT_W_FCS_ABORT: + printk("PECI_INT_W_FCS_ABORT \n"); + ast_peci_write(PECI_INT_W_FCS_ABORT, AST_PECI_INT_STS); + break; + case PECI_INT_CMD_DONE: + printk("PECI_INT_CMD_DONE \n"); + ast_peci_write(PECI_INT_CMD_DONE, AST_PECI_INT_STS); + ast_peci_write(0, AST_PECI_CMD); + break; + default: + printk("no one handle .... \n"); + break; + + } + + complete(&ast_peci.xfer_complete); + + return IRQ_HANDLED; + +} + +static void ast_peci_ctrl_init(void) +{ + //PECI Timing Setting : should 4 times of peci clk period 64 = 16 * 4 ?? + ast_peci_write(PECI_TIMING_MESSAGE(64) | PECI_TIMING_ADDRESS(64), AST_PECI_TIMING); + + + //PECI Programmable AWFCS + //ast_peci_write(ast_peci, PECI_PROGRAM_AW_FCS, AST_PECI_EXP_FCS); + + //TODO ..... + //Clear Interrupt + ast_peci_write(PECI_INT_TIMEOUT | PECI_INT_CONNECT | + PECI_INT_W_FCS_BAD | PECI_INT_W_FCS_ABORT | + PECI_INT_CMD_DONE, AST_PECI_INT_STS); + + //PECI Negotiation Selection , interrupt enable + //Set nego mode : 1st bit of addr negotiation + ast_peci_write(PECI_INT_TIMEOUT | PECI_INT_CONNECT | + PECI_INT_W_FCS_BAD | PECI_INT_W_FCS_ABORT | + PECI_INT_CMD_DONE, AST_PECI_INT_CTRL); + + //PECI Spec wide speed rangs [2kbps~2Mbps] + //Sampling 8/16, READ mode : Point Sampling , CLK source : 24Mhz , DIV by 8 : 3 --> CLK is 3Mhz + //PECI CTRL Enable + + ast_peci_write(PECI_CTRL_SAMPLING(8) | PECI_CTRL_CLK_DIV(3) | + PECI_CTRL_PECI_EN | + PECI_CTRL_PECI_CLK_EN, AST_PECI_CTRL); +} + +static const struct file_operations ast_peci_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = ast_peci_ioctl, + .open = ast_peci_open, + .release = ast_peci_release, +}; + +struct miscdevice ast_peci_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ast-peci", + .fops = &ast_peci_fops, +}; + +static int ast_peci_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret=0; + + + PECI_DBG("ast_peci_probe\n"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (NULL == res) { + dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n"); + ret = -ENOENT; + goto out; + } + + if (!request_mem_region(res->start, resource_size(res), res->name)) { + dev_err(&pdev->dev, "cannot reserved region\n"); + ret = -ENXIO; + goto out; + } + + ast_peci.reg_base = ioremap(res->start, resource_size(res)); + if (!ast_peci.reg_base) { + ret = -EIO; + goto out_region; + } + + ast_peci.irq = platform_get_irq(pdev, 0); + if (ast_peci.irq < 0) { + dev_err(&pdev->dev, "no irq specified\n"); + ret = -ENOENT; + goto out_region; + } + + ret = request_irq(ast_peci.irq, ast_peci_handler, IRQF_SHARED, + "ast-peci", &ast_peci); + + if (ret) { + printk(KERN_INFO "PECI: Failed request irq %d\n", ast_peci.irq); + goto out_region; + } + + ret = misc_register(&ast_peci_misc); + if (ret){ + printk(KERN_ERR "PECI : failed to request interrupt\n"); + goto out_irq; + } + + ast_peci_ctrl_init(); + + printk(KERN_INFO "ast_peci: driver successfully loaded.\n"); + + return 0; + + +out_irq: + free_irq(ast_peci.irq, NULL); +out_region: + release_mem_region(res->start, res->end - res->start + 1); +out: + printk(KERN_WARNING "applesmc: driver init failed (ret=%d)!\n", ret); + return ret; +} + +static int ast_peci_remove(struct platform_device *pdev) +{ + struct resource *res; + + PECI_DBG("ast_peci_remove\n"); + + misc_deregister(&ast_peci_misc); + + free_irq(ast_peci.irq, &ast_peci); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + iounmap(ast_peci.reg_base); + + release_mem_region(res->start, res->end - res->start + 1); + + return 0; +} + +#ifdef CONFIG_PM +static int +ast_peci_suspend(struct platform_device *pdev, pm_message_t state) +{ + printk("ast_peci_suspend : TODO \n"); + return 0; +} + +static int +ast_peci_resume(struct platform_device *pdev) +{ + ast_peci_ctrl_init(); + return 0; +} + +#else +#define ast_peci_suspend NULL +#define ast_peci_resume NULL +#endif + +static struct platform_driver ast_peci_driver = { + .probe = ast_peci_probe, + .remove = __devexit_p(ast_peci_remove), + .suspend = ast_peci_suspend, + .resume = ast_peci_resume, + .driver = { + .name = "ast_peci", + .owner = THIS_MODULE, + }, +}; + +static int __init +ast_peci_init(void) +{ + return platform_driver_register(&ast_peci_driver); +} + +static void __exit +ast_peci_exit(void) +{ + platform_driver_unregister(&ast_peci_driver); +} + +module_init(ast_peci_init); +module_exit(ast_peci_exit); + +MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>"); +MODULE_DESCRIPTION("PECI driver"); +MODULE_LICENSE("GPL"); + |