summaryrefslogtreecommitdiffstats
path: root/drivers/char/aspeed
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/aspeed')
-rw-r--r--drivers/char/aspeed/Kconfig54
-rw-r--r--drivers/char/aspeed/Makefile9
-rw-r--r--drivers/char/aspeed/ast_kcs.c446
-rw-r--r--drivers/char/aspeed/ast_peci.c508
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");
+
OpenPOWER on IntegriCloud